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;
}