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,212 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* disk-drives-window.js - Disk drives window UI
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BaseWindow } from "../windows/base-window.js";
|
|
9
|
+
|
|
10
|
+
export class DiskDrivesWindow extends BaseWindow {
|
|
11
|
+
constructor() {
|
|
12
|
+
super({
|
|
13
|
+
id: "disk-drives",
|
|
14
|
+
title: "Disk Drives",
|
|
15
|
+
minWidth: 600,
|
|
16
|
+
minHeight: 100,
|
|
17
|
+
maxWidth: 600,
|
|
18
|
+
defaultWidth: 600,
|
|
19
|
+
defaultHeight: 310,
|
|
20
|
+
resizeDirections: [],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this._detailsOpen = false;
|
|
24
|
+
this._graphicsHidden = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_driveHTML(num) {
|
|
28
|
+
const prefix = `dd-d${num}`;
|
|
29
|
+
return `
|
|
30
|
+
<div class="disk-drive" id="disk${num}">
|
|
31
|
+
<div class="drive-image-container">
|
|
32
|
+
<canvas class="disk-surface" width="560" height="480"></canvas>
|
|
33
|
+
<span class="drive-label">D${num}</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="drive-info">
|
|
36
|
+
<span class="disk-name">No Disk</span>
|
|
37
|
+
<span class="disk-track" title="Current Track">T--</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="drive-controls">
|
|
40
|
+
<input type="file" id="disk${num}-input" accept=".dsk,.do,.po,.woz,.nib" hidden />
|
|
41
|
+
<button class="disk-insert" title="Insert Disk from File">Insert</button>
|
|
42
|
+
<div class="recent-container">
|
|
43
|
+
<button class="disk-recent" title="Recent Disks">Recent</button>
|
|
44
|
+
<div class="recent-dropdown"></div>
|
|
45
|
+
</div>
|
|
46
|
+
<button class="disk-blank" title="Insert Blank Disk">Blank</button>
|
|
47
|
+
<button class="disk-eject" disabled title="Eject Disk">Eject</button>
|
|
48
|
+
<button class="disk-browse" disabled title="Browse Files">Browse</button>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="drive-detail-panel">
|
|
51
|
+
<div class="drive-detail-grid">
|
|
52
|
+
<span class="dd-label">QTrack</span><span class="dd-val" id="${prefix}-qt">0</span>
|
|
53
|
+
<span class="dd-label">Phase</span><span class="dd-val" id="${prefix}-phase">0</span>
|
|
54
|
+
<span class="dd-label">Nibble</span><span class="dd-val" id="${prefix}-nibble">0</span>
|
|
55
|
+
<span class="dd-label">Motor</span><span class="dd-val" id="${prefix}-motor">OFF</span>
|
|
56
|
+
<span class="dd-label">Mode</span><span class="dd-val" id="${prefix}-mode">Read</span>
|
|
57
|
+
<span class="dd-label">Byte</span><span class="dd-val" id="${prefix}-byte">00</span>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
renderContent() {
|
|
64
|
+
return `
|
|
65
|
+
<div class="disk-drives-row">
|
|
66
|
+
${this._driveHTML(1)}
|
|
67
|
+
${this._driveHTML(2)}
|
|
68
|
+
</div>
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onContentRendered() {
|
|
73
|
+
const closeBtn = this.headerElement.querySelector(
|
|
74
|
+
`.${this.cssClasses.close}`,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
this._graphicsBtn = document.createElement("button");
|
|
78
|
+
this._graphicsBtn.className = "drive-graphics-btn active";
|
|
79
|
+
this._graphicsBtn.title = "Toggle disk graphics";
|
|
80
|
+
this._graphicsBtn.innerHTML = `
|
|
81
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
|
|
82
|
+
<path d="M8 3C4.5 3 1.6 5.3.6 8c1 2.7 3.9 5 7.4 5s6.4-2.3 7.4-5c-1-2.7-3.9-5-7.4-5zm0 8.5A3.5 3.5 0 1 1 8 4.5a3.5 3.5 0 0 1 0 7zm0-5.5a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/>
|
|
83
|
+
</svg>
|
|
84
|
+
`;
|
|
85
|
+
this.headerElement.insertBefore(this._graphicsBtn, closeBtn);
|
|
86
|
+
this._graphicsBtn.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
87
|
+
this._graphicsBtn.addEventListener("click", () => this._toggleGraphics());
|
|
88
|
+
|
|
89
|
+
this._detailBtn = document.createElement("button");
|
|
90
|
+
this._detailBtn.className = "drive-detail-btn";
|
|
91
|
+
this._detailBtn.title = "Toggle details";
|
|
92
|
+
this._detailBtn.innerHTML = `
|
|
93
|
+
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
|
|
94
|
+
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 2.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM6.5 7h1.75v4.5H10v1H6v-1h1.25V8H6.5V7z"/>
|
|
95
|
+
</svg>
|
|
96
|
+
`;
|
|
97
|
+
this.headerElement.insertBefore(this._detailBtn, closeBtn);
|
|
98
|
+
this._detailBtn.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
99
|
+
this._detailBtn.addEventListener("click", () => this._toggleDetails());
|
|
100
|
+
|
|
101
|
+
// Set initial size from content so defaultHeight doesn't need to be kept in sync
|
|
102
|
+
this._fitToContent();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
show() {
|
|
106
|
+
super.show();
|
|
107
|
+
this._fitToContent();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_toggleGraphics() {
|
|
111
|
+
this._graphicsHidden = !this._graphicsHidden;
|
|
112
|
+
this.contentElement.classList.toggle("hide-graphics", this._graphicsHidden);
|
|
113
|
+
if (this._graphicsBtn) {
|
|
114
|
+
this._graphicsBtn.classList.toggle("active", !this._graphicsHidden);
|
|
115
|
+
}
|
|
116
|
+
this._fitToContent();
|
|
117
|
+
if (this.onStateChange) this.onStateChange();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_toggleDetails() {
|
|
121
|
+
this._detailsOpen = !this._detailsOpen;
|
|
122
|
+
this.contentElement.classList.toggle("show-details", this._detailsOpen);
|
|
123
|
+
if (this._detailBtn) {
|
|
124
|
+
this._detailBtn.classList.toggle("active", this._detailsOpen);
|
|
125
|
+
}
|
|
126
|
+
this._fitToContent();
|
|
127
|
+
if (this.onStateChange) this.onStateChange();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_fitToContent() {
|
|
131
|
+
if (!this.element) return;
|
|
132
|
+
// Temporarily set auto height to measure natural size
|
|
133
|
+
this.element.style.height = "auto";
|
|
134
|
+
const newHeight = this.element.offsetHeight;
|
|
135
|
+
this.element.style.height = `${newHeight}px`;
|
|
136
|
+
this.currentHeight = newHeight;
|
|
137
|
+
this.minHeight = newHeight;
|
|
138
|
+
this.maxHeight = newHeight;
|
|
139
|
+
// Recalculate edge distances before constraining so the window
|
|
140
|
+
// isn't repositioned based on stale distances from a different height
|
|
141
|
+
this.updateEdgeDistances();
|
|
142
|
+
this.constrainToViewport();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Update technical details from WASM state (called by WindowManager.updateAll)
|
|
147
|
+
*/
|
|
148
|
+
update(wasmModule) {
|
|
149
|
+
if (!this._detailsOpen) return;
|
|
150
|
+
|
|
151
|
+
const selectedDrive = wasmModule._getSelectedDrive();
|
|
152
|
+
const lastByte = wasmModule._getLastDiskByte();
|
|
153
|
+
|
|
154
|
+
for (let d = 0; d < 2; d++) {
|
|
155
|
+
const prefix = `dd-d${d + 1}`;
|
|
156
|
+
const el = (id) => this.contentElement.querySelector(`#${prefix}-${id}`);
|
|
157
|
+
|
|
158
|
+
const qt = el("qt");
|
|
159
|
+
if (qt) qt.textContent = wasmModule._getDiskHeadPosition(d);
|
|
160
|
+
|
|
161
|
+
const phase = el("phase");
|
|
162
|
+
if (phase) phase.textContent = wasmModule._getDiskPhase(d);
|
|
163
|
+
|
|
164
|
+
const nibble = el("nibble");
|
|
165
|
+
if (nibble) nibble.textContent = wasmModule._getCurrentNibblePosition(d);
|
|
166
|
+
|
|
167
|
+
const motor = el("motor");
|
|
168
|
+
if (motor) {
|
|
169
|
+
const on = wasmModule._getDiskMotorOn(d);
|
|
170
|
+
motor.textContent = on ? "ON" : "OFF";
|
|
171
|
+
motor.classList.toggle("on", on);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const mode = el("mode");
|
|
175
|
+
if (mode) {
|
|
176
|
+
const w = wasmModule._getDiskWriteMode(d);
|
|
177
|
+
mode.textContent = w ? "Write" : "Read";
|
|
178
|
+
mode.classList.toggle("write", w);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const byte = el("byte");
|
|
182
|
+
if (byte) {
|
|
183
|
+
byte.textContent =
|
|
184
|
+
d === selectedDrive
|
|
185
|
+
? lastByte.toString(16).toUpperCase().padStart(2, "0")
|
|
186
|
+
: "--";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getState() {
|
|
192
|
+
const base = super.getState();
|
|
193
|
+
base.graphicsHidden = this._graphicsHidden;
|
|
194
|
+
base.detailsOpen = this._detailsOpen;
|
|
195
|
+
return base;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
restoreState(state) {
|
|
199
|
+
if (state.graphicsHidden) {
|
|
200
|
+
this._graphicsHidden = true;
|
|
201
|
+
this.contentElement.classList.add("hide-graphics");
|
|
202
|
+
if (this._graphicsBtn) this._graphicsBtn.classList.remove("active");
|
|
203
|
+
}
|
|
204
|
+
if (state.detailsOpen) {
|
|
205
|
+
this._detailsOpen = true;
|
|
206
|
+
this.contentElement.classList.add("show-details");
|
|
207
|
+
if (this._detailBtn) this._detailBtn.classList.add("active");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
super.restoreState(state);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* disk-operations.js - Disk image loading and management operations
|
|
3
|
+
*
|
|
4
|
+
* Written by
|
|
5
|
+
* Mike Daley <michael_daley@icloud.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
saveDiskToStorage,
|
|
10
|
+
clearDiskFromStorage,
|
|
11
|
+
addToRecentDisks,
|
|
12
|
+
} from "./disk-persistence.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Helper to insert a disk into WASM with proper memory management
|
|
16
|
+
* @param {Object} wasmModule - The WASM module
|
|
17
|
+
* @param {number} driveNum - Drive number (0 or 1)
|
|
18
|
+
* @param {Uint8Array} data - The disk image data
|
|
19
|
+
* @param {string} filename - The disk filename
|
|
20
|
+
* @returns {boolean} True if successful
|
|
21
|
+
*/
|
|
22
|
+
function insertDiskToWasm(wasmModule, driveNum, data, filename) {
|
|
23
|
+
// Allocate memory for disk data
|
|
24
|
+
const dataPtr = wasmModule._malloc(data.length);
|
|
25
|
+
wasmModule.HEAPU8.set(data, dataPtr);
|
|
26
|
+
|
|
27
|
+
// Allocate string for filename
|
|
28
|
+
const filenamePtr = wasmModule._malloc(filename.length + 1);
|
|
29
|
+
wasmModule.stringToUTF8(filename, filenamePtr, filename.length + 1);
|
|
30
|
+
|
|
31
|
+
// Insert disk
|
|
32
|
+
const success = wasmModule._insertDisk(driveNum, dataPtr, data.length, filenamePtr);
|
|
33
|
+
|
|
34
|
+
// Free memory
|
|
35
|
+
wasmModule._free(dataPtr);
|
|
36
|
+
wasmModule._free(filenamePtr);
|
|
37
|
+
|
|
38
|
+
return success;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Load a disk image from a file into a drive
|
|
43
|
+
* @param {Object} options
|
|
44
|
+
* @param {Object} options.wasmModule - The WASM module
|
|
45
|
+
* @param {Object} options.drive - The drive state object
|
|
46
|
+
* @param {number} options.driveNum - Drive number (0 or 1)
|
|
47
|
+
* @param {File} options.file - The file to load
|
|
48
|
+
* @param {Function} [options.onSuccess] - Callback on successful load
|
|
49
|
+
* @param {Function} [options.onError] - Callback on error
|
|
50
|
+
*/
|
|
51
|
+
export async function loadDisk({ wasmModule, drive, driveNum, file, onSuccess, onError }) {
|
|
52
|
+
try {
|
|
53
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
54
|
+
const data = new Uint8Array(arrayBuffer);
|
|
55
|
+
|
|
56
|
+
const success = insertDiskToWasm(wasmModule, driveNum, data, file.name);
|
|
57
|
+
|
|
58
|
+
if (success) {
|
|
59
|
+
drive.filename = file.name;
|
|
60
|
+
if (drive.ejectBtn) drive.ejectBtn.disabled = false;
|
|
61
|
+
if (drive.browseBtn) drive.browseBtn.disabled = false;
|
|
62
|
+
console.log(`Inserted disk in drive ${driveNum + 1}: ${file.name}`);
|
|
63
|
+
|
|
64
|
+
// Save to IndexedDB for persistence across sessions
|
|
65
|
+
await saveDiskToStorage(driveNum, file.name, data);
|
|
66
|
+
|
|
67
|
+
// Add to recent disks list for this drive
|
|
68
|
+
await addToRecentDisks(driveNum, file.name, data);
|
|
69
|
+
|
|
70
|
+
if (onSuccess) onSuccess(file.name);
|
|
71
|
+
} else {
|
|
72
|
+
const msg = `Failed to load disk image: ${file.name}`;
|
|
73
|
+
console.error(msg);
|
|
74
|
+
if (onError) onError(msg);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error("Error loading disk:", error);
|
|
78
|
+
if (onError) onError("Error loading disk: " + error.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Load a disk image from raw data (used for restoring from persistence)
|
|
84
|
+
* @param {Object} options
|
|
85
|
+
* @param {Object} options.wasmModule - The WASM module
|
|
86
|
+
* @param {Object} options.drive - The drive state object
|
|
87
|
+
* @param {number} options.driveNum - Drive number (0 or 1)
|
|
88
|
+
* @param {string} options.filename - The disk filename
|
|
89
|
+
* @param {Uint8Array} options.data - The disk image data
|
|
90
|
+
* @param {Function} [options.onSuccess] - Callback on successful load
|
|
91
|
+
* @param {Function} [options.onError] - Callback on error
|
|
92
|
+
*/
|
|
93
|
+
export function loadDiskFromData({ wasmModule, drive, driveNum, filename, data, onSuccess, onError }) {
|
|
94
|
+
try {
|
|
95
|
+
const success = insertDiskToWasm(wasmModule, driveNum, data, filename);
|
|
96
|
+
|
|
97
|
+
if (success) {
|
|
98
|
+
drive.filename = filename;
|
|
99
|
+
if (drive.ejectBtn) drive.ejectBtn.disabled = false;
|
|
100
|
+
if (drive.browseBtn) drive.browseBtn.disabled = false;
|
|
101
|
+
console.log(`Restored disk in drive ${driveNum + 1}: ${filename}`);
|
|
102
|
+
if (onSuccess) onSuccess(filename);
|
|
103
|
+
} else {
|
|
104
|
+
const msg = `Failed to restore disk image: ${filename}`;
|
|
105
|
+
console.error(msg);
|
|
106
|
+
if (onError) onError(msg);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("Error restoring disk:", error);
|
|
110
|
+
if (onError) onError("Error restoring disk: " + error.message);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Insert a blank WOZ disk into a drive
|
|
116
|
+
* @param {Object} options
|
|
117
|
+
* @param {Object} options.wasmModule - The WASM module
|
|
118
|
+
* @param {Object} options.drive - The drive state object
|
|
119
|
+
* @param {number} options.driveNum - Drive number (0 or 1)
|
|
120
|
+
* @param {Function} [options.onSuccess] - Callback on successful insert
|
|
121
|
+
* @param {Function} [options.onError] - Callback on error
|
|
122
|
+
*/
|
|
123
|
+
export function insertBlankDisk({ wasmModule, drive, driveNum, onSuccess, onError }) {
|
|
124
|
+
const filename = "Blank Disk.woz";
|
|
125
|
+
|
|
126
|
+
// Use the WASM function to create and insert a blank disk
|
|
127
|
+
const success = wasmModule._insertBlankDisk(driveNum);
|
|
128
|
+
|
|
129
|
+
if (success) {
|
|
130
|
+
drive.filename = filename;
|
|
131
|
+
if (drive.ejectBtn) drive.ejectBtn.disabled = false;
|
|
132
|
+
console.log(`Inserted blank disk in drive ${driveNum + 1}`);
|
|
133
|
+
if (onSuccess) onSuccess(filename);
|
|
134
|
+
} else {
|
|
135
|
+
const msg = "Failed to insert blank disk";
|
|
136
|
+
console.error(msg);
|
|
137
|
+
if (onError) onError(msg);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Perform the actual disk ejection
|
|
143
|
+
* @param {Object} options
|
|
144
|
+
* @param {Object} options.wasmModule - The WASM module
|
|
145
|
+
* @param {Object} options.drive - The drive state object
|
|
146
|
+
* @param {number} options.driveNum - Drive number (0 or 1)
|
|
147
|
+
* @param {Function} [options.onEject] - Callback after ejection
|
|
148
|
+
*/
|
|
149
|
+
export function performEject({ wasmModule, drive, driveNum, onEject }) {
|
|
150
|
+
wasmModule._ejectDisk(driveNum);
|
|
151
|
+
|
|
152
|
+
drive.filename = null;
|
|
153
|
+
if (drive.ejectBtn) drive.ejectBtn.disabled = true;
|
|
154
|
+
if (drive.browseBtn) drive.browseBtn.disabled = true;
|
|
155
|
+
if (drive.input) drive.input.value = "";
|
|
156
|
+
|
|
157
|
+
// Clear from IndexedDB
|
|
158
|
+
clearDiskFromStorage(driveNum);
|
|
159
|
+
|
|
160
|
+
console.log(`Ejected disk from drive ${driveNum + 1}`);
|
|
161
|
+
if (onEject) onEject();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Eject a disk, prompting to save if modified
|
|
166
|
+
* @param {Object} options
|
|
167
|
+
* @param {Object} options.wasmModule - The WASM module
|
|
168
|
+
* @param {Object} options.drive - The drive state object
|
|
169
|
+
* @param {number} options.driveNum - Drive number (0 or 1)
|
|
170
|
+
* @param {Function} [options.onEject] - Callback after ejection
|
|
171
|
+
*/
|
|
172
|
+
export async function ejectDisk({ wasmModule, drive, driveNum, onEject }) {
|
|
173
|
+
// Check if disk is modified
|
|
174
|
+
const hasModifiedCheck = typeof wasmModule._isDiskModified === "function";
|
|
175
|
+
const isModified = hasModifiedCheck && wasmModule._isDiskModified(driveNum);
|
|
176
|
+
|
|
177
|
+
if (isModified) {
|
|
178
|
+
// Generate suggested filename
|
|
179
|
+
let suggestedName = drive.filename || `disk${driveNum + 1}.woz`;
|
|
180
|
+
// Ensure WOZ extension for blank disks
|
|
181
|
+
if (suggestedName === "Blank Disk.woz" || !suggestedName.includes(".")) {
|
|
182
|
+
suggestedName = suggestedName.replace(/\.[^.]*$/, "") + ".woz";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Go directly to OS save picker
|
|
186
|
+
await saveDiskWithPicker(wasmModule, driveNum, suggestedName);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Always eject after save attempt
|
|
190
|
+
performEject({ wasmModule, drive, driveNum, onEject });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Save disk data using the File System Access API
|
|
195
|
+
* @param {Object} wasmModule - The WASM module
|
|
196
|
+
* @param {number} driveNum - Drive number (0 or 1)
|
|
197
|
+
* @param {string} suggestedName - Suggested filename
|
|
198
|
+
* @returns {Promise<boolean>} True if saved successfully
|
|
199
|
+
*/
|
|
200
|
+
export async function saveDiskWithPicker(wasmModule, driveNum, suggestedName) {
|
|
201
|
+
const sizePtr = wasmModule._malloc(4);
|
|
202
|
+
if (!sizePtr) {
|
|
203
|
+
console.error("saveDiskWithPicker: failed to allocate size pointer");
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const dataPtr = wasmModule._getDiskData(driveNum, sizePtr);
|
|
208
|
+
|
|
209
|
+
if (!dataPtr) {
|
|
210
|
+
console.error("saveDiskWithPicker: _getDiskData returned null");
|
|
211
|
+
wasmModule._free(sizePtr);
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Read size from WASM memory (little-endian 32-bit value)
|
|
216
|
+
const heap = wasmModule.HEAPU8;
|
|
217
|
+
const size =
|
|
218
|
+
heap[sizePtr] |
|
|
219
|
+
(heap[sizePtr + 1] << 8) |
|
|
220
|
+
(heap[sizePtr + 2] << 16) |
|
|
221
|
+
(heap[sizePtr + 3] << 24);
|
|
222
|
+
|
|
223
|
+
if (size <= 0 || size > 10000000) {
|
|
224
|
+
console.error(`saveDiskWithPicker: invalid size ${size}`);
|
|
225
|
+
wasmModule._free(sizePtr);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const data = new Uint8Array(wasmModule.HEAPU8.buffer, dataPtr, size);
|
|
230
|
+
|
|
231
|
+
// Create a copy of the data since the WASM buffer may become invalid
|
|
232
|
+
const dataCopy = new Uint8Array(data);
|
|
233
|
+
|
|
234
|
+
wasmModule._free(sizePtr);
|
|
235
|
+
|
|
236
|
+
// Try to use File System Access API (modern browsers)
|
|
237
|
+
if ("showSaveFilePicker" in window) {
|
|
238
|
+
try {
|
|
239
|
+
const handle = await window.showSaveFilePicker({
|
|
240
|
+
suggestedName: suggestedName,
|
|
241
|
+
types: [
|
|
242
|
+
{
|
|
243
|
+
description: "Disk Images",
|
|
244
|
+
accept: {
|
|
245
|
+
"application/octet-stream": [".dsk", ".do", ".po", ".woz", ".nib"],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const writable = await handle.createWritable();
|
|
252
|
+
await writable.write(dataCopy);
|
|
253
|
+
await writable.close();
|
|
254
|
+
|
|
255
|
+
console.log(`Saved disk from drive ${driveNum + 1} to: ${handle.name}`);
|
|
256
|
+
return true;
|
|
257
|
+
} catch (err) {
|
|
258
|
+
// User cancelled the picker or other error
|
|
259
|
+
if (err.name !== "AbortError") {
|
|
260
|
+
console.error("Error saving disk:", err);
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
// Fallback for browsers without File System Access API
|
|
266
|
+
downloadFile(dataCopy, suggestedName);
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Download file using traditional blob/anchor approach
|
|
273
|
+
* @param {Uint8Array} data - The file data
|
|
274
|
+
* @param {string} filename - The filename
|
|
275
|
+
*/
|
|
276
|
+
export function downloadFile(data, filename) {
|
|
277
|
+
const blob = new Blob([data], { type: "application/octet-stream" });
|
|
278
|
+
const url = URL.createObjectURL(blob);
|
|
279
|
+
const a = document.createElement("a");
|
|
280
|
+
a.href = url;
|
|
281
|
+
a.download = filename;
|
|
282
|
+
a.click();
|
|
283
|
+
URL.revokeObjectURL(url);
|
|
284
|
+
}
|