import wsAPI from "@/app/[language]/site/lib/wsAPI"; import { AbilitySiteYell, Avatar, SiteYellItem, } from "@/app/[language]/site/type"; import { formatText, getAbilityUpText, getDefaultAvatarID, getGenreText, getSiteName, } from "@/utilities/Utility"; import { wwwAPIPath } from "@/utilities/wwwAPI"; import CryptoJS from "crypto-js"; import { makeAutoObservable } from "mobx"; import { useTranslations } from "next-intl"; import { RefObject } from "react"; import { toast } from "react-toastify"; import { getDatetime } from "taehui-lib/date"; const EventPB = require("@/Event_pb"); export class SiteView { siteID = ""; isEditable = false; isNew = false; wasNotify = false; siteNotify = ""; siteName = ""; isNetMode = false; avatars = [] as Avatar[]; siteHand = ""; situationValue = 0; siteYellItems = [] as SiteYellItem[]; lastPendingSiteYell = undefined as SiteYellItem | undefined; isPendingSiteYellOpened = false; isSiteYellsViewLowest = false; isAvatarsViewOpened = false; isSiteHand = false; input = ""; siteYellsView = undefined as RefObject<HTMLDivElement> | undefined; constructor( siteID: string, siteNotify: string, isEditable: boolean, isNetMode: boolean, siteYellItems: SiteYellItem[], ) { this.siteID = siteID; this.siteNotify = siteNotify; this.isEditable = isEditable; this.isNetMode = isNetMode; this.siteYellItems = siteYellItems; makeAutoObservable(this); } setSiteYell(targetSiteYellID: number, siteYell: string) { const targetSiteYell = this.siteYellItems.find( ({ siteYellID }) => siteYellID === targetSiteYellID, ); if (targetSiteYell) { targetSiteYell.siteYell = siteYell; } } setSiteYellsView = (siteYellsView: RefObject<HTMLDivElement>) => { this.siteYellsView = siteYellsView; }; putSiteYell(siteYellItem: SiteYellItem, isGetSiteYell: boolean) { if (isGetSiteYell) { this.siteYellItems.unshift(siteYellItem); } else { this.siteYellItems.push(siteYellItem); } } siteYellsViewMove() { setTimeout(() => { if (this.siteYellsView) { const { current } = this.siteYellsView; if (current) { current.scrollTop = current.scrollHeight; } } }, 0); } setLastPendingSiteYell = (siteYellItem: SiteYellItem) => { this.lastPendingSiteYell = siteYellItem; this.isPendingSiteYellOpened = true; }; setNew = (isNew: boolean) => { this.isNew = isNew; }; setSiteNotify = (siteNotify: string) => { this.siteNotify = siteNotify; }; setPendingSiteYellOpened = (isPendingSiteYellOpened: boolean) => { this.isPendingSiteYellOpened = isPendingSiteYellOpened; }; setAvatarsViewOpened = (isAvatarsViewOpened: boolean) => { this.isAvatarsViewOpened = isAvatarsViewOpened; }; doCallSiteAvatar = ( siteName: string, siteHand: string, situationValue: number, avatars: Avatar[], ) => { this.siteName = siteName; this.siteHand = siteHand; this.situationValue = situationValue; this.avatars = avatars; }; setSiteYellsViewLowest = (isSiteYellsViewLowest: boolean) => { this.isSiteYellsViewLowest = isSiteYellsViewLowest; }; wipeSiteYell = (siteYellID: number) => { const i = this.siteYellItems.findIndex( (siteYellItem) => siteYellItem.siteYellID === siteYellID, ); const siteYellItem = this.siteYellItems[i]; this.siteYellItems.splice(i, 1, { siteYellID, avatarID: siteYellItem.avatarID, avatarName: siteYellItem.avatarName, siteYellVariety: "@Wiped", date: siteYellItem.date, }); }; setInput = (input: string) => { this.input = input; }; } const getSiteYell = ({ avatarID, avatarName, siteYell, date, siteYellID, }: { avatarID: string; avatarName: string; siteYell: string; date: number; siteYellID: number; }): SiteYellItem => { const dateText = getDatetime(date); switch (avatarName) { case "@Enter": return { siteYellID, avatarID, avatarName: siteYell, siteYellVariety: avatarName, date: dateText, }; case "@Quit": return { siteYellID, avatarID, avatarName: siteYell, siteYellVariety: avatarName, date: dateText, }; case "@Site": return { siteYellID, avatarID, avatarName: siteYell, siteYellVariety: avatarName, date: dateText, }; case "@Net": return { siteYellID, avatarID, avatarName: siteYell, siteYellVariety: avatarName, date: dateText, }; case "@Notify": return { siteYellID, avatarID, siteYellVariety: avatarName, date: dateText, siteYell, }; case "@Wiped": return { siteYellID, avatarID, avatarName: siteYell, siteYellVariety: avatarName, date: dateText, }; case "@Invite": case "@Comment": case "@Ability": case "@Level": case "@TV": return { siteYellID, avatarID, siteYellVariety: avatarName, date: dateText, siteYell: JSON.parse(siteYell), }; case "": return { siteYellID, siteYellVariety: "", siteYell, }; default: return { siteYellID, siteYellVariety: null, date: dateText, avatarID, avatarName, siteYell, }; } }; export default function setSiteStore( t: ReturnType<typeof useTranslations<string>>, ) { return { titleView: undefined as RefObject<HTMLDivElement> | undefined, isSiteWindowOpened: false, isSiteCipherWindowOpened: false, isNewSiteWindowOpened: false, isConfigureOpened: false, targetSiteID: "", saveTraffic: typeof window === "object" && window.localStorage.getItem("saveTraffic") === "true", autoEnterNotify: typeof window === "object" && window.localStorage.getItem("autoEnterNotify") !== "false", autoEnterDefault: typeof window === "object" && window.localStorage.getItem("autoEnterDefault") !== "false", autoEnterPlatform: typeof window === "object" && window.localStorage.getItem("autoEnterPlatform") !== "false", siteViews: [] as SiteView[], siteAvatarID: "", siteAvatarName: "", isLoggedIn: false, isVisible: false, isLoading: true, setTitleView(titleView: RefObject<HTMLDivElement>) { this.titleView = titleView; }, setEventHandler() { const autoEnter = () => { wsAPI.send({ eventID: EventPB.Event.EventID.ENTER_SITE, text: JSON.stringify({ siteID: "00000000-0000-0000-0000-000000000003", siteCipher: "", }), }); if (this.autoEnterNotify) { wsAPI.send({ eventID: EventPB.Event.EventID.ENTER_SITE, text: JSON.stringify({ siteID: "00000000-0000-0000-0000-000000000000", siteCipher: "", }), }); } if (this.autoEnterDefault) { wsAPI.send({ eventID: EventPB.Event.EventID.ENTER_SITE, text: JSON.stringify({ siteID: "00000000-0000-0000-0000-000000000001", siteCipher: "", }), }); } if (this.autoEnterPlatform) { wsAPI.send({ eventID: EventPB.Event.EventID.ENTER_SITE, text: JSON.stringify({ siteID: "00000000-0000-0000-0000-000000000002", siteCipher: "", }), }); } }; const autoLogIn = () => { if (window.localStorage.getItem("autoLogIn") === "true") { wsAPI.send({ eventID: EventPB.Event.EventID.LOG_IN, text: JSON.stringify({ avatarID: window.localStorage.getItem("avatarID") ?? "", avatarCipher: CryptoJS.AES.decrypt( window.localStorage.getItem("avatarCipher") ?? "", "591A6F91-2A27-4A88-88FA-0FEB7CB5FD94", ).toString(CryptoJS.enc.Utf8), }), }); } }; const doNotify = (avatarID: string, toNotify: string, siteID: string) => { if ( !["denied", "default"].includes(Notification?.permission) && !this.isVisible ) { const siteView = this.getSiteView(siteID); if (siteView?.wasNotify === false) { new Notification("Qwilight", { body: toNotify, icon: `${wwwAPIPath}/drawing?avatarID=${getDefaultAvatarID( avatarID, )}&drawingVariety=0`, }); siteView.wasNotify = true; } } }; wsAPI.setEventDefaultHandler(({ eventID, text }) => { switch (eventID) { case EventPB.Event.EventID.ESTABLISH: if (text) { const { avatarID, avatarName } = JSON.parse(text); this.setSiteAvatar(avatarID, avatarName); this.setLoading(false); autoEnter(); const totem = window.sessionStorage.getItem("totem"); if (totem) { wsAPI.send({ eventID: EventPB.Event.EventID.LOG_IN, text: totem, }); } else { autoLogIn(); } } break; case EventPB.Event.EventID.FAILED_VALIDATE_TOTEM: autoLogIn(); break; // EventPB.Event.EventID.LOG_IN case undefined: if (text) { const { totem, avatarID, avatarName } = JSON.parse(text); if (totem !== window.sessionStorage.getItem("totem")) { toast.success(t("loggedInText", { avatarName })); } window.sessionStorage.setItem("totem", totem); this.setSiteAvatar(avatarID, avatarName); this.setLoggedIn(true); autoEnter(); } break; case EventPB.Event.EventID.NOT_LOG_IN: if (text) { toast.success( t("notLoggedInText", { avatarName: this.siteAvatarName, }), ); window.sessionStorage.removeItem("totem"); const { avatarID, avatarName } = JSON.parse(text); this.setSiteAvatar(avatarID, avatarName); this.setLoggedIn(false); autoEnter(); } break; case EventPB.Event.EventID.SITE_YELL: if (text) { const { siteID, avatarID, avatarName, siteYell, siteYellID, date, } = JSON.parse(text); const siteYellItem = getSiteYell({ avatarID, avatarName, siteYell, date, siteYellID, }); const siteView = this.getSiteView(siteID); if (siteView) { siteView.putSiteYell(siteYellItem, false); if (this.targetSiteID === siteID) { if ( avatarID === this.siteAvatarID || siteView.isSiteYellsViewLowest ) { siteView.siteYellsViewMove(); } else { siteView.setLastPendingSiteYell(siteYellItem); } } else { siteView.setNew(true); } const ltDate = new Date(Number(date)).toLocaleTimeString(); let toNotify = ""; switch (avatarName) { case "@Notify": siteView.setSiteNotify(siteYell); toNotify = `${t("siteYellTaehui")} (${ltDate}) ${siteYell}`; break; case "@Comment": { const { avatarName, artist, title, genre, levelText, stand, } = JSON.parse(siteYell); toNotify = `${avatarName} (${ltDate}) ${levelText} ${artist} - ${title} ${getGenreText( genre, )} ${t("textStand", { value: formatText(stand) })}`; break; } case "@Ability": { toNotify = getAbilityUpText( JSON.parse(siteYell) as AbilitySiteYell, ); break; } case "@Level": { const { title } = JSON.parse(siteYell); toNotify = t("wwwLevelClearText", { title }); break; } case "@Enter": { toNotify = `${siteYell} ${ltDate} ${t("siteYellEnter")}`; break; } case "@Quit": { toNotify = `${siteYell} ${ltDate} ${t("siteYellQuit")}`; break; } case "@Invite": { const { avatarName, siteName } = JSON.parse(siteYell); toNotify = `${avatarName} (${ltDate}) ${t( "siteYellInvite", { siteName, }, )}`; break; } case "@TV": { const { title, text } = JSON.parse(siteYell); toNotify = `${text} (${ltDate}) ${t("siteYellTV", { title, })}`; break; } case "@Wiped": toNotify = `${avatarName} (${ltDate}) ${t("wipedSiteYell")}`; break; case "": toNotify = siteYell; break; case null: toNotify = `${avatarName} (${ltDate}) ${siteYell}`; break; } if (toNotify) { doNotify(avatarID, toNotify, siteID); } } } break; case EventPB.Event.EventID.MODIFY_SITE_YELL: if (text) { const { siteID, siteYell, siteYellID } = JSON.parse(text); this.getSiteView(siteID)?.setSiteYell(siteYellID, siteYell); } break; case EventPB.Event.EventID.WIPE_SITE_YELL: if (text) { const { siteID, siteYellID } = JSON.parse(text); this.getSiteView(siteID)?.wipeSiteYell(siteYellID); } break; case EventPB.Event.EventID.GET_SITE_YELLS: if (text) { const { siteID, data } = JSON.parse(text); const siteView = this.getSiteView(siteID); if (siteView) { if (siteView.siteYellsView) { const { current } = siteView.siteYellsView; if (current) { const lastPosition1BeforeCalled = current.scrollHeight - current.clientHeight; data .reverse() .map(getSiteYell) .forEach((siteYellItem: SiteYellItem) => { siteView.putSiteYell(siteYellItem, true); }); setTimeout(() => { current.scrollTop = current.scrollHeight - current.clientHeight - lastPosition1BeforeCalled; }, 0); } } } } break; case EventPB.Event.EventID.CALL_SITE_AVATAR: if (text) { const { siteID, siteName, situationValue, data, siteHand } = JSON.parse(text); const siteView = this.getSiteView(siteID); if (siteView) { const avatars = data.map( ({ avatarID, avatarName, avatarConfigure, isValve, isAudioInput, }: { avatarID: string; avatarName: string; avatarConfigure: number; isValve: boolean; isAudioInput: boolean; }) => ({ avatarID, avatarName, avatarConfigure, isSiteHand: avatarID === siteHand, isMe: this.siteAvatarID === avatarID, isValve, isAudioInput, }), ); siteView.doCallSiteAvatar( getSiteName(siteName, t), siteHand, situationValue, avatars, ); } } break; case EventPB.Event.EventID.ENTER_SITE: if (text) { const { siteID, siteNotify, isEditable, isNetMode, data } = JSON.parse(text); const siteView = new SiteView( siteID, siteNotify, isEditable, isNetMode, data.map(getSiteYell), ); this.putSiteView(siteView); this.setTargetSiteID(siteID); } break; case EventPB.Event.EventID.QUIT_SITE: if (text) { this.wipeSiteView(text); } break; case EventPB.Event.EventID.NOTIFY: if (text) { const { v, text: toNotify } = JSON.parse(text); switch (v) { case 0: toast.success(toNotify); break; case 1: toast.error(toNotify); break; case 2: toast.warning(toNotify); break; case 3: toast.info(toNotify); break; } } break; case EventPB.Event.EventID.POST_FILE: if (text) { this.getSiteView(this.targetSiteID)?.setInput(text); } break; } }); }, setEventCloseHandler() { wsAPI.setEventCloseHandler(() => { this.setSiteAvatar("", ""); this.setLoggedIn(false); this.wipeSiteViews(); this.setLoading(true); }); }, setLoading(isLoading: boolean) { this.isLoading = isLoading; }, setSiteAvatar(siteAvatarID: string, siteAvatarName: string) { this.siteAvatarID = siteAvatarID; this.siteAvatarName = siteAvatarName; wsAPI.setAvatarID(this.siteAvatarID); }, setSaveTraffic(SaveTraffic: boolean) { this.saveTraffic = SaveTraffic; }, setAutoEnterNotify(autoEnterNotify: boolean) { this.autoEnterNotify = autoEnterNotify; }, setAutoEnterDefault(autoEnterDefault: boolean) { this.autoEnterDefault = autoEnterDefault; }, setAutoEnterPlatform(autoEnterPlatform: boolean) { this.autoEnterPlatform = autoEnterPlatform; }, setSiteWindowOpened(isSiteWindowOpened: boolean) { this.isSiteWindowOpened = isSiteWindowOpened; }, setSiteCipherWindowOpened(isSiteCipherWindowOpened: boolean) { this.isSiteCipherWindowOpened = isSiteCipherWindowOpened; }, setNewSiteWindowOpened(isNewSiteWindowOpened: boolean) { this.isNewSiteWindowOpened = isNewSiteWindowOpened; }, setConfigureOpened(isConfigureOpened: boolean) { this.isConfigureOpened = isConfigureOpened; }, getSiteView(targetSiteID: string) { return this.siteViews.find(({ siteID }) => siteID === targetSiteID); }, setTargetSiteID(targetSiteID: string) { this.targetSiteID = targetSiteID; const siteView = this.getSiteView(this.targetSiteID); if (siteView) { siteView.setNew(false); } }, setVisible() { return (this.isVisible = document.visibilityState === "visible"); }, setLoggedIn(isLoggedIn: boolean) { this.isLoggedIn = isLoggedIn; }, putSiteView(siteView: SiteView) { this.siteViews.push(siteView); }, wipeSiteView(targetSiteID: string) { this.siteViews.splice( this.siteViews.findIndex(({ siteID }) => siteID === targetSiteID), 1, ); this.targetSiteID = this.siteViews[this.siteViews.length - 1].siteID; }, wipeSiteViews() { this.siteViews = []; this.targetSiteID = ""; }, }; }