ChatResource: refactor to use ChatPane

This commit is contained in:
Liam Fitzgerald 2021-04-28 13:56:35 +10:00
parent d635d596b8
commit e937911536
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
2 changed files with 144 additions and 154 deletions

View File

@ -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<StoreState> {
add(ship: Patp, contact: any) {
@ -73,6 +74,28 @@ export default class ContactsApi extends BaseApi<StoreState> {
);
}
async disallowedShipsForOurContact(ships: string[]): Promise<string[]> {
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', {

View File

@ -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<ChatInput>();
const canWrite = isWriter(group, station);
const { association, api } = props;
const { resource } = association;
const [toShare, setToShare] = useState<string[] | string | undefined>();
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<Record<string, string>>(
'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 <Loading />;
}
return (
<Col {...bind} height="100%" overflow="hidden" position="relative">
<ShareProfile
our={ourContact}
api={props.api}
recipient={owner}
recipients={recipients}
showBanner={showBanner}
setShowBanner={setShowBanner}
group={group}
groupPath={groupPath}
/>
{dragging && <SubmitDragger />}
<ChatWindow
key={station}
graph={graph}
graphSize={graph.size}
unreadCount={unreadCount as number}
showOurContact={ !showBanner && hasLoadedAllowed }
association={props.association}
pendingSize={Object.keys(graphTimesentMap[graphPath] || {}).length}
group={group}
ship={owner}
onReply={onReply}
station={station}
api={props.api}
scrollTo={scrollTo ? bigInt(scrollTo) : undefined}
/>
{ canWrite && (
<ChatInput
ref={chatInput}
api={props.api}
station={station}
ourContact={
(!showBanner && hasLoadedAllowed) ? ourContact : null
}
envelopes={[]}
onUnmount={appendUnsent}
placeholder="Message..."
message={unsent[station] || ''}
deleteMessage={clearUnsent}
/> )}
</Col>
<ChatPane
id={resource.slice(7)}
graph={graph}
unreadCount={unreadCount}
api={api}
canWrite={canWrite}
onReply={onReply}
fetchMessages={fetchMessages}
dismissUnread={dismissUnread}
getPermalink={getPermalink}
isAdmin={isAdmin}
onSubmit={onSubmit}
promptShare={toShare}
/>
);
}
ChatResource.whyDidYouRender = true;
export { ChatResource };