mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-28 19:55:53 +03:00
groups: handle s3 upload failures in chat
This commit is contained in:
parent
836cdb2478
commit
9db2024676
@ -1,6 +1,14 @@
|
||||
import { Box, Icon, LoadingSpinner, Row } from '@tlon/indigo-react';
|
||||
import { Box, Col, Icon, LoadingSpinner, Row, Text } from '@tlon/indigo-react';
|
||||
import { Contact, Content, evalCord } from '@urbit/api';
|
||||
import React, { FC, PropsWithChildren, useRef, useState } from 'react';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
import React, {
|
||||
FC,
|
||||
PropsWithChildren,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import tokenizeMessage from '~/logic/lib/tokenizeMessage';
|
||||
import { IuseStorage } from '~/logic/lib/useStorage';
|
||||
import { MOBILE_BROWSER_REGEX } from '~/logic/lib/util';
|
||||
@ -11,41 +19,54 @@ import { ChatAvatar } from './ChatAvatar';
|
||||
import { useChatStore } from './ChatPane';
|
||||
import { useImperativeHandle } from 'react';
|
||||
import { FileUploadSource, useFileUpload } from '~/logic/lib/useFileUpload';
|
||||
import { Portal } from '~/views/components/Portal';
|
||||
import styled from 'styled-components';
|
||||
import { useOutsideClick } from '~/logic/lib/useOutsideClick';
|
||||
|
||||
type ChatInputProps = PropsWithChildren<IuseStorage & {
|
||||
hideAvatars: boolean;
|
||||
ourContact?: Contact;
|
||||
placeholder: string;
|
||||
onSubmit: (contents: Content[]) => void;
|
||||
}>;
|
||||
const FixedOverlay = styled(Col)`
|
||||
position: fixed;
|
||||
-webkit-transition: all 0.1s ease-out;
|
||||
-moz-transition: all 0.1s ease-out;
|
||||
-o-transition: all 0.1s ease-out;
|
||||
transition: all 0.1s ease-out;
|
||||
`;
|
||||
|
||||
type ChatInputProps = PropsWithChildren<
|
||||
IuseStorage & {
|
||||
hideAvatars: boolean;
|
||||
ourContact?: Contact;
|
||||
placeholder: string;
|
||||
onSubmit: (contents: Content[]) => void;
|
||||
}
|
||||
>;
|
||||
|
||||
const InputBox: FC = ({ children }) => (
|
||||
<Row
|
||||
alignItems='center'
|
||||
position='relative'
|
||||
alignItems="center"
|
||||
position="relative"
|
||||
flexGrow={1}
|
||||
flexShrink={0}
|
||||
borderTop={1}
|
||||
borderTopColor='lightGray'
|
||||
backgroundColor='white'
|
||||
className='cf'
|
||||
borderTopColor="lightGray"
|
||||
backgroundColor="white"
|
||||
className="cf"
|
||||
zIndex={0}
|
||||
>
|
||||
{ children }
|
||||
{children}
|
||||
</Row>
|
||||
);
|
||||
|
||||
const IconBox = ({ children, ...props }) => (
|
||||
<Box
|
||||
ml='12px'
|
||||
ml="12px"
|
||||
mr={3}
|
||||
flexShrink={0}
|
||||
height='16px'
|
||||
width='16px'
|
||||
flexBasis='16px'
|
||||
height="16px"
|
||||
width="16px"
|
||||
flexBasis="16px"
|
||||
{...props}
|
||||
>
|
||||
{ children }
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -68,99 +89,153 @@ const MobileSubmitButton = ({ enabled, onSubmit }) => (
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const ChatInput = React.forwardRef(({ ourContact, hideAvatars, placeholder, onSubmit }: ChatInputProps, ref) => {
|
||||
const chatEditor = useRef<CodeMirrorShim>(null);
|
||||
useImperativeHandle(ref, () => chatEditor.current);
|
||||
const [inCodeMode, setInCodeMode] = useState(false);
|
||||
export const ChatInput = React.forwardRef(
|
||||
({ ourContact, hideAvatars, placeholder, onSubmit }: ChatInputProps, ref) => {
|
||||
const chatEditor = useRef<CodeMirrorShim>(null);
|
||||
useImperativeHandle(ref, () => chatEditor.current);
|
||||
const [inCodeMode, setInCodeMode] = useState(false);
|
||||
const [uploadError, setUploadError] = useState<string>('');
|
||||
const [showPortal, setShowPortal] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const innerRef = useRef<HTMLDivElement>(null);
|
||||
const outerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {
|
||||
message,
|
||||
setMessage
|
||||
} = useChatStore();
|
||||
const { canUpload, uploading, promptUpload, onPaste } = useFileUpload({
|
||||
onSuccess: uploadSuccess
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
setShowPortal(false);
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
function uploadSuccess(url: string, source: FileUploadSource) {
|
||||
if (source === 'paste') {
|
||||
setMessage(url);
|
||||
} else {
|
||||
onSubmit([{ url }]);
|
||||
}
|
||||
}
|
||||
useOutsideClick(innerRef, () => setShowPortal(false));
|
||||
|
||||
function toggleCode() {
|
||||
setInCodeMode(!inCodeMode);
|
||||
}
|
||||
const handleError = useCallback((err: Error) => {
|
||||
setUploadError(err.message);
|
||||
}, []);
|
||||
|
||||
async function submit() {
|
||||
const text = chatEditor.current?.getValue() || '';
|
||||
const { message, setMessage } = useChatStore();
|
||||
const { canUpload, uploading, promptUpload, onPaste } = useFileUpload({
|
||||
onSuccess: uploadSuccess,
|
||||
onError: handleError
|
||||
});
|
||||
|
||||
if (text === '') {
|
||||
return;
|
||||
function uploadSuccess(url: string, source: FileUploadSource) {
|
||||
if (source === 'paste') {
|
||||
setMessage(url);
|
||||
} else {
|
||||
onSubmit([{ url }]);
|
||||
}
|
||||
}
|
||||
|
||||
if (inCodeMode) {
|
||||
const output = await airlock.thread<string[]>(evalCord(text));
|
||||
onSubmit([{ code: { output, expression: text } }]);
|
||||
} else {
|
||||
onSubmit(tokenizeMessage(text));
|
||||
function toggleCode() {
|
||||
setInCodeMode(!inCodeMode);
|
||||
}
|
||||
|
||||
setInCodeMode(false);
|
||||
setMessage('');
|
||||
chatEditor.current.focus();
|
||||
}
|
||||
async function submit() {
|
||||
const text = chatEditor.current?.getValue() || '';
|
||||
|
||||
return (
|
||||
<InputBox>
|
||||
<Row p='12px 4px 12px 12px' flexShrink={0} alignItems='center'>
|
||||
<ChatAvatar contact={ourContact} hideAvatars={hideAvatars} />
|
||||
</Row>
|
||||
<ChatEditor
|
||||
ref={chatEditor}
|
||||
inCodeMode={inCodeMode}
|
||||
submit={submit}
|
||||
onPaste={(cm, e) => onPaste(e)}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<IconBox mr={canUpload ? '12px' : 3}>
|
||||
<Icon
|
||||
icon='Dojo'
|
||||
cursor='pointer'
|
||||
onClick={toggleCode}
|
||||
color={inCodeMode ? 'blue' : 'black'}
|
||||
/>
|
||||
</IconBox>
|
||||
{canUpload && (
|
||||
<IconBox>
|
||||
{uploading ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<Icon
|
||||
icon='Attachment'
|
||||
cursor='pointer'
|
||||
width='16'
|
||||
height='16'
|
||||
onClick={() =>
|
||||
promptUpload().then(url => uploadSuccess(url, 'direct'))
|
||||
}
|
||||
if (text === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inCodeMode) {
|
||||
const output = await airlock.thread<string[]>(evalCord(text));
|
||||
onSubmit([{ code: { output, expression: text } }]);
|
||||
} else {
|
||||
onSubmit(tokenizeMessage(text));
|
||||
}
|
||||
|
||||
setInCodeMode(false);
|
||||
setMessage('');
|
||||
chatEditor.current.focus();
|
||||
}
|
||||
|
||||
return (
|
||||
<Box ref={outerRef}>
|
||||
<VisibilitySensor active={showPortal} onChange={setVisible}>
|
||||
<InputBox>
|
||||
{showPortal && (
|
||||
<Portal>
|
||||
<FixedOverlay
|
||||
ref={innerRef}
|
||||
backgroundColor="white"
|
||||
color="washedGray"
|
||||
border={1}
|
||||
right={25}
|
||||
bottom={75}
|
||||
borderRadius={2}
|
||||
borderColor="lightGray"
|
||||
boxShadow="0px 0px 0px 3px"
|
||||
zIndex={3}
|
||||
fontSize={0}
|
||||
height="75px"
|
||||
width="250px"
|
||||
padding={3}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Text>{uploadError}</Text>
|
||||
<Text>Please check S3 settings.</Text>
|
||||
</FixedOverlay>
|
||||
</Portal>
|
||||
)}
|
||||
<Row p="12px 4px 12px 12px" flexShrink={0} alignItems="center">
|
||||
<ChatAvatar contact={ourContact} hideAvatars={hideAvatars} />
|
||||
</Row>
|
||||
<ChatEditor
|
||||
ref={chatEditor}
|
||||
inCodeMode={inCodeMode}
|
||||
submit={submit}
|
||||
onPaste={(cm, e) => onPaste(e)}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
)}
|
||||
</IconBox>
|
||||
)}
|
||||
{MOBILE_BROWSER_REGEX.test(navigator.userAgent) && (
|
||||
<MobileSubmitButton
|
||||
enabled={message !== ''}
|
||||
onSubmit={submit}
|
||||
/>
|
||||
)}
|
||||
</InputBox>
|
||||
);
|
||||
});
|
||||
<IconBox mr={canUpload ? '12px' : 3}>
|
||||
<Icon
|
||||
icon="Dojo"
|
||||
cursor="pointer"
|
||||
onClick={toggleCode}
|
||||
color={inCodeMode ? 'blue' : 'black'}
|
||||
/>
|
||||
</IconBox>
|
||||
{canUpload && (
|
||||
<IconBox>
|
||||
{uploading ? (
|
||||
uploadError != '' ? (
|
||||
<Icon
|
||||
icon="ExclaimationMark"
|
||||
cursor="pointer"
|
||||
onClick={() => setShowPortal(true)}
|
||||
/>
|
||||
) : (
|
||||
<LoadingSpinner />
|
||||
)
|
||||
) : (
|
||||
<Icon
|
||||
icon="Attachment"
|
||||
cursor="pointer"
|
||||
width="16"
|
||||
height="16"
|
||||
onClick={() =>
|
||||
promptUpload(handleError).then(url =>
|
||||
uploadSuccess(url, 'direct')
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</IconBox>
|
||||
)}
|
||||
{MOBILE_BROWSER_REGEX.test(navigator.userAgent) && (
|
||||
<MobileSubmitButton enabled={message !== ''} onSubmit={submit} />
|
||||
)}
|
||||
</InputBox>
|
||||
</VisibilitySensor>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// @ts-ignore withLocalState prop passing weirdness
|
||||
export default withLocalState<Omit<ChatInputProps, keyof IuseStorage>, 'hideAvatars', ChatInput>(
|
||||
ChatInput,
|
||||
['hideAvatars']
|
||||
);
|
||||
export default withLocalState<
|
||||
Omit<ChatInputProps, keyof IuseStorage>,
|
||||
'hideAvatars',
|
||||
typeof ChatInput
|
||||
>(ChatInput, ['hideAvatars']);
|
||||
|
Loading…
Reference in New Issue
Block a user