diff --git a/static/js/04-main.js b/static/js/04-main.js index fc69436..a8e1ca1 100644 --- a/static/js/04-main.js +++ b/static/js/04-main.js @@ -12,6 +12,8 @@ Glow.openCallPanel = async () => { overlay?.classList.remove("hidden"); participants?.classList.remove("hidden"); + Glow.restoreCallPanelPosition?.(); + if (Glow.state.calls.activeCall?.groupSession?.id) { await Glow.calls.refreshBackendParticipants(); } @@ -66,6 +68,100 @@ Glow.loadRooms = async () => { Glow.updateChatControls(); }; +Glow.restoreCallPanelPosition = () => { + const overlay = Glow.els.callOverlay; + if (!overlay) return; + + const savedX = localStorage.getItem("glow_call_panel_x"); + const savedY = localStorage.getItem("glow_call_panel_y"); + + if (savedX == null || savedY == null) return; + + const x = Number(savedX); + const y = Number(savedY); + + if (!Number.isFinite(x) || !Number.isFinite(y)) return; + + Glow.state.calls.drag.x = x; + Glow.state.calls.drag.y = y; + overlay.style.left = `${x}px`; + overlay.style.top = `${y}px`; + overlay.style.right = "auto"; + overlay.style.bottom = "auto"; + overlay.style.alignItems = "flex-start"; + overlay.style.justifyContent = "flex-start"; +}; + +Glow.persistCallPanelPosition = () => { + const drag = Glow.state.calls?.drag; + if (!drag) return; + if (!Number.isFinite(drag.x) || !Number.isFinite(drag.y)) return; + localStorage.setItem("glow_call_panel_x", String(drag.x)); + localStorage.setItem("glow_call_panel_y", String(drag.y)); +}; + +Glow.confirmDirtySettingsBeforeNavigate = async action => { + if (Glow.state.activeView !== "settings" || !Glow.isSettingsDirty()) { + await action(); + return true; + } + + Glow.syncSettingsSavePanel?.(); + + const values = await Glow.openDialog({ + title: "Unsaved settings changes", + body: "You have unsaved changes. Would you like to save before leaving this page?", + confirmText: "Save and leave", + cancelText: "Stay" + }); + + if (values === null) { + const discard = await Glow.openDialog({ + title: "Discard changes?", + body: "If you leave now, your unsaved settings changes will be lost.", + confirmText: "Discard changes", + cancelText: "Stay", + danger: true + }); + + if (!discard) { + return false; + } + + Glow.discardSettingsDraft?.(); + await action(); + return true; + } + + try { + await Glow.saveSettingsDraft?.(); + await action(); + return true; + } catch (error) { + Glow.showToast(error.message || "Could not save settings.", "error"); + return false; + } +}; + +Glow.navigateToView = async (view, options = {}) => { + return Glow.confirmDirtySettingsBeforeNavigate(async () => { + if (typeof options.before === "function") { + await options.before(); + } + + Glow.state.activeView = view; + Glow.updateViewUI(); + + if (typeof options.after === "function") { + await options.after(); + } + + if (options.schedulePoll !== false) { + Glow.schedulePoll?.(); + } + }); +}; + Glow.updateViewUI = () => { const view = Glow.state.activeView || "feed"; const room = Glow.state.activeRoom; @@ -89,6 +185,8 @@ Glow.updateViewUI = () => { Glow.els.composerWrap.classList.toggle("hidden", !chatActive); Glow.els.threadComposerWrap?.classList.toggle("hidden", !threadActive); + Glow.syncSettingsSavePanel?.(); + if (!chatActive) { Glow.state.typingUsers = []; Glow.state.dmTypingUsers = []; @@ -220,7 +318,7 @@ Glow.updateAuthUI = () => { Glow.els.logoutButton.classList.toggle("hidden", !authenticated); Glow.els.headerAccount.textContent = - user ? `${user.username} · ${user.role}` : "not signed in"; + user ? `${user.username} · ${user.role}` : "not signed in"; document.body.classList.toggle("is-moderator", Glow.canModerate()); document.body.classList.toggle("is-admin", Glow.isAdmin()); @@ -397,6 +495,9 @@ Glow.logout = async () => { Glow.state.activePage = null; Glow.state.activeProfile = null; Glow.state.settings = null; + Glow.state.settingsSaved = null; + Glow.state.settingsDraft = null; + Glow.state.settingsDirty = false; Glow.clearMessages(); Glow.clearFeed(); @@ -574,6 +675,23 @@ Glow.els.threadInput?.addEventListener("input", () => { }); Glow.els.messageInput.addEventListener("keydown", event => { + if (Glow.state.emojiAutocomplete?.open) { + if (event.key === "Enter" || event.key === "Tab") { + if (typeof Glow.applyEmojiSuggestion === "function") { + event.preventDefault(); + Glow.applyEmojiSuggestion(); + return; + } + } + if (event.key === "Escape") { + if (typeof Glow.closeEmojiAutocomplete === "function") { + event.preventDefault(); + Glow.closeEmojiAutocomplete(); + return; + } + } + } + if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); Glow.sendMessage(event); @@ -581,6 +699,23 @@ Glow.els.messageInput.addEventListener("keydown", event => { }); Glow.els.threadInput?.addEventListener("keydown", event => { + if (Glow.state.emojiAutocomplete?.open && Glow.state.emojiAutocomplete.owner === "thread") { + if (event.key === "Enter" || event.key === "Tab") { + if (typeof Glow.applyEmojiSuggestion === "function") { + event.preventDefault(); + Glow.applyEmojiSuggestion(); + return; + } + } + if (event.key === "Escape") { + if (typeof Glow.closeEmojiAutocomplete === "function") { + event.preventDefault(); + Glow.closeEmojiAutocomplete(); + return; + } + } + } + if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); Glow.sendThreadReply?.(event); @@ -625,31 +760,45 @@ Glow.els.threadAttachmentInput?.addEventListener("change", () => { Glow.setSelectedThreadAttachment?.(files); }); -Glow.els.feedButton.addEventListener("click", Glow.openFeed); - -Glow.els.dmsButton?.addEventListener("click", async () => { - Glow.state.activeView = "dms"; - Glow.updateViewUI(); - await Glow.loadDms?.(); - Glow.schedulePoll(); +Glow.els.feedButton.addEventListener("click", async () => { + await Glow.navigateToView("feed", { + before: async () => { + Glow.state.activeDm = null; + }, + after: async () => { + await Glow.loadFeed(); + } + }); }); -Glow.els.pagesButton?.addEventListener("click", () => { - Glow.openPages?.(); +Glow.els.dmsButton?.addEventListener("click", async () => { + await Glow.navigateToView("dms", { + after: async () => { + await Glow.loadDms?.(); + } + }); +}); + +Glow.els.pagesButton?.addEventListener("click", async () => { + await Glow.confirmDirtySettingsBeforeNavigate(async () => { + Glow.openPages?.(); + }); }); Glow.els.backButton?.addEventListener("click", async () => { - if (Glow.state.activeView === "thread") { - await Glow.returnToChannel?.(); - } else if (Glow.state.activeView === "page") { - await Glow.openPages?.(); - } else if (Glow.state.activeView === "profile" || Glow.state.activeView === "settings") { - await Glow.openFeed?.(); - } else if (Glow.state.activeView === "dm") { - Glow.state.activeView = "dms"; - Glow.updateViewUI(); - await Glow.loadDms?.(); - } + await Glow.confirmDirtySettingsBeforeNavigate(async () => { + if (Glow.state.activeView === "thread") { + await Glow.returnToChannel?.(); + } else if (Glow.state.activeView === "page") { + await Glow.openPages?.(); + } else if (Glow.state.activeView === "profile" || Glow.state.activeView === "settings") { + await Glow.openFeed?.(); + } else if (Glow.state.activeView === "dm") { + Glow.state.activeView = "dms"; + Glow.updateViewUI(); + await Glow.loadDms?.(); + } + }); }); Glow.els.loginButton.addEventListener("click", () => { @@ -675,19 +824,21 @@ Glow.els.roomSelect.addEventListener("change", async () => { ); if (!selected) return; - Glow.state.activeDm = null; - Glow.state.activeRoom = selected; - Glow.state.activeView = "room"; - Glow.state.typingUsers = []; - Glow.state.dmTypingUsers = []; - Glow.renderTyping(); - Glow.updateViewUI(); + await Glow.confirmDirtySettingsBeforeNavigate(async () => { + Glow.state.activeDm = null; + Glow.state.activeRoom = selected; + Glow.state.activeView = "room"; + Glow.state.typingUsers = []; + Glow.state.dmTypingUsers = []; + Glow.renderTyping(); + Glow.updateViewUI(); - await Glow.loadMessages(); - Glow.pollTyping(); - Glow.pingPresence?.(); - Glow.refreshPresence?.(); - Glow.schedulePoll(); + await Glow.loadMessages(); + Glow.pollTyping(); + Glow.pingPresence?.(); + Glow.refreshPresence?.(); + Glow.schedulePoll(); + }); }); Glow.els.teamSelect?.addEventListener("change", () => { @@ -700,8 +851,8 @@ Glow.els.adminFab.addEventListener("click", Glow.openAdminPanel); Glow.els.closeAdminButton.addEventListener("click", Glow.closeAdminPanel); Glow.els.createRoomButton.addEventListener("click", Glow.createRoom); Glow.els.clearMessagesButton.addEventListener("click", Glow.clearActiveRoom); -Glow.els.typingToggleButton.addEventListener("click", Glow.toggleTypingIndicators); -Glow.els.notificationsToggleButton.addEventListener("click", Glow.toggleNotifications); +Glow.els.typingToggleButton?.addEventListener("click", Glow.toggleTypingIndicators); +Glow.els.notificationsToggleButton?.addEventListener("click", Glow.toggleNotifications); Glow.els.themeButton?.addEventListener("click", Glow.openAccentColorPicker); Glow.els.settingsButton?.addEventListener("click", () => { @@ -765,9 +916,10 @@ document.getElementById("closeCallPanelButton")?.addEventListener("click", () => Glow.calls.ensureState(); Glow.state.calls.panelOpen = false; document.getElementById("callOverlay")?.classList.add("hidden"); + Glow.persistCallPanelPosition?.(); }); -document.addEventListener("keydown", event => { +document.addEventListener("keydown", async event => { if (event.key !== "Escape") return; if (!Glow.els.dialogOverlay.classList.contains("hidden")) { @@ -775,6 +927,11 @@ document.addEventListener("keydown", event => { return; } + if (!Glow.els.imageCropOverlay?.classList.contains("hidden")) { + Glow.profileResetCropState?.(); + return; + } + if (!Glow.els.mediaOverlay?.classList.contains("hidden")) { Glow.closeMediaModal?.(); return; @@ -794,23 +951,30 @@ document.addEventListener("keydown", event => { Glow.calls.ensureState(); Glow.state.calls.panelOpen = false; Glow.els.callOverlay.classList.add("hidden"); + Glow.persistCallPanelPosition?.(); return; } if (Glow.state.activeView === "thread") { - Glow.returnToChannel?.(); + await Glow.confirmDirtySettingsBeforeNavigate(async () => { + Glow.returnToChannel?.(); + }); return; } if (Glow.state.activeView === "page") { - Glow.openPages?.(); + await Glow.confirmDirtySettingsBeforeNavigate(async () => { + Glow.openPages?.(); + }); return; } if (Glow.state.activeView === "dm") { - Glow.state.activeView = "dms"; - Glow.updateViewUI(); - Glow.loadDms?.(); + await Glow.confirmDirtySettingsBeforeNavigate(async () => { + Glow.state.activeView = "dms"; + Glow.updateViewUI(); + Glow.loadDms?.(); + }); return; } @@ -894,7 +1058,13 @@ setInterval(() => { } }, 10000); -window.addEventListener("beforeunload", () => { +window.addEventListener("beforeunload", event => { + if (Glow.state.activeView === "settings" && Glow.isSettingsDirty()) { + event.preventDefault(); + event.returnValue = ""; + return ""; + } + const sessionId = Glow.state.calls.activeCall?.groupSession?.id; if (!sessionId || !navigator.sendBeacon) return; @@ -926,4 +1096,5 @@ Glow.autoResizeComposer(); Glow.updateMessageCount(); Glow.autoResizeThreadComposer?.(); Glow.updateThreadMessageCount?.(); -Glow.checkAuth(); +Glow.state.dmLayoutMode = Glow.loadDmLayoutPreference(); +Glow.checkAuth(); \ No newline at end of file