Browse Source

Improve topics list UI

develop
Apostolos Fanakis 4 years ago
parent
commit
a17b316845
  1. 1
      packages/concordia-app/package.json
  2. 5
      packages/concordia-app/public/locales/en/translation.json
  3. 86
      packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx
  4. 8
      packages/concordia-app/src/components/TopicList/TopicListRow/styles.css
  5. 29
      packages/concordia-app/src/components/TopicList/index.jsx
  6. 4
      packages/concordia-app/src/components/TopicList/styles.css
  7. 5
      yarn.lock

1
packages/concordia-app/package.json

@ -34,6 +34,7 @@
"i18next-http-backend": "^1.0.21", "i18next-http-backend": "^1.0.21",
"level": "~6.0.1", "level": "~6.0.1",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"moment": "^2.29.1",
"orbit-db-identity-provider": "~0.3.1", "orbit-db-identity-provider": "~0.3.1",
"prop-types": "~15.7.2", "prop-types": "~15.7.2",
"react": "~16.13.1", "react": "~16.13.1",

5
packages/concordia-app/public/locales/en/translation.json

@ -19,5 +19,8 @@
"topic.create.form.subject.field.placeholder": "Subject", "topic.create.form.subject.field.placeholder": "Subject",
"topic.create.form.message.field.label": "First post message", "topic.create.form.message.field.label": "First post message",
"topic.create.form.message.field.placeholder": "Message", "topic.create.form.message.field.placeholder": "Message",
"topic.create.form.post.button": "Post" "topic.create.form.post.button": "Post",
"topic.list.row.author.date": "Posted by {{author}}, {{timeAgo}}",
"topic.list.row.number.of.replies": "{{numberOfReplies}} replies",
"topic.list.row.topic.id": "#{{id}}"
} }

86
packages/concordia-app/src/components/TopicList/TopicListRow/index.jsx

