Newer
Older
taehui / qwilight-fe / src / app / [language] / site / page.tsx
@Taehui Taehui on 16 Mar 10 KB 2024-03-17 오전 2:53
"use client";

import scss from "@/app/[language]/site/page.module.scss";
import AvatarItems from "@/site/AvatarItems";
import ConfigureWindow from "@/site/ConfigureWindow";
import { OnSiteYellInput } from "@/site/Site";
import SiteComponent from "@/site/SiteComponent";
import SiteInput from "@/site/SiteInput";
import SiteWindow from "@/site/SiteWindow";
import SiteYellItem from "@/site/SiteYellItem";
import SiteYellItems from "@/site/SiteYellItems";

import { useSiteStore } from "@/Stores";
import { getDefaultAvatarID } from "@/Utility";
import { useQueryClient } from "@tanstack/react-query";
import { observer } from "mobx-react-lite";
import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Item, ItemParams, Menu, useContextMenu } from "react-contexify";
import { toast } from "react-toastify";
import {
  Alert,
  Button,
  Col,
  Offcanvas,
  OffcanvasBody,
  OffcanvasHeader,
  Row,
} from "reactstrap";
import Swal from "sweetalert2";
import { useTo } from "taehui-ts/fe-utility";

const EventPB = require("@/Event_pb");

