aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx')
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx291
1 files changed, 0 insertions, 291 deletions
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
deleted file mode 100644
index 5f3f0345..00000000
--- a/FrontEnd/src/app/views/timeline-common/TimelinePostEdit.tsx
+++ /dev/null
@@ -1,291 +0,0 @@
-import React from "react";
-import classnames from "classnames";
-import { useTranslation } from "react-i18next";
-import { Row, Col, Form } from "react-bootstrap";
-
-import { UiLogicError } from "@/common";
-
-import {
- getHttpTimelineClient,
- HttpTimelineInfo,
- HttpTimelinePostInfo,
- HttpTimelinePostPostRequestData,
-} from "@/http/timeline";
-
-import { pushAlert } from "@/services/alert";
-import { base64 } from "@/http/common";
-
-import BlobImage from "../common/BlobImage";
-import LoadingButton from "../common/LoadingButton";
-import { PopupMenu } from "../common/Menu";
-import MarkdownPostEdit from "./MarkdownPostEdit";
-
-interface TimelinePostEditTextProps {
- text: string;
- disabled: boolean;
- onChange: (text: string) => void;
- className?: string;
- style?: React.CSSProperties;
-}
-
-const TimelinePostEditText: React.FC<TimelinePostEditTextProps> = (props) => {
- const { text, disabled, onChange, className, style } = props;
-
- return (
- <Form.Control
- as="textarea"
- value={text}
- disabled={disabled}
- onChange={(event) => {
- onChange(event.target.value);
- }}
- className={className}
- style={style}
- />
- );
-};
-
-interface TimelinePostEditImageProps {
- onSelect: (file: File | null) => void;
- disabled: boolean;
-}
-
-const TimelinePostEditImage: React.FC<TimelinePostEditImageProps> = (props) => {
- const { onSelect, disabled } = props;
-
- const { t } = useTranslation();
-
- const [file, setFile] = React.useState<File | null>(null);
- const [error, setError] = React.useState<boolean>(false);
-
- const onInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
- setError(false);
- const files = e.target.files;
- if (files == null || files.length === 0) {
- setFile(null);
- onSelect(null);
- } else {
- setFile(files[0]);
- }
- };
-
- React.useEffect(() => {
- return () => {
- onSelect(null);
- };
- }, [onSelect]);
-
- return (
- <>
- <Form.Control
- type="file"
- onChange={onInputChange}
- accept="image/*"
- disabled={disabled}
- className="mx-3 my-1"
- />
- {file != null && !error && (
- <BlobImage
- blob={file}
- className="timeline-post-edit-image"
- onLoad={() => onSelect(file)}
- onError={() => {
- onSelect(null);
- setError(true);
- }}
- />
- )}
- {error ? <div className="text-danger">{t("loadImageError")}</div> : null}
- </>
- );
-};
-
-type PostKind = "text" | "markdown" | "image";
-
-const postKindIconClassNameMap: Record<PostKind, string> = {
- text: "bi-fonts",
- markdown: "bi-markdown",
- image: "bi-image",
-};
-
-export interface TimelinePostEditProps {
- className?: string;
- timeline: HttpTimelineInfo;
- onPosted: (newPost: HttpTimelinePostInfo) => void;
- onHeightChange?: (height: number) => void;
-}
-
-const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
- const { timeline, onHeightChange, className, onPosted } = props;
-
- const { t } = useTranslation();
-
- const [process, setProcess] = React.useState<boolean>(false);
-
- const [kind, setKind] = React.useState<Exclude<PostKind, "markdown">>("text");
- const [showMarkdown, setShowMarkdown] = React.useState<boolean>(false);
-
- const [text, setText] = React.useState<string>("");
- const [image, setImage] = React.useState<File | null>(null);
-
- const draftTextLocalStorageKey = `timeline.${timeline.name}.postDraft.text`;
-
- React.useEffect(() => {
- setText(window.localStorage.getItem(draftTextLocalStorageKey) ?? "");
- }, [draftTextLocalStorageKey]);
-
- const canSend =
- (kind === "text" && text.length !== 0) ||
- (kind === "image" && image != null);
-
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const containerRef = React.useRef<HTMLDivElement>(null!);
-
- const notifyHeightChange = (): void => {
- if (onHeightChange) {
- onHeightChange(containerRef.current.clientHeight);
- }
- };
-
- React.useEffect(() => {
- notifyHeightChange();
- return () => {
- if (onHeightChange) {
- onHeightChange(0);
- }
- };
- });
-
- const onPostError = (): void => {
- pushAlert({
- type: "danger",
- message: "timeline.sendPostFailed",
- });
- };
-
- const onSend = async (): Promise<void> => {
- setProcess(true);
-
- let requestData: HttpTimelinePostPostRequestData;
- switch (kind) {
- case "text":
- requestData = {
- contentType: "text/plain",
- data: await base64(text),
- };
- break;
- case "image":
- if (image == null) {
- throw new UiLogicError(
- "Content type is image but image blob is null."
- );
- }
- requestData = {
- contentType: image.type,
- data: await base64(image),
- };
- break;
- default:
- throw new UiLogicError("Unknown content type.");
- }
-
- getHttpTimelineClient()
- .postPost(timeline.name, {
- dataList: [requestData],
- })
- .then(
- (data) => {
- if (kind === "text") {
- setText("");
- window.localStorage.removeItem(draftTextLocalStorageKey);
- }
- setProcess(false);
- setKind("text");
- onPosted(data);
- },
- (_) => {
- setProcess(false);
- onPostError();
- }
- );
- };
-
- return (
- <div
- ref={containerRef}
- className={classnames("container-fluid bg-light", className)}
- >
- {showMarkdown ? (
- <MarkdownPostEdit
- className="w-100"
- onClose={() => setShowMarkdown(false)}
- timeline={timeline.name}
- onPosted={onPosted}
- onPostError={onPostError}
- />
- ) : (
- <Row>
- <Col className="px-1 py-1">
- {(() => {
- if (kind === "text") {
- return (
- <TimelinePostEditText
- className="w-100 h-100 timeline-post-edit"
- text={text}
- disabled={process}
- onChange={(t) => {
- setText(t);
- window.localStorage.setItem(draftTextLocalStorageKey, t);
- }}
- />
- );
- } else if (kind === "image") {
- return (
- <TimelinePostEditImage
- onSelect={setImage}
- disabled={process}
- />
- );
- }
- })()}
- </Col>
- <Col xs="auto" className="align-self-end m-1">
- <div className="d-block text-center mt-1 mb-2">
- <PopupMenu
- items={(["text", "image", "markdown"] as const).map((kind) => ({
- type: "button",
- text: `timeline.post.type.${kind}`,
- iconClassName: postKindIconClassNameMap[kind],
- onClick: () => {
- if (kind === "markdown") {
- setShowMarkdown(true);
- } else {
- setKind(kind);
- }
- },
- }))}
- >
- <i
- className={classnames(
- postKindIconClassNameMap[kind],
- "icon-button large"
- )}
- />
- </PopupMenu>
- </div>
- <LoadingButton
- variant="primary"
- onClick={onSend}
- disabled={!canSend}
- loading={process}
- >
- {t("timeline.send")}
- </LoadingButton>
- </Col>
- </Row>
- )}
- </div>
- );
-};
-
-export default TimelinePostEdit;