From 9e9ede570d924e5c318c92ff78dedf7349d30657 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Tue, 14 Apr 2026 17:19:35 +0000 Subject: [PATCH] integrate context with window.history API --- dist/index.js | 106 +++++++++++------- src/View/Modals/dataTransferModal.tsx | 4 +- src/View/Modals/settingsModal.tsx | 2 +- src/View/Modals/storageModal.tsx | 2 +- src/ViewModel/Chat/chatListViewModel.ts | 2 - src/ViewModel/Chat/chatViewModel.ts | 18 +-- src/ViewModel/Global/coreViewModel.ts | 20 +++- src/ViewModel/Global/fileTransferViewModel.ts | 10 +- src/ViewModel/Global/settingsViewModel.ts | 22 ++-- src/ViewModel/Global/storageViewModel.ts | 10 +- src/ViewModel/Pages/boardViewModel.ts | 15 ++- src/ViewModel/Pages/taskPageViewModel.ts | 13 ++- src/ViewModel/Pages/taskViewModel.ts | 16 +-- 13 files changed, 150 insertions(+), 90 deletions(-) diff --git a/dist/index.js b/dist/index.js index d400bebf..c9d12a52 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2265,14 +2265,15 @@ this.connectionModel = connectionModel2; this.chatListModel = chatListModel2; this.fileTransferModel = fileTransferModel2; - this.BUILD = "Build 26.04.14.D"; + this.BUILD = "Build 26.04.14.E"; // CONTEXT this.contextStack = /* @__PURE__ */ new Map(); - this.closeContext = (contextId) => { + this.closeContext = (contextId, fromHistoryEvent = false) => { if (!this.contexts.map((context) => context.contextId).includes(contextId)) return; while (this.contexts.length > 0) { const currentContext = this.context; + currentContext.handleContextClose(fromHistoryEvent); this.contextStack.delete(currentContext.contextId); if (currentContext.contextId == contextId) break; } @@ -2297,6 +2298,9 @@ this.taskStatusSuggestions = new ListState(); this.translations = allTranslations[settingsModel2.language] || allTranslations.en; document.body.addEventListener("keydown", this.handleKeyDown); + window.onpopstate = () => { + this.closeContext(this.context.contextId, true); + }; } get contexts() { return [...this.contextStack.values()]; @@ -2307,6 +2311,7 @@ set context(context) { if (this.contextStack.has(context.contextId)) return; this.contextStack.set(context.contextId, context); + history.pushState({ id: context.contextId }, ""); } // util static checkIsKeystroke(e) { @@ -2326,6 +2331,10 @@ fn(); return true; }; + this.close = () => { + }; + this.handleContextClose = (fromHistoryEvent) => { + }; this.registerKeyStroke = (key, fn) => { this.keystrokes.set(key, fn); }; @@ -2339,7 +2348,7 @@ this.registerContext = (key, context) => { this.contexts.set(key, context); }; - this.closeContext = () => { + this.closeCurrentContext = () => { if (this.currentContext.value) { this.coreViewModel.closeContext( this.currentContext.value.contextId @@ -2354,7 +2363,7 @@ ); if (!selectedContext) return; if (selectedContext != this.currentContext.value) { - this.closeContext(); + this.closeCurrentContext(); } this.coreViewModel.context = selectedContext; this.currentContext.value = selectedContext; @@ -2438,10 +2447,6 @@ this.coreViewModel.context = this; this.containingViewModel.selectTask(this); }; - this.close = () => { - this.coreViewModel.closeContext(this.contextId); - this.containingViewModel.closeTask(); - }; this.closeAndDiscard = () => { this.close(); this.loadTaskData(); @@ -2522,9 +2527,15 @@ this.priority.value = this.task.priority ?? ""; this.date.value = this.task.date ?? ""; this.time.value = this.task.time ?? ""; - this.selectedVersionId.value = this.task.fileContentId; this.updateSuggestions(); }; + // exit + this.close = () => { + this.coreViewModel.closeContext(this.contextId); + }; + this.handleContextClose = () => { + this.containingViewModel.closeTask(); + }; this.loadAllData(); this.selectedVersionId.subscribeSilent((selectedVersionId) => { this.switchVersion(selectedVersionId); @@ -3302,6 +3313,7 @@ }; this.close = () => { this.taskPageViewModel.closeBoard(); + this.taskPageViewModel.storeLastUsedBoard(); this.taskViewModels.clear(); }; this.showSettings = () => { @@ -3365,6 +3377,12 @@ this.loadTasks(); this.loadSearchSuggestions(); }; + // exit + this.handleContextClose = (fromHistoryEvent) => { + this.taskPageViewModel.handleBoardClosed(this); + if (!fromHistoryEvent) return; + this.taskPageViewModel.storeLastUsedBoard(); + }; this.preloadData(); this.isSelected = createProxyState( [this.taskPageViewModel.selectedBoardId], @@ -3378,11 +3396,6 @@ }); this.selectedPage.subscribeSilent(() => { this.storeLastUsedView(); - console.log( - "SELECTED PAGE", - this.selectedPage.value, - this.contextId - ); }); boardsAndTasksModel.taskHandlerManager.setHandler( this.boardInfo.fileId, @@ -3502,16 +3515,19 @@ this.selectBoard = (boardViewModel) => { this.chatViewModel.displayedColor.value = boardViewModel.color.value; if (this.selectedBoardId.value == boardViewModel.boardInfo.fileId) { - return this.updateContexts(); + this.updateContexts(); + return; } this.selectedBoardId.value = boardViewModel.boardInfo.fileId; this.storeLastUsedBoard(); }; this.closeBoard = () => { - this.closeContext(); + this.closeCurrentContext(); + }; + this.handleBoardClosed = (boardViewModel) => { + if (boardViewModel.boardInfo.fileId != this.selectedBoardId.value) return; this.selectedBoardId.value = void 0; this.chatViewModel.resetColor(); - this.storeLastUsedBoard(); }; this.updateBoardIndices = () => { this.boardIndexManager.update([...this.boardViewModels.value.values()]); @@ -3535,7 +3551,7 @@ }; // load this.loadData = () => { - this.closeContext(); + this.closeCurrentContext(); const boardIds = this.boardsAndTasksModel.listBoardIds(); for (const boardId of boardIds) { if (this.boardViewModels.value.has(boardId)) continue; @@ -3598,12 +3614,8 @@ this.coreViewModel.context = this; this.chatListViewModel.openChat(this); }; - this.close = () => { - this.coreViewModel.closeContext(this.contextId); - this.chatListViewModel.closeChat(); - }; this.openPage = (page) => { - this.closeContext(); + this.closeCurrentContext(); this.selectedPage.value = page; }; this.setColor = (color) => { @@ -3662,6 +3674,13 @@ } ); }; + // exit + this.close = () => { + this.coreViewModel.closeContext(this.contextId); + }; + this.handleContextClose = () => { + this.chatListViewModel.closeChat(); + }; this.calendarViewModel = new CalendarPageViewModel( coreViewModel2, this, @@ -6255,8 +6274,11 @@ }; this.coreViewModel.fileTransferModel.prepareToReceive(transferData); }; - this.hideModal = () => { + // exit + this.close = () => { this.coreViewModel.closeContext(this.contextId); + }; + this.handleContextClose = () => { this.presentedModal.value = void 0; }; this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler( @@ -6267,7 +6289,7 @@ "file-transfer-view-model", () => this.initiateTransfer() ); - this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideModal); + this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); } }; @@ -6326,7 +6348,7 @@ /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.toThisDeviceButton)), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") ) - )), /* @__PURE__ */ createElement("button", { "on:click": fileTransferViewModel2.hideModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + )), /* @__PURE__ */ createElement("button", { "on:click": fileTransferViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } function FileSelectionModal(coreViewModel2, fileTransferViewModel2) { const OptionConverter = (fileOption) => { @@ -6454,7 +6476,7 @@ "button", { class: "flex", - "on:click": fileTransferViewModel2.hideModal, + "on:click": fileTransferViewModel2.close, "toggle:disabled": fileTransferViewModel2.didNotFinishSending }, coreViewModel2.translations.general.closeButton, @@ -6888,13 +6910,6 @@ this.coreViewModel.context = this; this.isShowingSettingsModal.value = true; }; - this.hideSettingsModal = () => { - this.coreViewModel.closeContext(this.contextId); - this.isShowingSettingsModal.value = false; - if (this.requiresReload.value == true) { - window.location.reload(); - } - }; this.showModalPage = (page) => { this.selectedModalPage.value = page; }; @@ -6906,6 +6921,16 @@ } document.body.setAttribute("theme", theme); }; + // exit + this.close = () => { + this.coreViewModel.closeContext(this.contextId); + }; + this.handleContextClose = () => { + this.isShowingSettingsModal.value = false; + if (this.requiresReload.value == true) { + window.location.reload(); + } + }; this.username.value = coreViewModel2.settingsModel.username; this.usernameInput.value = coreViewModel2.settingsModel.username; this.firstDayOfWeek.value = coreViewModel2.settingsModel.firstDayOfWeek; @@ -6928,7 +6953,7 @@ ); this.registerKeyStroke( "backspace" /* CloseOrCancel */, - this.hideSettingsModal + this.close ); } static generateThemeMedia() { @@ -7063,7 +7088,7 @@ new State(DirectoryItemList(storageViewModel2)), detailView, true - )), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.hideStorageModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + )), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } // src/ViewModel/Global/storageViewModel.ts @@ -7104,8 +7129,11 @@ this.coreViewModel.context = this; this.isShowingStorageModal.value = true; }; - this.hideStorageModal = () => { + // exit + this.close = () => { this.coreViewModel.closeContext(this.contextId); + }; + this.handleContextClose = () => { if (this.didMakeChanges.value == true) { window.location.reload(); return; @@ -7120,7 +7148,7 @@ [this.selectedPath], () => this.getSelectedItemContent() ); - this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideStorageModal); + this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); } }; @@ -7403,7 +7431,7 @@ detailView, true, settingsViewModel2.selectedModalPage - )), /* @__PURE__ */ createElement("button", { "on:click": settingsViewModel2.hideSettingsModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) + )), /* @__PURE__ */ createElement("button", { "on:click": settingsViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) ); } function SettingsLeftPane(coreViewModel2, settingsViewModel2) { diff --git a/src/View/Modals/dataTransferModal.tsx b/src/View/Modals/dataTransferModal.tsx index 7db6b1c8..9bb02fbc 100644 --- a/src/View/Modals/dataTransferModal.tsx +++ b/src/View/Modals/dataTransferModal.tsx @@ -112,7 +112,7 @@ function DirectionSelectionModal( - @@ -393,7 +393,7 @@ function TransferDisplayModal( diff --git a/src/View/Modals/storageModal.tsx b/src/View/Modals/storageModal.tsx index 2b9f7d75..7c285d72 100644 --- a/src/View/Modals/storageModal.tsx +++ b/src/View/Modals/storageModal.tsx @@ -78,7 +78,7 @@ export function StorageModal( true, )} - diff --git a/src/ViewModel/Chat/chatListViewModel.ts b/src/ViewModel/Chat/chatListViewModel.ts index d9f512bb..eef94501 100644 --- a/src/ViewModel/Chat/chatListViewModel.ts +++ b/src/ViewModel/Chat/chatListViewModel.ts @@ -1,12 +1,10 @@ import * as React from "bloatless-react"; -import ChatListModel from "../../Model/Chat/chatListModel"; import ChatModel from "../../Model/Chat/chatModel"; import ChatViewModel from "./chatViewModel"; import CoreViewModel from "../Global/coreViewModel"; import { IndexManager } from "../../Model/Utility/utility"; import SettingsViewModel from "../Global/settingsViewModel"; -import StorageModel from "../../Model/Global/storageModel"; import ConnectionViewModel from "../Global/connectionViewModel"; import NotificationViewModel from "../Global/notificationViewModel"; diff --git a/src/ViewModel/Chat/chatViewModel.ts b/src/ViewModel/Chat/chatViewModel.ts index cd9501ac..b54ccb82 100644 --- a/src/ViewModel/Chat/chatViewModel.ts +++ b/src/ViewModel/Chat/chatViewModel.ts @@ -58,14 +58,9 @@ export default class ChatViewModel extends ContextHost { this.chatListViewModel.openChat(this); //this.selectedPage.callSubscriptions(); }; - - close = (): void => { - this.coreViewModel.closeContext(this.contextId); - this.chatListViewModel.closeChat(); - }; - + openPage = (page: ChatPageTypes): void => { - this.closeContext(); + this.closeCurrentContext(); this.selectedPage.value = page; }; @@ -140,6 +135,15 @@ export default class ChatViewModel extends ContextHost { ); }; + // exit + close = (): void => { + this.coreViewModel.closeContext(this.contextId); + }; + + handleContextClose = (): void => { + this.chatListViewModel.closeChat(); + } + // init constructor( public readonly coreViewModel: CoreViewModel, diff --git a/src/ViewModel/Global/coreViewModel.ts b/src/ViewModel/Global/coreViewModel.ts index 81b3f3b7..7399eb84 100644 --- a/src/ViewModel/Global/coreViewModel.ts +++ b/src/ViewModel/Global/coreViewModel.ts @@ -14,6 +14,7 @@ export default class CoreViewModel { // CONTEXT private contextStack = new Map(); + get contexts(): Context[] { return [...this.contextStack.values()]; } @@ -23,16 +24,20 @@ export default class CoreViewModel { set context(context: Context) { if (this.contextStack.has(context.contextId)) return; this.contextStack.set(context.contextId, context); + history.pushState({ id: context.contextId }, ""); } - closeContext = (contextId: string): void => { + + closeContext = (contextId: string, fromHistoryEvent: boolean = false): void => { if ( !this.contexts .map((context) => context.contextId) .includes(contextId) ) return; + while (this.contexts.length > 0) { const currentContext: Context = this.context; + currentContext.handleContextClose(fromHistoryEvent); this.contextStack.delete(currentContext.contextId); if (currentContext.contextId == contextId) break; } @@ -73,6 +78,10 @@ export default class CoreViewModel { allTranslations[settingsModel.language] || allTranslations.en; document.body.addEventListener("keydown", this.handleKeyDown); + + window.onpopstate = () => { + this.closeContext(this.context.contextId, true); + } } // util @@ -94,13 +103,14 @@ export class Context { return true; }; - close: () => void; + close = (): void => {}; + handleContextClose = (fromHistoryEvent: boolean): void => {}; registerKeyStroke = (key: string, fn: () => void): void => { this.keystrokes.set(key, fn); }; - constructor(public contextDebugDescription: string) {} + constructor(public contextDebugDescription: string) { } } export class ContextHost extends Context { @@ -119,7 +129,7 @@ export class ContextHost extends Context { this.contexts.set(key, context); }; - closeContext = (): void => { + closeCurrentContext = (): void => { if (this.currentContext.value) { this.coreViewModel.closeContext( this.currentContext.value.contextId, @@ -136,7 +146,7 @@ export class ContextHost extends Context { ); if (!selectedContext) return; if (selectedContext != this.currentContext.value) { - this.closeContext(); + this.closeCurrentContext(); } this.coreViewModel.context = selectedContext; diff --git a/src/ViewModel/Global/fileTransferViewModel.ts b/src/ViewModel/Global/fileTransferViewModel.ts index 8f8b3427..ed90df2c 100644 --- a/src/ViewModel/Global/fileTransferViewModel.ts +++ b/src/ViewModel/Global/fileTransferViewModel.ts @@ -164,11 +164,15 @@ export default class FileTransferViewModel extends Context { this.coreViewModel.fileTransferModel.prepareToReceive(transferData); }; - hideModal = (): void => { + // exit + close = (): void => { this.coreViewModel.closeContext(this.contextId); - this.presentedModal.value = undefined; }; + handleContextClose = (): void => { + this.presentedModal.value = undefined; + } + // init constructor(public readonly coreViewModel: CoreViewModel) { super("file-transfer"); @@ -184,7 +188,7 @@ export default class FileTransferViewModel extends Context { ); // keystrokes - this.registerKeyStroke(CommonKeys.CloseOrCancel, this.hideModal); + this.registerKeyStroke(CommonKeys.CloseOrCancel, this.close); } } diff --git a/src/ViewModel/Global/settingsViewModel.ts b/src/ViewModel/Global/settingsViewModel.ts index 99202a5a..e678428f 100644 --- a/src/ViewModel/Global/settingsViewModel.ts +++ b/src/ViewModel/Global/settingsViewModel.ts @@ -48,14 +48,6 @@ export default class SettingsViewModel extends Context { this.isShowingSettingsModal.value = true; }; - hideSettingsModal = (): void => { - this.coreViewModel.closeContext(this.contextId); - this.isShowingSettingsModal.value = false; - if (this.requiresReload.value == true) { - window.location.reload(); - } - }; - showModalPage = (page: SettingsModalPages): void => { this.selectedModalPage.value = page; }; @@ -69,6 +61,18 @@ export default class SettingsViewModel extends Context { document.body.setAttribute("theme", theme); }; + // exit + close = (): void => { + this.coreViewModel.closeContext(this.contextId); + }; + + handleContextClose = (): void => { + this.isShowingSettingsModal.value = false; + if (this.requiresReload.value == true) { + window.location.reload(); + } + } + // init constructor(public readonly coreViewModel: CoreViewModel) { super("settings"); @@ -100,7 +104,7 @@ export default class SettingsViewModel extends Context { // keystrokes this.registerKeyStroke( CommonKeys.CloseOrCancel, - this.hideSettingsModal, + this.close, ); } diff --git a/src/ViewModel/Global/storageViewModel.ts b/src/ViewModel/Global/storageViewModel.ts index 2561d42d..4f4baab5 100644 --- a/src/ViewModel/Global/storageViewModel.ts +++ b/src/ViewModel/Global/storageViewModel.ts @@ -54,14 +54,18 @@ export default class StorageViewModel extends Context { this.isShowingStorageModal.value = true; }; - hideStorageModal = (): void => { + // exit + close = (): void => { this.coreViewModel.closeContext(this.contextId); + }; + + handleContextClose = (): void => { if (this.didMakeChanges.value == true) { window.location.reload(); return; } this.isShowingStorageModal.value = false; - }; + } // init constructor(public readonly coreViewModel: CoreViewModel) { @@ -77,6 +81,6 @@ export default class StorageViewModel extends Context { ); // keystrokes - this.registerKeyStroke(CommonKeys.CloseOrCancel, this.hideStorageModal); + this.registerKeyStroke(CommonKeys.CloseOrCancel, this.close); } } diff --git a/src/ViewModel/Pages/boardViewModel.ts b/src/ViewModel/Pages/boardViewModel.ts index d0f241e4..b45515ba 100644 --- a/src/ViewModel/Pages/boardViewModel.ts +++ b/src/ViewModel/Pages/boardViewModel.ts @@ -9,7 +9,6 @@ import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; import { Colors } from "../../colors"; import CoreViewModel from "../Global/coreViewModel"; import SearchViewModel from "../Utility/searchViewModel"; -import StorageModel from "../../Model/Global/storageModel"; import TaskContainingPageViewModel from "./taskContainingPageViewModel"; import TaskPageViewModel from "./taskPageViewModel"; import TaskViewModel from "./taskViewModel"; @@ -164,6 +163,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel { close = (): void => { this.taskPageViewModel.closeBoard(); + this.taskPageViewModel.storeLastUsedBoard(); this.taskViewModels.clear(); }; @@ -246,6 +246,14 @@ export default class BoardViewModel extends TaskContainingPageViewModel { this.loadSearchSuggestions(); }; + // exit + handleContextClose = (fromHistoryEvent: boolean): void => { + this.taskPageViewModel.handleBoardClosed(this); + + if (!fromHistoryEvent) return; + this.taskPageViewModel.storeLastUsedBoard(); + } + // init constructor( public readonly coreViewModel: CoreViewModel, @@ -276,11 +284,6 @@ export default class BoardViewModel extends TaskContainingPageViewModel { this.selectedPage.subscribeSilent(() => { this.storeLastUsedView(); - console.log( - "SELECTED PAGE", - this.selectedPage.value, - this.contextId, - ); }); // handlers diff --git a/src/ViewModel/Pages/taskPageViewModel.ts b/src/ViewModel/Pages/taskPageViewModel.ts index e0aee8de..27708acf 100644 --- a/src/ViewModel/Pages/taskPageViewModel.ts +++ b/src/ViewModel/Pages/taskPageViewModel.ts @@ -104,7 +104,8 @@ export default class TaskPageViewModel extends ContextHost { this.chatViewModel.displayedColor.value = boardViewModel.color.value; if (this.selectedBoardId.value == boardViewModel.boardInfo.fileId) { - return this.updateContexts(); + this.updateContexts(); + return; } this.selectedBoardId.value = boardViewModel.boardInfo.fileId; @@ -112,12 +113,14 @@ export default class TaskPageViewModel extends ContextHost { }; closeBoard = (): void => { - this.closeContext(); + this.closeCurrentContext(); + } + + handleBoardClosed = (boardViewModel: BoardViewModel): void => { + if (boardViewModel.boardInfo.fileId != this.selectedBoardId.value) return; this.selectedBoardId.value = undefined; this.chatViewModel.resetColor(); - - this.storeLastUsedBoard(); }; updateBoardIndices = (): void => { @@ -149,7 +152,7 @@ export default class TaskPageViewModel extends ContextHost { // load loadData = (): void => { - this.closeContext(); + this.closeCurrentContext(); const boardIds: string[] = this.boardsAndTasksModel.listBoardIds(); for (const boardId of boardIds) { diff --git a/src/ViewModel/Pages/taskViewModel.ts b/src/ViewModel/Pages/taskViewModel.ts index 7c4a8fc7..78c44a64 100644 --- a/src/ViewModel/Pages/taskViewModel.ts +++ b/src/ViewModel/Pages/taskViewModel.ts @@ -89,11 +89,6 @@ export default class TaskViewModel extends Context { this.containingViewModel.selectTask(this); }; - close = (): void => { - this.coreViewModel.closeContext(this.contextId); - this.containingViewModel.closeTask(); - }; - closeAndDiscard = (): void => { this.close(); this.loadTaskData(); @@ -203,11 +198,18 @@ export default class TaskViewModel extends Context { this.date.value = this.task.date ?? ""; this.time.value = this.task.time ?? ""; - this.selectedVersionId.value = this.task.fileContentId; - this.updateSuggestions(); }; + // exit + close = (): void => { + this.coreViewModel.closeContext(this.contextId); + }; + + handleContextClose = (): void => { + this.containingViewModel.closeTask(); + }; + // init constructor( public readonly coreViewModel: CoreViewModel,