diff options
author | crupest <crupest@outlook.com> | 2020-09-01 02:32:06 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2020-09-01 02:32:06 +0800 |
commit | aa89b6cce7701a57b0c377d938788b4c940013d6 (patch) | |
tree | ae95cb16698439ac825eb1d692ce14125b625ecb /Timeline/ClientApp/src/app/timeline/TimelinePostEdit.tsx | |
parent | c3e95a6cd7322c644159eed6350a20dfd1a002ff (diff) | |
download | timeline-aa89b6cce7701a57b0c377d938788b4c940013d6.tar.gz timeline-aa89b6cce7701a57b0c377d938788b4c940013d6.tar.bz2 timeline-aa89b6cce7701a57b0c377d938788b4c940013d6.zip |
...
Diffstat (limited to 'Timeline/ClientApp/src/app/timeline/TimelinePostEdit.tsx')
-rw-r--r-- | Timeline/ClientApp/src/app/timeline/TimelinePostEdit.tsx | 232 |
1 files changed, 0 insertions, 232 deletions
diff --git a/Timeline/ClientApp/src/app/timeline/TimelinePostEdit.tsx b/Timeline/ClientApp/src/app/timeline/TimelinePostEdit.tsx deleted file mode 100644 index b30dc8d3..00000000 --- a/Timeline/ClientApp/src/app/timeline/TimelinePostEdit.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import React from "react"; -import { Button, Spinner, Row, Col } from "reactstrap"; -import { useTranslation } from "react-i18next"; -import Svg from "react-inlinesvg"; -import textIcon from "bootstrap-icons/icons/card-text.svg"; -import imageIcon from "bootstrap-icons/icons/image.svg"; - -import { pushAlert } from "../common/alert-service"; -import { TimelineCreatePostRequest } from "../data/timeline"; -import FileInput from "../common/FileInput"; -import { UiLogicError } from "../common"; - -interface TimelinePostEditImageProps { - onSelect: (blob: Blob | null) => void; -} - -const TimelinePostEditImage: React.FC<TimelinePostEditImageProps> = (props) => { - const { onSelect } = props; - const { t } = useTranslation(); - - const [file, setFile] = React.useState<File | null>(null); - const [fileUrl, setFileUrl] = React.useState<string | null>(null); - const [error, setError] = React.useState<string | null>(null); - - React.useEffect(() => { - if (file != null) { - const url = URL.createObjectURL(file); - setFileUrl(url); - return () => { - URL.revokeObjectURL(url); - }; - } - }, [file]); - - const onInputChange: React.ChangeEventHandler<HTMLInputElement> = 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 ( - <> - <FileInput - labelText={t("chooseImage")} - onChange={onInputChange} - accept="image/*" - className="mx-3 my-1" - /> - {fileUrl && error == null && ( - <img - src={fileUrl} - className="timeline-post-edit-image" - onLoad={onImgLoad} - onError={onImgError} - /> - )} - {error != null && <div className="text-danger">{t(error)}</div>} - </> - ); -}; - -export type TimelinePostSendCallback = ( - content: TimelineCreatePostRequest -) => Promise<void>; - -export interface TimelinePostEditProps { - className?: string; - onPost: TimelinePostSendCallback; - onHeightChange?: (height: number) => void; - timelineUniqueId: string; -} - -const TimelinePostEdit: React.FC<TimelinePostEditProps> = (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<string>(""); - const [imageBlob, setImageBlob] = React.useState<Blob | null>(null); - - const draftLocalStorageKey = `timeline.${props.timelineUniqueId}.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<HTMLDivElement>(null!); - - const notifyHeightChange = (): void => { - if (props.onHeightChange) { - props.onHeightChange(containerRef.current.clientHeight); - } - }; - - 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: TimelineCreatePostRequest = (() => { - switch (kind) { - case "text": - return { - content: { - type: "text", - text: text, - }, - } as TimelineCreatePostRequest; - case "image": - if (imageBlob == null) { - throw new UiLogicError( - "Content type is image but image blob is null." - ); - } - return { - content: { - type: "image", - data: imageBlob, - }, - } as TimelineCreatePostRequest; - 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 ( - <div ref={containerRef} className="container-fluid fixed-bottom bg-light"> - <Row> - <Col className="px-1 py-1"> - {kind === "text" ? ( - <textarea - className="w-100 h-100 timeline-post-edit" - value={text} - disabled={state === "process"} - onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => { - const value = event.currentTarget.value; - setText(value); - window.localStorage.setItem(draftLocalStorageKey, value); - }} - /> - ) : ( - <TimelinePostEditImage onSelect={onImageSelect} /> - )} - </Col> - <Col sm="col-auto align-self-end m-1"> - {(() => { - if (state === "input") { - return ( - <> - <div className="d-block text-center mt-1 mb-2"> - <Svg - onLoad={notifyHeightChange} - src={kind === "text" ? imageIcon : textIcon} - className="icon-button" - onClick={toggleKind} - /> - </div> - <Button color="primary" onClick={onSend} disabled={!canSend}> - {t("timeline.send")} - </Button> - </> - ); - } else { - return <Spinner />; - } - })()} - </Col> - </Row> - </div> - ); -}; - -export default TimelinePostEdit; |