import React from 'react'; import clsx from 'clsx'; import { Button, Spinner, Row, Col } from 'reactstrap'; import { useTranslation } from 'react-i18next'; import { pushAlert } from '../common/alert-service'; import { CreatePostRequest } from '../data/timeline'; import FileInput from '../common/FileInput'; import { UiLogicError } from '../common'; interface TimelinePostEditImageProps { onSelect: (blob: Blob | null) => void; } const TimelinePostEditImage: React.FC = (props) => { const { onSelect } = props; const { t } = useTranslation(); const [file, setFile] = React.useState(null); const [fileUrl, setFileUrl] = React.useState(null); const [error, setError] = React.useState(null); React.useEffect(() => { if (file != null) { const url = URL.createObjectURL(file); setFileUrl(url); return () => { URL.revokeObjectURL(url); }; } }, [file]); const onInputChange: React.ChangeEventHandler = React.useCallback( (e) => { const files = e.target.files; if (files == null || files.length === 0) { setFile(null); setFileUrl(null); } else { setFile(files[0]); } onSelect(null); setError(null); }, [onSelect] ); const onImgLoad = React.useCallback(() => { onSelect(file); }, [onSelect, file]); const onImgError = React.useCallback(() => { setError('loadImageError'); }, []); return ( <> {fileUrl && error == null && ( )} {error != null &&
{t(error)}
} ); }; export type TimelinePostSendCallback = ( content: CreatePostRequest ) => Promise; export interface TimelinePostEditProps { className?: string; onPost: TimelinePostSendCallback; onHeightChange?: (height: number) => void; timelineName: string; } const TimelinePostEdit: React.FC = (props) => { const { onPost } = props; const { t } = useTranslation(); const [state, setState] = React.useState<'input' | 'process'>('input'); const [kind, setKind] = React.useState<'text' | 'image'>('text'); const [text, setText] = React.useState(''); const [imageBlob, setImageBlob] = React.useState(null); const draftLocalStorageKey = `timeline.${props.timelineName}.postDraft`; React.useEffect(() => { setText(window.localStorage.getItem(draftLocalStorageKey) ?? ''); }, [draftLocalStorageKey]); const canSend = kind === 'text' || (kind === 'image' && imageBlob != null); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const containerRef = React.useRef(null!); React.useEffect(() => { if (props.onHeightChange) { props.onHeightChange(containerRef.current.clientHeight); } return () => { if (props.onHeightChange) { props.onHeightChange(0); } }; }); const toggleKind = React.useCallback(() => { setKind((oldKind) => (oldKind === 'text' ? 'image' : 'text')); setImageBlob(null); }, []); const onSend = React.useCallback(() => { setState('process'); const req: CreatePostRequest = (() => { switch (kind) { case 'text': return { content: { type: 'text', text: text, }, } as CreatePostRequest; case 'image': if (imageBlob == null) { throw new UiLogicError( 'Content type is image but image blob is null.' ); } return { content: { type: 'image', data: imageBlob, }, } as CreatePostRequest; default: throw new UiLogicError('Unknown content type.'); } })(); onPost(req).then( (_) => { if (kind === 'text') { setText(''); window.localStorage.removeItem(draftLocalStorageKey); } setState('input'); setKind('text'); }, (_) => { pushAlert({ type: 'danger', message: t('timeline.sendPostFailed'), }); setState('input'); } ); }, [onPost, kind, text, imageBlob, t, draftLocalStorageKey]); const onImageSelect = React.useCallback((blob: Blob | null) => { setImageBlob(blob); }, []); return (
{kind === 'text' ? (