diff --git a/package.json b/package.json index 9cecf5b9..11e60b78 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,8 @@ "@mui/material": "^5.15.2", "@nyariv/sandboxjs": "^0.8.23", "@primer/octicons-react": "^19.9.0", + "@zenfs/core": "^0.17.1", + "@zenfs/dom": "^0.2.15", "ajv": "^8.12.0", "anser": "^2.1.1", "axios": "^1.6.2", @@ -96,6 +98,7 @@ "onsenui": "^2.12.8", "properties-file": "^3.2.10", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-device-detect": "^2.2.3", "react-disappear": "^1.1.3", "react-dom": "^18.2.0", diff --git a/src/activitys/ModConfActivity/components/ModConfView/libs.ts b/src/activitys/ModConfActivity/components/ModConfView/libs.ts index 2326bb17..fb275787 100644 --- a/src/activitys/ModConfActivity/components/ModConfView/libs.ts +++ b/src/activitys/ModConfActivity/components/ModConfView/libs.ts @@ -38,6 +38,7 @@ import OnsenUI from "onsenui"; import * as DefaultComposer from "default-composer"; import * as UseHooksTS from "usehooks-ts"; import * as ModFS from "modfs"; +import * as DND from "react-beautiful-dnd"; export const InternalReact = { ...React, @@ -68,6 +69,7 @@ export const libraries = { "flatlist-react": FlatListReact.default, onsenui: OnsenUI, + "react-beautiful-dnd": DND, "@mmrl/activity": { SearchActivity: SearchActivity, diff --git a/src/hooks/useNativeFileStorage.tsx b/src/hooks/useNativeFileStorage.tsx index ef0ce6b2..ac0679b7 100644 --- a/src/hooks/useNativeFileStorage.tsx +++ b/src/hooks/useNativeFileStorage.tsx @@ -7,6 +7,7 @@ import { os } from "@Native/Os"; import { SetValue } from "./useNativeStorage"; import React from "react"; import { INITIAL_MOD_CONF } from "./useModFS"; +import { path } from "@Util/path"; type Loader = "json" | "yaml" | "yml" | "prop" | "properties" | "ini" | null; @@ -17,11 +18,12 @@ export function useNativeFileStorage( ): [T, SetValue] { const { loader } = opt; + const dir = React.useMemo(() => new SuFile(path.dirname(key), { readDefaultValue: JSON.stringify(INITIAL_MOD_CONF) }), [key]); const file = React.useMemo(() => new SuFile(key, { readDefaultValue: JSON.stringify(INITIAL_MOD_CONF) }), [key]); React.useEffect(() => { - if (!file.exist()) { - file.create(SuFile.NEW_FILE); + if (!dir.exist()) { + dir.create(SuFile.NEW_FOLDERS); } }, [key]); diff --git a/src/index.tsx b/src/index.tsx index e44e3e1b..4c4d2eef 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -84,6 +84,10 @@ const Fallback = React.memo((props: any) => { ); }); +import { configureSingle } from "@zenfs/core"; +import { IndexedDB } from "@zenfs/dom"; +await configureSingle({ backend: IndexedDB }); + ons.ready(() => { customElements.define("mmrl-app", MMRLApp); customElements.define("mmrl-anchor", MMRLAnchor); diff --git a/src/native/IsolatedEval/index.ts b/src/native/IsolatedEval/index.ts index bce3da04..1a195732 100644 --- a/src/native/IsolatedEval/index.ts +++ b/src/native/IsolatedEval/index.ts @@ -59,9 +59,22 @@ class IsolatedEval { Element: Element, Audio: IsoAudio, HTMLMediaElement: HTMLMediaElement, + HTMLButtonElement: HTMLButtonElement, + HTMLTextAreaElement: HTMLTextAreaElement, + HTMLInputElement: HTMLInputElement, + DragEvent: DragEvent, + InputEvent: InputEvent, + KeyboardEvent: KeyboardEvent, + MouseEvent: MouseEvent, + PointerEvent: PointerEvent, + TouchEvent: TouchEvent, + TransitionEvent: TransitionEvent, + UIEvent: UIEvent, + WheelEvent: WheelEvent, FileReader: FileReader, Blob: Blob, Event: Event, + ToggleEvent: ToggleEvent, EventTarget: EventTarget, NamedNodeMap: NamedNodeMap, DOMParser: IsoDOMParser, @@ -82,6 +95,15 @@ class IsolatedEval { clearInterval: clearInterval, clearTimeout: clearTimeout, setTimeout: setTimeout, + import: async (whatever: string) => { + if (!os.isAndroid) { + return await import(/* webpackIgnore: true */ whatever); + } + return undefined; + }, + DataTransfer: DataTransfer, + DataTransferItem: DataTransferItem, + DataTransferItemList: DataTransferItemList, eval() { throw new IsolatedFunctionBlockError("eval()"); }, @@ -120,11 +142,23 @@ class IsolatedEval { this._prototypeWhitelist.set(IsoDocument, new Set()); this._prototypeWhitelist.set(Response, new Set()); this._prototypeWhitelist.set(Element, new Set()); + this._prototypeWhitelist.set(HTMLInputElement, new Set()); + this._prototypeWhitelist.set(HTMLTextAreaElement, new Set()); + this._prototypeWhitelist.set(HTMLButtonElement, new Set()); this._prototypeWhitelist.set(HTMLMediaElement, new Set()); this._prototypeWhitelist.set(IsoAudio, new Set()); this._prototypeWhitelist.set(FileReader, new Set()); this._prototypeWhitelist.set(Blob, new Set()); this._prototypeWhitelist.set(Event, new Set()); + this._prototypeWhitelist.set(DragEvent, new Set()); + this._prototypeWhitelist.set(InputEvent, new Set()); + this._prototypeWhitelist.set(KeyboardEvent, new Set()); + this._prototypeWhitelist.set(MouseEvent, new Set()); + this._prototypeWhitelist.set(PointerEvent, new Set()); + this._prototypeWhitelist.set(TouchEvent, new Set()); + this._prototypeWhitelist.set(TransitionEvent, new Set()); + this._prototypeWhitelist.set(UIEvent, new Set()); + this._prototypeWhitelist.set(WheelEvent, new Set()); this._prototypeWhitelist.set(EventTarget, new Set()); this._prototypeWhitelist.set(NamedNodeMap, new Set()); this._prototypeWhitelist.set(IsoDOMParser, new Set()); @@ -144,6 +178,9 @@ class IsolatedEval { this._prototypeWhitelist.set(Path, new Set()); this._prototypeWhitelist.set(React, new Set()); this._prototypeWhitelist.set(ModFS, new Set()); + this._prototypeWhitelist.set(DataTransfer, new Set()); + this._prototypeWhitelist.set(DataTransferItem, new Set()); + this._prototypeWhitelist.set(DataTransferItemList, new Set()); this.require = this.require.bind(this); this._resolveModule = this._resolveModule.bind(this); diff --git a/src/native/SuFile.ts b/src/native/SuFile.ts index bd9b4370..a3a48eb0 100644 --- a/src/native/SuFile.ts +++ b/src/native/SuFile.ts @@ -1,4 +1,6 @@ +import { path } from "@Util/path"; import { Native } from "./Native"; +import { fs } from "@zenfs/core"; interface NativeSuFile extends NativeSuFileV2 { readFile(path: string): string; @@ -43,6 +45,8 @@ export type SuFileConstuctor = new (path: string) => SuFile; class SuFile extends Native { // @ts-ignore - Won't get even called private _file: ReturnType; + // @ts-ignore - Won't get even called + private _bfile: typeof fs; private _path: string; private _imgblob: string | ArrayBuffer | null = null; private _readDefaultValue: string; @@ -88,6 +92,8 @@ class SuFile extends Native { if (this.isAndroid) { this._file = this.interface.v2.bind(this.interface)(this._path); + } else { + this._bfile = fs; } } @@ -114,7 +120,7 @@ class SuFile extends Native { if (this.isAndroid) { return this._file.read(this._readDefaultValue); } else { - return localStorage.getItem(this._path) || this._readDefaultValue; + return this._bfile.readFileSync(this._path, "utf-8") || this._readDefaultValue; } } @@ -130,7 +136,7 @@ class SuFile extends Native { if (this.isAndroid) { this._file.write(content); } else { - localStorage.setItem(this._path, content); + this._bfile.writeFileSync(this._path, content); } } @@ -138,7 +144,7 @@ class SuFile extends Native { if (this.isAndroid) { return this._file.list(delimiter).split(delimiter); } else { - return [""]; + return this._bfile.readdirSync(this._path); } } @@ -154,7 +160,7 @@ class SuFile extends Native { if (this.isAndroid) { return this._file.exists(); } else { - return this._path in localStorage; + return this._bfile.existsSync(this._path); } } @@ -169,6 +175,8 @@ class SuFile extends Native { public deleteRecursive(): void { if (this.isAndroid) { this._file.deleteRecursive(); + } else { + this._bfile.rmSync(this._path, { recursive: true }); } } /** @@ -186,7 +194,21 @@ class SuFile extends Native { if (this.isAndroid) { return this._file.create(type); } else { - return false; + switch (type) { + case SuFile.NEW_FILE: + this._bfile.writeFileSync(this._path, ""); + break; + case SuFile.NEW_FOLDER: + this._bfile.mkdirSync(this._path); + break; + case SuFile.NEW_FOLDERS: + this._bfile.mkdirSync(this._path, { recursive: true }); + break; + default: + return true; + } + + return true; } } @@ -310,6 +332,25 @@ class SuFile extends Native { } return undefined; } + + public static createFileTree(fileTree: object, basePath: string) { + Object.keys(fileTree).forEach((key) => { + const value = fileTree[key]; + const fullPath = path.join(basePath, key.replace(/^\//, "")); // Remove leading '/' if present + + if (typeof value === "object") { + // If value is an object, create a directory + if (!SuFile.exist(fullPath)) { + SuFile.create(fullPath, SuFile.NEW_FOLDERS); + } + // Recursively create subdirectories and files + this.createFileTree(value, fullPath); + } else if (typeof value === "string") { + // If value is a string, treat it as file content + SuFile.write(fullPath, value); + } + }); + } } export { SuFile };