From 232a19d7dfe0e3847b3a9a9a9be83485ffb9031c Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 30 May 2020 16:23:25 +0800 Subject: Merge front end to this repo. But I need to wait for aspnet core support for custom port and package manager for dev server. --- .../ClientApp/src/timeline/TimelinePostEdit.tsx | 205 +++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx (limited to 'Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx') diff --git a/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx b/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx new file mode 100644 index 00000000..fe1fda9b --- /dev/null +++ b/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx @@ -0,0 +1,205 @@ +import React from 'react'; +import clsx from 'clsx'; +import { Container, 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'; + +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; +} + +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 canSend = kind === 'text' || (kind === 'image' && imageBlob != null); + + React.useEffect(() => { + if (props.onHeightChange) { + props.onHeightChange( + document.getElementById('timeline-post-edit-area')!.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 = + kind === 'text' + ? { + content: { + type: 'text', + text: text, + }, + } + : { + content: { + type: 'image', + data: imageBlob!, + }, + }; + + onPost(req).then( + (_) => { + if (kind === 'text') { + setText(''); + } + setState('input'); + setKind('text'); + }, + (_) => { + pushAlert({ + type: 'danger', + message: t('timeline.sendPostFailed'), + }); + setState('input'); + } + ); + }, [onPost, kind, text, imageBlob, t]); + + const onImageSelect = React.useCallback((blob: Blob | null) => { + setImageBlob(blob); + }, []); + + return ( + + + + {kind === 'text' ? ( +