mirror of
https://github.com/urbit/shrub.git
synced 2024-12-29 23:23:52 +03:00
chat-fe: MVP graphification
This commit is contained in:
parent
a1cf88faba
commit
403ef0a275
@ -67,6 +67,9 @@ function moduleToMark(mod: string): string | undefined {
|
|||||||
if(mod === 'publish') {
|
if(mod === 'publish') {
|
||||||
return 'graph-validator-publish';
|
return 'graph-validator-publish';
|
||||||
}
|
}
|
||||||
|
if(mod === 'chat') {
|
||||||
|
return 'graph-validator-chat';
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import urbitOb from 'urbit-ob';
|
||||||
|
|
||||||
const URL_REGEX = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source));
|
const URL_REGEX = new RegExp(String(/^((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/.source));
|
||||||
|
|
||||||
const isUrl = (string) => {
|
const isUrl = (string) => {
|
||||||
@ -45,11 +47,20 @@ const tokenizeMessage = (text) => {
|
|||||||
if (isUrl(str) && !isInCodeBlock) {
|
if (isUrl(str) && !isInCodeBlock) {
|
||||||
if (message.length > 0) {
|
if (message.length > 0) {
|
||||||
// If we're in the middle of a message, add it to the stack and reset
|
// If we're in the middle of a message, add it to the stack and reset
|
||||||
messages.push(message);
|
messages.push({ text: message.join('') });
|
||||||
message = [];
|
message = [];
|
||||||
}
|
}
|
||||||
messages.push([str]);
|
messages.push({ url: str });
|
||||||
message = [];
|
message = [];
|
||||||
|
} else if(urbitOb.isValidPatp(str) && !isInCodeBlock) {
|
||||||
|
if (message.length > 0) {
|
||||||
|
// If we're in the middle of a message, add it to the stack and reset
|
||||||
|
messages.push({ text: message.join('') });
|
||||||
|
message = [];
|
||||||
|
}
|
||||||
|
messages.push({ mention: str });
|
||||||
|
message = [];
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
message.push(str);
|
message.push(str);
|
||||||
}
|
}
|
||||||
@ -59,7 +70,7 @@ const tokenizeMessage = (text) => {
|
|||||||
|
|
||||||
if (message.length) {
|
if (message.length) {
|
||||||
// Add any remaining message
|
// Add any remaining message
|
||||||
messages.push(message);
|
messages.push({ text: message.join('') });
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
};
|
};
|
||||||
|
@ -8,9 +8,12 @@ export interface UrlContent {
|
|||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
export interface CodeContent {
|
export interface CodeContent {
|
||||||
expresssion: string;
|
code: {
|
||||||
output: string;
|
expresssion: string;
|
||||||
|
output: string | undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReferenceContent {
|
export interface ReferenceContent {
|
||||||
uid: string;
|
uid: string;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import ChatInput from './components/ChatInput';
|
|||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { SubmitDragger } from '~/views/components/s3-upload';
|
import { SubmitDragger } from '~/views/components/s3-upload';
|
||||||
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
|
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
|
||||||
|
import {Loading} from '~/views/components/Loading';
|
||||||
|
|
||||||
type ChatResourceProps = StoreState & {
|
type ChatResourceProps = StoreState & {
|
||||||
association: Association;
|
association: Association;
|
||||||
@ -31,6 +32,8 @@ export function ChatResource(props: ChatResourceProps) {
|
|||||||
const group = props.groups[groupPath];
|
const group = props.groups[groupPath];
|
||||||
const contacts = props.contacts[groupPath] || {};
|
const contacts = props.contacts[groupPath] || {};
|
||||||
|
|
||||||
|
const graph = props.graphs[station.slice(7)];
|
||||||
|
|
||||||
const pendingMessages = (props.pendingMessages.get(station) || []).map(
|
const pendingMessages = (props.pendingMessages.get(station) || []).map(
|
||||||
value => ({
|
value => ({
|
||||||
...value,
|
...value,
|
||||||
@ -38,12 +41,7 @@ export function ChatResource(props: ChatResourceProps) {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const isChatMissing =
|
const isChatMissing = !props.graphKeys.has(station.slice(7));
|
||||||
(props.chatInitialized &&
|
|
||||||
!(station in props.inbox) &&
|
|
||||||
props.chatSynced &&
|
|
||||||
!(station in props.chatSynced)) ||
|
|
||||||
false;
|
|
||||||
|
|
||||||
const isChatLoading =
|
const isChatLoading =
|
||||||
(props.chatInitialized &&
|
(props.chatInitialized &&
|
||||||
@ -61,12 +59,16 @@ export function ChatResource(props: ChatResourceProps) {
|
|||||||
const unreadCount = length - read;
|
const unreadCount = length - read;
|
||||||
const unreadMsg = unreadCount > 0 && envelopes[unreadCount - 1];
|
const unreadMsg = unreadCount > 0 && envelopes[unreadCount - 1];
|
||||||
|
|
||||||
const [, owner, name] = station.split('/');
|
const [,, owner, name] = station.split('/');
|
||||||
const ourContact = contacts?.[window.ship];
|
const ourContact = contacts?.[window.ship];
|
||||||
const lastMsgNum = envelopes.length || 0;
|
const lastMsgNum = envelopes.length || 0;
|
||||||
|
|
||||||
const chatInput = useRef<ChatInput>();
|
const chatInput = useRef<ChatInput>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
props.api.graph.getGraph(owner,name);
|
||||||
|
}, [station]);
|
||||||
|
|
||||||
const onFileDrag = useCallback(
|
const onFileDrag = useCallback(
|
||||||
(files: FileList) => {
|
(files: FileList) => {
|
||||||
if (!chatInput.current) {
|
if (!chatInput.current) {
|
||||||
@ -102,21 +104,26 @@ export function ChatResource(props: ChatResourceProps) {
|
|||||||
return clear;
|
return clear;
|
||||||
}, [station]);
|
}, [station]);
|
||||||
|
|
||||||
|
if(!graph) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col {...bind} height="100%" overflow="hidden" position="relative">
|
<Col {...bind} height="100%" overflow="hidden" position="relative">
|
||||||
{dragging && <SubmitDragger />}
|
{dragging && <SubmitDragger />}
|
||||||
<ChatWindow
|
<ChatWindow
|
||||||
remoteContentPolicy={props.remoteContentPolicy}
|
remoteContentPolicy={props.remoteContentPolicy}
|
||||||
mailboxSize={length}
|
mailboxSize={5}
|
||||||
match={props.match as any}
|
match={props.match as any}
|
||||||
stationPendingMessages={pendingMessages}
|
stationPendingMessages={[]}
|
||||||
history={props.history}
|
history={props.history}
|
||||||
isChatMissing={isChatMissing}
|
isChatMissing={isChatMissing}
|
||||||
isChatLoading={isChatLoading}
|
isChatLoading={isChatLoading}
|
||||||
isChatUnsynced={isChatUnsynced}
|
isChatUnsynced={isChatUnsynced}
|
||||||
unreadCount={unreadCount}
|
graph={graph}
|
||||||
unreadMsg={unreadMsg}
|
unreadCount={0}
|
||||||
envelopes={envelopes || []}
|
unreadMsg={false}
|
||||||
|
envelopes={[]}
|
||||||
contacts={contacts}
|
contacts={contacts}
|
||||||
association={props.association}
|
association={props.association}
|
||||||
group={group}
|
group={group}
|
||||||
|
@ -3,10 +3,11 @@ import ChatEditor from './chat-editor';
|
|||||||
import { S3Upload } from '~/views/components/s3-upload' ;
|
import { S3Upload } from '~/views/components/s3-upload' ;
|
||||||
import { uxToHex } from '~/logic/lib/util';
|
import { uxToHex } from '~/logic/lib/util';
|
||||||
import { Sigil } from '~/logic/lib/sigil';
|
import { Sigil } from '~/logic/lib/sigil';
|
||||||
|
import { createPost } from '~/logic/api/graph';
|
||||||
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
|
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
|
||||||
import GlobalApi from '~/logic/api/global';
|
import GlobalApi from '~/logic/api/global';
|
||||||
import { Envelope } from '~/types/chat-update';
|
import { Envelope } from '~/types/chat-update';
|
||||||
import { Contacts } from '~/types';
|
import { Contacts, Content } from '~/types';
|
||||||
import { Row, BaseImage, Box, Icon } from '@tlon/indigo-react';
|
import { Row, BaseImage, Box, Icon } from '@tlon/indigo-react';
|
||||||
|
|
||||||
interface ChatInputProps {
|
interface ChatInputProps {
|
||||||
@ -82,39 +83,23 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
|||||||
|
|
||||||
submit(text) {
|
submit(text) {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
|
const [,,ship,name] = props.station.split('/');
|
||||||
if (state.inCodeMode) {
|
if (state.inCodeMode) {
|
||||||
this.setState({
|
this.setState({
|
||||||
inCodeMode: false
|
inCodeMode: false
|
||||||
}, () => {
|
}, () => {
|
||||||
props.api.chat.message(
|
const contents: Content[] = [{ code: { expression: text, output: undefined }}];
|
||||||
props.station,
|
const post = createPost(contents);
|
||||||
`~${window.ship}`,
|
props.api.graph.addPost(ship, name, post);
|
||||||
Date.now(), {
|
|
||||||
code: {
|
|
||||||
expression: text,
|
|
||||||
output: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = tokenizeMessage(text);
|
const post = createPost(tokenizeMessage((text)))
|
||||||
|
|
||||||
props.deleteMessage();
|
props.deleteMessage();
|
||||||
|
|
||||||
messages.forEach((message) => {
|
props.api.graph.addPost(ship,name, post);
|
||||||
if (message.length > 0) {
|
|
||||||
message = this.getLetterType(message.join(' '));
|
|
||||||
props.api.chat.message(
|
|
||||||
props.station,
|
|
||||||
`~${window.ship}`,
|
|
||||||
Date.now(),
|
|
||||||
message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadSuccess(url) {
|
uploadSuccess(url) {
|
||||||
@ -123,12 +108,8 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
|||||||
this.chatEditor.current.editor.setValue(url);
|
this.chatEditor.current.editor.setValue(url);
|
||||||
this.setState({ uploadingPaste: false });
|
this.setState({ uploadingPaste: false });
|
||||||
} else {
|
} else {
|
||||||
props.api.chat.message(
|
const [,,ship,name] = props.station.split('/');
|
||||||
props.station,
|
props.api.graph.addPost(ship,name, createPost([{ url }]));
|
||||||
`~${window.ship}`,
|
|
||||||
Date.now(),
|
|
||||||
{ url }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,11 @@ import { Box, Row, Text, Rule } from "@tlon/indigo-react";
|
|||||||
import { OverlaySigil } from './overlay-sigil';
|
import { OverlaySigil } from './overlay-sigil';
|
||||||
import { uxToHex, cite, writeText } from '~/logic/lib/util';
|
import { uxToHex, cite, writeText } from '~/logic/lib/util';
|
||||||
import { Envelope, IMessage } from "~/types/chat-update";
|
import { Envelope, IMessage } from "~/types/chat-update";
|
||||||
import { Group, Association, Contacts, LocalUpdateRemoteContentPolicy } from "~/types";
|
import { Group, Association, Contacts, LocalUpdateRemoteContentPolicy, Post } from "~/types";
|
||||||
import TextContent from './content/text';
|
import TextContent from './content/text';
|
||||||
import CodeContent from './content/code';
|
import CodeContent from './content/code';
|
||||||
import RemoteContent from '~/views/components/RemoteContent';
|
import RemoteContent from '~/views/components/RemoteContent';
|
||||||
|
import { Mention } from "~/views/components/MentionText";
|
||||||
|
|
||||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||||
|
|
||||||
@ -30,9 +31,9 @@ export const DayBreak = ({ when }) => (
|
|||||||
|
|
||||||
interface ChatMessageProps {
|
interface ChatMessageProps {
|
||||||
measure(element): void;
|
measure(element): void;
|
||||||
msg: Envelope | IMessage;
|
msg: Post;
|
||||||
previousMsg?: Envelope | IMessage;
|
previousMsg?: Post;
|
||||||
nextMsg?: Envelope | IMessage;
|
nextMsg?: Post;
|
||||||
isLastRead: boolean;
|
isLastRead: boolean;
|
||||||
group: Group;
|
group: Group;
|
||||||
association: Association;
|
association: Association;
|
||||||
@ -91,13 +92,13 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
||||||
const dayBreak = nextMsg && new Date(msg.when).getDate() !== new Date(nextMsg.when).getDate();
|
const dayBreak = nextMsg && new Date(msg['time-sent']).getDate() !== new Date(nextMsg['time-sent']).getDate();
|
||||||
|
|
||||||
const containerClass = `${renderSigil
|
const containerClass = `${renderSigil
|
||||||
? `cf pl2 lh-copy`
|
? `cf pl2 lh-copy`
|
||||||
: `items-top cf hide-child`} ${isPending ? 'o-40' : ''} ${className}`
|
: `items-top cf hide-child`} ${isPending ? 'o-40' : ''} ${className}`
|
||||||
|
|
||||||
const timestamp = moment.unix(msg.when / 1000).format(renderSigil ? 'hh:mm a' : 'hh:mm');
|
const timestamp = moment.unix(msg['time-sent'] / 1000).format(renderSigil ? 'hh:mm a' : 'hh:mm');
|
||||||
|
|
||||||
const reboundMeasure = (event) => {
|
const reboundMeasure = (event) => {
|
||||||
return measure(this.divRef.current);
|
return measure(this.divRef.current);
|
||||||
@ -140,7 +141,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
ref={this.divRef}
|
ref={this.divRef}
|
||||||
className={containerClass}
|
className={containerClass}
|
||||||
style={style}
|
style={style}
|
||||||
data-number={msg.number}
|
|
||||||
mb={1}
|
mb={1}
|
||||||
>
|
>
|
||||||
{dayBreak && !isLastRead ? <DayBreak when={msg.when} /> : null}
|
{dayBreak && !isLastRead ? <DayBreak when={msg.when} /> : null}
|
||||||
@ -156,7 +156,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface MessageProps {
|
interface MessageProps {
|
||||||
msg: Envelope | IMessage;
|
msg: Post;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
group: Group;
|
group: Group;
|
||||||
association: Association;
|
association: Association;
|
||||||
@ -191,10 +191,10 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
|||||||
fontSize
|
fontSize
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const datestamp = moment.unix(msg.when / 1000).format(DATESTAMP_FORMAT);
|
const datestamp = moment.unix(msg['time-sent']).format(DATESTAMP_FORMAT);
|
||||||
const contact = msg.author in contacts ? contacts[msg.author] : false;
|
const contact = msg.author in contacts ? contacts[msg.author] : false;
|
||||||
const showNickname = !hideNicknames && contact && contact.nickname;
|
const showNickname = !hideNicknames && contact && contact.nickname;
|
||||||
const name = showNickname ? contact.nickname : cite(msg.author);
|
const name = showNickname ? contact!.nickname : cite(msg.author);
|
||||||
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
|
const color = contact ? `#${uxToHex(contact.color)}` : this.isDark ? '#000000' :'#FFFFFF'
|
||||||
const sigilClass = contact ? '' : this.isDark ? 'mix-blend-diff' : 'mix-blend-darken';
|
const sigilClass = contact ? '' : this.isDark ? 'mix-blend-diff' : 'mix-blend-darken';
|
||||||
|
|
||||||
@ -251,23 +251,32 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
|||||||
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
|
<Text flexShrink='0' gray mono className="v-mid">{timestamp}</Text>
|
||||||
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
|
<Text flexShrink={0} gray mono ml={2} className="v-mid child dn-s">{datestamp}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box flexShrink={0} fontSize={fontSize ? fontSize : '14px'}><MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure} fontSize={fontSize} /></Box>
|
<Box flexShrink={0} fontSize={fontSize ? fontSize : '14px'}>
|
||||||
|
{msg.contents.map(c =>
|
||||||
|
<MessageContent
|
||||||
|
contacts={contacts}
|
||||||
|
content={c}
|
||||||
|
remoteContentPolicy={remoteContentPolicy}
|
||||||
|
measure={measure}
|
||||||
|
fontSize={fontSize}
|
||||||
|
/>)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageWithoutSigil = ({ timestamp, msg, remoteContentPolicy, measure }) => (
|
export const MessageWithoutSigil = ({ timestamp, contacts, msg, remoteContentPolicy, measure }) => (
|
||||||
<>
|
<>
|
||||||
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
|
<Text flexShrink={0} mono gray display='inline-block' pt='2px' lineHeight='tall' className="child">{timestamp}</Text>
|
||||||
<Box flexShrink={0} fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
|
<Box flexShrink={0} fontSize='14px' className="clamp-message" style={{ flexGrow: 1 }}>
|
||||||
<MessageContent content={msg.letter} remoteContentPolicy={remoteContentPolicy} measure={measure}/>
|
{msg.contents.map(c => (<MessageContent contacts={contacts} content={c} remoteContentPolicy={remoteContentPolicy} measure={measure}/>))}
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const MessageContent = ({ content, remoteContentPolicy, measure, fontSize }) => {
|
export const MessageContent = ({ content, contacts, remoteContentPolicy, measure, fontSize }) => {
|
||||||
if ('code' in content) {
|
if ('code' in content) {
|
||||||
return <CodeContent content={content} />;
|
return <CodeContent content={content} />;
|
||||||
} else if ('url' in content) {
|
} else if ('url' in content) {
|
||||||
@ -286,15 +295,10 @@ export const MessageContent = ({ content, remoteContentPolicy, measure, fontSize
|
|||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
} else if ('me' in content) {
|
} else if ('text' in content) {
|
||||||
return (
|
|
||||||
<Text flexShrink={0} fontStyle='italic' fontSize={fontSize ? fontSize : '14px'} lineHeight='tall' color='black'>
|
|
||||||
{content.me}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if ('text' in content) {
|
|
||||||
return <TextContent fontSize={fontSize} content={content} />;
|
return <TextContent fontSize={fontSize} content={content} />;
|
||||||
|
} else if ('mention' in content) {
|
||||||
|
return <Mention ship={content.mention} contacts={contacts} />
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import { Contacts } from "~/types/contact-update";
|
|||||||
import { Association } from "~/types/metadata-update";
|
import { Association } from "~/types/metadata-update";
|
||||||
import { Group } from "~/types/group-update";
|
import { Group } from "~/types/group-update";
|
||||||
import { Envelope, IMessage } from "~/types/chat-update";
|
import { Envelope, IMessage } from "~/types/chat-update";
|
||||||
import { LocalUpdateRemoteContentPolicy } from "~/types";
|
import { LocalUpdateRemoteContentPolicy, Graph } from "~/types";
|
||||||
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
||||||
|
|
||||||
import VirtualScroller from "~/views/components/VirtualScroller";
|
import VirtualScroller from "~/views/components/VirtualScroller";
|
||||||
@ -29,13 +29,12 @@ type ChatWindowProps = RouteComponentProps<{
|
|||||||
station: string;
|
station: string;
|
||||||
}> & {
|
}> & {
|
||||||
unreadCount: number;
|
unreadCount: number;
|
||||||
envelopes: Envelope[];
|
|
||||||
isChatMissing: boolean;
|
isChatMissing: boolean;
|
||||||
isChatLoading: boolean;
|
isChatLoading: boolean;
|
||||||
isChatUnsynced: boolean;
|
isChatUnsynced: boolean;
|
||||||
unreadMsg: Envelope | false;
|
unreadMsg: Envelope | false;
|
||||||
stationPendingMessages: IMessage[];
|
stationPendingMessages: IMessage[];
|
||||||
mailboxSize: number;
|
graph: Graph;
|
||||||
contacts: Contacts;
|
contacts: Contacts;
|
||||||
association: Association;
|
association: Association;
|
||||||
group: Group;
|
group: Group;
|
||||||
@ -58,6 +57,7 @@ interface ChatWindowState {
|
|||||||
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
|
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
|
||||||
private virtualList: VirtualScroller | null;
|
private virtualList: VirtualScroller | null;
|
||||||
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||||
|
private prevSize = 0;
|
||||||
|
|
||||||
INITIALIZATION_MAX_TIME = 1500;
|
INITIALIZATION_MAX_TIME = 1500;
|
||||||
|
|
||||||
@ -112,6 +112,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialFetch() {
|
initialFetch() {
|
||||||
|
/*
|
||||||
const { envelopes, mailboxSize, unreadCount } = this.props;
|
const { envelopes, mailboxSize, unreadCount } = this.props;
|
||||||
if (envelopes.length > 0) {
|
if (envelopes.length > 0) {
|
||||||
const start = Math.min(mailboxSize - unreadCount, mailboxSize - DEFAULT_BACKLOG_SIZE);
|
const start = Math.min(mailboxSize - unreadCount, mailboxSize - DEFAULT_BACKLOG_SIZE);
|
||||||
@ -127,28 +128,37 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
this.initialFetch();
|
this.initialFetch();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: ChatWindowProps, prevState) {
|
componentDidUpdate(prevProps: ChatWindowProps, prevState) {
|
||||||
const { isChatMissing, history, envelopes, mailboxSize, stationPendingMessages, unreadCount, station } = this.props;
|
const { isChatMissing, history, graph, unreadCount, station } = this.props;
|
||||||
|
|
||||||
if (isChatMissing) {
|
if (isChatMissing) {
|
||||||
history.push("/~404");
|
history.push("/~404");
|
||||||
} else if (envelopes.length !== prevProps.envelopes.length && this.state.fetchPending) {
|
} else if (graph.size !== prevProps.graph.size && this.state.fetchPending) {
|
||||||
this.setState({ fetchPending: false });
|
this.setState({ fetchPending: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((mailboxSize !== prevProps.mailboxSize) || (envelopes.length !== prevProps.envelopes.length)) {
|
/*if ((mailboxSize !== prevProps.mailboxSize) || (envelopes.length !== prevProps.envelopes.length)) {
|
||||||
this.virtualList?.calculateVisibleItems();
|
this.virtualList?.calculateVisibleItems();
|
||||||
this.stayLockedIfActive();
|
this.stayLockedIfActive();
|
||||||
}
|
}*/
|
||||||
|
|
||||||
if (unreadCount > prevProps.unreadCount && this.state.idle) {
|
/*if (unreadCount > prevProps.unreadCount && this.state.idle) {
|
||||||
this.setState({
|
this.setState({
|
||||||
lastRead: unreadCount ? mailboxSize - unreadCount : -1,
|
lastRead: unreadCount ? mailboxSize - unreadCount : -1,
|
||||||
});
|
});
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
console.log(graph.size);
|
||||||
|
console.log(prevProps.graph.size);
|
||||||
|
|
||||||
|
if(this.prevSize !== graph.size) {
|
||||||
|
this.prevSize = graph.size;
|
||||||
|
this.virtualList?.calculateVisibleItems();
|
||||||
|
}
|
||||||
|
/*
|
||||||
if (stationPendingMessages.length !== prevProps.stationPendingMessages.length) {
|
if (stationPendingMessages.length !== prevProps.stationPendingMessages.length) {
|
||||||
this.virtualList?.calculateVisibleItems();
|
this.virtualList?.calculateVisibleItems();
|
||||||
}
|
}
|
||||||
@ -164,6 +174,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
lastRead: unreadCount ? mailboxSize - unreadCount : -1,
|
lastRead: unreadCount ? mailboxSize - unreadCount : -1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
stayLockedIfActive() {
|
stayLockedIfActive() {
|
||||||
@ -174,16 +185,18 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollToUnread() {
|
scrollToUnread() {
|
||||||
|
/*
|
||||||
const { mailboxSize, unreadCount, scrollTo } = this.props;
|
const { mailboxSize, unreadCount, scrollTo } = this.props;
|
||||||
const target = scrollTo || (mailboxSize - unreadCount);
|
const target = scrollTo || (mailboxSize - unreadCount);
|
||||||
this.virtualList?.scrollToData(target);
|
this.virtualList?.scrollToData(target);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissUnread() {
|
dismissUnread() {
|
||||||
if (this.state.fetchPending) return;
|
if (this.state.fetchPending) return;
|
||||||
if (this.props.unreadCount === 0) return;
|
if (this.props.unreadCount === 0) return;
|
||||||
this.props.api.chat.read(this.props.station);
|
//this.props.api.chat.read(this.props.station);
|
||||||
this.props.api.hark.readIndex({ chat: { chat: this.props.station, mention: false }});
|
//this.props.api.hark.readIndex({ chat: { chat: this.props.station, mention: false }});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchMessages(start, end, force = false): Promise<void> {
|
fetchMessages(start, end, force = false): Promise<void> {
|
||||||
@ -235,7 +248,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
envelopes,
|
|
||||||
stationPendingMessages,
|
stationPendingMessages,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
unreadMsg,
|
unreadMsg,
|
||||||
@ -248,6 +260,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
group,
|
group,
|
||||||
contacts,
|
contacts,
|
||||||
mailboxSize,
|
mailboxSize,
|
||||||
|
graph,
|
||||||
hideAvatars,
|
hideAvatars,
|
||||||
hideNicknames,
|
hideNicknames,
|
||||||
remoteContentPolicy,
|
remoteContentPolicy,
|
||||||
@ -256,27 +269,12 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
|
|
||||||
const unreadMarkerRef = this.unreadMarkerRef;
|
const unreadMarkerRef = this.unreadMarkerRef;
|
||||||
|
|
||||||
const messages = new BigIntOrderedMap();
|
|
||||||
let lastMessage = 0;
|
let lastMessage = 0;
|
||||||
|
|
||||||
[...envelopes]
|
|
||||||
.sort((a, b) => a.number - b.number)
|
|
||||||
.forEach(message => {
|
|
||||||
const num = bigInt(message.number);
|
|
||||||
messages.set(num, message);
|
|
||||||
lastMessage = message.number;
|
|
||||||
});
|
|
||||||
|
|
||||||
stationPendingMessages
|
|
||||||
.sort((a, b) => a.when - b.when)
|
|
||||||
.forEach((message, index) => {
|
|
||||||
const idx = bigInt(index + 1); // To 1-index it
|
|
||||||
messages.set(bigInt(mailboxSize).add(idx), message);
|
|
||||||
lastMessage = mailboxSize + index;
|
|
||||||
});
|
|
||||||
|
|
||||||
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy, unreadMarkerRef, history, api };
|
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy, unreadMarkerRef, history, api };
|
||||||
|
|
||||||
|
const keys = graph.keys().reverse();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UnreadNotice
|
<UnreadNotice
|
||||||
@ -296,10 +294,10 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
this.dismissUnread();
|
this.dismissUnread();
|
||||||
}}
|
}}
|
||||||
onScroll={this.onScroll.bind(this)}
|
onScroll={this.onScroll.bind(this)}
|
||||||
data={messages}
|
data={graph}
|
||||||
size={mailboxSize + stationPendingMessages.length}
|
size={graph.size}
|
||||||
renderer={({ index, measure, scrollWindow }) => {
|
renderer={({ index, measure, scrollWindow }) => {
|
||||||
const msg: Envelope | IMessage = messages.get(index);
|
const msg = graph.get(index)!.post;
|
||||||
if (!msg) return null;
|
if (!msg) return null;
|
||||||
if (!this.state.initialized) {
|
if (!this.state.initialized) {
|
||||||
return <MessagePlaceholder key={index.toString()} height="64px" index={index} />;
|
return <MessagePlaceholder key={index.toString()} height="64px" index={index} />;
|
||||||
@ -309,11 +307,14 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
|||||||
const isLastRead: boolean = Boolean(!isLastMessage && index.eq(bigInt(this.state.lastRead)));
|
const isLastRead: boolean = Boolean(!isLastMessage && index.eq(bigInt(this.state.lastRead)));
|
||||||
const highlighted = bigInt(this.props.scrollTo || -1).eq(index);
|
const highlighted = bigInt(this.props.scrollTo || -1).eq(index);
|
||||||
const props = { measure, highlighted, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
|
const props = { measure, highlighted, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
|
||||||
|
const graphIdx = keys.findIndex(idx => idx.eq(index));
|
||||||
|
const prevIdx = keys[graphIdx+1];
|
||||||
|
const nextIdx = keys[graphIdx-1];
|
||||||
return (
|
return (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={index.toString()}
|
key={index.toString()}
|
||||||
previousMsg={messages.get(index.add(bigInt.one))}
|
previousMsg={prevIdx && graph.get(prevIdx)?.post}
|
||||||
nextMsg={messages.get(index.subtract(bigInt.one))}
|
nextMsg={nextIdx && graph.get(nextIdx)?.post}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -37,7 +37,7 @@ export function MentionText(props: MentionTextProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Mention(props: { ship: string; contacts: Contacts }) {
|
export function Mention(props: { ship: string; contacts: Contacts }) {
|
||||||
const { contacts, ship } = props;
|
const { contacts, ship } = props;
|
||||||
const contact = contacts[ship];
|
const contact = contacts[ship];
|
||||||
const showNickname = !!contact?.nickname;
|
const showNickname = !!contact?.nickname;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent, Component } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
||||||
import normalizeWheel from 'normalize-wheel';
|
import normalizeWheel from 'normalize-wheel';
|
||||||
@ -34,10 +34,10 @@ interface VirtualScrollerState {
|
|||||||
scrollTop: number;
|
scrollTop: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class VirtualScroller extends PureComponent<VirtualScrollerProps, VirtualScrollerState> {
|
export default class VirtualScroller extends Component<VirtualScrollerProps, VirtualScrollerState> {
|
||||||
private scrollContainer: React.RefObject<HTMLDivElement>;
|
private scrollContainer: React.RefObject<HTMLDivElement>;
|
||||||
public window: HTMLDivElement | null;
|
public window: HTMLDivElement | null;
|
||||||
private cache: BigIntOrderedMap<BigInteger, any>;
|
private cache: BigIntOrderedMap<any>;
|
||||||
private pendingLoad: {
|
private pendingLoad: {
|
||||||
start: BigInteger;
|
start: BigInteger;
|
||||||
end: BigInteger
|
end: BigInteger
|
||||||
@ -144,8 +144,8 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
|
|||||||
|
|
||||||
|
|
||||||
//console.log([...items].map(([index]) => this.heightOf(index)));
|
//console.log([...items].map(([index]) => this.heightOf(index)));
|
||||||
const list = [...data];
|
//const list = [...data];
|
||||||
console.log(list[0][0].toString());
|
//console.log(list[0][0].toString());
|
||||||
// console.log(list[list.length - 1][0].toString());
|
// console.log(list[list.length - 1][0].toString());
|
||||||
[...data].forEach(([index, datum]) => {
|
[...data].forEach(([index, datum]) => {
|
||||||
const height = this.heightOf(index);
|
const height = this.heightOf(index);
|
||||||
|
@ -95,14 +95,11 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
string,
|
string,
|
||||||
string
|
string
|
||||||
>;
|
>;
|
||||||
const appName = app as AppName;
|
|
||||||
const isGraph = appIsGraph(app);
|
|
||||||
|
|
||||||
const resource = `${isGraph ? "/ship" : ""}/${host}/${name}`;
|
const appName = app as AppName;
|
||||||
const association =
|
|
||||||
isGraph
|
const resource = `/ship/${host}/${name}`;
|
||||||
? associations.graph[resource]
|
const association = associations.graph[resource]
|
||||||
: associations[appName][resource];
|
|
||||||
const resourceUrl = `${baseUrl}/resource/${app}${resource}`;
|
const resourceUrl = `${baseUrl}/resource/${app}${resource}`;
|
||||||
|
|
||||||
if (!association) {
|
if (!association) {
|
||||||
@ -133,10 +130,8 @@ export function GroupsPane(props: GroupsPaneProps) {
|
|||||||
path={relativePath("/join/:app/(ship)?/:host/:name")}
|
path={relativePath("/join/:app/(ship)?/:host/:name")}
|
||||||
render={(routeProps) => {
|
render={(routeProps) => {
|
||||||
const { app, host, name } = routeProps.match.params;
|
const { app, host, name } = routeProps.match.params;
|
||||||
const appName = app as AppName;
|
const appPath = `/ship/${host}/${name}`;
|
||||||
const isGraph = appIsGraph(app);
|
const association = associations.graph[appPath];
|
||||||
const appPath = `${isGraph ? '/ship/' : '/'}${host}/${name}`;
|
|
||||||
const association = isGraph ? associations.graph[appPath] : associations[appName][appPath];
|
|
||||||
const resourceUrl = `${baseUrl}/join/${app}${appPath}`;
|
const resourceUrl = `${baseUrl}/join/${app}${appPath}`;
|
||||||
|
|
||||||
if (!association) {
|
if (!association) {
|
||||||
|
@ -52,44 +52,22 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
|||||||
const resId: string = stringToSymbol(values.name);
|
const resId: string = stringToSymbol(values.name);
|
||||||
try {
|
try {
|
||||||
const { name, description, moduleType, ships } = values;
|
const { name, description, moduleType, ships } = values;
|
||||||
switch (moduleType) {
|
if (group) {
|
||||||
case 'chat':
|
await api.graph.createManagedGraph(
|
||||||
const appPath = `/~${window.ship}/${resId}`;
|
resId,
|
||||||
const groupPath = group || `/ship${appPath}`;
|
name,
|
||||||
|
description,
|
||||||
await api.chat.create(
|
group,
|
||||||
name,
|
moduleType
|
||||||
description,
|
);
|
||||||
appPath,
|
} else {
|
||||||
groupPath,
|
await api.graph.createUnmanagedGraph(
|
||||||
{ invite: { pending: ships.map(s => `~${s}`) } },
|
resId,
|
||||||
ships.map(s => `~${s}`),
|
name,
|
||||||
true,
|
description,
|
||||||
false
|
{ invite: { pending: ships.map((s) => `~${s}`) } },
|
||||||
);
|
moduleType
|
||||||
break;
|
);
|
||||||
case "publish":
|
|
||||||
case "link":
|
|
||||||
if (group) {
|
|
||||||
await api.graph.createManagedGraph(
|
|
||||||
resId,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
group,
|
|
||||||
moduleType
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await api.graph.createUnmanagedGraph(
|
|
||||||
resId,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
{ invite: { pending: ships.map((s) => `~${s}`) } },
|
|
||||||
moduleType
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log('fallthrough');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
@ -98,8 +76,7 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
|||||||
actions.setStatus({ success: null });
|
actions.setStatus({ success: null });
|
||||||
const resourceUrl = parentPath(location.pathname);
|
const resourceUrl = parentPath(location.pathname);
|
||||||
history.push(
|
history.push(
|
||||||
`${resourceUrl}/resource/${moduleType}` +
|
`${resourceUrl}/resource/${moduleType}/ship/~${window.ship}/${resId}`
|
||||||
`${moduleType !== 'chat' ? '/ship' : ''}/~${window.ship}/${resId}`
|
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
Loading…
Reference in New Issue
Block a user