import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { observer } from "mobx-react-lite"; import { Item, ItemParams, Menu, useContextMenu } from "react-contexify"; import { useTranslation } from "react-i18next"; import { Alert, Button, Col, Offcanvas, OffcanvasBody, OffcanvasHeader, Row, } from "reactstrap"; import { isFirefox } from "react-device-detect"; import Swal from "sweetalert2"; import { toast } from "react-toastify"; import { sprintf } from "sprintf-js"; import { useTo } from "taehui-ts/fe-utility"; import { useSiteStore } from "src/Stores"; import SiteComponent from "src/site/SiteComponent"; import ConfigureWindow from "src/site/ConfigureWindow"; import SiteWindow from "src/site/SiteWindow"; import SiteYellItems from "src/site/SiteYellItems"; import AvatarItems from "src/site/AvatarItems"; import SiteInput from "src/site/SiteInput"; import SiteYellItem from "src/site/SiteYellItem"; import { getDefaultAvatarID } from "src/Utility"; import { SiteViewLoading } from "src/Loading"; import scss from "src/site/SiteView.module.scss"; import { OnSiteYellInput } from "./Site"; import { useQueryClient } from "@tanstack/react-query"; const EventPB = require("src/Event_pb"); export default observer<{ titleComponent: MutableRefObject<HTMLDivElement | null>; siteYellsView: MutableRefObject<HTMLDivElement | null>; }>(({ titleComponent, siteYellsView }) => { const { avatars, siteNotify, isAvatarsOpened, isPendingSiteYellOpened, lastPendingSiteYell, targetSiteID, siteViews, siteAvatarID, siteYells, setSiteYellsViewLowest, getSiteView, onSiteIDModified, siteYellsViewMove, setPendingSiteYellOpened, setAvatarsOpened, isLoading, setSiteWindowOpened, } = useSiteStore(); const { t } = useTranslation(); const to = useTo(); const { show: viewSiteNameInput } = useContextMenu({ id: "siteName", }); const { show: viewAvatarInput } = useContextMenu({ id: "avatar", }); const { show: viewSiteYellInput } = useContextMenu({ id: "siteYell", }); useEffect(() => { if (window.location.pathname === "/qwilight/site") { siteYellsViewMove(siteYellsView); } }, [siteYellsView, 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( `/qwilight/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) - (isFirefox ? 1 : 0) }px`, ); }, [titleComponent]); useEffect(() => { onViewsModified(); }, [onViewsModified, isLoading, avatars, lastPendingSiteYell]); useEffect(() => { const onModified = () => { onViewsModified(); siteYellsViewMove(siteYellsView); }; window.addEventListener("resize", onModified); return () => { window.removeEventListener("resize", onModified); }; }, [onViewsModified, siteYellsView, siteYellsViewMove]); const onSiteYellsViewMove = useCallback(() => { 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(() => { 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(); if (isLoading) { return <SiteViewLoading />; } 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, siteYellsView); }} 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(siteYellsView); }} > {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)}> {sprintf(t("avatarCountText"), 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 /> </> ); });