export default observer(() => {
  const {
    avatars,
    siteNotify,
    isAvatarsOpened,
    isPendingSiteYellOpened,
    lastPendingSiteYell,
    targetSiteID,
    siteViews,
    siteAvatarID,
    siteYells,
    setSiteYellsViewLowest,
    getSiteView,
    onSiteIDModified,
    siteYellsViewMove,
    setPendingSiteYellOpened,
    setAvatarsOpened,
    isLoading,
    setSiteWindowOpened,
    siteYellsView,
    titleComponent,
  } = useSiteStore();
  const t = useTranslations();
  const to = useTo();
  const { show: viewSiteNameInput } = useContextMenu({
    id: "siteName",
  });
  const { show: viewAvatarInput } = useContextMenu({
    id: "avatar",
  });
  const { show: viewSiteYellInput } = useContextMenu({
    id: "siteYell",
  });

  const pathname = usePathname();

  useEffect(() => {
    if (pathname === "/site") {
      siteYellsViewMove();
    }
  }, [pathname, siteYellsViewMove]);

  const [siteYellsViewHeight, setSiteYellsViewHeight] = useState("");
  const siteViewsView = useRef<HTMLDivElement>(null);
  const inputComponent = useRef<HTMLDivElement>(null);

  const onViewAvatar = ({ props: { avatarID } }: ItemParams) => {
    if (avatarID.startsWith("*")) {
      toast.warning(t("notAvatarViewFault"));
    } else {
      to(`/avatar/${encodeURIComponent("#")}${getDefaultAvatarID(avatarID)}`);
    }
  };

  useEffect(() => {
    window.Notification?.requestPermission();
  }, []);

  const onViewsModified = useCallback(() => {
    setSiteYellsViewHeight(
      `${
        document.documentElement.clientHeight -
        (titleComponent?.current?.clientHeight ?? 0) -
        (siteViewsView.current?.clientHeight ?? 0) -
        (inputComponent.current?.clientHeight ?? 0)
      }px`,
    );
  }, [titleComponent]);

  useEffect(() => {
    onViewsModified();
  }, [onViewsModified, isLoading, avatars, lastPendingSiteYell]);

  useEffect(() => {
    const onModified = () => {
      onViewsModified();
      siteYellsViewMove();
    };

    window.addEventListener("resize", onModified);

    return () => {
      window.removeEventListener("resize", onModified);
    };
  }, [onViewsModified, siteYellsViewMove]);

  const onSiteYellsViewMove = useCallback(() => {
    if (siteYellsView) {
      const { current } = siteYellsView;
      if (current) {
        const siteYellID = siteYells[0]?.siteYellID;
        if (siteYellID && siteYellID > 0 && !current.scrollTop) {
          SiteComponent.send({
            eventID: EventPB.Event.EventID.GET_SITE_YELLS,
            text: JSON.stringify({
              siteID: targetSiteID,
              siteYellID: siteYellID,
            }),
          });
        }

        const isSiteYellsViewLowest =
          current.scrollTop + current.clientHeight >= current.scrollHeight;
        setSiteYellsViewLowest(isSiteYellsViewLowest);
        if (isSiteYellsViewLowest) {
          setPendingSiteYellOpened(false);
        }
      }
    }
  }, [
    setPendingSiteYellOpened,
    setSiteYellsViewLowest,
    siteYells,
    siteYellsView,
    targetSiteID,
  ]);

  useEffect(() => {
    if (siteYellsView) {
      const { current } = siteYellsView;

      if (current) {
        current.addEventListener("scroll", onSiteYellsViewMove);

        return () => {
          current.removeEventListener("scroll", onSiteYellsViewMove);
        };
      }
    }
  }, [onSiteYellsViewMove, isLoading, siteYellsView]);

  const isSiteHand = useMemo(
    () => getSiteView(targetSiteID)?.siteHand === siteAvatarID,
    [getSiteView, siteAvatarID, targetSiteID],
  );

  const queryClient = useQueryClient();

  const onSiteYellInput: OnSiteYellInput = (event, avatarID) => {
    viewSiteYellInput({ event, props: { avatarID } });
  };

  return (
    <>
      <div ref={siteViewsView}>
        <Row className="g-0">
          {siteViews.map(({ siteID, siteName, isNew }) => {
            return (
              <Col className="m-1" xs="auto" key={siteID}>
                <Button
                  color={
                    isNew
                      ? "warning"
                      : siteID === targetSiteID
                        ? "primary"
                        : "secondary"
                  }
                  onClick={() => {
                    onSiteIDModified(siteID);
                  }}
                  onContextMenu={(event) => {
                    event.preventDefault();
                    viewSiteNameInput({ event });
                  }}
                >
                  {siteName}
                </Button>
              </Col>
            );
          })}
          <Col className="m-1" xs="auto">
            <Button
              color="info"
              onClick={async () => {
                await queryClient.invalidateQueries({ queryKey: ["sites"] });
                setSiteWindowOpened(true);
              }}
            >
              {t("onSiteWindow")}
            </Button>
          </Col>
          {targetSiteID && (
            <Col className="m-1" xs="auto">
              <Button
                onClick={() => {
                  SiteComponent.send({
                    eventID: EventPB.Event("EventID.QUIT_SITE"),
                    text: targetSiteID,
                  });
                }}
                color="danger"
              >
                {t("quitSite")}
              </Button>
            </Col>
          )}
        </Row>
      </div>

      <Row className="justify-content-center g-0">
        <Col className="m-1" xs="auto" style={{ position: "absolute" }}>
          <Alert isOpen={!!siteNotify} color="primary">
            <span>{siteNotify}</span>
          </Alert>
        </Col>
      </Row>

      <Row className="justify-content-center g-0 route">
        <Col className="m-1" xs="auto" style={{ position: "absolute" }}>
          <Alert isOpen={isPendingSiteYellOpened} onClick={siteYellsViewMove}>
            {lastPendingSiteYell && (
              <SiteYellItem
                data={lastPendingSiteYell}
                onSiteYellInput={onSiteYellInput}
              />
            )}
          </Alert>
        </Col>
      </Row>

      <div
        ref={siteYellsView}
        className={scss.siteView}
        style={{
          height: siteYellsViewHeight,
        }}
      >
        <SiteYellItems onSiteYellInput={onSiteYellInput} />
      </div>

      <Offcanvas
        direction="end"
        isOpen={isAvatarsOpened}
        toggle={() => setAvatarsOpened(false)}
      >
        <OffcanvasHeader toggle={() => setAvatarsOpened(false)}>
          {t("avatarCountText", { avatarCount: avatars.length })}
        </OffcanvasHeader>
        <OffcanvasBody>
          <AvatarItems
            onAvatarInput={(event, avatarID) => {
              viewAvatarInput({ event, props: { avatarID } });
            }}
          />
        </OffcanvasBody>

        <Menu id="avatar">
          <Item
            disabled={!isSiteHand}
            onClick={({ props: { avatarID } }) => {
              SiteComponent.send({
                eventID: EventPB.Event("EventID.SET_SITE_OWNER"),
                text: JSON.stringify({
                  siteID: targetSiteID,
                  avatarID,
                }),
              });
            }}
          >
            <span>{t("setSiteHand")}</span>
          </Item>
          <Item onClick={onViewAvatar}>
            <span>{t("viewAvatarView")}</span>
          </Item>
          <Item
            onClick={({ props: { avatarID } }) => {
              SiteComponent.send({
                eventID: EventPB.Event("EventID.NEW_SILENT_SITE"),
                text: avatarID,
              });
            }}
          >
            <span>{t("silentSiteNew")}</span>
          </Item>
          <Item
            disabled={!isSiteHand}
            onClick={({ props: { avatarID } }) => {
              SiteComponent.send({
                eventID: EventPB.Event("EventID.EXILE_AVATAR"),
                text: JSON.stringify({
                  siteID: targetSiteID,
                  avatarID,
                }),
              });
            }}
          >
            <span>{t("exileAvatar")}</span>
          </Item>
        </Menu>
      </Offcanvas>

      <Menu id="siteYell">
        <Item onClick={onViewAvatar}>
          <span>{t("viewAvatarView")}</span>
        </Item>
      </Menu>

      <Menu id="siteName">
        <Item
          disabled={!isSiteHand}
          onClick={async () => {
            const { isConfirmed, value } = await Swal.fire({
              title: t("setSiteNameText"),
              input: "text",
            });
            if (isConfirmed) {
              SiteComponent.send({
                eventID: EventPB.Event("EventID.SET_SITE_NAME"),
                text: JSON.stringify({
                  siteID: targetSiteID,
                  siteName: value,
                }),
              });
            }
          }}
        >
          <span>{t("setSiteName")}</span>
        </Item>
      </Menu>

      <div ref={inputComponent}>
        <SiteInput />
      </div>

      <ConfigureWindow />
      <SiteWindow />
    </>
  );
});