Newer
Older
taehui / taehui-fe / src / forum / PostEssayView.tsx
@Taehui Taehui on 19 Nov 14 KB v1.0.0
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import {
  Button,
  Col,
  Collapse,
  Input,
  ListGroup,
  Modal,
  ModalBody,
  Row,
  TabContent,
  TabPane,
} from "reactstrap";
import { toast } from "react-toastify";
import { observer } from "mobx-react-lite";
import { useTranslation } from "react-i18next";
import { useWindowArea, useTo } from "taehui-ts/fe-utility";
import { getMillis } from "taehui-ts/date";
import { useAvatarStore, useForumStore, useTaehuiStore } from "src/Stores";
import { wwwAXIOS } from "src/Www";
import TextView from "src/forum/TextView";
import AutoEssayTitleView from "src/forum//AutoEssayTitleView";
import { AutoEssayViewLoading } from "src/Loading";

const PostEssayView = observer(({ mode }: { mode: "w" | "m" }) => {
  const forumStore = useForumStore();
  const {
    forumTitle,
    title,
    text,
    setTitle,
    setText,
    autoEssayID,
    setAutoEssayID,
    setAutoEssays,
    autoEssays,
  } = forumStore;
  const { titleViewHeight, avatarViewHeight } = useTaehuiStore();
  const { totem, taehuiAvatarID, taehuiAvatarName } = useAvatarStore();

  const { t } = useTranslation();
  const [isEdit, setEdit] = useState(true);
  const [testText, setTestText] = useState("");
  const [isFileUploading, setFileUploading] = useState(false);
  const [textViewHeight, setTextViewHeight] = useState(100);
  const [isAutoEssayOpened, setAutoEssayOpened] = useState(false);
  const [isAutoEssayViewLoading, setAutoEssayViewLoading] = useState(false);

  const { forumID, essayID } = useParams<{
    forumID: string;
    essayID: string | undefined;
  }>();

  const editView = useRef<HTMLDivElement>(null);
  const textView = useRef<HTMLTextAreaElement>(null);
  const inputView = useRef<HTMLDivElement>(null);

  const { windowHeight } = useWindowArea();

  const to = useTo();

  const setTag = (tag: string) => {
    const { current } = textView;
    if (current) {
      const { selectionStart, selectionEnd } = current;
      const t = text.substring(selectionStart, selectionEnd);
      const tag0 = "<" + tag + ">";
      const tag1 = "</" + tag + ">";
      if (t.startsWith(tag0) || t.endsWith(tag1)) {
        setText(
          text.substring(0, selectionStart) +
            text.substring(
              selectionStart + tag0.length,
              selectionStart + t.length - tag1.length,
            ) +
            text.substring(selectionEnd),
        );
        setTimeout(() => {
          current.selectionStart = selectionStart;
          current.selectionEnd = selectionEnd - tag0.length - tag1.length;
          current.focus();
        }, 0);
      } else {
        setText(
          text.substring(0, selectionStart) +
            tag0 +
            text.substring(selectionStart, selectionEnd) +
            tag1 +
            text.substring(selectionEnd),
        );
        setTimeout(() => {
          current.selectionStart = selectionStart;
          current.selectionEnd = selectionEnd + tag0.length + tag1.length;
          current.focus();
        }, 0);
      }
    }
  };

  useLayoutEffect(() => {
    if (!totem) {
      to(`/forum/${forumID}`);
    }
  }, [forumID, to, totem]);

  useEffect(() => {
    setTextViewHeight(
      windowHeight -
        titleViewHeight -
        avatarViewHeight -
        (editView.current?.clientHeight ?? 0) -
        (inputView.current?.clientHeight ?? 0),
    );
  }, [avatarViewHeight, titleViewHeight, windowHeight]);

  useEffect(() => {
    if (mode === "w") {
      setTitle("");
      setText("");
    }
  }, [mode, setText, setTitle]);

  useEffect(() => {
    if (isAutoEssayOpened) {
      setAutoEssayViewLoading(true);
      (async () => {
        if (forumID && (await setAutoEssays(forumID, totem))) {
          setAutoEssayViewLoading(false);
        }
      })();
    }
  }, [forumID, setAutoEssays, isAutoEssayOpened, totem]);

  useEffect(() => {
    const postAutoEssayID = setInterval(async () => {
      const { title, text } = forumStore;
      if (title && text) {
        if (autoEssayID === undefined) {
          const {
            status,
            data: { autoEssayID },
          } = await wwwAXIOS.post(
            `/autoEssay/${forumID}`,
            {
              title,
              text,
            },
            {
              headers: {
                millis: getMillis(),
                totem,
              },
            },
          );
          if (status === 201) {
            toast.success(t("postedAutoEssay"));
            setAutoEssayID(autoEssayID);
          }
        } else {
          const { status } = await wwwAXIOS.put(
            `/autoEssay/${autoEssayID}`,
            {
              title,
              text,
            },
            {
              headers: {
                millis: getMillis(),
                totem,
              },
            },
          );
          if (status === 204) {
            toast.success(t("postedAutoEssay"));
          }
        }
      }
    }, 60000);

    return () => {
      clearInterval(postAutoEssayID);
    };
  }, [autoEssayID, forumID, forumStore, setAutoEssayID, t, totem]);

  useLayoutEffect(() => {
    if (!isEdit) {
      setTestText(text);
    }
  }, [isEdit, text]);

  return (
    <>
      {isEdit && (
        <div ref={editView}>
          <Row className="g-0">
            <Col className="m-1">
              <Input
                invalid={!title}
                valid={!!title}
                placeholder={t("title")}
                value={title}
                onChange={({ target: { value } }) => {
                  setTitle(value);
                }}
              />
            </Col>
          </Row>
          <Row className="g-0">
            <Col className="m-1" xs="auto">
              <Button
                onClick={() => {
                  const inputElement = document.createElement("input");
                  inputElement.type = "file";
                  inputElement.accept = "audio/*,image/*,video/*";
                  inputElement.addEventListener(
                    "change",
                    async ({ target }) => {
                      const file = (target as HTMLInputElement).files?.[0];
                      if (file) {
                        try {
                          setFileUploading(true);
                          const form = new FormData();
                          form.append("data", file);
                          const { data, status } = await wwwAXIOS.post(
                            "/file",
                            form,
                            {
                              headers: {
                                millis: getMillis(),
                                totem,
                              },
                            },
                          );
                          if (status === 201) {
                            const { current } = textView;
                            if (current) {
                              const { selectionStart } = current;
                              const textBefore = text.substring(
                                0,
                                selectionStart,
                              );
                              const textLater = text.substring(
                                selectionStart,
                                text.length,
                              );
                              if (
                                data.fileName.match(
                                  /^.*\.(bmp|gif|jpeg|jpg|png|webp)$/i,
                                )
                              ) {
                                setText(
                                  `${textBefore}<img src="${data.fileName}">${textLater}`,
                                );
                              } else if (
                                data.fileName.match(
                                  /^.*\.(aif|aiff|asf|flac|m4a|mid|midi|mp2|mp3|ogg|opus|raw|wav|wma)$/i,
                                )
                              ) {
                                setText(
                                  `${textBefore}<audio src="${data.fileName}" controls></audio>${textLater}`,
                                );
                              } else if (
                                data.fileName.match(
                                  /^.*\.(avi|flv|m1v|mkv|mov|mp4|mpeg|mpg|webm|wmv)$/i,
                                )
                              ) {
                                setText(
                                  `${textBefore}<video src="${data.fileName}" controls></video>${textLater}`,
                                );
                              } else {
                                setText(
                                  `${textBefore}<a href="${data.fileName}">${file.name}</a>${textLater}`,
                                );
                              }
                            }
                          }
                        } finally {
                          setFileUploading(false);
                        }
                      }
                    },
                  );
                  inputElement.click();
                }}
                color="info"
              >
                {t("fileUpload")}
              </Button>
            </Col>
            <Col className="m-1" xs="auto">
              <Button
                onClick={() => {
                  setAutoEssayOpened((prevState) => !prevState);
                }}
                color={isAutoEssayOpened ? "secondary" : "primary"}
              >
                {t("autoEssays")}
              </Button>
            </Col>
          </Row>
          <Collapse isOpen={isAutoEssayOpened}>
            {isAutoEssayViewLoading ? (
              <AutoEssayViewLoading />
            ) : (
              <ListGroup>
                {autoEssays.map((autoEssay) => (
                  <AutoEssayTitleView
                    key={autoEssay.autoEssayID}
                    autoEssay={autoEssay}
                  />
                ))}
              </ListGroup>
            )}
          </Collapse>
        </div>
      )}
      <Row className="g-0">
        <Col className="m-1">
          <TabContent activeTab={isEdit ? 1 : 2}>
            <TabPane tabId={1}>
              <Input
                type="textarea"
                className="form-control"
                placeholder={t("text")}
                value={text}
                onChange={({ target: { value } }) => {
                  setText(value);
                }}
                innerRef={textView}
                style={{ height: textViewHeight }}
              />
            </TabPane>
            <TabPane tabId={2}>
              <TextView
                forumTitle={forumTitle}
                title={title}
                text={testText}
                avatarID={taehuiAvatarID}
                avatarName={taehuiAvatarName}
              />
            </TabPane>
          </TabContent>
        </Col>
      </Row>
      <div ref={inputView}>
        <Row className="g-0">
          <Col className="m-1" xs="auto">
            <Button
              onClick={() => {
                setTag("strong");
              }}
              color="info"
            >
              <strong>{t("textTag")}</strong>
            </Button>
          </Col>
          <Col className="m-1" xs="auto">
            <Button
              onClick={() => {
                setTag("i");
              }}
              color="info"
            >
              <i>{t("textTag")}</i>
            </Button>
          </Col>
          <Col className="m-1" xs="auto">
            <Button
              onClick={() => {
                setTag("u");
              }}
              color="info"
            >
              <u>{t("textTag")}</u>
            </Button>
          </Col>
          <Col className="m-1" xs="auto">
            <Button
              onClick={() => {
                setTag("s");
              }}
              color="info"
            >
              <s>{t("textTag")}</s>
            </Button>
          </Col>
        </Row>
        <Row className="g-0">
          <Col className="m-1">
            <Button
              color={isEdit ? "secondary" : "primary"}
              onClick={() => {
                setEdit((prevState) => !prevState);
              }}
            >
              {t("viewEditedEssay")}
            </Button>
          </Col>
          {mode === "w" && (
            <Col className="m-1" xs="auto">
              <Button
                color="success"
                onClick={async () => {
                  if (title && text) {
                    const { status, data } = await wwwAXIOS.post(
                      `/essay/${forumID}`,
                      {
                        title,
                        text,
                      },
                      {
                        headers: {
                          millis: getMillis(),
                          totem,
                        },
                      },
                    );
                    if (status === 201) {
                      to(`/forum/${forumID}/${data.essayID}`);
                    }
                  } else {
                    toast.error(t("failedValidation"));
                  }
                }}
              >
                {t("postEssay")}
              </Button>
            </Col>
          )}
          {mode === "m" && (
            <Col className="m-1" xs="auto">
              <Button
                color="warning"
                onClick={async () => {
                  if (title && text) {
                    const { status } = await wwwAXIOS.put(
                      `/essay/${essayID}`,
                      {
                        title,
                        text,
                      },
                      {
                        headers: {
                          millis: getMillis(),
                          totem,
                        },
                      },
                    );
                    if (status === 204) {
                      to(`/forum/${forumID}/${essayID}`);
                    }
                  } else {
                    toast.error(t("failedValidation"));
                  }
                }}
              >
                {t("doModifyEssay")}
              </Button>
            </Col>
          )}
          <Col className="m-1" xs="auto">
            <Button
              color="danger"
              onClick={() => {
                to(`/forum/${forumID}`);
              }}
            >
              {t("quit")}
            </Button>
          </Col>
        </Row>
      </div>
      <Modal isOpen={isFileUploading}>
        <ModalBody>
          <span>{t("fileUploading")}</span>
        </ModalBody>
      </Modal>
    </>
  );
});

export default PostEssayView;