From 276e2c2fa4bfaa52345e5797dfc03585fc484bf1 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Mon, 14 Jun 2021 11:45:18 +1000 Subject: [PATCH] interface: pending graph updates --- pkg/interface/src/logic/api/bootstrap.ts | 2 +- .../src/logic/reducers/hark-update.ts | 6 +++ pkg/interface/src/logic/state/graph.ts | 42 ++++++++++++++++--- pkg/interface/src/logic/state/group.ts | 2 +- .../src/views/apps/chat/ChatResource.tsx | 11 ++--- .../src/views/apps/chat/DmResource.tsx | 21 +++++----- .../views/apps/links/components/LinkItem.tsx | 6 +-- .../apps/links/components/LinkSubmit.tsx | 17 ++++---- .../views/apps/notifications/notification.tsx | 3 +- .../apps/publish/components/NotePreview.tsx | 8 ++-- .../src/views/components/Comments.tsx | 8 ++-- pkg/npm/api/graph/lib.ts | 4 +- 12 files changed, 83 insertions(+), 47 deletions(-) diff --git a/pkg/interface/src/logic/api/bootstrap.ts b/pkg/interface/src/logic/api/bootstrap.ts index a66d5a9e5..24803dfda 100644 --- a/pkg/interface/src/logic/api/bootstrap.ts +++ b/pkg/interface/src/logic/api/bootstrap.ts @@ -24,7 +24,7 @@ export const bootstrapApi = async () => { })(); }; - airlock.onRetry = (e) => { + airlock.onRetry = () => { useLocalState.setState({ subscription: 'reconnecting' }); }; diff --git a/pkg/interface/src/logic/reducers/hark-update.ts b/pkg/interface/src/logic/reducers/hark-update.ts index b62519712..fb7d26036 100644 --- a/pkg/interface/src/logic/reducers/hark-update.ts +++ b/pkg/interface/src/logic/reducers/hark-update.ts @@ -152,6 +152,9 @@ function unreads(json: any, state: HarkState): HarkState { data.forEach(({ index, stats }) => { const { unreads, notifications, last } = stats; updateNotificationStats(state, index, 'last', () => last); + if(index.graph.graph === '/ship/~hastuc-dibtux/test-book-7531') { + console.log(index, stats); + } _.each(notifications, ({ time, index }) => { if(!time) { addNotificationToUnread(state, index); @@ -197,6 +200,9 @@ function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: numbe } const property = [index.graph.graph, index.graph.index, 'unreads']; const curr = _.get(state.unreads.graph, property, 0); + if(typeof curr !== 'number') { + return state; + } const newCount = count(curr); _.set(state.unreads.graph, property, newCount); return state; diff --git a/pkg/interface/src/logic/state/graph.ts b/pkg/interface/src/logic/state/graph.ts index 24a12e3af..8fe87a9cd 100644 --- a/pkg/interface/src/logic/state/graph.ts +++ b/pkg/interface/src/logic/state/graph.ts @@ -5,7 +5,7 @@ import { Association, deSig, GraphNode, Graphs, FlatGraphs, resourceFromPath, Th import { useCallback } from 'react'; import { createState, createSubscription, reduceStateN } from './base'; import airlock from '~/logic/api'; -import { getDeepOlderThan, getFirstborn, getNewest, getNode, getOlderSiblings, getYoungerSiblings } from '@urbit/api/graph'; +import { addDmMessage, addPost, Content, getDeepOlderThan, getFirstborn, getNewest, getNode, getOlderSiblings, getYoungerSiblings, markPending, Post, addNode, GraphNodePoke } from '@urbit/api/graph'; import { GraphReducer, reduceDm } from '../reducers/graph-update'; import _ from 'lodash'; @@ -24,17 +24,16 @@ export interface GraphState { screening: boolean; graphTimesentMap: Record; getDeepOlderThan: (ship: string, name: string, count: number, start?: string) => Promise; - // getKeys: () => Promise; - // getTags: () => Promise; - // getTagQueries: () => Promise; - // getGraph: (ship: string, resource: string) => Promise; getNewest: (ship: string, resource: string, count: number, index?: string) => Promise; getOlderSiblings: (ship: string, resource: string, count: number, index?: string) => Promise; getYoungerSiblings: (ship: string, resource: string, count: number, index?: string) => Promise; - // getGraphSubset: (ship: string, resource: string, start: string, end: string) => Promise; getNode: (ship: string, resource: string, index: string) => Promise; getFirstborn: (ship: string, resource: string, index: string) => Promise; getGraph: (ship: string, name: string) => Promise; + addDmMessage: (ship: string, contents: Content[]) => Promise; + addPost: (ship: string, name: string, post: Post) => Promise; + + addNode: (ship: string, name: string, post: GraphNodePoke) => Promise; } // @ts-ignore investigate zustand types const useGraphState = createState('Graph', (set, get) => ({ @@ -47,6 +46,37 @@ const useGraphState = createState('Graph', (set, get) => ({ graphTimesentMap: {}, pendingDms: new Set(), screening: false, + addDmMessage: async (ship: string, contents: Content[]) => { + const promise = airlock.poke(addDmMessage(window.ship, ship, contents)); + const { json } = addDmMessage(window.ship, ship, contents); + markPending(json['add-nodes'].nodes); + json['add-nodes'].resource.ship = json['add-nodes'].resource.ship.slice(1); + GraphReducer({ + 'graph-update': json + }); + await promise; + }, + addPost: async (ship, name, post) => { + const promise = airlock.thread(addPost(ship, name, post)); + const { body } = addPost(ship, name, post); + markPending(body['add-nodes'].nodes); + body['add-nodes'].resource.ship = body['add-nodes'].resource.ship.slice(1); + GraphReducer({ + 'graph-update': body, + 'graph-update-flat': body + }); + await promise; + }, + addNode: async (ship, name, node) => { + const promise = airlock.thread(addNode(ship, name, node)); + const { body } = addNode(ship, name, node); + markPending(body['add-nodes'].nodes); + body['add-nodes'].resource.ship = body['add-nodes'].resource.ship.slice(1); + GraphReducer({ + 'graph-update': body + }); + await promise; + }, getDeepOlderThan: async (ship, name, count, start) => { const data = await airlock.scry(getDeepOlderThan(ship, name, count, start)); diff --git a/pkg/interface/src/logic/state/group.ts b/pkg/interface/src/logic/state/group.ts index c82f3ad42..6d5cfba80 100644 --- a/pkg/interface/src/logic/state/group.ts +++ b/pkg/interface/src/logic/state/group.ts @@ -31,7 +31,7 @@ const useGroupState = createState( reduceStateN(get(), e.groupUpdate, reduce); } }), - (set, get) => createSubscription('group-view', '/groups', (e) => { + (set, get) => createSubscription('group-view', '/all', (e) => { const data = _.get(e, 'group-view-update', false); if (data) { reduceStateN(get(), data, reduceView); diff --git a/pkg/interface/src/views/apps/chat/ChatResource.tsx b/pkg/interface/src/views/apps/chat/ChatResource.tsx index 94b0fb2e7..ab77f63c7 100644 --- a/pkg/interface/src/views/apps/chat/ChatResource.tsx +++ b/pkg/interface/src/views/apps/chat/ChatResource.tsx @@ -1,4 +1,4 @@ -import { addPost, Content, createPost, fetchIsAllowed, markCountAsRead, Post, removePosts } from '@urbit/api'; +import { Content, createPost, fetchIsAllowed, markCountAsRead, Post, removePosts } from '@urbit/api'; import { Association } from '@urbit/api/metadata'; import { BigInteger } from 'big-integer'; import React, { @@ -43,9 +43,10 @@ const ChatResource = (props: ChatResourceProps): ReactElement => { const [ getNewest, getOlderSiblings, - getYoungerSiblings + getYoungerSiblings, + addPost ] = useGraphState( - s => [s.getNewest, s.getOlderSiblings, s.getYoungerSiblings], + s => [s.getNewest, s.getOlderSiblings, s.getYoungerSiblings, s.addPost], shallow ); @@ -129,8 +130,8 @@ const ChatResource = (props: ChatResourceProps): ReactElement => { const onSubmit = useCallback((contents: Content[]) => { const { ship, name } = resourceFromPath(resource); - airlock.thread(addPost(ship, name, createPost(window.ship, contents))); - }, [resource]); + addPost(ship, name, createPost(window.ship, contents)); + }, [resource, addPost]); const onDelete = useCallback((msg: Post) => { const { ship, name } = resourceFromPath(resource); diff --git a/pkg/interface/src/views/apps/chat/DmResource.tsx b/pkg/interface/src/views/apps/chat/DmResource.tsx index 834caed95..69e7cb130 100644 --- a/pkg/interface/src/views/apps/chat/DmResource.tsx +++ b/pkg/interface/src/views/apps/chat/DmResource.tsx @@ -1,4 +1,4 @@ -import { addDmMessage, cite, Content, markCountAsRead, Post } from '@urbit/api'; +import { cite, Content, markCountAsRead, Post } from '@urbit/api'; import React, { useCallback, useEffect } from 'react'; import _ from 'lodash'; import bigInt from 'big-integer'; @@ -10,7 +10,6 @@ import useGraphState, { useDM } from '~/logic/state/graph'; import { useHarkDm } from '~/logic/state/hark'; import useSettingsState, { selectCalmState } from '~/logic/state/settings'; import { ChatPane } from './components/ChatPane'; -import { patpToUd } from '~/logic/lib/util'; import airlock from '~/logic/api'; import shallow from 'zustand/shallow'; @@ -60,11 +59,12 @@ export function DmResource(props: DmResourceProps) { const nickname = showNickname ? contact!.nickname : cite(ship) ?? ship; const [ - getNewest, + getYoungerSiblings, getOlderSiblings, - getYoungerSiblings + getNewest, + addDmMessage ] = useGraphState( - s => [s.getYoungerSiblings, s.getOlderSiblings, s.getNewest], + s => [s.getYoungerSiblings, s.getOlderSiblings, s.getNewest, s.addDmMessage], shallow ); @@ -73,7 +73,7 @@ export function DmResource(props: DmResourceProps) { `~${window.ship}`, 'dm-inbox', 100, - `/${patpToUd(ship)}` + `/${patp2dec(ship)}` ); }, [ship]); @@ -90,7 +90,7 @@ export function DmResource(props: DmResourceProps) { `~${window.ship}`, 'dm-inbox', pageSize, - `/${patpToUd(ship)}/${index.toString()}` + `/${patp2dec(ship)}/${index.toString()}` ); return expectedSize !== getCurrDmSize(ship); } else { @@ -102,7 +102,7 @@ export function DmResource(props: DmResourceProps) { `~${window.ship}`, 'dm-inbox', pageSize, - `/${patpToUd(ship)}/${index.toString()}` + `/${patp2dec(ship)}/${index.toString()}` ); return expectedSize !== getCurrDmSize(ship); } @@ -116,10 +116,9 @@ export function DmResource(props: DmResourceProps) { const onSubmit = useCallback( (contents: Content[]) => { - // XX optimistic - airlock.poke(addDmMessage(`~${window.ship}`, ship, contents)); + addDmMessage(ship, contents); }, - [ship] + [ship, addDmMessage] ); return ( diff --git a/pkg/interface/src/views/apps/links/components/LinkItem.tsx b/pkg/interface/src/views/apps/links/components/LinkItem.tsx index f44e7cc8b..a5e59f44c 100644 --- a/pkg/interface/src/views/apps/links/components/LinkItem.tsx +++ b/pkg/interface/src/views/apps/links/components/LinkItem.tsx @@ -98,10 +98,10 @@ export const LinkItem = React.forwardRef((props: LinkItemProps, ref: RefObject state.unreads); - const commColor = (unreads.graph?.[appPath]?.[`/${index}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray'; + const unreads = useHarkState(state => state.unreads?.[appPath]); + const commColor = (unreads?.[`/${index}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray'; // @ts-ignore hark will have to choose between sets and numbers - const isUnread = unreads.graph?.[appPath]?.['/']?.unreads?.has(node.post.index); + const isUnread = (unreads?.['/']?.unreads ?? new Set()).has(node.post.index); return ( { const { canUpload, uploadDefault, uploading, promptUpload } = useStorage(); + const addPost = useGraphState(s => s.addPost); const [submitFocused, setSubmitFocused] = useState(false); const [urlFocused, setUrlFocused] = useState(false); @@ -37,16 +37,15 @@ const LinkSubmit = (props: LinkSubmitProps) => { const parentIndex = props.parentIndex || ''; const post = createPost(contents, parentIndex); - airlock.thread(addPost( + addPost( `~${props.ship}`, props.name, post - )).then(() => { - setDisabled(false); - setLinkValue(''); - setLinkTitle(''); - setLinkValid(false); - }); + ); + setDisabled(false); + setLinkValue(''); + setLinkTitle(''); + setLinkValid(false); }; const validateLink = (link) => { diff --git a/pkg/interface/src/views/apps/notifications/notification.tsx b/pkg/interface/src/views/apps/notifications/notification.tsx index 5d97fbe59..a9734d8c1 100644 --- a/pkg/interface/src/views/apps/notifications/notification.tsx +++ b/pkg/interface/src/views/apps/notifications/notification.tsx @@ -13,7 +13,6 @@ import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction'; import { SwipeMenu } from '~/views/components/SwipeMenu'; import { GraphNotification } from './graph'; import { GroupNotification } from './group'; -import airlock from '~/logic/api'; import useHarkState from '~/logic/state/hark'; import shallow from 'zustand/shallow'; @@ -47,7 +46,7 @@ export function NotificationWrapper(props: { if (!notification || read) { return; } - return airlock.poke(readNote(notification.index)); + return readNote(notification.index); }; const { hovering, bind } = useHovering(); diff --git a/pkg/interface/src/views/apps/publish/components/NotePreview.tsx b/pkg/interface/src/views/apps/publish/components/NotePreview.tsx index 3f9ecc095..dab044895 100644 --- a/pkg/interface/src/views/apps/publish/components/NotePreview.tsx +++ b/pkg/interface/src/views/apps/publish/components/NotePreview.tsx @@ -66,15 +66,15 @@ export function NotePreview(props: NotePreviewProps) { const noteId = post.index.split('/')[1]; const url = `${props.baseUrl}/note/${noteId}`; - const [rev, title, body, content] = getLatestRevision(node); + const [, title, body] = getLatestRevision(node); const appPath = `/ship/${props.host}/${props.book}`; - const unreads = useHarkState(state => state.unreads); + const unreads = useHarkState(state => state.unreads.graph?.[appPath]); // @ts-ignore hark will have to choose between sets and numbers - const isUnread = unreads.graph?.[appPath]?.['/']?.unreads?.has(`/${noteId}/1/1`); + const isUnread = (unreads?.['/'].unreads ?? new Set()).has(`/${noteId}/1/1`); const snippet = getSnippet(body); - const commColor = (unreads.graph?.[appPath]?.[`/${noteId}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray'; + const commColor = (unreads?.[`/${noteId}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray'; const cursorStyle = post.pending ? 'default' : 'pointer'; diff --git a/pkg/interface/src/views/components/Comments.tsx b/pkg/interface/src/views/components/Comments.tsx index ae49b0188..d8106aed2 100644 --- a/pkg/interface/src/views/components/Comments.tsx +++ b/pkg/interface/src/views/components/Comments.tsx @@ -1,5 +1,5 @@ import { Col } from '@tlon/indigo-react'; -import { createPost, createBlankNodeWithChildPost, addNode, Association, GraphNode, Group, markCountAsRead, addPost } from '@urbit/api'; +import { createPost, createBlankNodeWithChildPost, Association, GraphNode, Group, markCountAsRead, addPost } from '@urbit/api'; import bigInt from 'big-integer'; import { FormikHelpers } from 'formik'; import React, { useEffect, useMemo } from 'react'; @@ -14,6 +14,7 @@ import { PropFunc } from '~/types/util'; import CommentInput from './CommentInput'; import { CommentItem } from './CommentItem'; import airlock from '~/logic/api'; +import useGraphState from '~/logic/state/graph'; interface CommentsProps { comments: GraphNode; @@ -35,6 +36,7 @@ export function Comments(props: CommentsProps & PropFunc) { group, ...rest } = props; + const addNode = useGraphState(s => s.addNode); const { query } = useQuery(); const selectedComment = useMemo(() => { @@ -54,12 +56,12 @@ export function Comments(props: CommentsProps & PropFunc) { try { const content = tokenizeMessage(comment); const node = createBlankNodeWithChildPost( - `~${window.ship}`, + window.ship, comments?.post?.index, '1', content ); - await airlock.thread(addNode(ship, name, node)); + addNode(ship, name, node); actions.resetForm(); actions.setStatus({ success: null }); } catch (e) { diff --git a/pkg/npm/api/graph/lib.ts b/pkg/npm/api/graph/lib.ts index a717e31e9..dd6cea035 100644 --- a/pkg/npm/api/graph/lib.ts +++ b/pkg/npm/api/graph/lib.ts @@ -345,7 +345,7 @@ export const removePosts = ( * @param ship recipient * @param contents contents of message */ -export const addDmMessage = (our: Patp, ship: Patp, contents: Content[]): Poke => { +export const addDmMessage = (our: PatpNoSig, ship: Patp, contents: Content[]): Poke => { const post = createPost(our, contents, `/${patp2dec(ship)}`); const node: GraphNode = { post, @@ -356,7 +356,7 @@ export const addDmMessage = (our: Patp, ship: Patp, contents: Content[]): Poke