From c039f1e142b2700133a5b5ff02434ef52bc0c546 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 26 Apr 2021 16:35:11 +1000 Subject: [PATCH 01/17] interface: remove unnecessary routing props --- pkg/interface/src/views/apps/chat/ChatResource.tsx | 9 +++++++-- pkg/interface/src/views/apps/links/LinkResource.tsx | 2 +- pkg/interface/src/views/apps/publish/PublishResource.tsx | 2 +- .../src/views/landscape/components/GroupsPane.tsx | 1 - .../src/views/landscape/components/Resource.tsx | 4 +++- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/ChatResource.tsx b/pkg/interface/src/views/apps/chat/ChatResource.tsx index e4f02840cd..8c4fafc69e 100644 --- a/pkg/interface/src/views/apps/chat/ChatResource.tsx +++ b/pkg/interface/src/views/apps/chat/ChatResource.tsx @@ -28,9 +28,9 @@ type ChatResourceProps = StoreState & { association: Association; api: GlobalApi; baseUrl: string; -} & RouteComponentProps; +}; -export function ChatResource(props: ChatResourceProps) { +function ChatResource(props: ChatResourceProps) { const station = props.association.resource; const groupPath = props.association.group; const groups = useGroupState(state => state.groups); @@ -196,3 +196,8 @@ export function ChatResource(props: ChatResourceProps) { ); } + +ChatResource.whyDidYouRender = true; + +export { ChatResource }; + diff --git a/pkg/interface/src/views/apps/links/LinkResource.tsx b/pkg/interface/src/views/apps/links/LinkResource.tsx index 09a4f388bf..605c6652c1 100644 --- a/pkg/interface/src/views/apps/links/LinkResource.tsx +++ b/pkg/interface/src/views/apps/links/LinkResource.tsx @@ -23,7 +23,7 @@ type LinkResourceProps = StoreState & { association: Association; api: GlobalApi; baseUrl: string; -} & RouteComponentProps; +}; export function LinkResource(props: LinkResourceProps) { const { diff --git a/pkg/interface/src/views/apps/publish/PublishResource.tsx b/pkg/interface/src/views/apps/publish/PublishResource.tsx index ca34b37fe6..2bb1320ea7 100644 --- a/pkg/interface/src/views/apps/publish/PublishResource.tsx +++ b/pkg/interface/src/views/apps/publish/PublishResource.tsx @@ -11,7 +11,7 @@ type PublishResourceProps = StoreState & { association: Association; api: GlobalApi; baseUrl: string; -} & RouteComponentProps; +}; export function PublishResource(props: PublishResourceProps) { const { association, api, baseUrl, notebooks } = props; diff --git a/pkg/interface/src/views/landscape/components/GroupsPane.tsx b/pkg/interface/src/views/landscape/components/GroupsPane.tsx index e284865c5e..568d8b1d85 100644 --- a/pkg/interface/src/views/landscape/components/GroupsPane.tsx +++ b/pkg/interface/src/views/landscape/components/GroupsPane.tsx @@ -124,7 +124,6 @@ export function GroupsPane(props: GroupsPaneProps) { > diff --git a/pkg/interface/src/views/landscape/components/Resource.tsx b/pkg/interface/src/views/landscape/components/Resource.tsx index 5ee47e34eb..0b29e2f909 100644 --- a/pkg/interface/src/views/landscape/components/Resource.tsx +++ b/pkg/interface/src/views/landscape/components/Resource.tsx @@ -15,12 +15,14 @@ import useGroupState from '~/logic/state/group'; import useContactState from '~/logic/state/contact'; import useHarkState from '~/logic/state/hark'; import useMetadataState from '~/logic/state/metadata'; +import {Workspace} from '~/types'; type ResourceProps = StoreState & { association: Association; api: GlobalApi; baseUrl: string; -} & RouteComponentProps; + workspace: Workspace; +}; export function Resource(props: ResourceProps): ReactElement { const { association, api, notificationsGraphConfig } = props; From c1f055d46e7395949254dddac7d1a42c01cf336d Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 26 Apr 2021 16:59:38 +1000 Subject: [PATCH 02/17] interface: make BigIntOrderedMap immutable --- .../src/logic/reducers/graph-update.ts | 59 +++++++++---------- .../src/logic/reducers/hark-update.ts | 8 +-- pkg/npm/api/lib/BigIntOrderedMap.ts | 57 +++++++++++------- 3 files changed, 67 insertions(+), 57 deletions(-) diff --git a/pkg/interface/src/logic/reducers/graph-update.ts b/pkg/interface/src/logic/reducers/graph-update.ts index 50c310af04..a37b918c96 100644 --- a/pkg/interface/src/logic/reducers/graph-update.ts +++ b/pkg/interface/src/logic/reducers/graph-update.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap"; +import produce from 'immer'; import bigInt, { BigInteger } from "big-integer"; import useGraphState, { GraphState } from '../state/graph'; import { reduceState } from '../state/base'; @@ -85,17 +86,9 @@ const addGraph = (json, state: GraphState): GraphState => { state.graphTimesentMap[resource] = {}; - for (let idx in data.graph) { - let item = data.graph[idx]; - let index = bigInt(idx); - - let node = processNode(item); - - state.graphs[resource].set( - index, - node - ); - } + state.graphs[resource] = state.graphs[resource].gas(Object.keys(data.graph).map(idx => { + return [bigInt(idx), processNode(data.graph[idx])]; + })); state.graphKeys.add(resource); } return state; @@ -128,8 +121,7 @@ const addNodes = (json, state) => { const _addNode = (graph, index, node) => { // set child of graph if (index.length === 1) { - graph.set(index[0], node); - return graph; + return graph.set(index[0], node); } // set parent of graph @@ -138,19 +130,20 @@ const addNodes = (json, state) => { console.error('parent node does not exist, cannot add child'); return graph; } - parNode.children = _addNode(parNode.children, index.slice(1), node); - graph.set(index[0], parNode); - return graph; + return graph.set(index[0], produce(parNode, draft => { + draft.children = _addNode(draft.children, index.slice(1), node); + })); }; const _remove = (graph, index) => { if (index.length === 1) { - graph.delete(index[0]); + return graph.delete(index[0]); } else { const child = graph.get(index[0]); if (child) { - child.children = _remove(child.children, index.slice(1)); - graph.set(index[0], child); + return graph.set(index[0], produce(child, draft => { + draft.children = _remove(draft.children, index.slice(1)); + })); } } @@ -166,10 +159,9 @@ const addNodes = (json, state) => { return bigInt(ind); }); - graph = _remove(graph, indexArr); delete state.graphTimesentMap[resource][timestamp]; + return _remove(graph, indexArr); } - return graph; }; @@ -208,11 +200,12 @@ const addNodes = (json, state) => { return aArr.length - bArr.length; }); - let graph = state.graphs[resource]; - indices.forEach((index) => { let node = data.nodes[index]; - graph = _removePending(graph, node.post, resource); + const old = state.graphs[resource].size; + state.graphs[resource] = _removePending(state.graphs[resource], node.post, resource); + const newSize = state.graphs[resource].size; + if (index.split('/').length === 0) { return; } let indexArr = index.split('/').slice(1).map((ind) => { @@ -227,15 +220,17 @@ const addNodes = (json, state) => { node.children = mapifyChildren(node?.children || {}); - graph = _addNode( - graph, + state.graphs[resource] = _addNode( + state.graphs[resource], indexArr, node ); + if(newSize !== old) { + console.log(`${resource}, (${old}, ${newSize}, ${state.graphs[resource].size})`); + } }); - state.graphs[resource] = graph; } return state; }; @@ -243,13 +238,15 @@ const addNodes = (json, state) => { const removeNodes = (json, state: GraphState): GraphState => { const _remove = (graph, index) => { if (index.length === 1) { - graph.delete(index[0]); + return graph.delete(index[0]); } else { const child = graph.get(index[0]); if (child) { - _remove(child.children, index.slice(1)); - graph.set(index[0], child); + return graph.set(index[0], produce(draft => { + draft.children = _remove(draft.children, index.slice(1)) + })); } + return graph; } }; @@ -264,7 +261,7 @@ const removeNodes = (json, state: GraphState): GraphState => { let indexArr = index.split('/').slice(1).map((ind) => { return bigInt(ind); }); - _remove(state.graphs[res], indexArr); + state.graphs[res] = _remove(state.graphs[res], indexArr); }); } return state; diff --git a/pkg/interface/src/logic/reducers/hark-update.ts b/pkg/interface/src/logic/reducers/hark-update.ts index 66392fb8da..c83746b12a 100644 --- a/pkg/interface/src/logic/reducers/hark-update.ts +++ b/pkg/interface/src/logic/reducers/hark-update.ts @@ -329,9 +329,9 @@ function added(json: any, state: HarkState): HarkState { ); if (arrIdx !== -1) { timebox[arrIdx] = { index, notification }; - state.notifications.set(time, timebox); + state.notifications = state.notifications.set(time, timebox); } else { - state.notifications.set(time, [...timebox, { index, notification }]); + state.notifications = state.notifications.set(time, [...timebox, { index, notification }]); } } return state; @@ -350,7 +350,7 @@ const timebox = (json: any, state: HarkState): HarkState => { if (data) { const time = makePatDa(data.time); if (!data.archive) { - state.notifications.set(time, data.notifications); + state.notifications = state.notifications.set(time, data.notifications); } } return state; @@ -403,7 +403,7 @@ function setRead( return state; } timebox[arrIdx].notification.read = read; - state.notifications.set(patDa, timebox); + state.notifications = state.notifications.set(patDa, timebox); return state; } diff --git a/pkg/npm/api/lib/BigIntOrderedMap.ts b/pkg/npm/api/lib/BigIntOrderedMap.ts index 630445c2f6..c5e26da2cc 100644 --- a/pkg/npm/api/lib/BigIntOrderedMap.ts +++ b/pkg/npm/api/lib/BigIntOrderedMap.ts @@ -1,6 +1,10 @@ -import { immerable } from 'immer'; +import produce, { immerable, castImmutable, castDraft, setAutoFreeze, enablePatches } from 'immer'; import bigInt, { BigInteger } from "big-integer"; +setAutoFreeze(false); + +enablePatches(); + function sortBigInt(a: BigInteger, b: BigInteger) { if (a.lt(b)) { return 1; @@ -11,19 +15,18 @@ function sortBigInt(a: BigInteger, b: BigInteger) { } } export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { - private root: Record = {} - private cachedIter: [BigInteger, V][] | null = null; + root: Record = {} + cachedIter: [BigInteger, V][] = []; [immerable] = true; constructor(items: [BigInteger, V][] = []) { items.forEach(([key, val]) => { this.set(key, val); }); - this.generateCachedIter(); } get size() { - return this.cachedIter?.length ?? Object.keys(this.root).length; + return Object.keys(this.root).length; } @@ -31,14 +34,30 @@ export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { return this.root[key.toString()] ?? null; } + gas(items: [BigInteger, V][]) { + return produce(this, draft => { + items.forEach(([key, value]) => { + draft.root[key.toString()] = castDraft(value); + }); + draft.generateCachedIter(); + }, + (patches) => { + //console.log(`gassed with ${JSON.stringify(patches, null, 2)}`); + }); + } + set(key: BigInteger, value: V) { - this.root[key.toString()] = value; - this.cachedIter = null; + return produce(this, draft => { + draft.root[key.toString()] = castDraft(value); + draft.generateCachedIter(); + }); } clear() { - this.cachedIter = null; - this.root = {} + return produce(this, draft => { + draft.cachedIter = []; + draft.root = {} + }); } has(key: BigInteger) { @@ -46,17 +65,15 @@ export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { } delete(key: BigInteger) { - const had = this.has(key); - if(had) { - delete this.root[key.toString()]; - this.cachedIter = null; - } - return had; + return produce(this, draft => { + delete draft.root[key.toString()]; + draft.cachedIter = draft.cachedIter.filter(([x]) => x.eq(key)); + }); } [Symbol.iterator](): IterableIterator<[BigInteger, V]> { let idx = 0; - const result = this.generateCachedIter(); + let result = [...this.cachedIter]; return { [Symbol.iterator]: this[Symbol.iterator], next: (): IteratorResult<[BigInteger, V]> => { @@ -79,19 +96,15 @@ export default class BigIntOrderedMap implements Iterable<[BigInteger, V]> { } keys() { - return Object.keys(this.root).map(k => bigInt(k)).sort(sortBigInt) + return Array.from(this).map(([k,v]) => k); } - private generateCachedIter() { - if(this.cachedIter) { - return this.cachedIter; - } + generateCachedIter() { const result = Object.keys(this.root).map(key => { const num = bigInt(key); return [num, this.root[key]] as [BigInteger, V]; }).sort(([a], [b]) => sortBigInt(a,b)); this.cachedIter = result; - return result; } } From aaea592cfc0b75e7931a0c9b4b5047b11a4e7e94 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 26 Apr 2021 17:04:14 +1000 Subject: [PATCH 03/17] VirtualScroller: rework for less memory use, faster speeds --- .../src/views/components/VirtualScroller.tsx | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/pkg/interface/src/views/components/VirtualScroller.tsx b/pkg/interface/src/views/components/VirtualScroller.tsx index f1e5aceeff..9799c571c2 100644 --- a/pkg/interface/src/views/components/VirtualScroller.tsx +++ b/pkg/interface/src/views/components/VirtualScroller.tsx @@ -1,4 +1,4 @@ -import React, { Component, useCallback } from 'react'; +import React, { Component, useCallback, SyntheticEvent } from 'react'; import _ from 'lodash'; import normalizeWheel from 'normalize-wheel'; import bigInt, { BigInteger } from 'big-integer'; @@ -76,7 +76,7 @@ interface VirtualScrollerProps { } interface VirtualScrollerState { - visibleItems: BigIntOrderedMap; + visibleItems: BigInteger[]; scrollbar: number; loaded: { top: boolean; @@ -91,10 +91,9 @@ const log = (level: LogLevel, message: string) => { if(logLevel.includes(level)) { console.log(`[${level}]: ${message}`); } - } -const ZONE_SIZE = IS_IOS ? 10 : 40; +const ZONE_SIZE = IS_IOS ? 10 : 80; // nb: in this file, an index refers to a BigInteger and an offset refers to a @@ -114,7 +113,7 @@ export default class VirtualScroller extends Component(); + private childRefs = new Map(); /** * A set of child refs which have been unmounted */ @@ -149,7 +148,7 @@ export default class VirtualScroller extends Component) { super(props); this.state = { - visibleItems: new BigIntOrderedMap(), + visibleItems: [], scrollbar: 0, loaded: { top: false, @@ -164,6 +163,7 @@ export default class VirtualScroller extends Component extends Component { const index = bigInt(o); - this.childRefs.delete(index); + this.childRefs.delete(index.toString()); }); this.orphans.clear(); }; @@ -206,13 +206,10 @@ export default class VirtualScroller extends Component, _prevState: VirtualScrollerState) { const { id, size, data, offset, pendingSize } = this.props; - const { visibleItems } = this.state; if(size !== prevProps.size || pendingSize !== prevProps.pendingSize) { - if(this.scrollLocked && visibleItems?.peekLargest() && data?.peekLargest()) { - if(!visibleItems.peekLargest()[0].eq(data.peekLargest()[0])) { - this.updateVisible(0); - } + if(this.scrollLocked) { + this.updateVisible(0); this.resetScroll(); } } @@ -228,11 +225,13 @@ export default class VirtualScroller extends Component i.eq(startIndex)) + const dataList = Array.from(data); + const offset = dataList.findIndex(([i]) => i.eq(startIndex)) if(offset === -1) { // TODO: revisit when we remove nodes for any other reason than // pending indices being removed @@ -252,19 +251,17 @@ export default class VirtualScroller extends Component( - [...data].slice(newOffset, newOffset + this.pageSize) - ); + const visibleItems = data.keys().slice(newOffset, newOffset + this.pageSize); this.save(); this.setState({ visibleItems, - }, () => { - requestAnimationFrame(() => { - this.restore(); - }); }); + requestAnimationFrame(() => { + this.restore(); + }); + } scrollKeyMap(): Map { @@ -296,7 +293,6 @@ export default class VirtualScroller extends Component extends Component { @@ -356,7 +352,7 @@ export default class VirtualScroller extends Component) { this.updateScroll(); if(!this.window) { // bail if we're going to adjust scroll anyway @@ -368,9 +364,11 @@ export default class VirtualScroller extends Component extends Component extends Component { - let ref = this.childRefs.get(index); + let ref = this.childRefs.get(index.toString()); if(!ref) { const offset = [...this.props.data].findIndex(([idx]) => idx.eq(index)); if(offset === -1) { @@ -446,7 +444,7 @@ export default class VirtualScroller extends Component { - ref = this.childRefs.get(index); + ref = this.childRefs.get(index.toString()); this.savedIndex = null; this.savedDistance = 0; this.saveDepth = 0; @@ -467,17 +465,18 @@ export default class VirtualScroller extends Component { - const el = this.childRefs.get(index); + ([...this.state.visibleItems]).reverse().forEach((index) => { + const el = this.childRefs.get(index.toString()); if(!el) { return; } @@ -490,11 +489,12 @@ export default class VirtualScroller extends Component extends Component { if(element) { - this.childRefs.set(index, element); + this.childRefs.set(index.toString(), element); this.orphans.delete(index.toString()); } else { this.orphans.add(index.toString()); @@ -525,11 +525,10 @@ export default class VirtualScroller extends Component extends Component )} - {indexesToRender.map(index => ( + {children.map(index => ( { setRef(el, props.index); - }, [setRef, props.index]) + // VirtualChild should always be keyed on the index, so the index should be + // valid for the entire lifecycle of the component, hence no dependencies + }, []); - return (); + return }; From 7a6b2eb0159c5678ada0bcc208a06a42878cb388 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 26 Apr 2021 17:04:36 +1000 Subject: [PATCH 04/17] virtualContext: drop useLayoutEffect --- pkg/interface/src/logic/lib/virtualContext.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/interface/src/logic/lib/virtualContext.tsx b/pkg/interface/src/logic/lib/virtualContext.tsx index ef57e67885..570c2726fd 100644 --- a/pkg/interface/src/logic/lib/virtualContext.tsx +++ b/pkg/interface/src/logic/lib/virtualContext.tsx @@ -43,8 +43,8 @@ export function useVirtualResizeState(s: boolean) { [_setState, save] ); - useLayoutEffect(() => { - restore(); + useEffect(() => { + requestAnimationFrame(restore); }, [state]); return [state, setState] as const; @@ -58,7 +58,7 @@ export function useVirtualResizeProp(prop: Primitive) { save(); } - useLayoutEffect(() => { + useEffect(() => { requestAnimationFrame(restore); }, [prop]); From 6308579588d1b3ef7edaaeb40731f9a92b7f694f Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 26 Apr 2021 17:05:00 +1000 Subject: [PATCH 05/17] interface: remove dead pendings --- pkg/interface/src/logic/state/graph.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/interface/src/logic/state/graph.ts b/pkg/interface/src/logic/state/graph.ts index 8f8a337cab..a17d810075 100644 --- a/pkg/interface/src/logic/state/graph.ts +++ b/pkg/interface/src/logic/state/graph.ts @@ -128,6 +128,8 @@ const useGraphState = createState('Graph', { // }); // graphReducer(node); // }, -}, ['graphs', 'graphKeys', 'looseNodes']); +}, ['graphs', 'graphKeys', 'looseNodes', 'graphTimesentMap']); + +window.useGraphState = useGraphState; export default useGraphState; From ae840659da53121cdce0c18bf72d446558cece7b Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 26 Apr 2021 17:05:57 +1000 Subject: [PATCH 06/17] ChatWindow: remove unnecessary props --- .../views/apps/chat/components/ChatWindow.tsx | 90 +++++++++---------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx b/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx index bc8cc388fd..d03d854844 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { useEffect, Component, useRef, useState, useCallback } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import _ from 'lodash'; import bigInt, { BigInteger } from 'big-integer'; @@ -30,10 +30,13 @@ const DEFAULT_BACKLOG_SIZE = 100; const IDLE_THRESHOLD = 64; const MAX_BACKLOG_SIZE = 1000; -type ChatWindowProps = RouteComponentProps<{ - ship: Patp; - station: string; -}> & { +const getCurrGraphSize = (ship: string, name: string) => { + const { graphs } = useGraphState.getState(); + const graph = graphs[`${ship}/${name}`]; + return graph.size; +}; + +type ChatWindowProps = { unreadCount: number; graph: Graph; graphSize: number; @@ -44,6 +47,8 @@ type ChatWindowProps = RouteComponentProps<{ api: GlobalApi; scrollTo?: BigInteger; onReply: (msg: Post) => void; + pendingSize?: number; + showOurContact: boolean; }; interface ChatWindowState { @@ -55,6 +60,7 @@ interface ChatWindowState { const virtScrollerStyle = { height: '100%' }; + class ChatWindow extends Component< ChatWindowProps, ChatWindowState @@ -81,6 +87,7 @@ class ChatWindow extends Component< this.handleWindowBlur = this.handleWindowBlur.bind(this); this.handleWindowFocus = this.handleWindowFocus.bind(this); this.stayLockedIfActive = this.stayLockedIfActive.bind(this); + this.fetchMessages = this.fetchMessages.bind(this); this.virtualList = null; this.unreadMarkerRef = React.createRef(); @@ -109,9 +116,11 @@ class ChatWindow extends Component< } const unreadIndex = graph.keys()[unreadCount]; if (!unreadIndex || unreadCount === 0) { - this.setState({ - unreadIndex: bigInt.zero - }); + if(state.unreadIndex.neq(bigInt.zero)) { + this.setState({ + unreadIndex: bigInt.zero + }); + } return; } this.setState({ @@ -138,7 +147,7 @@ class ChatWindow extends Component< } componentDidUpdate(prevProps: ChatWindowProps, prevState) { - const { history, graph, unreadCount, graphSize, station } = this.props; + const { graph, unreadCount, graphSize, station } = this.props; if(unreadCount === 0 && prevProps.unreadCount !== unreadCount) { this.unreadSet = true; } @@ -195,31 +204,35 @@ class ChatWindow extends Component< this.props.api.hark.markCountAsRead(association, '/', 'message'); } - setActive = () => { - if(this.state.idle) { - this.setState({ idle: false }); - } - } - fetchMessages = async (newer: boolean): Promise => { + async fetchMessages(newer: boolean): Promise { const { api, station, graph } = this.props; const pageSize = 100; const [, , ship, name] = station.split('/'); const expectedSize = graph.size + pageSize; if (newer) { - const [index] = graph.peekLargest()!; + const index = graph.peekLargest()?.[0]; + if(!index) { + console.log(`no index for: ${graph}`); + return true; + } await api.graph.getYoungerSiblings( ship, name, pageSize, `/${index.toString()}` ); - return expectedSize !== graph.size; + return expectedSize !== getCurrGraphSize(ship.slice(1), name); } else { - const [index] = graph.peekSmallest()!; + console.log('x'); + const index = graph.peekSmallest()?.[0]; + if(!index) { + console.log(`no index for: ${graph}`); + return true; + } await api.graph.getOlderSiblings(ship, name, pageSize, `/${index.toString()}`); - const done = expectedSize !== graph.size; + const done = expectedSize !== getCurrGraphSize(ship.slice(1), name); if(done) { this.calculateUnreadIndex(); } @@ -238,12 +251,9 @@ class ChatWindow extends Component< const { api, association, - group, showOurContact, graph, - history, - groups, - associations, + group, onReply } = this.props; const { unreadMarkerRef } = this; @@ -252,10 +262,8 @@ class ChatWindow extends Component< group, showOurContact, unreadMarkerRef, - history, api, - groups, - associations, + group, onReply }; @@ -275,10 +283,10 @@ class ChatWindow extends Component< graph.peekLargest()?.[0] ?? bigInt.zero ); const highlighted = index.eq(this.props.scrollTo ?? bigInt.zero); - const keys = graph.keys().reverse(); + const keys = graph.keys(); const graphIdx = keys.findIndex((idx) => idx.eq(index)); - const prevIdx = keys[graphIdx + 1]; - const nextIdx = keys[graphIdx - 1]; + const prevIdx = keys[graphIdx - 1]; + const nextIdx = keys[graphIdx + 1]; const isLastRead: boolean = this.state.unreadIndex.eq(index); const props = { highlighted, @@ -308,12 +316,8 @@ class ChatWindow extends Component< association, group, graph, - history, - groups, - associations, showOurContact, - pendingSize, - onReply, + pendingSize = 0, } = this.props; const unreadMarkerRef = this.unreadMarkerRef; @@ -321,16 +325,10 @@ class ChatWindow extends Component< association, group, unreadMarkerRef, - history, api, - associations }; const unreadMsg = graph.get(this.state.unreadIndex); - - // hack to force a re-render when we toggle showing contact - const contactsModified = - showOurContact ? 0 : 100; - + return ( { this.dismissedInitialUnread() && @@ -353,12 +351,11 @@ class ChatWindow extends Component< offset={unreadCount} origin='bottom' style={virtScrollerStyle} - onStartReached={this.setActive} onBottomLoaded={this.onBottomLoaded} onScroll={this.onScroll} data={graph} size={graph.size} - pendingSize={pendingSize + contactsModified} + pendingSize={pendingSize} id={association.resource} averageHeight={22} renderer={this.renderer} @@ -369,8 +366,5 @@ class ChatWindow extends Component< } } -export default withState(ChatWindow, [ - [useGroupState, ['groups']], - [useMetadataState, ['associations']], - [useGraphState, ['pendingSize']] -]); + +export default ChatWindow From 88a9d9ad1cfe37e3117068fbb9d07528070e1b50 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 26 Apr 2021 17:06:14 +1000 Subject: [PATCH 07/17] ChatResource: remove unnecessary props --- pkg/interface/src/views/apps/chat/ChatResource.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/ChatResource.tsx b/pkg/interface/src/views/apps/chat/ChatResource.tsx index 8c4fafc69e..d5e0063365 100644 --- a/pkg/interface/src/views/apps/chat/ChatResource.tsx +++ b/pkg/interface/src/views/apps/chat/ChatResource.tsx @@ -165,10 +165,9 @@ function ChatResource(props: ChatResourceProps) { {dragging && } Date: Mon, 26 Apr 2021 17:06:33 +1000 Subject: [PATCH 08/17] ChatMessage: remove dead props --- .../src/views/apps/chat/components/ChatMessage.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx index 776a3c7458..2ff5ab889d 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx @@ -141,7 +141,7 @@ const MessageActionItem = (props) => { ); }; -const MessageActions = ({ api, onReply, association, history, msg, group }) => { +const MessageActions = ({ api, onReply, association, msg, group }) => { const isAdmin = () => group.tags.role.admin.has(window.ship); const isOwn = () => msg.author === window.ship; const { doCopy, copyDisplay } = useCopy(`web+urbitgraph://group${association.group.slice(5)}/graph${association.resource.slice(5)}${msg.index}`, 'Copy Message Link'); @@ -244,7 +244,6 @@ interface ChatMessageProps { scrollWindow: HTMLDivElement; isLastMessage?: boolean; unreadMarkerRef: React.RefObject; - history: unknown; api: GlobalApi; highlighted?: boolean; renderSigil?: boolean; @@ -277,7 +276,6 @@ class ChatMessage extends Component { scrollWindow, isLastMessage, unreadMarkerRef, - history, api, highlighted, showOurContact, @@ -322,7 +320,6 @@ class ChatMessage extends Component { containerClass, isPending, showOurContact, - history, api, scrollWindow, highlighted, @@ -374,7 +371,7 @@ class ChatMessage extends Component { } } -export default React.forwardRef((props, ref) => ( +export default React.forwardRef((props: Omit, ref: any) => ( )); @@ -383,7 +380,6 @@ export const MessageAuthor = ({ msg, group, api, - history, scrollWindow, showOurContact, ...rest From 9304409e8bfcd7c0da766446bb5d959266efffc0 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 27 Apr 2021 11:33:45 +1000 Subject: [PATCH 09/17] ChatResource: cap initial backlog --- pkg/interface/src/views/apps/chat/ChatResource.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/ChatResource.tsx b/pkg/interface/src/views/apps/chat/ChatResource.tsx index d5e0063365..17b74ad6a4 100644 --- a/pkg/interface/src/views/apps/chat/ChatResource.tsx +++ b/pkg/interface/src/views/apps/chat/ChatResource.tsx @@ -40,7 +40,7 @@ function ChatResource(props: ChatResourceProps) { const graphPath = station.slice(7); const graph = graphs[graphPath]; const unreads = useHarkState(state => state.unreads); - const unreadCount = unreads.graph?.[station]?.['/']?.unreads || 0; + const unreadCount = unreads.graph?.[station]?.['/']?.unreads as number || 0; const graphTimesentMap = useGraphState(state => state.graphTimesentMap); const [,, owner, name] = station.split('/'); const ourContact = contacts?.[`~${window.ship}`]; @@ -48,7 +48,7 @@ function ChatResource(props: ChatResourceProps) { const canWrite = isWriter(group, station); useEffect(() => { - const count = 100 + unreadCount; + const count = Math.min(400, 100 + unreadCount); props.api.graph.getNewest(owner, name, count); }, [station]); From 19f9dd6009e2d1438fd1173ac709b15ae93a2489 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 27 Apr 2021 11:34:05 +1000 Subject: [PATCH 10/17] graph-update: fix add-graph --- .../src/logic/reducers/graph-update.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/pkg/interface/src/logic/reducers/graph-update.ts b/pkg/interface/src/logic/reducers/graph-update.ts index a37b918c96..52fe36f4c9 100644 --- a/pkg/interface/src/logic/reducers/graph-update.ts +++ b/pkg/interface/src/logic/reducers/graph-update.ts @@ -52,23 +52,18 @@ const keys = (json, state: GraphState): GraphState => { const processNode = (node) => { // is empty if (!node.children) { - node.children = new BigIntOrderedMap(); - return node; + return produce(node, draft => { + draft.children = new BigIntOrderedMap(); + }); } // is graph - let converted = new BigIntOrderedMap(); - for (let idx in node.children) { - let item = node.children[idx]; - let index = bigInt(idx); - - converted.set( - index, - processNode(item) - ); - } - node.children = converted; - return node; + return produce(node, draft => { + draft.children = new BigIntOrderedMap() + .gas(_.map(draft.children, (item, idx) => + [bigInt(idx), processNode(item)] as [BigInteger, any] + )); + }); }; @@ -89,6 +84,7 @@ const addGraph = (json, state: GraphState): GraphState => { state.graphs[resource] = state.graphs[resource].gas(Object.keys(data.graph).map(idx => { return [bigInt(idx), processNode(data.graph[idx])]; })); + state.graphKeys.add(resource); } return state; From ac5bc51da68d2a2338fd40a0478866236052a7b5 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 27 Apr 2021 15:10:27 +1000 Subject: [PATCH 11/17] ChatMessage: aggressively memoize, remove more dead props --- .../apps/chat/components/ChatMessage.tsx | 270 +++++++++--------- 1 file changed, 134 insertions(+), 136 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx index 2ff5ab889d..5892e6da7d 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatMessage.tsx @@ -3,6 +3,7 @@ import bigInt from 'big-integer'; import React, { useState, useEffect, + useMemo, useRef, Component, PureComponent, @@ -40,11 +41,12 @@ import styled from 'styled-components'; import useLocalState from '~/logic/state/local'; import useSettingsState, { selectCalmState } from '~/logic/state/settings'; import Timestamp from '~/views/components/Timestamp'; -import useContactState from '~/logic/state/contact'; +import useContactState, {useContact} from '~/logic/state/contact'; import { useIdlingState } from '~/logic/lib/idling'; import ProfileOverlay from '~/views/components/ProfileOverlay'; import {useCopy} from '~/logic/lib/useCopy'; import {GraphContentWide} from '~/views/landscape/components/Graph/GraphContentWide'; +import {Contact} from '@urbit/api'; export const DATESTAMP_FORMAT = '[~]YYYY.M.D'; @@ -80,7 +82,7 @@ export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => ( ); export const UnreadMarker = React.forwardRef( - ({ dayBreak, when, api, association }, ref) => { + ({ dayBreak, when, api, association }: any, ref) => { const [visible, setVisible] = useState(false); const idling = useIdlingState(); const dismiss = useCallback(() => { @@ -241,7 +243,6 @@ interface ChatMessageProps { className?: string; isPending: boolean; style?: unknown; - scrollWindow: HTMLDivElement; isLastMessage?: boolean; unreadMarkerRef: React.RefObject; api: GlobalApi; @@ -250,125 +251,124 @@ interface ChatMessageProps { hideHover?: boolean; innerRef: (el: HTMLDivElement | null) => void; onReply?: (msg: Post) => void; + showOurContact: boolean; } -class ChatMessage extends Component { - private divRef: React.RefObject; +function ChatMessage(props: ChatMessageProps) { + const { + msg, + previousMsg, + nextMsg, + isLastRead, + group, + association, + className = '', + isPending, + style, + isLastMessage, + unreadMarkerRef, + api, + highlighted, + showOurContact, + fontSize, + hideHover + } = props; - constructor(props) { - super(props); - this.divRef = React.createRef(); - } - - componentDidMount() {} - - render() { - const { - msg, - previousMsg, - nextMsg, - isLastRead, - group, - association, - className = '', - isPending, - style, - scrollWindow, - isLastMessage, - unreadMarkerRef, - api, - highlighted, - showOurContact, - fontSize, - hideHover - } = this.props; - - let onReply = this.props?.onReply ?? (() => {}); - const transcluded = this.props?.transcluded ?? 0; - let { renderSigil } = this.props; - - if (renderSigil === undefined) { - renderSigil = Boolean( - (nextMsg && msg.author !== nextMsg.author) || - !nextMsg || - msg.number === 1 - ); - } - - const date = daToUnix(bigInt(msg.index.split('/')[1])); - const nextDate = nextMsg ? ( - daToUnix(bigInt(nextMsg.index.split('/')[1])) - ) : null; - - const dayBreak = - nextMsg && - new Date(date).getDate() !== - new Date(nextDate).getDate(); - - const containerClass = `${isPending ? 'o-40' : ''} ${className}`; - - const timestamp = moment - .unix(date / 1000) - .format(renderSigil ? 'h:mm A' : 'h:mm'); - - const messageProps = { - msg, - timestamp, - association, - group, - style, - containerClass, - isPending, - showOurContact, - api, - scrollWindow, - highlighted, - fontSize, - hideHover, - transcluded, - onReply - }; - - const unreadContainerStyle = { - height: isLastRead ? '2rem' : '0' - }; - - return ( - - {dayBreak && !isLastRead ? ( - - ) : null} - {renderSigil ? ( - - - - - ) : ( - - - - )} - - {isLastRead ? ( - - ) : null} - - + let onReply = props?.onReply ?? (() => {}); + const transcluded = props?.transcluded ?? 0; + const renderSigil = props.renderSigil ?? (Boolean(nextMsg && msg.author !== nextMsg.author) || + !nextMsg || + msg.number === 1 ); - } + + + + const date = useMemo(() => daToUnix(bigInt(msg.index.split('/')[1])), [msg.index]); + const nextDate = useMemo(() => nextMsg ? ( + daToUnix(bigInt(nextMsg.index.split('/')[1])) + ) : null, + [nextMsg] + ); + + const dayBreak = useMemo(() => + nextDate && + new Date(date).getDate() !== + new Date(nextDate).getDate() + , [nextDate, date]) + + const containerClass = `${isPending ? 'o-40' : ''} ${className}`; + + const timestamp = useMemo(() => moment + .unix(date / 1000) + .format(renderSigil ? 'h:mm A' : 'h:mm'), + [date, renderSigil] + ); + + const messageProps = { + msg, + timestamp, + association, + group, + isPending, + showOurContact, + api, + highlighted, + fontSize, + hideHover, + transcluded, + onReply + }; + + const message = useMemo(() => ( + + ), [renderSigil, msg, timestamp, api, transcluded, showOurContact]); + + const unreadContainerStyle = { + height: isLastRead ? '2rem' : '0' + }; + + return ( + + {dayBreak && !isLastRead ? ( + + ) : null} + {renderSigil ? ( + + + {message} + + ) : ( + + {message} + + )} + + {isLastRead ? ( + + ) : null} + + + ); } export default React.forwardRef((props: Omit, ref: any) => ( @@ -378,29 +378,25 @@ export default React.forwardRef((props: Omit, ref: export const MessageAuthor = ({ timestamp, msg, - group, api, - scrollWindow, showOurContact, - ...rest }) => { const osDark = useLocalState((state) => state.dark); const theme = useSettingsState((s) => s.display.theme); const dark = theme === 'dark' || (theme === 'auto' && osDark); - const contacts = useContactState((state) => state.contacts); + let contact: Contact | null = useContact(`~${msg.author}`); const date = daToUnix(bigInt(msg.index.split('/')[1])); const datestamp = moment .unix(date / 1000) .format(DATESTAMP_FORMAT); - const contact = + contact = ((msg.author === window.ship && showOurContact) || - msg.author !== window.ship) && - `~${msg.author}` in contacts - ? contacts[`~${msg.author}`] - : undefined; + msg.author !== window.ship) + ? contact + : null; const showNickname = useShowNickname(contact); const { hideAvatars } = useSettingsState(selectCalmState); @@ -453,7 +449,7 @@ export const MessageAuthor = ({ ); return ( - + + +export const Message = React.memo(({ timestamp, msg, - group, api, - scrollWindow, timestampHover, transcluded, - showOurContact, - ...rest -}) => { + showOurContact +}: MessageProps) => { const { hovering, bind } = useHovering(); return ( - + {timestampHover ? ( ); -}; +}); + +Message.displayName = 'Message'; export const MessagePlaceholder = ({ height, From 5281d4120557c6ee51e02c0987201ff339abd30b Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 27 Apr 2021 15:12:10 +1000 Subject: [PATCH 12/17] VirtualScroller: smaller pages, disable children shifting layout --- .../src/views/components/VirtualScroller.tsx | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/pkg/interface/src/views/components/VirtualScroller.tsx b/pkg/interface/src/views/components/VirtualScroller.tsx index 9799c571c2..1d9bbbad46 100644 --- a/pkg/interface/src/views/components/VirtualScroller.tsx +++ b/pkg/interface/src/views/components/VirtualScroller.tsx @@ -159,7 +159,7 @@ export default class VirtualScroller extends Component extends Component { event.preventDefault(); @@ -364,7 +364,7 @@ export default class VirtualScroller extends Component extends Component { + this.savedIndex = null; + this.savedDistance = 0; + this.saveDepth--; + }); return; } @@ -425,14 +427,16 @@ export default class VirtualScroller extends Component { - this.savedIndex = null; - this.savedDistance = 0; - this.saveDepth--; - }); + this.savedIndex = null; + this.savedDistance = 0; + this.saveDepth--; + }); + } scrollToIndex = (index: BigInteger) => { @@ -471,11 +475,12 @@ export default class VirtualScroller extends Component { + ([...visibleItems]).reverse().forEach((index) => { const el = this.childRefs.get(index.toString()); if(!el) { return; @@ -499,7 +504,8 @@ export default class VirtualScroller extends Component {}, restore: () => {} }; setRef = (element: HTMLElement | null, index: BigInteger) => { if(element) { @@ -529,7 +535,7 @@ export default class VirtualScroller extends Component From 215b301be5d0d89894a8389952550331a7467720 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 27 Apr 2021 15:17:55 +1000 Subject: [PATCH 13/17] glob: update to 0v3.hls3k.gsbae.rm6pr.p6qve.46dh8 --- pkg/arvo/app/glob.hoon | 2 +- pkg/arvo/app/landscape/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/arvo/app/glob.hoon b/pkg/arvo/app/glob.hoon index 0fb9ffa4a6..bf7f12d097 100644 --- a/pkg/arvo/app/glob.hoon +++ b/pkg/arvo/app/glob.hoon @@ -5,7 +5,7 @@ /- glob /+ default-agent, verb, dbug |% -++ hash 0v3.g6u13.haedt.jt4hd.61ek5.6t30q +++ hash 0v3.hls3k.gsbae.rm6pr.p6qve.46dh8 +$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))] +$ all-states $% state-0 diff --git a/pkg/arvo/app/landscape/index.html b/pkg/arvo/app/landscape/index.html index 8a76a571f8..4f6c71f597 100644 --- a/pkg/arvo/app/landscape/index.html +++ b/pkg/arvo/app/landscape/index.html @@ -24,6 +24,6 @@
- + From 7524dd268fa223cb6458b86a49f2869c205f8082 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 27 Apr 2021 15:33:38 +1000 Subject: [PATCH 14/17] UnreadNotice: show even if are missing unread message --- .../views/apps/chat/components/ChatWindow.tsx | 4 +-- .../apps/chat/components/unread-notice.js | 28 ++++++++----------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx b/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx index d03d854844..cf34168df5 100644 --- a/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx +++ b/pkg/interface/src/views/apps/chat/components/ChatWindow.tsx @@ -131,8 +131,8 @@ class ChatWindow extends Component< dismissedInitialUnread() { const { unreadCount, graph } = this.props; - return this.state.unreadIndex.neq(bigInt.zero) && - this.state.unreadIndex.neq(graph.keys()?.[unreadCount]?.[0] ?? bigInt.zero); + return this.state.unreadIndex.eq(bigInt.zero) ? unreadCount > graph.size : + this.state.unreadIndex.neq(graph.keys()?.[unreadCount]?.[0] ?? bigInt.zero); } handleWindowBlur() { diff --git a/pkg/interface/src/views/apps/chat/components/unread-notice.js b/pkg/interface/src/views/apps/chat/components/unread-notice.js index f3f6e73f1a..82ba3e35ca 100644 --- a/pkg/interface/src/views/apps/chat/components/unread-notice.js +++ b/pkg/interface/src/views/apps/chat/components/unread-notice.js @@ -8,22 +8,11 @@ import Timestamp from '~/views/components/Timestamp'; export const UnreadNotice = (props) => { const { unreadCount, unreadMsg, dismissUnread, onClick } = props; - if (!unreadMsg || unreadCount === 0) { + if (unreadCount === 0) { return null; } - const stamp = moment.unix(unreadMsg.post['time-sent'] / 1000); - - let datestamp = moment - .unix(unreadMsg.post['time-sent'] / 1000) - .format('YYYY.M.D'); - const timestamp = moment - .unix(unreadMsg.post['time-sent'] / 1000) - .format('HH:mm'); - - if (datestamp === moment().format('YYYY.M.D')) { - datestamp = null; - } + const stamp = unreadMsg && moment.unix(unreadMsg.post['time-sent'] / 1000); return ( { whiteSpace='pre' overflow='hidden' display='flex' - cursor='pointer' + cursor={unreadMsg ? 'pointer' : null} onClick={onClick} > - {unreadCount} new message{unreadCount > 1 ? 's' : ''} since{' '} - + {unreadCount} new message{unreadCount > 1 ? 's' : ''} + {unreadMsg && ( + <> + since{' '} + + + )} Date: Tue, 27 Apr 2021 16:25:50 +1000 Subject: [PATCH 15/17] VirtualScroller: fix for origin=top --- .../src/views/apps/links/LinkWindow.tsx | 14 +- .../views/apps/links/components/LinkItem.tsx | 7 +- .../src/views/components/VirtualScroller.tsx | 14 +- .../components/Home/Post/PostFeed.js | 185 +++++++++--------- 4 files changed, 115 insertions(+), 105 deletions(-) diff --git a/pkg/interface/src/views/apps/links/LinkWindow.tsx b/pkg/interface/src/views/apps/links/LinkWindow.tsx index 7b44aa08a3..2f085ca009 100644 --- a/pkg/interface/src/views/apps/links/LinkWindow.tsx +++ b/pkg/interface/src/views/apps/links/LinkWindow.tsx @@ -6,7 +6,7 @@ import React, { Component, } from "react"; -import { Col, Text } from "@tlon/indigo-react"; +import { Box, Col, Text } from "@tlon/indigo-react"; import bigInt from "big-integer"; import { Association, Graph, Unreads, Group, Rolodex } from "@urbit/api"; @@ -48,7 +48,7 @@ class LinkWindow extends Component { return isWriter(group, association.resource); } - renderItem = ({ index, scrollWindow }) => { + renderItem = React.forwardRef(({ index, scrollWindow }, ref) => { const { props } = this; const { association, graph, api } = props; const [, , ship, name] = association.resource.split("/"); @@ -80,12 +80,14 @@ class LinkWindow extends Component { api={api} /> - + ); } - return ; - }; + return + ; + + }); render() { const { graph, api, association } = this.props; @@ -136,4 +138,4 @@ class LinkWindow extends Component { } } -export default LinkWindow; \ No newline at end of file +export default LinkWindow; diff --git a/pkg/interface/src/views/apps/links/components/LinkItem.tsx b/pkg/interface/src/views/apps/links/components/LinkItem.tsx index a3e2065430..8937c24d39 100644 --- a/pkg/interface/src/views/apps/links/components/LinkItem.tsx +++ b/pkg/interface/src/views/apps/links/components/LinkItem.tsx @@ -19,7 +19,7 @@ interface LinkItemProps { node: GraphNode; association: Association; resource: string; api: GlobalApi; group: Group; path: string; } -export const LinkItem = (props: LinkItemProps): ReactElement => { +export const LinkItem = React.forwardRef((props: LinkItemProps, ref): ReactElement => { const { association, node, @@ -30,7 +30,6 @@ export const LinkItem = (props: LinkItemProps): ReactElement => { ...rest } = props; - const ref = useRef(null); const remoteRef = useRef(null); const index = node.post.index.split('/')[1]; @@ -97,7 +96,7 @@ export const LinkItem = (props: LinkItemProps): ReactElement => { const unreads = useHarkState(state => state.unreads); const commColor = (unreads.graph?.[appPath]?.[`/${index}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray'; const isUnread = unreads.graph?.[appPath]?.['/']?.unreads?.has(node.post.index); - + return ( { ); -}; +}); diff --git a/pkg/interface/src/views/components/VirtualScroller.tsx b/pkg/interface/src/views/components/VirtualScroller.tsx index 1d9bbbad46..b2a37329e6 100644 --- a/pkg/interface/src/views/components/VirtualScroller.tsx +++ b/pkg/interface/src/views/components/VirtualScroller.tsx @@ -428,7 +428,11 @@ export default class VirtualScroller extends Component { @@ -479,8 +483,10 @@ export default class VirtualScroller extends Component { + const topSpacing = this.props.origin === 'top' ? scrollTop : scrollHeight - scrollTop; + console.log(scrollTop); + const items = this.props.origin === 'top' ? visibleItems : [...visibleItems].reverse(); + items.forEach((index) => { const el = this.childRefs.get(index.toString()); if(!el) { return; @@ -499,7 +505,9 @@ export default class VirtualScroller extends Component { - const { - graph, - graphPath, - api, - history, - baseUrl, - parentNode, - grandparentNode, - association, - group, - vip - } = this.props; - const graphResource = resourceFromPath(graphPath); - const node = graph.get(index); - if (!node) { return null; } - - const first = graph.peekLargest()?.[0]; - const post = node?.post; - if (!node || !post) { - return null; - } - let nodeIndex = parentNode ? parentNode.post.index.split('/').slice(1).map((ind) => { - return bigInt(ind); - }) : []; - - if (parentNode && index.eq(first ?? bigInt.zero)) { - return ( - - - - - - - ); - } - - return ( - - ); - }); this.fetchPosts = this.fetchPosts.bind(this); this.doNotFetch = this.doNotFetch.bind(this); } + renderItem = React.forwardRef(({ index, scrollWindow }, ref) => { + const { + graph, + graphPath, + api, + history, + baseUrl, + parentNode, + grandparentNode, + association, + group, + vip + } = this.props; + const graphResource = resourceFromPath(graphPath); + const node = graph.get(index); + if (!node) { return null; } + + const first = graph.peekLargest()?.[0]; + const post = node?.post; + if (!node || !post) { + return null; + } + let nodeIndex = parentNode ? parentNode.post.index.split('/').slice(1).map((ind) => { + return bigInt(ind); + }) : []; + + if (parentNode && index.eq(first ?? bigInt.zero)) { + return ( + + + + + + + ); + } + + return ( + + + + ); + }); + + async fetchPosts(newer) { const { graph, graphPath, api } = this.props; const graphResource = resourceFromPath(graphPath); From 4f6003fd241b4c6f553ec12f55e056bfc70c4f21 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Tue, 27 Apr 2021 16:32:05 +1000 Subject: [PATCH 16/17] graph-update: fix recursive add-nodes case --- pkg/interface/src/logic/reducers/graph-update.ts | 8 +++++--- pkg/interface/src/views/components/VirtualScroller.tsx | 4 ---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/interface/src/logic/reducers/graph-update.ts b/pkg/interface/src/logic/reducers/graph-update.ts index 52fe36f4c9..02d43d6dae 100644 --- a/pkg/interface/src/logic/reducers/graph-update.ts +++ b/pkg/interface/src/logic/reducers/graph-update.ts @@ -105,7 +105,7 @@ const removeGraph = (json, state: GraphState): GraphState => { }; const mapifyChildren = (children) => { - return new BigIntOrderedMap( + return new BigIntOrderedMap().gas( _.map(children, (node, idx) => { idx = idx && idx.startsWith('/') ? idx.slice(1) : idx; const nd = {...node, children: mapifyChildren(node.children || {}) }; @@ -214,12 +214,14 @@ const addNodes = (json, state) => { state.graphTimesentMap[resource][node.post['time-sent']] = index; } - node.children = mapifyChildren(node?.children || {}); + state.graphs[resource] = _addNode( state.graphs[resource], indexArr, - node + produce(node, draft => { + draft.children = mapifyChildren(draft?.children || {}); + }) ); if(newSize !== old) { console.log(`${resource}, (${old}, ${newSize}, ${state.graphs[resource].size})`); diff --git a/pkg/interface/src/views/components/VirtualScroller.tsx b/pkg/interface/src/views/components/VirtualScroller.tsx index b2a37329e6..2986873f26 100644 --- a/pkg/interface/src/views/components/VirtualScroller.tsx +++ b/pkg/interface/src/views/components/VirtualScroller.tsx @@ -432,7 +432,6 @@ export default class VirtualScroller extends Component { @@ -484,7 +483,6 @@ export default class VirtualScroller extends Component { const el = this.childRefs.get(index.toString()); @@ -505,9 +503,7 @@ export default class VirtualScroller extends Component Date: Tue, 27 Apr 2021 15:51:02 -0400 Subject: [PATCH 17/17] UnreadNotice: add missing space --- pkg/interface/src/views/apps/chat/components/unread-notice.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/interface/src/views/apps/chat/components/unread-notice.js b/pkg/interface/src/views/apps/chat/components/unread-notice.js index 82ba3e35ca..53ad6f00ba 100644 --- a/pkg/interface/src/views/apps/chat/components/unread-notice.js +++ b/pkg/interface/src/views/apps/chat/components/unread-notice.js @@ -44,10 +44,10 @@ export const UnreadNotice = (props) => { cursor={unreadMsg ? 'pointer' : null} onClick={onClick} > - {unreadCount} new message{unreadCount > 1 ? 's' : ''} + {unreadCount} new message{unreadCount > 1 ? 's' : ''} {unreadMsg && ( <> - since{' '} + {' '}since{' '} )}