From da0cd03c57c4bcf4793462441ff786cfd2a2eb60 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Tue, 29 Apr 2025 20:57:20 +0200 Subject: [PATCH 01/10] Prepare filesystem explorer --- page/package-lock.json | 45 ++++- page/package.json | 4 +- page/src/FilesystemExplorer.tsx | 79 ++++++++ page/src/Playground.tsx | 58 +++++- page/src/components/folder.tsx | 110 +++++++++++ page/src/components/ui/context-menu.tsx | 250 ++++++++++++++++++++++++ page/src/components/ui/drawer.tsx | 130 ++++++++++++ page/src/emulator.ts | 5 +- page/src/filesystem.ts | 103 ++++------ 9 files changed, 709 insertions(+), 75 deletions(-) create mode 100644 page/src/FilesystemExplorer.tsx create mode 100644 page/src/components/folder.tsx create mode 100644 page/src/components/ui/context-menu.tsx create mode 100644 page/src/components/ui/drawer.tsx diff --git a/page/package-lock.json b/page/package-lock.json index 51c380ce..69568032 100644 --- a/page/package-lock.json +++ b/page/package-lock.json @@ -11,6 +11,7 @@ "@fontsource/inter": "^5.2.5", "@irori/idbfs": "^0.5.0", "@radix-ui/react-checkbox": "^1.2.3", + "@radix-ui/react-context-menu": "^2.2.12", "@radix-ui/react-dialog": "^1.1.11", "@radix-ui/react-dropdown-menu": "^2.1.12", "@radix-ui/react-label": "^2.1.4", @@ -36,7 +37,8 @@ "react-window": "^1.8.11", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.4", - "tw-animate-css": "^1.2.8" + "tw-animate-css": "^1.2.8", + "vaul": "^1.1.2" }, "devDependencies": { "@eslint/js": "^9.22.0", @@ -1356,6 +1358,34 @@ } } }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.12.tgz", + "integrity": "sha512-5UFKuTMX8F2/KjHvyqu9IYT8bEtDSCJwwIx1PghBo4jh9S6jJVsceq9xIjqsOVcxsynGwV5eaqPE3n/Cu+DrSA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.12", + "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.11.tgz", @@ -5265,6 +5295,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/vite": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", diff --git a/page/package.json b/page/package.json index 374fc448..b428945a 100644 --- a/page/package.json +++ b/page/package.json @@ -13,6 +13,7 @@ "@fontsource/inter": "^5.2.5", "@irori/idbfs": "^0.5.0", "@radix-ui/react-checkbox": "^1.2.3", + "@radix-ui/react-context-menu": "^2.2.12", "@radix-ui/react-dialog": "^1.1.11", "@radix-ui/react-dropdown-menu": "^2.1.12", "@radix-ui/react-label": "^2.1.4", @@ -38,7 +39,8 @@ "react-window": "^1.8.11", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.4", - "tw-animate-css": "^1.2.8" + "tw-animate-css": "^1.2.8", + "vaul": "^1.1.2" }, "devDependencies": { "@eslint/js": "^9.22.0", diff --git a/page/src/FilesystemExplorer.tsx b/page/src/FilesystemExplorer.tsx new file mode 100644 index 00000000..411604fb --- /dev/null +++ b/page/src/FilesystemExplorer.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import { Folder, FolderElement, FolderElementType } from "./components/folder"; +import { Filesystem } from "./filesystem"; + +export interface FilesystemExplorerProps { + filesystem: Filesystem; +} +export interface FilesystemExplorerState { + path: string[]; +} + +function getFolderElements(filesystem: Filesystem, path: string[]) { + const fullPath = "/root/filesys/" + path.join("/"); + const files = filesystem.readDir(fullPath); + + return files + .filter((f) => { + if (f == ".") { + return false; + } + + if (path.length == 0 && f == "..") { + return false; + } + + return true; + }) + .map((f) => { + const element: FolderElement = { + name: f, + type: filesystem.isFolder(`${fullPath}/${f}`) + ? FolderElementType.Folder + : FolderElementType.File, + }; + + return element; + }); +} + +export class FilesystemExplorer extends React.Component< + FilesystemExplorerProps, + FilesystemExplorerState +> { + constructor(props: FilesystemExplorerProps) { + super(props); + + this._onElementSelect = this._onElementSelect.bind(this); + + this.state = { + path: [], + }; + } + + _onElementSelect(element: FolderElement) { + if (element.type != FolderElementType.Folder) { + return; + } + + this.setState((s) => { + const path = [...s.path]; + + if (element.name == "..") { + path.pop(); + } else { + path.push(element.name); + } + + return { + path, + }; + }); + } + + render() { + const elements = getFolderElements(this.props.filesystem, this.state.path); + + return ; + } +} diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index f9b98885..ac1e691a 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -4,7 +4,7 @@ import { Output } from "@/components/output"; import { Separator } from "@/components/ui/separator"; import { Emulator, UserFile, EmulationState } from "./emulator"; -import { setupFilesystem } from "./filesystem"; +import { Filesystem, setupFilesystem } from "./filesystem"; import "./App.css"; import { @@ -38,6 +38,18 @@ import { } from "@/components/ui/dropdown-menu"; import { Button } from "@/components/ui/button"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; +import { FilesystemExplorer } from "./FilesystemExplorer"; + function selectAndReadFile(): Promise { return new Promise((resolve, reject) => { const fileInput = document.createElement("input"); @@ -75,8 +87,23 @@ export function Playground() { const output = useRef(null); const [settings, setSettings] = useState(createDefaultSettings()); const [emulator, setEmulator] = useState(null); + const [filesystem, setFilesystem] = useState(null); + const [filesystemPromise, setFilesystemPromise] = + useState | null>(null); const [, forceUpdate] = useReducer((x) => x + 1, 0); + if (!filesystemPromise) { + const promise = new Promise((resolve) => { + setupFilesystem((current, total, file) => { + logLine(`Processing filesystem (${current}/${total}): ${file}`); + }).then(resolve); + }); + + promise.then(setFilesystem); + + setFilesystemPromise(promise); + } + function logLine(line: string) { output.current?.logLine(line); } @@ -103,7 +130,7 @@ export function Playground() { logLine("Starting emulation..."); - await setupFilesystem((current, total, file) => { + const fs = await setupFilesystem((current, total, file) => { logLine(`Processing filesystem (${current}/${total}): ${file}`); }); @@ -111,7 +138,7 @@ export function Playground() { new_emulator.onTerminate().then(() => setEmulator(null)); setEmulator(new_emulator); - new_emulator.start(settings, userFile); + new_emulator.start(settings, userFile, fs); } async function loadAndRunUserFile() { @@ -186,6 +213,31 @@ export function Playground() { + + {!filesystem ? ( + <> + ) : ( + + + + + + + {/*Filesystem Explorer + Description + + + */} + + + + + + + )} +
void; + +export interface FolderProps { + elements: FolderElement[]; + clickHandler: ClickHandler; + //deleteHandler: (element: FolderElement) => void; + //renameHandler: (element: FolderElement, name: string) => void; +} + +function elementComparator(e1: FolderElement, e2: FolderElement) { + if (e1.type != e2.type) { + return e1.type - e2.type; + } + + return e1.name.localeCompare(e2.name); +} + +function renderIcon(element: FolderElement) { + let className = "w-10 h-10"; + switch (element.type) { + case FolderElementType.File: + return ; + case FolderElementType.Folder: + return element.name == ".." ? ( + + ) : ( + + ); + default: + return <>; + } +} + +function renderElement(element: FolderElement, clickHandler: ClickHandler) { + return ( +
clickHandler(element)} + className="folder-element select-none flex flex-col gap-4 items-center text-center p-4 m-4 w-30 h-25 rounded-lg border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50" + > + {renderIcon(element)} + + {element.name} + +
+ ); +} + +function renderElementWithContext( + element: FolderElement, + clickHandler: ClickHandler, +) { + if (element.name == "..") { + return renderElement(element, clickHandler); + } + + return ( + + + {renderElement(element, clickHandler)} + + + Rename + Delete + + + ); +} + +export function Folder(props: FolderProps) { + return ( + + + +
+ {props.elements + .sort(elementComparator) + .map((e) => renderElementWithContext(e, props.clickHandler))} +
+
+ + Create new folder + +
+
+ ); +} diff --git a/page/src/components/ui/context-menu.tsx b/page/src/components/ui/context-menu.tsx new file mode 100644 index 00000000..f7cd5022 --- /dev/null +++ b/page/src/components/ui/context-menu.tsx @@ -0,0 +1,250 @@ +import * as React from "react"; +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +function ContextMenu({ + ...props +}: React.ComponentProps) { + return ; +} + +function ContextMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function ContextMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function ContextMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function ContextMenuSub({ + ...props +}: React.ComponentProps) { + return ; +} + +function ContextMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function ContextMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + {children} + + + ); +} + +function ContextMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function ContextMenuContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function ContextMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean; + variant?: "default" | "destructive"; +}) { + return ( + + ); +} + +function ContextMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function ContextMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function ContextMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + ); +} + +function ContextMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function ContextMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ); +} + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup, +}; diff --git a/page/src/components/ui/drawer.tsx b/page/src/components/ui/drawer.tsx new file mode 100644 index 00000000..7e2126e4 --- /dev/null +++ b/page/src/components/ui/drawer.tsx @@ -0,0 +1,130 @@ +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; + +import { cn } from "@/lib/utils"; + +function Drawer({ + ...props +}: React.ComponentProps) { + return ; +} + +function DrawerTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function DrawerPortal({ + ...props +}: React.ComponentProps) { + return ; +} + +function DrawerClose({ + ...props +}: React.ComponentProps) { + return ; +} + +function DrawerOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DrawerContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + +
+ {children} + + + ); +} + +function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function DrawerTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DrawerDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +}; diff --git a/page/src/emulator.ts b/page/src/emulator.ts index 3bc7f45a..35647fa8 100644 --- a/page/src/emulator.ts +++ b/page/src/emulator.ts @@ -5,7 +5,7 @@ import * as flatbuffers from "flatbuffers"; import * as fbDebugger from "@/fb/debugger"; import * as fbDebuggerEvent from "@/fb/debugger/event"; -import { storeFile } from "./filesystem"; +import { Filesystem } from "./filesystem"; type LogHandler = (lines: string[]) => void; @@ -80,6 +80,7 @@ export class Emulator { async start( settings: Settings = createDefaultSettings(), userFile: UserFile | null = null, + fs: Filesystem, ) { var file = "c:/test-sample.exe"; if (userFile) { @@ -87,7 +88,7 @@ export class Emulator { const canonicalName = filename?.toLowerCase(); file = "c:/" + canonicalName; - await storeFile("root/filesys/c/" + canonicalName, userFile.data); + await fs.storeFile("root/filesys/c/" + canonicalName, userFile.data); } this._setState(EmulationState.Running); diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index 0b264a27..317df299 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -1,65 +1,6 @@ -import { parseZipFile, FileEntry, ProgressHandler } from "./zip-file"; +import { parseZipFile, ProgressHandler } from "./zip-file"; import idbfsModule, { MainModule } from "@irori/idbfs"; -function openDatabase(): Promise { - return new Promise((resolve, reject) => { - const request = indexedDB.open("cacheDB", 1); - - request.onerror = (event: Event) => { - reject(event); - }; - - request.onsuccess = (event: Event) => { - resolve((event as any).target.result as IDBDatabase); - }; - - request.onupgradeneeded = (event: Event) => { - const db = (event as any).target.result as IDBDatabase; - if (!db.objectStoreNames.contains("cacheStore")) { - db.createObjectStore("cacheStore", { keyPath: "id" }); - } - }; - }); -} - -async function saveData(id: string, data: any) { - const db = await openDatabase(); - return new Promise((resolve, reject) => { - const transaction = db.transaction(["cacheStore"], "readwrite"); - const objectStore = transaction.objectStore("cacheStore"); - const request = objectStore.put({ id: id, data: data }); - - request.onsuccess = () => { - resolve("Data saved successfully"); - }; - - request.onerror = (event) => { - reject("Save error: " + (event as any).target.errorCode); - }; - }); -} - -async function getData(id: string) { - const db = await openDatabase(); - return new Promise((resolve, reject) => { - const transaction = db.transaction(["cacheStore"], "readonly"); - const objectStore = transaction.objectStore("cacheStore"); - const request = objectStore.get(id); - - request.onsuccess = (event) => { - if ((event as any).target.result) { - resolve((event as any).target.result.data); - } else { - resolve(null); - } - }; - - request.onerror = (event) => { - reject("Retrieve error: " + (event as any).target.errorCode); - }; - }); -} - function fetchFilesystemZip() { return fetch("./root.zip?1", { method: "GET", @@ -97,11 +38,42 @@ async function initializeIDBFS() { return idbfs; } +export class Filesystem { + private idbfs: MainModule; + + constructor(idbfs: MainModule) { + this.idbfs = idbfs; + } + + async storeFile(file: string, data: ArrayBuffer) { + const buffer = new Uint8Array(data); + this.idbfs.FS.writeFile(file, buffer); + await this.sync(); + } + + async sync() { + await synchronizeIDBFS(this.idbfs, false); + } + + readDir(dir: string): string[] { + return this.idbfs.FS.readdir(dir); + } + + stat(file: string) { + return this.idbfs.FS.stat(file, false); + } + + isFolder(file: string) { + return (this.stat(file).mode & 0x4000) != 0; + } +} + export async function setupFilesystem(progressHandler: ProgressHandler) { const idbfs = await initializeIDBFS(); + const fs = new Filesystem(idbfs); if (idbfs.FS.analyzePath("/root/api-set.bin", false).exists) { - return; + return fs; } const filesystem = await fetchFilesystem(progressHandler); @@ -119,12 +91,7 @@ export async function setupFilesystem(progressHandler: ProgressHandler) { } }); - await synchronizeIDBFS(idbfs, false); -} + await fs.sync(); -export async function storeFile(file: string, data: ArrayBuffer) { - const idbfs = await initializeIDBFS(); - const buffer = new Uint8Array(data); - idbfs.FS.writeFile(file, buffer); - await synchronizeIDBFS(idbfs, false); + return fs; } From 3531d1d5eae9504c7664b1527e25c68321d69e67 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Tue, 29 Apr 2025 21:14:57 +0200 Subject: [PATCH 02/10] Add tooltips --- page/src/components/folder.tsx | 61 ++++++++++++++++++++++-------- page/src/components/ui/tooltip.tsx | 2 - 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/page/src/components/folder.tsx b/page/src/components/folder.tsx index 15d2080f..f3e19c01 100644 --- a/page/src/components/folder.tsx +++ b/page/src/components/folder.tsx @@ -2,6 +2,8 @@ import { FolderFill, FolderSymlinkFill, FileEarmark, + FiletypeExe, + FileEarmarkBinary, } from "react-bootstrap-icons"; import { ScrollArea } from "@/components/ui/scroll-area"; import { @@ -9,7 +11,15 @@ import { ContextMenuContent, ContextMenuItem, ContextMenuTrigger, + ContextMenuSeparator, + ContextMenuLabel, } from "@/components/ui/context-menu"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; export enum FolderElementType { Folder = 0, @@ -38,10 +48,15 @@ function elementComparator(e1: FolderElement, e2: FolderElement) { return e1.name.localeCompare(e2.name); } -function renderIcon(element: FolderElement) { - let className = "w-10 h-10"; +function getIcon(element: FolderElement, className: string = "") { switch (element.type) { case FolderElementType.File: + if (element.name.endsWith(".dll")) { + return ; + } + if (element.name.endsWith(".exe")) { + return ; + } return ; case FolderElementType.Folder: return element.name == ".." ? ( @@ -54,6 +69,11 @@ function renderIcon(element: FolderElement) { } } +function renderIcon(element: FolderElement) { + let className = "w-10 h-10"; + return getIcon(element, className); +} + function renderElement(element: FolderElement, clickHandler: ClickHandler) { return (
- {renderElement(element, clickHandler)} + + + {renderElement(element, clickHandler)} + + +

{element.name}

+
+
+ {element.name} + Rename Delete @@ -93,18 +122,20 @@ function renderElementWithContext( export function Folder(props: FolderProps) { return ( - - -
- {props.elements - .sort(elementComparator) - .map((e) => renderElementWithContext(e, props.clickHandler))} -
-
- - Create new folder - -
+ + + +
+ {props.elements + .sort(elementComparator) + .map((e) => renderElementWithContext(e, props.clickHandler))} +
+
+ + Create new folder + +
+
); } diff --git a/page/src/components/ui/tooltip.tsx b/page/src/components/ui/tooltip.tsx index bf4a342a..515ee323 100644 --- a/page/src/components/ui/tooltip.tsx +++ b/page/src/components/ui/tooltip.tsx @@ -1,5 +1,3 @@ -"use client"; - import * as React from "react"; import * as TooltipPrimitive from "@radix-ui/react-tooltip"; From af5c2a9d5bc34787a383105cfc79add2744f0848 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 30 Apr 2025 08:06:28 +0200 Subject: [PATCH 03/10] Add dialogs --- page/src/FilesystemExplorer.tsx | 109 +++++++++++++++++++++++- page/src/components/folder.tsx | 28 +++++-- page/src/components/ui/dialog.tsx | 133 ++++++++++++++++++++++++++++++ page/src/filesystem.ts | 7 +- 4 files changed, 269 insertions(+), 8 deletions(-) create mode 100644 page/src/components/ui/dialog.tsx diff --git a/page/src/FilesystemExplorer.tsx b/page/src/FilesystemExplorer.tsx index 411604fb..0ef015c9 100644 --- a/page/src/FilesystemExplorer.tsx +++ b/page/src/FilesystemExplorer.tsx @@ -2,11 +2,23 @@ import React from "react"; import { Folder, FolderElement, FolderElementType } from "./components/folder"; import { Filesystem } from "./filesystem"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "./components/ui/button"; +import { Input } from "./components/ui/input"; + export interface FilesystemExplorerProps { filesystem: Filesystem; } export interface FilesystemExplorerState { path: string[]; + createFolder: boolean; + errorText: string; } function getFolderElements(filesystem: Filesystem, path: string[]) { @@ -44,13 +56,22 @@ export class FilesystemExplorer extends React.Component< constructor(props: FilesystemExplorerProps) { super(props); + this._showError = this._showError.bind(this); + this._onFolderCreate = this._onFolderCreate.bind(this); this._onElementSelect = this._onElementSelect.bind(this); + this._showFolderCreateDialog = this._showFolderCreateDialog.bind(this); this.state = { path: [], + createFolder: false, + errorText: "", }; } + _showError(errorText: string) { + this.setState({ errorText }); + } + _onElementSelect(element: FolderElement) { if (element.type != FolderElementType.Folder) { return; @@ -71,9 +92,95 @@ export class FilesystemExplorer extends React.Component< }); } + _showFolderCreateDialog() { + this.setState({ + createFolder: true, + }); + } + + async _onFolderCreate(name: string) { + this.setState({ + createFolder: false, + }); + + name = name.toLowerCase(); + + if (name.length == 0) { + return; + } + + if (name.includes("/") || name.includes("\\")) { + this._showError("Folder must not contain special characters"); + return; + } + + if (this.state.path.length == 0 && name.length > 1) { + this._showError("Drives must be a single letter"); + return; + } + + const fullPath = "/root/filesys/" + [...this.state.path, name].join("/"); + await this.props.filesystem.createFolder(fullPath); + this.forceUpdate(); + } + render() { const elements = getFolderElements(this.props.filesystem, this.state.path); - return ; + return ( + <> + this.setState({ createFolder: open })} + > + +
{ + const folderName = (e.target as any).elements.name.value; + this._onFolderCreate(folderName); + e.preventDefault(); + }} + > + + Create new folder + +
+ +
+ + + +
+
+
+ + 0} + onOpenChange={(open) => + open ? {} : this.setState({ errorText: "" }) + } + > + + + Error + +
{this.state.errorText}
+ + + +
+
+ + + + ); } } diff --git a/page/src/components/folder.tsx b/page/src/components/folder.tsx index f3e19c01..0ff777e2 100644 --- a/page/src/components/folder.tsx +++ b/page/src/components/folder.tsx @@ -32,10 +32,12 @@ export interface FolderElement { } type ClickHandler = (element: FolderElement) => void; +type CreateFolderHandler = () => void; export interface FolderProps { elements: FolderElement[]; clickHandler: ClickHandler; + createFolderHandler: CreateFolderHandler; //deleteHandler: (element: FolderElement) => void; //renameHandler: (element: FolderElement, name: string) => void; } @@ -70,19 +72,18 @@ function getIcon(element: FolderElement, className: string = "") { } function renderIcon(element: FolderElement) { - let className = "w-10 h-10"; + let className = "w-6 h-6 flex-1"; return getIcon(element, className); } function renderElement(element: FolderElement, clickHandler: ClickHandler) { return (
clickHandler(element)} - className="folder-element select-none flex flex-col gap-4 items-center text-center p-4 m-4 w-30 h-25 rounded-lg border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50" + className="folder-element select-none flex flex-col gap-2 items-center text-center text-xs p-2 m-2 w-25 h-18 rounded-lg border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50" > {renderIcon(element)} - + {element.name}
@@ -119,6 +120,21 @@ function renderElementWithContext( ); } +function renderElementWrapper( element: FolderElement, + clickHandler: ClickHandler,) { + return
+ {renderElementWithContext(element, clickHandler)} +
+} + +function renderFolderCreator(createFolderHandler: CreateFolderHandler) { + return ( + + Create new Folder + + ); +} + export function Folder(props: FolderProps) { return ( @@ -128,11 +144,11 @@ export function Folder(props: FolderProps) {
{props.elements .sort(elementComparator) - .map((e) => renderElementWithContext(e, props.clickHandler))} + .map((e) => renderElementWrapper(e, props.clickHandler))}
- Create new folder + {renderFolderCreator(props.createFolderHandler)} diff --git a/page/src/components/ui/dialog.tsx b/page/src/components/ui/dialog.tsx new file mode 100644 index 00000000..3b37abd1 --- /dev/null +++ b/page/src/components/ui/dialog.tsx @@ -0,0 +1,133 @@ +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { XIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +function Dialog({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + {children} + + + Close + + + + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index 317df299..f2a864f2 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -45,12 +45,17 @@ export class Filesystem { this.idbfs = idbfs; } - async storeFile(file: string, data: ArrayBuffer) { + async storeFiles(file: string, data: ArrayBuffer) { const buffer = new Uint8Array(data); this.idbfs.FS.writeFile(file, buffer); await this.sync(); } + async createFolder(folder: string) { + this.idbfs.FS.mkdir(folder, 777); + await this.sync(); + } + async sync() { await synchronizeIDBFS(this.idbfs, false); } From 51971c5ec7927103ff6b13bd59d44b10f60bfcf4 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 30 Apr 2025 08:59:25 +0200 Subject: [PATCH 04/10] Basic working file explorer --- page/src/FilesystemExplorer.tsx | 259 ++++++++++++++++++++++++-------- page/src/Playground.tsx | 11 +- page/src/components/folder.tsx | 34 +++-- page/src/filesystem.ts | 25 +++ 4 files changed, 248 insertions(+), 81 deletions(-) diff --git a/page/src/FilesystemExplorer.tsx b/page/src/FilesystemExplorer.tsx index 0ef015c9..1a0db87c 100644 --- a/page/src/FilesystemExplorer.tsx +++ b/page/src/FilesystemExplorer.tsx @@ -4,6 +4,7 @@ import { Filesystem } from "./filesystem"; import { Dialog, + DialogClose, DialogContent, DialogFooter, DialogHeader, @@ -11,6 +12,7 @@ import { } from "@/components/ui/dialog"; import { Button } from "./components/ui/button"; import { Input } from "./components/ui/input"; +import { DialogDescription } from "@radix-ui/react-dialog"; export interface FilesystemExplorerProps { filesystem: Filesystem; @@ -19,10 +21,34 @@ export interface FilesystemExplorerState { path: string[]; createFolder: boolean; errorText: string; + removeFile: string; + renameFile: string; +} + +function makeFullPath(path: string[]) { + return "/root/filesys/" + path.join("/"); +} + +function makeFullPathAndJoin(path: string[], element: string) { + return makeFullPath([...path, element]); +} + +function makeFullPathWithState( + state: FilesystemExplorerState, + element: string, +) { + return makeFullPathAndJoin(state.path, element); +} + +function makeRelativePathWithState( + state: FilesystemExplorerState, + element: string, +) { + return [...state.path, element].join("/"); } function getFolderElements(filesystem: Filesystem, path: string[]) { - const fullPath = "/root/filesys/" + path.join("/"); + const fullPath = makeFullPath(path); const files = filesystem.readDir(fullPath); return files @@ -56,15 +82,14 @@ export class FilesystemExplorer extends React.Component< constructor(props: FilesystemExplorerProps) { super(props); - this._showError = this._showError.bind(this); - this._onFolderCreate = this._onFolderCreate.bind(this); this._onElementSelect = this._onElementSelect.bind(this); - this._showFolderCreateDialog = this._showFolderCreateDialog.bind(this); this.state = { path: [], createFolder: false, errorText: "", + removeFile: "", + renameFile: "", }; } @@ -92,16 +117,18 @@ export class FilesystemExplorer extends React.Component< }); } - _showFolderCreateDialog() { - this.setState({ - createFolder: true, - }); + async _onFileRename(file: string, newFile: string) { + const oldPath = makeFullPathWithState(this.state, file); + const newPath = makeFullPathWithState(this.state, newFile); + + this.setState({ renameFile: "" }); + + await this.props.filesystem.rename(oldPath, newPath); + this.forceUpdate(); } async _onFolderCreate(name: string) { - this.setState({ - createFolder: false, - }); + this.setState({ createFolder: false }); name = name.toLowerCase(); @@ -110,75 +137,185 @@ export class FilesystemExplorer extends React.Component< } if (name.includes("/") || name.includes("\\")) { - this._showError("Folder must not contain special characters"); - return; - } + this._showError("Folder must not contain special characters"); + return; + } if (this.state.path.length == 0 && name.length > 1) { this._showError("Drives must be a single letter"); return; } - const fullPath = "/root/filesys/" + [...this.state.path, name].join("/"); + const fullPath = makeFullPathWithState(this.state, name); await this.props.filesystem.createFolder(fullPath); this.forceUpdate(); } + _renderCreateFolderDialog() { + return ( + this.setState({ createFolder: open })} + > + +
{ + const folderName = (e.target as any).elements.name.value; + this._onFolderCreate(folderName); + e.preventDefault(); + }} + > + + Create new folder + + Create new folder + + +
+ +
+ + + +
+
+
+ ); + } + + _renderRenameDialog() { + return ( + 0} + onOpenChange={(open) => (open ? {} : this.setState({ renameFile: "" }))} + > + +
{ + const newName = (e.target as any).elements.name.value; + this._onFileRename(this.state.renameFile, newName); + e.preventDefault(); + }} + > + + Rename {this.state.renameFile} + + Rename {this.state.renameFile} + + +
+ +
+ + + + + + +
+
+
+ ); + } + + _renderErrorDialog() { + return ( + 0} + onOpenChange={(open) => (open ? {} : this.setState({ errorText: "" }))} + > + + + Error + + Error: {this.state.errorText} + + +
{this.state.errorText}
+ + + +
+
+ ); + } + + _renderRemoveDialog() { + return ( + 0} + onOpenChange={(open) => (open ? {} : this.setState({ removeFile: "" }))} + > + + + Delete {this.state.removeFile}? + + Delete {this.state.removeFile} + + +
+ Are you sure you want to delete{" "} + + {makeRelativePathWithState(this.state, this.state.removeFile)} + +
+ + + + +
+
+ ); + } + render() { const elements = getFolderElements(this.props.filesystem, this.state.path); return ( <> - this.setState({ createFolder: open })} - > - -
{ - const folderName = (e.target as any).elements.name.value; - this._onFolderCreate(folderName); - e.preventDefault(); - }} - > - - Create new folder - -
- -
- - - -
-
-
- - 0} - onOpenChange={(open) => - open ? {} : this.setState({ errorText: "" }) - } - > - - - Error - -
{this.state.errorText}
- - - -
-
+ {this._renderCreateFolderDialog()} + {this._renderRenameDialog()} + {this._renderErrorDialog()} + {this._renderRemoveDialog()} this.setState({ createFolder: true })} + removeElementHandler={(e) => this.setState({ removeFile: e.name })} + renameElementHandler={(e) => this.setState({ renameFile: e.name })} /> ); diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index ac1e691a..e4bbb80c 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -225,11 +225,12 @@ export function Playground() { - {/*Filesystem Explorer - Description - - - */} + + Filesystem Explorer + + + Filesystem Explorer + diff --git a/page/src/components/folder.tsx b/page/src/components/folder.tsx index 0ff777e2..a32ea608 100644 --- a/page/src/components/folder.tsx +++ b/page/src/components/folder.tsx @@ -33,13 +33,15 @@ export interface FolderElement { type ClickHandler = (element: FolderElement) => void; type CreateFolderHandler = () => void; +type RemoveElementHandler = (element: FolderElement) => void; +type RenameElementHandler = (element: FolderElement) => void; export interface FolderProps { elements: FolderElement[]; clickHandler: ClickHandler; createFolderHandler: CreateFolderHandler; - //deleteHandler: (element: FolderElement) => void; - //renameHandler: (element: FolderElement, name: string) => void; + removeElementHandler: RemoveElementHandler; + renameElementHandler: RenameElementHandler; } function elementComparator(e1: FolderElement, e2: FolderElement) { @@ -90,12 +92,9 @@ function renderElement(element: FolderElement, clickHandler: ClickHandler) { ); } -function renderElementWithContext( - element: FolderElement, - clickHandler: ClickHandler, -) { +function renderElementWithContext(element: FolderElement, props: FolderProps) { if (element.name == "..") { - return renderElement(element, clickHandler); + return renderElement(element, props.clickHandler); } return ( @@ -103,7 +102,7 @@ function renderElementWithContext( - {renderElement(element, clickHandler)} + {renderElement(element, props.clickHandler)}

{element.name}

@@ -113,18 +112,23 @@ function renderElementWithContext( {element.name} - Rename - Delete + props.renameElementHandler(element)}> + Rename + + props.removeElementHandler(element)}> + Delete + ); } -function renderElementWrapper( element: FolderElement, - clickHandler: ClickHandler,) { - return
- {renderElementWithContext(element, clickHandler)} +function renderElementWrapper(element: FolderElement, props: FolderProps) { + return ( +
+ {renderElementWithContext(element, props)}
+ ); } function renderFolderCreator(createFolderHandler: CreateFolderHandler) { @@ -144,7 +148,7 @@ export function Folder(props: FolderProps) {
{props.elements .sort(elementComparator) - .map((e) => renderElementWrapper(e, props.clickHandler))} + .map((e) => renderElementWrapper(e, props))}
diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index f2a864f2..b7165a49 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -51,6 +51,31 @@ export class Filesystem { await this.sync(); } + _unlinkRecursive(element: string) { + if (!this.isFolder(element)) { + this.idbfs.FS.unlink(element); + return; + } + + this.readDir(element) // + .filter((e) => e != "." && e != "..") + .forEach((e) => { + this._unlinkRecursive(`${element}/${e}`); + }); + + this.idbfs.FS.rmdir(element); + } + + async rename(oldFile: string, newFile: string) { + this.idbfs.FS.rename(oldFile, newFile); + await this.sync(); + } + + async unlink(file: string) { + this._unlinkRecursive(file); + await this.sync(); + } + async createFolder(folder: string) { this.idbfs.FS.mkdir(folder, 777); await this.sync(); From 62b06a171709c3c1c37d3e0087598ee3ce750f1e Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 30 Apr 2025 10:13:27 +0200 Subject: [PATCH 05/10] Finish filesystem explorer --- page/package-lock.json | 39 +++++++++++++ page/package.json | 1 + page/src/FilesystemExplorer.tsx | 98 ++++++++++++++++++++++++++++++--- page/src/Playground.tsx | 29 ++++++---- page/src/emulator.ts | 20 +------ page/src/filesystem.ts | 29 ++++++++-- src/tools/create-root.bat | 1 + 7 files changed, 175 insertions(+), 42 deletions(-) diff --git a/page/package-lock.json b/page/package-lock.json index 69568032..9ebe7d75 100644 --- a/page/package-lock.json +++ b/page/package-lock.json @@ -31,6 +31,7 @@ "react": "^19.0.0", "react-bootstrap-icons": "^1.11.5", "react-dom": "^19.0.0", + "react-dropzone": "^14.3.8", "react-helmet": "^6.1.0", "react-resizable-panels": "^2.1.9", "react-router-dom": "^7.5.3", @@ -3074,6 +3075,15 @@ "node": ">=10" } }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3660,6 +3670,18 @@ "node": ">=16.0.0" } }, + "node_modules/file-selector": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "license": "MIT", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4647,6 +4669,23 @@ "react": "^19.1.0" } }, + "node_modules/react-dropzone": { + "version": "14.3.8", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", + "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", diff --git a/page/package.json b/page/package.json index b428945a..2d5911ea 100644 --- a/page/package.json +++ b/page/package.json @@ -33,6 +33,7 @@ "react": "^19.0.0", "react-bootstrap-icons": "^1.11.5", "react-dom": "^19.0.0", + "react-dropzone": "^14.3.8", "react-helmet": "^6.1.0", "react-resizable-panels": "^2.1.9", "react-router-dom": "^7.5.3", diff --git a/page/src/FilesystemExplorer.tsx b/page/src/FilesystemExplorer.tsx index 1a0db87c..a747d0ce 100644 --- a/page/src/FilesystemExplorer.tsx +++ b/page/src/FilesystemExplorer.tsx @@ -14,8 +14,11 @@ import { Button } from "./components/ui/button"; import { Input } from "./components/ui/input"; import { DialogDescription } from "@radix-ui/react-dialog"; +import Dropzone from "react-dropzone"; + export interface FilesystemExplorerProps { filesystem: Filesystem; + runFile: (file: string) => void; } export interface FilesystemExplorerState { path: string[]; @@ -40,6 +43,17 @@ function makeFullPathWithState( return makeFullPathAndJoin(state.path, element); } +function relativePathToWindowsPath(fullPath: string) { + if (fullPath.length == 0) { + return fullPath; + } + + const drive = fullPath.substring(0, 1); + const rest = fullPath.substring(1); + + return `${drive}:${rest}`; +} + function makeRelativePathWithState( state: FilesystemExplorerState, element: string, @@ -47,6 +61,14 @@ function makeRelativePathWithState( return [...state.path, element].join("/"); } +function makeWindowsPathWithState( + state: FilesystemExplorerState, + element: string, +) { + const fullPath = makeRelativePathWithState(state, element); + return relativePathToWindowsPath(fullPath); +} + function getFolderElements(filesystem: Filesystem, path: string[]) { const fullPath = makeFullPath(path); const files = filesystem.readDir(fullPath); @@ -75,6 +97,37 @@ function getFolderElements(filesystem: Filesystem, path: string[]) { }); } +interface FileWithData { + file: File; + data: ArrayBuffer; +} + +function readFile(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + if (reader.readyState === FileReader.DONE) { + resolve({ + file, + data: reader.result as ArrayBuffer, + }); + } + }; + reader.onerror = reject; + reader.readAsArrayBuffer(file); + }); +} + +async function readFiles(files: FileList | File[]): Promise { + const promises = []; + + for (let i = 0; i < files.length; i++) { + promises.push(readFile(files[i])); + } + + return Promise.all(promises); +} + export class FilesystemExplorer extends React.Component< FilesystemExplorerProps, FilesystemExplorerState @@ -82,6 +135,7 @@ export class FilesystemExplorer extends React.Component< constructor(props: FilesystemExplorerProps) { super(props); + this._onFileDrop = this._onFileDrop.bind(this); this._onElementSelect = this._onElementSelect.bind(this); this.state = { @@ -99,6 +153,11 @@ export class FilesystemExplorer extends React.Component< _onElementSelect(element: FolderElement) { if (element.type != FolderElementType.Folder) { + if (element.name.endsWith(".exe")) { + const file = makeWindowsPathWithState(this.state, element.name); + this.props.runFile(file); + } + return; } @@ -151,6 +210,18 @@ export class FilesystemExplorer extends React.Component< this.forceUpdate(); } + async _onFileDrop(files: FileList | File[]) { + const fileData = (await readFiles(files)).map((f) => { + return { + name: makeFullPathWithState(this.state, f.file.name.toLowerCase()), + data: f.data, + }; + }); + + await this.props.filesystem.storeFiles(fileData); + this.forceUpdate(); + } + _renderCreateFolderDialog() { return ( this.setState({ createFolder: true })} - removeElementHandler={(e) => this.setState({ removeFile: e.name })} - renameElementHandler={(e) => this.setState({ renameFile: e.name })} - /> + + {({ getRootProps, getInputProps }) => ( +
+ + + this.setState({ createFolder: true }) + } + removeElementHandler={(e) => + this.setState({ removeFile: e.name }) + } + renameElementHandler={(e) => + this.setState({ renameFile: e.name }) + } + /> +
+ )} +
); } diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index e4bbb80c..04748301 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -3,7 +3,7 @@ import { Output } from "@/components/output"; import { Separator } from "@/components/ui/separator"; -import { Emulator, UserFile, EmulationState } from "./emulator"; +import { Emulator, EmulationState } from "./emulator"; import { Filesystem, setupFilesystem } from "./filesystem"; import "./App.css"; @@ -50,7 +50,7 @@ import { } from "@/components/ui/drawer"; import { FilesystemExplorer } from "./FilesystemExplorer"; -function selectAndReadFile(): Promise { +/*function selectAndReadFile(): Promise { return new Promise((resolve, reject) => { const fileInput = document.createElement("input"); fileInput.type = "file"; @@ -81,7 +81,7 @@ function selectAndReadFile(): Promise { fileInput.click(); }); -} +}*/ export function Playground() { const output = useRef(null); @@ -124,26 +124,26 @@ export function Playground() { } } - async function createEmulator(userFile: UserFile | null = null) { + async function createEmulator(userFile: string) { emulator?.stop(); output.current?.clear(); logLine("Starting emulation..."); - const fs = await setupFilesystem((current, total, file) => { - logLine(`Processing filesystem (${current}/${total}): ${file}`); - }); + if (filesystemPromise) { + await filesystemPromise; + } const new_emulator = new Emulator(logLines, (_) => forceUpdate()); new_emulator.onTerminate().then(() => setEmulator(null)); setEmulator(new_emulator); - new_emulator.start(settings, userFile, fs); + new_emulator.start(settings, userFile); } async function loadAndRunUserFile() { - const fileBuffer = await selectAndReadFile(); - await createEmulator(fileBuffer); + //const fileBuffer = await selectAndReadFile(); + //await createEmulator(fileBuffer); } return ( @@ -164,7 +164,9 @@ export function Playground() { Run Application - createEmulator()}> + createEmulator("c:/test-sample.exe")} + > Select Sample @@ -233,7 +235,10 @@ export function Playground() { - + diff --git a/page/src/emulator.ts b/page/src/emulator.ts index 35647fa8..63962ed7 100644 --- a/page/src/emulator.ts +++ b/page/src/emulator.ts @@ -9,11 +9,6 @@ import { Filesystem } from "./filesystem"; type LogHandler = (lines: string[]) => void; -export interface UserFile { - name: string; - data: ArrayBuffer; -} - export enum EmulationState { Stopped, Paused, @@ -77,20 +72,7 @@ export class Emulator { this.worker.onmessage = this._onMessage.bind(this); } - async start( - settings: Settings = createDefaultSettings(), - userFile: UserFile | null = null, - fs: Filesystem, - ) { - var file = "c:/test-sample.exe"; - if (userFile) { - const filename = userFile.name.split("/").pop()?.split("\\").pop(); - const canonicalName = filename?.toLowerCase(); - file = "c:/" + canonicalName; - - await fs.storeFile("root/filesys/c/" + canonicalName, userFile.data); - } - + async start(settings: Settings = createDefaultSettings(), file: string) { this._setState(EmulationState.Running); this.worker.postMessage({ message: "run", diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index b7165a49..45d2c3c8 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -38,6 +38,11 @@ async function initializeIDBFS() { return idbfs; } +export interface FileWithData { + name: string; + data: ArrayBuffer; +} + export class Filesystem { private idbfs: MainModule; @@ -45,9 +50,21 @@ export class Filesystem { this.idbfs = idbfs; } - async storeFiles(file: string, data: ArrayBuffer) { - const buffer = new Uint8Array(data); - this.idbfs.FS.writeFile(file, buffer); + _storeFile(file: FileWithData) { + if (file.name.includes("/")) { + const folder = file.name.split("/").slice(0, -1).join("/"); + this._createFolder(folder); + } + + const buffer = new Uint8Array(file.data); + this.idbfs.FS.writeFile(file.name, buffer); + } + + async storeFiles(files: FileWithData[]) { + files.forEach((f) => { + this._storeFile(f); + }); + await this.sync(); } @@ -76,8 +93,12 @@ export class Filesystem { await this.sync(); } + _createFolder(folder: string) { + this.idbfs.FS.mkdirTree(folder, 0o777); + } + async createFolder(folder: string) { - this.idbfs.FS.mkdir(folder, 777); + this._createFolder(folder); await this.sync(); } diff --git a/src/tools/create-root.bat b/src/tools/create-root.bat index 07936796..5c2fe140 100644 --- a/src/tools/create-root.bat +++ b/src/tools/create-root.bat @@ -126,6 +126,7 @@ CALL :collect d3d10.dll CALL :collect d3d10core.dll CALL :collect cabinet.dll CALL :collect msacm32.dll +CALL :collect coloradapterclient.dll CALL :collect locale.nls CALL :collect c_1252.nls From 3c380e84208445fba198a086725f92f0b7ab06cb Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 30 Apr 2025 11:50:27 +0200 Subject: [PATCH 06/10] Breadcrumbs and tooltips --- page/src/App.tsx | 17 +++-- page/src/FilesystemExplorer.tsx | 112 +++++++++++++++++++++++++++-- page/src/Playground.tsx | 120 +++++++++----------------------- page/src/components/folder.tsx | 49 ++++++------- page/src/filesystem.ts | 22 ++++++ 5 files changed, 195 insertions(+), 125 deletions(-) diff --git a/page/src/App.tsx b/page/src/App.tsx index 17a58b11..e71e2107 100644 --- a/page/src/App.tsx +++ b/page/src/App.tsx @@ -1,4 +1,5 @@ import { ThemeProvider } from "@/components/theme-provider"; +import { TooltipProvider } from "@/components/ui/tooltip"; import { HashRouter, Route, Routes, Navigate } from "react-router-dom"; import { Playground } from "./Playground"; import { LandingPage } from "./LandingPage"; @@ -18,13 +19,15 @@ import "./App.css"; function App() { return ( - - - } /> - } /> - } /> - - + + + + } /> + } /> + } /> + + + ); } diff --git a/page/src/FilesystemExplorer.tsx b/page/src/FilesystemExplorer.tsx index a747d0ce..9255fbc9 100644 --- a/page/src/FilesystemExplorer.tsx +++ b/page/src/FilesystemExplorer.tsx @@ -15,10 +15,22 @@ import { Input } from "./components/ui/input"; import { DialogDescription } from "@radix-ui/react-dialog"; import Dropzone from "react-dropzone"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; + +import { HouseFill } from "react-bootstrap-icons"; export interface FilesystemExplorerProps { filesystem: Filesystem; runFile: (file: string) => void; + resetFilesys: () => void; + path: string[]; } export interface FilesystemExplorerState { path: string[]; @@ -128,6 +140,43 @@ async function readFiles(files: FileList | File[]): Promise { return Promise.all(promises); } +function selectFiles(): Promise { + return new Promise((resolve) => { + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = ".exe"; + + fileInput.addEventListener("change", function (event) { + const files = (event as any).target.files as FileList; + resolve(files); + }); + + fileInput.click(); + }); +} + +interface BreadcrumbElement { + node: React.ReactNode; + targetPath: string[]; +} + +function generateBreadcrumbElements(path: string[]): BreadcrumbElement[] { + const elements = path.map((p, index) => { + const e: BreadcrumbElement = { + node: p, + targetPath: path.slice(0, index + 1), + }; + + return e; + }); + elements.unshift({ + node: , + targetPath: [], + }); + + return elements; +} + export class FilesystemExplorer extends React.Component< FilesystemExplorerProps, FilesystemExplorerState @@ -135,11 +184,12 @@ export class FilesystemExplorer extends React.Component< constructor(props: FilesystemExplorerProps) { super(props); - this._onFileDrop = this._onFileDrop.bind(this); + this._onAddFiles = this._onAddFiles.bind(this); + this._uploadFiles = this._uploadFiles.bind(this); this._onElementSelect = this._onElementSelect.bind(this); this.state = { - path: [], + path: this.props.path, createFolder: false, errorText: "", removeFile: "", @@ -186,6 +236,11 @@ export class FilesystemExplorer extends React.Component< this.forceUpdate(); } + async _onAddFiles() { + const files = await selectFiles(); + await this._uploadFiles(files); + } + async _onFolderCreate(name: string) { this.setState({ createFolder: false }); @@ -210,7 +265,16 @@ export class FilesystemExplorer extends React.Component< this.forceUpdate(); } - async _onFileDrop(files: FileList | File[]) { + async _uploadFiles(files: FileList | File[]) { + if (files.length == 0) { + return; + } + + if (this.state.path.length == 0) { + this._showError("Files must be within a drive"); + return; + } + const fileData = (await readFiles(files)).map((f) => { return { name: makeFullPathWithState(this.state, f.file.name.toLowerCase()), @@ -249,6 +313,11 @@ export class FilesystemExplorer extends React.Component< + + + @@ -371,6 +440,38 @@ export class FilesystemExplorer extends React.Component< ); } + _renderBreadcrumbElements() { + const elements = generateBreadcrumbElements(this.state.path); + + return elements.map((e, index) => { + if (index == this.state.path.length) { + return ( + + {e.node} + + ); + } + + const navigate = () => this.setState({ path: e.targetPath }); + return ( + <> + + {e.node} + + + + ); + }); + } + + _renderBreadCrumb() { + return ( + + {this._renderBreadcrumbElements()} + + ); + } + render() { const elements = getFolderElements(this.props.filesystem, this.state.path); @@ -381,7 +482,9 @@ export class FilesystemExplorer extends React.Component< {this._renderErrorDialog()} {this._renderRemoveDialog()} - + {this._renderBreadCrumb()} + + {({ getRootProps, getInputProps }) => (
@@ -397,6 +500,7 @@ export class FilesystemExplorer extends React.Component< renameElementHandler={(e) => this.setState({ renameFile: e.name }) } + addFilesHandler={this._onAddFiles} />
)} diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index 04748301..ab15d6ba 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -16,92 +16,62 @@ import { import { createDefaultSettings } from "./settings"; import { SettingsMenu } from "@/components/settings-menu"; -import { - PlayFill, - StopFill, - GearFill, - PauseFill, - FileEarmarkCheckFill, - ImageFill, -} from "react-bootstrap-icons"; +import { PlayFill, StopFill, GearFill, PauseFill } from "react-bootstrap-icons"; import { StatusIndicator } from "@/components/status-indicator"; import { Header } from "./Header"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, - DropdownMenuGroup, -} from "@/components/ui/dropdown-menu"; import { Button } from "@/components/ui/button"; import { Drawer, - DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, - DrawerTrigger, } from "@/components/ui/drawer"; import { FilesystemExplorer } from "./FilesystemExplorer"; -/*function selectAndReadFile(): Promise { - return new Promise((resolve, reject) => { - const fileInput = document.createElement("input"); - fileInput.type = "file"; - fileInput.accept = ".exe"; - - fileInput.addEventListener("change", function (event) { - const file = (event as any).target.files[0]; - if (file) { - const reader = new FileReader(); - - reader.onload = function (e: ProgressEvent) { - const arrayBuffer = e.target?.result; - resolve({ - name: file.name, - data: arrayBuffer as ArrayBuffer, - }); - }; - - reader.onerror = function (e: ProgressEvent) { - reject(new Error("Error reading file: " + e.target?.error)); - }; - - reader.readAsArrayBuffer(file); - } else { - reject(new Error("No file selected")); - } - }); - - fileInput.click(); - }); -}*/ - export function Playground() { const output = useRef(null); const [settings, setSettings] = useState(createDefaultSettings()); const [emulator, setEmulator] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); const [filesystem, setFilesystem] = useState(null); const [filesystemPromise, setFilesystemPromise] = useState | null>(null); const [, forceUpdate] = useReducer((x) => x + 1, 0); - if (!filesystemPromise) { + async function resetFilesys() { + const fs = await initFilesys(); + await fs.delete(); + + setFilesystem(null); + setFilesystemPromise(null); + setDrawerOpen(false); + } + + function initFilesys() { + if (filesystemPromise) { + return filesystemPromise; + } + const promise = new Promise((resolve) => { + logLine("Loading filesystem..."); setupFilesystem((current, total, file) => { logLine(`Processing filesystem (${current}/${total}): ${file}`); }).then(resolve); }); promise.then(setFilesystem); - setFilesystemPromise(promise); + + return promise; + } + + async function start() { + await initFilesys(); + setDrawerOpen(true); } function logLine(line: string) { @@ -128,6 +98,8 @@ export function Playground() { emulator?.stop(); output.current?.clear(); + setDrawerOpen(false); + logLine("Starting emulation..."); if (filesystemPromise) { @@ -141,11 +113,6 @@ export function Playground() { new_emulator.start(settings, userFile); } - async function loadAndRunUserFile() { - //const fileBuffer = await selectAndReadFile(); - //await createEmulator(fileBuffer); - } - return ( <>
- - - - - - Run Application - - - createEmulator("c:/test-sample.exe")} - > - - Select Sample - - loadAndRunUserFile()}> - - Select your .exe - - - - + - + @@ -238,6 +180,8 @@ export function Playground() { diff --git a/page/src/components/folder.tsx b/page/src/components/folder.tsx index a32ea608..3c92d8ed 100644 --- a/page/src/components/folder.tsx +++ b/page/src/components/folder.tsx @@ -14,10 +14,10 @@ import { ContextMenuSeparator, ContextMenuLabel, } from "@/components/ui/context-menu"; + import { Tooltip, TooltipContent, - TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; @@ -35,6 +35,7 @@ type ClickHandler = (element: FolderElement) => void; type CreateFolderHandler = () => void; type RemoveElementHandler = (element: FolderElement) => void; type RenameElementHandler = (element: FolderElement) => void; +type AddFilesHandler = () => void; export interface FolderProps { elements: FolderElement[]; @@ -42,6 +43,7 @@ export interface FolderProps { createFolderHandler: CreateFolderHandler; removeElementHandler: RemoveElementHandler; renameElementHandler: RenameElementHandler; + addFilesHandler: AddFilesHandler; } function elementComparator(e1: FolderElement, e2: FolderElement) { @@ -100,7 +102,7 @@ function renderElementWithContext(element: FolderElement, props: FolderProps) { return ( - + {renderElement(element, props.clickHandler)} @@ -131,31 +133,26 @@ function renderElementWrapper(element: FolderElement, props: FolderProps) { ); } -function renderFolderCreator(createFolderHandler: CreateFolderHandler) { - return ( - - Create new Folder - - ); -} - export function Folder(props: FolderProps) { return ( - - - - -
- {props.elements - .sort(elementComparator) - .map((e) => renderElementWrapper(e, props))} -
-
- - {renderFolderCreator(props.createFolderHandler)} - -
-
-
+ + + +
+ {props.elements + .sort(elementComparator) + .map((e) => renderElementWrapper(e, props))} +
+
+
+ + + Create new Folder + + + Add Files + + +
); } diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index 45d2c3c8..34302912 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -43,6 +43,24 @@ export interface FileWithData { data: ArrayBuffer; } +function deleteDatabase(dbName: string) { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(dbName); + + request.onsuccess = () => { + resolve(); + }; + + request.onerror = () => { + reject(new Error(`Error deleting database ${dbName}.`)); + }; + + request.onblocked = () => { + reject(new Error(`Deletion of database ${dbName} blocked.`)); + }; + }); +} + export class Filesystem { private idbfs: MainModule; @@ -117,6 +135,10 @@ export class Filesystem { isFolder(file: string) { return (this.stat(file).mode & 0x4000) != 0; } + + delete() { + return deleteDatabase("/root"); + } } export async function setupFilesystem(progressHandler: ProgressHandler) { From 3128ed5c42978c14fe98333702b28b9309805ec0 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 30 Apr 2025 12:22:41 +0200 Subject: [PATCH 07/10] Finish file explorer --- page/src/FilesystemExplorer.tsx | 67 ++++++++++++++++++++++++++++++--- page/src/Playground.tsx | 14 ++++--- page/src/filesystem.ts | 22 +++++++++-- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/page/src/FilesystemExplorer.tsx b/page/src/FilesystemExplorer.tsx index 9255fbc9..4c8e8a9a 100644 --- a/page/src/FilesystemExplorer.tsx +++ b/page/src/FilesystemExplorer.tsx @@ -35,6 +35,7 @@ export interface FilesystemExplorerProps { export interface FilesystemExplorerState { path: string[]; createFolder: boolean; + resetFilesys: boolean; errorText: string; removeFile: string; renameFile: string; @@ -191,6 +192,7 @@ export class FilesystemExplorer extends React.Component< this.state = { path: this.props.path, createFolder: false, + resetFilesys: false, errorText: "", removeFile: "", renameFile: "", @@ -440,14 +442,56 @@ export class FilesystemExplorer extends React.Component< ); } + _renderResetDialog() { + return ( + this.setState({ resetFilesys: open })} + > + + + Reset filesystem + + Reset filesystem + + +
+ Are you sure you want to reset the filesystem? +
+ + + + +
+
+ ); + } + _renderBreadcrumbElements() { const elements = generateBreadcrumbElements(this.state.path); return elements.map((e, index) => { if (index == this.state.path.length) { return ( - - {e.node} + + + {e.node} + ); } @@ -455,8 +499,10 @@ export class FilesystemExplorer extends React.Component< const navigate = () => this.setState({ path: e.targetPath }); return ( <> - - {e.node} + + + {e.node} + @@ -481,8 +527,19 @@ export class FilesystemExplorer extends React.Component< {this._renderRenameDialog()} {this._renderErrorDialog()} {this._renderRemoveDialog()} + {this._renderResetDialog()} - {this._renderBreadCrumb()} +
+
{this._renderBreadCrumb()}
+
+ +
+
{({ getRootProps, getInputProps }) => ( diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index ab15d6ba..92df9544 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -46,9 +46,11 @@ export function Playground() { const fs = await initFilesys(); await fs.delete(); - setFilesystem(null); setFilesystemPromise(null); + setFilesystem(null); setDrawerOpen(false); + + output.current?.clear(); } function initFilesys() { @@ -122,7 +124,7 @@ export function Playground() {
@@ -155,7 +157,7 @@ export function Playground() { diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index 34302912..357e3f31 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -61,6 +61,10 @@ function deleteDatabase(dbName: string) { }); } +function filterPseudoDir(e: string) { + return e != "." && e != ".."; +} + export class Filesystem { private idbfs: MainModule; @@ -93,7 +97,7 @@ export class Filesystem { } this.readDir(element) // - .filter((e) => e != "." && e != "..") + .filter(filterPseudoDir) .forEach((e) => { this._unlinkRecursive(`${element}/${e}`); }); @@ -136,8 +140,20 @@ export class Filesystem { return (this.stat(file).mode & 0x4000) != 0; } - delete() { - return deleteDatabase("/root"); + async delete() { + this.readDir("/root") // + .filter(filterPseudoDir) // + .forEach((e) => { + try { + this._unlinkRecursive(e); + } catch (_) {} + }); + + await this.sync(); + + try { + await deleteDatabase("/root"); + } catch (e) {} } } From 8b69f8c93c5302dd86c4a37d5438f4873fbe1f32 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 30 Apr 2025 13:07:40 +0200 Subject: [PATCH 08/10] Small refactoring --- cmake/misc/node-pre-script.js | 6 +- page/src/App.tsx | 4 +- page/src/Playground.tsx | 318 ++++++++++-------- page/src/components/settings-menu.tsx | 6 +- ...emExplorer.tsx => filesystem-explorer.tsx} | 22 +- .../src/{LandingPage.tsx => landing-page.tsx} | 0 6 files changed, 207 insertions(+), 149 deletions(-) rename page/src/{FilesystemExplorer.tsx => filesystem-explorer.tsx} (97%) rename page/src/{LandingPage.tsx => landing-page.tsx} (100%) diff --git a/cmake/misc/node-pre-script.js b/cmake/misc/node-pre-script.js index ee8eb4ee..de622d94 100644 --- a/cmake/misc/node-pre-script.js +++ b/cmake/misc/node-pre-script.js @@ -1,3 +1,3 @@ -Module['preRun'] = () => { - ENV = process.env; -}; +Module["preRun"] = () => { + ENV = process.env; +}; diff --git a/page/src/App.tsx b/page/src/App.tsx index e71e2107..ce332ae1 100644 --- a/page/src/App.tsx +++ b/page/src/App.tsx @@ -1,8 +1,8 @@ import { ThemeProvider } from "@/components/theme-provider"; import { TooltipProvider } from "@/components/ui/tooltip"; import { HashRouter, Route, Routes, Navigate } from "react-router-dom"; -import { Playground } from "./Playground"; -import { LandingPage } from "./LandingPage"; +import { Playground } from "./playground"; +import { LandingPage } from "./landing-page"; import "@fontsource/inter/100.css"; import "@fontsource/inter/200.css"; diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index 92df9544..80f5320e 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -1,7 +1,6 @@ -import { useState, useRef, useReducer } from "react"; -import { Output } from "@/components/output"; +import React from "react"; -import { Separator } from "@/components/ui/separator"; +import { Output } from "@/components/output"; import { Emulator, EmulationState } from "./emulator"; import { Filesystem, setupFilesystem } from "./filesystem"; @@ -13,7 +12,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; -import { createDefaultSettings } from "./settings"; +import { createDefaultSettings, Settings } from "./settings"; import { SettingsMenu } from "@/components/settings-menu"; import { PlayFill, StopFill, GearFill, PauseFill } from "react-bootstrap-icons"; @@ -30,176 +29,229 @@ import { DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; -import { FilesystemExplorer } from "./FilesystemExplorer"; +import { FilesystemExplorer } from "./filesystem-explorer"; -export function Playground() { - const output = useRef(null); - const [settings, setSettings] = useState(createDefaultSettings()); - const [emulator, setEmulator] = useState(null); - const [drawerOpen, setDrawerOpen] = useState(false); - const [filesystem, setFilesystem] = useState(null); - const [filesystemPromise, setFilesystemPromise] = - useState | null>(null); - const [, forceUpdate] = useReducer((x) => x + 1, 0); +interface PlaygroundProps {} +interface PlaygroundState { + settings: Settings; + filesystemPromise: Promise | null; + filesystem: Filesystem | null; + emulator: Emulator | null; + drawerOpen: boolean; +} - async function resetFilesys() { - const fs = await initFilesys(); - await fs.delete(); +export class Playground extends React.Component< + PlaygroundProps, + PlaygroundState +> { + private output: React.RefObject; - setFilesystemPromise(null); - setFilesystem(null); - setDrawerOpen(false); + constructor(props: PlaygroundProps) { + super(props); - output.current?.clear(); + this.output = React.createRef(); + + this.start = this.start.bind(this); + this.resetFilesys = this.resetFilesys.bind(this); + this.createEmulator = this.createEmulator.bind(this); + this.toggleEmulatorState = this.toggleEmulatorState.bind(this); + + this.state = { + settings: createDefaultSettings(), + filesystemPromise: null, + filesystem: null, + emulator: null, + drawerOpen: false, + }; } - function initFilesys() { - if (filesystemPromise) { - return filesystemPromise; + async resetFilesys() { + if (!this.state.filesystem) { + return; + } + + await this.state.filesystem.delete(); + + this.setState({ + filesystemPromise: null, + filesystem: null, + drawerOpen: false, + }); + + this.output.current?.clear(); + } + + initFilesys() { + if (this.state.filesystemPromise) { + return this.state.filesystemPromise; } const promise = new Promise((resolve) => { - logLine("Loading filesystem..."); + this.logLine("Loading filesystem..."); setupFilesystem((current, total, file) => { - logLine(`Processing filesystem (${current}/${total}): ${file}`); + this.logLine(`Processing filesystem (${current}/${total}): ${file}`); }).then(resolve); }); - promise.then(setFilesystem); - setFilesystemPromise(promise); + promise.then((filesystem) => this.setState({ filesystem })); + this.setState({ filesystemPromise: promise }); return promise; } - async function start() { - await initFilesys(); - setDrawerOpen(true); + setDrawerOpen(drawerOpen: boolean) { + this.setState({ drawerOpen }); } - function logLine(line: string) { - output.current?.logLine(line); + async start() { + await this.initFilesys(); + this.setDrawerOpen(true); } - function logLines(lines: string[]) { - output.current?.logLines(lines); + logLine(line: string) { + this.output.current?.logLine(line); } - function isEmulatorPaused() { - return emulator && emulator.getState() == EmulationState.Paused; + logLines(lines: string[]) { + this.output.current?.logLines(lines); } - function toggleEmulatorState() { - if (isEmulatorPaused()) { - emulator?.resume(); + isEmulatorPaused() { + return ( + this.state.emulator && + this.state.emulator.getState() == EmulationState.Paused + ); + } + + toggleEmulatorState() { + if (this.isEmulatorPaused()) { + this.state.emulator?.resume(); } else { - emulator?.pause(); + this.state.emulator?.pause(); } } - async function createEmulator(userFile: string) { - emulator?.stop(); - output.current?.clear(); + async createEmulator(userFile: string) { + this.state.emulator?.stop(); + this.output.current?.clear(); - setDrawerOpen(false); + this.setDrawerOpen(false); - logLine("Starting emulation..."); + this.logLine("Starting emulation..."); - if (filesystemPromise) { - await filesystemPromise; + if (this.state.filesystemPromise) { + await this.state.filesystemPromise; } - const new_emulator = new Emulator(logLines, (_) => forceUpdate()); - new_emulator.onTerminate().then(() => setEmulator(null)); - setEmulator(new_emulator); + const new_emulator = new Emulator( + (l) => this.logLines(l), + (_) => this.forceUpdate(), + ); + new_emulator.onTerminate().then(() => this.setState({ emulator: null })); - new_emulator.start(settings, userFile); + this.setState({ emulator: new_emulator }); + + new_emulator.start(this.state.settings, userFile); } - return ( - <> -
-
-
- + render() { + return ( + <> +
+
+
+ - - + + + + + + + + this.setState({ settings: s })} + /> + + + + {!this.state.filesystem ? ( + <> ) : ( - <> - Pause - + this.setState({ drawerOpen: o })} + > + + + + Filesystem Explorer + + + Filesystem Explorer + + + + + + + )} - - - - - - - - - - - {!filesystem ? ( - <> - ) : ( - - - - - Filesystem Explorer - - - Filesystem Explorer - - - - - - - - )} - -
- +
+ +
+
+
+
-
-
-
-
- - ); + + ); + } } diff --git a/page/src/components/settings-menu.tsx b/page/src/components/settings-menu.tsx index aa80d528..06a0dac4 100644 --- a/page/src/components/settings-menu.tsx +++ b/page/src/components/settings-menu.tsx @@ -24,8 +24,10 @@ export class SettingsMenu extends React.Component { this.setState(() => settings); } - componentDidUpdate() { - this.props.onChange(this.state); + componentDidUpdate(_: SettingsMenuProps, oldSettings: Settings) { + if (JSON.stringify(oldSettings) !== JSON.stringify(this.state)) { + this.props.onChange(this.state); + } } render() { diff --git a/page/src/FilesystemExplorer.tsx b/page/src/filesystem-explorer.tsx similarity index 97% rename from page/src/FilesystemExplorer.tsx rename to page/src/filesystem-explorer.tsx index 4c8e8a9a..b3d24df9 100644 --- a/page/src/FilesystemExplorer.tsx +++ b/page/src/filesystem-explorer.tsx @@ -485,7 +485,7 @@ export class FilesystemExplorer extends React.Component< _renderBreadcrumbElements() { const elements = generateBreadcrumbElements(this.state.path); - return elements.map((e, index) => { + const nodes = elements.map((e, index) => { if (index == this.state.path.length) { return ( @@ -498,16 +498,20 @@ export class FilesystemExplorer extends React.Component< const navigate = () => this.setState({ path: e.targetPath }); return ( - <> - - - {e.node} - - - - + + + {e.node} + + ); }); + + return [ + ...nodes.map((n, index) => [ + n, + , + ]), + ].slice(0, -1); } _renderBreadCrumb() { diff --git a/page/src/LandingPage.tsx b/page/src/landing-page.tsx similarity index 100% rename from page/src/LandingPage.tsx rename to page/src/landing-page.tsx From 737d1cb0f78e01142804ce60258143f22f2f0e0d Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 30 Apr 2025 13:22:08 +0200 Subject: [PATCH 09/10] Fix rename pt 1 --- page/{src => src_}/App.css | 0 page/{src => src_}/App.tsx | 0 page/{src => src_}/Header.tsx | 0 page/{src => src_}/components/app-sidebar.tsx | 0 page/{src => src_}/components/folder.tsx | 0 page/{src => src_}/components/output.tsx | 0 page/{src => src_}/components/settings-menu.tsx | 0 page/{src => src_}/components/status-indicator.tsx | 0 page/{src => src_}/components/theme-provider.tsx | 0 page/{src => src_}/components/ui/badge.tsx | 0 page/{src => src_}/components/ui/breadcrumb.tsx | 0 page/{src => src_}/components/ui/button.tsx | 0 page/{src => src_}/components/ui/card.tsx | 0 page/{src => src_}/components/ui/checkbox.tsx | 0 page/{src => src_}/components/ui/context-menu.tsx | 0 page/{src => src_}/components/ui/dialog.tsx | 0 page/{src => src_}/components/ui/drawer.tsx | 0 page/{src => src_}/components/ui/dropdown-menu.tsx | 0 page/{src => src_}/components/ui/input.tsx | 0 page/{src => src_}/components/ui/label.tsx | 0 page/{src => src_}/components/ui/popover.tsx | 0 page/{src => src_}/components/ui/resizable.tsx | 0 page/{src => src_}/components/ui/scroll-area.tsx | 0 page/{src => src_}/components/ui/separator.tsx | 0 page/{src => src_}/components/ui/sheet.tsx | 0 page/{src => src_}/components/ui/sidebar.tsx | 0 page/{src => src_}/components/ui/skeleton.tsx | 0 page/{src => src_}/components/ui/tabs.tsx | 0 page/{src => src_}/components/ui/tooltip.tsx | 0 page/{src => src_}/emulator.ts | 0 page/{src => src_}/fb/debugger.ts | 0 page/{src => src_}/fb/debugger/debug-event.ts | 0 page/{src => src_}/fb/debugger/event.ts | 0 page/{src => src_}/fb/debugger/get-state-request.ts | 0 page/{src => src_}/fb/debugger/get-state-response.ts | 0 page/{src => src_}/fb/debugger/pause-request.ts | 0 page/{src => src_}/fb/debugger/read-memory-request.ts | 0 page/{src => src_}/fb/debugger/read-memory-response.ts | 0 page/{src => src_}/fb/debugger/read-register-request.ts | 0 page/{src => src_}/fb/debugger/read-register-response.ts | 0 page/{src => src_}/fb/debugger/run-request.ts | 0 page/{src => src_}/fb/debugger/state.ts | 0 page/{src => src_}/fb/debugger/write-memory-request.ts | 0 page/{src => src_}/fb/debugger/write-memory-response.ts | 0 page/{src => src_}/fb/debugger/write-register-request.ts | 0 page/{src => src_}/fb/debugger/write-register-response.ts | 0 page/{src => src_}/fb/events.ts | 0 page/{src => src_}/filesystem-explorer.tsx | 0 page/{src => src_}/filesystem.ts | 0 page/{src => src_}/hooks/use-mobile.ts | 0 page/{src => src_}/index.css | 0 page/{src => src_}/landing-page.tsx | 0 page/{src => src_}/lib/utils.ts | 0 page/{src => src_}/main.tsx | 0 page/{src/Playground.tsx => src_/playground.tsx} | 0 page/{src => src_}/settings.ts | 0 page/{src => src_}/vite-env.d.ts | 0 page/{src => src_}/zip-file.ts | 0 58 files changed, 0 insertions(+), 0 deletions(-) rename page/{src => src_}/App.css (100%) rename page/{src => src_}/App.tsx (100%) rename page/{src => src_}/Header.tsx (100%) rename page/{src => src_}/components/app-sidebar.tsx (100%) rename page/{src => src_}/components/folder.tsx (100%) rename page/{src => src_}/components/output.tsx (100%) rename page/{src => src_}/components/settings-menu.tsx (100%) rename page/{src => src_}/components/status-indicator.tsx (100%) rename page/{src => src_}/components/theme-provider.tsx (100%) rename page/{src => src_}/components/ui/badge.tsx (100%) rename page/{src => src_}/components/ui/breadcrumb.tsx (100%) rename page/{src => src_}/components/ui/button.tsx (100%) rename page/{src => src_}/components/ui/card.tsx (100%) rename page/{src => src_}/components/ui/checkbox.tsx (100%) rename page/{src => src_}/components/ui/context-menu.tsx (100%) rename page/{src => src_}/components/ui/dialog.tsx (100%) rename page/{src => src_}/components/ui/drawer.tsx (100%) rename page/{src => src_}/components/ui/dropdown-menu.tsx (100%) rename page/{src => src_}/components/ui/input.tsx (100%) rename page/{src => src_}/components/ui/label.tsx (100%) rename page/{src => src_}/components/ui/popover.tsx (100%) rename page/{src => src_}/components/ui/resizable.tsx (100%) rename page/{src => src_}/components/ui/scroll-area.tsx (100%) rename page/{src => src_}/components/ui/separator.tsx (100%) rename page/{src => src_}/components/ui/sheet.tsx (100%) rename page/{src => src_}/components/ui/sidebar.tsx (100%) rename page/{src => src_}/components/ui/skeleton.tsx (100%) rename page/{src => src_}/components/ui/tabs.tsx (100%) rename page/{src => src_}/components/ui/tooltip.tsx (100%) rename page/{src => src_}/emulator.ts (100%) rename page/{src => src_}/fb/debugger.ts (100%) rename page/{src => src_}/fb/debugger/debug-event.ts (100%) rename page/{src => src_}/fb/debugger/event.ts (100%) rename page/{src => src_}/fb/debugger/get-state-request.ts (100%) rename page/{src => src_}/fb/debugger/get-state-response.ts (100%) rename page/{src => src_}/fb/debugger/pause-request.ts (100%) rename page/{src => src_}/fb/debugger/read-memory-request.ts (100%) rename page/{src => src_}/fb/debugger/read-memory-response.ts (100%) rename page/{src => src_}/fb/debugger/read-register-request.ts (100%) rename page/{src => src_}/fb/debugger/read-register-response.ts (100%) rename page/{src => src_}/fb/debugger/run-request.ts (100%) rename page/{src => src_}/fb/debugger/state.ts (100%) rename page/{src => src_}/fb/debugger/write-memory-request.ts (100%) rename page/{src => src_}/fb/debugger/write-memory-response.ts (100%) rename page/{src => src_}/fb/debugger/write-register-request.ts (100%) rename page/{src => src_}/fb/debugger/write-register-response.ts (100%) rename page/{src => src_}/fb/events.ts (100%) rename page/{src => src_}/filesystem-explorer.tsx (100%) rename page/{src => src_}/filesystem.ts (100%) rename page/{src => src_}/hooks/use-mobile.ts (100%) rename page/{src => src_}/index.css (100%) rename page/{src => src_}/landing-page.tsx (100%) rename page/{src => src_}/lib/utils.ts (100%) rename page/{src => src_}/main.tsx (100%) rename page/{src/Playground.tsx => src_/playground.tsx} (100%) rename page/{src => src_}/settings.ts (100%) rename page/{src => src_}/vite-env.d.ts (100%) rename page/{src => src_}/zip-file.ts (100%) diff --git a/page/src/App.css b/page/src_/App.css similarity index 100% rename from page/src/App.css rename to page/src_/App.css diff --git a/page/src/App.tsx b/page/src_/App.tsx similarity index 100% rename from page/src/App.tsx rename to page/src_/App.tsx diff --git a/page/src/Header.tsx b/page/src_/Header.tsx similarity index 100% rename from page/src/Header.tsx rename to page/src_/Header.tsx diff --git a/page/src/components/app-sidebar.tsx b/page/src_/components/app-sidebar.tsx similarity index 100% rename from page/src/components/app-sidebar.tsx rename to page/src_/components/app-sidebar.tsx diff --git a/page/src/components/folder.tsx b/page/src_/components/folder.tsx similarity index 100% rename from page/src/components/folder.tsx rename to page/src_/components/folder.tsx diff --git a/page/src/components/output.tsx b/page/src_/components/output.tsx similarity index 100% rename from page/src/components/output.tsx rename to page/src_/components/output.tsx diff --git a/page/src/components/settings-menu.tsx b/page/src_/components/settings-menu.tsx similarity index 100% rename from page/src/components/settings-menu.tsx rename to page/src_/components/settings-menu.tsx diff --git a/page/src/components/status-indicator.tsx b/page/src_/components/status-indicator.tsx similarity index 100% rename from page/src/components/status-indicator.tsx rename to page/src_/components/status-indicator.tsx diff --git a/page/src/components/theme-provider.tsx b/page/src_/components/theme-provider.tsx similarity index 100% rename from page/src/components/theme-provider.tsx rename to page/src_/components/theme-provider.tsx diff --git a/page/src/components/ui/badge.tsx b/page/src_/components/ui/badge.tsx similarity index 100% rename from page/src/components/ui/badge.tsx rename to page/src_/components/ui/badge.tsx diff --git a/page/src/components/ui/breadcrumb.tsx b/page/src_/components/ui/breadcrumb.tsx similarity index 100% rename from page/src/components/ui/breadcrumb.tsx rename to page/src_/components/ui/breadcrumb.tsx diff --git a/page/src/components/ui/button.tsx b/page/src_/components/ui/button.tsx similarity index 100% rename from page/src/components/ui/button.tsx rename to page/src_/components/ui/button.tsx diff --git a/page/src/components/ui/card.tsx b/page/src_/components/ui/card.tsx similarity index 100% rename from page/src/components/ui/card.tsx rename to page/src_/components/ui/card.tsx diff --git a/page/src/components/ui/checkbox.tsx b/page/src_/components/ui/checkbox.tsx similarity index 100% rename from page/src/components/ui/checkbox.tsx rename to page/src_/components/ui/checkbox.tsx diff --git a/page/src/components/ui/context-menu.tsx b/page/src_/components/ui/context-menu.tsx similarity index 100% rename from page/src/components/ui/context-menu.tsx rename to page/src_/components/ui/context-menu.tsx diff --git a/page/src/components/ui/dialog.tsx b/page/src_/components/ui/dialog.tsx similarity index 100% rename from page/src/components/ui/dialog.tsx rename to page/src_/components/ui/dialog.tsx diff --git a/page/src/components/ui/drawer.tsx b/page/src_/components/ui/drawer.tsx similarity index 100% rename from page/src/components/ui/drawer.tsx rename to page/src_/components/ui/drawer.tsx diff --git a/page/src/components/ui/dropdown-menu.tsx b/page/src_/components/ui/dropdown-menu.tsx similarity index 100% rename from page/src/components/ui/dropdown-menu.tsx rename to page/src_/components/ui/dropdown-menu.tsx diff --git a/page/src/components/ui/input.tsx b/page/src_/components/ui/input.tsx similarity index 100% rename from page/src/components/ui/input.tsx rename to page/src_/components/ui/input.tsx diff --git a/page/src/components/ui/label.tsx b/page/src_/components/ui/label.tsx similarity index 100% rename from page/src/components/ui/label.tsx rename to page/src_/components/ui/label.tsx diff --git a/page/src/components/ui/popover.tsx b/page/src_/components/ui/popover.tsx similarity index 100% rename from page/src/components/ui/popover.tsx rename to page/src_/components/ui/popover.tsx diff --git a/page/src/components/ui/resizable.tsx b/page/src_/components/ui/resizable.tsx similarity index 100% rename from page/src/components/ui/resizable.tsx rename to page/src_/components/ui/resizable.tsx diff --git a/page/src/components/ui/scroll-area.tsx b/page/src_/components/ui/scroll-area.tsx similarity index 100% rename from page/src/components/ui/scroll-area.tsx rename to page/src_/components/ui/scroll-area.tsx diff --git a/page/src/components/ui/separator.tsx b/page/src_/components/ui/separator.tsx similarity index 100% rename from page/src/components/ui/separator.tsx rename to page/src_/components/ui/separator.tsx diff --git a/page/src/components/ui/sheet.tsx b/page/src_/components/ui/sheet.tsx similarity index 100% rename from page/src/components/ui/sheet.tsx rename to page/src_/components/ui/sheet.tsx diff --git a/page/src/components/ui/sidebar.tsx b/page/src_/components/ui/sidebar.tsx similarity index 100% rename from page/src/components/ui/sidebar.tsx rename to page/src_/components/ui/sidebar.tsx diff --git a/page/src/components/ui/skeleton.tsx b/page/src_/components/ui/skeleton.tsx similarity index 100% rename from page/src/components/ui/skeleton.tsx rename to page/src_/components/ui/skeleton.tsx diff --git a/page/src/components/ui/tabs.tsx b/page/src_/components/ui/tabs.tsx similarity index 100% rename from page/src/components/ui/tabs.tsx rename to page/src_/components/ui/tabs.tsx diff --git a/page/src/components/ui/tooltip.tsx b/page/src_/components/ui/tooltip.tsx similarity index 100% rename from page/src/components/ui/tooltip.tsx rename to page/src_/components/ui/tooltip.tsx diff --git a/page/src/emulator.ts b/page/src_/emulator.ts similarity index 100% rename from page/src/emulator.ts rename to page/src_/emulator.ts diff --git a/page/src/fb/debugger.ts b/page/src_/fb/debugger.ts similarity index 100% rename from page/src/fb/debugger.ts rename to page/src_/fb/debugger.ts diff --git a/page/src/fb/debugger/debug-event.ts b/page/src_/fb/debugger/debug-event.ts similarity index 100% rename from page/src/fb/debugger/debug-event.ts rename to page/src_/fb/debugger/debug-event.ts diff --git a/page/src/fb/debugger/event.ts b/page/src_/fb/debugger/event.ts similarity index 100% rename from page/src/fb/debugger/event.ts rename to page/src_/fb/debugger/event.ts diff --git a/page/src/fb/debugger/get-state-request.ts b/page/src_/fb/debugger/get-state-request.ts similarity index 100% rename from page/src/fb/debugger/get-state-request.ts rename to page/src_/fb/debugger/get-state-request.ts diff --git a/page/src/fb/debugger/get-state-response.ts b/page/src_/fb/debugger/get-state-response.ts similarity index 100% rename from page/src/fb/debugger/get-state-response.ts rename to page/src_/fb/debugger/get-state-response.ts diff --git a/page/src/fb/debugger/pause-request.ts b/page/src_/fb/debugger/pause-request.ts similarity index 100% rename from page/src/fb/debugger/pause-request.ts rename to page/src_/fb/debugger/pause-request.ts diff --git a/page/src/fb/debugger/read-memory-request.ts b/page/src_/fb/debugger/read-memory-request.ts similarity index 100% rename from page/src/fb/debugger/read-memory-request.ts rename to page/src_/fb/debugger/read-memory-request.ts diff --git a/page/src/fb/debugger/read-memory-response.ts b/page/src_/fb/debugger/read-memory-response.ts similarity index 100% rename from page/src/fb/debugger/read-memory-response.ts rename to page/src_/fb/debugger/read-memory-response.ts diff --git a/page/src/fb/debugger/read-register-request.ts b/page/src_/fb/debugger/read-register-request.ts similarity index 100% rename from page/src/fb/debugger/read-register-request.ts rename to page/src_/fb/debugger/read-register-request.ts diff --git a/page/src/fb/debugger/read-register-response.ts b/page/src_/fb/debugger/read-register-response.ts similarity index 100% rename from page/src/fb/debugger/read-register-response.ts rename to page/src_/fb/debugger/read-register-response.ts diff --git a/page/src/fb/debugger/run-request.ts b/page/src_/fb/debugger/run-request.ts similarity index 100% rename from page/src/fb/debugger/run-request.ts rename to page/src_/fb/debugger/run-request.ts diff --git a/page/src/fb/debugger/state.ts b/page/src_/fb/debugger/state.ts similarity index 100% rename from page/src/fb/debugger/state.ts rename to page/src_/fb/debugger/state.ts diff --git a/page/src/fb/debugger/write-memory-request.ts b/page/src_/fb/debugger/write-memory-request.ts similarity index 100% rename from page/src/fb/debugger/write-memory-request.ts rename to page/src_/fb/debugger/write-memory-request.ts diff --git a/page/src/fb/debugger/write-memory-response.ts b/page/src_/fb/debugger/write-memory-response.ts similarity index 100% rename from page/src/fb/debugger/write-memory-response.ts rename to page/src_/fb/debugger/write-memory-response.ts diff --git a/page/src/fb/debugger/write-register-request.ts b/page/src_/fb/debugger/write-register-request.ts similarity index 100% rename from page/src/fb/debugger/write-register-request.ts rename to page/src_/fb/debugger/write-register-request.ts diff --git a/page/src/fb/debugger/write-register-response.ts b/page/src_/fb/debugger/write-register-response.ts similarity index 100% rename from page/src/fb/debugger/write-register-response.ts rename to page/src_/fb/debugger/write-register-response.ts diff --git a/page/src/fb/events.ts b/page/src_/fb/events.ts similarity index 100% rename from page/src/fb/events.ts rename to page/src_/fb/events.ts diff --git a/page/src/filesystem-explorer.tsx b/page/src_/filesystem-explorer.tsx similarity index 100% rename from page/src/filesystem-explorer.tsx rename to page/src_/filesystem-explorer.tsx diff --git a/page/src/filesystem.ts b/page/src_/filesystem.ts similarity index 100% rename from page/src/filesystem.ts rename to page/src_/filesystem.ts diff --git a/page/src/hooks/use-mobile.ts b/page/src_/hooks/use-mobile.ts similarity index 100% rename from page/src/hooks/use-mobile.ts rename to page/src_/hooks/use-mobile.ts diff --git a/page/src/index.css b/page/src_/index.css similarity index 100% rename from page/src/index.css rename to page/src_/index.css diff --git a/page/src/landing-page.tsx b/page/src_/landing-page.tsx similarity index 100% rename from page/src/landing-page.tsx rename to page/src_/landing-page.tsx diff --git a/page/src/lib/utils.ts b/page/src_/lib/utils.ts similarity index 100% rename from page/src/lib/utils.ts rename to page/src_/lib/utils.ts diff --git a/page/src/main.tsx b/page/src_/main.tsx similarity index 100% rename from page/src/main.tsx rename to page/src_/main.tsx diff --git a/page/src/Playground.tsx b/page/src_/playground.tsx similarity index 100% rename from page/src/Playground.tsx rename to page/src_/playground.tsx diff --git a/page/src/settings.ts b/page/src_/settings.ts similarity index 100% rename from page/src/settings.ts rename to page/src_/settings.ts diff --git a/page/src/vite-env.d.ts b/page/src_/vite-env.d.ts similarity index 100% rename from page/src/vite-env.d.ts rename to page/src_/vite-env.d.ts diff --git a/page/src/zip-file.ts b/page/src_/zip-file.ts similarity index 100% rename from page/src/zip-file.ts rename to page/src_/zip-file.ts From f67710a31eef582f1ed7bebced229421731e4bc5 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 30 Apr 2025 13:22:25 +0200 Subject: [PATCH 10/10] Fix rename pt 2 --- page/{src_ => src}/App.css | 0 page/{src_ => src}/App.tsx | 0 page/{src_ => src}/Header.tsx | 0 page/{src_ => src}/components/app-sidebar.tsx | 0 page/{src_ => src}/components/folder.tsx | 0 page/{src_ => src}/components/output.tsx | 0 page/{src_ => src}/components/settings-menu.tsx | 0 page/{src_ => src}/components/status-indicator.tsx | 0 page/{src_ => src}/components/theme-provider.tsx | 0 page/{src_ => src}/components/ui/badge.tsx | 0 page/{src_ => src}/components/ui/breadcrumb.tsx | 0 page/{src_ => src}/components/ui/button.tsx | 0 page/{src_ => src}/components/ui/card.tsx | 0 page/{src_ => src}/components/ui/checkbox.tsx | 0 page/{src_ => src}/components/ui/context-menu.tsx | 0 page/{src_ => src}/components/ui/dialog.tsx | 0 page/{src_ => src}/components/ui/drawer.tsx | 0 page/{src_ => src}/components/ui/dropdown-menu.tsx | 0 page/{src_ => src}/components/ui/input.tsx | 0 page/{src_ => src}/components/ui/label.tsx | 0 page/{src_ => src}/components/ui/popover.tsx | 0 page/{src_ => src}/components/ui/resizable.tsx | 0 page/{src_ => src}/components/ui/scroll-area.tsx | 0 page/{src_ => src}/components/ui/separator.tsx | 0 page/{src_ => src}/components/ui/sheet.tsx | 0 page/{src_ => src}/components/ui/sidebar.tsx | 0 page/{src_ => src}/components/ui/skeleton.tsx | 0 page/{src_ => src}/components/ui/tabs.tsx | 0 page/{src_ => src}/components/ui/tooltip.tsx | 0 page/{src_ => src}/emulator.ts | 0 page/{src_ => src}/fb/debugger.ts | 0 page/{src_ => src}/fb/debugger/debug-event.ts | 0 page/{src_ => src}/fb/debugger/event.ts | 0 page/{src_ => src}/fb/debugger/get-state-request.ts | 0 page/{src_ => src}/fb/debugger/get-state-response.ts | 0 page/{src_ => src}/fb/debugger/pause-request.ts | 0 page/{src_ => src}/fb/debugger/read-memory-request.ts | 0 page/{src_ => src}/fb/debugger/read-memory-response.ts | 0 page/{src_ => src}/fb/debugger/read-register-request.ts | 0 page/{src_ => src}/fb/debugger/read-register-response.ts | 0 page/{src_ => src}/fb/debugger/run-request.ts | 0 page/{src_ => src}/fb/debugger/state.ts | 0 page/{src_ => src}/fb/debugger/write-memory-request.ts | 0 page/{src_ => src}/fb/debugger/write-memory-response.ts | 0 page/{src_ => src}/fb/debugger/write-register-request.ts | 0 page/{src_ => src}/fb/debugger/write-register-response.ts | 0 page/{src_ => src}/fb/events.ts | 0 page/{src_ => src}/filesystem-explorer.tsx | 0 page/{src_ => src}/filesystem.ts | 0 page/{src_ => src}/hooks/use-mobile.ts | 0 page/{src_ => src}/index.css | 0 page/{src_ => src}/landing-page.tsx | 0 page/{src_ => src}/lib/utils.ts | 0 page/{src_ => src}/main.tsx | 0 page/{src_ => src}/playground.tsx | 0 page/{src_ => src}/settings.ts | 0 page/{src_ => src}/vite-env.d.ts | 0 page/{src_ => src}/zip-file.ts | 0 58 files changed, 0 insertions(+), 0 deletions(-) rename page/{src_ => src}/App.css (100%) rename page/{src_ => src}/App.tsx (100%) rename page/{src_ => src}/Header.tsx (100%) rename page/{src_ => src}/components/app-sidebar.tsx (100%) rename page/{src_ => src}/components/folder.tsx (100%) rename page/{src_ => src}/components/output.tsx (100%) rename page/{src_ => src}/components/settings-menu.tsx (100%) rename page/{src_ => src}/components/status-indicator.tsx (100%) rename page/{src_ => src}/components/theme-provider.tsx (100%) rename page/{src_ => src}/components/ui/badge.tsx (100%) rename page/{src_ => src}/components/ui/breadcrumb.tsx (100%) rename page/{src_ => src}/components/ui/button.tsx (100%) rename page/{src_ => src}/components/ui/card.tsx (100%) rename page/{src_ => src}/components/ui/checkbox.tsx (100%) rename page/{src_ => src}/components/ui/context-menu.tsx (100%) rename page/{src_ => src}/components/ui/dialog.tsx (100%) rename page/{src_ => src}/components/ui/drawer.tsx (100%) rename page/{src_ => src}/components/ui/dropdown-menu.tsx (100%) rename page/{src_ => src}/components/ui/input.tsx (100%) rename page/{src_ => src}/components/ui/label.tsx (100%) rename page/{src_ => src}/components/ui/popover.tsx (100%) rename page/{src_ => src}/components/ui/resizable.tsx (100%) rename page/{src_ => src}/components/ui/scroll-area.tsx (100%) rename page/{src_ => src}/components/ui/separator.tsx (100%) rename page/{src_ => src}/components/ui/sheet.tsx (100%) rename page/{src_ => src}/components/ui/sidebar.tsx (100%) rename page/{src_ => src}/components/ui/skeleton.tsx (100%) rename page/{src_ => src}/components/ui/tabs.tsx (100%) rename page/{src_ => src}/components/ui/tooltip.tsx (100%) rename page/{src_ => src}/emulator.ts (100%) rename page/{src_ => src}/fb/debugger.ts (100%) rename page/{src_ => src}/fb/debugger/debug-event.ts (100%) rename page/{src_ => src}/fb/debugger/event.ts (100%) rename page/{src_ => src}/fb/debugger/get-state-request.ts (100%) rename page/{src_ => src}/fb/debugger/get-state-response.ts (100%) rename page/{src_ => src}/fb/debugger/pause-request.ts (100%) rename page/{src_ => src}/fb/debugger/read-memory-request.ts (100%) rename page/{src_ => src}/fb/debugger/read-memory-response.ts (100%) rename page/{src_ => src}/fb/debugger/read-register-request.ts (100%) rename page/{src_ => src}/fb/debugger/read-register-response.ts (100%) rename page/{src_ => src}/fb/debugger/run-request.ts (100%) rename page/{src_ => src}/fb/debugger/state.ts (100%) rename page/{src_ => src}/fb/debugger/write-memory-request.ts (100%) rename page/{src_ => src}/fb/debugger/write-memory-response.ts (100%) rename page/{src_ => src}/fb/debugger/write-register-request.ts (100%) rename page/{src_ => src}/fb/debugger/write-register-response.ts (100%) rename page/{src_ => src}/fb/events.ts (100%) rename page/{src_ => src}/filesystem-explorer.tsx (100%) rename page/{src_ => src}/filesystem.ts (100%) rename page/{src_ => src}/hooks/use-mobile.ts (100%) rename page/{src_ => src}/index.css (100%) rename page/{src_ => src}/landing-page.tsx (100%) rename page/{src_ => src}/lib/utils.ts (100%) rename page/{src_ => src}/main.tsx (100%) rename page/{src_ => src}/playground.tsx (100%) rename page/{src_ => src}/settings.ts (100%) rename page/{src_ => src}/vite-env.d.ts (100%) rename page/{src_ => src}/zip-file.ts (100%) diff --git a/page/src_/App.css b/page/src/App.css similarity index 100% rename from page/src_/App.css rename to page/src/App.css diff --git a/page/src_/App.tsx b/page/src/App.tsx similarity index 100% rename from page/src_/App.tsx rename to page/src/App.tsx diff --git a/page/src_/Header.tsx b/page/src/Header.tsx similarity index 100% rename from page/src_/Header.tsx rename to page/src/Header.tsx diff --git a/page/src_/components/app-sidebar.tsx b/page/src/components/app-sidebar.tsx similarity index 100% rename from page/src_/components/app-sidebar.tsx rename to page/src/components/app-sidebar.tsx diff --git a/page/src_/components/folder.tsx b/page/src/components/folder.tsx similarity index 100% rename from page/src_/components/folder.tsx rename to page/src/components/folder.tsx diff --git a/page/src_/components/output.tsx b/page/src/components/output.tsx similarity index 100% rename from page/src_/components/output.tsx rename to page/src/components/output.tsx diff --git a/page/src_/components/settings-menu.tsx b/page/src/components/settings-menu.tsx similarity index 100% rename from page/src_/components/settings-menu.tsx rename to page/src/components/settings-menu.tsx diff --git a/page/src_/components/status-indicator.tsx b/page/src/components/status-indicator.tsx similarity index 100% rename from page/src_/components/status-indicator.tsx rename to page/src/components/status-indicator.tsx diff --git a/page/src_/components/theme-provider.tsx b/page/src/components/theme-provider.tsx similarity index 100% rename from page/src_/components/theme-provider.tsx rename to page/src/components/theme-provider.tsx diff --git a/page/src_/components/ui/badge.tsx b/page/src/components/ui/badge.tsx similarity index 100% rename from page/src_/components/ui/badge.tsx rename to page/src/components/ui/badge.tsx diff --git a/page/src_/components/ui/breadcrumb.tsx b/page/src/components/ui/breadcrumb.tsx similarity index 100% rename from page/src_/components/ui/breadcrumb.tsx rename to page/src/components/ui/breadcrumb.tsx diff --git a/page/src_/components/ui/button.tsx b/page/src/components/ui/button.tsx similarity index 100% rename from page/src_/components/ui/button.tsx rename to page/src/components/ui/button.tsx diff --git a/page/src_/components/ui/card.tsx b/page/src/components/ui/card.tsx similarity index 100% rename from page/src_/components/ui/card.tsx rename to page/src/components/ui/card.tsx diff --git a/page/src_/components/ui/checkbox.tsx b/page/src/components/ui/checkbox.tsx similarity index 100% rename from page/src_/components/ui/checkbox.tsx rename to page/src/components/ui/checkbox.tsx diff --git a/page/src_/components/ui/context-menu.tsx b/page/src/components/ui/context-menu.tsx similarity index 100% rename from page/src_/components/ui/context-menu.tsx rename to page/src/components/ui/context-menu.tsx diff --git a/page/src_/components/ui/dialog.tsx b/page/src/components/ui/dialog.tsx similarity index 100% rename from page/src_/components/ui/dialog.tsx rename to page/src/components/ui/dialog.tsx diff --git a/page/src_/components/ui/drawer.tsx b/page/src/components/ui/drawer.tsx similarity index 100% rename from page/src_/components/ui/drawer.tsx rename to page/src/components/ui/drawer.tsx diff --git a/page/src_/components/ui/dropdown-menu.tsx b/page/src/components/ui/dropdown-menu.tsx similarity index 100% rename from page/src_/components/ui/dropdown-menu.tsx rename to page/src/components/ui/dropdown-menu.tsx diff --git a/page/src_/components/ui/input.tsx b/page/src/components/ui/input.tsx similarity index 100% rename from page/src_/components/ui/input.tsx rename to page/src/components/ui/input.tsx diff --git a/page/src_/components/ui/label.tsx b/page/src/components/ui/label.tsx similarity index 100% rename from page/src_/components/ui/label.tsx rename to page/src/components/ui/label.tsx diff --git a/page/src_/components/ui/popover.tsx b/page/src/components/ui/popover.tsx similarity index 100% rename from page/src_/components/ui/popover.tsx rename to page/src/components/ui/popover.tsx diff --git a/page/src_/components/ui/resizable.tsx b/page/src/components/ui/resizable.tsx similarity index 100% rename from page/src_/components/ui/resizable.tsx rename to page/src/components/ui/resizable.tsx diff --git a/page/src_/components/ui/scroll-area.tsx b/page/src/components/ui/scroll-area.tsx similarity index 100% rename from page/src_/components/ui/scroll-area.tsx rename to page/src/components/ui/scroll-area.tsx diff --git a/page/src_/components/ui/separator.tsx b/page/src/components/ui/separator.tsx similarity index 100% rename from page/src_/components/ui/separator.tsx rename to page/src/components/ui/separator.tsx diff --git a/page/src_/components/ui/sheet.tsx b/page/src/components/ui/sheet.tsx similarity index 100% rename from page/src_/components/ui/sheet.tsx rename to page/src/components/ui/sheet.tsx diff --git a/page/src_/components/ui/sidebar.tsx b/page/src/components/ui/sidebar.tsx similarity index 100% rename from page/src_/components/ui/sidebar.tsx rename to page/src/components/ui/sidebar.tsx diff --git a/page/src_/components/ui/skeleton.tsx b/page/src/components/ui/skeleton.tsx similarity index 100% rename from page/src_/components/ui/skeleton.tsx rename to page/src/components/ui/skeleton.tsx diff --git a/page/src_/components/ui/tabs.tsx b/page/src/components/ui/tabs.tsx similarity index 100% rename from page/src_/components/ui/tabs.tsx rename to page/src/components/ui/tabs.tsx diff --git a/page/src_/components/ui/tooltip.tsx b/page/src/components/ui/tooltip.tsx similarity index 100% rename from page/src_/components/ui/tooltip.tsx rename to page/src/components/ui/tooltip.tsx diff --git a/page/src_/emulator.ts b/page/src/emulator.ts similarity index 100% rename from page/src_/emulator.ts rename to page/src/emulator.ts diff --git a/page/src_/fb/debugger.ts b/page/src/fb/debugger.ts similarity index 100% rename from page/src_/fb/debugger.ts rename to page/src/fb/debugger.ts diff --git a/page/src_/fb/debugger/debug-event.ts b/page/src/fb/debugger/debug-event.ts similarity index 100% rename from page/src_/fb/debugger/debug-event.ts rename to page/src/fb/debugger/debug-event.ts diff --git a/page/src_/fb/debugger/event.ts b/page/src/fb/debugger/event.ts similarity index 100% rename from page/src_/fb/debugger/event.ts rename to page/src/fb/debugger/event.ts diff --git a/page/src_/fb/debugger/get-state-request.ts b/page/src/fb/debugger/get-state-request.ts similarity index 100% rename from page/src_/fb/debugger/get-state-request.ts rename to page/src/fb/debugger/get-state-request.ts diff --git a/page/src_/fb/debugger/get-state-response.ts b/page/src/fb/debugger/get-state-response.ts similarity index 100% rename from page/src_/fb/debugger/get-state-response.ts rename to page/src/fb/debugger/get-state-response.ts diff --git a/page/src_/fb/debugger/pause-request.ts b/page/src/fb/debugger/pause-request.ts similarity index 100% rename from page/src_/fb/debugger/pause-request.ts rename to page/src/fb/debugger/pause-request.ts diff --git a/page/src_/fb/debugger/read-memory-request.ts b/page/src/fb/debugger/read-memory-request.ts similarity index 100% rename from page/src_/fb/debugger/read-memory-request.ts rename to page/src/fb/debugger/read-memory-request.ts diff --git a/page/src_/fb/debugger/read-memory-response.ts b/page/src/fb/debugger/read-memory-response.ts similarity index 100% rename from page/src_/fb/debugger/read-memory-response.ts rename to page/src/fb/debugger/read-memory-response.ts diff --git a/page/src_/fb/debugger/read-register-request.ts b/page/src/fb/debugger/read-register-request.ts similarity index 100% rename from page/src_/fb/debugger/read-register-request.ts rename to page/src/fb/debugger/read-register-request.ts diff --git a/page/src_/fb/debugger/read-register-response.ts b/page/src/fb/debugger/read-register-response.ts similarity index 100% rename from page/src_/fb/debugger/read-register-response.ts rename to page/src/fb/debugger/read-register-response.ts diff --git a/page/src_/fb/debugger/run-request.ts b/page/src/fb/debugger/run-request.ts similarity index 100% rename from page/src_/fb/debugger/run-request.ts rename to page/src/fb/debugger/run-request.ts diff --git a/page/src_/fb/debugger/state.ts b/page/src/fb/debugger/state.ts similarity index 100% rename from page/src_/fb/debugger/state.ts rename to page/src/fb/debugger/state.ts diff --git a/page/src_/fb/debugger/write-memory-request.ts b/page/src/fb/debugger/write-memory-request.ts similarity index 100% rename from page/src_/fb/debugger/write-memory-request.ts rename to page/src/fb/debugger/write-memory-request.ts diff --git a/page/src_/fb/debugger/write-memory-response.ts b/page/src/fb/debugger/write-memory-response.ts similarity index 100% rename from page/src_/fb/debugger/write-memory-response.ts rename to page/src/fb/debugger/write-memory-response.ts diff --git a/page/src_/fb/debugger/write-register-request.ts b/page/src/fb/debugger/write-register-request.ts similarity index 100% rename from page/src_/fb/debugger/write-register-request.ts rename to page/src/fb/debugger/write-register-request.ts diff --git a/page/src_/fb/debugger/write-register-response.ts b/page/src/fb/debugger/write-register-response.ts similarity index 100% rename from page/src_/fb/debugger/write-register-response.ts rename to page/src/fb/debugger/write-register-response.ts diff --git a/page/src_/fb/events.ts b/page/src/fb/events.ts similarity index 100% rename from page/src_/fb/events.ts rename to page/src/fb/events.ts diff --git a/page/src_/filesystem-explorer.tsx b/page/src/filesystem-explorer.tsx similarity index 100% rename from page/src_/filesystem-explorer.tsx rename to page/src/filesystem-explorer.tsx diff --git a/page/src_/filesystem.ts b/page/src/filesystem.ts similarity index 100% rename from page/src_/filesystem.ts rename to page/src/filesystem.ts diff --git a/page/src_/hooks/use-mobile.ts b/page/src/hooks/use-mobile.ts similarity index 100% rename from page/src_/hooks/use-mobile.ts rename to page/src/hooks/use-mobile.ts diff --git a/page/src_/index.css b/page/src/index.css similarity index 100% rename from page/src_/index.css rename to page/src/index.css diff --git a/page/src_/landing-page.tsx b/page/src/landing-page.tsx similarity index 100% rename from page/src_/landing-page.tsx rename to page/src/landing-page.tsx diff --git a/page/src_/lib/utils.ts b/page/src/lib/utils.ts similarity index 100% rename from page/src_/lib/utils.ts rename to page/src/lib/utils.ts diff --git a/page/src_/main.tsx b/page/src/main.tsx similarity index 100% rename from page/src_/main.tsx rename to page/src/main.tsx diff --git a/page/src_/playground.tsx b/page/src/playground.tsx similarity index 100% rename from page/src_/playground.tsx rename to page/src/playground.tsx diff --git a/page/src_/settings.ts b/page/src/settings.ts similarity index 100% rename from page/src_/settings.ts rename to page/src/settings.ts diff --git a/page/src_/vite-env.d.ts b/page/src/vite-env.d.ts similarity index 100% rename from page/src_/vite-env.d.ts rename to page/src/vite-env.d.ts diff --git a/page/src_/zip-file.ts b/page/src/zip-file.ts similarity index 100% rename from page/src_/zip-file.ts rename to page/src/zip-file.ts