mirror of
https://github.com/urbit/shrub.git
synced 2024-12-11 11:02:25 +03:00
ChatResource: refactor to use ChatPane
This commit is contained in:
parent
d635d596b8
commit
e937911536
@ -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', {
|
||||
|
@ -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 };
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user