Files
op-pedal/vite/src/pipedal/DialogStack.tsx
T
2026-06-02 12:59:41 -04:00

204 lines
5.4 KiB
TypeScript

/*
* Copyright (c) Robin E.R. Davies
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
export interface IDialogStackable {
getTag: () => string;
isOpen: () => boolean;
onDialogStackClose(): void;
}
interface DialogStackEntry {
dialog: IDialogStackable | null;
historyState: DialogStackHistoryState
};
let dialogStack: DialogStackEntry[] = [];
const DLG_STATE_MAGIC = "DlgState438327"
export interface DialogStackHistoryState {
magic: string;
topmost: boolean;
stackDepth: number;
tag: string;
id: string;
previousState: DialogStackHistoryState | null;
};
export function pushDialogStack(dialog: IDialogStackable): void {
let stackId = Math.random().toString(36).substring(7);
let dialogStackState: DialogStackHistoryState = {
magic: DLG_STATE_MAGIC,
topmost: false,
stackDepth: dialogStack.length + 1,
tag: dialog.getTag(),
id: stackId, previousState:
window.history.state as DialogStackHistoryState | null
};
window.history.replaceState(
dialogStackState,
"",
"#" + dialog.getTag());
// refresh reloads the page.
let topState: DialogStackHistoryState = {
topmost: true,
magic: DLG_STATE_MAGIC,
previousState: dialogStackState,
tag: dialog.getTag(),
id: stackId,
stackDepth: dialogStack.length + 1
};
dialogStack.push(
{
dialog: dialog,
historyState: dialogStackState
});
window.history.pushState(
topState,
"",
"#" + dialog.getTag());
if (!gWatchingHistory) {
gWatchingHistory = true;
window.addEventListener("popstate", handlePopState);
}
}
let gWatchingHistory = false;
let gPopFromBack = true;
let handlePopState = (ev: PopStateEvent) => {
let evTag: DialogStackHistoryState | null = ev.state as DialogStackHistoryState | null;
if (!evTag) return;
if (evTag.magic !== DLG_STATE_MAGIC) return;
ev.stopPropagation();
let id = evTag.id;
let ix = dialogStack.length-1;
while (ix >= 0) {
let entry = dialogStack[ix];
if (entry.historyState.id === id) {
break;
}
ix--;
}
if (ix < 0) {
// user must have navigated directly.
// so just reload the page.
dialogStack = [];
window.location.reload();
ev.stopPropagation();
return;
}
while (dialogStack.length > ix) {
let topEntry = dialogStack[dialogStack.length - 1];
if (topEntry.dialog) {
try {
if (topEntry.dialog.isOpen()) {
topEntry.dialog.onDialogStackClose();
}
} catch (_e) {
}
topEntry.dialog = null;
}
dialogStack.pop();
}
let previousState = evTag.previousState;
if (previousState)
{
let newState = {...previousState, topmost: true};
window.history.replaceState(newState,"","#"+previousState.tag);
} else {
window.history.replaceState(null,"",window.location.pathname);
}
if (dialogStack.length != 0 && gPopFromBack) {
if (dialogStack[dialogStack.length -1].dialog === null)
{
if (window.history.state != null)
{
window.setTimeout(() => { window.history.back(); }, 0);
} else {
dialogStack.pop();
}
}
}
ev.stopPropagation();
}
export function popDialogStack(dialog: IDialogStackable): void {
let ix = dialogStack.length-1;
while (ix >= 0) {
let entry = dialogStack[ix];
if (entry.dialog === dialog) {
break;
}
ix--;
}
if (ix < 0)
{
return;
}
dialogStack[ix].dialog = null;
if (dialogStack.length != 0 && dialogStack[dialogStack.length - 1].dialog === null)
{
window.history.back();
}
}
// Close all dialogs higher in the dialog stack than "tag".
export function removeDialogStackEntriesAbove(tag: string) {
while (dialogStack.length > 0) {
let entry = dialogStack[dialogStack.length - 1];
if (entry.dialog && entry.dialog.getTag() === tag) {
return;
}
dialogStack.pop();
if (entry.dialog && entry.dialog.isOpen()) {
entry.dialog.onDialogStackClose();
}
}
}