From e937911536f6bf5c76ffe53b31486ec6d1ebcc79 Mon Sep 17 00:00:00 2001 From: Liam Fitzgerald Date: Wed, 28 Apr 2021 13:56:35 +1000 Subject: [PATCH] ChatResource: refactor to use ChatPane --- pkg/interface/src/logic/api/contacts.ts | 23 ++ .../src/views/apps/chat/ChatResource.tsx | 275 ++++++++---------- 2 files changed, 144 insertions(+), 154 deletions(-) diff --git a/pkg/interface/src/logic/api/contacts.ts b/pkg/interface/src/logic/api/contacts.ts index d4f799f691..bf8fe00fe8 100644 --- a/pkg/interface/src/logic/api/contacts.ts +++ b/pkg/interface/src/logic/api/contacts.ts @@ -2,6 +2,7 @@ import BaseApi from './base'; import { StoreState } from '../store/type'; import { Patp } from '@urbit/api'; import { ContactEdit } from '@urbit/api/contacts'; +import _ from 'lodash'; export default class ContactsApi extends BaseApi { add(ship: Patp, contact: any) { @@ -73,6 +74,28 @@ export default class ContactsApi extends BaseApi { ); } + async disallowedShipsForOurContact(ships: string[]): Promise { + return _.compact( + await Promise.all( + ships.map( + async s => { + const ship = `~${s}`; + if(s === window.ship) { + return null + } + const allowed = await this.fetchIsAllowed( + `~${window.ship}`, + 'personal', + ship, + true + ) + return allowed ? null : ship; + } + ) + ) + ); + } + retrieve(ship: string) { const resource = { ship, name: '' }; return this.action('contact-pull-hook', 'pull-hook-action', { diff --git a/pkg/interface/src/views/apps/chat/ChatResource.tsx b/pkg/interface/src/views/apps/chat/ChatResource.tsx index 17b74ad6a4..f335531c57 100644 --- a/pkg/interface/src/views/apps/chat/ChatResource.tsx +++ b/pkg/interface/src/views/apps/chat/ChatResource.tsx @@ -1,8 +1,14 @@ -import React, { useRef, useCallback, useEffect, useState } from 'react'; +import React, { + useRef, + useCallback, + useEffect, + useState, + useMemo, +} from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { Col } from '@tlon/indigo-react'; import _ from 'lodash'; -import bigInt from 'big-integer'; +import bigInt, { BigInteger } from 'big-integer'; import { Association } from '@urbit/api/metadata'; import { StoreState } from '~/logic/store/type'; @@ -16,13 +22,20 @@ import { useLocalStorageState } from '~/logic/lib/useLocalStorageState'; import { Loading } from '~/views/components/Loading'; import { isWriter, resourceFromPath } from '~/logic/lib/group'; -import './css/custom.css'; import useContactState from '~/logic/state/contact'; -import useGraphState from '~/logic/state/graph'; -import useGroupState from '~/logic/state/group'; +import useGraphState, { useGraphForAssoc } from '~/logic/state/graph'; +import useGroupState, { useGroupForAssoc } from '~/logic/state/group'; import useHarkState from '~/logic/state/hark'; -import {Post} from '@urbit/api'; -import {getPermalinkForGraph} from '~/logic/lib/permalinks'; +import { Content, createPost, Post } from '@urbit/api'; +import { getPermalinkForGraph } from '~/logic/lib/permalinks'; +import { ChatPane } from './components/ChatPane'; + +const getCurrGraphSize = (ship: string, name: string) => { + const { graphs } = useGraphState.getState(); + const graph = graphs[`${ship}/${name}`]; + return graph?.size ?? 0; +}; + type ChatResourceProps = StoreState & { association: Association; @@ -31,172 +44,126 @@ type ChatResourceProps = StoreState & { }; function ChatResource(props: ChatResourceProps) { - const station = props.association.resource; - const groupPath = props.association.group; - const groups = useGroupState(state => state.groups); - const group = groups[groupPath]; - const contacts = useContactState(state => state.contacts); - const graphs = useGraphState(state => state.graphs); - const graphPath = station.slice(7); - const graph = graphs[graphPath]; - const unreads = useHarkState(state => state.unreads); - 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}`]; - const chatInput = useRef(); - const canWrite = isWriter(group, station); + const { association, api } = props; + const { resource } = association; + const [toShare, setToShare] = useState(); + const group = useGroupForAssoc(association)!; + const graph = useGraphForAssoc(association); + const unreads = useHarkState((state) => state.unreads); + const unreadCount = + (unreads.graph?.[resource]?.['/']?.unreads as number) || 0; + const canWrite = group ? isWriter(group, resource) : false; useEffect(() => { const count = Math.min(400, 100 + unreadCount); - props.api.graph.getNewest(owner, name, count); - }, [station]); - - const onFileDrag = useCallback( - (files: FileList | File[]) => { - if (!chatInput.current) { - return; - } - chatInput.current?.uploadFiles(files); - }, - [chatInput.current] - ); - - const { bind, dragging } = useFileDrag(onFileDrag); - - const [unsent, setUnsent] = useLocalStorageState>( - 'chat-unsent', - {} - ); - - const appendUnsent = useCallback( - (u: string) => setUnsent(s => ({ ...s, [station]: u })), - [station] - ); - - const clearUnsent = useCallback( - () => { - setUnsent(s => { - if(station in s) { - return _.omit(s, station); - } - return s; - }); - }, - [station] - ); - - const scrollTo = new URLSearchParams(location.search).get('msg'); - - const [showBanner, setShowBanner] = useState(false); - const [hasLoadedAllowed, setHasLoadedAllowed] = useState(false); - const [recipients, setRecipients] = useState([]); - - const res = resourceFromPath(groupPath); - const onReply = useCallback((msg: Post) => { - const url = getPermalinkForGraph( - props.association.group, - props.association.resource, - msg.index - ); - const message = `${url}\n~${msg.author} : `; - setUnsent(s => ({...s, [props.association.resource]: message })); - }, [props.association, group, setUnsent]); - - useEffect(() => { - (async () => { - if (!res) { return; } - if (!group) { return; } - if (group.hidden) { - const members = _.compact(await Promise.all( + const { ship, name } = resourceFromPath(resource); + props.api.graph.getNewest(ship, name, count); + setToShare(undefined); + (async function() { + if(group.hidden) { + const members = await props.api.contacts.disallowedShipsForOurContact( Array.from(group.members) - .map(s => { - const ship = `~${s}`; - if(s === window.ship) { - return Promise.resolve(null); - } - return props.api.contacts.fetchIsAllowed( - `~${window.ship}`, - 'personal', - ship, - true - ).then(isAllowed => { - return isAllowed ? null : ship; - }); - }) - )); - + ); if(members.length > 0) { - setShowBanner(true); - setRecipients(members); - } else { - setShowBanner(false); + setToShare(members); } } else { - const groupShared = await props.api.contacts.fetchIsAllowed( + const { ship: groupHost } = resourceFromPath(association.group); + const shared = await props.api.contacts.fetchIsAllowed( `~${window.ship}`, 'personal', - res.ship, + groupHost, true ); - setShowBanner(!groupShared); + if(!shared) { + setToShare(association.group); + } } - - setHasLoadedAllowed(true); })(); - }, [groupPath, group]); + }, [resource]); - if(!graph) { + const onReply = useCallback( + (msg: Post) => { + const url = getPermalinkForGraph( + props.association.group, + props.association.resource, + msg.index + ); + return `${url}\n~${msg.author} : `; + }, + [association] + ); + + const isAdmin = useMemo( + () => (group ? group.tags.role.admin.has(`~${window.ship}`) : false), + [group] + ); + + const fetchMessages = useCallback(async (newer: boolean) => { + const { api } = props; + const pageSize = 100; + + const [, , ship, name] = resource.split('/'); + const graphSize = graph?.size ?? 0; + const expectedSize = graphSize + pageSize; + if (newer) { + const index = graph.peekLargest()?.[0]; + if(!index) { + return true; + } + await api.graph.getYoungerSiblings( + ship, + name, + pageSize, + `/${index.toString()}` + ); + return expectedSize !== getCurrGraphSize(ship.slice(1), name); + } else { + const index = graph.peekSmallest()?.[0]; + if(!index) { + return true; + } + await api.graph.getOlderSiblings(ship, name, pageSize, `/${index.toString()}`); + const done = expectedSize !== getCurrGraphSize(ship.slice(1), name); + return done; + } + }, [graph, resource]); + + const onSubmit = useCallback((contents: Content[]) => { + const { ship, name } = resourceFromPath(resource); + api.graph.addPost(ship, name, createPost(window.ship, contents)) + }, [resource]); + + const dismissUnread = useCallback(() => { + api.hark.markCountAsRead(association, '/', 'message'); + }, [association]); + + const getPermalink = useCallback( + (index: BigInteger) => + getPermalinkForGraph(association.group, resource, `/${index.toString()}`), + [association] + ); + + if (!graph) { return ; } return ( - - - {dragging && } - - { canWrite && ( - )} - + ); } -ChatResource.whyDidYouRender = true; - export { ChatResource }; -