web-a2e 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- package/wiki/Video-Rendering.md +658 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* state-manager.js - Emulator state serialization and management
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* StateManager - Manages emulator state save/restore and UI
|
|
10
|
+
* Handles auto-save, manual save/restore, slot saves, and state popup UI
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
saveStateToStorage,
|
|
15
|
+
loadStateFromStorage,
|
|
16
|
+
hasSavedState,
|
|
17
|
+
getSavedStateTimestamp,
|
|
18
|
+
saveStateToSlot,
|
|
19
|
+
loadStateFromSlot,
|
|
20
|
+
} from "./state-persistence.js";
|
|
21
|
+
|
|
22
|
+
// Constants
|
|
23
|
+
const AUTO_SAVE_INTERVAL_MS = 5000;
|
|
24
|
+
const THUMBNAIL_WIDTH = 140;
|
|
25
|
+
const THUMBNAIL_HEIGHT = 96;
|
|
26
|
+
const PREVIEW_WIDTH = 560;
|
|
27
|
+
const PREVIEW_HEIGHT = 384;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} StateManagerDeps
|
|
31
|
+
* @property {Object} emulator - The emulator instance
|
|
32
|
+
* @property {Object} wasmModule - The WASM module
|
|
33
|
+
* @property {Object} uiController - UI controller for notifications
|
|
34
|
+
* @property {Object} diskManager - Disk manager for state sync
|
|
35
|
+
* @property {Object} reminderController - Reminder controller
|
|
36
|
+
* @property {Object} [cpuDebuggerWindow] - CPU debugger window (for resync after import)
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export class StateManager {
|
|
40
|
+
/**
|
|
41
|
+
* @param {StateManagerDeps} deps - Dependencies
|
|
42
|
+
*/
|
|
43
|
+
constructor(deps) {
|
|
44
|
+
this.emulator = deps.emulator;
|
|
45
|
+
this.wasmModule = deps.wasmModule;
|
|
46
|
+
this.uiController = deps.uiController;
|
|
47
|
+
this.diskManager = deps.diskManager;
|
|
48
|
+
this.reminderController = deps.reminderController;
|
|
49
|
+
this.cpuDebuggerWindow = deps.cpuDebuggerWindow || null;
|
|
50
|
+
this.basicProgramWindow = deps.basicProgramWindow || null;
|
|
51
|
+
|
|
52
|
+
this.autoSaveEnabled = false;
|
|
53
|
+
this.autoSaveInterval = null;
|
|
54
|
+
|
|
55
|
+
/** @type {function|null} Called after each autosave completes */
|
|
56
|
+
this.onAutosave = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize state management
|
|
61
|
+
*/
|
|
62
|
+
init() {
|
|
63
|
+
this.setupAutoSave();
|
|
64
|
+
this.setupStatePopup();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set up auto-save functionality
|
|
69
|
+
*/
|
|
70
|
+
setupAutoSave() {
|
|
71
|
+
// Load saved auto-save setting (default to enabled)
|
|
72
|
+
const savedAutosave = localStorage.getItem("a2e-autosave-state");
|
|
73
|
+
this.autoSaveEnabled = savedAutosave === "true";
|
|
74
|
+
|
|
75
|
+
// Save window states and emulator state when page is closed
|
|
76
|
+
window.addEventListener("beforeunload", () => {
|
|
77
|
+
if (this.autoSaveEnabled) {
|
|
78
|
+
this.saveState();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Save state when page becomes hidden (tab switch, minimize, mobile)
|
|
83
|
+
document.addEventListener("visibilitychange", () => {
|
|
84
|
+
if (document.hidden && this.emulator.isRunning() && this.autoSaveEnabled) {
|
|
85
|
+
this.saveState();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Periodic auto-save while running, deferred to idle time to avoid
|
|
90
|
+
// stalling the audio/render loop (which causes stutters in Safari/Brave)
|
|
91
|
+
this.autoSavePending = false;
|
|
92
|
+
this.autoSaveIdleHandle = null;
|
|
93
|
+
this.autoSaveInterval = setInterval(() => {
|
|
94
|
+
if (this.emulator.isRunning() && !document.hidden && this.autoSaveEnabled && !this.autoSavePending) {
|
|
95
|
+
this.autoSavePending = true;
|
|
96
|
+
const doSave = () => {
|
|
97
|
+
this.autoSavePending = false;
|
|
98
|
+
this.saveState();
|
|
99
|
+
};
|
|
100
|
+
if (typeof requestIdleCallback === "function") {
|
|
101
|
+
this.autoSaveIdleHandle = requestIdleCallback(doSave, { timeout: AUTO_SAVE_INTERVAL_MS });
|
|
102
|
+
} else {
|
|
103
|
+
// Safari <16.4 fallback — setTimeout(0) yields to the render loop
|
|
104
|
+
this.autoSaveIdleHandle = setTimeout(doSave, 0);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, AUTO_SAVE_INTERVAL_MS);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Set up state controls within the System menu
|
|
112
|
+
*/
|
|
113
|
+
setupStatePopup() {
|
|
114
|
+
const autosaveToggle = document.getElementById("autosave-toggle");
|
|
115
|
+
|
|
116
|
+
// Initialize toggle state
|
|
117
|
+
if (autosaveToggle) {
|
|
118
|
+
autosaveToggle.checked = this.autoSaveEnabled;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Update last saved time when system menu opens
|
|
122
|
+
const systemMenuContainer = document.getElementById("file-menu-container");
|
|
123
|
+
if (systemMenuContainer) {
|
|
124
|
+
const observer = new MutationObserver(() => {
|
|
125
|
+
if (systemMenuContainer.classList.contains("open")) {
|
|
126
|
+
this.updateLastSavedTime();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
observer.observe(systemMenuContainer, { attributes: true, attributeFilter: ["class"] });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Auto-save toggle
|
|
133
|
+
if (autosaveToggle) {
|
|
134
|
+
autosaveToggle.addEventListener("change", () => {
|
|
135
|
+
this.autoSaveEnabled = autosaveToggle.checked;
|
|
136
|
+
localStorage.setItem("a2e-autosave-state", this.autoSaveEnabled);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Update the "last saved" time display
|
|
143
|
+
*/
|
|
144
|
+
async updateLastSavedTime() {
|
|
145
|
+
const lastSavedEl = document.getElementById("state-last-saved");
|
|
146
|
+
if (!lastSavedEl) return;
|
|
147
|
+
|
|
148
|
+
const timestamp = await getSavedStateTimestamp();
|
|
149
|
+
if (timestamp) {
|
|
150
|
+
const date = new Date(timestamp);
|
|
151
|
+
const now = new Date();
|
|
152
|
+
const diffMs = now - date;
|
|
153
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
154
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
155
|
+
|
|
156
|
+
let timeStr;
|
|
157
|
+
if (diffMins < 1) {
|
|
158
|
+
timeStr = "just now";
|
|
159
|
+
} else if (diffMins < 60) {
|
|
160
|
+
timeStr = `${diffMins} minute${diffMins === 1 ? "" : "s"} ago`;
|
|
161
|
+
} else if (diffHours < 24) {
|
|
162
|
+
timeStr = `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`;
|
|
163
|
+
} else {
|
|
164
|
+
timeStr = date.toLocaleDateString();
|
|
165
|
+
}
|
|
166
|
+
lastSavedEl.textContent = `Last saved: ${timeStr}`;
|
|
167
|
+
} else {
|
|
168
|
+
lastSavedEl.textContent = "No saved state";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Capture current emulator state as a Uint8Array
|
|
174
|
+
* @returns {Uint8Array|null}
|
|
175
|
+
*/
|
|
176
|
+
captureStateData() {
|
|
177
|
+
if (!this.emulator.isRunning() || !this.wasmModule) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const sizePtr = this.wasmModule._malloc(4);
|
|
182
|
+
try {
|
|
183
|
+
const statePtr = this.wasmModule._exportState(sizePtr);
|
|
184
|
+
|
|
185
|
+
if (statePtr && sizePtr) {
|
|
186
|
+
const heapU32 = new Uint32Array(this.wasmModule.HEAPU8.buffer);
|
|
187
|
+
const size = heapU32[sizePtr / 4];
|
|
188
|
+
|
|
189
|
+
if (size > 0) {
|
|
190
|
+
const stateData = new Uint8Array(size);
|
|
191
|
+
stateData.set(
|
|
192
|
+
new Uint8Array(this.wasmModule.HEAPU8.buffer, statePtr, size)
|
|
193
|
+
);
|
|
194
|
+
return stateData;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
} finally {
|
|
199
|
+
this.wasmModule._free(sizePtr);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Import raw state data into the emulator (power cycles first)
|
|
205
|
+
* @param {Uint8Array} stateData
|
|
206
|
+
* @returns {boolean} True if state was imported successfully
|
|
207
|
+
*/
|
|
208
|
+
importStateData(stateData) {
|
|
209
|
+
if (!this.wasmModule || !stateData) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Power cycle: stop and restart the emulator for a clean slate
|
|
214
|
+
const wasRunning = this.emulator.isRunning();
|
|
215
|
+
if (wasRunning) {
|
|
216
|
+
this.emulator.stop();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Start fresh
|
|
220
|
+
this.emulator.start();
|
|
221
|
+
|
|
222
|
+
// Copy state data to WASM memory
|
|
223
|
+
const statePtr = this.wasmModule._malloc(stateData.length);
|
|
224
|
+
this.wasmModule.HEAPU8.set(stateData, statePtr);
|
|
225
|
+
|
|
226
|
+
// Import state
|
|
227
|
+
const success = this.wasmModule._importState(statePtr, stateData.length);
|
|
228
|
+
|
|
229
|
+
this.wasmModule._free(statePtr);
|
|
230
|
+
|
|
231
|
+
if (success) {
|
|
232
|
+
if (this.reminderController) {
|
|
233
|
+
this.reminderController.dismissPowerReminder();
|
|
234
|
+
this.reminderController.showBasicReminder(false);
|
|
235
|
+
}
|
|
236
|
+
if (this.diskManager) {
|
|
237
|
+
this.diskManager.syncWithEmulatorState();
|
|
238
|
+
}
|
|
239
|
+
// Re-push JS-side breakpoints/watchpoints/beam breakpoints to C++
|
|
240
|
+
// since importState() calls reset() which clears them on the WASM side
|
|
241
|
+
if (this.cpuDebuggerWindow) {
|
|
242
|
+
this.cpuDebuggerWindow.bpManager.resyncToWasm();
|
|
243
|
+
this.cpuDebuggerWindow.resyncBeamToWasm();
|
|
244
|
+
}
|
|
245
|
+
// Re-sync BASIC breakpoints
|
|
246
|
+
if (this.basicProgramWindow) {
|
|
247
|
+
this.basicProgramWindow.getBreakpointManager().resyncToWasm();
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
} else {
|
|
251
|
+
this.emulator.stop();
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Capture a thumbnail screenshot of the current emulator display
|
|
258
|
+
* @returns {string|null} Data URL of the thumbnail, or null
|
|
259
|
+
*/
|
|
260
|
+
captureScreenshot() {
|
|
261
|
+
const canvas = document.getElementById("screen");
|
|
262
|
+
if (!canvas) return null;
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const offscreen = document.createElement("canvas");
|
|
266
|
+
offscreen.width = THUMBNAIL_WIDTH;
|
|
267
|
+
offscreen.height = THUMBNAIL_HEIGHT;
|
|
268
|
+
const ctx = offscreen.getContext("2d");
|
|
269
|
+
ctx.drawImage(canvas, 0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
|
|
270
|
+
return offscreen.toDataURL("image/jpeg", 0.85);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error("Failed to capture screenshot:", error);
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Capture a high-resolution preview of the current emulator display
|
|
279
|
+
* @returns {string|null} Data URL of the preview, or null
|
|
280
|
+
*/
|
|
281
|
+
capturePreview() {
|
|
282
|
+
const canvas = document.getElementById("screen");
|
|
283
|
+
if (!canvas) return null;
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const offscreen = document.createElement("canvas");
|
|
287
|
+
offscreen.width = PREVIEW_WIDTH;
|
|
288
|
+
offscreen.height = PREVIEW_HEIGHT;
|
|
289
|
+
const ctx = offscreen.getContext("2d");
|
|
290
|
+
ctx.drawImage(canvas, 0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
|
291
|
+
return offscreen.toDataURL("image/jpeg", 0.85);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error("Failed to capture preview:", error);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Save the current emulator state to IndexedDB (auto-save)
|
|
300
|
+
* @returns {Promise<void>}
|
|
301
|
+
*/
|
|
302
|
+
async saveState() {
|
|
303
|
+
if (!this.emulator.isRunning() || !this.wasmModule) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
this.uiController.flashStateButton();
|
|
309
|
+
const stateData = this.captureStateData();
|
|
310
|
+
if (stateData) {
|
|
311
|
+
const thumbnail = this.captureScreenshot();
|
|
312
|
+
const preview = this.capturePreview();
|
|
313
|
+
await saveStateToStorage(stateData, thumbnail, preview);
|
|
314
|
+
if (this.onAutosave) this.onAutosave();
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error("Failed to save emulator state:", error);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Restore emulator state from IndexedDB (auto-save)
|
|
323
|
+
* @returns {Promise<boolean>} True if state was restored
|
|
324
|
+
*/
|
|
325
|
+
async restoreState() {
|
|
326
|
+
if (!this.wasmModule) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const stateData = await loadStateFromStorage();
|
|
332
|
+
if (!stateData) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const success = this.importStateData(stateData);
|
|
337
|
+
if (success) {
|
|
338
|
+
console.log("Restored emulator state from storage");
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error("Failed to restore emulator state:", error);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Save current state to a numbered slot with screenshot
|
|
350
|
+
* @param {number} slotNumber - Slot number (1-5)
|
|
351
|
+
* @returns {Promise<boolean>}
|
|
352
|
+
*/
|
|
353
|
+
async saveToSlot(slotNumber) {
|
|
354
|
+
const stateData = this.captureStateData();
|
|
355
|
+
if (!stateData) return false;
|
|
356
|
+
|
|
357
|
+
const thumbnail = this.captureScreenshot();
|
|
358
|
+
const preview = this.capturePreview();
|
|
359
|
+
await saveStateToSlot(slotNumber, stateData, thumbnail, preview);
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Restore state from a numbered slot
|
|
365
|
+
* @param {number} slotNumber - Slot number (1-5)
|
|
366
|
+
* @returns {Promise<boolean>}
|
|
367
|
+
*/
|
|
368
|
+
async restoreFromSlot(slotNumber) {
|
|
369
|
+
const slot = await loadStateFromSlot(slotNumber);
|
|
370
|
+
if (!slot) return false;
|
|
371
|
+
|
|
372
|
+
return this.importStateData(slot.data);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Restore state from raw file data (e.g. uploaded .a2state file)
|
|
377
|
+
* @param {Uint8Array} stateData
|
|
378
|
+
* @returns {boolean}
|
|
379
|
+
*/
|
|
380
|
+
restoreFromFileData(stateData) {
|
|
381
|
+
return this.importStateData(stateData);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Check if there's a saved state available
|
|
386
|
+
* @returns {Promise<boolean>}
|
|
387
|
+
*/
|
|
388
|
+
async hasSavedState() {
|
|
389
|
+
return hasSavedState();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Check if auto-save is enabled
|
|
394
|
+
* @returns {boolean}
|
|
395
|
+
*/
|
|
396
|
+
isAutoSaveEnabled() {
|
|
397
|
+
return this.autoSaveEnabled;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Clean up resources
|
|
402
|
+
*/
|
|
403
|
+
destroy() {
|
|
404
|
+
if (this.autoSaveInterval) {
|
|
405
|
+
clearInterval(this.autoSaveInterval);
|
|
406
|
+
this.autoSaveInterval = null;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* state-persistence.js - State persistence to IndexedDB
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createDatabaseManager } from "../utils/indexeddb-helper.js";
|
|
9
|
+
|
|
10
|
+
const DB_NAME = "a2e-state-persistence";
|
|
11
|
+
const DB_VERSION = 1;
|
|
12
|
+
const STORE_NAME = "emulatorState";
|
|
13
|
+
|
|
14
|
+
const db = createDatabaseManager({
|
|
15
|
+
dbName: DB_NAME,
|
|
16
|
+
version: DB_VERSION,
|
|
17
|
+
onUpgrade: (event) => {
|
|
18
|
+
const database = event.target.result;
|
|
19
|
+
if (!database.objectStoreNames.contains(STORE_NAME)) {
|
|
20
|
+
database.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Save emulator state to IndexedDB
|
|
27
|
+
* @param {Uint8Array} stateData - The serialized emulator state
|
|
28
|
+
* @param {string|null} [thumbnail] - Optional data URL of screenshot thumbnail
|
|
29
|
+
* @param {string|null} [preview] - Optional data URL of high-res preview
|
|
30
|
+
* @returns {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
export async function saveStateToStorage(stateData, thumbnail, preview) {
|
|
33
|
+
try {
|
|
34
|
+
const stateRecord = {
|
|
35
|
+
id: "autosave",
|
|
36
|
+
data: stateData,
|
|
37
|
+
savedAt: Date.now(),
|
|
38
|
+
thumbnail: thumbnail || null,
|
|
39
|
+
preview: preview || null,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await db.put(STORE_NAME, stateRecord);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("Error saving emulator state:", error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load emulator state from IndexedDB
|
|
50
|
+
* @returns {Promise<Uint8Array | null>}
|
|
51
|
+
*/
|
|
52
|
+
export async function loadStateFromStorage() {
|
|
53
|
+
try {
|
|
54
|
+
const result = await db.get(STORE_NAME, "autosave");
|
|
55
|
+
if (result) {
|
|
56
|
+
console.log("Loaded emulator state from storage");
|
|
57
|
+
return new Uint8Array(result.data);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error("Error loading emulator state:", error);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Clear saved emulator state from IndexedDB
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
export async function clearStateFromStorage() {
|
|
71
|
+
try {
|
|
72
|
+
await db.remove(STORE_NAME, "autosave");
|
|
73
|
+
console.log("Cleared emulator state from storage");
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error("Error clearing emulator state:", error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if there is a saved emulator state
|
|
81
|
+
* @returns {Promise<boolean>}
|
|
82
|
+
*/
|
|
83
|
+
export async function hasSavedState() {
|
|
84
|
+
try {
|
|
85
|
+
const result = await db.get(STORE_NAME, "autosave");
|
|
86
|
+
return result != null;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("Error checking for saved state:", error);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get the timestamp of the last saved state
|
|
95
|
+
* @returns {Promise<number | null>} Timestamp in milliseconds, or null if no saved state
|
|
96
|
+
*/
|
|
97
|
+
export async function getSavedStateTimestamp() {
|
|
98
|
+
try {
|
|
99
|
+
const result = await db.get(STORE_NAME, "autosave");
|
|
100
|
+
return result ? result.savedAt : null;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error("Error getting saved state timestamp:", error);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get autosave summary info (without loading full state data)
|
|
109
|
+
* @returns {Promise<{savedAt: number, thumbnail: string|null}|null>}
|
|
110
|
+
*/
|
|
111
|
+
export async function getAutosaveInfo() {
|
|
112
|
+
try {
|
|
113
|
+
const result = await db.get(STORE_NAME, "autosave");
|
|
114
|
+
if (result) {
|
|
115
|
+
return {
|
|
116
|
+
savedAt: result.savedAt,
|
|
117
|
+
thumbnail: result.thumbnail || null,
|
|
118
|
+
preview: result.preview || null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("Error getting autosave info:", error);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- Save State Slots ---
|
|
129
|
+
|
|
130
|
+
const SLOT_COUNT = 5;
|
|
131
|
+
|
|
132
|
+
function slotKey(slotNumber) {
|
|
133
|
+
return `slot-${slotNumber}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Save emulator state to a numbered slot
|
|
138
|
+
* @param {number} slotNumber - Slot number (1-5)
|
|
139
|
+
* @param {Uint8Array} stateData - The serialized emulator state
|
|
140
|
+
* @param {string|null} thumbnail - Data URL of screenshot thumbnail
|
|
141
|
+
* @param {string|null} [preview] - Optional data URL of high-res preview
|
|
142
|
+
* @returns {Promise<void>}
|
|
143
|
+
*/
|
|
144
|
+
export async function saveStateToSlot(slotNumber, stateData, thumbnail, preview) {
|
|
145
|
+
try {
|
|
146
|
+
const record = {
|
|
147
|
+
id: slotKey(slotNumber),
|
|
148
|
+
data: new Uint8Array(stateData),
|
|
149
|
+
savedAt: Date.now(),
|
|
150
|
+
thumbnail: thumbnail || null,
|
|
151
|
+
preview: preview || null,
|
|
152
|
+
};
|
|
153
|
+
await db.put(STORE_NAME, record);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(`Error saving state to slot ${slotNumber}:`, error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Load emulator state from a numbered slot
|
|
161
|
+
* @param {number} slotNumber - Slot number (1-5)
|
|
162
|
+
* @returns {Promise<{data: Uint8Array, savedAt: number, thumbnail: string|null}|null>}
|
|
163
|
+
*/
|
|
164
|
+
export async function loadStateFromSlot(slotNumber) {
|
|
165
|
+
try {
|
|
166
|
+
const result = await db.get(STORE_NAME, slotKey(slotNumber));
|
|
167
|
+
if (result) {
|
|
168
|
+
return {
|
|
169
|
+
data: new Uint8Array(result.data),
|
|
170
|
+
savedAt: result.savedAt,
|
|
171
|
+
thumbnail: result.thumbnail || null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(`Error loading state from slot ${slotNumber}:`, error);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Clear a numbered slot
|
|
183
|
+
* @param {number} slotNumber - Slot number (1-5)
|
|
184
|
+
* @returns {Promise<void>}
|
|
185
|
+
*/
|
|
186
|
+
export async function clearSlot(slotNumber) {
|
|
187
|
+
try {
|
|
188
|
+
await db.remove(STORE_NAME, slotKey(slotNumber));
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error(`Error clearing slot ${slotNumber}:`, error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get summary info for all 5 slots (without loading full state data)
|
|
196
|
+
* @returns {Promise<Array<{slotNumber: number, savedAt: number, thumbnail: string|null}|null>>}
|
|
197
|
+
*/
|
|
198
|
+
export async function getAllSlotInfo() {
|
|
199
|
+
const slots = [];
|
|
200
|
+
for (let i = 1; i <= SLOT_COUNT; i++) {
|
|
201
|
+
try {
|
|
202
|
+
const result = await db.get(STORE_NAME, slotKey(i));
|
|
203
|
+
if (result) {
|
|
204
|
+
slots.push({
|
|
205
|
+
slotNumber: i,
|
|
206
|
+
savedAt: result.savedAt,
|
|
207
|
+
thumbnail: result.thumbnail || null,
|
|
208
|
+
preview: result.preview || null,
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
slots.push(null);
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
slots.push(null);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return slots;
|
|
218
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* confirm.js - Custom confirm dialog using app modal styles
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Show a confirm dialog using the app's modal styling.
|
|
10
|
+
* @param {string} message - The confirmation message
|
|
11
|
+
* @param {string} [confirmLabel='OK'] - Label for the confirm button
|
|
12
|
+
* @returns {Promise<boolean>} Resolves true if confirmed, false if cancelled
|
|
13
|
+
*/
|
|
14
|
+
export function showConfirm(message, confirmLabel = "OK") {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const dialog = document.createElement("dialog");
|
|
17
|
+
dialog.className = "modal";
|
|
18
|
+
dialog.innerHTML = `
|
|
19
|
+
<div class="modal-content">
|
|
20
|
+
<div class="modal-body">
|
|
21
|
+
<p>${message}</p>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="modal-footer">
|
|
24
|
+
<button class="modal-btn modal-btn-secondary confirm-cancel">Cancel</button>
|
|
25
|
+
<button class="modal-btn modal-btn-primary confirm-ok">${confirmLabel}</button>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
document.body.appendChild(dialog);
|
|
31
|
+
dialog.showModal();
|
|
32
|
+
|
|
33
|
+
const cleanup = (result) => {
|
|
34
|
+
dialog.close();
|
|
35
|
+
dialog.remove();
|
|
36
|
+
resolve(result);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
dialog.querySelector(".confirm-ok").addEventListener("click", () => cleanup(true));
|
|
40
|
+
dialog.querySelector(".confirm-cancel").addEventListener("click", () => cleanup(false));
|
|
41
|
+
dialog.addEventListener("cancel", () => cleanup(false));
|
|
42
|
+
});
|
|
43
|
+
}
|