group-feed: improve performance and make mobile load

This commit is contained in:
Logan Allen 2021-03-26 16:52:50 -05:00
parent b7e52edd58
commit 2dddf1b837
12 changed files with 322 additions and 294 deletions

View File

@ -0,0 +1,20 @@
import { memo } from 'react';
const withMemo = (Component, checkedProps) => {
function areEqual(prevProps, nextProps) {
let isEqual = true;
for (let i = 0; i < checkedProps.length; i++) {
const checkedProp = checkedProps[i];
console.log(checkedProp);
if (JSON.stringify(prevProps[checkedProp]) !== JSON.stringify(nextProps[checkedProp])) {
isEqual = false;
break;
}
}
return isEqual;
}
return memo(Component, areEqual);
};
export default withMemo;

View File

@ -210,7 +210,6 @@ export function GroupsPane(props: GroupsPaneProps) {
associations={associations} associations={associations}
groups={groups} groups={groups}
groupPath={groupPath} groupPath={groupPath}
contacts={contacts}
workspace={workspace} workspace={workspace}
/> />
{popovers(routeProps, baseUrl)} {popovers(routeProps, baseUrl)}

View File

@ -6,8 +6,8 @@ import { Col } from '@tlon/indigo-react'
import { resourceFromPath } from '~/logic/lib/group'; import { resourceFromPath } from '~/logic/lib/group';
import useGraphState from '~/logic/state/graph'; import useGraphState from '~/logic/state/graph';
import { GroupFeedHeader } from './GroupFeedHeader'; import { GroupFeedHeader } from './GroupFeedHeader';
import { PostTimeline } from './Post/PostTimeline'; import PostTimeline from './Post/PostTimeline';
import { PostReplies } from './Post/PostReplies'; import PostReplies from './Post/PostReplies';
export function GroupFeed(props) { export function GroupFeed(props) {
@ -16,7 +16,7 @@ export function GroupFeed(props) {
api, api,
history, history,
associations, associations,
graphPath graphPath,
} = props; } = props;
const graphs = useGraphState(state => state.graphs); const graphs = useGraphState(state => state.graphs);
const graphResource = resourceFromPath(graphPath); const graphResource = resourceFromPath(graphPath);
@ -29,6 +29,9 @@ export function GroupFeed(props) {
const relativePath = (path) => baseUrl + path; const relativePath = (path) => baseUrl + path;
const association = associations.graph[graphPath]; const association = associations.graph[graphPath];
const graphId = `${graphResource.ship.slice(1)}/${graphResource.name}`;
const graph = graphs[graphId];
useEffect(() => { useEffect(() => {
// TODO: VirtualScroller should support lower starting values than 100 // TODO: VirtualScroller should support lower starting values than 100
api.graph.getNewest(graphResource.ship, graphResource.name, 100); api.graph.getNewest(graphResource.ship, graphResource.name, 100);
@ -53,11 +56,13 @@ export function GroupFeed(props) {
render={(routeProps) => { render={(routeProps) => {
return ( return (
<PostTimeline <PostTimeline
{...props} baseUrl={baseUrl}
api={api}
history={history}
graphPath={graphPath}
association={association} association={association}
graphs={graphs} graph={graph}
pendingSize={pendingSize} pendingSize={pendingSize} />
graphResource={graphResource} />
); );
}} /> }} />
<Route <Route
@ -65,11 +70,13 @@ export function GroupFeed(props) {
render={(routeProps) => { render={(routeProps) => {
return ( return (
<PostReplies <PostReplies
{...props} baseUrl={baseUrl}
api={api}
history={history}
graphPath={graphPath}
association={association} association={association}
graphs={graphs} graph={graph}
pendingSize={pendingSize} pendingSize={pendingSize} />
graphResource={graphResource} />
); );
}} /> }} />
</Switch> </Switch>

View File

@ -15,8 +15,7 @@ export function GroupHome(props) {
groupPath, groupPath,
groups, groups,
graphs, graphs,
baseUrl, baseUrl
contacts,
} = props; } = props;
const metadata = associations?.groups[groupPath]?.metadata; const metadata = associations?.groups[groupPath]?.metadata;
@ -59,7 +58,6 @@ export function GroupHome(props) {
<GroupFeed <GroupFeed
associations={associations} associations={associations}
groups={groups} groups={groups}
contacts={contacts}
graphPath={graphPath} graphPath={graphPath}
graphs={graphs} graphs={graphs}
api={api} api={api}

View File

@ -1,10 +1,13 @@
import React from 'react'; import React from 'react';
import { Col } from '@tlon/indigo-react'; import { Col } from '@tlon/indigo-react';
import { MentionText } from '~/views/components/MentionText'; import { MentionText } from '~/views/components/MentionText';
import useContactState from '~/logic/state/contact';
export function PostContent(props) { export function PostContent(props) {
const { post, contacts, isParent, api } = props; const { post, isParent, api } = props;
const contacts = useContactState(state => state.contacts);
return ( return (
<Col <Col
width="100%" width="100%"

View File

@ -3,13 +3,13 @@ import bigInt from 'big-integer';
import VirtualScroller from "~/views/components/VirtualScroller"; import VirtualScroller from "~/views/components/VirtualScroller";
import PostItem from './PostItem'; import PostItem from './PostItem';
import { Col } from '@tlon/indigo-react'; import { Col } from '@tlon/indigo-react';
import { resourceFromPath } from '~/logic/lib/group';
const virtualScrollerStyle = { const virtualScrollerStyle = {
height: "100%" height: "100%"
}; };
export class PostFeed extends React.Component { export class PostFeed extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -18,14 +18,14 @@ export class PostFeed extends React.Component {
this.renderItem = React.forwardRef(({ index, scrollWindow }, ref) => { this.renderItem = React.forwardRef(({ index, scrollWindow }, ref) => {
const { const {
graph, graph,
graphResource, graphPath,
contacts,
api, api,
history, history,
baseUrl, baseUrl,
parentNode, parentNode,
association association
} = this.props; } = this.props;
const graphResource = resourceFromPath(graphPath);
const node = graph.get(index); const node = graph.get(index);
if (!node) { return null; } if (!node) { return null; }
@ -39,7 +39,6 @@ export class PostFeed extends React.Component {
}) : []; }) : [];
if (parentNode && index.eq(first ?? bigInt.zero)) { if (parentNode && index.eq(first ?? bigInt.zero)) {
return ( return (
<React.Fragment key={index.toString()}> <React.Fragment key={index.toString()}>
<Col <Col
@ -52,8 +51,7 @@ export class PostFeed extends React.Component {
key={parentNode.post.index} key={parentNode.post.index}
ref={ref} ref={ref}
node={parentNode} node={parentNode}
contacts={contacts} graphPath={graphPath}
graphResource={graphResource}
association={association} association={association}
api={api} api={api}
index={nodeIndex} index={nodeIndex}
@ -65,8 +63,7 @@ export class PostFeed extends React.Component {
<PostItem <PostItem
ref={ref} ref={ref}
node={node} node={node}
contacts={contacts} graphPath={graphPath}
graphResource={graphResource}
association={association} association={association}
api={api} api={api}
index={[...nodeIndex, index]} index={[...nodeIndex, index]}
@ -84,8 +81,7 @@ export class PostFeed extends React.Component {
key={index.toString()} key={index.toString()}
ref={ref} ref={ref}
node={node} node={node}
contacts={contacts} graphPath={graphPath}
graphResource={graphResource}
association={association} association={association}
api={api} api={api}
index={[...nodeIndex, index]} index={[...nodeIndex, index]}
@ -102,7 +98,8 @@ export class PostFeed extends React.Component {
} }
async fetchPosts(newer) { async fetchPosts(newer) {
const { graph, graphResource, api } = this.props; const { graph, graphPath, api } = this.props;
const graphResource = resourceFromPath(graphPath);
if (this.isFetching) { if (this.isFetching) {
return false; return false;
@ -134,11 +131,12 @@ export class PostFeed extends React.Component {
} }
render() { render() {
const { graph, pendingSize, parentNode } = this.props; const { graph, pendingSize, parentNode, history } = this.props;
return ( return (
<Col width="100%" height="100%" position="relative"> <Col width="100%" height="100%" position="relative">
<VirtualScroller <VirtualScroller
key={history.location.pathname}
origin="top" origin="top"
offset={0} offset={0}
data={graph} data={graph}

View File

@ -4,11 +4,12 @@ import Author from '~/views/components/Author';
import { useCopy } from '~/logic/lib/useCopy'; import { useCopy } from '~/logic/lib/useCopy';
import { getPermalinkForGraph } from '~/logic/lib/permalinks'; import { getPermalinkForGraph } from '~/logic/lib/permalinks';
import { Dropdown } from '~/views/components/Dropdown'; import { Dropdown } from '~/views/components/Dropdown';
import useContactState from '~/logic/state/contact';
export function PostHeader(props) { export function PostHeader(props) {
const { post, contacts, api, association, isReply } = props; const { post, api, association, isReply } = props;
const contacts = useContactState(state => state.contacts);
const mb = isReply ? "2" : "3"; const mb = isReply ? "2" : "3";
const permalink = !!association ? getPermalinkForGraph( const permalink = !!association ? getPermalinkForGraph(

View File

@ -7,10 +7,12 @@ import tokenizeMessage from '~/logic/lib/tokenizeMessage';
import { useToggleState } from '~/logic/lib/useToggleState'; import { useToggleState } from '~/logic/lib/useToggleState';
import { createPost } from '~/logic/api/graph'; import { createPost } from '~/logic/api/graph';
import useStorage from '~/logic/lib/useStorage'; import useStorage from '~/logic/lib/useStorage';
import { resourceFromPath } from '~/logic/lib/group';
export function PostInput(props) { export function PostInput(props) {
const { api, graphResource, index, submitCallback } = props; const { api, graphPath, index, submitCallback } = props;
const graphResource = resourceFromPath(graphPath);
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
const [code, toggleCode] = useToggleState(false); const [code, toggleCode] = useToggleState(false);

View File

@ -7,6 +7,7 @@ import { PostInput } from './PostInput';
import { Mention } from "~/views/components/MentionText"; import { Mention } from "~/views/components/MentionText";
import withState from '~/logic/lib/withState'; import withState from '~/logic/lib/withState';
import { useHovering } from '~/logic/lib/util'; import { useHovering } from '~/logic/lib/util';
import { resourceFromPath } from '~/logic/lib/group';
class PostItem extends React.Component { class PostItem extends React.Component {
@ -43,9 +44,8 @@ class PostItem extends React.Component {
render() { render() {
const { const {
node, node,
contacts,
api, api,
graphResource, graphPath,
association, association,
index, index,
innerRef, innerRef,
@ -55,6 +55,7 @@ class PostItem extends React.Component {
hovering, hovering,
bind bind
} = this.props; } = this.props;
const graphResource = resourceFromPath(graphPath);
let indexString = ''; let indexString = '';
@ -113,7 +114,7 @@ class PostItem extends React.Component {
borderLeftColor="lightGray"></Box> borderLeftColor="lightGray"></Box>
<PostInput <PostInput
api={api} api={api}
graphResource={graphResource} graphPath={graphPath}
index={indexString} index={indexString}
submitCallback={this.submitCallback} /> submitCallback={this.submitCallback} />
</Col> </Col>

View File

@ -5,23 +5,30 @@ import { PostInput } from './PostInput';
import PostItem from './PostItem'; import PostItem from './PostItem';
import { PostFeed } from './PostFeed'; import { PostFeed } from './PostFeed';
import { Loading } from '~/views/components/Loading'; import { Loading } from '~/views/components/Loading';
import { resourceFromPath } from '~/logic/lib/group';
export function PostReplies(props) { export default class PostReplies extends React.PureComponent {
constructor(props) {
super(props);
this.whyDidYouRender = true;
}
render() {
let graph = this.props.graph;
const { const {
baseUrl, baseUrl,
api, api,
history, history,
association, association,
groups, groups,
contacts,
graphPath, graphPath,
graphs, pendingSize
pendingSize, } = this.props;
graphResource
} = props; const graphResource = resourceFromPath(graphPath);
const graphId = `${graphResource.ship.slice(1)}/${graphResource.name}`; const graphId = `${graphResource.ship.slice(1)}/${graphResource.name}`;
const shouldRenderFeed = graphId in graphs; const shouldRenderFeed = !!graph;
if (!shouldRenderFeed) { if (!shouldRenderFeed) {
return ( return (
@ -38,7 +45,6 @@ export function PostReplies(props) {
}); });
let node; let node;
let graph = graphs[graphId];
nodeIndex.forEach((i) => { nodeIndex.forEach((i) => {
if (!graph) { if (!graph) {
return null; return null;
@ -66,8 +72,7 @@ export function PostReplies(props) {
<PostItem <PostItem
key={node.post.index} key={node.post.index}
node={node} node={node}
contacts={contacts} graphPath={graphPath}
graphResource={graphResource}
association={association} association={association}
api={api} api={api}
index={nodeIndex} index={nodeIndex}
@ -96,7 +101,7 @@ export function PostReplies(props) {
<Box height="calc(100% - 48px)" width="100%" alignItems="center" pl="1" pt="3"> <Box height="calc(100% - 48px)" width="100%" alignItems="center" pl="1" pt="3">
<PostFeed <PostFeed
key={locationUrl} key={locationUrl}
graphResource={graphResource} graphPath={graphPath}
graph={graph} graph={graph}
parentNode={node} parentNode={node}
pendingSize={pendingSize} pendingSize={pendingSize}
@ -110,4 +115,5 @@ export function PostReplies(props) {
</Box> </Box>
); );
} }
}

View File

@ -3,23 +3,27 @@ import { Text, Col, Box } from '@tlon/indigo-react'
import { PostInput } from './PostInput'; import { PostInput } from './PostInput';
import { PostFeed } from './PostFeed'; import { PostFeed } from './PostFeed';
import { Loading } from '~/views/components/Loading'; import { Loading } from '~/views/components/Loading';
import { resourceFromPath } from '~/logic/lib/group';
export function PostTimeline(props) { export default class PostTimeline extends React.PureComponent {
constructor(props) {
super(props);
this.whyDidYouRender = true;
}
render() {
const { const {
baseUrl, baseUrl,
api, api,
history, history,
association, association,
groups,
contacts,
graphPath, graphPath,
graphs, graph,
pendingSize, pendingSize,
graphResource } = this.props;
} = props; const graphResource = resourceFromPath(graphPath);
const graphId = `${graphResource.ship.slice(1)}/${graphResource.name}`; const shouldRenderFeed = !!graph;
const shouldRenderFeed = graphId in graphs;
if (!shouldRenderFeed) { if (!shouldRenderFeed) {
return ( return (
@ -29,7 +33,6 @@ export function PostTimeline(props) {
); );
} }
const graph = graphs[graphId];
const first = graph.peekLargest()?.[0]; const first = graph.peekLargest()?.[0];
if (!first) { if (!first) {
return ( return (
@ -48,7 +51,7 @@ export function PostTimeline(props) {
alignItems="center"> alignItems="center">
<PostInput <PostInput
api={api} api={api}
graphResource={graphResource} /> graphPath={graphPath} />
</Col> </Col>
<Box <Box
pl="2" pl="2"
@ -77,17 +80,15 @@ export function PostTimeline(props) {
mb="3" mb="3"
flexDirection="column" flexDirection="column"
alignItems="center"> alignItems="center">
<PostInput api={api} graphResource={graphResource} /> <PostInput api={api} graphPath={graphPath} />
</Box> </Box>
<Box height="calc(100% - 176px)" width="100%" alignItems="center" pl="1"> <Box height="calc(100% - 176px)" width="100%" alignItems="center" pl="1">
<PostFeed <PostFeed
key={graphPath} key={graphPath}
graphResource={graphResource} graphPath={graphPath}
graph={graphs[graphId]} graph={graph}
pendingSize={pendingSize} pendingSize={pendingSize}
association={association} association={association}
groups={groups}
contacts={contacts}
api={api} api={api}
history={history} history={history}
baseUrl={baseUrl} baseUrl={baseUrl}
@ -96,4 +97,5 @@ export function PostTimeline(props) {
</> </>
); );
} }
}

View File

@ -21,6 +21,7 @@ import useGraphState from '~/logic/state/graph';
import useHarkState, { withHarkState } from '~/logic/state/hark'; import useHarkState, { withHarkState } from '~/logic/state/hark';
import withState from '~/logic/lib/withState'; import withState from '~/logic/lib/withState';
type LandscapeProps = StoreState & { type LandscapeProps = StoreState & {
ship: PatpNoSig; ship: PatpNoSig;
api: GlobalApi; api: GlobalApi;
@ -67,14 +68,8 @@ export function DMRedirect(props: LandscapeProps & RouteComponentProps & { ship:
); );
} }
class Landscape extends Component<LandscapeProps, Record<string, never>> { export default function Landscape(props) {
componentDidMount(): void { const notificationsCount = useHarkState(s => s.notificationsCount);
this.props.subscription.startApp('groups');
this.props.subscription.startApp('graph');
}
render(): ReactElement {
const { props } = this;
return ( return (
<> <>
@ -154,8 +149,4 @@ class Landscape extends Component<LandscapeProps, Record<string, never>> {
</> </>
); );
} }
}
export default withState(Landscape, [
[useHarkState, ['notificationsCount']]
]);