mirror of
https://github.com/urbit/shrub.git
synced 2024-12-25 13:04:17 +03:00
chat: adding localstorage sync and cleanup
This commit is contained in:
parent
a00ab9e347
commit
fa5e974ad4
@ -4,6 +4,9 @@ module.exports = {
|
|||||||
'jest': true
|
'jest': true
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
// Because we use styled system, and use
|
||||||
|
// the convention of each prop on a new line
|
||||||
|
// we probably shouldn't keep this on
|
||||||
'max-lines-per-function': ['off', {}]
|
'max-lines-per-function': ['off', {}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -113,7 +113,6 @@ interface ChatEditorProps {
|
|||||||
inCodeMode: boolean;
|
inCodeMode: boolean;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
submit: (message: string) => void;
|
submit: (message: string) => void;
|
||||||
onUnmount: (message: string) => void;
|
|
||||||
onPaste: (codemirrorInstance, event: ClipboardEvent) => void;
|
onPaste: (codemirrorInstance, event: ClipboardEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,18 +5,16 @@ import tokenizeMessage from '~/logic/lib/tokenizeMessage';
|
|||||||
import useStorage, { IuseStorage } from '~/logic/lib/useStorage';
|
import useStorage, { IuseStorage } from '~/logic/lib/useStorage';
|
||||||
import { MOBILE_BROWSER_REGEX } from '~/logic/lib/util';
|
import { MOBILE_BROWSER_REGEX } from '~/logic/lib/util';
|
||||||
import { withLocalState } from '~/logic/state/local';
|
import { withLocalState } from '~/logic/state/local';
|
||||||
import withStorage from '~/views/components/withStorage';
|
|
||||||
import ChatEditor, { CodeMirrorShim } from './ChatEditor';
|
import ChatEditor, { CodeMirrorShim } from './ChatEditor';
|
||||||
import airlock from '~/logic/api';
|
import airlock from '~/logic/api';
|
||||||
import { ChatAvatar } from './ChatAvatar';
|
import { ChatAvatar } from './ChatAvatar';
|
||||||
import { useChatStore } from './ChatPane';
|
import { useChatStore } from './ChatPane';
|
||||||
|
import { useImperativeHandle } from 'react';
|
||||||
|
|
||||||
type ChatInputProps = PropsWithChildren<IuseStorage & {
|
type ChatInputProps = PropsWithChildren<IuseStorage & {
|
||||||
hideAvatars: boolean;
|
hideAvatars: boolean;
|
||||||
ourContact?: Contact;
|
ourContact?: Contact;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
onUnmount(msg: string): void;
|
|
||||||
deleteMessage(): void;
|
|
||||||
onSubmit: (contents: Content[]) => void;
|
onSubmit: (contents: Content[]) => void;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@ -69,8 +67,9 @@ const MobileSubmitButton = ({ enabled, onSubmit }) => (
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
export function ChatInput({ ourContact, hideAvatars, placeholder, onSubmit, onUnmount }: ChatInputProps) {
|
export const ChatInput = React.forwardRef(({ ourContact, hideAvatars, placeholder, onSubmit }: ChatInputProps, ref) => {
|
||||||
const chatEditor = useRef<CodeMirrorShim>(null);
|
const chatEditor = useRef<CodeMirrorShim>(null);
|
||||||
|
useImperativeHandle(ref, () => ({ uploadFiles }));
|
||||||
const {
|
const {
|
||||||
message,
|
message,
|
||||||
setMessage
|
setMessage
|
||||||
@ -104,15 +103,6 @@ export function ChatInput({ ourContact, hideAvatars, placeholder, onSubmit, onUn
|
|||||||
chatEditor.current.focus();
|
chatEditor.current.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadSuccess(url: string) {
|
|
||||||
if (uploadingPaste) {
|
|
||||||
chatEditor.current.setValue(url);
|
|
||||||
setUploadingPaste(false);
|
|
||||||
} else {
|
|
||||||
onSubmit([{ url }]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPaste(codemirrorInstance, event: React.ClipboardEvent<HTMLTextAreaElement>) {
|
function onPaste(codemirrorInstance, event: React.ClipboardEvent<HTMLTextAreaElement>) {
|
||||||
if (!event.clipboardData || !event.clipboardData.files.length) {
|
if (!event.clipboardData || !event.clipboardData.files.length) {
|
||||||
return;
|
return;
|
||||||
@ -130,11 +120,24 @@ export function ChatInput({ ourContact, hideAvatars, placeholder, onSubmit, onUn
|
|||||||
}
|
}
|
||||||
Array.from(files).forEach((file) => {
|
Array.from(files).forEach((file) => {
|
||||||
uploadDefault(file)
|
uploadDefault(file)
|
||||||
.then(this.uploadSuccess)
|
.then(uploadSuccess)
|
||||||
.catch(this.uploadError);
|
.catch(uploadError);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uploadSuccess(url: string) {
|
||||||
|
if (uploadingPaste) {
|
||||||
|
chatEditor.current.setValue(url);
|
||||||
|
setUploadingPaste(false);
|
||||||
|
} else {
|
||||||
|
onSubmit([{ url }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadError(error: Error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputBox>
|
<InputBox>
|
||||||
<Row p='12px 4px 12px 12px' flexShrink={0} alignItems='center'>
|
<Row p='12px 4px 12px 12px' flexShrink={0} alignItems='center'>
|
||||||
@ -144,7 +147,6 @@ export function ChatInput({ ourContact, hideAvatars, placeholder, onSubmit, onUn
|
|||||||
ref={chatEditor}
|
ref={chatEditor}
|
||||||
inCodeMode={inCodeMode}
|
inCodeMode={inCodeMode}
|
||||||
submit={submit}
|
submit={submit}
|
||||||
onUnmount={onUnmount}
|
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
@ -181,11 +183,10 @@ export function ChatInput({ ourContact, hideAvatars, placeholder, onSubmit, onUn
|
|||||||
)}
|
)}
|
||||||
</InputBox>
|
</InputBox>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
// @ts-ignore withLocalState prop passing weirdness
|
// @ts-ignore withLocalState prop passing weirdness
|
||||||
export default withLocalState<Omit<ChatInputProps, keyof IuseStorage>, 'hideAvatars', ChatInput>(
|
export default withLocalState<Omit<ChatInputProps, keyof IuseStorage>, 'hideAvatars', ChatInput>(
|
||||||
// @ts-ignore withLocalState prop passing weirdness
|
ChatInput,
|
||||||
withStorage<ChatInputProps, ChatInput>(ChatInput, { accept: 'image/*' }),
|
|
||||||
['hideAvatars']
|
['hideAvatars']
|
||||||
);
|
);
|
||||||
|
@ -1,39 +1,40 @@
|
|||||||
import { Col } from '@tlon/indigo-react';
|
import { Col } from '@tlon/indigo-react';
|
||||||
import { Content, Graph, Post } from '@urbit/api';
|
import { Content, Graph, Post } from '@urbit/api';
|
||||||
import bigInt, { BigInteger } from 'big-integer';
|
import bigInt, { BigInteger } from 'big-integer';
|
||||||
import _ from 'lodash';
|
|
||||||
import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
|
import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import create from 'zustand';
|
import create from 'zustand';
|
||||||
import { useFileDrag } from '~/logic/lib/useDrag';
|
import { useFileDrag } from '~/logic/lib/useDrag';
|
||||||
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
|
import { retrieve } from '~/logic/lib/useLocalStorageState';
|
||||||
import { useOurContact } from '~/logic/state/contact';
|
import { useOurContact } from '~/logic/state/contact';
|
||||||
import { useGraphTimesent } from '~/logic/state/graph';
|
import { useGraphTimesent } from '~/logic/state/graph';
|
||||||
import ShareProfile from '~/views/apps/chat/components/ShareProfile';
|
import ShareProfile from '~/views/apps/chat/components/ShareProfile';
|
||||||
import { Loading } from '~/views/components/Loading';
|
import { Loading } from '~/views/components/Loading';
|
||||||
import SubmitDragger from '~/views/components/SubmitDragger';
|
import SubmitDragger from '~/views/components/SubmitDragger';
|
||||||
import ChatInput, { ChatInput as NakedChatInput } from './ChatInput';
|
import ChatInput from './ChatInput';
|
||||||
import ChatWindow from './ChatWindow';
|
import ChatWindow from './ChatWindow';
|
||||||
|
|
||||||
interface useChatStoreType {
|
interface useChatStoreType {
|
||||||
|
id: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
messageStore: Record<string, string>;
|
||||||
setMessage: (message: string) => void;
|
setMessage: (message: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useChatStore = create<useChatStoreType>(set => ({
|
const unsentKey = 'chat-unsent';
|
||||||
|
|
||||||
|
export const useChatStore = create<useChatStoreType>((set, get) => ({
|
||||||
|
id: '',
|
||||||
message: '',
|
message: '',
|
||||||
setMessage: (message: string) => set({ message })
|
messageStore: retrieve(unsentKey, {}),
|
||||||
|
setMessage: (message: string) => {
|
||||||
|
const store = get().messageStore;
|
||||||
|
store[get().id] = message;
|
||||||
|
|
||||||
|
localStorage.setItem(unsentKey, JSON.stringify(store));
|
||||||
|
set({ message });
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// const unsentKey = 'chat-unsent';
|
|
||||||
|
|
||||||
// export const useChatStore = create<useChatStoreType>(set => ({
|
|
||||||
// message: retrieve(unsentKey, ''),
|
|
||||||
// setMessage: (message: string) => {
|
|
||||||
// set({ message });
|
|
||||||
// localStorage.setItem(unsentKey, message);
|
|
||||||
// }
|
|
||||||
// }));
|
|
||||||
|
|
||||||
interface ChatPaneProps {
|
interface ChatPaneProps {
|
||||||
/**
|
/**
|
||||||
* A key to uniquely identify a ChatPane instance. Should be either the
|
* A key to uniquely identify a ChatPane instance. Should be either the
|
||||||
@ -100,7 +101,7 @@ export function ChatPane(props: ChatPaneProps): ReactElement {
|
|||||||
} = props;
|
} = props;
|
||||||
const graphTimesentMap = useGraphTimesent(id);
|
const graphTimesentMap = useGraphTimesent(id);
|
||||||
const ourContact = useOurContact();
|
const ourContact = useOurContact();
|
||||||
const chatInput = useRef<NakedChatInput>();
|
const chatInput = useRef<{ uploadFiles: (files: FileList | File[]) => void }>();
|
||||||
const setMessage = useChatStore(s => s.setMessage);
|
const setMessage = useChatStore(s => s.setMessage);
|
||||||
|
|
||||||
const onFileDrag = useCallback(
|
const onFileDrag = useCallback(
|
||||||
@ -108,29 +109,19 @@ export function ChatPane(props: ChatPaneProps): ReactElement {
|
|||||||
if (!chatInput.current) {
|
if (!chatInput.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(chatInput.current as NakedChatInput)?.uploadFiles(files);
|
chatInput.current?.uploadFiles(files);
|
||||||
},
|
},
|
||||||
[chatInput]
|
[chatInput]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { bind, dragging } = useFileDrag(onFileDrag);
|
const { bind, dragging } = useFileDrag(onFileDrag);
|
||||||
|
|
||||||
const [, setUnsent] = useLocalStorageState<Record<string, string>>(
|
useEffect(() => {
|
||||||
'chat-unsent',
|
const messageStore = retrieve(unsentKey, {});
|
||||||
{}
|
useChatStore.setState({
|
||||||
);
|
id,
|
||||||
|
messageStore,
|
||||||
const appendUnsent = useCallback(
|
message: messageStore[id] || ''
|
||||||
(u: string) => setUnsent(s => ({ ...s, [id]: u })),
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const clearUnsent = useCallback(() => {
|
|
||||||
setUnsent((s) => {
|
|
||||||
if (id in s) {
|
|
||||||
return _.omit(s, id);
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
});
|
});
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
@ -145,8 +136,6 @@ export function ChatPane(props: ChatPaneProps): ReactElement {
|
|||||||
const onReply = useCallback(
|
const onReply = useCallback(
|
||||||
(msg: Post) => {
|
(msg: Post) => {
|
||||||
const message = props.onReply(msg);
|
const message = props.onReply(msg);
|
||||||
console.log(message);
|
|
||||||
// setUnsent(s => ({ ...s, [id]: message }));
|
|
||||||
setMessage(message);
|
setMessage(message);
|
||||||
},
|
},
|
||||||
[id, props.onReply]
|
[id, props.onReply]
|
||||||
@ -185,9 +174,7 @@ export function ChatPane(props: ChatPaneProps): ReactElement {
|
|||||||
ref={chatInput}
|
ref={chatInput}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
ourContact={(promptShare.length === 0 && ourContact) || undefined}
|
ourContact={(promptShare.length === 0 && ourContact) || undefined}
|
||||||
onUnmount={appendUnsent}
|
|
||||||
placeholder="Message..."
|
placeholder="Message..."
|
||||||
deleteMessage={clearUnsent}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
Loading…
Reference in New Issue
Block a user