@ -1,34 +1,42 @@
import React, { import React, {
memo, useEffect, useMemo, useState, memo, useEffect, useMemo, useState,
} from 'react'; } from 'react';
import { List } from 'semantic-ui-react'; import {
Dimmer, Grid, List, Loader, Placeholder,
} from 'semantic-ui-react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import moment from 'moment';
import { useHistory } from 'react-router';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions'; import { FETCH_USER_DATABASE } from '../../../redux/actions/peerDbReplicationActions';
import { breeze } from '../../../redux/store'; import { breeze } from '../../../redux/store';
import './styles.css';
const { orbit } = breeze; const { orbit } = breeze;
const TopicListRow = (props) => { const TopicListRow = (props) => {
const { id: topicId, topicCallHash } = props; const { id: topicId, topicCallHash, loading } = props;
const getTopicResults = useSelector((state) => state.contracts.Forum.getTopic); const getTopicResults = useSelector((state) => state.contracts.Forum.getTopic);
const [numberOfReplies, setNumberOfReplies] = useState(null); const [numberOfReplies, setNumberOfReplies] = useState(null);
const [username, setUsername] = useState(null); const [username, setUsername] = useState(null);
const [topicAuthor, setTopicAuthor] = useState(null); const [topicAuthor, setTopicAuthor] = useState(null);
const [timestamp, setTimestamp] = useState(null); const [timeAgo, setTimeAgo] = useState(null);
const [topicSubject, setTopicSubject] = useState(null); const [topicSubject, setTopicSubject] = useState(null);
const userAddress = useSelector((state) => state.user.address); const userAddress = useSelector((state) => state.user.address);
const topics = useSelector((state) => state.orbitData.topics); const topics = useSelector((state) => state.orbitData.topics);
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory();
const { t } = useTranslation();
useEffect(() => { useEffect(() => {
if (topicCallHash && getTopicResults[topicCallHash] !== undefined) { if (!loading && topicCallHash && getTopicResults[topicCallHash] !== undefined) {
setTopicAuthor(getTopicResults[topicCallHash].value[0]); setTopicAuthor(getTopicResults[topicCallHash].value[0]);
setUsername(getTopicResults[topicCallHash].value[1]); setUsername(getTopicResults[topicCallHash].value[1]);
setTimestamp(getTopicResults[topicCallHash].value[2] * 1000); setTimeAgo(moment(getTopicResults[topicCallHash].value[2] * 1000).fromNow());
setNumberOfReplies(getTopicResults[topicCallHash].value[3].length); setNumberOfReplies(getTopicResults[topicCallHash].value[3].length);
} }
}, [getTopicResults, topicCallHash]); }, [getTopicResults, loading, topicCallHash]);
useEffect(() => { useEffect(() => {
if (topicAuthor && userAddress !== topicAuthor) { if (topicAuthor && userAddress !== topicAuthor) {
@ -45,30 +53,68 @@ const TopicListRow = (props) => {
.find((topic) => topic.id === topicId); .find((topic) => topic.id === topicId);
if (topicFound) { if (topicFound) {
setTopicSubject(topicFound); setTopicSubject(topicFound.subject);
} }
}, [topicId, topics]); }, [topicId, topics]);
return useMemo(() => ( return useMemo(() => {
<> const handleTopicClick = () => {
history.push(`/topics/${topicId}`);
};
return (
<Dimmer.Dimmable as={List.Item} onClick={handleTopicClick} blurring dimmed={loading} className="list-item">
<Dimmer>
<Loader />
</Dimmer>
<List.Icon name="user circle" size="big" inverted color="black" verticalAlign="middle" />
<List.Content>
<List.Header> <List.Header>
<List.Icon name="right triangle" /> <Grid>
{topicSubject && topicSubject.subject} <Grid.Column floated="left" width={14}>
{topicSubject !== null
? topicSubject
: <Placeholder><Placeholder.Line length="very long" /></Placeholder>}
</Grid.Column>
<Grid.Column floated="right" width={2} textAlign="right">
<span className="topic-metadata">
{t('topic.list.row.topic.id', { id: topicId })}
</span>
</Grid.Column>
</Grid>
</List.Header> </List.Header>
<List.Content> <List.Description>
{username} <Grid verticalAlign="middle">
{numberOfReplies} <Grid.Column floated="left" width={14}>
{' '} {username !== null && timeAgo !== null
replies ? t('topic.list.row.author.date', { author: username, timeAgo })
{timestamp} : <Placeholder><Placeholder.Line length="long" /></Placeholder>}
</Grid.Column>
<Grid.Column floated="right" width={2} textAlign="right">
{numberOfReplies !== null
? (
<span className="topic-metadata">
{t('topic.list.row.number.of.replies', { numberOfReplies })}
</span>
)
: <Placeholder fluid><Placeholder.Line /></Placeholder>}
</Grid.Column>
</Grid>
</List.Description>
</List.Content> </List.Content>
</> </Dimmer.Dimmable>
), [topicSubject, username, numberOfReplies, timestamp]); );
}, [history, loading, numberOfReplies, t, timeAgo, topicId, topicSubject, username]);
};
TopicListRow.defaultProps = {
loading: false,
}; };
TopicListRow.propTypes = { TopicListRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
topicCallHash: PropTypes.string.isRequired, topicCallHash: PropTypes.string,
loading: PropTypes.bool,
}; };
TopicListRow.whyDidYouRender = true; TopicListRow.whyDidYouRender = true;

8
packages/concordia-app/src/components/TopicList/TopicListRow/styles.css

@ -0,0 +1,8 @@
.topic-metadata {
font-size: 12px !important;
font-weight: initial;
}
.list-item {
text-align: start;
}

29
packages/concordia-app/src/components/TopicList/index.jsx

@ -4,11 +4,7 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { List } from 'semantic-ui-react'; import { List } from 'semantic-ui-react';
import { useHistory } from 'react-router';
import TopicListRow from './TopicListRow'; import TopicListRow from './TopicListRow';
import { PLACEHOLDER_TYPE_TOPIC } from '../../constants/PlaceholderTypes';
import Placeholder from '../Placeholder';
import './styles.css';
import { drizzle } from '../../redux/store'; import { drizzle } from '../../redux/store';
const { contracts: { Forum: { methods: { getTopic: { cacheCall: getTopicChainData } } } } } = drizzle; const { contracts: { Forum: { methods: { getTopic: { cacheCall: getTopicChainData } } } } } = drizzle;
@ -18,7 +14,6 @@ const TopicList = (props) => {
const [getTopicCallHashes, setGetTopicCallHashes] = useState([]); const [getTopicCallHashes, setGetTopicCallHashes] = useState([]);
const drizzleInitialized = useSelector((state) => state.drizzleStatus.initialized); const drizzleInitialized = useSelector((state) => state.drizzleStatus.initialized);
const drizzleInitializationFailed = useSelector((state) => state.drizzleStatus.failed); const drizzleInitializationFailed = useSelector((state) => state.drizzleStatus.failed);
const history = useHistory();
useEffect(() => { useEffect(() => {
if (drizzleInitialized && !drizzleInitializationFailed) { if (drizzleInitialized && !drizzleInitializationFailed) {
@ -44,27 +39,15 @@ const TopicList = (props) => {
.map((topicId) => { .map((topicId) => {
const topicHash = getTopicCallHashes.find((getTopicCallHash) => getTopicCallHash.id === topicId); const topicHash = getTopicCallHashes.find((getTopicCallHash) => getTopicCallHash.id === topicId);
const handleTopicClick = () => {
history.push(`/topics/${topicId}`);
};
if (topicHash) {
return (
<List.Item key={topicId} className="list-item" name={topicId} onClick={handleTopicClick}>
<TopicListRow id={topicId} topicCallHash={topicHash.hash} />
</List.Item>
);
}
return ( return (
<List.Item key={topicId} className="list-item" name={topicId} onClick={() => handleTopicClick(topicId)}> <TopicListRow
<Placeholder id={topicId}
placeholderType={PLACEHOLDER_TYPE_TOPIC} key={topicId}
extra={{ topicId }} topicCallHash={topicHash && topicHash.hash}
loading={topicHash === undefined}
/> />
</List.Item>
); );
}), [getTopicCallHashes, history, topicIds]); }), [getTopicCallHashes, topicIds]);
return ( return (
<List selection divided id="topic-list" size="big"> <List selection divided id="topic-list" size="big">

4
packages/concordia-app/src/components/TopicList/styles.css

@ -1,7 +1,3 @@
#topic-list{ #topic-list{
height: 100%; height: 100%;
} }
.list-item {
text-align: start;
}

5
yarn.lock

@ -11942,6 +11942,11 @@ mock-fs@^4.1.0:
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.13.0.tgz#31c02263673ec3789f90eb7b6963676aa407a598" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.13.0.tgz#31c02263673ec3789f90eb7b6963676aa407a598"
integrity sha512-DD0vOdofJdoaRNtnWcrXe6RQbpHkPPmtqGq14uRX0F8ZKJ5nv89CVTYl/BZdppDxBDaV0hl75htg3abpEWlPZA== integrity sha512-DD0vOdofJdoaRNtnWcrXe6RQbpHkPPmtqGq14uRX0F8ZKJ5nv89CVTYl/BZdppDxBDaV0hl75htg3abpEWlPZA==
moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
mortice@^2.0.0: mortice@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/mortice/-/mortice-2.0.0.tgz#7be171409c2115561ba3fc035e4527f9082eefde" resolved "https://registry.yarnpkg.com/mortice/-/mortice-2.0.0.tgz#7be171409c2115561ba3fc035e4527f9082eefde"

Loading…
Cancel
Save