diff --git a/.eslintrc.yaml b/.eslintrc.yaml deleted file mode 100644 index 21b3b9a9e..000000000 --- a/.eslintrc.yaml +++ /dev/null @@ -1,44 +0,0 @@ -env: - browser: true - es6: true - worker: true - -extends: - - eslint:recommended - - plugin:@typescript-eslint/recommended - -parser: - "@typescript-eslint/parser" - -parserOptions: - ecmaVersion: 2020 - sourceType: module - -plugins: - - "@typescript-eslint" - -root: - true - -globals: - Elm: readonly - MediaMetadata: readonly - RemoteStorage: readonly - tocca: readonly - -rules: - "@typescript-eslint/ban-ts-comment": - 0 - - no-cond-assign: - 0 - - no-unexpected-multiline: - 0 - - "@typescript-eslint/no-explicit-any": - 0 - - "@typescript-eslint/no-unused-vars": - - error - - argsIgnorePattern: "^_" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 8107789b9..538b3589e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,2 @@ github: icidasset ko_fi: icidasset -patreon: diffuse diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 05aef6e72..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,134 +0,0 @@ -name: Build & create release - -on: - workflow_dispatch: - inputs: - -jobs: - ######### - # BUILD # - ######### - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - # Tasks - - run: npm install - - run: npx just build-prod - - # Upload artifacts - - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ - - ################## - # CREATE RELEASE # - ################## - create-release: - needs: build - runs-on: ubuntu-latest - - outputs: - RELEASE_UPLOAD_ID: ${{ steps.create_release.outputs.id }} - TAG_NAME: ${{ steps.package-version.outputs.current-version }} - - steps: - - uses: actions/checkout@v4 - - run: mkdir compressed - - # Download artifacts - - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - # Create zip - - uses: montudor/action-zip@v1 - with: - args: zip -qq -r ./compressed/diffuse-web.zip ./dist - - # Create tar.gz - - uses: master-atul/tar-action@v1.1.0 - with: - command: c - cwd: . - files: ./dist - outPath: compressed/diffuse-web.tar.gz - - # Get Diffuse's version number - - id: package-version - uses: martinbeentjes/npm-get-version-action@v1.3.1 - - # Create release - - uses: softprops/action-gh-release@v1 - id: create_release - with: - token: ${{ secrets.GITHUB_TOKEN }} - - tag_name: "${{ steps.package-version.outputs.current-version }}" - name: "v${{ steps.package-version.outputs.current-version }}" - body: "See the assets to download this version and install." - draft: true - - files: | - compressed/diffuse-web.zip - compressed/diffuse-web.tar.gz - - ######### - # TAURI # - ######### - tauri: - needs: create-release - - strategy: - fail-fast: false - matrix: - os: [macos-15, macos-13, ubuntu-latest, windows-latest] - - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - # Install dependencies - - run: npm install - - # OS - - name: install linux dependencies - if: matrix.os == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-4-1 libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0 - - # Rust - - uses: moonrepo/setup-rust@v1 - - # Download artifacts - - uses: actions/download-artifact@v4 - with: - name: dist - path: dist/ - - # Make a build - - run: npx tauri build - - # Upload artifacts - - uses: softprops/action-gh-release@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - tag_name: "${{ needs.create-release.outputs.TAG_NAME }}" - draft: true - - files: | - src-tauri/target/release/bundle/appimage/*.AppImage - src-tauri/target/release/bundle/deb/*.deb - src-tauri/target/release/bundle/dmg/*.dmg - src-tauri/target/release/bundle/msi/*.msi - src-tauri/target/release/bundle/nsis/*.exe - src-tauri/target/release/bundle/rpm/*.rpm diff --git a/.gitignore b/.gitignore index 7fc92681c..58a6415e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ .DS_Store -.gren -.zed -app -fission.yaml* -elm-stuff node_modules -/.deploy* +/_site +/_vendor /dist -/src/Library/Css/Classes.elm +/src/definitions/types/ +/vendor diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 000000000..74dc2b9e5 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,41 @@ +{ + "lsp": { + "deno": { + "settings": { + "deno": { + "enable": true + } + } + }, + "json-language-server": { + "settings": { + "json": { + "schemas": [ + { + "fileMatch": ["deno.json"], + "url": "https://raw.githubusercontent.com/denoland/deno/refs/heads/main/cli/schemas/config-file.v1.json" + }, + { + "fileMatch": ["package.json"], + "url": "http://json.schemastore.org/package" + } + ] + } + } + } + }, + "languages": { + "JavaScript": { + "language_servers": ["deno", "!typescript-language-server", "!vtsls", "!eslint"], + "formatter": "language_server" + }, + "TypeScript": { + "language_servers": ["deno", "!typescript-language-server", "!vtsls", "!eslint"], + "formatter": "language_server" + }, + "TSX": { + "language_servers": ["deno", "!typescript-language-server", "!vtsls", "!eslint"], + "formatter": "language_server" + } + } +} diff --git a/.zed/tasks.json b/.zed/tasks.json new file mode 100644 index 000000000..e96163364 --- /dev/null +++ b/.zed/tasks.json @@ -0,0 +1,7 @@ +[ + { + "label": "deno test", + "command": "deno test -A --filter '/^$ZED_CUSTOM_DENO_TEST_NAME$/' '$ZED_FILE'", + "tags": ["js-test"] + } +] diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fe35c6c..b28332e36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 4.0.0 + +**Rewrite from scratch.** + + ## 3.5.0 - **Improve audio playback, processing and error handling**. diff --git a/Justfile b/Justfile deleted file mode 100644 index 86bb684f0..000000000 --- a/Justfile +++ /dev/null @@ -1,237 +0,0 @@ -export NODE_NO_WARNINGS := "1" - - -BUILD_DIR := "./dist" -NPM_DIR := "./node_modules" -SRC_DIR := "./src" -SYSTEM_DIR := "./system" - -ESBUILD := "node system/Js/esbuild.mjs" -ELM_REVIEW := NPM_DIR + "/.bin/elm-review " + SRC_DIR + " --config system/Review --compiler " + NPM_DIR + "/.bin/elm --elm-format-path " + NPM_DIR + "/.bin/elm-format" - - -default: dev - - -# Tasks -# ===== - -@build: clean css elm copy-wasm js system license - echo "> Build completed โšก" - - -@build-prod: quality clean (css "minify") elm-prod copy-wasm js-prod system license - echo "> Production build completed ๐Ÿ›ณ" - - -check-versions: - #!/usr/bin/env node - console.log("> Checking version numbers ๐Ÿงฎ") - const pwd = "{{invocation_directory()}}" - - const package = require(`${pwd}/package.json`) - const manifest = require(`${pwd}/src/Static/Manifests/manifest.json`) - - if (package.version !== manifest.version) { - console.error(`The version from package.json doesn't match the one from the app manifest. The package version is '${package.version}' and the manifest version is '${manifest.version}'.`) - process.exit(1) - } - - -@clean: - echo "> Cleaning build directory" - rm -rf {{BUILD_DIR}} || true - mkdir -p {{BUILD_DIR}} - - -@copy-wasm: - echo "> Copying WASM files" - mkdir -p {{BUILD_DIR}}/wasm - cp {{NPM_DIR}}/mediainfo.js/dist/MediaInfoModule.wasm {{BUILD_DIR}}/wasm/media-info.wasm - - -@css minify="false": - echo "{{ if minify == "minify" { "> Compiling CSS (optimised)" } else { "> Compiling CSS" } }}" - - {{NPM_DIR}}/.bin/tailwind \ - --input {{SRC_DIR}}/Css/About.css \ - --output {{BUILD_DIR}}/about.css \ - --content {{SRC_DIR}}/Static/About/**/*.* \ - --config {{SYSTEM_DIR}}/Css/Tailwind.js \ - --postcss {{SYSTEM_DIR}}/Css/PostCSS.js \ - --jit \ - {{ if minify == "minify" { "--minify" } else { "" } }} - - {{NPM_DIR}}/.bin/tailwind \ - --input {{SRC_DIR}}/Css/Application.css \ - --output {{BUILD_DIR}}/application.css \ - --content "{{SRC_DIR}}/Static/Html/**/*.*,{{SRC_DIR}}/Core/Themes/**/*.elm,{{SRC_DIR}}/Core/UI/**/*.elm,{{SRC_DIR}}/Core/UI.elm,{{SRC_DIR}}/Library/**/*.elm,{{SRC_DIR}}/Javascript/**/*.ts" \ - --config {{SYSTEM_DIR}}/Css/Tailwind.js \ - --postcss {{SYSTEM_DIR}}/Css/PostCSS.js \ - --jit \ - {{ if minify == "minify" { "--minify" } else { "" } }} - - -@elm: - echo "> Compiling Elm application" - {{NPM_DIR}}/.bin/elm make {{SRC_DIR}}/Core/Brain.elm --output {{BUILD_DIR}}/js/brain.elm.js - {{NPM_DIR}}/.bin/elm make {{SRC_DIR}}/Core/UI.elm --output {{BUILD_DIR}}/js/ui.elm.js - - -@elm-prod: - echo "> Compiling Elm application (optimised)" - {{NPM_DIR}}/.bin/elm make {{SRC_DIR}}/Core/Brain.elm --output {{BUILD_DIR}}/js/brain.elm.js --optimize - {{NPM_DIR}}/.bin/elm make {{SRC_DIR}}/Core/UI.elm --output {{BUILD_DIR}}/js/ui.elm.js --optimize - - {{NPM_DIR}}/.bin/esbuild {{BUILD_DIR}}/js/brain.elm.js \ - --minify --outfile={{BUILD_DIR}}/js/brain.elm.tmp.js - - {{NPM_DIR}}/.bin/esbuild {{BUILD_DIR}}/js/ui.elm.js \ - --minify --outfile={{BUILD_DIR}}/js/ui.elm.tmp.js - - rm {{BUILD_DIR}}/js/brain.elm.js - mv {{BUILD_DIR}}/js/brain.elm.tmp.js {{BUILD_DIR}}/js/brain.elm.js - rm {{BUILD_DIR}}/js/ui.elm.js - mv {{BUILD_DIR}}/js/ui.elm.tmp.js {{BUILD_DIR}}/js/ui.elm.js - - -js: - #!/usr/bin/env bash - build_timestamp="`date '+%s'`" - echo "> Compiling Javascript code" - - # Workers - {{ESBUILD}} ./src/Javascript/Workers/search.ts \ - --outfile={{BUILD_DIR}}/search.js - - {{ESBUILD}} ./src/Javascript/Workers/service.ts \ - --outfile={{BUILD_DIR}}/service-worker.js \ - --define:BUILD_TIMESTAMP=$build_timestamp - - {{ESBUILD}} ./src/Javascript/Brain/index.ts \ - --outdir={{BUILD_DIR}}/js/brain/ \ - --splitting \ - --alias:brain.elm.js={{BUILD_DIR}}/js/brain.elm.js \ - --inject:./system/Js/node-shims.js \ - --alias:node:buffer=buffer/ \ - --alias:node:stream=stream - - # Main - {{ESBUILD}} ./src/Javascript/UI/index.ts \ - --outdir={{BUILD_DIR}}/js/ui/ \ - --define:BUILD_TIMESTAMP=$build_timestamp \ - --splitting \ - --alias:node:buffer=buffer/ \ - --alias:node:stream=stream - - -js-prod: - #!/usr/bin/env bash - build_timestamp="`date '+%s'`" - echo "> Compiling Javascript code (optimised)" - - # Workers - {{ESBUILD}} ./src/Javascript/Workers/search.ts \ - --minify \ - --outfile={{BUILD_DIR}}/search.js - - {{ESBUILD}} ./src/Javascript/Workers/service.ts \ - --minify \ - --outfile={{BUILD_DIR}}/service-worker.js \ - --define:BUILD_TIMESTAMP=$build_timestamp - - {{ESBUILD}} ./src/Javascript/Brain/index.ts \ - --outdir={{BUILD_DIR}}/js/brain/ \ - --splitting \ - --minify \ - --alias:brain.elm.js={{BUILD_DIR}}/js/brain.elm.js \ - --inject:./system/Js/node-shims.js \ - --alias:node:buffer=buffer/ \ - --alias:node:stream=stream - - # Main - {{ESBUILD}} ./src/Javascript/UI/index.ts \ - --outdir={{BUILD_DIR}}/js/ui/ \ - --define:BUILD_TIMESTAMP=$build_timestamp \ - --splitting \ - --minify \ - --alias:node:buffer=buffer/ \ - --alias:node:stream=stream - - -@license: - echo "> Copying license file" - cp LICENSE {{BUILD_DIR}}/LICENSE - - -@system: - echo "> Compiling system" - {{NPM_DIR}}/.bin/gren make system/Build/Build.gren - node app - rm app - - - -# -# Dev tasks -# - -@dev: build - just watch & just server - - -@doc-tests: - echo "> Running documentation tests" - ( cd src && \ - find . -name "*.elm" -print0 | \ - xargs -0 -n 1 -I % sh -c 'elm-proofread -- % || exit 255; echo "\n\n"' \ - ) - - -@elm-format: - echo "> Running elm-format" - {{NPM_DIR}}/.bin/elm-format {{SRC_DIR}} --yes - - -@elm-housekeeping: elm-format elm-review - - -@elm-review: - echo "> Running elm-review" - {{ELM_REVIEW}} --fix-all - - -@quality: check-versions - echo "> Running es-lint" - {{NPM_DIR}}/.bin/eslint src/Javascript/**/* - echo "> Running elm-review" - {{ELM_REVIEW}} - - -@server: - echo "> Booting up web server on port 8000" - {{NPM_DIR}}/.bin/serve {{BUILD_DIR}} -p 8000 --no-request-logging - - -@test: doc-tests - - -@watch: - echo "> Watching" - just watch-css & just watch-elm & just watch-js & just watch-system - - -@watch-css: - watchexec -p -w {{SRC_DIR}}/Css -w {{SYSTEM_DIR}}/Css -- just css js - - -@watch-elm: - watchexec -p -w {{SRC_DIR}} -e elm -- just elm js css - - -@watch-js: - watchexec -p -w {{SRC_DIR}} -e js,ts -- just js - - -@watch-system: - watchexec -p --ignore *.elm --ignore *.js --ignore *.ts --ignore *.css --ignore src-tauri/** -- just system js diff --git a/README.md b/README.md index 3871550cd..cb963a288 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,19 @@ -Diffuse +Diffuse -_A music player that connects to your cloud/distributed storage, -in the form of a static, serverless, web application._ +**Construct your audio player**, make a music player by composing web components. -๐Ÿ“ Available at [diffuse.sh](https://diffuse.sh/) and for [download](https://github.com/icidasset/diffuse/releases). +Diffuse provides a range of custom elements: audio input, data output, metadata & artwork processing, audio playback, a queue system, and much more. -
- +It also features themes which are prebuilt compositions of those elements. +More information on the [website](https://elements.diffuse.sh). -### Integrations +## Build it yourself -Music layer for music storage. -User layer for user-data storage. - -#### Music layer - -- [Amazon S3](https://aws.amazon.com/s3/) -- [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/) -- [Azure File Storage](https://azure.microsoft.com/en-us/services/storage/files/) -- [Dropbox](https://dropbox.com/) -- [IPFS](https://ipfs.io/) -- [WebDAV](https://en.wikipedia.org/wiki/WebDAV) - -#### User layer - -- [Dropbox](https://www.dropbox.com/) -- [IPFS](https://ipfs.io/) (using MFS) -- [RemoteStorage](https://remotestorage.io/) - - - ---- - - - -### Hosting on your own server - -Diffuse is a static web application, which means it's just HTML, CSS, and Javascript. No REST API, database, or anything backend-related involved. The app uses a hash (aka. fragment-based) routing system, so you don't need any special server rules for routing. You can download a pre-build web-only version of Diffuse on the [releases](https://github.com/icidasset/diffuse/releases) page. Diffuse uses service workers, so you may need HTTPS for it to work smoothly in certain browsers. - -I should also note that some source services use OAuth, so you'll need to use your own application credentials (eg. Dropbox). That said, if you're working locally, you can use `http://localhost:8000` or `http://127.0.0.1:44999` to use the default ones, that's what the old Electron app was using. - -In short: -- Diffuse is a static, serverless web application -- Routing is done using hashes/fragments (eg. `diffuse.sh/#/sources`) -- Download a web build on the [releases](https://github.com/icidasset/diffuse/releases) page -- Uses service workers (use HTTPS if possible) -- May need own OAuth application credentials for some source services - - - ---- - - - -### Building it yourself - -This project can be built with [Node.js](https://nodejs.org/). +Install [deno](https://docs.deno.com/runtime/getting_started/installation/). ```shell -# ๐Ÿฑ - -# 1. Install dependencies -npm install - -# 2. Build -npx just build - -# 3. Start static-file server -npx just server - -# 4. Watch for changes (requires [watchexec](https://github.com/watchexec/watchexec/) to be installed) -npx just watch - -# Alternatively, to build, serve & watch: -npx just +deno run gen:defs:types +deno run build ``` diff --git a/_config.ts b/_config.ts new file mode 100644 index 000000000..79fb980f8 --- /dev/null +++ b/_config.ts @@ -0,0 +1,126 @@ +import { builtinModules } from "node:module"; +import lume from "lume/mod.ts"; + +import esbuild from "lume/plugins/esbuild.ts"; +import postcss from "lume/plugins/postcss.ts"; +import purgecss from "lume/plugins/purgecss.ts"; +import sourceMaps from "lume/plugins/source_maps.ts"; + +import * as path from "@std/path"; +import { ensureDirSync } from "@std/fs/ensure-dir"; +import { walkSync } from "@std/fs/walk"; +import { nodeModulesPolyfillPlugin } from "esbuild-plugins-node-modules-polyfill"; + +const site = lume({ + dest: "./dist", + src: "./src", + server: { + debugBar: false, + }, +}); + +export default site; + +// JS + +site.use(esbuild({ + extensions: [".js"], + options: { + bundle: true, + minify: false, + // outExtension: { ".js": ".min.js" }, + plugins: [nodeModulesPolyfillPlugin()], + splitting: true, + }, +})); + +site.add([".js"]); + +// CSS + +site.use(postcss()); +// site.use(purgecss()); +site.add([".css"]); + +site.remoteFile( + "styles/vendor/98.css", + import.meta.resolve("./node_modules/98.css/dist/98.css"), +); + +// BINARY ASSETS + +site.add("/favicons", "/"); +site.add("/fonts"); +site.add("/images"); +site.add([".woff2"]); + +site.remoteFile( + "styles/vendor/ms_sans_serif.woff2", + import.meta.resolve( + "./node_modules/98.css/fonts/converted/ms_sans_serif.woff2", + ), +); + +site.remoteFile( + "styles/vendor/ms_sans_serif_bold.woff2", + import.meta.resolve( + "./node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2", + ), +); + +site.remoteFile( + "fonts/ms_sans_serif.woff2", + import.meta.resolve( + "./node_modules/98.css/fonts/converted/ms_sans_serif.woff2", + ), +); + +site.remoteFile( + "fonts/ms_sans_serif_bold.woff2", + import.meta.resolve( + "./node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2", + ), +); + +// DEFINITIONS + +site.add("/definitions"); + +// PHOSPHOR ICONS + +function phosphor(path: string) { + site.remoteFile( + `styles/vendor/phosphor/${path}`, + import.meta.resolve(`./node_modules/@phosphor-icons/web/src/${path}`), + ); + + site.add(`styles/vendor/phosphor/${path}`); +} + +phosphor("fill/style.css"); +phosphor("fill/Phosphor-Fill.svg"); +phosphor("fill/Phosphor-Fill.ttf"); +phosphor("fill/Phosphor-Fill.woff"); +phosphor("fill/Phosphor-Fill.woff2"); + +// MISC + +site.use(sourceMaps()); + +site.script("copy-type-defs", () => { + for ( + const f of walkSync( + "./src/", + { includeDirs: false, exts: [".d.ts"] }, + ) + ) { + const dest = "dist/" + f.path.replace(/^src\//, ""); + const dir = path.dirname(dest); + ensureDirSync(dir); + Deno.copyFileSync(f.path, dest); + } +}); + +site.addEventListener("afterBuild", () => { + // site.run("copy-type-defs"); +}); diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 000000000..1f91086e2 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,124 @@ +{ + "name": "@tokono.ma/diffuse", + "version": "4.0.0-alpha", + "vendor": true, + "imports": { + "98.css": "npm:98.css@^0.1.21", + "@atcute/lex-cli": "npm:@atcute/lex-cli@^2.3.1", + "@atcute/lexicons": "npm:@atcute/lexicons@^1.2.2", + "@bradenmacdonald/s3-lite-client": "jsr:@bradenmacdonald/s3-lite-client@^0.9.4", + "@fry69/deep-diff": "jsr:@fry69/deep-diff@^0.1.10", + "@js-temporal/polyfill": "npm:@js-temporal/polyfill@^0.5.1", + "@kunkun/kkrpc": "jsr:@kunkun/kkrpc@^0.6.0", + "@mary/ds-queue": "jsr:@mary/ds-queue@^0.1.3", + "@okikio/transferables": "jsr:@okikio/transferables@^1.0.2", + "@orama/orama": "npm:@orama/orama@^3.1.18", + "@phosphor-icons/web": "npm:@phosphor-icons/web@^2.1.2", + "@vicary/debounce-microtask": "jsr:@vicary/debounce-microtask@^0.1.8", + "alien-signals": "npm:alien-signals@^3.0.0", + "esbuild-plugins-node-modules-polyfill": "npm:esbuild-plugins-node-modules-polyfill@^1.7.1", + "fast-average-color": "npm:fast-average-color@^9.5.0", + "idb-keyval": "npm:idb-keyval@^6.2.2", + "iso-base": "npm:iso-base@^4.3.0", + "lit-html": "npm:lit-html@^3.3.1", + "query-string": "npm:query-string@^9.3.1", + "subsonic-api": "npm:subsonic-api@^3.2.0", + "throttle-debounce": "npm:throttle-debounce@^5.0.2", + "uri-js": "npm:uri-js@^4.4.1", + "xxh32": "npm:xxh32@^2.0.5", + + // music-metadata + // NOTE: A lot of issues with `node:` imports, hence this mess. + "@tokenizer/http": "https://esm.sh/@tokenizer/http@0.9.2/lib/http-client.js", + "@tokenizer/range": "https://esm.sh/@tokenizer/range@0.13.0/lib/index.js", + "music-metadata": "https://esm.sh/music-metadata@11.9.0/lib/core.js", + + // Webamp + "webamp": "npm:webamp@^2.2.0", + + // Paths + "@common/": "./src/common/", + "@components/": "./src/components/", + "@definitions/": "./src/definitions/", + "@styles/": "./src/styles/", + + // Build + "@std/fs": "jsr:@std/fs@^1.0.19", + "@std/path": "jsr:@std/path@^1.1.2", + "lume/": "https://cdn.jsdelivr.net/gh/lumeland/lume@3.1.2/", + "lume/jsx-runtime": "https://cdn.jsdelivr.net/gh/oscarotero/ssx@0.1.14/jsx-runtime.ts", + }, + "exports": {}, + "tasks": { + "build": { + "description": "Build the site for production", + "command": "deno task lume", + }, + "gen:defs:types": { + "description": "Generate definition Typescript types", + "command": "deno run -A npm:@atcute/lex-cli generate -c ./lexicon.config.js", + }, + "lume": { + "description": "Run Lume command", + "command": "deno run -P=lume --allow-write --allow-read lume/cli.ts", + }, + "serve": { + "description": "Run and serve the site for development", + "command": "deno task lume -s", + }, + }, + "compilerOptions": { + "checkJs": true, + "lib": ["deno.ns", "dom", "esnext"], + "types": ["lume/types.ts"], + "jsx": "react-jsx", + "jsxImportSource": "lume", + }, + "unstable": ["temporal", "fmt-component"], + "lint": { + "plugins": ["https://cdn.jsdelivr.net/gh/lumeland/lume@3.1.2/lint.ts"], + "rules": { + "exclude": ["no-import-prefix"], + }, + }, + "publish": { + "exclude": ["!dist"], + "include": [ + "LICENSE", + "README.md", + "src/**/*.css", + "src/**/*.ico", + "src/**/*.jpg", + "src/**/*.js", + "src/**/*.json", + "src/**/*.png", + "src/**/*.svg", + "src/**/*.ts", + "src/**/*.vto", + "src/**/*.woff", + "src/**/*.woff2", + "src/**/*.xml", + ], + }, + "permissions": { + "lume": { + "read": true, + "write": ["./"], + "import": ["cdn.jsdelivr.net:443", "jsr.io:443", "deno.land:443", "esm.sh:443"], + "net": [ + "0.0.0.0", + "cdn.jsdelivr.net:443", + "data.jsdelivr.com:443", + "jsr.io:443", + "deno.land:443", + "esm.sh:443", + "registry.npmjs.org:443", + ], + "env": true, + "run": true, + "ffi": true, + "sys": true, + }, + }, + "lock": false, +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 000000000..395a73cea --- /dev/null +++ b/deno.lock @@ -0,0 +1,2149 @@ +{ + "version": "5", + "specifiers": { + "jsr:@bradenmacdonald/s3-lite-client@~0.9.4": "0.9.4", + "jsr:@deno/loader@0.3.6": "0.3.6", + "jsr:@fry69/deep-diff@~0.1.10": "0.1.10", + "jsr:@kunkun/kkrpc@0.6": "0.6.0", + "jsr:@mary/ds-queue@~0.1.3": "0.1.3", + "jsr:@mys/m-rpc@~0.12.2": "0.12.2", + "jsr:@mys/worker-fn@^3.2.1": "3.2.1", + "jsr:@okikio/transferables@^1.0.2": "1.0.2", + "jsr:@orama/orama@^2.0.6": "2.0.6", + "jsr:@std/cli@1.0.22": "1.0.22", + "jsr:@std/cli@^1.0.21": "1.0.22", + "jsr:@std/collections@^1.1.3": "1.1.3", + "jsr:@std/crypto@1.0.5": "1.0.5", + "jsr:@std/encoding@1.0.10": "1.0.10", + "jsr:@std/encoding@^1.0.10": "1.0.10", + "jsr:@std/fmt@1.0.8": "1.0.8", + "jsr:@std/fmt@^1.0.8": "1.0.8", + "jsr:@std/front-matter@1.0.9": "1.0.9", + "jsr:@std/fs@1.0.19": "1.0.19", + "jsr:@std/fs@^1.0.19": "1.0.19", + "jsr:@std/html@^1.0.4": "1.0.5", + "jsr:@std/http@1.0.20": "1.0.20", + "jsr:@std/internal@^1.0.10": "1.0.12", + "jsr:@std/internal@^1.0.9": "1.0.12", + "jsr:@std/jsonc@1.0.2": "1.0.2", + "jsr:@std/media-types@^1.1.0": "1.1.0", + "jsr:@std/net@^1.0.4": "1.0.6", + "jsr:@std/path@1.1.2": "1.1.2", + "jsr:@std/path@^1.1.1": "1.1.2", + "jsr:@std/path@^1.1.2": "1.1.2", + "jsr:@std/semver@1.0.5": "1.0.5", + "jsr:@std/streams@^1.0.10": "1.0.13", + "jsr:@std/toml@1.0.10": "1.0.10", + "jsr:@std/toml@^1.0.3": "1.0.10", + "jsr:@std/yaml@1.0.9": "1.0.9", + "jsr:@std/yaml@^1.0.5": "1.0.9", + "jsr:@vicary/debounce-microtask@~0.1.8": "0.1.8", + "npm:98.css@~0.1.21": "0.1.21", + "npm:@atcute/lex-cli@*": "2.3.1", + "npm:@atcute/lex-cli@^2.3.1": "2.3.1", + "npm:@atcute/lexicons@^1.2.2": "1.2.2", + "npm:@js-temporal/polyfill@~0.5.1": "0.5.1", + "npm:@phosphor-icons/web@^2.1.2": "2.1.2", + "npm:@tauri-apps/plugin-shell@^2.2.0": "2.3.3", + "npm:alien-signals@3": "3.0.3", + "npm:autoprefixer@10.4.21": "10.4.21_postcss@8.5.6", + "npm:esbuild-plugins-node-modules-polyfill@^1.7.1": "1.7.1_esbuild@0.25.12", + "npm:fast-average-color@^9.5.0": "9.5.0", + "npm:idb-keyval@^6.2.2": "6.2.2", + "npm:lightningcss-wasm@1.30.1": "1.30.1", + "npm:lit-html@^3.3.1": "3.3.1", + "npm:markdown-it-attrs@4.3.1": "4.3.1_markdown-it@14.1.0", + "npm:markdown-it-deflist@3.0.0": "3.0.0", + "npm:markdown-it@14.1.0": "14.1.0", + "npm:morphdom@^2.7.7": "2.7.7", + "npm:postcss-import@16.1.1": "16.1.1_postcss@8.5.6", + "npm:postcss@8.5.6": "8.5.6", + "npm:purgecss-from-html@7.0.2": "7.0.2", + "npm:purgecss@7.0.2": "7.0.2", + "npm:query-string@^9.3.1": "9.3.1", + "npm:socket.io-client@^4.8.1": "4.8.1", + "npm:subsonic-api@^3.2.0": "3.2.0", + "npm:superjson@^2.2.2": "2.2.5", + "npm:throttle-debounce@^5.0.2": "5.0.2", + "npm:uint8arrays@^5.1.0": "5.1.0", + "npm:uri-js@^4.4.1": "4.4.1", + "npm:webamp@^2.2.0": "2.2.0_redux@5.0.1_react@19.2.0_react-dom@19.2.0__react@19.2.0", + "npm:xxh32@^2.0.5": "2.0.5" + }, + "jsr": { + "@bradenmacdonald/s3-lite-client@0.9.4": { + "integrity": "f52e31c7efdaeb1ccdf65a1db995b5920d635717c96d45dcf9450c3cc47ecaaf" + }, + "@deno/loader@0.3.6": { + "integrity": "98f08d837c18ece5ba15122264fb29580967407c34e6552e152b8f453a60c2be" + }, + "@fry69/deep-diff@0.1.10": { + "integrity": "cdd88fefaef1ac896a038a5f3c0895038d8c725e61bac50489c455156e0275f5" + }, + "@kunkun/kkrpc@0.6.0": { + "integrity": "44674738cac0712740ee9e2a57048bd53e1086829a2c4f21e15c03333793a19f", + "dependencies": [ + "npm:@tauri-apps/plugin-shell", + "npm:socket.io-client", + "npm:superjson" + ] + }, + "@mary/ds-queue@0.1.3": { + "integrity": "a743caa397b924cb08b0bbdffc526eb1ea2d3fc9e675da6edc137c437fc93c76" + }, + "@mys/m-rpc@0.12.2": { + "integrity": "36599d3d4708db9f5c0f7da35a17b7e7da1fafddb69de6cfcdc6afe94cd4f084", + "dependencies": [ + "jsr:@okikio/transferables" + ] + }, + "@mys/worker-fn@3.2.1": { + "integrity": "330960f21041edd20fa9c5f78b136f62e3781e35797ac635534f003545be76cd", + "dependencies": [ + "jsr:@mys/m-rpc" + ] + }, + "@okikio/transferables@1.0.2": { + "integrity": "46a80015a1c4672b0b246e38838b3ea1e2edc6c775a235184a2f8eb49a8314f7" + }, + "@orama/orama@2.0.6": { + "integrity": "0221bbd9a638e42772fc6e082cf057755f83a8cea919c85fa0baa9bb027ab078" + }, + "@std/cli@1.0.22": { + "integrity": "50d1e4f87887cb8a8afa29b88505ab5081188f5cad3985460c3b471fa49ff21a" + }, + "@std/collections@1.1.3": { + "integrity": "bf8b0818886df6a32b64c7d3b037a425111f28278d69fd0995aeb62777c986b0" + }, + "@std/crypto@1.0.5": { + "integrity": "0dcfbb319fe0bba1bd3af904ceb4f948cde1b92979ec1614528380ed308a3b40" + }, + "@std/encoding@1.0.10": { + "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" + }, + "@std/fmt@1.0.8": { + "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" + }, + "@std/front-matter@1.0.9": { + "integrity": "ee6201d06674cbef137dda2252f62477450b48249e7d8d9ab57a30f85ff6f051", + "dependencies": [ + "jsr:@std/toml@^1.0.3", + "jsr:@std/yaml@^1.0.5" + ] + }, + "@std/fs@1.0.19": { + "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", + "dependencies": [ + "jsr:@std/internal@^1.0.9", + "jsr:@std/path@^1.1.1" + ] + }, + "@std/html@1.0.5": { + "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" + }, + "@std/http@1.0.20": { + "integrity": "b5cc33fc001bccce65ed4c51815668c9891c69ccd908295997e983d8f56070a1", + "dependencies": [ + "jsr:@std/cli@^1.0.21", + "jsr:@std/encoding@^1.0.10", + "jsr:@std/fmt@^1.0.8", + "jsr:@std/fs@^1.0.19", + "jsr:@std/html", + "jsr:@std/media-types", + "jsr:@std/net", + "jsr:@std/path@^1.1.1", + "jsr:@std/streams" + ] + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, + "@std/jsonc@1.0.2": { + "integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7" + }, + "@std/media-types@1.1.0": { + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" + }, + "@std/net@1.0.6": { + "integrity": "110735f93e95bb9feb95790a8b1d1bf69ec0dc74f3f97a00a76ea5efea25500c" + }, + "@std/path@1.1.2": { + "integrity": "c0b13b97dfe06546d5e16bf3966b1cadf92e1cc83e56ba5476ad8b498d9e3038", + "dependencies": [ + "jsr:@std/internal@^1.0.10" + ] + }, + "@std/semver@1.0.5": { + "integrity": "529f79e83705714c105ad0ba55bec0f9da0f24d2f726b6cc1c15e505cc2c0624" + }, + "@std/streams@1.0.13": { + "integrity": "772d208cd0d3e5dac7c1d9e6cdb25842846d136eea4a41a62e44ed4ab0c8dd9e" + }, + "@std/toml@1.0.10": { + "integrity": "87b2b7ff95afe7209a868732eb013a2707be29a15229f5b57bb13eededff4655", + "dependencies": [ + "jsr:@std/collections" + ] + }, + "@std/yaml@1.0.9": { + "integrity": "6bad3dc766dd85b4b37eabcba81b6aa4eac7a392792ae29abcfb0f90602d55bb" + }, + "@vicary/debounce-microtask@0.1.8": { + "integrity": "fe180e0c599903ccf7a93e719ea986c48affc1ff78951a1bc0ccb874aa30fd0e" + } + }, + "npm": { + "98.css@0.1.21": { + "integrity": "sha512-ddk5qtUWyapM0Bzd5jwGExoE5fdSEGrP+F5VbYjyZLf2c9UVmn6w2NPTvCsoD4BWdGsjdLjlkQGhWwWTJcYQJQ==" + }, + "@assemblyscript/loader@0.17.14": { + "integrity": "sha512-+PVTOfla/0XMLRTQLJFPg4u40XcdTfon6GGea70hBGi8Pd7ZymIXyVUR+vK8wt5Jb4MVKTKPIz43Myyebw5mZA==" + }, + "@atcute/lex-cli@2.3.1": { + "integrity": "sha512-HrHD91CFSFd/p0UFe3akFA1HXiboQwd5LbYiU0srKdLxGX+NLTX/EdCdhbLV6M7LsXdmxk7PB6BMcprsX4rbvg==", + "dependencies": [ + "@atcute/lexicon-doc", + "@badrap/valita", + "@optique/core", + "@optique/run", + "picocolors", + "prettier" + ], + "bin": true + }, + "@atcute/lexicon-doc@1.1.4": { + "integrity": "sha512-OL0fsXtbnN/KwCq/L3nWGvOCdSHV0NWTatgLUIPt+T9AhcziFNaXAbbjvVHdflr3ZaLh3ksleHK0J789UBhlWQ==", + "dependencies": [ + "@badrap/valita" + ] + }, + "@atcute/lexicons@1.2.2": { + "integrity": "sha512-bgEhJq5Z70/0TbK5sx+tAkrR8FsCODNiL2gUEvS5PuJfPxmFmRYNWaMGehxSPaXWpU2+Oa9ckceHiYbrItDTkA==", + "dependencies": [ + "@standard-schema/spec", + "esm-env" + ] + }, + "@babel/runtime@7.28.4": { + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==" + }, + "@badrap/valita@0.4.6": { + "integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==" + }, + "@borewit/text-codec@0.1.1": { + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==" + }, + "@borewit/text-codec@0.2.0": { + "integrity": "sha512-X999CKBxGwX8wW+4gFibsbiNdwqmdQEXmUejIWaIqdrHBgS5ARIOOeyiQbHjP9G58xVEPcuvP6VwwH3A0OFTOA==" + }, + "@esbuild/aix-ppc64@0.25.12": { + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "os": ["aix"], + "cpu": ["ppc64"] + }, + "@esbuild/android-arm64@0.25.12": { + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@esbuild/android-arm@0.25.12": { + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "os": ["android"], + "cpu": ["arm"] + }, + "@esbuild/android-x64@0.25.12": { + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "os": ["android"], + "cpu": ["x64"] + }, + "@esbuild/darwin-arm64@0.25.12": { + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@esbuild/darwin-x64@0.25.12": { + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@esbuild/freebsd-arm64@0.25.12": { + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, + "@esbuild/freebsd-x64@0.25.12": { + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@esbuild/linux-arm64@0.25.12": { + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@esbuild/linux-arm@0.25.12": { + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@esbuild/linux-ia32@0.25.12": { + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "os": ["linux"], + "cpu": ["ia32"] + }, + "@esbuild/linux-loong64@0.25.12": { + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "os": ["linux"], + "cpu": ["loong64"] + }, + "@esbuild/linux-mips64el@0.25.12": { + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, + "@esbuild/linux-ppc64@0.25.12": { + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@esbuild/linux-riscv64@0.25.12": { + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@esbuild/linux-s390x@0.25.12": { + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@esbuild/linux-x64@0.25.12": { + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@esbuild/netbsd-arm64@0.25.12": { + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, + "@esbuild/netbsd-x64@0.25.12": { + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "os": ["netbsd"], + "cpu": ["x64"] + }, + "@esbuild/openbsd-arm64@0.25.12": { + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, + "@esbuild/openbsd-x64@0.25.12": { + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "os": ["openbsd"], + "cpu": ["x64"] + }, + "@esbuild/openharmony-arm64@0.25.12": { + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@esbuild/sunos-x64@0.25.12": { + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "os": ["sunos"], + "cpu": ["x64"] + }, + "@esbuild/win32-arm64@0.25.12": { + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@esbuild/win32-ia32@0.25.12": { + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@esbuild/win32-x64@0.25.12": { + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@isaacs/balanced-match@4.0.1": { + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" + }, + "@isaacs/brace-expansion@5.0.0": { + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dependencies": [ + "@isaacs/balanced-match" + ] + }, + "@isaacs/cliui@8.0.2": { + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": [ + "string-width@5.1.2", + "string-width-cjs@npm:string-width@4.2.3", + "strip-ansi@7.1.2", + "strip-ansi-cjs@npm:strip-ansi@6.0.1", + "wrap-ansi@8.1.0", + "wrap-ansi-cjs@npm:wrap-ansi@7.0.0" + ] + }, + "@js-temporal/polyfill@0.5.1": { + "integrity": "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==", + "dependencies": [ + "jsbi" + ] + }, + "@jspm/core@2.1.0": { + "integrity": "sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==" + }, + "@optique/core@0.6.2": { + "integrity": "sha512-HTxIHJ8xLOSZotiU6Zc5BCJv+SJ8DMYmuiQM+7tjF7RolJn/pdZNe7M78G3+DgXL9lIf82l8aGcilmgVYRQnGQ==" + }, + "@optique/run@0.6.2": { + "integrity": "sha512-ERksB5bHozwEUVlTPToIc8UjZZBOgLeBhFZYh2lgldUbNDt7LItzgcErsPq5au5i5IBmmyCti4+2A3x+MRI4Xw==", + "dependencies": [ + "@optique/core" + ] + }, + "@phosphor-icons/web@2.1.2": { + "integrity": "sha512-rPAR9o/bEcp4Cw4DEeZHXf+nlGCMNGkNDRizYHM47NLxz9vvEHp/Tt6FMK1NcWadzw/pFDPnRBGi/ofRya958A==" + }, + "@redux-devtools/extension@3.3.0_redux@5.0.1": { + "integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==", + "dependencies": [ + "@babel/runtime", + "immutable", + "redux" + ] + }, + "@sentry/browser@5.9.1": { + "integrity": "sha512-7AOabwp9yAH9h6Xe6TfDwlLxHbUSWs+SPWHI7bPlht2yDSAqkXYGSzRr5X0XQJX9oBQdx2cEPMqHyJrbNaP/og==", + "dependencies": [ + "@sentry/core", + "@sentry/types", + "@sentry/utils", + "tslib" + ] + }, + "@sentry/core@5.8.0": { + "integrity": "sha512-aAh2KLidIXJVGrxmHSVq2eVKbu7tZiYn5ylW6yzJXFetS5z4MA+JYaSBaG2inVYDEEqqMIkb17TyWxxziUDieg==", + "dependencies": [ + "@sentry/hub", + "@sentry/minimal", + "@sentry/types", + "@sentry/utils", + "tslib" + ] + }, + "@sentry/hub@5.8.0": { + "integrity": "sha512-VdApn1ZCNwH1wwQwoO6pu53PM/qgHG+DQege0hbByluImpLBhAj9w50nXnF/8KzV4UoMIVbzCb6jXzMRmqqp9A==", + "dependencies": [ + "@sentry/types", + "@sentry/utils", + "tslib" + ] + }, + "@sentry/minimal@5.8.0": { + "integrity": "sha512-MIlFOgd+JvAUrBBmq7vr9ovRH1HvckhnwzHdoUPpKRBN+rQgTyZy1o6+kA2fASCbrRqFCP+Zk7EHMACKg8DpIw==", + "dependencies": [ + "@sentry/hub", + "@sentry/types", + "tslib" + ] + }, + "@sentry/types@5.7.1": { + "integrity": "sha512-tbUnTYlSliXvnou5D4C8Zr+7/wJrHLbpYX1YkLXuIJRU0NSi81bHMroAuHWILcQKWhVjaV/HZzr7Y/hhWtbXVQ==" + }, + "@sentry/utils@5.8.0": { + "integrity": "sha512-KDxUvBSYi0/dHMdunbxAxD3389pcQioLtcO6CI6zt/nJXeVFolix66cRraeQvqupdLhvOk/el649W4fCPayTHw==", + "dependencies": [ + "@sentry/types", + "tslib" + ] + }, + "@socket.io/component-emitter@3.1.2": { + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "@standard-schema/spec@1.0.0": { + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==" + }, + "@tauri-apps/api@2.9.0": { + "integrity": "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw==" + }, + "@tauri-apps/plugin-shell@2.3.3": { + "integrity": "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ==", + "dependencies": [ + "@tauri-apps/api" + ] + }, + "@tokenizer/inflate@0.2.7": { + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dependencies": [ + "debug@4.4.3", + "fflate", + "token-types@6.1.1" + ] + }, + "@tokenizer/token@0.3.0": { + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, + "@types/hoist-non-react-statics@3.3.7_@types+react@19.2.2": { + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", + "dependencies": [ + "@types/react", + "hoist-non-react-statics" + ] + }, + "@types/react@19.2.2": { + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", + "dependencies": [ + "csstype" + ] + }, + "@types/trusted-types@2.0.7": { + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "@types/use-sync-external-store@0.0.3": { + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, + "acorn@8.15.0": { + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": true + }, + "alien-signals@3.0.3": { + "integrity": "sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==" + }, + "ani-cursor@0.0.5": { + "integrity": "sha512-gGxst72lG9TOwEfbVpX9vHhzUGw+4Ee2XB6AfYq5JP+bxBtpAjgnTBepCVxYF5t1TPrWHN23nWqLTflJOA3/ag==", + "dependencies": [ + "byte-data", + "riff-file" + ] + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-regex@6.2.2": { + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": [ + "color-convert" + ] + }, + "ansi-styles@6.2.3": { + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" + }, + "argparse@2.0.1": { + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "assert@1.5.1": { + "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", + "dependencies": [ + "object.assign", + "util" + ] + }, + "autoprefixer@10.4.21_postcss@8.5.6": { + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dependencies": [ + "browserslist", + "caniuse-lite", + "fraction.js", + "normalize-range", + "picocolors", + "postcss", + "postcss-value-parser" + ], + "bin": true + }, + "babel-runtime@6.26.0": { + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": [ + "core-js", + "regenerator-runtime" + ] + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "baseline-browser-mapping@2.8.25": { + "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", + "bin": true + }, + "browserslist@4.27.0": { + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dependencies": [ + "baseline-browser-mapping", + "caniuse-lite", + "electron-to-chromium", + "node-releases", + "update-browserslist-db" + ], + "bin": true + }, + "buffer@5.7.1": { + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dependencies": [ + "base64-js", + "ieee754" + ] + }, + "butterchurn-presets@3.0.0-beta.4": { + "integrity": "sha512-TbQLUPvGOYMZAtWKoCmBtludh9aQZ6NaMGQU4lvPeadBPy3Du3yNmwBjlTMLP5c5mRWElxQPjTL1PtR7FZK3OQ==", + "dependencies": [ + "@babel/runtime" + ] + }, + "butterchurn@3.0.0-beta.5": { + "integrity": "sha512-BStK4OAbBb9Pvt8PuQlS4WVmYBwU1KuDMRHF1V89QjoIFauAqq7tpV4EpYXj7K563r5daLrMX+2y5DBhZZ9Xig==", + "dependencies": [ + "@assemblyscript/loader", + "ecma-proposal-math-extensions", + "eel-wasm" + ] + }, + "byte-data@18.1.1": { + "integrity": "sha512-Kv/B0r7adgnCcrs/y703sac2XFLdHW5kPfis1j8+Ij/hmEcWhBKf+1pNTv+vsNqXb207Uiyri8bpnogNxR/4Lg==", + "dependencies": [ + "endianness", + "ieee754-buffer", + "utf8-buffer" + ] + }, + "call-bind-apply-helpers@1.0.2": { + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": [ + "es-errors", + "function-bind" + ] + }, + "call-bind@1.0.8": { + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": [ + "call-bind-apply-helpers", + "es-define-property", + "get-intrinsic", + "set-function-length" + ] + }, + "call-bound@1.0.4": { + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": [ + "call-bind-apply-helpers", + "get-intrinsic" + ] + }, + "caniuse-lite@1.0.30001754": { + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==" + }, + "chainsaw@0.0.9": { + "integrity": "sha512-nG8PYH+/4xB+8zkV4G844EtfvZ5tTiLFoX3dZ4nhF4t3OCKIb9UvaFyNmeZO2zOSmRWzBoTD+napN6hiL+EgcA==", + "dependencies": [ + "traverse" + ] + }, + "classnames@2.5.1": { + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "commander@12.1.0": { + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==" + }, + "confbox@0.1.8": { + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==" + }, + "confbox@0.2.2": { + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==" + }, + "content-type@1.0.5": { + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "copy-anything@4.0.5": { + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dependencies": [ + "is-what" + ] + }, + "core-js@2.6.12": { + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": true, + "scripts": true + }, + "core-util-is@1.0.3": { + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cross-spawn@7.0.6": { + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": [ + "path-key", + "shebang-command", + "which" + ] + }, + "cssesc@3.0.0": { + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": true + }, + "csstype@3.1.3": { + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "debug@4.3.7": { + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": [ + "ms" + ] + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, + "decode-uri-component@0.4.1": { + "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==" + }, + "define-data-property@1.1.4": { + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": [ + "es-define-property", + "es-errors", + "gopd" + ] + }, + "define-properties@1.2.1": { + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": [ + "define-data-property", + "has-property-descriptors", + "object-keys" + ] + }, + "domelementtype@2.3.0": { + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler@5.0.3": { + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": [ + "domelementtype" + ] + }, + "dunder-proto@1.0.1": { + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": [ + "call-bind-apply-helpers", + "es-errors", + "gopd" + ] + }, + "eastasianwidth@0.2.0": { + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "ecma-proposal-math-extensions@0.0.2": { + "integrity": "sha512-80BnDp2Fn7RxXlEr5HHZblniY4aQ97MOAicdWWpSo0vkQiISSE9wLR4SqxKsu4gCtXFBIPPzy8JMhay4NWRg/Q==" + }, + "eel-wasm@0.0.16": { + "integrity": "sha512-1tkId7I7E1Vs4fXTRsH83Sjn2S/AbzrVQKLBRGys6NLc3eVH4NBffJsdEeLHOWWUgQpVXBEP3CV/srUZNIuBnw==" + }, + "electron-to-chromium@1.5.248": { + "integrity": "sha512-zsur2yunphlyAO4gIubdJEXCK6KOVvtpiuDfCIqbM9FjcnMYiyn0ICa3hWfPr0nc41zcLWobgy1iL7VvoOyA2Q==" + }, + "emoji-regex@8.0.0": { + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "emoji-regex@9.2.2": { + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "endianness@8.0.2": { + "integrity": "sha512-IU+77+jJ7lpw2qZ3NUuqBZFy3GuioNgXUdsL1L9tooDNTaw0TgOnwNuc+8Ns+haDaTifK97QLzmOANJtI/rGvw==" + }, + "engine.io-client@6.6.3": { + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7", + "engine.io-parser", + "ws", + "xmlhttprequest-ssl" + ] + }, + "engine.io-parser@5.2.3": { + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" + }, + "entities@4.5.0": { + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "entities@6.0.1": { + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" + }, + "es-define-property@1.0.1": { + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors@1.3.0": { + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms@1.1.1": { + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": [ + "es-errors" + ] + }, + "esbuild-plugins-node-modules-polyfill@1.7.1_esbuild@0.25.12": { + "integrity": "sha512-IEaUhaS1RukGGcatDzqJmR+AzUWJ2upTJZP2i7FzR37Iw5Lk0LReCTnWnPMWnGG9lO4MWTGKEGGLWEOPegTRJA==", + "dependencies": [ + "@jspm/core", + "esbuild", + "local-pkg", + "resolve.exports" + ] + }, + "esbuild@0.25.12": { + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "optionalDependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-arm64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/openharmony-arm64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ], + "scripts": true, + "bin": true + }, + "escalade@3.2.0": { + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" + }, + "esm-env@1.2.2": { + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" + }, + "exsolve@1.0.8": { + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==" + }, + "fast-average-color@9.5.0": { + "integrity": "sha512-nC6x2YIlJ9xxgkMFMd1BNoM1ctMjNoRKfRliPmiEWW3S6rLTHiQcy9g3pt/xiKv/D0NAAkhb9VyV+WJFvTqMGg==" + }, + "fflate@0.8.2": { + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + }, + "file-type@11.1.0": { + "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==" + }, + "file-type@21.0.0": { + "integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==", + "dependencies": [ + "@tokenizer/inflate", + "strtok3@10.3.4", + "token-types@6.1.1", + "uint8array-extras" + ] + }, + "filter-obj@5.1.0": { + "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==" + }, + "foreground-child@3.3.1": { + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dependencies": [ + "cross-spawn", + "signal-exit" + ] + }, + "fraction.js@4.3.7": { + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==" + }, + "fscreen@1.2.0": { + "integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==" + }, + "function-bind@1.1.2": { + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic@1.3.0": { + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": [ + "call-bind-apply-helpers", + "es-define-property", + "es-errors", + "es-object-atoms", + "function-bind", + "get-proto", + "gopd", + "has-symbols", + "hasown", + "math-intrinsics" + ] + }, + "get-proto@1.0.1": { + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": [ + "dunder-proto", + "es-object-atoms" + ] + }, + "glob@11.0.3": { + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "dependencies": [ + "foreground-child", + "jackspeak", + "minimatch", + "minipass", + "package-json-from-dist", + "path-scurry" + ], + "bin": true + }, + "glsl-optimizer-js@0.0.2": { + "integrity": "sha512-SMkVILyc1LeBEBgiHOe+4Bh8MEqxLNyAns0NfgmxJTxZZdj7oCoZt+n846rbdB8OLGsg16f5C9nmhi9XEuM8SQ==" + }, + "gopd@1.2.0": { + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-property-descriptors@1.0.2": { + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": [ + "es-define-property" + ] + }, + "has-symbols@1.1.0": { + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "hashish@0.0.4": { + "integrity": "sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==", + "dependencies": [ + "traverse" + ] + }, + "hasown@2.0.2": { + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": [ + "function-bind" + ] + }, + "hoist-non-react-statics@3.3.2": { + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": [ + "react-is@16.13.1" + ] + }, + "idb-keyval@6.2.2": { + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==" + }, + "ieee754-buffer@2.0.0": { + "integrity": "sha512-AXUAT0nMEi7h1Is8HXGXof3eejl/GabZFKSj8Ym6kVRUSwrAb52EkAXywiCQYSHGQMRn7lvfY7vhPMjVc+Kybg==" + }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "immediate@3.0.6": { + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "immutable@4.3.7": { + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" + }, + "inherits@2.0.3": { + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "invariant@2.2.4": { + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": [ + "loose-envify" + ] + }, + "is-core-module@2.16.1": { + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": [ + "hasown" + ] + }, + "is-fullwidth-code-point@3.0.0": { + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-typedarray@1.0.0": { + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "is-what@5.5.0": { + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==" + }, + "isarray@1.0.0": { + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "jackspeak@4.1.1": { + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dependencies": [ + "@isaacs/cliui" + ] + }, + "js-tokens@4.0.0": { + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "jsbi@4.3.2": { + "integrity": "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==" + }, + "jszip@3.10.1": { + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": [ + "lie", + "pako", + "readable-stream@2.3.8", + "setimmediate" + ] + }, + "lie@3.3.0": { + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": [ + "immediate" + ] + }, + "lightningcss-wasm@1.30.1": { + "integrity": "sha512-KJTnKEn0REV6DoJzxG0m5EKVEFA1CVE1isDYpXjsuqWXwLKFPJtA9Z9BSzPZJwAZFN2KaUzy+IWGP59p5bm2sA==" + }, + "linkify-it@5.0.0": { + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": [ + "uc.micro" + ] + }, + "lit-html@3.3.1": { + "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==", + "dependencies": [ + "@types/trusted-types" + ] + }, + "local-pkg@1.1.2": { + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dependencies": [ + "mlly", + "pkg-types@2.3.0", + "quansync" + ] + }, + "lodash@4.17.21": { + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "loose-envify@1.4.0": { + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": [ + "js-tokens" + ], + "bin": true + }, + "lru-cache@11.2.2": { + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==" + }, + "markdown-it-attrs@4.3.1_markdown-it@14.1.0": { + "integrity": "sha512-/ko6cba+H6gdZ0DOw7BbNMZtfuJTRp9g/IrGIuz8lYc/EfnmWRpaR3CFPnNbVz0LDvF8Gf1hFGPqrQqq7De0rg==", + "dependencies": [ + "markdown-it" + ] + }, + "markdown-it-deflist@3.0.0": { + "integrity": "sha512-OxPmQ/keJZwbubjiQWOvKLHwpV2wZ5I3Smc81OjhwbfJsjdRrvD5aLTQxmZzzePeO0kbGzAo3Krk4QLgA8PWLg==" + }, + "markdown-it@14.1.0": { + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": [ + "argparse", + "entities@4.5.0", + "linkify-it", + "mdurl", + "punycode.js", + "uc.micro" + ], + "bin": true + }, + "math-intrinsics@1.1.0": { + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mdurl@2.0.0": { + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, + "media-typer@0.3.0": { + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "media-typer@1.1.0": { + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" + }, + "milkdrop-eel-parser@0.0.4": { + "integrity": "sha512-4PsOdTMDB7GM3UFzqXQQXf8MBeoolOhsBLMlhug+IIMZ+yNkvqLbdqDbrueGZc8P8tLRJP8pbAxna1yjFr06HQ==" + }, + "milkdrop-preset-converter-aws@0.1.6": { + "integrity": "sha512-nr89LRZYgdrDn17vGQCvUK/LM9d90mywElL7zlzXBTgkxWAs/Kamn1Yl9676ugt4L4BAGo6PTEipIqeYXFSM7g==", + "dependencies": [ + "babel-runtime", + "glsl-optimizer-js", + "milkdrop-eel-parser", + "milkdrop-preset-utils" + ] + }, + "milkdrop-preset-utils@0.1.0": { + "integrity": "sha512-yK5y03SN8INC+ssLLYGGsaAHgNxXEUK6PQVV44rg9OAA27F2aPM0tA5uGsDdASH9sgPaAaRVMV5NoEvEkh66Sw==", + "dependencies": [ + "babel-runtime", + "lodash" + ] + }, + "minimatch@10.1.1": { + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dependencies": [ + "@isaacs/brace-expansion" + ] + }, + "minipass@7.1.2": { + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "mlly@1.8.0": { + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dependencies": [ + "acorn", + "pathe", + "pkg-types@1.3.1", + "ufo" + ] + }, + "morphdom@2.7.7": { + "integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "multiformats@13.4.1": { + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==" + }, + "music-metadata-browser@0.6.6": { + "integrity": "sha512-14KFz4HR6rM6RATcLtJoBDRbehU/dKdVzElCdeI8CjP7Un2HtSf0WiT7f7Lz+XNkcBMZUjthmC6Wy4+NNayCRw==", + "dependencies": [ + "assert", + "buffer", + "debug@4.4.3", + "music-metadata@3.8.0", + "readable-stream@3.6.2", + "remove", + "typedarray-to-buffer" + ], + "deprecated": true + }, + "music-metadata@11.9.0": { + "integrity": "sha512-J7VqD8FY6KRcm75Fzj86FPsckiD/EdvO5OS3P+JiMf/2krP3TcAseZYfkic6eFeJ0iBhhzcdxgfu8hLW95aXXw==", + "dependencies": [ + "@borewit/text-codec@0.2.0", + "@tokenizer/token", + "content-type", + "debug@4.4.3", + "file-type@21.0.0", + "media-typer@1.1.0", + "strtok3@10.3.4", + "token-types@6.1.1", + "uint8array-extras" + ] + }, + "music-metadata@3.8.0": { + "integrity": "sha512-aIADbp3uCS+ANr4nnFEHzTzMy81OT7PR7WBMW73SJ28Y7P94nnEugmTOj1ICP2JmxBBDlo+MeYVgiPnxVN69tg==", + "dependencies": [ + "debug@4.4.3", + "file-type@11.1.0", + "media-typer@0.3.0", + "strtok3@2.3.0", + "token-types@1.3.2" + ] + }, + "nanoid@3.3.11": { + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "bin": true + }, + "node-releases@2.0.27": { + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" + }, + "normalize-range@0.1.2": { + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" + }, + "object-keys@1.1.1": { + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign@4.1.7": { + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dependencies": [ + "call-bind", + "call-bound", + "define-properties", + "es-object-atoms", + "has-symbols", + "object-keys" + ] + }, + "package-json-from-dist@1.0.1": { + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "pako@1.0.11": { + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parse5-htmlparser2-tree-adapter@7.1.0": { + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dependencies": [ + "domhandler", + "parse5" + ] + }, + "parse5@7.3.0": { + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dependencies": [ + "entities@6.0.1" + ] + }, + "path-key@3.1.1": { + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse@1.0.7": { + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-scurry@2.0.1": { + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dependencies": [ + "lru-cache", + "minipass" + ] + }, + "pathe@2.0.3": { + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" + }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "pify@2.3.0": { + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" + }, + "pkg-types@1.3.1": { + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dependencies": [ + "confbox@0.1.8", + "mlly", + "pathe" + ] + }, + "pkg-types@2.3.0": { + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dependencies": [ + "confbox@0.2.2", + "exsolve", + "pathe" + ] + }, + "postcss-import@16.1.1_postcss@8.5.6": { + "integrity": "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==", + "dependencies": [ + "postcss", + "postcss-value-parser", + "read-cache", + "resolve" + ] + }, + "postcss-selector-parser@6.1.2": { + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dependencies": [ + "cssesc", + "util-deprecate" + ] + }, + "postcss-value-parser@4.2.0": { + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "postcss@8.5.6": { + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dependencies": [ + "nanoid", + "picocolors", + "source-map-js" + ] + }, + "prettier@3.6.2": { + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "bin": true + }, + "process-nextick-args@2.0.1": { + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "punycode.js@2.3.1": { + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" + }, + "punycode@2.3.1": { + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, + "purgecss-from-html@7.0.2": { + "integrity": "sha512-eJOLW9wIt30qvruvz+FCBmaW5XLt+bx0VCGKn+ZhEDzj69e834kt4pIWhIn0APFfwYu4t9x5rSkjyAvbI77xqg==", + "dependencies": [ + "parse5", + "parse5-htmlparser2-tree-adapter" + ] + }, + "purgecss@7.0.2": { + "integrity": "sha512-4Ku8KoxNhOWi9X1XJ73XY5fv+I+hhTRedKpGs/2gaBKU8ijUiIKF/uyyIyh7Wo713bELSICF5/NswjcuOqYouQ==", + "dependencies": [ + "commander", + "glob", + "postcss", + "postcss-selector-parser" + ], + "bin": true + }, + "quansync@0.2.11": { + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==" + }, + "query-string@9.3.1": { + "integrity": "sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw==", + "dependencies": [ + "decode-uri-component", + "filter-obj", + "split-on-first" + ] + }, + "react-dom@19.2.0_react@19.2.0": { + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "dependencies": [ + "react", + "scheduler" + ] + }, + "react-is@16.13.1": { + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-is@18.3.1": { + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "react-redux@8.1.3_react@19.2.0_react-dom@19.2.0__react@19.2.0_redux@5.0.1": { + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "dependencies": [ + "@babel/runtime", + "@types/hoist-non-react-statics", + "@types/use-sync-external-store", + "hoist-non-react-statics", + "react", + "react-dom", + "react-is@18.3.1", + "redux", + "use-sync-external-store" + ], + "optionalPeers": [ + "react-dom", + "redux" + ] + }, + "react@19.2.0": { + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==" + }, + "read-cache@1.0.0": { + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": [ + "pify" + ] + }, + "readable-stream@2.3.8": { + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": [ + "core-util-is", + "inherits", + "isarray", + "process-nextick-args", + "safe-buffer", + "string_decoder", + "util-deprecate" + ] + }, + "readable-stream@3.6.2": { + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": [ + "inherits", + "string_decoder", + "util-deprecate" + ] + }, + "redux-sentry-middleware@0.1.8": { + "integrity": "sha512-xubpYH9RgE31tZUESeRW5agwQa19Yd6Gy+4iO09raW/2TITPO5fhJdXpVwJfpGMbIYhEmHFqE2wD5Lnz7YtAeA==" + }, + "redux-thunk@2.4.2_redux@5.0.1": { + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "dependencies": [ + "redux" + ] + }, + "redux@5.0.1": { + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "regenerator-runtime@0.11.1": { + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "remove@0.1.5": { + "integrity": "sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ==", + "dependencies": [ + "seq" + ] + }, + "reselect@3.0.1": { + "integrity": "sha512-b/6tFZCmRhtBMa4xGqiiRp9jh9Aqi2A687Lo265cN0/QohJQEBPiQ52f4QB6i0eF3yp3hmLL21LSGBcML2dlxA==" + }, + "resolve.exports@2.0.3": { + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==" + }, + "resolve@1.22.11": { + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dependencies": [ + "is-core-module", + "path-parse", + "supports-preserve-symlinks-flag" + ], + "bin": true + }, + "riff-file@1.0.3": { + "integrity": "sha512-Vv8wwGr0BCks7VMI3Lv0houZee4DaHFjjTT0LMhMJKio2YmLncLeIVpK63ydSverngNk8XQPU3fbeP3bWgSIig==", + "dependencies": [ + "byte-data" + ] + }, + "safe-buffer@5.1.2": { + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "scheduler@0.27.0": { + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + }, + "seq@0.3.5": { + "integrity": "sha512-sisY2Ln1fj43KBkRtXkesnRHYNdswIkIibvNe/0UKm2GZxjMbqmccpiatoKr/k2qX5VKiLU8xm+tz/74LAho4g==", + "dependencies": [ + "chainsaw", + "hashish" + ] + }, + "set-function-length@1.2.2": { + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": [ + "define-data-property", + "es-errors", + "function-bind", + "get-intrinsic", + "gopd", + "has-property-descriptors" + ] + }, + "setimmediate@1.0.5": { + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "shebang-command@2.0.0": { + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": [ + "shebang-regex" + ] + }, + "shebang-regex@3.0.0": { + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "signal-exit@4.1.0": { + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + }, + "socket.io-client@4.8.1": { + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7", + "engine.io-client", + "socket.io-parser" + ] + }, + "socket.io-parser@4.2.4": { + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": [ + "@socket.io/component-emitter", + "debug@4.3.7" + ] + }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, + "split-on-first@3.0.0": { + "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==" + }, + "string-width@4.2.3": { + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": [ + "emoji-regex@8.0.0", + "is-fullwidth-code-point", + "strip-ansi@6.0.1" + ] + }, + "string-width@5.1.2": { + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": [ + "eastasianwidth", + "emoji-regex@9.2.2", + "strip-ansi@7.1.2" + ] + }, + "string_decoder@1.1.1": { + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": [ + "safe-buffer" + ] + }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": [ + "ansi-regex@5.0.1" + ] + }, + "strip-ansi@7.1.2": { + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dependencies": [ + "ansi-regex@6.2.2" + ] + }, + "strtok3@10.3.4": { + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "dependencies": [ + "@tokenizer/token" + ] + }, + "strtok3@2.3.0": { + "integrity": "sha512-AA67/1atBh7X0fUTDevjW89by2ZkY9RZAnkwusx5Yc1COYf0ruUbpYOOIs03SnRA1CF9K3+BtRXKOEtKhAXVaQ==", + "dependencies": [ + "debug@4.4.3", + "then-read-stream", + "token-types@1.3.2" + ] + }, + "subsonic-api@3.2.0": { + "integrity": "sha512-BADBQ2hONdLb3agCiSDzNzTIFLWJAuxJTUJvC2zDFvXUVfnK3yy7r8xFu3NkrQl8p5UVI7q8Qfm62N1lFxWbww==" + }, + "superjson@2.2.5": { + "integrity": "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==", + "dependencies": [ + "copy-anything" + ] + }, + "supports-preserve-symlinks-flag@1.0.0": { + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "then-read-stream@1.5.1": { + "integrity": "sha512-I+iiemYWhp1ysJQEioqpEICgvHlqHS5WrQGZkboFLs7Jm350Kvq4cN3qRCzHpETUuq5+NsdrdWEg6M0NFxtwtQ==", + "deprecated": true + }, + "throttle-debounce@5.0.2": { + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==" + }, + "tinyqueue@1.2.3": { + "integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==" + }, + "token-types@1.3.2": { + "integrity": "sha512-LemYprKRfZPUiwVEMIL8fIP/cvZBpMds1PklsyoQyLZdKk7SQlldNGzw4TTrg2MnWLGSkMM6gUa1EW0h1d72fg==", + "dependencies": [ + "ieee754" + ] + }, + "token-types@6.1.1": { + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", + "dependencies": [ + "@borewit/text-codec@0.1.1", + "@tokenizer/token", + "ieee754" + ] + }, + "traverse@0.3.9": { + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==" + }, + "tslib@1.14.1": { + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "typedarray-to-buffer@3.1.5": { + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": [ + "is-typedarray" + ] + }, + "uc.micro@2.1.0": { + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, + "ufo@1.6.1": { + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==" + }, + "uint8array-extras@1.5.0": { + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==" + }, + "uint8arrays@5.1.0": { + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "dependencies": [ + "multiformats" + ] + }, + "update-browserslist-db@1.1.4_browserslist@4.27.0": { + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dependencies": [ + "browserslist", + "escalade", + "picocolors" + ], + "bin": true + }, + "uri-js@4.4.1": { + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": [ + "punycode" + ] + }, + "use-sync-external-store@1.6.0_react@19.2.0": { + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "dependencies": [ + "react" + ] + }, + "utf8-buffer@1.0.0": { + "integrity": "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==" + }, + "util-deprecate@1.0.2": { + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "util@0.10.4": { + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dependencies": [ + "inherits" + ] + }, + "webamp@2.2.0_redux@5.0.1_react@19.2.0_react-dom@19.2.0__react@19.2.0": { + "integrity": "sha512-XzKr65Z4d+4rxA1J//aPkZRqvPS0aqAxpryNKaWt/EDQ4uCJadxjr966QElagH+iZxWMCDekW5dV/dTx5b+WPQ==", + "dependencies": [ + "@redux-devtools/extension", + "@sentry/browser", + "ani-cursor", + "butterchurn", + "butterchurn-presets", + "classnames", + "fscreen", + "invariant", + "jszip", + "lodash", + "milkdrop-preset-converter-aws", + "music-metadata@11.9.0", + "music-metadata-browser", + "react", + "react-dom", + "react-redux", + "redux", + "redux-sentry-middleware", + "redux-thunk", + "reselect", + "strtok3@10.3.4", + "tinyqueue", + "winamp-eqf" + ] + }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": [ + "isexe" + ], + "bin": true + }, + "winamp-eqf@1.0.0": { + "integrity": "sha512-yUIb4+lTYBKP4L6nPXdDj1CQBXlJ+/PrNAkT1VbTAgeFjX8lPxAthsUE5NxQP4s8SO4YMJemsrErZ49Bh+/Veg==" + }, + "wrap-ansi@7.0.0": { + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": [ + "ansi-styles@4.3.0", + "string-width@4.2.3", + "strip-ansi@6.0.1" + ] + }, + "wrap-ansi@8.1.0": { + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": [ + "ansi-styles@6.2.3", + "string-width@5.1.2", + "strip-ansi@7.1.2" + ] + }, + "ws@8.17.1": { + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==" + }, + "xmlhttprequest-ssl@2.1.2": { + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==" + }, + "xxh32@2.0.5": { + "integrity": "sha512-glQIaPvLHV4xG2Sn0E4mZWY25JT34+XcG4e2c8OMIH2SXxVrm6MmJ8miCsqGBLtf+rn2YcaeS11vq/66vkXGUQ==" + } + }, + "redirects": { + "https://esm.sh/@borewit/text-codec@^0.1.0?target=denonext": "https://esm.sh/@borewit/text-codec@0.1.1?target=denonext", + "https://esm.sh/@borewit/text-codec@^0.2.0?target=denonext": "https://esm.sh/@borewit/text-codec@0.2.0?target=denonext", + "https://esm.sh/@tokenizer/inflate@^0.2.7?target=denonext": "https://esm.sh/@tokenizer/inflate@0.2.7?target=denonext", + "https://esm.sh/@tokenizer/range@^0.12.0?target=denonext": "https://esm.sh/@tokenizer/range@0.12.0?target=denonext", + "https://esm.sh/content-type@^1.0.5?target=denonext": "https://esm.sh/content-type@1.0.5?target=denonext", + "https://esm.sh/debug@^4.3.7?target=denonext": "https://esm.sh/debug@4.4.3?target=denonext", + "https://esm.sh/debug@^4.4.0?target=denonext": "https://esm.sh/debug@4.4.3?target=denonext", + "https://esm.sh/debug@^4.4.3?target=denonext": "https://esm.sh/debug@4.4.3?target=denonext", + "https://esm.sh/fflate@^0.8.2?target=denonext": "https://esm.sh/fflate@0.8.2?target=denonext", + "https://esm.sh/file-type@^21.0.0?target=denonext": "https://esm.sh/file-type@21.0.0?target=denonext", + "https://esm.sh/ieee754@^1.2.1?target=denonext": "https://esm.sh/ieee754@1.2.1?target=denonext", + "https://esm.sh/media-typer@^1.1.0?target=denonext": "https://esm.sh/media-typer@1.1.0?target=denonext", + "https://esm.sh/ms@^2.1.3?target=denonext": "https://esm.sh/ms@2.1.3?target=denonext", + "https://esm.sh/peek-readable@^5.3.1?target=denonext": "https://esm.sh/peek-readable@5.4.2?target=denonext", + "https://esm.sh/strtok3@^10.2.0?target=denonext": "https://esm.sh/strtok3@10.3.4?target=denonext", + "https://esm.sh/strtok3@^10.2.2/core?target=denonext": "https://esm.sh/strtok3@10.3.4/core?target=denonext", + "https://esm.sh/strtok3@^10.3.4?target=denonext": "https://esm.sh/strtok3@10.3.4?target=denonext", + "https://esm.sh/strtok3@^9.1.1?target=denonext": "https://esm.sh/strtok3@9.1.1?target=denonext", + "https://esm.sh/supports-color?target=denonext": "https://esm.sh/supports-color@10.2.2?target=denonext", + "https://esm.sh/token-types@^6.0.0?target=denonext": "https://esm.sh/token-types@6.1.1?target=denonext", + "https://esm.sh/token-types@^6.1.1?target=denonext": "https://esm.sh/token-types@6.1.1?target=denonext", + "https://esm.sh/uint8array-extras@^1.4.0?target=denonext": "https://esm.sh/uint8array-extras@1.5.0?target=denonext", + "https://esm.sh/uint8array-extras@^1.5.0?target=denonext": "https://esm.sh/uint8array-extras@1.5.0?target=denonext" + }, + "remote": { + "https://deno.land/std@0.170.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", + "https://deno.land/std@0.170.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", + "https://deno.land/std@0.170.0/encoding/base64.ts": "8605e018e49211efc767686f6f687827d7f5fd5217163e981d8d693105640d7a", + "https://deno.land/std@0.170.0/fmt/colors.ts": "03ad95e543d2808bc43c17a3dd29d25b43d0f16287fe562a0be89bf632454a12", + "https://deno.land/std@0.170.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.170.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.170.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", + "https://deno.land/std@0.170.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.170.0/path/glob.ts": "81cc6c72be002cd546c7a22d1f263f82f63f37fe0035d9726aa96fc8f6e4afa1", + "https://deno.land/std@0.170.0/path/mod.ts": "cf7cec7ac11b7048bb66af8ae03513e66595c279c65cfa12bfc07d9599608b78", + "https://deno.land/std@0.170.0/path/posix.ts": "b859684bc4d80edfd4cad0a82371b50c716330bed51143d6dcdbe59e6278b30c", + "https://deno.land/std@0.170.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.170.0/path/win32.ts": "7cebd2bda6657371adc00061a1d23fdd87bcdf64b4843bb148b0b24c11b40f69", + "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/cli/parse_args.ts": "5250832fb7c544d9111e8a41ad272c016f5a53f975ef84d5a9fe5fcb70566ece", + "https://deno.land/std@0.224.0/cli/spinner.ts": "cf873605771270b4324cc063b5031ab250d8efee8799e45e1a3bfdd333ff721d", + "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412", + "https://deno.land/std@0.224.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", + "https://deno.land/std@0.224.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", + "https://deno.land/std@0.224.0/fs/ensure_dir.ts": "51a6279016c65d2985f8803c848e2888e206d1b510686a509fa7cc34ce59d29f", + "https://deno.land/std@0.224.0/fs/expand_glob.ts": "2e428d90acc6676b2aa7b5c78ef48f30641b13f1fe658e7976c9064fb4b05309", + "https://deno.land/std@0.224.0/fs/walk.ts": "cddf87d2705c0163bff5d7767291f05b0f46ba10b8b28f227c3849cace08d303", + "https://deno.land/std@0.224.0/jsonc/parse.ts": "06fbe10f0bb0cba684f7902bf7de5126b16eb0e5a82220c98a4b86675c7f9cff", + "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", + "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", + "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", + "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", + "https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", + "https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3", + "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", + "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", + "https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e", + "https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", + "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", + "https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", + "https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972", + "https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", + "https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", + "https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", + "https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0", + "https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", + "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", + "https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0", + "https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", + "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", + "https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", + "https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f", + "https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", + "https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63", + "https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", + "https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf", + "https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", + "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", + "https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660", + "https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", + "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", + "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", + "https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8", + "https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", + "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", + "https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", + "https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", + "https://deno.land/std@0.224.0/semver/_shared.ts": "5c53a675225cba9ad74ae2e17c124e333728fc2b551a13e8a32b99433b90c1c2", + "https://deno.land/std@0.224.0/semver/compare.ts": "7b5610c25ded57dc4aa41034ee02857d1a6fff609ab183afea17849284f86236", + "https://deno.land/std@0.224.0/semver/format.ts": "a4492b55a10210a10b9307491c0ec7f0c2475cc82af33de1c2565a15083b83df", + "https://deno.land/std@0.224.0/semver/less_than.ts": "890eb36e6294d245934a33dbe6818164c4ec6fddf3aa585a590031393f781719", + "https://deno.land/std@0.224.0/semver/parse.ts": "94c09f3486643853e7438e64f2c6741462fbeb84cf141ad5bfe88b73ec4cb0f3", + "https://deno.land/std@0.224.0/semver/types.ts": "9286e72b160e25856608f4bc5f08f8f5ccba54e6cdfc9aba8adee68a355c4b36", + "https://deno.land/x/cliffy@v0.25.7/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004", + "https://deno.land/x/cliffy@v0.25.7/ansi/ansi.ts": "7f43d07d31dd7c24b721bb434c39cbb5132029fa4be3dd8938873065f65e5810", + "https://deno.land/x/cliffy@v0.25.7/ansi/ansi_escapes.ts": "885f61f343223f27b8ec69cc138a54bea30542924eacd0f290cd84edcf691387", + "https://deno.land/x/cliffy@v0.25.7/ansi/chain.ts": "31fb9fcbf72fed9f3eb9b9487270d2042ccd46a612d07dd5271b1a80ae2140a0", + "https://deno.land/x/cliffy@v0.25.7/ansi/colors.ts": "5f71993af5bd1aa0a795b15f41692d556d7c89584a601fed75997df844b832c9", + "https://deno.land/x/cliffy@v0.25.7/ansi/cursor_position.ts": "d537491e31d9c254b208277448eff92ff7f55978c4928dea363df92c0df0813f", + "https://deno.land/x/cliffy@v0.25.7/ansi/deps.ts": "0f35cb7e91868ce81561f6a77426ea8bc55dc15e13f84c7352f211023af79053", + "https://deno.land/x/cliffy@v0.25.7/ansi/mod.ts": "bb4e6588e6704949766205709463c8c33b30fec66c0b1846bc84a3db04a4e075", + "https://deno.land/x/cliffy@v0.25.7/ansi/tty.ts": "8fb064c17ead6cdf00c2d3bc87a9fd17b1167f2daa575c42b516f38bdb604673", + "https://deno.land/x/cliffy@v0.25.7/command/_errors.ts": "a9bd23dc816b32ec96c9b8f3057218241778d8c40333b43341138191450965e5", + "https://deno.land/x/cliffy@v0.25.7/command/_utils.ts": "9ab3d69fabab6c335b881b8a5229cbd5db0c68f630a1c307aff988b6396d9baf", + "https://deno.land/x/cliffy@v0.25.7/command/command.ts": "a2b83c612acd65c69116f70dec872f6da383699b83874b70fcf38cddf790443f", + "https://deno.land/x/cliffy@v0.25.7/command/completions/_bash_completions_generator.ts": "43b4abb543d4dc60233620d51e69d82d3b7c44e274e723681e0dce2a124f69f9", + "https://deno.land/x/cliffy@v0.25.7/command/completions/_fish_completions_generator.ts": "d0289985f5cf0bd288c05273bfa286b24c27feb40822eb7fd9d7fee64e6580e8", + "https://deno.land/x/cliffy@v0.25.7/command/completions/_zsh_completions_generator.ts": "14461eb274954fea4953ee75938821f721da7da607dc49bcc7db1e3f33a207bd", + "https://deno.land/x/cliffy@v0.25.7/command/completions/bash.ts": "053aa2006ec327ccecacb00ba28e5eb836300e5c1bec1b3cfaee9ddcf8189756", + "https://deno.land/x/cliffy@v0.25.7/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", + "https://deno.land/x/cliffy@v0.25.7/command/completions/fish.ts": "9938beaa6458c6cf9e2eeda46a09e8cd362d4f8c6c9efe87d3cd8ca7477402a5", + "https://deno.land/x/cliffy@v0.25.7/command/completions/mod.ts": "aeef7ec8e319bb157c39a4bab8030c9fe8fa327b4c1e94c9c1025077b45b40c0", + "https://deno.land/x/cliffy@v0.25.7/command/completions/zsh.ts": "8b04ab244a0b582f7927d405e17b38602428eeb347a9968a657e7ea9f40e721a", + "https://deno.land/x/cliffy@v0.25.7/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", + "https://deno.land/x/cliffy@v0.25.7/command/deps.ts": "275b964ce173770bae65f6b8ebe9d2fd557dc10292cdd1ed3db1735f0d77fa1d", + "https://deno.land/x/cliffy@v0.25.7/command/help/_help_generator.ts": "f7c349cb2ddb737e70dc1f89bcb1943ca9017a53506be0d4138e0aadb9970a49", + "https://deno.land/x/cliffy@v0.25.7/command/help/mod.ts": "09d74d3eb42d21285407cda688074c29595d9c927b69aedf9d05ff3f215820d3", + "https://deno.land/x/cliffy@v0.25.7/command/mod.ts": "d0a32df6b14028e43bb2d41fa87d24bc00f9662a44e5a177b3db02f93e473209", + "https://deno.land/x/cliffy@v0.25.7/command/type.ts": "24e88e3085e1574662b856ccce70d589959648817135d4469fab67b9cce1b364", + "https://deno.land/x/cliffy@v0.25.7/command/types.ts": "ae02eec0ed7a769f7dba2dd5d3a931a61724b3021271b1b565cf189d9adfd4a0", + "https://deno.land/x/cliffy@v0.25.7/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", + "https://deno.land/x/cliffy@v0.25.7/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", + "https://deno.land/x/cliffy@v0.25.7/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", + "https://deno.land/x/cliffy@v0.25.7/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", + "https://deno.land/x/cliffy@v0.25.7/command/types/enum.ts": "2178345972adf7129a47e5f02856ca3e6852a91442a1c78307dffb8a6a3c6c9f", + "https://deno.land/x/cliffy@v0.25.7/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", + "https://deno.land/x/cliffy@v0.25.7/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", + "https://deno.land/x/cliffy@v0.25.7/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", + "https://deno.land/x/cliffy@v0.25.7/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", + "https://deno.land/x/cliffy@v0.25.7/command/upgrade/mod.ts": "17e2df3b620905583256684415e6c4a31e8de5c59066eb6d6c9c133919292dc4", + "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider.ts": "d6fb846043232cbd23c57d257100c7fc92274984d75a5fead0f3e4266dc76ab8", + "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", + "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", + "https://deno.land/x/cliffy@v0.25.7/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", + "https://deno.land/x/cliffy@v0.25.7/command/upgrade/upgrade_command.ts": "3640a287d914190241ea1e636774b1b4b0e1828fa75119971dd5304784061e05", + "https://deno.land/x/cliffy@v0.25.7/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", + "https://deno.land/x/cliffy@v0.25.7/flags/_utils.ts": "340d3ecab43cde9489187e1f176504d2c58485df6652d1cdd907c0e9c3ce4cc2", + "https://deno.land/x/cliffy@v0.25.7/flags/_validate_flags.ts": "16eb5837986c6f6f7620817820161a78d66ce92d690e3697068726bbef067452", + "https://deno.land/x/cliffy@v0.25.7/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", + "https://deno.land/x/cliffy@v0.25.7/flags/flags.ts": "68a9dfcacc4983a84c07ba19b66e5e9fccd04389fad215210c60fb414cc62576", + "https://deno.land/x/cliffy@v0.25.7/flags/mod.ts": "b21c2c135cd2437cc16245c5f168a626091631d6d4907ad10db61c96c93bdb25", + "https://deno.land/x/cliffy@v0.25.7/flags/types.ts": "7452ea5296758fb7af89930349ce40d8eb9a43b24b3f5759283e1cb5113075fd", + "https://deno.land/x/cliffy@v0.25.7/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", + "https://deno.land/x/cliffy@v0.25.7/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", + "https://deno.land/x/cliffy@v0.25.7/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", + "https://deno.land/x/cliffy@v0.25.7/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", + "https://deno.land/x/cliffy@v0.25.7/keycode/key_code.ts": "c4ab0ffd102c2534962b765ded6d8d254631821bf568143d9352c1cdcf7a24be", + "https://deno.land/x/cliffy@v0.25.7/keycode/key_codes.ts": "917f0a2da0dbace08cf29bcfdaaa2257da9fe7e705fff8867d86ed69dfb08cfe", + "https://deno.land/x/cliffy@v0.25.7/keycode/mod.ts": "292d2f295316c6e0da6955042a7b31ab2968ff09f2300541d00f05ed6c2aa2d4", + "https://deno.land/x/cliffy@v0.25.7/mod.ts": "e3515ccf6bd4e4ac89322034e07e2332ed71901e4467ee5bc9d72851893e167b", + "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_input.ts": "737cff2de02c8ce35250f5dd79c67b5fc176423191a2abd1f471a90dd725659e", + "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_list.ts": "79b301bf09eb19f0d070d897f613f78d4e9f93100d7e9a26349ef0bfaa7408d2", + "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_prompt.ts": "8630ce89a66d83e695922df41721cada52900b515385d86def597dea35971bb2", + "https://deno.land/x/cliffy@v0.25.7/prompt/_generic_suggestions.ts": "2a8b619f91e8f9a270811eff557f10f1343a444a527b5fc22c94de832939920c", + "https://deno.land/x/cliffy@v0.25.7/prompt/_utils.ts": "676cca30762656ed1a9bcb21a7254244278a23ffc591750e98a501644b6d2df3", + "https://deno.land/x/cliffy@v0.25.7/prompt/checkbox.ts": "e5a5a9adbb86835dffa2afbd23c6f7a8fe25a9d166485388ef25aba5dc3fbf9e", + "https://deno.land/x/cliffy@v0.25.7/prompt/confirm.ts": "94c8e55de3bbcd53732804420935c432eab29945497d1c47c357d236a89cb5f6", + "https://deno.land/x/cliffy@v0.25.7/prompt/deps.ts": "4c38ab18e55a792c9a136c1c29b2b6e21ea4820c45de7ef4cf517ce94012c57d", + "https://deno.land/x/cliffy@v0.25.7/prompt/figures.ts": "26af0fbfe21497220e4b887bb550fab997498cde14703b98e78faf370fbb4b94", + "https://deno.land/x/cliffy@v0.25.7/prompt/input.ts": "ee45532e0a30c2463e436e08ae291d79d1c2c40872e17364c96d2b97c279bf4d", + "https://deno.land/x/cliffy@v0.25.7/prompt/list.ts": "6780427ff2a932a48c9b882d173c64802081d6cdce9ff618d66ba6504b6abc50", + "https://deno.land/x/cliffy@v0.25.7/prompt/mod.ts": "195aed14d10d279914eaa28c696dec404d576ca424c097a5bc2b4a7a13b66c89", + "https://deno.land/x/cliffy@v0.25.7/prompt/number.ts": "015305a76b50138234dde4fd50eb886c6c7c0baa1b314caf811484644acdc2cf", + "https://deno.land/x/cliffy@v0.25.7/prompt/prompt.ts": "0e7f6a1d43475ee33fb25f7d50749b2f07fc0bcddd9579f3f9af12d05b4a4412", + "https://deno.land/x/cliffy@v0.25.7/prompt/secret.ts": "58745f5231fb2c44294c4acf2511f8c5bfddfa1e12f259580ff90dedea2703d6", + "https://deno.land/x/cliffy@v0.25.7/prompt/select.ts": "1e982eae85718e4e15a3ee10a5ae2233e532d7977d55888f3a309e8e3982b784", + "https://deno.land/x/cliffy@v0.25.7/prompt/toggle.ts": "842c3754a40732f2e80bcd4670098713e402e64bd930e6cab2b787f7ad4d931a", + "https://deno.land/x/cliffy@v0.25.7/table/border.ts": "2514abae4e4f51eda60a5f8c927ba24efd464a590027e900926b38f68e01253c", + "https://deno.land/x/cliffy@v0.25.7/table/cell.ts": "1d787d8006ac8302020d18ec39f8d7f1113612c20801b973e3839de9c3f8b7b3", + "https://deno.land/x/cliffy@v0.25.7/table/deps.ts": "5b05fa56c1a5e2af34f2103fd199e5f87f0507549963019563eae519271819d2", + "https://deno.land/x/cliffy@v0.25.7/table/layout.ts": "46bf10ae5430cf4fbb92f23d588230e9c6336edbdb154e5c9581290562b169f4", + "https://deno.land/x/cliffy@v0.25.7/table/mod.ts": "e74f69f38810ee6139a71132783765feb94436a6619c07474ada45b465189834", + "https://deno.land/x/cliffy@v0.25.7/table/row.ts": "5f519ba7488d2ef76cbbf50527f10f7957bfd668ce5b9169abbc44ec88302645", + "https://deno.land/x/cliffy@v0.25.7/table/table.ts": "ec204c9d08bb3ff1939c5ac7412a4c9ed7d00925d4fc92aff9bfe07bd269258d", + "https://deno.land/x/cliffy@v0.25.7/table/utils.ts": "187bb7dcbcfb16199a5d906113f584740901dfca1007400cba0df7dcd341bc29", + "https://deno.land/x/deno_dom@v0.1.56/build/deno-wasm/deno-wasm-dynamic.js": "fd05e83a855aa68b09396dc47a7638ef526c39917971715ac37e0fc3227db886", + "https://deno.land/x/deno_dom@v0.1.56/build/deno-wasm/deno-wasm.js": "ccde5219e42040b8f7a8b653acc6c8cca197c29f98d6b75f74c48111e34bcbea", + "https://deno.land/x/deno_dom@v0.1.56/build/deno-wasm/deno-wasm_bg-wasm.js": "52c57905a74047d1574f0b96e301ef1d5974cd962705615555b5e46b5ac420e4", + "https://deno.land/x/deno_dom@v0.1.56/build/deno-wasm/deno-wasm_bg.wasm": "b5fbd7ede316f7c13e4a7b927a053a35b475a9acce7c7cae864e91d9b8dd24c7", + "https://deno.land/x/deno_dom@v0.1.56/build/deno-wasm/env.js": "17da784c11b192c591dbf7df21ec6f65f63f45ca9a713e4ca100f3c886b4023f", + "https://deno.land/x/deno_dom@v0.1.56/build/deno-wasm/wbg.js": "b5b6de12bc010fa23438c8a8276067b9044a405da516d0bbe4ae61b7c6d61a22", + "https://deno.land/x/deno_dom@v0.1.56/deno-dom-wasm.ts": "34f0654b452568fbd95b8d1b2a493de19710ac9ceeb6b82f541d09c29613572d", + "https://deno.land/x/deno_dom@v0.1.56/src/api.ts": "0ff5790f0a3eeecb4e00b7d8fbfa319b165962cf6d0182a65ba90f158d74f7d7", + "https://deno.land/x/deno_dom@v0.1.56/src/constructor-lock.ts": "0e7b297e8b9cf921a3b0d3a692ec5fb462c5afc47ec554292e20090b9e16b40a", + "https://deno.land/x/deno_dom@v0.1.56/src/deserialize.ts": "514953418b7ae558ed7361ad9be21013f46cba2f58bd7f4acc90cf1e89f9c8cf", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/document-fragment.ts": "0b915d094830d43b330dc2fb8012b990f2c815773c6cdcd4a9fdff99fe47412e", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/document.ts": "ad584ac4ce6dce03f0ff6ef4b7db86fd598f9c7824da1387f7f2acd7d6948e4a", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/dom-parser.ts": "ab5e8382700bc3936ac67e8d11b7bb9c6999a994ac8b070b7cae94e2d2ecee5a", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/element.ts": "9726ebf139b97ae96671d38da720988cfd6481ff9068ba13f3b0d00e72f0c8c0", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/elements/html-template-element.ts": "1707dfb4cbb145f3bcb94426d7cdedbaa336620d0afed30e99f50fe87ba24a98", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/html-collection.ts": "dcf328e883877f7748d3e20fb6319e739f297a6e24f4b00ec5b1a2f390cfa3c6", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/node-list.ts": "be9793475d82539da8b97a17b6b5538cc723538c10cc5820a23e5e4b2248845d", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/node.ts": "53ada9e4b2ae21f10f5941ff257ed4585920ae392020544648f349c05d15d30c", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/selectors/custom-api.ts": "852696bd58e534bc41bd3be9e2250b60b67cd95fd28ed16b1deff1d548531a71", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/selectors/nwsapi-types.ts": "c43b36c36acc5d32caabaa54fda8c9d239b2b0fcbce9a28efb93c84aa1021698", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/selectors/nwsapi.js": "985d7d8fc1eabbb88946b47a1c44c1b2d4aa79ff23c21424219f1528fa27a2ff", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/selectors/selectors.ts": "83eab57be2290fb48e3130533448c93c6c61239f2a2f3b85f1917f80ca0fdc75", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/selectors/sizzle-types.ts": "78149e2502409989ce861ed636b813b059e16bc267bb543e7c2b26ef43e4798b", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/selectors/sizzle.js": "c3aed60c1045a106d8e546ac2f85cc82e65f62d9af2f8f515210b9212286682a", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/string-cache.ts": "8e935804f7bac244cc70cec90a28c9f6d30fea14c61c2c4ea48fca274376d786", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/utils-types.ts": "96db30e3e4a75b194201bb9fa30988215da7f91b380fca6a5143e51ece2a8436", + "https://deno.land/x/deno_dom@v0.1.56/src/dom/utils.ts": "bc429635e9204051ba1ecc1b212031b5ee7c6bcd95120c91bef696804aa67e74", + "https://deno.land/x/deno_dom@v0.1.56/src/parser.ts": "e06b2300d693e6ae7564e53dfa5c9a9e97fdb8c044c39c52c8b93b5d60860be3", + "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", + "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", + "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", + "https://deno.land/x/esbuild@v0.25.10/mod.js": "1f4e77a22600b008d4ae918043f2fe60fffbc185b475110880eeb6bd4ebc29fe", + "https://deno.land/x/lume@v3.0.11/cli.ts": "fc1acd64c7e4c1e148b4ee8f424594f2aa39f6867a1c3ae0358248a1d4d5cafd", + "https://deno.land/x/lume@v3.0.11/cli/build.ts": "fe8c45d8af369d719fb7378df09387abec21acffa37d04f1129d80d5b20ec1e8", + "https://deno.land/x/lume@v3.0.11/cli/build_worker.ts": "41abe46d5f0191a0beaa52eb88e8b7261b0502f124e489a53361b9bde91bd5f6", + "https://deno.land/x/lume@v3.0.11/cli/cms.ts": "7f3f46c3353661a7679926d0ddcfe3e596f3c97ad2de7f535bde5906e42c3f5a", + "https://deno.land/x/lume@v3.0.11/cli/create.ts": "db576c8cd3aa5a27685c6283573983be59de0be7ec404a9f815b24d4ea61c639", + "https://deno.land/x/lume@v3.0.11/cli/missing_worker_apis.ts": "70625ded7fee5de7d215e0829ce8dc4bb7060f6a496c09db880ebaec8b3efb92", + "https://deno.land/x/lume@v3.0.11/cli/run.ts": "27e7c84c2bcadc3aa4ca4fbad02330f33000dca9a2ef41780bad3676606bc029", + "https://deno.land/x/lume@v3.0.11/cli/upgrade.ts": "a11e7c9024f78c2e7376c57b4a99e389dbf490769779d2d37a4a3ccd6ef27d9e", + "https://deno.land/x/lume@v3.0.11/cli/utils.ts": "71e1ee512aa630cf4b2b3ddd646f1ef5f20b43b538d396ad4e27128f7a8439c3", + "https://deno.land/x/lume@v3.0.11/core/cache.ts": "bec945853a7111babf1fb465090d84dbf4176af0ad465b63b51031134ad6ea2a", + "https://deno.land/x/lume@v3.0.11/core/components.ts": "e5b0d2aca8e630735534a4cb781802fe9c194c3be4e1010c0abe73617c607d84", + "https://deno.land/x/lume@v3.0.11/core/data_loader.ts": "8698a9e9b1aac27147dc835ba89a0e30828c81338eceae86630607d78f146215", + "https://deno.land/x/lume@v3.0.11/core/debugbar.ts": "77d23362c29e69a8f42b8fc13d6129e62ac6ecf339bc0ac4739082c800b44f58", + "https://deno.land/x/lume@v3.0.11/core/events.ts": "e4fd1786eb7dd4a041d7d922779b9edf1ee89e51fd17ba5e756f380879ccb557", + "https://deno.land/x/lume@v3.0.11/core/file.ts": "5836010f28967a83fb02d721e78cfea1f2d125b4349204a8a3d04ac62070c127", + "https://deno.land/x/lume@v3.0.11/core/formats.ts": "e65130e5c5f2e49435619479710c812199b480a9e145fdc6b2bac11cfe6ea08e", + "https://deno.land/x/lume@v3.0.11/core/fs.ts": "ad0b1eb43361f76f36674505ef6b8870176ef386c43ee962e6c750506b40a071", + "https://deno.land/x/lume@v3.0.11/core/loaders/binary.ts": "bb1e1cf3faac49f6007dc6814168dc0f633da17356db18e68862e4b2a87a3f33", + "https://deno.land/x/lume@v3.0.11/core/loaders/json.ts": "ae28e711196215ca2772e9e31f2646ff4c3cf3f66ae75bf8cbcab94de5dbd24f", + "https://deno.land/x/lume@v3.0.11/core/loaders/module.ts": "abcb210fa6724b83407407cd0f7ef90462b35a2017bc135a3d124dd7f38843f6", + "https://deno.land/x/lume@v3.0.11/core/loaders/text.ts": "42860fc3482651fa6cfba18a734bb548d6e6e1163bf1015c2abc447ab150acbd", + "https://deno.land/x/lume@v3.0.11/core/loaders/toml.ts": "72ddfef2deea62815c28e27faa2c5356e09b3109e9547e47a6defea3d3332452", + "https://deno.land/x/lume@v3.0.11/core/loaders/yaml.ts": "241dc41fbe51b92e38dc748eda614c35d80fb8c63a6d40253453c6bb78c9c47e", + "https://deno.land/x/lume@v3.0.11/core/processors.ts": "047a87b0c9a0377ef15daaf1b671a29d541e4bb744c152f02a5c4f0a80fbbb64", + "https://deno.land/x/lume@v3.0.11/core/renderer.ts": "8c69046aa0fdc51fddbbd36c02aeb9b2226a5853f4ae8aeb549c17c43af13e88", + "https://deno.land/x/lume@v3.0.11/core/scopes.ts": "dbdf93d7a9cead84833779e974f190b1379356ec7c0ccd34aa92f917c2cdd2f9", + "https://deno.land/x/lume@v3.0.11/core/scripts.ts": "286969b120d2290ba57a7fdd9b37e587aacf4e4162d92f51f1f1e9e18c864f30", + "https://deno.land/x/lume@v3.0.11/core/searcher.ts": "19530e0149ca925334f98052863a52cdfbbeea9977342b209829999a34e816a6", + "https://deno.land/x/lume@v3.0.11/core/server.ts": "9378853e85df284ecbf23fb846cb106aa6ce7b1673a141f1920851f52904faef", + "https://deno.land/x/lume@v3.0.11/core/site.ts": "20c30402f81d7fe072fd5f385617198b0473cb20df49404c63929fbf210e616c", + "https://deno.land/x/lume@v3.0.11/core/source.ts": "d4dbe91058369ffaf23778da7e8d8287234f3901eed378accb7933e76529a216", + "https://deno.land/x/lume@v3.0.11/core/utils/browsers.ts": "f668e129d6b969663f736d1759b0544577e0ea609727d1009bf8d6e3ec9eef3e", + "https://deno.land/x/lume@v3.0.11/core/utils/cdn.ts": "93c528ff31f7e087ab0d62d09d2b4cf5bdcc7756853a6fa96da15f58016123c9", + "https://deno.land/x/lume@v3.0.11/core/utils/cli_options.ts": "c1fd59592064e76ce71c4d65d7c0de6cafee836fbd8eb42717cc68f56ed36b8a", + "https://deno.land/x/lume@v3.0.11/core/utils/concurrent.ts": "cb0775b3d95f3faa356aa3a3e489dccef8807ed93cc4f84fcf5bc81e87c29504", + "https://deno.land/x/lume@v3.0.11/core/utils/date.ts": "3eb0b0e2ea15a95cdfe737be70cd4f48cbe49401928cb04c25a230f411ab2478", + "https://deno.land/x/lume@v3.0.11/core/utils/digest.ts": "fcd15e7666fb28989b7d5b23c111044cfa71456fa39e26090ee88ac517ee4336", + "https://deno.land/x/lume@v3.0.11/core/utils/dom.ts": "fffb0c0c3ae613282e0447c3e4c122a62f44c776771d525a0ca09759883b4b9e", + "https://deno.land/x/lume@v3.0.11/core/utils/env.ts": "9d0d859303e8cb799d122088f077c54b85258763f2541313be3bf66b58ce33a3", + "https://deno.land/x/lume@v3.0.11/core/utils/format.ts": "bad71315eefd5ad0413841bbe5e8406d636d58d3ed3ef48674655b3a21a0aab0", + "https://deno.land/x/lume@v3.0.11/core/utils/generator.ts": "1e664e9fd4c469e38a0acf5c94fd49dac4f38cb6334563ea4b7fc498b5958877", + "https://deno.land/x/lume@v3.0.11/core/utils/log.ts": "9652d9b7a78fa61d667b6749a35ea02a00927b541d6d4d72e7f3de1881101bde", + "https://deno.land/x/lume@v3.0.11/core/utils/lume_config.ts": "854c9d037e12e50804266717a462f924855fe2b0a819ceed4950414c4f70c5ac", + "https://deno.land/x/lume@v3.0.11/core/utils/lume_version.ts": "c1c63818097e4a273183429ab5b2446a253307f7bc2d0d6361a17b4f230a617d", + "https://deno.land/x/lume@v3.0.11/core/utils/merge_data.ts": "8433920c7e66f27ae558777ed9add637f8c2f67adf9ca2c9ca60d566b9b3583f", + "https://deno.land/x/lume@v3.0.11/core/utils/net.ts": "d0d58c95668effc13669015c219295532f67e4a02396286308c772871b615a9b", + "https://deno.land/x/lume@v3.0.11/core/utils/object.ts": "9b2d1c20503137b612fcbb311b42d1f5500ae170b68f1dca43cc6b057423bc6e", + "https://deno.land/x/lume@v3.0.11/core/utils/page_content.ts": "bbadb588f9d9fcf1a2af156ce4b68974dfad39b65c3c8d42a6f1895b194c7eec", + "https://deno.land/x/lume@v3.0.11/core/utils/page_date.ts": "2a3d9c203df298ca61f568fdf509945f127f990769623c3edfd753d39807b757", + "https://deno.land/x/lume@v3.0.11/core/utils/page_url.ts": "e292cba024f66d35b1b622f9ac13e0910b0ed5ce12c368bd8014e03c1bb0b063", + "https://deno.land/x/lume@v3.0.11/core/utils/path.ts": "7a1d199113928cc35782aa3262cbe6f7a4894bc262d7d300de9385b3da45602f", + "https://deno.land/x/lume@v3.0.11/core/utils/read.ts": "9a0c87095be118bb599eeebba78613d5b3b1cf6c51c3b4c8016af45675bc7303", + "https://deno.land/x/lume@v3.0.11/core/utils/tokens.ts": "201777343e716403bfb1dbbc1a988a85b8d3f12699daaacbe8bbdc3c352a57ff", + "https://deno.land/x/lume@v3.0.11/core/watcher.ts": "9bdb33b2dac840b65545a894c6faecfd31064cc37852dc175081a2048548a89d", + "https://deno.land/x/lume@v3.0.11/core/writer.ts": "e8952538d57c0b587a3e9344b9b10d1b71274aca234b927b05a09c88ac3f4304", + "https://deno.land/x/lume@v3.0.11/deps/base64.ts": "c7b786540e9f348726e6c6f3ba00866328463f7323ebd02d91c6bc7b125e19b0", + "https://deno.land/x/lume@v3.0.11/deps/cli.ts": "3bfb2281c11151afa5f24adcbb0346390c9cb07060d7c233e4a425b31d2427ed", + "https://deno.land/x/lume@v3.0.11/deps/cliffy.ts": "faff0c2ca187ec9fd1ad8660141f85b9d05b5c36bab25b40eb5038c02590a310", + "https://deno.land/x/lume@v3.0.11/deps/colors.ts": "01c038ca4f8ad503ae0c81338223e3e9e1cbcaf0a14cc12bb6cd6c12f249df98", + "https://deno.land/x/lume@v3.0.11/deps/crypto.ts": "0939b1e974472d1db1d611b4160a5a51d796da1368289277d2e26803243959d5", + "https://deno.land/x/lume@v3.0.11/deps/debugbar.ts": "fac82ee05c8fc2c2a7686201d33fce65ffec7f35b7b7c293c7c9f563afa1aa02", + "https://deno.land/x/lume@v3.0.11/deps/deno_loader.ts": "b68402ff8b600efdbdb49565af3fba0342417e49d59bd0d1cfe89b989d5ca1c4", + "https://deno.land/x/lume@v3.0.11/deps/dom.ts": "00090db01ad1bf53d761aaad4e7deccc4841bdf4b5ddadb5e540b0c2e216a390", + "https://deno.land/x/lume@v3.0.11/deps/esbuild.ts": "d231b2f5c6559238e20896e35ebebc81fc41b37942435a43ca20ec2d1d3dbd71", + "https://deno.land/x/lume@v3.0.11/deps/front_matter.ts": "f5e5780d4a0502d50cde1f42a4aa7830756dc9bd0251ba7448cecd1eaa60878f", + "https://deno.land/x/lume@v3.0.11/deps/fs.ts": "95fbbfe19afd419842d0c36f0f480f1ec0e027a3310a389ef79e6cf2baf5aa27", + "https://deno.land/x/lume@v3.0.11/deps/hex.ts": "828718f24a780ff3ade8d0a8a5b57497cb31c257560ef12af99b6eb1a31e3bbd", + "https://deno.land/x/lume@v3.0.11/deps/http.ts": "8b0a9e00933c3506461596d94447e7fdc16dcbe93029a57cbda446207af22b4b", + "https://deno.land/x/lume@v3.0.11/deps/init.ts": "05d45af66ebdfe63e43540618f51ece8f99d98dc49de890f10eeb43abe9ed0f3", + "https://deno.land/x/lume@v3.0.11/deps/jsonc.ts": "79f0eddc3c9e593310eb8e5918eb1506b1c7d7816e4ecb96894f634ecbe626ff", + "https://deno.land/x/lume@v3.0.11/deps/lightningcss.ts": "5f5167c6eb306ef759f0043f8f33f2eaf63c69210aa1aa837505e990ee619c46", + "https://deno.land/x/lume@v3.0.11/deps/markdown_it.ts": "24c1c0fd18c99b9067d9ff5d051f934cb7c3446e6afbad934f6268af8d1ceb4d", + "https://deno.land/x/lume@v3.0.11/deps/path.ts": "f970dec772314a3ffbe672329eeb398e3eb66e875581c62af6db80c41d093aea", + "https://deno.land/x/lume@v3.0.11/deps/postcss.ts": "7ad7485032d40a2bc7977eb71d9431df8d52b53d61153e60b38c29d1c98cb870", + "https://deno.land/x/lume@v3.0.11/deps/purgecss.ts": "524914d3311933aaeb8052c89c361efdc4656bf6fb8aeec9d1b3d7750dbf8d3f", + "https://deno.land/x/lume@v3.0.11/deps/semver.ts": "f850a82a6cf8551e9b927d77f34218961027b1cbe44f3a88c387df62267e31f2", + "https://deno.land/x/lume@v3.0.11/deps/toml.ts": "32830bda333eaf4f1c3d79e4306ba449c17a85b25f94aae9b327d3790a2d1dea", + "https://deno.land/x/lume@v3.0.11/deps/vento.ts": "78db4022ee124fbcfd84caeb6c5a70f2c1e1706ec9f6415d0f1fe2e9aabcba2b", + "https://deno.land/x/lume@v3.0.11/deps/yaml.ts": "a639f4fc44ddcfc87f35e38980bbe9fc8101bf8ce34867522e76cc13cb156611", + "https://deno.land/x/lume@v3.0.11/lint.ts": "23cf68a7cc17edfdb16f2e905de3c5d5a1da541638f04fb8f7d5c762288f2c52", + "https://deno.land/x/lume@v3.0.11/middlewares/basic_auth.ts": "c18f0da9f88be4581e5e3da99214fd7abdad829ab00dbdd2fb3116f1f876add2", + "https://deno.land/x/lume@v3.0.11/middlewares/logger.ts": "c96f1a9f9d5757555b6f141865ce8551ac176f90c8ee3e9ad797b2b400a9a567", + "https://deno.land/x/lume@v3.0.11/middlewares/no_cache.ts": "0119e3ae3a596ab12c42df693b93e5b03dd9608e289d862242751a9739438f35", + "https://deno.land/x/lume@v3.0.11/middlewares/no_cors.ts": "4d24619b5373c98bcc3baf404db47ba088c87ac8538ea1784e58d197b81d4d02", + "https://deno.land/x/lume@v3.0.11/middlewares/not_found.ts": "0f92cd91239444247a1c3dce1bed4e978445687ca76f544a0ccd483a352f761a", + "https://deno.land/x/lume@v3.0.11/middlewares/reload.ts": "1f81573c7574d09bd0dbdf3dd107b98afbdcc63f9aa90edec8f8184157bbe677", + "https://deno.land/x/lume@v3.0.11/middlewares/reload_client.js": "9026da20a25fe58ad36233539ada3f38d56d935c5b0c1c69b7fcd21511efadee", + "https://deno.land/x/lume@v3.0.11/mod.ts": "349b3b7fe199bc6703b1e1fb77f3ab92699fce966da2261d3419a951b1ef2c5d", + "https://deno.land/x/lume@v3.0.11/plugins/esbuild.ts": "6f0a5684a7600b836b318d622ebbf77e4a3a7fa6f5ca366fddcea37871b5f770", + "https://deno.land/x/lume@v3.0.11/plugins/json.ts": "5c49499e56b919ec848d4118ec97dd4fe0a323a6cc4c648dc45ab55297614c12", + "https://deno.land/x/lume@v3.0.11/plugins/lume_cms.ts": "c7375fdc097fcd3edca2cfd05c34dbefd78ec03b5a8569a33c81fdfc6458a5d5", + "https://deno.land/x/lume@v3.0.11/plugins/markdown.ts": "7e82d897c1e35bf119dcd18b6aec7a6ba5aa06848897b34ff9cd161ec7c8757e", + "https://deno.land/x/lume@v3.0.11/plugins/modules.ts": "4e177c0ffe972b9deef10db2bf0ae52b405418af4dbac03db9e7ffbd6a3ec6ae", + "https://deno.land/x/lume@v3.0.11/plugins/paginate.ts": "6a1a9a24d0fabed2f722a6a6f29d98559219c69475685034181816e82d367f2e", + "https://deno.land/x/lume@v3.0.11/plugins/postcss.ts": "5a45a017b1385bb6cc30832d7de21d7d7d146a6c6e037f48446b33e36deafbd3", + "https://deno.land/x/lume@v3.0.11/plugins/purgecss.ts": "c90d540903b589b3a700196bbc05c9a428c91abaea7238e41f6fcd8f38dcd2d4", + "https://deno.land/x/lume@v3.0.11/plugins/search.ts": "5acb5be828bbbd012fb9226cb97ec3e370d43d05aa44d16e7e7d50bab368b442", + "https://deno.land/x/lume@v3.0.11/plugins/source_maps.ts": "10afd5d8617003ed68db9895bc13e57d1742697fa55657e27efd535da6e52c34", + "https://deno.land/x/lume@v3.0.11/plugins/toml.ts": "e5bf35ed4915587acd453f002b00ae9b88c1782cadc25c703d7642a390af43ea", + "https://deno.land/x/lume@v3.0.11/plugins/url.ts": "15f2e80b6fcbf86f8795a3676b8d533bab003ac016ff127e58165a6ac3bffc1a", + "https://deno.land/x/lume@v3.0.11/plugins/vento.ts": "fd60ee80435994bcf88b2cda9c51eaed0ba49a2363f42920675f2d5a0a4a6ab2", + "https://deno.land/x/lume@v3.0.11/plugins/yaml.ts": "d0ebf37c38648172c6b95c502753a3edf60278ab4f6a063f3ca00f31e0dd90cc", + "https://deno.land/x/lume_init@v0.5.6/deps.ts": "49352263d13ebd8296f53470b177c0fd1e67f7642b84b8fe5e19248c3382c998", + "https://deno.land/x/lume_init@v0.5.6/init.ts": "8c530b8e1401e516e4b1f4c3d7e545a33cdb0fbc81f3539ef2bf14ade12eca90", + "https://deno.land/x/lume_init@v0.5.6/mod.ts": "33ef6609ada31cbd25ffa362176c906bc9184b2a03080cfaf04c5dd5b0061443", + "https://deno.land/x/lume_init@v0.5.6/steps/cms.ts": "97e37fef3b04122a512c37859f1f2c0ef58cde0557852f5da86de51acce7592d", + "https://deno.land/x/lume_init@v0.5.6/steps/git.ts": "e55e7857ed89685724555707b9cdc2897ec7bd535644be3f1935c2030331a316", + "https://deno.land/x/lume_init@v0.5.6/steps/load.ts": "82ce37a99d1fd182c355692c7ec91f72658e81172b11bca0829e4ae4da58cdfb", + "https://deno.land/x/lume_init@v0.5.6/steps/lume.ts": "24e23abb2680fa66023434936852f3b927c9d7f34b981a482dc77f5fa963c994", + "https://deno.land/x/lume_init@v0.5.6/steps/plugins.ts": "3b4ea954420c31ae1c07ee5a509aef5433a9b75d67f72661a38cb8f338f94006", + "https://deno.land/x/lume_init@v0.5.6/steps/save.ts": "91b033d3a07b1cef365ce27f5dd3925c90a3923290e2120eaec1c1c79bbc34c2", + "https://deno.land/x/lume_init@v0.5.6/steps/success.ts": "986a3cf2f1ac795f398cf04ea3fded39266fa169e87fae61b3377be1649f04be", + "https://deno.land/x/lume_init@v0.5.6/steps/themes.ts": "284da34d8bd4c7b184ef3f624475f8306b5eca63c3e4784fd268f5e64512dcd2", + "https://deno.land/x/lume_init@v0.5.6/steps/update.ts": "1271c0d6895dfb2acf1495a36676e4f1c454e9ba090a85bf88835e0756f26d3a", + "https://deno.land/x/lume_init@v0.5.6/steps/utils.ts": "6ab641763c6fec0c72ea97438f42f2a22a1c50f4124ecbdf7c20731a62d283df", + "https://deno.land/x/lume_init@v0.5.6/upgrade.ts": "0db7e96794f082391d42fd65ebe0c2cfbb5fce5d625866f1b83138909d7265e1", + "https://deno.land/x/nudd@v0.2.10/deps.ts": "3c605a52a62eefe9892b77e1b91f781562a6b8791c7c9b49ad62d0c761e18f70", + "https://deno.land/x/nudd@v0.2.10/registry/denoland.ts": "15783578eaf6a2edbf348aa1170de7ae4cc8b158ef57096263cd80c35d17ccd6", + "https://deno.land/x/nudd@v0.2.10/registry/jsdelivr.ts": "ceb3f75529a42ae1bc28564ebe13427704c572e1f8edc5bb7ef135779da2cbf0", + "https://deno.land/x/nudd@v0.2.10/registry/utils.ts": "b7aeec1113f0408a9339938662cb24b5404e1ab67ddbfd59b3f9fd60414ba1b2", + "https://deno.land/x/ssx@v0.1.12/jsx-runtime.ts": "a334a1ee3a25de7f3b84b7b8d842bcae40e9116f6edb6ec76cb265712c8a2ab8", + "https://deno.land/x/vento@v2.1.1/core/environment.ts": "36f3e145adfe1af3740cfcfc6ff237d6fe48225d3627123b17022251afbe3074", + "https://deno.land/x/vento@v2.1.1/core/errors.ts": "8606b682b465d598a394feea135dd2f84033b5ef2a61a23d116ccb782a0a547a", + "https://deno.land/x/vento@v2.1.1/core/js.ts": "83084240150d7e8b83e43ec8fcf78564a8ba8599c3d517976efbb11b208903b2", + "https://deno.land/x/vento@v2.1.1/core/reserved.ts": "e3ccafb4e5524b9c51fa14fa0e4bf17fd9bdd791848f17afa2aeb97835c486d1", + "https://deno.land/x/vento@v2.1.1/core/tokenizer.ts": "460faa3de0e561e5046c46528c8bcbfc46b9de576af7ec8c4a8954a61a80ec76", + "https://deno.land/x/vento@v2.1.1/loaders/file.ts": "83f579ac39838642bb45c6ddf48c05c08134cdd95fa7364d004fb443e972196a", + "https://deno.land/x/vento@v2.1.1/mod.ts": "26081287509a87d81c51917795ff94ea80056c43599af8140bb5f666f0aaa767", + "https://deno.land/x/vento@v2.1.1/plugins/auto_trim.ts": "e244c3721c555ef3a247670c01c60dd22fca2ae40a0ce872672d7768e9e9c876", + "https://deno.land/x/vento@v2.1.1/plugins/echo.ts": "5672b58a399ca1475953e32c5282321f7430668bbe963ec40c0a6949c3ec2116", + "https://deno.land/x/vento@v2.1.1/plugins/escape.ts": "1db6eb7b22f88bbf94d21636c9b156ec4868e10bbcfa7dc5eae7113c36ef7b8a", + "https://deno.land/x/vento@v2.1.1/plugins/export.ts": "c1f25c8e1e35027cc21e780f8fb3961febec4771463eb262959fbf035b8b8a63", + "https://deno.land/x/vento@v2.1.1/plugins/for.ts": "a893a3de2c5cab43d5018cb80718ea46377c172d550e7a8ead076f00c58d59bf", + "https://deno.land/x/vento@v2.1.1/plugins/function.ts": "73cec242bb09c840ae2065fbdc2a9a03c4d20a7b996bded387e61e26b68e6171", + "https://deno.land/x/vento@v2.1.1/plugins/if.ts": "db13776a5b9e7988f65c27943c344bec7463c5f3162a06cc4da1a313d7109195", + "https://deno.land/x/vento@v2.1.1/plugins/import.ts": "4555c60768fc061f16c61281c5c6bf163cc5899106d95cdcddde080493372920", + "https://deno.land/x/vento@v2.1.1/plugins/include.ts": "929d35733f73e68068f22378b69195b49a111fcaa39c306017f40028e76b1532", + "https://deno.land/x/vento@v2.1.1/plugins/js.ts": "f5a7fd8cc48cb1b1dc0b396e3d1fee4664cf28932dc5f5f301e38243e0e987dc", + "https://deno.land/x/vento@v2.1.1/plugins/layout.ts": "0cfdccbd069eef61c013a6660cb265afd36e3931ff9051cc70e3133ee74dad64", + "https://deno.land/x/vento@v2.1.1/plugins/mod.ts": "017d5bb3e3c80b7f67271cdf8779686f55916070c5d168a143e6a37c35bcd731", + "https://deno.land/x/vento@v2.1.1/plugins/set.ts": "cf9dfbf68b52039781fd86ec0b9587a8bcd486fdef9f08989719cfdb7fa233d0", + "https://deno.land/x/vento@v2.1.1/plugins/trim.ts": "8d33271327b09ffd8f569ebde85125b1324fa9538a54d6072ac97a9fb5d24264", + "https://deno.land/x/vento@v2.1.1/plugins/unescape.ts": "1c56f0310c7757880df7684fc6b7bf9efd27fdb6b929b89626802f5a99cb93ee", + "https://esm.sh/@borewit/text-codec@0.1.1/denonext/text-codec.mjs": "ea8f41f92f2798340cf2b568602fc1e6f9957e8192265d92d6d06725d0bbfcff", + "https://esm.sh/@borewit/text-codec@0.1.1?target=denonext": "a3d25ab29c470e2e6909ba99905ec372fa66bcc46dd22732dc80b6871603548c", + "https://esm.sh/@borewit/text-codec@0.2.0/denonext/text-codec.mjs": "1fa7af74bcd1b8c1a460836447074360915a752cffe0dbdb8ac472f422a7fbd9", + "https://esm.sh/@borewit/text-codec@0.2.0?target=denonext": "eeee292364426dcdf670db8c94fab465a2308318f20be285ad500336bbc83e5c", + "https://esm.sh/@tokenizer/http@0.9.2/denonext/lib/http-client.mjs": "38999713979a80886994c1ed9b0e20936d019aee9d239b736f7a8bd857b98e1c", + "https://esm.sh/@tokenizer/http@0.9.2/lib/http-client.js": "8b84ffc15c9fe43835c1083d10940ed4397f940c3c2d04d2c9193059aff60964", + "https://esm.sh/@tokenizer/inflate@0.2.7/denonext/inflate.mjs": "ba141d0e59f11c19e2012b5b1fdd3f23f2514495e700fa3013db051e20863f91", + "https://esm.sh/@tokenizer/inflate@0.2.7?target=denonext": "bbc80d9e77f0da6ce0d1adef82539261020623b044410c2a52570bf15b02afe2", + "https://esm.sh/@tokenizer/range@0.12.0/denonext/range.mjs": "7e1e470c638f4e654ce1244e6b256f14b108148273987cbfb8736faa89b30f1b", + "https://esm.sh/@tokenizer/range@0.12.0?target=denonext": "10d3d7b112467d490e1743263d5dba231c069eba0b66f7a2c683d4d64334e19b", + "https://esm.sh/@tokenizer/range@0.13.0/denonext/range.mjs": "da9decedb2697a2dadc4293c2d99fb7de8eb262599a124590f26e62e692008b9", + "https://esm.sh/@tokenizer/range@0.13.0/lib/index.js": "a2b7e610d108dd0dd02da635fc42a4c14cd7a71d52e3704b7fc8e99653cb6416", + "https://esm.sh/content-type@1.0.5/denonext/content-type.mjs": "6fd962b99a80821956a22379aded6b7ca37cfbf95be5639047437e03211e1b56", + "https://esm.sh/content-type@1.0.5?target=denonext": "9eb28c5db95297f2dc51214da829cd8d7b7f3d7bf17dcbf2f60f300173ca8343", + "https://esm.sh/debug@4.4.3/denonext/debug.mjs": "fc4bb8fd2502bf87537c57f5c7c3213eb0258040361828a03970ba7d0933c516", + "https://esm.sh/debug@4.4.3?target=denonext": "9df8b8806c13297f96185899110f8146b8b0c7944847c8392cbfb4e730a634dd", + "https://esm.sh/fflate@0.8.2/denonext/fflate.mjs": "51759ec52e8522bbcd6dce941956a878c733975dcc159618c683c659270a59d3", + "https://esm.sh/fflate@0.8.2?target=denonext": "e0cdd268850b4eac9ef9f188a3b4c5c9dd3ebd0f66c4006a13911b2a0c0512bb", + "https://esm.sh/file-type@21.0.0/denonext/file-type.mjs": "ef71de29ef71c13095f6ce3fd63288a201dbba91cd70d2342436956b9e07d6e9", + "https://esm.sh/file-type@21.0.0?target=denonext": "e797e823999c60b6447441f60631803faee3d9c48baf948dfff9d75b8aae2361", + "https://esm.sh/ieee754@1.2.1/denonext/ieee754.mjs": "6e87635f6124ae21ea355a521f021a04afc277248bd5097dc85edce4d7017f86", + "https://esm.sh/ieee754@1.2.1?target=denonext": "b8839b7562d93529fd58ec105fa4230b816e3dd0a2dd35e3d3b07cae2f16ae97", + "https://esm.sh/media-typer@1.1.0/denonext/media-typer.mjs": "3dc6174765452b8271cad4031a308c8015c67bee4de2b0aff5daec36cb1b766e", + "https://esm.sh/media-typer@1.1.0?target=denonext": "8d2581e00eea8816b1bd4a698ad68e928402b462e697077e9fffb1e960e25d50", + "https://esm.sh/ms@2.1.3/denonext/ms.mjs": "9039464da1f4ae1c2042742d335c82556c048bbe49449b5d0cd5198193afa147", + "https://esm.sh/ms@2.1.3?target=denonext": "36f5aa7503ff0ff44ce9e3155a60362d8d3ae5db8db048be5764a3a515b6a263", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/aiff/AiffParser.mjs": "9589f066b8770e6d8cc5ccd08199158a7c1a5ec8797c8af268071866604a1852", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/apev2/APEv2Parser.mjs": "3595d619cef291dd82f23dd8045b91e995b448fa6727c9f9a802be1f496584e4", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/asf/AsfParser.mjs": "c7a8e7490d82eb8578d0d4f91293761f67d4217125791d2ebc730ec1318bbf85", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/core.mjs": "f8300b19edc7159ae65bbb3b15af99a52dc1067eb022a0acd60ae08e39b88961", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/dsdiff/DsdiffParser.mjs": "1f4e33d399200dbef55286a357dc8ff8e57377f37cfc999c04a6dff646663896", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/dsf/DsfParser.mjs": "eae0eac54d0638200b3cda31612559f3dcbf4d399aee6040364dd559487fea09", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/flac/FlacParser.mjs": "422777d4646ce64e0112c14f649f466b5d6fe2e74e4c582e2652edd03eb48ce0", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/matroska/MatroskaParser.mjs": "b8714d5e77c13319673db0f34a80710de8fdb1fb4047d6405d301fa4bed52ddf", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/mp4/MP4Parser.mjs": "9ab9fcae397cac31d54018ef05e81d639160c80847ed15356b85ec7937c2978a", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/mpeg/MpegParser.mjs": "9cade9c40906843ec596396352af8cb19f466fc78c081b3ba9f7135372304230", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/musepack/MusepackParser.mjs": "e9672e8603a0cd6bcbb52978082fb1cc7533cdebacf5c382f0e398c2badb86e4", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/ogg/OggParser.mjs": "a1801b61660907dec429f162e63367e048fb357cb0c83b0aa3f0cec8a8786554", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/wav/WaveParser.mjs": "99450cb1f387ed495f0880db1352e8aeef60f9f754116abd168c9fdd88d2beca", + "https://esm.sh/music-metadata@11.9.0/denonext/lib/wavpack/WavPackParser.mjs": "1e22a6a0886d48ed4d965e5f2535639bb8acbb2a992c7660f1a73c20256fc64b", + "https://esm.sh/music-metadata@11.9.0/lib/core.js": "5c5c3ef0ef7624e374ffb3cb615bd16fa35d99b67a45b6e76bd2bac6183a999f", + "https://esm.sh/peek-readable@5.4.2/denonext/peek-readable.mjs": "7e784eacb3816872f18606f82cc3f30f2d11e674c5199f84e6cf7d820a3ced23", + "https://esm.sh/peek-readable@5.4.2?target=denonext": "6e1779af5114cfd4f28e9c2d7bbc51a57aef81a958981ed4140761e616e0b8a1", + "https://esm.sh/strtok3@10.3.4/core?target=denonext": "935bc31ec3d554a733ef4dbaec81137e00d3333c81deadf9de58de4c095746d1", + "https://esm.sh/strtok3@10.3.4/denonext/core.mjs": "3183a30c522666467d6d0b5234113fe216e1ec79f4bd135002545458323d62f4", + "https://esm.sh/strtok3@10.3.4/denonext/lib/stream/AbstractStreamReader.mjs": "675c8d26bd83e2e8907a988ba996e1ca45ae10397579d83ed027c14b13e78134", + "https://esm.sh/strtok3@10.3.4/denonext/lib/stream/Errors.mjs": "1459007f24621eb036ba3be2fa592ce39381874722aab8e0302f8d6345a620d6", + "https://esm.sh/strtok3@10.3.4/denonext/lib/stream/index.mjs": "7b6b3d37331712b157f2f6108081831b574d2b6493049aa8738834c4961ae633", + "https://esm.sh/strtok3@10.3.4/denonext/strtok3.mjs": "7228ab10418d0fcee23ac018de8c4843c796471c691c9e940540a23145d589e6", + "https://esm.sh/strtok3@10.3.4?target=denonext": "529358386976a93cbb5db101cda728e9cf25ac9d7d13663ee0d714bd92e54f83", + "https://esm.sh/strtok3@9.1.1/denonext/strtok3.mjs": "72f95ec1079a343006ffc12ca7f7c39fac9f46009265e0ca90a4ea8ee1bb21f7", + "https://esm.sh/strtok3@9.1.1?target=denonext": "d72e11d3fbf73c34c4cd8662a9386d8efa0ac8f8a66c93e3a6f7111ba2761370", + "https://esm.sh/supports-color@10.2.2/denonext/supports-color.mjs": "a263c0229c209e3fed166348cd02e96f08996902c9e8e7c6de4ac50c8ee2a748", + "https://esm.sh/supports-color@10.2.2?target=denonext": "f938c90ae6175cd0ec937dabb90259b115033d9dfde267b69566089f06f92d06", + "https://esm.sh/token-types@6.1.1/denonext/token-types.mjs": "84e80406d9aed2a04e049b9629d9769d66c5665dcf0f781605e0b9a8c6276265", + "https://esm.sh/token-types@6.1.1?target=denonext": "006570441bb0e41ec43ef18fbf171264c2058298ce6511f2ad4a5f488a21312a", + "https://esm.sh/uint8array-extras@1.5.0/denonext/uint8array-extras.mjs": "660b5b7967799e1ab7274f0062b66fc33a86c68df583429d8c52fa0b32332ae9", + "https://esm.sh/uint8array-extras@1.5.0?target=denonext": "04a46e303f8dc389be14886417be8ac772550255f6801a33411511711cdb3546" + }, + "workspace": { + "dependencies": [ + "jsr:@bradenmacdonald/s3-lite-client@~0.9.4", + "jsr:@fry69/deep-diff@~0.1.10", + "jsr:@kunkun/kkrpc@0.6", + "jsr:@mary/ds-queue@~0.1.3", + "jsr:@mys/m-rpc@~0.12.2", + "jsr:@mys/worker-fn@^3.2.1", + "jsr:@okikio/transferables@^1.0.2", + "jsr:@orama/orama@^2.0.6", + "jsr:@std/fs@^1.0.19", + "jsr:@std/path@^1.1.2", + "jsr:@vicary/debounce-microtask@~0.1.8", + "npm:98.css@~0.1.21", + "npm:@atcute/lex-cli@^2.3.1", + "npm:@atcute/lexicons@^1.2.2", + "npm:@js-temporal/polyfill@~0.5.1", + "npm:@phosphor-icons/web@^2.1.2", + "npm:alien-signals@3", + "npm:esbuild-plugins-node-modules-polyfill@^1.7.1", + "npm:fast-average-color@^9.5.0", + "npm:idb-keyval@^6.2.2", + "npm:lit-html@^3.3.1", + "npm:morphdom@^2.7.7", + "npm:query-string@^9.3.1", + "npm:subsonic-api@^3.2.0", + "npm:throttle-debounce@^5.0.2", + "npm:uint8arrays@^5.1.0", + "npm:uri-js@^4.4.1", + "npm:webamp@^2.2.0", + "npm:xxh32@^2.0.5" + ] + } +} diff --git a/elm.json b/elm.json deleted file mode 100644 index 779122f5c..000000000 --- a/elm.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "type": "application", - "source-directories": ["src/Core", "src/Library"], - "elm-version": "0.19.1", - "dependencies": { - "direct": { - "FabienHenon/elm-infinite-list-view": "3.2.0", - "Fresheyeball/elm-return": "7.1.0", - "Gizra/elm-all-set": "1.0.1", - "Gizra/elm-debouncer": "2.0.0", - "Herteby/enum": "1.0.1", - "NoRedInk/elm-json-decode-pipeline": "1.0.1", - "arturopala/elm-monocle": "2.2.0", - "avh4/elm-color": "1.0.0", - "elm/browser": "1.0.2", - "elm/core": "1.0.5", - "elm/file": "1.0.5", - "elm/html": "1.0.0", - "elm/http": "2.0.0", - "elm/json": "1.1.3", - "elm/random": "1.0.0", - "elm/regex": "1.0.0", - "elm/svg": "1.0.1", - "elm/time": "1.0.0", - "elm/url": "1.0.0", - "elm/virtual-dom": "1.0.3", - "elm-community/dict-extra": "2.4.0", - "elm-community/html-extra": "3.4.0", - "elm-community/list-extra": "8.7.0", - "elm-community/maybe-extra": "5.3.0", - "elm-explorations/markdown": "1.0.0", - "icidasset/elm-binary": "2.1.0", - "icidasset/elm-material-icons": "11.0.0", - "icidasset/elm-sha": "2.0.2", - "jinjor/elm-xml-parser": "2.0.0", - "jzxhuang/http-extras": "2.1.0", - "lobanov/elm-taskport": "2.0.1", - "mpizenberg/elm-pointer-events": "4.0.2", - "newlandsvalley/elm-binary-base64": "1.0.3", - "noahzgordon/elm-color-extra": "1.0.2", - "ohanhi/keyboard": "2.0.1", - "prozacchiwawa/elm-urlbase64": "1.0.6", - "robinheghan/murmur3": "1.0.0", - "rtfeldman/elm-hex": "1.0.0", - "ryan-haskell/date-format": "1.0.0", - "truqu/elm-base64": "2.0.4", - "truqu/elm-md5": "1.1.0", - "wernerdegroot/listzipper": "4.0.0", - "ymtszw/elm-xml-decode": "3.2.2" - }, - "indirect": { - "elm/bytes": "1.0.8", - "elm/parser": "1.1.0", - "fredcy/elm-parseint": "2.0.1", - "miniBill/elm-xml-parser": "1.0.1", - "pzp1997/assoc-list": "1.0.0", - "zwilias/elm-utf-tools": "2.0.1" - } - }, - "test-dependencies": { - "direct": {}, - "indirect": {} - } -} diff --git a/gren.json b/gren.json deleted file mode 100644 index 4b56e6f9e..000000000 --- a/gren.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "application", - "platform": "node", - "source-directories": ["src/Static", "system/Build/"], - "gren-version": "0.3.0", - "dependencies": { - "direct": { - "gren-lang/core": "4.0.1", - "gren-lang/node": "3.0.1", - "icidasset/html-gren": "4.1.0", - "icidasset/markdown-gren": "3.1.0", - "icidasset/shikensu-gren": "5.1.0" - }, - "indirect": { - "gren-lang/parser": "3.0.1", - "gren-lang/url": "3.0.0" - } - } -} diff --git a/lexicon.config.js b/lexicon.config.js new file mode 100644 index 000000000..096861a9e --- /dev/null +++ b/lexicon.config.js @@ -0,0 +1,6 @@ +import { defineLexiconConfig } from "@atcute/lex-cli"; + +export default defineLexiconConfig({ + files: ["src/definitions/**/*.json"], + outdir: "src/definitions/", +}); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 38747e77a..000000000 --- a/package-lock.json +++ /dev/null @@ -1,10697 +0,0 @@ -{ - "name": "diffuse", - "version": "3.5.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "diffuse", - "version": "3.5.0", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "@tokenizer/http": "^0.9.1", - "@tokenizer/range": "^0.10.0", - "elm-taskport": "^2.0.1", - "encoding-japanese": "^2.0.0", - "fast-text-encoding": "^1.0.6", - "file-saver": "^2.0.2", - "jszip": "^3.7.1", - "load-script2": "^2.0.5", - "localforage": "^1.10.0", - "lunr": "^2.3.8", - "mediainfo.js": "0.3.3", - "music-metadata": "^10.6.0", - "readable-stream": "^4.5.2", - "remotestoragejs": "^2.0.0-beta.6", - "serve": "^14.2.1", - "throttle-debounce": "^5.0.0", - "timer.js": "^1.0.4", - "tocca": "^2.0.9", - "uint8arrays": "^4.0.10" - }, - "devDependencies": { - "@tauri-apps/api": "^2.0.0-beta.0", - "@tauri-apps/cli": "^2.0.0-beta.0", - "@tauri-apps/plugin-dialog": "^2.0.0-beta.0", - "@tauri-apps/plugin-fs": "^2.0.0-beta.0", - "@tauri-apps/plugin-shell": "^2.0.0-beta.0", - "@types/elm": "^0.19.3", - "@types/file-saver": "^2.0.7", - "@types/lunr": "^2.3.7", - "@types/throttle-debounce": "^5.0.2", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", - "assert": "^2.1.0", - "autoprefixer": "^10.4.19", - "buffer": "^6.0.3", - "elm": "0.19.1-6", - "elm-format": "^0.8.7", - "elm-review": "^2.10.3", - "esbuild": "^0.20.2", - "esbuild-plugin-wasm": "^1.1.0", - "eslint": "^8.56.0", - "events": "^3.3.0", - "gren-lang": "^0.3.0", - "just-install": "^2.0.2", - "postcss": "^8.4.34", - "postcss-custom-properties": "^13.3.4", - "postcss-import": "^16.0.0", - "stream": "^0.0.3", - "tailwindcss": "^3.4.1", - "tailwindcss-animations": "^2.0.0", - "tailwindcss-interaction-variants": "^5.0.0", - "tiny-parse-argv": "^2.4.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@avh4/elm-format-darwin-arm64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-darwin-arm64/-/elm-format-darwin-arm64-0.8.7-2.tgz", - "integrity": "sha512-F5JD44mJ3KX960J5GkXMfh1/dtkXuPcQpX2EToHQKjLTZUfnhZ++ytQQt0gAvrJ0bzoOvhNzjNjUHDA1ruTVbg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@avh4/elm-format-darwin-x64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-darwin-x64/-/elm-format-darwin-x64-0.8.7-2.tgz", - "integrity": "sha512-4pfF1cl0KyTion+7Mg4XKM3yi4Yc7vP76Kt/DotLVGJOSag4ISGic1og2mt8RZZ7XArybBmHNyYkiUbe/cEiCw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@avh4/elm-format-linux-arm64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-linux-arm64/-/elm-format-linux-arm64-0.8.7-2.tgz", - "integrity": "sha512-WkVmuce2zU6s9dupHhqPc886Vaqpea8dZlxv2fpZ4wSzPUbiiKHoHZzoVndMIMTUL0TZukP3Ps0n/lWO5R5+FA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@avh4/elm-format-linux-x64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-linux-x64/-/elm-format-linux-x64-0.8.7-2.tgz", - "integrity": "sha512-kmncfJrTBjVT94JtQvMf4M5Pn2Yl0sZt3wo7AzgFiDnB/CiZ+KjJyXuWM64NeGiv4MQqzPq65tsFXUH1CIJeiQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@avh4/elm-format-win32-x64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-win32-x64/-/elm-format-win32-x64-0.8.7-2.tgz", - "integrity": "sha512-sBdMBGq/8mD8Y5C+fIr5vlb3N50yB7S1MfgeAq2QEbvkr/sKrCZI540i43lZDH9gWsfA1w2W8wCe0penFYzsGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.7.tgz", - "integrity": "sha512-9J4aMRJ7A2WRjaRLvsMeWrL69FmEuijtiW1XlK/sG+V0UJiHVYUyvj9mY4WAXfU/hGIiGOgL8e0jJcRyaZTjDQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz", - "integrity": "sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^2.2.3" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz", - "integrity": "sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/@elm_binaries/darwin_arm64": { - "version": "0.19.1-0", - "resolved": "https://registry.npmjs.org/@elm_binaries/darwin_arm64/-/darwin_arm64-0.19.1-0.tgz", - "integrity": "sha512-mjbsH7BNHEAmoE2SCJFcfk5fIHwFIpxtSgnEAqMsVLpBUFoEtAeX+LQ+N0vSFJB3WAh73+QYx/xSluxxLcL6dA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@elm_binaries/darwin_x64": { - "version": "0.19.1-0", - "resolved": "https://registry.npmjs.org/@elm_binaries/darwin_x64/-/darwin_x64-0.19.1-0.tgz", - "integrity": "sha512-QGUtrZTPBzaxgi9al6nr+9313wrnUVHuijzUK39UsPS+pa+n6CmWyV/69sHZeX9qy6UfeugE0PzF3qcUiy2GDQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@elm_binaries/linux_x64": { - "version": "0.19.1-0", - "resolved": "https://registry.npmjs.org/@elm_binaries/linux_x64/-/linux_x64-0.19.1-0.tgz", - "integrity": "sha512-T1ZrWVhg2kKAsi8caOd3vp/1A3e21VuCpSG63x8rDie50fHbCytTway9B8WHEdnBFv4mYWiA68dzGxYCiFmU2w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@elm_binaries/win32_x64": { - "version": "0.19.1-0", - "resolved": "https://registry.npmjs.org/@elm_binaries/win32_x64/-/win32_x64-0.19.1-0.tgz", - "integrity": "sha512-yDleiXqSE9EcqKtd9SkC/4RIW8I71YsXzMPL79ub2bBPHjWTcoyyeBbYjoOB9SxSlArJ74HaoBApzT6hY7Zobg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@tauri-apps/api": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-beta.0.tgz", - "integrity": "sha512-WLoh/Vk8cgY7XrJV7Vpb6PssReBZWQCATfYBb1aCRDk+sp0NyPwumx6fZ2ECAKzAcs3OeanluwZcajruIW4CPQ==", - "dev": true, - "engines": { - "node": ">= 18", - "npm": ">= 6.6.0", - "yarn": ">= 1.19.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/cli": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0-beta.1.tgz", - "integrity": "sha512-u3AcZPdHsg9qT3e9PSD0H2IVZetQvWuBOyF81CN7/sY+AJGOli7i2d38Bj4wJs50tuMotoseiMcxuyxTlAdBnw==", - "dev": true, - "bin": { - "tauri": "tauri.js" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - }, - "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.0.0-beta.1", - "@tauri-apps/cli-darwin-x64": "2.0.0-beta.1", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.0.0-beta.1", - "@tauri-apps/cli-linux-arm64-gnu": "2.0.0-beta.1", - "@tauri-apps/cli-linux-arm64-musl": "2.0.0-beta.1", - "@tauri-apps/cli-linux-x64-gnu": "2.0.0-beta.1", - "@tauri-apps/cli-linux-x64-musl": "2.0.0-beta.1", - "@tauri-apps/cli-win32-arm64-msvc": "2.0.0-beta.1", - "@tauri-apps/cli-win32-ia32-msvc": "2.0.0-beta.1", - "@tauri-apps/cli-win32-x64-msvc": "2.0.0-beta.1" - } - }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-beta.1.tgz", - "integrity": "sha512-d71utEr9H3fXAI6nKPaPuINpnvMQn+UIscOTzTMcrmIDqptOO0ix8z6C3HSvNxV0OjtlxzNJGWwOb24U0OYrgw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-beta.1.tgz", - "integrity": "sha512-bzsWZjQt5NG1uhbDTGw8Hmvm+J1d+9J7HXMMMwQc4E3kBns95sr4bIoXvgIq3cZYS4uyZOvdhEdjkSGg1c65Lg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-beta.1.tgz", - "integrity": "sha512-FMnZpk4a5D9QgZKkT00P3f4CHEZFpn/b+pWfZJ7vxCdir+Cc1eKOHiqhvmMBEeLlYlQFBaYeAK0EaZWnN82ZJA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-beta.1.tgz", - "integrity": "sha512-0kE65P+6ppeAOFsJV6av5VhkjDv1dcHkObErpjJHpwYowuC3aqaCCnH3biR9gNvcoVUXsCwmMA/BkxUpq9W9/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-beta.1.tgz", - "integrity": "sha512-Wsj1eSrrAVeuFQWJq1gVIA78I8JM50fEsxbrMAOf89ZXpCYxJTNCJkyRQyLB+yHhv9nmhA3a1Mmr5ubhRETy1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-beta.1.tgz", - "integrity": "sha512-LkzLJWg+ud2gWuq8yAWJ3Sahrp79Vbd2Cotbm/RbfMi7RbRV8TQYj4zfUhyFJVnk4nF89kTnwfNxLdTw67CAOw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-beta.1.tgz", - "integrity": "sha512-Ro3PuLSNEZAw9/Rc2CP3k9P7LaUQ2TOFXJeW6G4aCXrd0MlJwlGhhjdZuLbmgzD1rda4dSpZGJPhbYvu8YD7eQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-beta.1.tgz", - "integrity": "sha512-SWNF+5B+lBbW/Kq1wTMVG9x97PqJUOo8eWAr/nlMm3J0lYbTWAa8/ScibaPjq82HiPhv8WCJXlcO6FEqWCoJ2A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-beta.1.tgz", - "integrity": "sha512-NvfP16fSlfq6GLHJH+gAxEsJn+Jvz3HoxMTLxAg7Ra0ycMODFu4xbNn6Hp7Djn297qTHHLYDva4Np6Whw5DUlQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-beta.1.tgz", - "integrity": "sha512-9TKbDQyVHW0p1a7aXQEKg+MhCyFMpzD26puLKOxbTPiTcRUR4lUFq5Bhf1VR5ihoqnZNhJEtuR1mA16ZrIkuKQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/plugin-dialog": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-beta.0.tgz", - "integrity": "sha512-cNxiBwGB6Xgnwy5GyAabwPyvm1iaC7MmrgzcUwB1JaydLjp/Yu3Z0TmkmmEClFrrvIQA+sMcq0C8wwv6i4aedw==", - "dev": true, - "dependencies": { - "@tauri-apps/api": "2.0.0-beta.0" - } - }, - "node_modules/@tauri-apps/plugin-fs": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.0.0-beta.0.tgz", - "integrity": "sha512-oFAA8T5wyg5dmsF/WmjjtUMEnJtzu+p2ElsK2FdwLVSB2AdAdud5izZhd1o63HFjR6waiIYebSI+llzJG7b5MQ==", - "dev": true, - "dependencies": { - "@tauri-apps/api": "2.0.0-beta.0" - } - }, - "node_modules/@tauri-apps/plugin-shell": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-beta.0.tgz", - "integrity": "sha512-AADGPuGP+YM5Ar48XfA8o9k9jtlSBpEVcsbOEwWdifi0oAiVOBXgiy9C1Icsub/tlD3YjK7oT8vDcOXLGYBhUg==", - "dev": true, - "dependencies": { - "@tauri-apps/api": "2.0.0-beta.0" - } - }, - "node_modules/@tokenizer/http": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@tokenizer/http/-/http-0.9.1.tgz", - "integrity": "sha512-jwHLkafS9ZVEt8dFR4s4GEa5d/iKiowtn7uE19hbxIYh7IK1tAkU4eWRXB5GJ0PtPRf2BEDzAqKMhAzDpDlUmw==", - "dependencies": { - "@tokenizer/range": "^0.10.0", - "debug": "^4.3.7", - "strtok3": "^9.0.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/range": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@tokenizer/range/-/range-0.10.0.tgz", - "integrity": "sha512-noYSSTeDdpxLn1WYVB7TZhcq80pA+5b0HdM1Dtvkkk3nlHw4h2nFoaAdrvdwtbjAAT76GIL87/uK5mKE3T+oYg==", - "dependencies": { - "debug": "^4.3.6", - "strtok3": "^9.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "node_modules/@types/elm": { - "version": "0.19.3", - "resolved": "https://registry.npmjs.org/@types/elm/-/elm-0.19.3.tgz", - "integrity": "sha512-1DnHZiIHvDyjL6MHrePqbD3ooLLix13k6ow8gEydFOAXImkcvbzQX0Ri+WJOM7RvgPfmyUe6uQ2Acupb1oL+GA==", - "dev": true - }, - "node_modules/@types/file-saver": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", - "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", - "dev": true - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/lunr": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.7.tgz", - "integrity": "sha512-Tb/kUm38e8gmjahQzdCKhbdsvQ9/ppzHFfsJ0dMs3ckqQsRj+P5IkSAwFTBrBxdyr3E/LoMUUrZngjDYAjiE3A==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.16.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", - "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==", - "dev": true - }, - "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true - }, - "node_modules/@types/throttle-debounce": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz", - "integrity": "sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==", - "dev": true - }, - "node_modules/@types/tv4": { - "version": "1.2.31", - "resolved": "https://registry.npmjs.org/@types/tv4/-/tv4-1.2.31.tgz", - "integrity": "sha512-P97XU07fcpauSw3/fE2Q7eF6bHl4oHhwkikjnM7zlQLENrdC2rZuHSdNlMBhnW82NyBEsVJHII1Jk3d/MtQsQQ==" - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "node_modules/@zeit/schemas": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz", - "integrity": "sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==" - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" - } - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/boxen": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", - "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.0", - "chalk": "^5.0.1", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/boxen/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", - "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001629", - "electron-to-chromium": "^1.4.796", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.16" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", - "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "set-function-length": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001636", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", - "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk-template": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", - "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", - "dependencies": { - "chalk": "^4.1.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/chalk-template?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.8.0.tgz", - "integrity": "sha512-/eG5sJcvEIwxcdYM86k5tPwn0MUzkX5YY3eImTGpJOZgVe4SdTMY14vQpcxgBzJ0wXwAYrS8E+c3uHeK4JNyzQ==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clipboardy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", - "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", - "dependencies": { - "arch": "^2.2.0", - "execa": "^5.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/component-emitter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", - "integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", - "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.2", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.806", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.806.tgz", - "integrity": "sha512-nkoEX2QIB8kwCOtvtgwhXWy2IHVcOLQZu9Qo36uaGB835mdX/h8uLRlosL6QIhLVUnAiicXRW00PwaPZC74Nrg==", - "dev": true - }, - "node_modules/elm": { - "version": "0.19.1-6", - "resolved": "https://registry.npmjs.org/elm/-/elm-0.19.1-6.tgz", - "integrity": "sha512-mKYyierHICPdMx/vhiIacdPmTPnh889gjHOZ75ZAoCxo3lZmSWbGP8HMw78wyctJH0HwvTmeKhlYSWboQNYPeQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "elm": "bin/elm" - }, - "engines": { - "node": ">=7.0.0" - }, - "optionalDependencies": { - "@elm_binaries/darwin_arm64": "0.19.1-0", - "@elm_binaries/darwin_x64": "0.19.1-0", - "@elm_binaries/linux_x64": "0.19.1-0", - "@elm_binaries/win32_x64": "0.19.1-0" - } - }, - "node_modules/elm-format": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/elm-format/-/elm-format-0.8.7.tgz", - "integrity": "sha512-sVzFXfWnb+6rzXK+q3e3Ccgr6/uS5mFbFk1VSmigC+x2XZ28QycAa7lS8owl009ALPhRQk+pZ95Eq5ANjpEZsQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "elm-format": "bin/elm-format" - }, - "optionalDependencies": { - "@avh4/elm-format-darwin-arm64": "0.8.7-2", - "@avh4/elm-format-darwin-x64": "0.8.7-2", - "@avh4/elm-format-linux-arm64": "0.8.7-2", - "@avh4/elm-format-linux-x64": "0.8.7-2", - "@avh4/elm-format-win32-x64": "0.8.7-2" - } - }, - "node_modules/elm-review": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/elm-review/-/elm-review-2.10.3.tgz", - "integrity": "sha512-9gBqLc5Xm3v9Ncpf8y2haEaBZZyMX25vmwQkrNqSIaNYQ2vdGOIx1eyqRtt1xwX5O8pZdqU8IWC/ENyTrOTKbw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "chokidar": "^3.5.2", - "cross-spawn": "^7.0.3", - "elm-tooling": "^1.14.1", - "fastest-levenshtein": "^1.0.16", - "find-up": "^4.1.0", - "folder-hash": "^3.3.0", - "fs-extra": "^9.0.0", - "glob": "^7.1.4", - "got": "^11.8.5", - "graceful-fs": "^4.2.11", - "minimist": "^1.2.6", - "ora": "^5.4.0", - "path-key": "^3.1.1", - "prompts": "^2.2.1", - "rimraf": "^5.0.0", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.1.1", - "which": "^2.0.2", - "wrap-ansi": "^6.2.0" - }, - "bin": { - "elm-review": "bin/elm-review" - }, - "engines": { - "node": ">=10.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/jfmengels" - } - }, - "node_modules/elm-review/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/elm-review/node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/elm-review/node_modules/rimraf": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", - "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", - "dev": true, - "dependencies": { - "glob": "^10.2.5" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/elm-review/node_modules/rimraf/node_modules/glob": { - "version": "10.2.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz", - "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2", - "path-scurry": "^1.7.0" - }, - "bin": { - "glob": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/elm-taskport": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/elm-taskport/-/elm-taskport-2.0.1.tgz", - "integrity": "sha512-8UgIjzmGuoU6Wt6VC0tkJnzvc5xHL5yH7GdN+/QNxaaA3ckoyveCtV0QJqNCRa42bpyR1JhOwLSApwf2Id5xZg==" - }, - "node_modules/elm-tooling": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/elm-tooling/-/elm-tooling-1.15.0.tgz", - "integrity": "sha512-quRE5LGJyrkPBoJ3MvFQ5RGgf80J0L0d3NkduStvXh4TmZuMXNC3Z/l2ZRoq2UTUaNWeYfO1Zx5wns1AvsTrnw==", - "dev": true, - "bin": { - "elm-tooling": "index.js" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/encoding-japanese": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz", - "integrity": "sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==", - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" - } - }, - "node_modules/esbuild-plugin-wasm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esbuild-plugin-wasm/-/esbuild-plugin-wasm-1.1.0.tgz", - "integrity": "sha512-0bQ6+1tUbySSnxzn5jnXHMDvYnT0cN/Wd4Syk8g/sqAIJUg7buTIi22svS3Qz6ssx895NT+TgLPb33xi1OkZig==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "individual", - "url": "https://ko-fi.com/tschrock" - } - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" - }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "dependencies": { - "punycode": "^1.3.2" - } - }, - "node_modules/fast-url-parser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" - }, - "node_modules/file-type": { - "version": "19.6.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", - "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", - "dependencies": { - "get-stream": "^9.0.1", - "strtok3": "^9.0.1", - "token-types": "^6.0.0", - "uint8array-extras": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" - } - }, - "node_modules/file-type/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-type/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/folder-hash": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-3.3.3.tgz", - "integrity": "sha512-SDgHBgV+RCjrYs8aUwCb9rTgbTVuSdzvFmLaChsLre1yf+D64khCW++VYciaByZ8Rm0uKF8R/XEpXuTRSGUM1A==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "graceful-fs": "~4.2.0", - "minimatch": "~3.0.4" - }, - "bin": { - "folder-hash": "bin/folder-hash" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/folder-hash/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", - "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/gren-compiler-library": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/gren-compiler-library/-/gren-compiler-library-0.3.0.tgz", - "integrity": "sha512-CvcdoHYJuqwc4BUKvQPPhtegTUmW5I6rhQlKxxmu+qRbDYFeB/cypM3Xha3aqJr9l14JdmL5fMichENRqg3CBg==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.2", - "xdg-basedir": "^5.1.0" - } - }, - "node_modules/gren-lang": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/gren-lang/-/gren-lang-0.3.0.tgz", - "integrity": "sha512-Gl/0jckyK97BH/lUPj68vP4m05d1QTrLlhZ2ELQgKfDJu8PVKeG+LtRRkdpIzcncCfgR/htWdwt2UkwpPXzalA==", - "dev": true, - "dependencies": { - "gren-compiler-library": "0.3.0" - }, - "bin": { - "gren": "index.js" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-port-reachable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", - "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/jackspeak": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz", - "integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/jszip/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/jszip/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/jszip/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/just-install": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/just-install/-/just-install-2.0.2.tgz", - "integrity": "sha512-zH6aon3V2P8ZbD+njaMB/orHsOyFMgONSpxKtbovNu7Bhb1rD9qhnMkT2Nj91++b9GgqHNbozhUdIMxecmWJaw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "extract-zip": "^2.0.1", - "node-fetch": "^3.3.2" - }, - "bin": { - "just": "bin/just.js", - "just-install": "install.js" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/just-install/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/load-script2": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/load-script2/-/load-script2-2.0.6.tgz", - "integrity": "sha512-pyuw/AR+ycZkRKgkMyXOIf/o2OnRQPc9grjZ04wVOeoJ7SqX97WlcZahl+3/r5qi09L/5d0iCVZ0q4OqNT/v0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "dependencies": { - "lie": "3.1.1" - } - }, - "node_modules/localforage/node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/mediainfo.js": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/mediainfo.js/-/mediainfo.js-0.3.3.tgz", - "integrity": "sha512-+k6cobEQ0Ies/XajglElyHZyguxzEfnztGrZ+bEcuJ4gK74MeN1T0aX1DIFAMRwmFsnJlTJVTiOHJHWZssEJjA==", - "dependencies": { - "yargs": "^17.7.2" - }, - "bin": { - "mediainfo.js": "dist/esm/cli.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", - "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/music-metadata": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-10.6.0.tgz", - "integrity": "sha512-YRTM8KhXZHe1zBsBKA+1JwZBR7SYP/6S/9tm+FRne9dD6SdC6F+s3e5+OugwaChh/zxmAb/yw6SaLAhHZxvIiA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - }, - { - "type": "buymeacoffee", - "url": "https://buymeacoffee.com/borewit" - } - ], - "dependencies": { - "@tokenizer/token": "^0.3.0", - "content-type": "^1.0.5", - "debug": "^4.3.7", - "file-type": "^19.6.0", - "media-typer": "^1.1.0", - "strtok3": "^9.0.1", - "token-types": "^6.0.0", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/optionator/node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-scurry": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz", - "integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==", - "dev": true, - "dependencies": { - "lru-cache": "^9.1.1", - "minipass": "^5.0.0 || ^6.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", - "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/peek-readable": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz", - "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss": { - "version": "8.4.34", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.34.tgz", - "integrity": "sha512-4eLTO36woPSocqZ1zIrFD2K1v6wH7pY1uBh0JIM2KKfrVtGvPFiAku6aNOP0W1Wr9qwnaCsF0Z+CrVnryB2A8Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-custom-properties": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.4.tgz", - "integrity": "sha512-9YN0gg9sG3OH+Z9xBrp2PWRb+O4msw+5Sbp3ZgqrblrwKspXVQe5zr5sVqi43gJGwW/Rv1A483PRQUzQOEewvA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.7", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-import": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.0.0.tgz", - "integrity": "sha512-e77lhVvrD1I2y7dYmBv0k9ULTdArgEYZt97T4w6sFIU5uxIHvDFQlKgUUyY7v7Barj0Yf/zm5A4OquZN7jKm5Q==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" - }, - "engines": { - "node": ">= 14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.11" - }, - "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz", - "integrity": "sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "dependencies": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", - "dependencies": { - "rc": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remotestoragejs": { - "version": "2.0.0-beta.6", - "resolved": "https://registry.npmjs.org/remotestoragejs/-/remotestoragejs-2.0.0-beta.6.tgz", - "integrity": "sha512-xGBmCPe99F4Gs/IEEzvGFjlmOkG48rRCSFz4+7xFNVs4WUJ3nzkm+fQbKelTB2HHErgOLSIyxc2dloqhI5srAQ==", - "dependencies": { - "@types/node": "16.11.59", - "@types/tv4": "^1.2.29", - "esm": "^3.2.25", - "tv4": "^1.3.0", - "webfinger.js": "^2.7.1", - "xhr2": "^0.2.1" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/remotestoragejs/node_modules/@types/node": { - "version": "16.11.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.59.tgz", - "integrity": "sha512-6u+36Dj3aDzhfBVUf/mfmc92OEdzQ2kx2jcXGdigfl70E/neV21ZHE6UCz4MDzTRcVqGAM27fk+DLXvyDsn3Jw==" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serve": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.1.tgz", - "integrity": "sha512-48er5fzHh7GCShLnNyPBRPEjs2I6QBozeGr02gaacROiyS/8ARADlj595j39iZXAqBbJHH/ivJJyPRWY9sQWZA==", - "dependencies": { - "@zeit/schemas": "2.29.0", - "ajv": "8.11.0", - "arg": "5.0.2", - "boxen": "7.0.0", - "chalk": "5.0.1", - "chalk-template": "0.4.0", - "clipboardy": "3.0.0", - "compression": "1.7.4", - "is-port-reachable": "4.0.0", - "serve-handler": "6.1.5", - "update-check": "1.5.4" - }, - "bin": { - "serve": "build/main.js" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/serve-handler": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", - "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", - "range-parser": "1.2.0" - } - }, - "node_modules/serve-handler/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-handler/node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/serve/node_modules/chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/serve/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.2", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stream": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.3.tgz", - "integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==", - "dev": true, - "dependencies": { - "component-emitter": "^2.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strtok3": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.0.1.tgz", - "integrity": "sha512-ERPW+XkvX9W2A+ov07iy+ZFJpVdik04GhDA4eVogiG9hpC97Kem2iucyzhFxbFRvQ5o2UckFtKZdp1hkGvnrEw==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.3.1" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/sucrase": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", - "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", - "dev": true, - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.19.1", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss-animations": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tailwindcss-animations/-/tailwindcss-animations-2.0.0.tgz", - "integrity": "sha512-YLdhPiQeDtpnqWd0J70rvy08MDbsq+BBLrzMmPR1Pe6MJDFYDuk4CYGI14GVgj7xRBGkByzp/rAfBcUWD7zpng==", - "deprecated": "Use Tailwind 1.6's animation utilities", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/tailwindcss-interaction-variants": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tailwindcss-interaction-variants/-/tailwindcss-interaction-variants-5.0.0.tgz", - "integrity": "sha512-Tmx9HjwuNA3G7cLUcebGkNchKkNVIZXBroxa0LBoq/KUb/xEKEM8qugUG7QczSNPhaZzpXmo3IzPkRhf4tDaOQ==", - "deprecated": "Use Tailwind's JIT engine", - "dev": true, - "dependencies": { - "lodash": "^4.17.19", - "postcss-selector-parser": "^6.0.2" - } - }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tailwindcss/node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/throttle-debounce": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz", - "integrity": "sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==", - "engines": { - "node": ">=12.22" - } - }, - "node_modules/timer.js": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/timer.js/-/timer.js-1.0.4.tgz", - "integrity": "sha512-UU/lL02CjPMu8cu29h/uyt5yBqcm0vlx51Je9xCmVcH+TLS+X/+chv4V4rHlb37ZiI4iUVyLuC3QvyoFMzadwQ==" - }, - "node_modules/tiny-parse-argv": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tiny-parse-argv/-/tiny-parse-argv-2.4.0.tgz", - "integrity": "sha512-WTEsnmeHNr99hLQIDA+gnsS+fDsCDITlqgI+zEhx9M6ErPt0heoNZ1PGvql6wcf95sIx40J0MLYXaPveGwtpoA==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tocca": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/tocca/-/tocca-2.0.9.tgz", - "integrity": "sha512-FXofUGAFcgEIEOS3m9Dk9URaY3x+JeerPfVFc8K820PjV0JcCsH98BFxrVOTANC8YvM4QTrmKMol2/818gFmCw==" - }, - "node_modules/token-types": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz", - "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==", - "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" - } - }, - "node_modules/ts-api-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", - "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", - "dev": true, - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, - "node_modules/tv4": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uint8array-extras": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", - "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/uint8arrays": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", - "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", - "dependencies": { - "multiformats": "^12.0.1" - } - }, - "node_modules/uint8arrays/node_modules/multiformats": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", - "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==", - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/update-check": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", - "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", - "dependencies": { - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", - "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/webfinger.js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/webfinger.js/-/webfinger.js-2.7.1.tgz", - "integrity": "sha512-H4RokaE4RC39N3uiRTcjKMmy6yYg06lUPORQHvv8DjowZ6I5SalxUeoqHbtTN3EVBmYP/XHQ8Ow6BLEIpe2DtA==", - "dependencies": { - "xhr2": "0.2.1" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/widest-line/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xhr2": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", - "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true - }, - "@avh4/elm-format-darwin-arm64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-darwin-arm64/-/elm-format-darwin-arm64-0.8.7-2.tgz", - "integrity": "sha512-F5JD44mJ3KX960J5GkXMfh1/dtkXuPcQpX2EToHQKjLTZUfnhZ++ytQQt0gAvrJ0bzoOvhNzjNjUHDA1ruTVbg==", - "dev": true, - "optional": true - }, - "@avh4/elm-format-darwin-x64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-darwin-x64/-/elm-format-darwin-x64-0.8.7-2.tgz", - "integrity": "sha512-4pfF1cl0KyTion+7Mg4XKM3yi4Yc7vP76Kt/DotLVGJOSag4ISGic1og2mt8RZZ7XArybBmHNyYkiUbe/cEiCw==", - "dev": true, - "optional": true - }, - "@avh4/elm-format-linux-arm64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-linux-arm64/-/elm-format-linux-arm64-0.8.7-2.tgz", - "integrity": "sha512-WkVmuce2zU6s9dupHhqPc886Vaqpea8dZlxv2fpZ4wSzPUbiiKHoHZzoVndMIMTUL0TZukP3Ps0n/lWO5R5+FA==", - "dev": true, - "optional": true - }, - "@avh4/elm-format-linux-x64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-linux-x64/-/elm-format-linux-x64-0.8.7-2.tgz", - "integrity": "sha512-kmncfJrTBjVT94JtQvMf4M5Pn2Yl0sZt3wo7AzgFiDnB/CiZ+KjJyXuWM64NeGiv4MQqzPq65tsFXUH1CIJeiQ==", - "dev": true, - "optional": true - }, - "@avh4/elm-format-win32-x64": { - "version": "0.8.7-2", - "resolved": "https://registry.npmjs.org/@avh4/elm-format-win32-x64/-/elm-format-win32-x64-0.8.7-2.tgz", - "integrity": "sha512-sBdMBGq/8mD8Y5C+fIr5vlb3N50yB7S1MfgeAq2QEbvkr/sKrCZI540i43lZDH9gWsfA1w2W8wCe0penFYzsGw==", - "dev": true, - "optional": true - }, - "@csstools/cascade-layer-name-parser": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.7.tgz", - "integrity": "sha512-9J4aMRJ7A2WRjaRLvsMeWrL69FmEuijtiW1XlK/sG+V0UJiHVYUyvj9mY4WAXfU/hGIiGOgL8e0jJcRyaZTjDQ==", - "dev": true, - "requires": {} - }, - "@csstools/css-parser-algorithms": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz", - "integrity": "sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==", - "dev": true, - "requires": {} - }, - "@csstools/css-tokenizer": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz", - "integrity": "sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==", - "dev": true - }, - "@elm_binaries/darwin_arm64": { - "version": "0.19.1-0", - "resolved": "https://registry.npmjs.org/@elm_binaries/darwin_arm64/-/darwin_arm64-0.19.1-0.tgz", - "integrity": "sha512-mjbsH7BNHEAmoE2SCJFcfk5fIHwFIpxtSgnEAqMsVLpBUFoEtAeX+LQ+N0vSFJB3WAh73+QYx/xSluxxLcL6dA==", - "dev": true, - "optional": true - }, - "@elm_binaries/darwin_x64": { - "version": "0.19.1-0", - "resolved": "https://registry.npmjs.org/@elm_binaries/darwin_x64/-/darwin_x64-0.19.1-0.tgz", - "integrity": "sha512-QGUtrZTPBzaxgi9al6nr+9313wrnUVHuijzUK39UsPS+pa+n6CmWyV/69sHZeX9qy6UfeugE0PzF3qcUiy2GDQ==", - "dev": true, - "optional": true - }, - "@elm_binaries/linux_x64": { - "version": "0.19.1-0", - "resolved": "https://registry.npmjs.org/@elm_binaries/linux_x64/-/linux_x64-0.19.1-0.tgz", - "integrity": "sha512-T1ZrWVhg2kKAsi8caOd3vp/1A3e21VuCpSG63x8rDie50fHbCytTway9B8WHEdnBFv4mYWiA68dzGxYCiFmU2w==", - "dev": true, - "optional": true - }, - "@elm_binaries/win32_x64": { - "version": "0.19.1-0", - "resolved": "https://registry.npmjs.org/@elm_binaries/win32_x64/-/win32_x64-0.19.1-0.tgz", - "integrity": "sha512-yDleiXqSE9EcqKtd9SkC/4RIW8I71YsXzMPL79ub2bBPHjWTcoyyeBbYjoOB9SxSlArJ74HaoBApzT6hY7Zobg==", - "dev": true, - "optional": true - }, - "@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", - "dev": true, - "optional": true - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - } - }, - "@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - } - }, - "@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true - }, - "@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "requires": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true - }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - }, - "dependencies": { - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - } - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true - }, - "@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==" - }, - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@tauri-apps/api": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-beta.0.tgz", - "integrity": "sha512-WLoh/Vk8cgY7XrJV7Vpb6PssReBZWQCATfYBb1aCRDk+sp0NyPwumx6fZ2ECAKzAcs3OeanluwZcajruIW4CPQ==", - "dev": true - }, - "@tauri-apps/cli": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0-beta.1.tgz", - "integrity": "sha512-u3AcZPdHsg9qT3e9PSD0H2IVZetQvWuBOyF81CN7/sY+AJGOli7i2d38Bj4wJs50tuMotoseiMcxuyxTlAdBnw==", - "dev": true, - "requires": { - "@tauri-apps/cli-darwin-arm64": "2.0.0-beta.1", - "@tauri-apps/cli-darwin-x64": "2.0.0-beta.1", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.0.0-beta.1", - "@tauri-apps/cli-linux-arm64-gnu": "2.0.0-beta.1", - "@tauri-apps/cli-linux-arm64-musl": "2.0.0-beta.1", - "@tauri-apps/cli-linux-x64-gnu": "2.0.0-beta.1", - "@tauri-apps/cli-linux-x64-musl": "2.0.0-beta.1", - "@tauri-apps/cli-win32-arm64-msvc": "2.0.0-beta.1", - "@tauri-apps/cli-win32-ia32-msvc": "2.0.0-beta.1", - "@tauri-apps/cli-win32-x64-msvc": "2.0.0-beta.1" - } - }, - "@tauri-apps/cli-darwin-arm64": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-beta.1.tgz", - "integrity": "sha512-d71utEr9H3fXAI6nKPaPuINpnvMQn+UIscOTzTMcrmIDqptOO0ix8z6C3HSvNxV0OjtlxzNJGWwOb24U0OYrgw==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-darwin-x64": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-beta.1.tgz", - "integrity": "sha512-bzsWZjQt5NG1uhbDTGw8Hmvm+J1d+9J7HXMMMwQc4E3kBns95sr4bIoXvgIq3cZYS4uyZOvdhEdjkSGg1c65Lg==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-beta.1.tgz", - "integrity": "sha512-FMnZpk4a5D9QgZKkT00P3f4CHEZFpn/b+pWfZJ7vxCdir+Cc1eKOHiqhvmMBEeLlYlQFBaYeAK0EaZWnN82ZJA==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-beta.1.tgz", - "integrity": "sha512-0kE65P+6ppeAOFsJV6av5VhkjDv1dcHkObErpjJHpwYowuC3aqaCCnH3biR9gNvcoVUXsCwmMA/BkxUpq9W9/g==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-linux-arm64-musl": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-beta.1.tgz", - "integrity": "sha512-Wsj1eSrrAVeuFQWJq1gVIA78I8JM50fEsxbrMAOf89ZXpCYxJTNCJkyRQyLB+yHhv9nmhA3a1Mmr5ubhRETy1Q==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-linux-x64-gnu": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-beta.1.tgz", - "integrity": "sha512-LkzLJWg+ud2gWuq8yAWJ3Sahrp79Vbd2Cotbm/RbfMi7RbRV8TQYj4zfUhyFJVnk4nF89kTnwfNxLdTw67CAOw==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-linux-x64-musl": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-beta.1.tgz", - "integrity": "sha512-Ro3PuLSNEZAw9/Rc2CP3k9P7LaUQ2TOFXJeW6G4aCXrd0MlJwlGhhjdZuLbmgzD1rda4dSpZGJPhbYvu8YD7eQ==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-beta.1.tgz", - "integrity": "sha512-SWNF+5B+lBbW/Kq1wTMVG9x97PqJUOo8eWAr/nlMm3J0lYbTWAa8/ScibaPjq82HiPhv8WCJXlcO6FEqWCoJ2A==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-beta.1.tgz", - "integrity": "sha512-NvfP16fSlfq6GLHJH+gAxEsJn+Jvz3HoxMTLxAg7Ra0ycMODFu4xbNn6Hp7Djn297qTHHLYDva4Np6Whw5DUlQ==", - "dev": true, - "optional": true - }, - "@tauri-apps/cli-win32-x64-msvc": { - "version": "2.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-beta.1.tgz", - "integrity": "sha512-9TKbDQyVHW0p1a7aXQEKg+MhCyFMpzD26puLKOxbTPiTcRUR4lUFq5Bhf1VR5ihoqnZNhJEtuR1mA16ZrIkuKQ==", - "dev": true, - "optional": true - }, - "@tauri-apps/plugin-dialog": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-beta.0.tgz", - "integrity": "sha512-cNxiBwGB6Xgnwy5GyAabwPyvm1iaC7MmrgzcUwB1JaydLjp/Yu3Z0TmkmmEClFrrvIQA+sMcq0C8wwv6i4aedw==", - "dev": true, - "requires": { - "@tauri-apps/api": "2.0.0-beta.0" - } - }, - "@tauri-apps/plugin-fs": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.0.0-beta.0.tgz", - "integrity": "sha512-oFAA8T5wyg5dmsF/WmjjtUMEnJtzu+p2ElsK2FdwLVSB2AdAdud5izZhd1o63HFjR6waiIYebSI+llzJG7b5MQ==", - "dev": true, - "requires": { - "@tauri-apps/api": "2.0.0-beta.0" - } - }, - "@tauri-apps/plugin-shell": { - "version": "2.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-beta.0.tgz", - "integrity": "sha512-AADGPuGP+YM5Ar48XfA8o9k9jtlSBpEVcsbOEwWdifi0oAiVOBXgiy9C1Icsub/tlD3YjK7oT8vDcOXLGYBhUg==", - "dev": true, - "requires": { - "@tauri-apps/api": "2.0.0-beta.0" - } - }, - "@tokenizer/http": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@tokenizer/http/-/http-0.9.1.tgz", - "integrity": "sha512-jwHLkafS9ZVEt8dFR4s4GEa5d/iKiowtn7uE19hbxIYh7IK1tAkU4eWRXB5GJ0PtPRf2BEDzAqKMhAzDpDlUmw==", - "requires": { - "@tokenizer/range": "^0.10.0", - "debug": "^4.3.7", - "strtok3": "^9.0.1" - } - }, - "@tokenizer/range": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@tokenizer/range/-/range-0.10.0.tgz", - "integrity": "sha512-noYSSTeDdpxLn1WYVB7TZhcq80pA+5b0HdM1Dtvkkk3nlHw4h2nFoaAdrvdwtbjAAT76GIL87/uK5mKE3T+oYg==", - "requires": { - "debug": "^4.3.6", - "strtok3": "^9.0.0" - } - }, - "@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" - }, - "@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "@types/elm": { - "version": "0.19.3", - "resolved": "https://registry.npmjs.org/@types/elm/-/elm-0.19.3.tgz", - "integrity": "sha512-1DnHZiIHvDyjL6MHrePqbD3ooLLix13k6ow8gEydFOAXImkcvbzQX0Ri+WJOM7RvgPfmyUe6uQ2Acupb1oL+GA==", - "dev": true - }, - "@types/file-saver": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", - "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", - "dev": true - }, - "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/lunr": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.7.tgz", - "integrity": "sha512-Tb/kUm38e8gmjahQzdCKhbdsvQ9/ppzHFfsJ0dMs3ckqQsRj+P5IkSAwFTBrBxdyr3E/LoMUUrZngjDYAjiE3A==", - "dev": true - }, - "@types/node": { - "version": "18.16.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", - "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==", - "dev": true - }, - "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/semver": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", - "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true - }, - "@types/throttle-debounce": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz", - "integrity": "sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==", - "dev": true - }, - "@types/tv4": { - "version": "1.2.31", - "resolved": "https://registry.npmjs.org/@types/tv4/-/tv4-1.2.31.tgz", - "integrity": "sha512-P97XU07fcpauSw3/fE2Q7eF6bHl4oHhwkikjnM7zlQLENrdC2rZuHSdNlMBhnW82NyBEsVJHII1Jk3d/MtQsQQ==" - }, - "@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", - "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - } - }, - "@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - } - }, - "@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - } - }, - "@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, - "@zeit/schemas": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz", - "integrity": "sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==" - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "requires": { - "string-width": "^4.1.0" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==" - }, - "arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" - } - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, - "autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", - "dev": true, - "requires": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - } - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "boxen": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", - "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", - "requires": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.0", - "chalk": "^5.0.1", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" - }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" - }, - "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" - }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", - "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001629", - "electron-to-chromium": "^1.4.796", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.16" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true - }, - "cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "call-bind": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", - "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "set-function-length": "^1.2.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==" - }, - "camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001636", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", - "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chalk-template": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", - "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", - "requires": { - "chalk": "^4.1.2" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==" - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.8.0.tgz", - "integrity": "sha512-/eG5sJcvEIwxcdYM86k5tPwn0MUzkX5YY3eImTGpJOZgVe4SdTMY14vQpcxgBzJ0wXwAYrS8E+c3uHeK4JNyzQ==", - "dev": true - }, - "clipboardy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", - "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", - "requires": { - "arch": "^2.2.0", - "execa": "^5.1.1", - "is-wsl": "^2.2.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true - }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "component-emitter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", - "integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==", - "dev": true - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==" - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true - }, - "debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "requires": { - "ms": "^2.1.3" - } - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true - } - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true - }, - "define-data-property": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", - "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.2", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" - } - }, - "define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "requires": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "electron-to-chromium": { - "version": "1.4.806", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.806.tgz", - "integrity": "sha512-nkoEX2QIB8kwCOtvtgwhXWy2IHVcOLQZu9Qo36uaGB835mdX/h8uLRlosL6QIhLVUnAiicXRW00PwaPZC74Nrg==", - "dev": true - }, - "elm": { - "version": "0.19.1-6", - "resolved": "https://registry.npmjs.org/elm/-/elm-0.19.1-6.tgz", - "integrity": "sha512-mKYyierHICPdMx/vhiIacdPmTPnh889gjHOZ75ZAoCxo3lZmSWbGP8HMw78wyctJH0HwvTmeKhlYSWboQNYPeQ==", - "dev": true, - "requires": { - "@elm_binaries/darwin_arm64": "0.19.1-0", - "@elm_binaries/darwin_x64": "0.19.1-0", - "@elm_binaries/linux_x64": "0.19.1-0", - "@elm_binaries/win32_x64": "0.19.1-0" - } - }, - "elm-format": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/elm-format/-/elm-format-0.8.7.tgz", - "integrity": "sha512-sVzFXfWnb+6rzXK+q3e3Ccgr6/uS5mFbFk1VSmigC+x2XZ28QycAa7lS8owl009ALPhRQk+pZ95Eq5ANjpEZsQ==", - "dev": true, - "requires": { - "@avh4/elm-format-darwin-arm64": "0.8.7-2", - "@avh4/elm-format-darwin-x64": "0.8.7-2", - "@avh4/elm-format-linux-arm64": "0.8.7-2", - "@avh4/elm-format-linux-x64": "0.8.7-2", - "@avh4/elm-format-win32-x64": "0.8.7-2" - } - }, - "elm-review": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/elm-review/-/elm-review-2.10.3.tgz", - "integrity": "sha512-9gBqLc5Xm3v9Ncpf8y2haEaBZZyMX25vmwQkrNqSIaNYQ2vdGOIx1eyqRtt1xwX5O8pZdqU8IWC/ENyTrOTKbw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "chokidar": "^3.5.2", - "cross-spawn": "^7.0.3", - "elm-tooling": "^1.14.1", - "fastest-levenshtein": "^1.0.16", - "find-up": "^4.1.0", - "folder-hash": "^3.3.0", - "fs-extra": "^9.0.0", - "glob": "^7.1.4", - "got": "^11.8.5", - "graceful-fs": "^4.2.11", - "minimist": "^1.2.6", - "ora": "^5.4.0", - "path-key": "^3.1.1", - "prompts": "^2.2.1", - "rimraf": "^5.0.0", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.1.1", - "which": "^2.0.2", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "rimraf": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", - "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", - "dev": true, - "requires": { - "glob": "^10.2.5" - }, - "dependencies": { - "glob": { - "version": "10.2.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz", - "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2", - "path-scurry": "^1.7.0" - } - } - } - } - } - }, - "elm-taskport": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/elm-taskport/-/elm-taskport-2.0.1.tgz", - "integrity": "sha512-8UgIjzmGuoU6Wt6VC0tkJnzvc5xHL5yH7GdN+/QNxaaA3ckoyveCtV0QJqNCRa42bpyR1JhOwLSApwf2Id5xZg==" - }, - "elm-tooling": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/elm-tooling/-/elm-tooling-1.15.0.tgz", - "integrity": "sha512-quRE5LGJyrkPBoJ3MvFQ5RGgf80J0L0d3NkduStvXh4TmZuMXNC3Z/l2ZRoq2UTUaNWeYfO1Zx5wns1AvsTrnw==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "encoding-japanese": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz", - "integrity": "sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true - }, - "esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" - } - }, - "esbuild-plugin-wasm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esbuild-plugin-wasm/-/esbuild-plugin-wasm-1.1.0.tgz", - "integrity": "sha512-0bQ6+1tUbySSnxzn5jnXHMDvYnT0cN/Wd4Syk8g/sqAIJUg7buTIi22svS3Qz6ssx895NT+TgLPb33xi1OkZig==", - "dev": true - }, - "escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "dependencies": { - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - } - } - }, - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" - } - } - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" - }, - "fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "requires": { - "punycode": "^1.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - } - } - }, - "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "requires": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" - }, - "file-type": { - "version": "19.6.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", - "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", - "requires": { - "get-stream": "^9.0.1", - "strtok3": "^9.0.1", - "token-types": "^6.0.0", - "uint8array-extras": "^1.3.0" - }, - "dependencies": { - "get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "requires": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - } - }, - "is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==" - } - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "folder-hash": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-3.3.3.tgz", - "integrity": "sha512-SDgHBgV+RCjrYs8aUwCb9rTgbTVuSdzvFmLaChsLre1yf+D64khCW++VYciaByZ8Rm0uKF8R/XEpXuTRSGUM1A==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "graceful-fs": "~4.2.0", - "minimatch": "~3.0.4" - }, - "dependencies": { - "minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "dependencies": { - "signal-exit": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", - "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", - "dev": true - } - } - }, - "formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "requires": { - "fetch-blob": "^3.1.2" - } - }, - "fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "gren-compiler-library": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/gren-compiler-library/-/gren-compiler-library-0.3.0.tgz", - "integrity": "sha512-CvcdoHYJuqwc4BUKvQPPhtegTUmW5I6rhQlKxxmu+qRbDYFeB/cypM3Xha3aqJr9l14JdmL5fMichENRqg3CBg==", - "dev": true, - "requires": { - "follow-redirects": "^1.15.2", - "xdg-basedir": "^5.1.0" - } - }, - "gren-lang": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/gren-lang/-/gren-lang-0.3.0.tgz", - "integrity": "sha512-Gl/0jckyK97BH/lUPj68vP4m05d1QTrLlhZ2ELQgKfDJu8PVKeG+LtRRkdpIzcncCfgR/htWdwt2UkwpPXzalA==", - "dev": true, - "requires": { - "gren-compiler-library": "0.3.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.2" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, - "requires": { - "function-bind": "^1.1.2" - } - }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true - }, - "is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-port-reachable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", - "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==" - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "jackspeak": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz", - "integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==", - "dev": true, - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, - "jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "just-install": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/just-install/-/just-install-2.0.2.tgz", - "integrity": "sha512-zH6aon3V2P8ZbD+njaMB/orHsOyFMgONSpxKtbovNu7Bhb1rD9qhnMkT2Nj91++b9GgqHNbozhUdIMxecmWJaw==", - "dev": true, - "requires": { - "extract-zip": "^2.0.1", - "node-fetch": "^3.3.2" - }, - "dependencies": { - "node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } - } - } - }, - "keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "requires": { - "immediate": "~3.0.5" - } - }, - "lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "load-script2": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/load-script2/-/load-script2-2.0.6.tgz", - "integrity": "sha512-pyuw/AR+ycZkRKgkMyXOIf/o2OnRQPc9grjZ04wVOeoJ7SqX97WlcZahl+3/r5qi09L/5d0iCVZ0q4OqNT/v0Q==" - }, - "localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "requires": { - "lie": "3.1.1" - }, - "dependencies": { - "lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "requires": { - "immediate": "~3.0.5" - } - } - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" - }, - "media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" - }, - "mediainfo.js": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/mediainfo.js/-/mediainfo.js-0.3.3.tgz", - "integrity": "sha512-+k6cobEQ0Ies/XajglElyHZyguxzEfnztGrZ+bEcuJ4gK74MeN1T0aX1DIFAMRwmFsnJlTJVTiOHJHWZssEJjA==", - "requires": { - "yargs": "^17.7.2" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" - }, - "minipass": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", - "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", - "dev": true - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "music-metadata": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-10.6.0.tgz", - "integrity": "sha512-YRTM8KhXZHe1zBsBKA+1JwZBR7SYP/6S/9tm+FRne9dD6SdC6F+s3e5+OugwaChh/zxmAb/yw6SaLAhHZxvIiA==", - "requires": { - "@tokenizer/token": "^0.3.0", - "content-type": "^1.0.5", - "debug": "^4.3.7", - "file-type": "^19.6.0", - "media-typer": "^1.1.0", - "strtok3": "^9.0.1", - "token-types": "^6.0.0", - "uint8array-extras": "^1.4.0" - } - }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true - }, - "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "dependencies": { - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - } - } - }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - } - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-scurry": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz", - "integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==", - "dev": true, - "requires": { - "lru-cache": "^9.1.1", - "minipass": "^5.0.0 || ^6.0.2" - }, - "dependencies": { - "lru-cache": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", - "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", - "dev": true - } - } - }, - "path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "peek-readable": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz", - "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==" - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "postcss": { - "version": "8.4.34", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.34.tgz", - "integrity": "sha512-4eLTO36woPSocqZ1zIrFD2K1v6wH7pY1uBh0JIM2KKfrVtGvPFiAku6aNOP0W1Wr9qwnaCsF0Z+CrVnryB2A8Q==", - "dev": true, - "requires": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-custom-properties": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.4.tgz", - "integrity": "sha512-9YN0gg9sG3OH+Z9xBrp2PWRb+O4msw+5Sbp3ZgqrblrwKspXVQe5zr5sVqi43gJGwW/Rv1A483PRQUzQOEewvA==", - "dev": true, - "requires": { - "@csstools/cascade-layer-name-parser": "^1.0.7", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-import": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.0.0.tgz", - "integrity": "sha512-e77lhVvrD1I2y7dYmBv0k9ULTdArgEYZt97T4w6sFIU5uxIHvDFQlKgUUyY7v7Barj0Yf/zm5A4OquZN7jKm5Q==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, - "requires": { - "camelcase-css": "^2.0.1" - } - }, - "postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" - } - }, - "postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.11" - } - }, - "postcss-selector-parser": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz", - "integrity": "sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" - } - } - }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, - "requires": { - "pify": "^2.3.0" - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", - "requires": { - "rc": "^1.0.1" - } - }, - "remotestoragejs": { - "version": "2.0.0-beta.6", - "resolved": "https://registry.npmjs.org/remotestoragejs/-/remotestoragejs-2.0.0-beta.6.tgz", - "integrity": "sha512-xGBmCPe99F4Gs/IEEzvGFjlmOkG48rRCSFz4+7xFNVs4WUJ3nzkm+fQbKelTB2HHErgOLSIyxc2dloqhI5srAQ==", - "requires": { - "@types/node": "16.11.59", - "@types/tv4": "^1.2.29", - "esm": "^3.2.25", - "fsevents": "^2.3.2", - "tv4": "^1.3.0", - "webfinger.js": "^2.7.1", - "xhr2": "^0.2.1" - }, - "dependencies": { - "@types/node": { - "version": "16.11.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.59.tgz", - "integrity": "sha512-6u+36Dj3aDzhfBVUf/mfmc92OEdzQ2kx2jcXGdigfl70E/neV21ZHE6UCz4MDzTRcVqGAM27fk+DLXvyDsn3Jw==" - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "requires": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "serve": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.1.tgz", - "integrity": "sha512-48er5fzHh7GCShLnNyPBRPEjs2I6QBozeGr02gaacROiyS/8ARADlj595j39iZXAqBbJHH/ivJJyPRWY9sQWZA==", - "requires": { - "@zeit/schemas": "2.29.0", - "ajv": "8.11.0", - "arg": "5.0.2", - "boxen": "7.0.0", - "chalk": "5.0.1", - "chalk-template": "0.4.0", - "clipboardy": "3.0.0", - "compression": "1.7.4", - "is-port-reachable": "4.0.0", - "serve-handler": "6.1.5", - "update-check": "1.5.4" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==" - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - } - } - }, - "serve-handler": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", - "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", - "requires": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", - "range-parser": "1.2.0" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "~1.33.0" - } - } - } - }, - "set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", - "dev": true, - "requires": { - "define-data-property": "^1.1.2", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true - }, - "stream": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.3.tgz", - "integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==", - "dev": true, - "requires": { - "component-emitter": "^2.0.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strtok3": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.0.1.tgz", - "integrity": "sha512-ERPW+XkvX9W2A+ov07iy+ZFJpVdik04GhDA4eVogiG9hpC97Kem2iucyzhFxbFRvQ5o2UckFtKZdp1hkGvnrEw==", - "requires": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.3.1" - } - }, - "sucrase": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", - "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", - "dev": true, - "requires": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.5.3", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.19.1", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" - }, - "dependencies": { - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - } - } - }, - "tailwindcss-animations": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tailwindcss-animations/-/tailwindcss-animations-2.0.0.tgz", - "integrity": "sha512-YLdhPiQeDtpnqWd0J70rvy08MDbsq+BBLrzMmPR1Pe6MJDFYDuk4CYGI14GVgj7xRBGkByzp/rAfBcUWD7zpng==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "tailwindcss-interaction-variants": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tailwindcss-interaction-variants/-/tailwindcss-interaction-variants-5.0.0.tgz", - "integrity": "sha512-Tmx9HjwuNA3G7cLUcebGkNchKkNVIZXBroxa0LBoq/KUb/xEKEM8qugUG7QczSNPhaZzpXmo3IzPkRhf4tDaOQ==", - "dev": true, - "requires": { - "lodash": "^4.17.19", - "postcss-selector-parser": "^6.0.2" - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, - "throttle-debounce": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz", - "integrity": "sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==" - }, - "timer.js": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/timer.js/-/timer.js-1.0.4.tgz", - "integrity": "sha512-UU/lL02CjPMu8cu29h/uyt5yBqcm0vlx51Je9xCmVcH+TLS+X/+chv4V4rHlb37ZiI4iUVyLuC3QvyoFMzadwQ==" - }, - "tiny-parse-argv": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tiny-parse-argv/-/tiny-parse-argv-2.4.0.tgz", - "integrity": "sha512-WTEsnmeHNr99hLQIDA+gnsS+fDsCDITlqgI+zEhx9M6ErPt0heoNZ1PGvql6wcf95sIx40J0MLYXaPveGwtpoA==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tocca": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/tocca/-/tocca-2.0.9.tgz", - "integrity": "sha512-FXofUGAFcgEIEOS3m9Dk9URaY3x+JeerPfVFc8K820PjV0JcCsH98BFxrVOTANC8YvM4QTrmKMol2/818gFmCw==" - }, - "token-types": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz", - "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==", - "requires": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" - } - }, - "ts-api-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", - "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", - "dev": true, - "requires": {} - }, - "ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true - }, - "tv4": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==" - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, - "peer": true - }, - "uint8array-extras": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", - "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==" - }, - "uint8arrays": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-4.0.10.tgz", - "integrity": "sha512-AnJNUGGDJAgFw/eWu/Xb9zrVKEGlwJJCaeInlf3BkecE/zcTobk5YXYIPNQJO1q5Hh1QZrQQHf0JvcHqz2hqoA==", - "requires": { - "multiformats": "^12.0.1" - }, - "dependencies": { - "multiformats": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.0.1.tgz", - "integrity": "sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==" - } - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", - "dev": true, - "requires": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" - } - }, - "update-check": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", - "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", - "requires": { - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "web-streams-polyfill": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", - "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", - "dev": true - }, - "webfinger.js": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/webfinger.js/-/webfinger.js-2.7.1.tgz", - "integrity": "sha512-H4RokaE4RC39N3uiRTcjKMmy6yYg06lUPORQHvv8DjowZ6I5SalxUeoqHbtTN3EVBmYP/XHQ8Ow6BLEIpe2DtA==", - "requires": { - "xhr2": "0.2.1" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "requires": { - "string-width": "^5.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "requires": { - "ansi-regex": "^6.0.1" - } - } - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "dev": true - }, - "xhr2": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", - "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", - "dev": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index b4a27019d..000000000 --- a/package.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "diffuse", - "description": "A music player that connects to your cloud/distributed storage", - "version": "3.5.0", - "author": "Steven Vandevelde ", - "homepage": "https://diffuse.sh", - "repository": "github:icidasset/diffuse", - "license": "SEE LICENSE IN LICENSE", - "devDependencies": { - "@tauri-apps/api": "^2.0.0-beta.0", - "@tauri-apps/cli": "^2.0.0-beta.0", - "@tauri-apps/plugin-dialog": "^2.0.0-beta.0", - "@tauri-apps/plugin-fs": "^2.0.0-beta.0", - "@tauri-apps/plugin-shell": "^2.0.0-beta.0", - "@types/elm": "^0.19.3", - "@types/file-saver": "^2.0.7", - "@types/lunr": "^2.3.7", - "@types/throttle-debounce": "^5.0.2", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", - "assert": "^2.1.0", - "autoprefixer": "^10.4.19", - "buffer": "^6.0.3", - "elm": "0.19.1-6", - "elm-format": "^0.8.7", - "elm-review": "^2.10.3", - "esbuild": "^0.20.2", - "esbuild-plugin-wasm": "^1.1.0", - "eslint": "^8.56.0", - "events": "^3.3.0", - "gren-lang": "^0.3.0", - "just-install": "^2.0.2", - "postcss": "^8.4.34", - "postcss-custom-properties": "^13.3.4", - "postcss-import": "^16.0.0", - "stream": "^0.0.3", - "tailwindcss": "^3.4.1", - "tailwindcss-animations": "^2.0.0", - "tailwindcss-interaction-variants": "^5.0.0", - "tiny-parse-argv": "^2.4.0" - }, - "dependencies": { - "@tokenizer/http": "^0.9.1", - "@tokenizer/range": "^0.10.0", - "elm-taskport": "^2.0.1", - "encoding-japanese": "^2.0.0", - "fast-text-encoding": "^1.0.6", - "file-saver": "^2.0.2", - "jszip": "^3.7.1", - "load-script2": "^2.0.5", - "localforage": "^1.10.0", - "lunr": "^2.3.8", - "mediainfo.js": "0.3.3", - "music-metadata": "^10.6.0", - "readable-stream": "^4.5.2", - "remotestoragejs": "^2.0.0-beta.6", - "serve": "^14.2.1", - "throttle-debounce": "^5.0.0", - "timer.js": "^1.0.4", - "tocca": "^2.0.9", - "uint8arrays": "^4.0.10" - }, - "scripts": { - "tauri": "tauri" - } -} diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore deleted file mode 100644 index f4dfb82b2..000000000 --- a/src-tauri/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock deleted file mode 100644 index 95073b87a..000000000 --- a/src-tauri/Cargo.lock +++ /dev/null @@ -1,5549 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" - -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "ashpd" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" -dependencies = [ - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.8.5", - "raw-window-handle", - "serde", - "serde_repr", - "tokio", - "url", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "zbus", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "async-trait" -version = "0.1.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" -dependencies = [ - "serde", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2", -] - -[[package]] -name = "brotli" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "bytemuck" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.8.0", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8769706aad5d996120af43197bf46ef6ad0fda35216b4505f926a365a232d924" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.11", -] - -[[package]] -name = "cargo_toml" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbd1fe9db3ebf71b89060adaf7b0504c2d6a425cf061313099547e382c2e472" -dependencies = [ - "serde", - "toml 0.8.20", -] - -[[package]] -name = "cc" -version = "1.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" -dependencies = [ - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-targets 0.52.6", -] - -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - -[[package]] -name = "cocoa" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" -dependencies = [ - "bitflags 2.8.0", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" -dependencies = [ - "bitflags 2.8.0", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.8.0", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.8.0", - "core-foundation", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.98", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.98", -] - -[[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.98", -] - -[[package]] -name = "darling_macro" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derive_more" -version = "0.99.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.98", -] - -[[package]] -name = "diffuse" -version = "3.5.0" -dependencies = [ - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-dialog", - "tauri-plugin-fs", - "tauri-plugin-localhost", - "tauri-plugin-positioner", - "tauri-plugin-shell", - "tauri-plugin-window-state", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.6", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.5.0", - "windows-sys 0.59.0", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.6", -] - -[[package]] -name = "dlopen2" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" - -[[package]] -name = "embed-resource" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.8.20", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" -dependencies = [ - "serde", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "flate2" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.8.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.0", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "html5ever" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" -dependencies = [ - "log", - "mac", - "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "http" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" -dependencies = [ - "bytes", - "fnv", - "itoa 1.0.14", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "httparse", - "itoa 1.0.14", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown 0.15.2", - "serde", -] - -[[package]] -name = "infer" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847" -dependencies = [ - "cfb", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.8.0", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "kuchikiki" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 1.9.3", - "matches", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading 0.7.4", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.169" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.8.0", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "markup5ever" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" -dependencies = [ - "log", - "phf 0.10.1", - "phf_codegen 0.10.0", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - -[[package]] -name = "muda" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdae9c00e61cc0579bcac625e8ad22104c60548a025bfc972dc83868a28e1484" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 1.0.69", - "windows-sys 0.59.0", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.8.0", - "jni-sys", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.8.0", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" -dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.8.0", - "block2", - "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2", - "objc2", - "objc2-contacts", - "objc2-foundation", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.8.0", - "block2", - "dispatch", - "libc", - "objc2", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" -dependencies = [ - "bitflags 2.8.0", - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" - -[[package]] -name = "open" -version = "5.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "os_pipe" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.1", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "plist" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" -dependencies = [ - "base64 0.22.1", - "indexmap 2.7.1", - "quick-xml 0.32.0", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.7", -] - -[[package]] -name = "proc-macro-crate" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" -dependencies = [ - "toml_edit 0.22.24", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" -dependencies = [ - "memchr", -] - -[[package]] -name = "quinn" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.11", - "tokio", - "tracing", -] - -[[package]] -name = "quinn-proto" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" -dependencies = [ - "bytes", - "getrandom 0.2.15", - "rand 0.8.5", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.11", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "quote" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "redox_users" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 2.0.11", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "reqwest" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", - "windows-registry", -] - -[[package]] -name = "rfd" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a24763657bff09769a8ccf12c8b8a50416fb035fe199263b4c5071e4e3f006f" -dependencies = [ - "ashpd", - "block2", - "core-foundation", - "core-foundation-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "log", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "ring" -version = "0.17.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.8.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" -dependencies = [ - "web-time", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" - -[[package]] -name = "ryu" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schemars" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars_derive" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.98", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "selectors" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more", - "fxhash", - "log", - "matches", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", - "thin-slice", -] - -[[package]] -name = "semver" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-untagged" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2676ba99bd82f75cae5cbd2c8eda6fa0b8760f18978ea840e980dd5567b5c5b6" -dependencies = [ - "erased-serde", - "serde", - "typeid", -] - -[[package]] -name = "serde_derive" -version = "1.0.217" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_json" -version = "1.0.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" -dependencies = [ - "itoa 1.0.14", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa 1.0.14", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.7.1", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "servo_arc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shared_child" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" - -[[package]] -name = "socket2" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "softbuffer" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" -dependencies = [ - "bytemuck", - "cfg_aliases", - "core-graphics", - "foreign-types", - "js-sys", - "log", - "objc2", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "wasm-bindgen", - "web-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "string_cache" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244292f3441c89febe5b5bdfbb6863aeaf4f64da810ea3050fd927b27b8d92ce" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.20", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3731d04d4ac210cd5f344087733943b9bfb1a32654387dad4d1c70de21aee2c9" -dependencies = [ - "bitflags 2.8.0", - "cocoa", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "windows-core 0.58.0", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a998b6be84104ca05c7e9a21f2180ddec020c8b84ea59a8fc8530a2a19588d" -dependencies = [ - "anyhow", - "bytes", - "dirs 6.0.0", - "dunce", - "embed_plist", - "futures-util", - "getrandom 0.2.15", - "glob", - "gtk", - "heck 0.5.0", - "http", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.11", - "tokio", - "tray-icon", - "url", - "urlpattern", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e950124f6779c6cf98e3260c7a6c8488a74aa6350dd54c6950fdaa349bca2df" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs 5.0.1", - "glob", - "heck 0.5.0", - "json-patch", - "schemars", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "toml 0.8.20", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f77894f9ddb5cb6c04fcfe8c8869ebe0aded4dabf19917118d48be4a95599ab5" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.98", - "tauri-utils", - "thiserror 2.0.11", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3240a5caed760a532e8f687be6f05b2c7d11a1d791fb53ccc08cfeb3e5308736" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.98", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5841b9a0200e954ef7457f8d327091424328891e267a97b641dc246cc54d0dec" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars", - "serde", - "serde_json", - "tauri-utils", - "toml 0.8.20", - "walkdir", -] - -[[package]] -name = "tauri-plugin-dialog" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b59fd750551b1066744ab956a1cd6b1ea3e1b3763b0b9153ac27a044d596426" -dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.11", - "url", -] - -[[package]] -name = "tauri-plugin-fs" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1edf18000f02903a7c2e5997fb89aca455ecbc0acc15c6535afbb883be223" -dependencies = [ - "anyhow", - "dunce", - "glob", - "percent-encoding", - "schemars", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.11", - "toml 0.8.20", - "url", - "uuid", -] - -[[package]] -name = "tauri-plugin-localhost" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f19f292368d8ec7bbbfd4b3a473636385ae2ce6c0a70d40ef0f5a0f024c85df" -dependencies = [ - "http", - "log", - "serde", - "serde_json", - "tauri", - "thiserror 2.0.11", - "tiny_http", -] - -[[package]] -name = "tauri-plugin-positioner" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c95b371d489bee3d1be5e5bd1538080ad408317fcc2d8546d24b290249f7bb5" -dependencies = [ - "log", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "thiserror 2.0.11", -] - -[[package]] -name = "tauri-plugin-shell" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c50a63e60fb8925956cc5b7569f4b750ac197a4d39f13b8dd46ea8e2bad79" -dependencies = [ - "encoding_rs", - "log", - "open", - "os_pipe", - "regex", - "schemars", - "serde", - "serde_json", - "shared_child", - "tauri", - "tauri-plugin", - "thiserror 2.0.11", - "tokio", -] - -[[package]] -name = "tauri-plugin-window-state" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e344b512b0d99d9d06225f235d87d6c66d89496a3bf323d9b578d940596e6c" -dependencies = [ - "bitflags 2.8.0", - "log", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.11", -] - -[[package]] -name = "tauri-runtime" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2274ef891ccc0a8d318deffa9d70053f947664d12d58b9c0d1ae5e89237e01f7" -dependencies = [ - "dpi", - "gtk", - "http", - "jni", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.11", - "url", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3707b40711d3b9f6519150869e358ffbde7c57567fb9b5a8b51150606939b2a0" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fb10e7cc97456b2d5b9c03e335b5de5da982039a303a20d10006885e4523a0" -dependencies = [ - "brotli", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.11", - "toml 0.8.20", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" -dependencies = [ - "embed-resource", - "toml 0.7.8", -] - -[[package]] -name = "tempfile" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40f762a77d2afa88c2d919489e390a12bdd261ed568e60cfa7e48d4e20f0d33" -dependencies = [ - "cfg-if", - "fastrand", - "getrandom 0.3.1", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" -dependencies = [ - "thiserror-impl 2.0.11", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "itoa 1.0.14", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.24", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" -dependencies = [ - "indexmap 2.7.1", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap 2.7.1", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.7.2", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48a05076dd272615d03033bf04f480199f7d1b66a8ac64d75c625fc4a70c06b" -dependencies = [ - "core-graphics", - "crossbeam-channel", - "dirs 5.0.1", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 1.0.69", - "windows-sys 0.59.0", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typeid" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicode-ident" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" -dependencies = [ - "getrandom 0.3.1", - "serde", -] - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.98", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wayland-backend" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" -dependencies = [ - "cc", - "downcast-rs", - "rustix", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" -dependencies = [ - "bitflags 2.8.0", - "rustix", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" -dependencies = [ - "bitflags 2.8.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" -dependencies = [ - "proc-macro2", - "quick-xml 0.37.2", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" -dependencies = [ - "dlib", - "log", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webpki-roots" -version = "0.26.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webview2-com" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823e7ebcfaea51e78f72c87fc3b65a1e602c321f407a0b36dbb327d7bb7cd921" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.58.0", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "webview2-com-sys" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a82bce72db6e5ee83c68b5de1e2cd6ea195b9fbff91cb37df5884cbe3222df4" -dependencies = [ - "thiserror 1.0.69", - "windows", - "windows-core 0.58.0", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831ad7678290beae36be6f9fad9234139c7f00f3b536347de7745621716be82d" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows-version" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12476c23a74725c539b24eae8bfc0dac4029c39cdb561d9f23616accd4ae26d" -dependencies = [ - "windows-targets 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.8.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "wry" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e33c08b174442ff80d5c791020696f9f8b4e4a87b8cfc7494aad6167ec44e1" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.11", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows-core 0.58.0", - "windows-version", - "x11-dl", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236" -dependencies = [ - "async-broadcast", - "async-recursion", - "async-trait", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "nix", - "ordered-stream", - "serde", - "serde_repr", - "static_assertions", - "tokio", - "tracing", - "uds_windows", - "windows-sys 0.59.0", - "winnow 0.7.2", - "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0" -dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.98", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" -dependencies = [ - "serde", - "static_assertions", - "winnow 0.7.2", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - -[[package]] -name = "zvariant" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2df9ee044893fcffbdc25de30546edef3e32341466811ca18421e3cd6c5a3ac" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "url", - "winnow 0.7.2", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74170caa85b8b84cc4935f2d56a57c7a15ea6185ccdd7eadb57e6edd90f94b2f" -dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.98", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "static_assertions", - "syn 2.0.98", - "winnow 0.7.2", -] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml deleted file mode 100644 index a39e1f8e1..000000000 --- a/src-tauri/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "diffuse" -version = "3.5.0" -description = "A music player that connects to your cloud/distributed storage" -authors = ["Steven Vandevelde"] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[build-dependencies] -tauri-build = { version = "2.0.5", features = [] } - -[dependencies] -tauri = { version = "2.2.5", features = ["unstable"] } -tauri-plugin-dialog = "2.2.0" -tauri-plugin-fs = "2.2.0" -tauri-plugin-localhost = "2.2.0" -tauri-plugin-positioner = "2.2.0" -tauri-plugin-shell = "2.2.0" -tauri-plugin-window-state = "2.2.1" -serde_json = "1.0" - -[features] -# this feature is used for production builds or when `devPath` points to the filesystem -# DO NOT REMOVE!! -custom-protocol = ["tauri/custom-protocol"] - -# [lib] -# crate-type = ["staticlib", "cdylib", "rlib"] diff --git a/src-tauri/build.rs b/src-tauri/build.rs deleted file mode 100644 index d860e1e6a..000000000 --- a/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json deleted file mode 100644 index 8c59b539d..000000000 --- a/src-tauri/capabilities/main.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "main-capability", - "description": "Capability for the main window", - "context": "local", - "windows": ["main"], - "permissions": [ - "core:path:default", - "core:event:default", - "core:window:default", - "core:app:default", - "dialog:allow-ask", - "dialog:allow-confirm", - "dialog:allow-message", - "dialog:allow-open", - "dialog:allow-save", - "fs:allow-download-write", - "core:resources:default", - "core:menu:default", - "core:tray:default", - "shell:allow-open" - ], - "platforms": ["linux", "macOS", "windows", "android", "iOS"] -} diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json deleted file mode 100644 index e6d0fa5b6..000000000 --- a/src-tauri/gen/schemas/acl-manifests.json +++ /dev/null @@ -1 +0,0 @@ -{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null},"fs":{"default_permission":{"identifier":"default","description":"This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n","permissions":["create-app-specific-dirs","read-app-specific-dirs-recursive","deny-default"]},"permissions":{"allow-copy-file":{"identifier":"allow-copy-file","description":"Enables the copy_file command without any pre-configured scope.","commands":{"allow":["copy_file"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-exists":{"identifier":"allow-exists","description":"Enables the exists command without any pre-configured scope.","commands":{"allow":["exists"],"deny":[]}},"allow-fstat":{"identifier":"allow-fstat","description":"Enables the fstat command without any pre-configured scope.","commands":{"allow":["fstat"],"deny":[]}},"allow-ftruncate":{"identifier":"allow-ftruncate","description":"Enables the ftruncate command without any pre-configured scope.","commands":{"allow":["ftruncate"],"deny":[]}},"allow-lstat":{"identifier":"allow-lstat","description":"Enables the lstat command without any pre-configured scope.","commands":{"allow":["lstat"],"deny":[]}},"allow-mkdir":{"identifier":"allow-mkdir","description":"Enables the mkdir command without any pre-configured scope.","commands":{"allow":["mkdir"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-read":{"identifier":"allow-read","description":"Enables the read command without any pre-configured scope.","commands":{"allow":["read"],"deny":[]}},"allow-read-dir":{"identifier":"allow-read-dir","description":"Enables the read_dir command without any pre-configured scope.","commands":{"allow":["read_dir"],"deny":[]}},"allow-read-file":{"identifier":"allow-read-file","description":"Enables the read_file command without any pre-configured scope.","commands":{"allow":["read_file"],"deny":[]}},"allow-read-text-file":{"identifier":"allow-read-text-file","description":"Enables the read_text_file command without any pre-configured scope.","commands":{"allow":["read_text_file"],"deny":[]}},"allow-read-text-file-lines":{"identifier":"allow-read-text-file-lines","description":"Enables the read_text_file_lines command without any pre-configured scope.","commands":{"allow":["read_text_file_lines","read_text_file_lines_next"],"deny":[]}},"allow-read-text-file-lines-next":{"identifier":"allow-read-text-file-lines-next","description":"Enables the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":["read_text_file_lines_next"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-rename":{"identifier":"allow-rename","description":"Enables the rename command without any pre-configured scope.","commands":{"allow":["rename"],"deny":[]}},"allow-seek":{"identifier":"allow-seek","description":"Enables the seek command without any pre-configured scope.","commands":{"allow":["seek"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"allow-stat":{"identifier":"allow-stat","description":"Enables the stat command without any pre-configured scope.","commands":{"allow":["stat"],"deny":[]}},"allow-truncate":{"identifier":"allow-truncate","description":"Enables the truncate command without any pre-configured scope.","commands":{"allow":["truncate"],"deny":[]}},"allow-unwatch":{"identifier":"allow-unwatch","description":"Enables the unwatch command without any pre-configured scope.","commands":{"allow":["unwatch"],"deny":[]}},"allow-watch":{"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]}},"allow-write":{"identifier":"allow-write","description":"Enables the write command without any pre-configured scope.","commands":{"allow":["write"],"deny":[]}},"allow-write-file":{"identifier":"allow-write-file","description":"Enables the write_file command without any pre-configured scope.","commands":{"allow":["write_file","open","write"],"deny":[]}},"allow-write-text-file":{"identifier":"allow-write-text-file","description":"Enables the write_text_file command without any pre-configured scope.","commands":{"allow":["write_text_file"],"deny":[]}},"create-app-specific-dirs":{"identifier":"create-app-specific-dirs","description":"This permissions allows to create the application specific directories.\n","commands":{"allow":["mkdir","scope-app-index"],"deny":[]}},"deny-copy-file":{"identifier":"deny-copy-file","description":"Denies the copy_file command without any pre-configured scope.","commands":{"allow":[],"deny":["copy_file"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-exists":{"identifier":"deny-exists","description":"Denies the exists command without any pre-configured scope.","commands":{"allow":[],"deny":["exists"]}},"deny-fstat":{"identifier":"deny-fstat","description":"Denies the fstat command without any pre-configured scope.","commands":{"allow":[],"deny":["fstat"]}},"deny-ftruncate":{"identifier":"deny-ftruncate","description":"Denies the ftruncate command without any pre-configured scope.","commands":{"allow":[],"deny":["ftruncate"]}},"deny-lstat":{"identifier":"deny-lstat","description":"Denies the lstat command without any pre-configured scope.","commands":{"allow":[],"deny":["lstat"]}},"deny-mkdir":{"identifier":"deny-mkdir","description":"Denies the mkdir command without any pre-configured scope.","commands":{"allow":[],"deny":["mkdir"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-read":{"identifier":"deny-read","description":"Denies the read command without any pre-configured scope.","commands":{"allow":[],"deny":["read"]}},"deny-read-dir":{"identifier":"deny-read-dir","description":"Denies the read_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["read_dir"]}},"deny-read-file":{"identifier":"deny-read-file","description":"Denies the read_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_file"]}},"deny-read-text-file":{"identifier":"deny-read-text-file","description":"Denies the read_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file"]}},"deny-read-text-file-lines":{"identifier":"deny-read-text-file-lines","description":"Denies the read_text_file_lines command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines"]}},"deny-read-text-file-lines-next":{"identifier":"deny-read-text-file-lines-next","description":"Denies the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines_next"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-rename":{"identifier":"deny-rename","description":"Denies the rename command without any pre-configured scope.","commands":{"allow":[],"deny":["rename"]}},"deny-seek":{"identifier":"deny-seek","description":"Denies the seek command without any pre-configured scope.","commands":{"allow":[],"deny":["seek"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}},"deny-stat":{"identifier":"deny-stat","description":"Denies the stat command without any pre-configured scope.","commands":{"allow":[],"deny":["stat"]}},"deny-truncate":{"identifier":"deny-truncate","description":"Denies the truncate command without any pre-configured scope.","commands":{"allow":[],"deny":["truncate"]}},"deny-unwatch":{"identifier":"deny-unwatch","description":"Denies the unwatch command without any pre-configured scope.","commands":{"allow":[],"deny":["unwatch"]}},"deny-watch":{"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]}},"deny-webview-data-linux":{"identifier":"deny-webview-data-linux","description":"This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-webview-data-windows":{"identifier":"deny-webview-data-windows","description":"This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]}},"deny-write":{"identifier":"deny-write","description":"Denies the write command without any pre-configured scope.","commands":{"allow":[],"deny":["write"]}},"deny-write-file":{"identifier":"deny-write-file","description":"Denies the write_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_file"]}},"deny-write-text-file":{"identifier":"deny-write-text-file","description":"Denies the write_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text_file"]}},"read-all":{"identifier":"read-all","description":"This enables all read related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists","watch","unwatch"],"deny":[]}},"read-app-specific-dirs-recursive":{"identifier":"read-app-specific-dirs-recursive","description":"This permission allows recursive read functionality on the application\nspecific base directories. \n","commands":{"allow":["read_dir","read_file","read_text_file","read_text_file_lines","read_text_file_lines_next","exists","scope-app-recursive"],"deny":[]}},"read-dirs":{"identifier":"read-dirs","description":"This enables directory read and file metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists"],"deny":[]}},"read-files":{"identifier":"read-files","description":"This enables file read related commands without any pre-configured accessible paths.","commands":{"allow":["read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists"],"deny":[]}},"read-meta":{"identifier":"read-meta","description":"This enables all index or metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists","size"],"deny":[]}},"scope":{"identifier":"scope","description":"An empty permission you can use to modify the global scope.","commands":{"allow":[],"deny":[]}},"scope-app":{"identifier":"scope-app","description":"This scope permits access to all files and list content of top level directories in the application folders.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"},{"path":"$APPDATA"},{"path":"$APPDATA/*"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"},{"path":"$APPCACHE"},{"path":"$APPCACHE/*"},{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-app-index":{"identifier":"scope-app-index","description":"This scope permits to list all files and folders in the application directories.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPDATA"},{"path":"$APPLOCALDATA"},{"path":"$APPCACHE"},{"path":"$APPLOG"}]}},"scope-app-recursive":{"identifier":"scope-app-recursive","description":"This scope permits recursive access to the complete application folders, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"},{"path":"$APPDATA"},{"path":"$APPDATA/**"},{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"},{"path":"$APPCACHE"},{"path":"$APPCACHE/**"},{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-appcache":{"identifier":"scope-appcache","description":"This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/*"}]}},"scope-appcache-index":{"identifier":"scope-appcache-index","description":"This scope permits to list all files and folders in the `$APPCACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"}]}},"scope-appcache-recursive":{"identifier":"scope-appcache-recursive","description":"This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE"},{"path":"$APPCACHE/**"}]}},"scope-appconfig":{"identifier":"scope-appconfig","description":"This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/*"}]}},"scope-appconfig-index":{"identifier":"scope-appconfig-index","description":"This scope permits to list all files and folders in the `$APPCONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"}]}},"scope-appconfig-recursive":{"identifier":"scope-appconfig-recursive","description":"This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG"},{"path":"$APPCONFIG/**"}]}},"scope-appdata":{"identifier":"scope-appdata","description":"This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/*"}]}},"scope-appdata-index":{"identifier":"scope-appdata-index","description":"This scope permits to list all files and folders in the `$APPDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"}]}},"scope-appdata-recursive":{"identifier":"scope-appdata-recursive","description":"This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]}},"scope-applocaldata":{"identifier":"scope-applocaldata","description":"This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/*"}]}},"scope-applocaldata-index":{"identifier":"scope-applocaldata-index","description":"This scope permits to list all files and folders in the `$APPLOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"}]}},"scope-applocaldata-recursive":{"identifier":"scope-applocaldata-recursive","description":"This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA"},{"path":"$APPLOCALDATA/**"}]}},"scope-applog":{"identifier":"scope-applog","description":"This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/*"}]}},"scope-applog-index":{"identifier":"scope-applog-index","description":"This scope permits to list all files and folders in the `$APPLOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"}]}},"scope-applog-recursive":{"identifier":"scope-applog-recursive","description":"This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG"},{"path":"$APPLOG/**"}]}},"scope-audio":{"identifier":"scope-audio","description":"This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/*"}]}},"scope-audio-index":{"identifier":"scope-audio-index","description":"This scope permits to list all files and folders in the `$AUDIO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"}]}},"scope-audio-recursive":{"identifier":"scope-audio-recursive","description":"This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO"},{"path":"$AUDIO/**"}]}},"scope-cache":{"identifier":"scope-cache","description":"This scope permits access to all files and list content of top level directories in the `$CACHE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/*"}]}},"scope-cache-index":{"identifier":"scope-cache-index","description":"This scope permits to list all files and folders in the `$CACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"}]}},"scope-cache-recursive":{"identifier":"scope-cache-recursive","description":"This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE"},{"path":"$CACHE/**"}]}},"scope-config":{"identifier":"scope-config","description":"This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/*"}]}},"scope-config-index":{"identifier":"scope-config-index","description":"This scope permits to list all files and folders in the `$CONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"}]}},"scope-config-recursive":{"identifier":"scope-config-recursive","description":"This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG"},{"path":"$CONFIG/**"}]}},"scope-data":{"identifier":"scope-data","description":"This scope permits access to all files and list content of top level directories in the `$DATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/*"}]}},"scope-data-index":{"identifier":"scope-data-index","description":"This scope permits to list all files and folders in the `$DATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"}]}},"scope-data-recursive":{"identifier":"scope-data-recursive","description":"This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA"},{"path":"$DATA/**"}]}},"scope-desktop":{"identifier":"scope-desktop","description":"This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/*"}]}},"scope-desktop-index":{"identifier":"scope-desktop-index","description":"This scope permits to list all files and folders in the `$DESKTOP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"}]}},"scope-desktop-recursive":{"identifier":"scope-desktop-recursive","description":"This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP"},{"path":"$DESKTOP/**"}]}},"scope-document":{"identifier":"scope-document","description":"This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/*"}]}},"scope-document-index":{"identifier":"scope-document-index","description":"This scope permits to list all files and folders in the `$DOCUMENT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"}]}},"scope-document-recursive":{"identifier":"scope-document-recursive","description":"This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT"},{"path":"$DOCUMENT/**"}]}},"scope-download":{"identifier":"scope-download","description":"This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/*"}]}},"scope-download-index":{"identifier":"scope-download-index","description":"This scope permits to list all files and folders in the `$DOWNLOAD`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"}]}},"scope-download-recursive":{"identifier":"scope-download-recursive","description":"This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD"},{"path":"$DOWNLOAD/**"}]}},"scope-exe":{"identifier":"scope-exe","description":"This scope permits access to all files and list content of top level directories in the `$EXE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/*"}]}},"scope-exe-index":{"identifier":"scope-exe-index","description":"This scope permits to list all files and folders in the `$EXE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"}]}},"scope-exe-recursive":{"identifier":"scope-exe-recursive","description":"This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE"},{"path":"$EXE/**"}]}},"scope-font":{"identifier":"scope-font","description":"This scope permits access to all files and list content of top level directories in the `$FONT` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/*"}]}},"scope-font-index":{"identifier":"scope-font-index","description":"This scope permits to list all files and folders in the `$FONT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"}]}},"scope-font-recursive":{"identifier":"scope-font-recursive","description":"This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT"},{"path":"$FONT/**"}]}},"scope-home":{"identifier":"scope-home","description":"This scope permits access to all files and list content of top level directories in the `$HOME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/*"}]}},"scope-home-index":{"identifier":"scope-home-index","description":"This scope permits to list all files and folders in the `$HOME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"}]}},"scope-home-recursive":{"identifier":"scope-home-recursive","description":"This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME"},{"path":"$HOME/**"}]}},"scope-localdata":{"identifier":"scope-localdata","description":"This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/*"}]}},"scope-localdata-index":{"identifier":"scope-localdata-index","description":"This scope permits to list all files and folders in the `$LOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"}]}},"scope-localdata-recursive":{"identifier":"scope-localdata-recursive","description":"This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA"},{"path":"$LOCALDATA/**"}]}},"scope-log":{"identifier":"scope-log","description":"This scope permits access to all files and list content of top level directories in the `$LOG` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/*"}]}},"scope-log-index":{"identifier":"scope-log-index","description":"This scope permits to list all files and folders in the `$LOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"}]}},"scope-log-recursive":{"identifier":"scope-log-recursive","description":"This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG"},{"path":"$LOG/**"}]}},"scope-picture":{"identifier":"scope-picture","description":"This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/*"}]}},"scope-picture-index":{"identifier":"scope-picture-index","description":"This scope permits to list all files and folders in the `$PICTURE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"}]}},"scope-picture-recursive":{"identifier":"scope-picture-recursive","description":"This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE"},{"path":"$PICTURE/**"}]}},"scope-public":{"identifier":"scope-public","description":"This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/*"}]}},"scope-public-index":{"identifier":"scope-public-index","description":"This scope permits to list all files and folders in the `$PUBLIC`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"}]}},"scope-public-recursive":{"identifier":"scope-public-recursive","description":"This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC"},{"path":"$PUBLIC/**"}]}},"scope-resource":{"identifier":"scope-resource","description":"This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/*"}]}},"scope-resource-index":{"identifier":"scope-resource-index","description":"This scope permits to list all files and folders in the `$RESOURCE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"}]}},"scope-resource-recursive":{"identifier":"scope-resource-recursive","description":"This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE"},{"path":"$RESOURCE/**"}]}},"scope-runtime":{"identifier":"scope-runtime","description":"This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/*"}]}},"scope-runtime-index":{"identifier":"scope-runtime-index","description":"This scope permits to list all files and folders in the `$RUNTIME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"}]}},"scope-runtime-recursive":{"identifier":"scope-runtime-recursive","description":"This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME"},{"path":"$RUNTIME/**"}]}},"scope-temp":{"identifier":"scope-temp","description":"This scope permits access to all files and list content of top level directories in the `$TEMP` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/*"}]}},"scope-temp-index":{"identifier":"scope-temp-index","description":"This scope permits to list all files and folders in the `$TEMP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"}]}},"scope-temp-recursive":{"identifier":"scope-temp-recursive","description":"This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP"},{"path":"$TEMP/**"}]}},"scope-template":{"identifier":"scope-template","description":"This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/*"}]}},"scope-template-index":{"identifier":"scope-template-index","description":"This scope permits to list all files and folders in the `$TEMPLATE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"}]}},"scope-template-recursive":{"identifier":"scope-template-recursive","description":"This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE"},{"path":"$TEMPLATE/**"}]}},"scope-video":{"identifier":"scope-video","description":"This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/*"}]}},"scope-video-index":{"identifier":"scope-video-index","description":"This scope permits to list all files and folders in the `$VIDEO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"}]}},"scope-video-recursive":{"identifier":"scope-video-recursive","description":"This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO"},{"path":"$VIDEO/**"}]}},"write-all":{"identifier":"write-all","description":"This enables all write related commands without any pre-configured accessible paths.","commands":{"allow":["mkdir","create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}},"write-files":{"identifier":"write-files","description":"This enables all file write related commands without any pre-configured accessible paths.","commands":{"allow":["create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]}}},"permission_sets":{"allow-app-meta":{"identifier":"allow-app-meta","description":"This allows non-recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-index"]},"allow-app-meta-recursive":{"identifier":"allow-app-meta-recursive","description":"This allows full recursive read access to metadata of the application folders, including file listing and statistics.","permissions":["read-meta","scope-app-recursive"]},"allow-app-read":{"identifier":"allow-app-read","description":"This allows non-recursive read access to the application folders.","permissions":["read-all","scope-app"]},"allow-app-read-recursive":{"identifier":"allow-app-read-recursive","description":"This allows full recursive read access to the complete application folders, files and subdirectories.","permissions":["read-all","scope-app-recursive"]},"allow-app-write":{"identifier":"allow-app-write","description":"This allows non-recursive write access to the application folders.","permissions":["write-all","scope-app"]},"allow-app-write-recursive":{"identifier":"allow-app-write-recursive","description":"This allows full recursive write access to the complete application folders, files and subdirectories.","permissions":["write-all","scope-app-recursive"]},"allow-appcache-meta":{"identifier":"allow-appcache-meta","description":"This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-index"]},"allow-appcache-meta-recursive":{"identifier":"allow-appcache-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-recursive"]},"allow-appcache-read":{"identifier":"allow-appcache-read","description":"This allows non-recursive read access to the `$APPCACHE` folder.","permissions":["read-all","scope-appcache"]},"allow-appcache-read-recursive":{"identifier":"allow-appcache-read-recursive","description":"This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["read-all","scope-appcache-recursive"]},"allow-appcache-write":{"identifier":"allow-appcache-write","description":"This allows non-recursive write access to the `$APPCACHE` folder.","permissions":["write-all","scope-appcache"]},"allow-appcache-write-recursive":{"identifier":"allow-appcache-write-recursive","description":"This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["write-all","scope-appcache-recursive"]},"allow-appconfig-meta":{"identifier":"allow-appconfig-meta","description":"This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-index"]},"allow-appconfig-meta-recursive":{"identifier":"allow-appconfig-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-recursive"]},"allow-appconfig-read":{"identifier":"allow-appconfig-read","description":"This allows non-recursive read access to the `$APPCONFIG` folder.","permissions":["read-all","scope-appconfig"]},"allow-appconfig-read-recursive":{"identifier":"allow-appconfig-read-recursive","description":"This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["read-all","scope-appconfig-recursive"]},"allow-appconfig-write":{"identifier":"allow-appconfig-write","description":"This allows non-recursive write access to the `$APPCONFIG` folder.","permissions":["write-all","scope-appconfig"]},"allow-appconfig-write-recursive":{"identifier":"allow-appconfig-write-recursive","description":"This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["write-all","scope-appconfig-recursive"]},"allow-appdata-meta":{"identifier":"allow-appdata-meta","description":"This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-index"]},"allow-appdata-meta-recursive":{"identifier":"allow-appdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-recursive"]},"allow-appdata-read":{"identifier":"allow-appdata-read","description":"This allows non-recursive read access to the `$APPDATA` folder.","permissions":["read-all","scope-appdata"]},"allow-appdata-read-recursive":{"identifier":"allow-appdata-read-recursive","description":"This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["read-all","scope-appdata-recursive"]},"allow-appdata-write":{"identifier":"allow-appdata-write","description":"This allows non-recursive write access to the `$APPDATA` folder.","permissions":["write-all","scope-appdata"]},"allow-appdata-write-recursive":{"identifier":"allow-appdata-write-recursive","description":"This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["write-all","scope-appdata-recursive"]},"allow-applocaldata-meta":{"identifier":"allow-applocaldata-meta","description":"This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-index"]},"allow-applocaldata-meta-recursive":{"identifier":"allow-applocaldata-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-recursive"]},"allow-applocaldata-read":{"identifier":"allow-applocaldata-read","description":"This allows non-recursive read access to the `$APPLOCALDATA` folder.","permissions":["read-all","scope-applocaldata"]},"allow-applocaldata-read-recursive":{"identifier":"allow-applocaldata-read-recursive","description":"This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-applocaldata-recursive"]},"allow-applocaldata-write":{"identifier":"allow-applocaldata-write","description":"This allows non-recursive write access to the `$APPLOCALDATA` folder.","permissions":["write-all","scope-applocaldata"]},"allow-applocaldata-write-recursive":{"identifier":"allow-applocaldata-write-recursive","description":"This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-applocaldata-recursive"]},"allow-applog-meta":{"identifier":"allow-applog-meta","description":"This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-index"]},"allow-applog-meta-recursive":{"identifier":"allow-applog-meta-recursive","description":"This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-recursive"]},"allow-applog-read":{"identifier":"allow-applog-read","description":"This allows non-recursive read access to the `$APPLOG` folder.","permissions":["read-all","scope-applog"]},"allow-applog-read-recursive":{"identifier":"allow-applog-read-recursive","description":"This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["read-all","scope-applog-recursive"]},"allow-applog-write":{"identifier":"allow-applog-write","description":"This allows non-recursive write access to the `$APPLOG` folder.","permissions":["write-all","scope-applog"]},"allow-applog-write-recursive":{"identifier":"allow-applog-write-recursive","description":"This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["write-all","scope-applog-recursive"]},"allow-audio-meta":{"identifier":"allow-audio-meta","description":"This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-index"]},"allow-audio-meta-recursive":{"identifier":"allow-audio-meta-recursive","description":"This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-recursive"]},"allow-audio-read":{"identifier":"allow-audio-read","description":"This allows non-recursive read access to the `$AUDIO` folder.","permissions":["read-all","scope-audio"]},"allow-audio-read-recursive":{"identifier":"allow-audio-read-recursive","description":"This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["read-all","scope-audio-recursive"]},"allow-audio-write":{"identifier":"allow-audio-write","description":"This allows non-recursive write access to the `$AUDIO` folder.","permissions":["write-all","scope-audio"]},"allow-audio-write-recursive":{"identifier":"allow-audio-write-recursive","description":"This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["write-all","scope-audio-recursive"]},"allow-cache-meta":{"identifier":"allow-cache-meta","description":"This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-index"]},"allow-cache-meta-recursive":{"identifier":"allow-cache-meta-recursive","description":"This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-recursive"]},"allow-cache-read":{"identifier":"allow-cache-read","description":"This allows non-recursive read access to the `$CACHE` folder.","permissions":["read-all","scope-cache"]},"allow-cache-read-recursive":{"identifier":"allow-cache-read-recursive","description":"This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.","permissions":["read-all","scope-cache-recursive"]},"allow-cache-write":{"identifier":"allow-cache-write","description":"This allows non-recursive write access to the `$CACHE` folder.","permissions":["write-all","scope-cache"]},"allow-cache-write-recursive":{"identifier":"allow-cache-write-recursive","description":"This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.","permissions":["write-all","scope-cache-recursive"]},"allow-config-meta":{"identifier":"allow-config-meta","description":"This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-index"]},"allow-config-meta-recursive":{"identifier":"allow-config-meta-recursive","description":"This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-recursive"]},"allow-config-read":{"identifier":"allow-config-read","description":"This allows non-recursive read access to the `$CONFIG` folder.","permissions":["read-all","scope-config"]},"allow-config-read-recursive":{"identifier":"allow-config-read-recursive","description":"This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["read-all","scope-config-recursive"]},"allow-config-write":{"identifier":"allow-config-write","description":"This allows non-recursive write access to the `$CONFIG` folder.","permissions":["write-all","scope-config"]},"allow-config-write-recursive":{"identifier":"allow-config-write-recursive","description":"This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["write-all","scope-config-recursive"]},"allow-data-meta":{"identifier":"allow-data-meta","description":"This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-index"]},"allow-data-meta-recursive":{"identifier":"allow-data-meta-recursive","description":"This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-recursive"]},"allow-data-read":{"identifier":"allow-data-read","description":"This allows non-recursive read access to the `$DATA` folder.","permissions":["read-all","scope-data"]},"allow-data-read-recursive":{"identifier":"allow-data-read-recursive","description":"This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.","permissions":["read-all","scope-data-recursive"]},"allow-data-write":{"identifier":"allow-data-write","description":"This allows non-recursive write access to the `$DATA` folder.","permissions":["write-all","scope-data"]},"allow-data-write-recursive":{"identifier":"allow-data-write-recursive","description":"This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.","permissions":["write-all","scope-data-recursive"]},"allow-desktop-meta":{"identifier":"allow-desktop-meta","description":"This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-index"]},"allow-desktop-meta-recursive":{"identifier":"allow-desktop-meta-recursive","description":"This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-recursive"]},"allow-desktop-read":{"identifier":"allow-desktop-read","description":"This allows non-recursive read access to the `$DESKTOP` folder.","permissions":["read-all","scope-desktop"]},"allow-desktop-read-recursive":{"identifier":"allow-desktop-read-recursive","description":"This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["read-all","scope-desktop-recursive"]},"allow-desktop-write":{"identifier":"allow-desktop-write","description":"This allows non-recursive write access to the `$DESKTOP` folder.","permissions":["write-all","scope-desktop"]},"allow-desktop-write-recursive":{"identifier":"allow-desktop-write-recursive","description":"This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["write-all","scope-desktop-recursive"]},"allow-document-meta":{"identifier":"allow-document-meta","description":"This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-index"]},"allow-document-meta-recursive":{"identifier":"allow-document-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-recursive"]},"allow-document-read":{"identifier":"allow-document-read","description":"This allows non-recursive read access to the `$DOCUMENT` folder.","permissions":["read-all","scope-document"]},"allow-document-read-recursive":{"identifier":"allow-document-read-recursive","description":"This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["read-all","scope-document-recursive"]},"allow-document-write":{"identifier":"allow-document-write","description":"This allows non-recursive write access to the `$DOCUMENT` folder.","permissions":["write-all","scope-document"]},"allow-document-write-recursive":{"identifier":"allow-document-write-recursive","description":"This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["write-all","scope-document-recursive"]},"allow-download-meta":{"identifier":"allow-download-meta","description":"This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-index"]},"allow-download-meta-recursive":{"identifier":"allow-download-meta-recursive","description":"This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-recursive"]},"allow-download-read":{"identifier":"allow-download-read","description":"This allows non-recursive read access to the `$DOWNLOAD` folder.","permissions":["read-all","scope-download"]},"allow-download-read-recursive":{"identifier":"allow-download-read-recursive","description":"This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["read-all","scope-download-recursive"]},"allow-download-write":{"identifier":"allow-download-write","description":"This allows non-recursive write access to the `$DOWNLOAD` folder.","permissions":["write-all","scope-download"]},"allow-download-write-recursive":{"identifier":"allow-download-write-recursive","description":"This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["write-all","scope-download-recursive"]},"allow-exe-meta":{"identifier":"allow-exe-meta","description":"This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-index"]},"allow-exe-meta-recursive":{"identifier":"allow-exe-meta-recursive","description":"This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-recursive"]},"allow-exe-read":{"identifier":"allow-exe-read","description":"This allows non-recursive read access to the `$EXE` folder.","permissions":["read-all","scope-exe"]},"allow-exe-read-recursive":{"identifier":"allow-exe-read-recursive","description":"This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.","permissions":["read-all","scope-exe-recursive"]},"allow-exe-write":{"identifier":"allow-exe-write","description":"This allows non-recursive write access to the `$EXE` folder.","permissions":["write-all","scope-exe"]},"allow-exe-write-recursive":{"identifier":"allow-exe-write-recursive","description":"This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.","permissions":["write-all","scope-exe-recursive"]},"allow-font-meta":{"identifier":"allow-font-meta","description":"This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-index"]},"allow-font-meta-recursive":{"identifier":"allow-font-meta-recursive","description":"This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-recursive"]},"allow-font-read":{"identifier":"allow-font-read","description":"This allows non-recursive read access to the `$FONT` folder.","permissions":["read-all","scope-font"]},"allow-font-read-recursive":{"identifier":"allow-font-read-recursive","description":"This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.","permissions":["read-all","scope-font-recursive"]},"allow-font-write":{"identifier":"allow-font-write","description":"This allows non-recursive write access to the `$FONT` folder.","permissions":["write-all","scope-font"]},"allow-font-write-recursive":{"identifier":"allow-font-write-recursive","description":"This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.","permissions":["write-all","scope-font-recursive"]},"allow-home-meta":{"identifier":"allow-home-meta","description":"This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-index"]},"allow-home-meta-recursive":{"identifier":"allow-home-meta-recursive","description":"This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-recursive"]},"allow-home-read":{"identifier":"allow-home-read","description":"This allows non-recursive read access to the `$HOME` folder.","permissions":["read-all","scope-home"]},"allow-home-read-recursive":{"identifier":"allow-home-read-recursive","description":"This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.","permissions":["read-all","scope-home-recursive"]},"allow-home-write":{"identifier":"allow-home-write","description":"This allows non-recursive write access to the `$HOME` folder.","permissions":["write-all","scope-home"]},"allow-home-write-recursive":{"identifier":"allow-home-write-recursive","description":"This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.","permissions":["write-all","scope-home-recursive"]},"allow-localdata-meta":{"identifier":"allow-localdata-meta","description":"This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-index"]},"allow-localdata-meta-recursive":{"identifier":"allow-localdata-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-recursive"]},"allow-localdata-read":{"identifier":"allow-localdata-read","description":"This allows non-recursive read access to the `$LOCALDATA` folder.","permissions":["read-all","scope-localdata"]},"allow-localdata-read-recursive":{"identifier":"allow-localdata-read-recursive","description":"This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-localdata-recursive"]},"allow-localdata-write":{"identifier":"allow-localdata-write","description":"This allows non-recursive write access to the `$LOCALDATA` folder.","permissions":["write-all","scope-localdata"]},"allow-localdata-write-recursive":{"identifier":"allow-localdata-write-recursive","description":"This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-localdata-recursive"]},"allow-log-meta":{"identifier":"allow-log-meta","description":"This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-index"]},"allow-log-meta-recursive":{"identifier":"allow-log-meta-recursive","description":"This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-recursive"]},"allow-log-read":{"identifier":"allow-log-read","description":"This allows non-recursive read access to the `$LOG` folder.","permissions":["read-all","scope-log"]},"allow-log-read-recursive":{"identifier":"allow-log-read-recursive","description":"This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.","permissions":["read-all","scope-log-recursive"]},"allow-log-write":{"identifier":"allow-log-write","description":"This allows non-recursive write access to the `$LOG` folder.","permissions":["write-all","scope-log"]},"allow-log-write-recursive":{"identifier":"allow-log-write-recursive","description":"This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.","permissions":["write-all","scope-log-recursive"]},"allow-picture-meta":{"identifier":"allow-picture-meta","description":"This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-index"]},"allow-picture-meta-recursive":{"identifier":"allow-picture-meta-recursive","description":"This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-recursive"]},"allow-picture-read":{"identifier":"allow-picture-read","description":"This allows non-recursive read access to the `$PICTURE` folder.","permissions":["read-all","scope-picture"]},"allow-picture-read-recursive":{"identifier":"allow-picture-read-recursive","description":"This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["read-all","scope-picture-recursive"]},"allow-picture-write":{"identifier":"allow-picture-write","description":"This allows non-recursive write access to the `$PICTURE` folder.","permissions":["write-all","scope-picture"]},"allow-picture-write-recursive":{"identifier":"allow-picture-write-recursive","description":"This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["write-all","scope-picture-recursive"]},"allow-public-meta":{"identifier":"allow-public-meta","description":"This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-index"]},"allow-public-meta-recursive":{"identifier":"allow-public-meta-recursive","description":"This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-recursive"]},"allow-public-read":{"identifier":"allow-public-read","description":"This allows non-recursive read access to the `$PUBLIC` folder.","permissions":["read-all","scope-public"]},"allow-public-read-recursive":{"identifier":"allow-public-read-recursive","description":"This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["read-all","scope-public-recursive"]},"allow-public-write":{"identifier":"allow-public-write","description":"This allows non-recursive write access to the `$PUBLIC` folder.","permissions":["write-all","scope-public"]},"allow-public-write-recursive":{"identifier":"allow-public-write-recursive","description":"This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["write-all","scope-public-recursive"]},"allow-resource-meta":{"identifier":"allow-resource-meta","description":"This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-index"]},"allow-resource-meta-recursive":{"identifier":"allow-resource-meta-recursive","description":"This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-recursive"]},"allow-resource-read":{"identifier":"allow-resource-read","description":"This allows non-recursive read access to the `$RESOURCE` folder.","permissions":["read-all","scope-resource"]},"allow-resource-read-recursive":{"identifier":"allow-resource-read-recursive","description":"This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["read-all","scope-resource-recursive"]},"allow-resource-write":{"identifier":"allow-resource-write","description":"This allows non-recursive write access to the `$RESOURCE` folder.","permissions":["write-all","scope-resource"]},"allow-resource-write-recursive":{"identifier":"allow-resource-write-recursive","description":"This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["write-all","scope-resource-recursive"]},"allow-runtime-meta":{"identifier":"allow-runtime-meta","description":"This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-index"]},"allow-runtime-meta-recursive":{"identifier":"allow-runtime-meta-recursive","description":"This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-recursive"]},"allow-runtime-read":{"identifier":"allow-runtime-read","description":"This allows non-recursive read access to the `$RUNTIME` folder.","permissions":["read-all","scope-runtime"]},"allow-runtime-read-recursive":{"identifier":"allow-runtime-read-recursive","description":"This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["read-all","scope-runtime-recursive"]},"allow-runtime-write":{"identifier":"allow-runtime-write","description":"This allows non-recursive write access to the `$RUNTIME` folder.","permissions":["write-all","scope-runtime"]},"allow-runtime-write-recursive":{"identifier":"allow-runtime-write-recursive","description":"This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["write-all","scope-runtime-recursive"]},"allow-temp-meta":{"identifier":"allow-temp-meta","description":"This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-index"]},"allow-temp-meta-recursive":{"identifier":"allow-temp-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-recursive"]},"allow-temp-read":{"identifier":"allow-temp-read","description":"This allows non-recursive read access to the `$TEMP` folder.","permissions":["read-all","scope-temp"]},"allow-temp-read-recursive":{"identifier":"allow-temp-read-recursive","description":"This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.","permissions":["read-all","scope-temp-recursive"]},"allow-temp-write":{"identifier":"allow-temp-write","description":"This allows non-recursive write access to the `$TEMP` folder.","permissions":["write-all","scope-temp"]},"allow-temp-write-recursive":{"identifier":"allow-temp-write-recursive","description":"This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.","permissions":["write-all","scope-temp-recursive"]},"allow-template-meta":{"identifier":"allow-template-meta","description":"This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-index"]},"allow-template-meta-recursive":{"identifier":"allow-template-meta-recursive","description":"This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-recursive"]},"allow-template-read":{"identifier":"allow-template-read","description":"This allows non-recursive read access to the `$TEMPLATE` folder.","permissions":["read-all","scope-template"]},"allow-template-read-recursive":{"identifier":"allow-template-read-recursive","description":"This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["read-all","scope-template-recursive"]},"allow-template-write":{"identifier":"allow-template-write","description":"This allows non-recursive write access to the `$TEMPLATE` folder.","permissions":["write-all","scope-template"]},"allow-template-write-recursive":{"identifier":"allow-template-write-recursive","description":"This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["write-all","scope-template-recursive"]},"allow-video-meta":{"identifier":"allow-video-meta","description":"This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-index"]},"allow-video-meta-recursive":{"identifier":"allow-video-meta-recursive","description":"This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-recursive"]},"allow-video-read":{"identifier":"allow-video-read","description":"This allows non-recursive read access to the `$VIDEO` folder.","permissions":["read-all","scope-video"]},"allow-video-read-recursive":{"identifier":"allow-video-read-recursive","description":"This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["read-all","scope-video-recursive"]},"allow-video-write":{"identifier":"allow-video-write","description":"This allows non-recursive write access to the `$VIDEO` folder.","permissions":["write-all","scope-video"]},"allow-video-write-recursive":{"identifier":"allow-video-write-recursive","description":"This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["write-all","scope-video-recursive"]},"deny-default":{"identifier":"deny-default","description":"This denies access to dangerous Tauri relevant files and folders by default.","permissions":["deny-webview-data-linux","deny-webview-data-windows"]}},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"description":"A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},{"properties":{"path":{"description":"A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"}},"required":["path"],"type":"object"}],"description":"FS scope entry.","title":"FsScopeEntry"}},"positioner":{"default_permission":{"identifier":"default","description":"Allows the moveWindow and handleIconState APIs","permissions":["allow-move-window","allow-move-window-constrained","allow-set-tray-icon-state"]},"permissions":{"allow-move-window":{"identifier":"allow-move-window","description":"Enables the move_window command without any pre-configured scope.","commands":{"allow":["move_window"],"deny":[]}},"allow-move-window-constrained":{"identifier":"allow-move-window-constrained","description":"Enables the move_window_constrained command without any pre-configured scope.","commands":{"allow":["move_window_constrained"],"deny":[]}},"allow-set-tray-icon-state":{"identifier":"allow-set-tray-icon-state","description":"Enables the set_tray_icon_state command without any pre-configured scope.","commands":{"allow":["set_tray_icon_state"],"deny":[]}},"deny-move-window":{"identifier":"deny-move-window","description":"Denies the move_window command without any pre-configured scope.","commands":{"allow":[],"deny":["move_window"]}},"deny-move-window-constrained":{"identifier":"deny-move-window-constrained","description":"Denies the move_window_constrained command without any pre-configured scope.","commands":{"allow":[],"deny":["move_window_constrained"]}},"deny-set-tray-icon-state":{"identifier":"deny-set-tray-icon-state","description":"Denies the set_tray_icon_state command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tray_icon_state"]}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":{"identifier":"default","description":"This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n","permissions":["allow-open"]},"permissions":{"allow-execute":{"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]}},"allow-kill":{"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-spawn":{"identifier":"allow-spawn","description":"Enables the spawn command without any pre-configured scope.","commands":{"allow":["spawn"],"deny":[]}},"allow-stdin-write":{"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]}},"deny-execute":{"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]}},"deny-kill":{"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-spawn":{"identifier":"deny-spawn","description":"Denies the spawn command without any pre-configured scope.","commands":{"allow":[],"deny":["spawn"]}},"deny-stdin-write":{"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"cmd":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"}},"required":["cmd","name"],"type":"object"},{"additionalProperties":false,"properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellScopeEntryAllowedArgs"}],"description":"The allowed arguments for the command execution."},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["name","sidecar"],"type":"object"}],"definitions":{"ShellScopeEntryAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"raw":{"default":false,"description":"Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.","type":"boolean"},"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellScopeEntryAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellScopeEntryAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"Shell scope entry.","title":"ShellScopeEntry"}},"window-state":{"default_permission":{"identifier":"default","description":"This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n","permissions":["allow-filename","allow-restore-state","allow-save-window-state"]},"permissions":{"allow-filename":{"identifier":"allow-filename","description":"Enables the filename command without any pre-configured scope.","commands":{"allow":["filename"],"deny":[]}},"allow-restore-state":{"identifier":"allow-restore-state","description":"Enables the restore_state command without any pre-configured scope.","commands":{"allow":["restore_state"],"deny":[]}},"allow-save-window-state":{"identifier":"allow-save-window-state","description":"Enables the save_window_state command without any pre-configured scope.","commands":{"allow":["save_window_state"],"deny":[]}},"deny-filename":{"identifier":"deny-filename","description":"Denies the filename command without any pre-configured scope.","commands":{"allow":[],"deny":["filename"]}},"deny-restore-state":{"identifier":"deny-restore-state","description":"Denies the restore_state command without any pre-configured scope.","commands":{"allow":[],"deny":["restore_state"]}},"deny-save-window-state":{"identifier":"deny-save-window-state","description":"Denies the save_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["save_window_state"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json deleted file mode 100644 index f5273c7d7..000000000 --- a/src-tauri/gen/schemas/capabilities.json +++ /dev/null @@ -1 +0,0 @@ -{"main-capability":{"identifier":"main-capability","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","dialog:allow-ask","dialog:allow-confirm","dialog:allow-message","dialog:allow-open","dialog:allow-save","fs:allow-download-write","core:resources:default","core:menu:default","core:tray:default","shell:allow-open"],"platforms":["linux","macOS","windows","android","iOS"]}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/desktop-schema.json b/src-tauri/gen/schemas/desktop-schema.json deleted file mode 100644 index 7ee3c87f4..000000000 --- a/src-tauri/gen/schemas/desktop-schema.json +++ /dev/null @@ -1,5181 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CapabilityFile", - "description": "Capability formats accepted in a capability file.", - "anyOf": [ - { - "description": "A single capability.", - "allOf": [ - { - "$ref": "#/definitions/Capability" - } - ] - }, - { - "description": "A list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - }, - { - "description": "A list of capabilities.", - "type": "object", - "required": [ - "capabilities" - ], - "properties": { - "capabilities": { - "description": "The list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - } - } - } - ], - "definitions": { - "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", - "type": "object", - "required": [ - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", - "type": "string" - }, - "description": { - "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.", - "default": "", - "type": "string" - }, - "remote": { - "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", - "anyOf": [ - { - "$ref": "#/definitions/CapabilityRemote" - }, - { - "type": "null" - } - ] - }, - "local": { - "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", - "default": true, - "type": "boolean" - }, - "windows": { - "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "webviews": { - "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionEntry" - }, - "uniqueItems": true - }, - "platforms": { - "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "CapabilityRemote": { - "description": "Configuration for remote URLs that are associated with the capability.", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionEntry": { - "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", - "anyOf": [ - { - "description": "Reference a permission or permission set by identifier.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - { - "description": "Reference a permission or permission set by identifier and extends its scope.", - "type": "object", - "allOf": [ - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-size" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-size" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - } - } - } - ] - } - }, - "deny": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - } - } - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "deny": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - } - ], - "required": [ - "identifier" - ] - } - ] - }, - "Identifier": { - "description": "Permission identifier", - "oneOf": [ - { - "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", - "type": "string", - "const": "core:default" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:app:default" - }, - { - "description": "Enables the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-hide" - }, - { - "description": "Enables the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-show" - }, - { - "description": "Enables the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-default-window-icon" - }, - { - "description": "Enables the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-name" - }, - { - "description": "Enables the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-app-theme" - }, - { - "description": "Enables the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-tauri-version" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-version" - }, - { - "description": "Denies the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-hide" - }, - { - "description": "Denies the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-show" - }, - { - "description": "Denies the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-default-window-icon" - }, - { - "description": "Denies the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-name" - }, - { - "description": "Denies the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-app-theme" - }, - { - "description": "Denies the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-tauri-version" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-version" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:event:default" - }, - { - "description": "Enables the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit" - }, - { - "description": "Enables the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit-to" - }, - { - "description": "Enables the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-listen" - }, - { - "description": "Enables the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-unlisten" - }, - { - "description": "Denies the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit" - }, - { - "description": "Denies the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit-to" - }, - { - "description": "Denies the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-listen" - }, - { - "description": "Denies the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-unlisten" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:image:default" - }, - { - "description": "Enables the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-bytes" - }, - { - "description": "Enables the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-path" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-new" - }, - { - "description": "Enables the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-rgba" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-size" - }, - { - "description": "Denies the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-bytes" - }, - { - "description": "Denies the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-path" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-new" - }, - { - "description": "Denies the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-rgba" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:menu:default" - }, - { - "description": "Enables the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-append" - }, - { - "description": "Enables the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-create-default" - }, - { - "description": "Enables the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-get" - }, - { - "description": "Enables the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-insert" - }, - { - "description": "Enables the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-checked" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-enabled" - }, - { - "description": "Enables the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-items" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-new" - }, - { - "description": "Enables the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-popup" - }, - { - "description": "Enables the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-prepend" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove" - }, - { - "description": "Enables the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove-at" - }, - { - "description": "Enables the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-accelerator" - }, - { - "description": "Enables the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-app-menu" - }, - { - "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-help-menu-for-nsapp" - }, - { - "description": "Enables the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-window-menu" - }, - { - "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-windows-menu-for-nsapp" - }, - { - "description": "Enables the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-checked" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-enabled" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-icon" - }, - { - "description": "Enables the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-text" - }, - { - "description": "Enables the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-text" - }, - { - "description": "Denies the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-append" - }, - { - "description": "Denies the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-create-default" - }, - { - "description": "Denies the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-get" - }, - { - "description": "Denies the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-insert" - }, - { - "description": "Denies the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-checked" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-enabled" - }, - { - "description": "Denies the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-items" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-new" - }, - { - "description": "Denies the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-popup" - }, - { - "description": "Denies the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-prepend" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove" - }, - { - "description": "Denies the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove-at" - }, - { - "description": "Denies the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-accelerator" - }, - { - "description": "Denies the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-app-menu" - }, - { - "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-help-menu-for-nsapp" - }, - { - "description": "Denies the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-window-menu" - }, - { - "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-windows-menu-for-nsapp" - }, - { - "description": "Denies the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-checked" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-enabled" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-icon" - }, - { - "description": "Denies the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-text" - }, - { - "description": "Denies the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-text" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:path:default" - }, - { - "description": "Enables the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-basename" - }, - { - "description": "Enables the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-dirname" - }, - { - "description": "Enables the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-extname" - }, - { - "description": "Enables the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-is-absolute" - }, - { - "description": "Enables the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-join" - }, - { - "description": "Enables the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-normalize" - }, - { - "description": "Enables the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve" - }, - { - "description": "Enables the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve-directory" - }, - { - "description": "Denies the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-basename" - }, - { - "description": "Denies the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-dirname" - }, - { - "description": "Denies the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-extname" - }, - { - "description": "Denies the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-is-absolute" - }, - { - "description": "Denies the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-join" - }, - { - "description": "Denies the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-normalize" - }, - { - "description": "Denies the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve" - }, - { - "description": "Denies the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve-directory" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:resources:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:allow-close" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:deny-close" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:tray:default" - }, - { - "description": "Enables the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-get-by-id" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-new" - }, - { - "description": "Enables the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-remove-by-id" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon" - }, - { - "description": "Enables the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-as-template" - }, - { - "description": "Enables the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-menu" - }, - { - "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-show-menu-on-left-click" - }, - { - "description": "Enables the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-temp-dir-path" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-title" - }, - { - "description": "Enables the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-tooltip" - }, - { - "description": "Enables the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-visible" - }, - { - "description": "Denies the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-get-by-id" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-new" - }, - { - "description": "Denies the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-remove-by-id" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon" - }, - { - "description": "Denies the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-as-template" - }, - { - "description": "Denies the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-menu" - }, - { - "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-show-menu-on-left-click" - }, - { - "description": "Denies the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-temp-dir-path" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-title" - }, - { - "description": "Denies the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-tooltip" - }, - { - "description": "Denies the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-visible" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:webview:default" - }, - { - "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-clear-all-browsing-data" - }, - { - "description": "Enables the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview" - }, - { - "description": "Enables the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview-window" - }, - { - "description": "Enables the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-get-all-webviews" - }, - { - "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-internal-toggle-devtools" - }, - { - "description": "Enables the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-print" - }, - { - "description": "Enables the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-reparent" - }, - { - "description": "Enables the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-background-color" - }, - { - "description": "Enables the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-focus" - }, - { - "description": "Enables the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-position" - }, - { - "description": "Enables the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-size" - }, - { - "description": "Enables the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-zoom" - }, - { - "description": "Enables the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-close" - }, - { - "description": "Enables the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-hide" - }, - { - "description": "Enables the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-position" - }, - { - "description": "Enables the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-show" - }, - { - "description": "Enables the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-size" - }, - { - "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-clear-all-browsing-data" - }, - { - "description": "Denies the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview" - }, - { - "description": "Denies the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview-window" - }, - { - "description": "Denies the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-get-all-webviews" - }, - { - "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-internal-toggle-devtools" - }, - { - "description": "Denies the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-print" - }, - { - "description": "Denies the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-reparent" - }, - { - "description": "Denies the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-background-color" - }, - { - "description": "Denies the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-focus" - }, - { - "description": "Denies the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-position" - }, - { - "description": "Denies the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-size" - }, - { - "description": "Denies the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-zoom" - }, - { - "description": "Denies the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-close" - }, - { - "description": "Denies the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-hide" - }, - { - "description": "Denies the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-position" - }, - { - "description": "Denies the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-show" - }, - { - "description": "Denies the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:window:default" - }, - { - "description": "Enables the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-available-monitors" - }, - { - "description": "Enables the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-center" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-close" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-create" - }, - { - "description": "Enables the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-current-monitor" - }, - { - "description": "Enables the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-cursor-position" - }, - { - "description": "Enables the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-destroy" - }, - { - "description": "Enables the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-get-all-windows" - }, - { - "description": "Enables the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-hide" - }, - { - "description": "Enables the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-position" - }, - { - "description": "Enables the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-size" - }, - { - "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-internal-toggle-maximize" - }, - { - "description": "Enables the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-closable" - }, - { - "description": "Enables the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-decorated" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-enabled" - }, - { - "description": "Enables the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-focused" - }, - { - "description": "Enables the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-fullscreen" - }, - { - "description": "Enables the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximizable" - }, - { - "description": "Enables the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximized" - }, - { - "description": "Enables the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimizable" - }, - { - "description": "Enables the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimized" - }, - { - "description": "Enables the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-resizable" - }, - { - "description": "Enables the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-visible" - }, - { - "description": "Enables the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-maximize" - }, - { - "description": "Enables the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-minimize" - }, - { - "description": "Enables the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-monitor-from-point" - }, - { - "description": "Enables the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-position" - }, - { - "description": "Enables the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-size" - }, - { - "description": "Enables the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-primary-monitor" - }, - { - "description": "Enables the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-request-user-attention" - }, - { - "description": "Enables the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scale-factor" - }, - { - "description": "Enables the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-bottom" - }, - { - "description": "Enables the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-top" - }, - { - "description": "Enables the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-background-color" - }, - { - "description": "Enables the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-count" - }, - { - "description": "Enables the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-label" - }, - { - "description": "Enables the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-closable" - }, - { - "description": "Enables the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-content-protected" - }, - { - "description": "Enables the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-grab" - }, - { - "description": "Enables the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-icon" - }, - { - "description": "Enables the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-position" - }, - { - "description": "Enables the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-visible" - }, - { - "description": "Enables the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-decorations" - }, - { - "description": "Enables the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-effects" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-enabled" - }, - { - "description": "Enables the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focus" - }, - { - "description": "Enables the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-fullscreen" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-icon" - }, - { - "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-ignore-cursor-events" - }, - { - "description": "Enables the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-max-size" - }, - { - "description": "Enables the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-maximizable" - }, - { - "description": "Enables the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-min-size" - }, - { - "description": "Enables the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-minimizable" - }, - { - "description": "Enables the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-overlay-icon" - }, - { - "description": "Enables the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-position" - }, - { - "description": "Enables the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-progress-bar" - }, - { - "description": "Enables the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-resizable" - }, - { - "description": "Enables the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-shadow" - }, - { - "description": "Enables the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size" - }, - { - "description": "Enables the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size-constraints" - }, - { - "description": "Enables the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-skip-taskbar" - }, - { - "description": "Enables the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-theme" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title" - }, - { - "description": "Enables the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title-bar-style" - }, - { - "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-visible-on-all-workspaces" - }, - { - "description": "Enables the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-show" - }, - { - "description": "Enables the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-dragging" - }, - { - "description": "Enables the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-resize-dragging" - }, - { - "description": "Enables the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-theme" - }, - { - "description": "Enables the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-title" - }, - { - "description": "Enables the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-toggle-maximize" - }, - { - "description": "Enables the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unmaximize" - }, - { - "description": "Enables the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unminimize" - }, - { - "description": "Denies the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-available-monitors" - }, - { - "description": "Denies the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-center" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-close" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-create" - }, - { - "description": "Denies the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-current-monitor" - }, - { - "description": "Denies the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-cursor-position" - }, - { - "description": "Denies the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-destroy" - }, - { - "description": "Denies the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-get-all-windows" - }, - { - "description": "Denies the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-hide" - }, - { - "description": "Denies the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-position" - }, - { - "description": "Denies the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-size" - }, - { - "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-internal-toggle-maximize" - }, - { - "description": "Denies the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-closable" - }, - { - "description": "Denies the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-decorated" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-enabled" - }, - { - "description": "Denies the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-focused" - }, - { - "description": "Denies the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-fullscreen" - }, - { - "description": "Denies the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximizable" - }, - { - "description": "Denies the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximized" - }, - { - "description": "Denies the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimizable" - }, - { - "description": "Denies the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimized" - }, - { - "description": "Denies the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-resizable" - }, - { - "description": "Denies the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-visible" - }, - { - "description": "Denies the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-maximize" - }, - { - "description": "Denies the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-minimize" - }, - { - "description": "Denies the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-monitor-from-point" - }, - { - "description": "Denies the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-position" - }, - { - "description": "Denies the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-size" - }, - { - "description": "Denies the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-primary-monitor" - }, - { - "description": "Denies the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-request-user-attention" - }, - { - "description": "Denies the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scale-factor" - }, - { - "description": "Denies the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-bottom" - }, - { - "description": "Denies the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-top" - }, - { - "description": "Denies the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-background-color" - }, - { - "description": "Denies the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-count" - }, - { - "description": "Denies the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-label" - }, - { - "description": "Denies the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-closable" - }, - { - "description": "Denies the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-content-protected" - }, - { - "description": "Denies the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-grab" - }, - { - "description": "Denies the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-icon" - }, - { - "description": "Denies the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-position" - }, - { - "description": "Denies the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-visible" - }, - { - "description": "Denies the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-decorations" - }, - { - "description": "Denies the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-effects" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-enabled" - }, - { - "description": "Denies the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focus" - }, - { - "description": "Denies the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-fullscreen" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-icon" - }, - { - "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-ignore-cursor-events" - }, - { - "description": "Denies the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-max-size" - }, - { - "description": "Denies the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-maximizable" - }, - { - "description": "Denies the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-min-size" - }, - { - "description": "Denies the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-minimizable" - }, - { - "description": "Denies the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-overlay-icon" - }, - { - "description": "Denies the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-position" - }, - { - "description": "Denies the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-progress-bar" - }, - { - "description": "Denies the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-resizable" - }, - { - "description": "Denies the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-shadow" - }, - { - "description": "Denies the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size" - }, - { - "description": "Denies the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size-constraints" - }, - { - "description": "Denies the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-skip-taskbar" - }, - { - "description": "Denies the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-theme" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title" - }, - { - "description": "Denies the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title-bar-style" - }, - { - "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-visible-on-all-workspaces" - }, - { - "description": "Denies the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-show" - }, - { - "description": "Denies the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-dragging" - }, - { - "description": "Denies the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-resize-dragging" - }, - { - "description": "Denies the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-theme" - }, - { - "description": "Denies the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-title" - }, - { - "description": "Denies the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-toggle-maximize" - }, - { - "description": "Denies the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unmaximize" - }, - { - "description": "Denies the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unminimize" - }, - { - "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", - "type": "string", - "const": "dialog:default" - }, - { - "description": "Enables the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-ask" - }, - { - "description": "Enables the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-confirm" - }, - { - "description": "Enables the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-message" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-open" - }, - { - "description": "Enables the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-save" - }, - { - "description": "Denies the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-ask" - }, - { - "description": "Denies the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-confirm" - }, - { - "description": "Denies the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-message" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-open" - }, - { - "description": "Denies the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-save" - }, - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-size" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-size" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - }, - { - "description": "Allows the moveWindow and handleIconState APIs", - "type": "string", - "const": "positioner:default" - }, - { - "description": "Enables the move_window command without any pre-configured scope.", - "type": "string", - "const": "positioner:allow-move-window" - }, - { - "description": "Enables the move_window_constrained command without any pre-configured scope.", - "type": "string", - "const": "positioner:allow-move-window-constrained" - }, - { - "description": "Enables the set_tray_icon_state command without any pre-configured scope.", - "type": "string", - "const": "positioner:allow-set-tray-icon-state" - }, - { - "description": "Denies the move_window command without any pre-configured scope.", - "type": "string", - "const": "positioner:deny-move-window" - }, - { - "description": "Denies the move_window_constrained command without any pre-configured scope.", - "type": "string", - "const": "positioner:deny-move-window-constrained" - }, - { - "description": "Denies the set_tray_icon_state command without any pre-configured scope.", - "type": "string", - "const": "positioner:deny-set-tray-icon-state" - }, - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - }, - { - "description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n", - "type": "string", - "const": "window-state:default" - }, - { - "description": "Enables the filename command without any pre-configured scope.", - "type": "string", - "const": "window-state:allow-filename" - }, - { - "description": "Enables the restore_state command without any pre-configured scope.", - "type": "string", - "const": "window-state:allow-restore-state" - }, - { - "description": "Enables the save_window_state command without any pre-configured scope.", - "type": "string", - "const": "window-state:allow-save-window-state" - }, - { - "description": "Denies the filename command without any pre-configured scope.", - "type": "string", - "const": "window-state:deny-filename" - }, - { - "description": "Denies the restore_state command without any pre-configured scope.", - "type": "string", - "const": "window-state:deny-restore-state" - }, - { - "description": "Denies the save_window_state command without any pre-configured scope.", - "type": "string", - "const": "window-state:deny-save-window-state" - } - ] - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "ShellScopeEntryAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "raw": { - "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", - "default": false, - "type": "boolean" - }, - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellScopeEntryAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellScopeEntryAllowedArg" - } - } - ] - } - } -} \ No newline at end of file diff --git a/src-tauri/gen/schemas/macOS-schema.json b/src-tauri/gen/schemas/macOS-schema.json deleted file mode 100644 index 7ee3c87f4..000000000 --- a/src-tauri/gen/schemas/macOS-schema.json +++ /dev/null @@ -1,5181 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "CapabilityFile", - "description": "Capability formats accepted in a capability file.", - "anyOf": [ - { - "description": "A single capability.", - "allOf": [ - { - "$ref": "#/definitions/Capability" - } - ] - }, - { - "description": "A list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - }, - { - "description": "A list of capabilities.", - "type": "object", - "required": [ - "capabilities" - ], - "properties": { - "capabilities": { - "description": "The list of capabilities.", - "type": "array", - "items": { - "$ref": "#/definitions/Capability" - } - } - } - } - ], - "definitions": { - "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", - "type": "object", - "required": [ - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", - "type": "string" - }, - "description": { - "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.", - "default": "", - "type": "string" - }, - "remote": { - "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", - "anyOf": [ - { - "$ref": "#/definitions/CapabilityRemote" - }, - { - "type": "null" - } - ] - }, - "local": { - "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", - "default": true, - "type": "boolean" - }, - "windows": { - "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "webviews": { - "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", - "type": "array", - "items": { - "type": "string" - } - }, - "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionEntry" - }, - "uniqueItems": true - }, - "platforms": { - "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "CapabilityRemote": { - "description": "Configuration for remote URLs that are associated with the capability.", - "type": "object", - "required": [ - "urls" - ], - "properties": { - "urls": { - "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionEntry": { - "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", - "anyOf": [ - { - "description": "Reference a permission or permission set by identifier.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - { - "description": "Reference a permission or permission set by identifier and extends its scope.", - "type": "object", - "allOf": [ - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-size" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-size" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - } - } - } - ] - } - }, - "deny": { - "items": { - "title": "FsScopeEntry", - "description": "FS scope entry.", - "anyOf": [ - { - "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - { - "type": "object", - "required": [ - "path" - ], - "properties": { - "path": { - "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - } - } - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "if": { - "properties": { - "identifier": { - "anyOf": [ - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - } - ] - } - } - }, - "then": { - "properties": { - "allow": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - }, - "deny": { - "items": { - "title": "ShellScopeEntry", - "description": "Shell scope entry.", - "anyOf": [ - { - "type": "object", - "required": [ - "cmd", - "name" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "cmd": { - "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", - "type": "string" - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "name", - "sidecar" - ], - "properties": { - "args": { - "description": "The allowed arguments for the command execution.", - "allOf": [ - { - "$ref": "#/definitions/ShellScopeEntryAllowedArgs" - } - ] - }, - "name": { - "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", - "type": "string" - }, - "sidecar": { - "description": "If this command is a sidecar command.", - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - } - } - } - }, - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - } - } - }, - { - "properties": { - "identifier": { - "description": "Identifier of the permission or permission set.", - "allOf": [ - { - "$ref": "#/definitions/Identifier" - } - ] - }, - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - } - ], - "required": [ - "identifier" - ] - } - ] - }, - "Identifier": { - "description": "Permission identifier", - "oneOf": [ - { - "description": "Default core plugins set which includes:\n- 'core:path:default'\n- 'core:event:default'\n- 'core:window:default'\n- 'core:webview:default'\n- 'core:app:default'\n- 'core:image:default'\n- 'core:resources:default'\n- 'core:menu:default'\n- 'core:tray:default'\n", - "type": "string", - "const": "core:default" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:app:default" - }, - { - "description": "Enables the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-hide" - }, - { - "description": "Enables the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-app-show" - }, - { - "description": "Enables the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-default-window-icon" - }, - { - "description": "Enables the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-name" - }, - { - "description": "Enables the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-set-app-theme" - }, - { - "description": "Enables the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-tauri-version" - }, - { - "description": "Enables the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:allow-version" - }, - { - "description": "Denies the app_hide command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-hide" - }, - { - "description": "Denies the app_show command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-app-show" - }, - { - "description": "Denies the default_window_icon command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-default-window-icon" - }, - { - "description": "Denies the name command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-name" - }, - { - "description": "Denies the set_app_theme command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-set-app-theme" - }, - { - "description": "Denies the tauri_version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-tauri-version" - }, - { - "description": "Denies the version command without any pre-configured scope.", - "type": "string", - "const": "core:app:deny-version" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:event:default" - }, - { - "description": "Enables the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit" - }, - { - "description": "Enables the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-emit-to" - }, - { - "description": "Enables the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-listen" - }, - { - "description": "Enables the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:allow-unlisten" - }, - { - "description": "Denies the emit command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit" - }, - { - "description": "Denies the emit_to command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-emit-to" - }, - { - "description": "Denies the listen command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-listen" - }, - { - "description": "Denies the unlisten command without any pre-configured scope.", - "type": "string", - "const": "core:event:deny-unlisten" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:image:default" - }, - { - "description": "Enables the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-bytes" - }, - { - "description": "Enables the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-from-path" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-new" - }, - { - "description": "Enables the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-rgba" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:allow-size" - }, - { - "description": "Denies the from_bytes command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-bytes" - }, - { - "description": "Denies the from_path command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-from-path" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-new" - }, - { - "description": "Denies the rgba command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-rgba" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "core:image:deny-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:menu:default" - }, - { - "description": "Enables the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-append" - }, - { - "description": "Enables the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-create-default" - }, - { - "description": "Enables the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-get" - }, - { - "description": "Enables the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-insert" - }, - { - "description": "Enables the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-checked" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-is-enabled" - }, - { - "description": "Enables the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-items" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-new" - }, - { - "description": "Enables the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-popup" - }, - { - "description": "Enables the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-prepend" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove" - }, - { - "description": "Enables the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-remove-at" - }, - { - "description": "Enables the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-accelerator" - }, - { - "description": "Enables the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-app-menu" - }, - { - "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-help-menu-for-nsapp" - }, - { - "description": "Enables the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-window-menu" - }, - { - "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-as-windows-menu-for-nsapp" - }, - { - "description": "Enables the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-checked" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-enabled" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-icon" - }, - { - "description": "Enables the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-set-text" - }, - { - "description": "Enables the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:allow-text" - }, - { - "description": "Denies the append command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-append" - }, - { - "description": "Denies the create_default command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-create-default" - }, - { - "description": "Denies the get command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-get" - }, - { - "description": "Denies the insert command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-insert" - }, - { - "description": "Denies the is_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-checked" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-is-enabled" - }, - { - "description": "Denies the items command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-items" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-new" - }, - { - "description": "Denies the popup command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-popup" - }, - { - "description": "Denies the prepend command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-prepend" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove" - }, - { - "description": "Denies the remove_at command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-remove-at" - }, - { - "description": "Denies the set_accelerator command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-accelerator" - }, - { - "description": "Denies the set_as_app_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-app-menu" - }, - { - "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-help-menu-for-nsapp" - }, - { - "description": "Denies the set_as_window_menu command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-window-menu" - }, - { - "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-as-windows-menu-for-nsapp" - }, - { - "description": "Denies the set_checked command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-checked" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-enabled" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-icon" - }, - { - "description": "Denies the set_text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-set-text" - }, - { - "description": "Denies the text command without any pre-configured scope.", - "type": "string", - "const": "core:menu:deny-text" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:path:default" - }, - { - "description": "Enables the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-basename" - }, - { - "description": "Enables the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-dirname" - }, - { - "description": "Enables the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-extname" - }, - { - "description": "Enables the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-is-absolute" - }, - { - "description": "Enables the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-join" - }, - { - "description": "Enables the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-normalize" - }, - { - "description": "Enables the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve" - }, - { - "description": "Enables the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:allow-resolve-directory" - }, - { - "description": "Denies the basename command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-basename" - }, - { - "description": "Denies the dirname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-dirname" - }, - { - "description": "Denies the extname command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-extname" - }, - { - "description": "Denies the is_absolute command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-is-absolute" - }, - { - "description": "Denies the join command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-join" - }, - { - "description": "Denies the normalize command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-normalize" - }, - { - "description": "Denies the resolve command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve" - }, - { - "description": "Denies the resolve_directory command without any pre-configured scope.", - "type": "string", - "const": "core:path:deny-resolve-directory" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:resources:default" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:allow-close" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:resources:deny-close" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:tray:default" - }, - { - "description": "Enables the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-get-by-id" - }, - { - "description": "Enables the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-new" - }, - { - "description": "Enables the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-remove-by-id" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon" - }, - { - "description": "Enables the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-icon-as-template" - }, - { - "description": "Enables the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-menu" - }, - { - "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-show-menu-on-left-click" - }, - { - "description": "Enables the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-temp-dir-path" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-title" - }, - { - "description": "Enables the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-tooltip" - }, - { - "description": "Enables the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:allow-set-visible" - }, - { - "description": "Denies the get_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-get-by-id" - }, - { - "description": "Denies the new command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-new" - }, - { - "description": "Denies the remove_by_id command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-remove-by-id" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon" - }, - { - "description": "Denies the set_icon_as_template command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-icon-as-template" - }, - { - "description": "Denies the set_menu command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-menu" - }, - { - "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-show-menu-on-left-click" - }, - { - "description": "Denies the set_temp_dir_path command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-temp-dir-path" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-title" - }, - { - "description": "Denies the set_tooltip command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-tooltip" - }, - { - "description": "Denies the set_visible command without any pre-configured scope.", - "type": "string", - "const": "core:tray:deny-set-visible" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:webview:default" - }, - { - "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-clear-all-browsing-data" - }, - { - "description": "Enables the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview" - }, - { - "description": "Enables the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-create-webview-window" - }, - { - "description": "Enables the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-get-all-webviews" - }, - { - "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-internal-toggle-devtools" - }, - { - "description": "Enables the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-print" - }, - { - "description": "Enables the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-reparent" - }, - { - "description": "Enables the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-background-color" - }, - { - "description": "Enables the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-focus" - }, - { - "description": "Enables the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-position" - }, - { - "description": "Enables the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-size" - }, - { - "description": "Enables the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-set-webview-zoom" - }, - { - "description": "Enables the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-close" - }, - { - "description": "Enables the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-hide" - }, - { - "description": "Enables the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-position" - }, - { - "description": "Enables the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-show" - }, - { - "description": "Enables the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:allow-webview-size" - }, - { - "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-clear-all-browsing-data" - }, - { - "description": "Denies the create_webview command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview" - }, - { - "description": "Denies the create_webview_window command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-create-webview-window" - }, - { - "description": "Denies the get_all_webviews command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-get-all-webviews" - }, - { - "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-internal-toggle-devtools" - }, - { - "description": "Denies the print command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-print" - }, - { - "description": "Denies the reparent command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-reparent" - }, - { - "description": "Denies the set_webview_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-background-color" - }, - { - "description": "Denies the set_webview_focus command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-focus" - }, - { - "description": "Denies the set_webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-position" - }, - { - "description": "Denies the set_webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-size" - }, - { - "description": "Denies the set_webview_zoom command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-set-webview-zoom" - }, - { - "description": "Denies the webview_close command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-close" - }, - { - "description": "Denies the webview_hide command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-hide" - }, - { - "description": "Denies the webview_position command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-position" - }, - { - "description": "Denies the webview_show command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-show" - }, - { - "description": "Denies the webview_size command without any pre-configured scope.", - "type": "string", - "const": "core:webview:deny-webview-size" - }, - { - "description": "Default permissions for the plugin.", - "type": "string", - "const": "core:window:default" - }, - { - "description": "Enables the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-available-monitors" - }, - { - "description": "Enables the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-center" - }, - { - "description": "Enables the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-close" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-create" - }, - { - "description": "Enables the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-current-monitor" - }, - { - "description": "Enables the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-cursor-position" - }, - { - "description": "Enables the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-destroy" - }, - { - "description": "Enables the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-get-all-windows" - }, - { - "description": "Enables the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-hide" - }, - { - "description": "Enables the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-position" - }, - { - "description": "Enables the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-inner-size" - }, - { - "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-internal-toggle-maximize" - }, - { - "description": "Enables the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-closable" - }, - { - "description": "Enables the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-decorated" - }, - { - "description": "Enables the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-enabled" - }, - { - "description": "Enables the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-focused" - }, - { - "description": "Enables the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-fullscreen" - }, - { - "description": "Enables the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximizable" - }, - { - "description": "Enables the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-maximized" - }, - { - "description": "Enables the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimizable" - }, - { - "description": "Enables the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-minimized" - }, - { - "description": "Enables the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-resizable" - }, - { - "description": "Enables the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-is-visible" - }, - { - "description": "Enables the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-maximize" - }, - { - "description": "Enables the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-minimize" - }, - { - "description": "Enables the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-monitor-from-point" - }, - { - "description": "Enables the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-position" - }, - { - "description": "Enables the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-outer-size" - }, - { - "description": "Enables the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-primary-monitor" - }, - { - "description": "Enables the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-request-user-attention" - }, - { - "description": "Enables the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-scale-factor" - }, - { - "description": "Enables the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-bottom" - }, - { - "description": "Enables the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-always-on-top" - }, - { - "description": "Enables the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-background-color" - }, - { - "description": "Enables the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-count" - }, - { - "description": "Enables the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-badge-label" - }, - { - "description": "Enables the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-closable" - }, - { - "description": "Enables the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-content-protected" - }, - { - "description": "Enables the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-grab" - }, - { - "description": "Enables the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-icon" - }, - { - "description": "Enables the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-position" - }, - { - "description": "Enables the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-cursor-visible" - }, - { - "description": "Enables the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-decorations" - }, - { - "description": "Enables the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-effects" - }, - { - "description": "Enables the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-enabled" - }, - { - "description": "Enables the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-focus" - }, - { - "description": "Enables the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-fullscreen" - }, - { - "description": "Enables the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-icon" - }, - { - "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-ignore-cursor-events" - }, - { - "description": "Enables the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-max-size" - }, - { - "description": "Enables the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-maximizable" - }, - { - "description": "Enables the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-min-size" - }, - { - "description": "Enables the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-minimizable" - }, - { - "description": "Enables the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-overlay-icon" - }, - { - "description": "Enables the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-position" - }, - { - "description": "Enables the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-progress-bar" - }, - { - "description": "Enables the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-resizable" - }, - { - "description": "Enables the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-shadow" - }, - { - "description": "Enables the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size" - }, - { - "description": "Enables the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-size-constraints" - }, - { - "description": "Enables the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-skip-taskbar" - }, - { - "description": "Enables the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-theme" - }, - { - "description": "Enables the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title" - }, - { - "description": "Enables the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-title-bar-style" - }, - { - "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-set-visible-on-all-workspaces" - }, - { - "description": "Enables the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-show" - }, - { - "description": "Enables the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-dragging" - }, - { - "description": "Enables the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-start-resize-dragging" - }, - { - "description": "Enables the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-theme" - }, - { - "description": "Enables the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-title" - }, - { - "description": "Enables the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-toggle-maximize" - }, - { - "description": "Enables the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unmaximize" - }, - { - "description": "Enables the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:allow-unminimize" - }, - { - "description": "Denies the available_monitors command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-available-monitors" - }, - { - "description": "Denies the center command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-center" - }, - { - "description": "Denies the close command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-close" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-create" - }, - { - "description": "Denies the current_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-current-monitor" - }, - { - "description": "Denies the cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-cursor-position" - }, - { - "description": "Denies the destroy command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-destroy" - }, - { - "description": "Denies the get_all_windows command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-get-all-windows" - }, - { - "description": "Denies the hide command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-hide" - }, - { - "description": "Denies the inner_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-position" - }, - { - "description": "Denies the inner_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-inner-size" - }, - { - "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-internal-toggle-maximize" - }, - { - "description": "Denies the is_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-closable" - }, - { - "description": "Denies the is_decorated command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-decorated" - }, - { - "description": "Denies the is_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-enabled" - }, - { - "description": "Denies the is_focused command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-focused" - }, - { - "description": "Denies the is_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-fullscreen" - }, - { - "description": "Denies the is_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximizable" - }, - { - "description": "Denies the is_maximized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-maximized" - }, - { - "description": "Denies the is_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimizable" - }, - { - "description": "Denies the is_minimized command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-minimized" - }, - { - "description": "Denies the is_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-resizable" - }, - { - "description": "Denies the is_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-is-visible" - }, - { - "description": "Denies the maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-maximize" - }, - { - "description": "Denies the minimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-minimize" - }, - { - "description": "Denies the monitor_from_point command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-monitor-from-point" - }, - { - "description": "Denies the outer_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-position" - }, - { - "description": "Denies the outer_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-outer-size" - }, - { - "description": "Denies the primary_monitor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-primary-monitor" - }, - { - "description": "Denies the request_user_attention command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-request-user-attention" - }, - { - "description": "Denies the scale_factor command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-scale-factor" - }, - { - "description": "Denies the set_always_on_bottom command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-bottom" - }, - { - "description": "Denies the set_always_on_top command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-always-on-top" - }, - { - "description": "Denies the set_background_color command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-background-color" - }, - { - "description": "Denies the set_badge_count command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-count" - }, - { - "description": "Denies the set_badge_label command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-badge-label" - }, - { - "description": "Denies the set_closable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-closable" - }, - { - "description": "Denies the set_content_protected command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-content-protected" - }, - { - "description": "Denies the set_cursor_grab command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-grab" - }, - { - "description": "Denies the set_cursor_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-icon" - }, - { - "description": "Denies the set_cursor_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-position" - }, - { - "description": "Denies the set_cursor_visible command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-cursor-visible" - }, - { - "description": "Denies the set_decorations command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-decorations" - }, - { - "description": "Denies the set_effects command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-effects" - }, - { - "description": "Denies the set_enabled command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-enabled" - }, - { - "description": "Denies the set_focus command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-focus" - }, - { - "description": "Denies the set_fullscreen command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-fullscreen" - }, - { - "description": "Denies the set_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-icon" - }, - { - "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-ignore-cursor-events" - }, - { - "description": "Denies the set_max_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-max-size" - }, - { - "description": "Denies the set_maximizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-maximizable" - }, - { - "description": "Denies the set_min_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-min-size" - }, - { - "description": "Denies the set_minimizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-minimizable" - }, - { - "description": "Denies the set_overlay_icon command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-overlay-icon" - }, - { - "description": "Denies the set_position command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-position" - }, - { - "description": "Denies the set_progress_bar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-progress-bar" - }, - { - "description": "Denies the set_resizable command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-resizable" - }, - { - "description": "Denies the set_shadow command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-shadow" - }, - { - "description": "Denies the set_size command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size" - }, - { - "description": "Denies the set_size_constraints command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-size-constraints" - }, - { - "description": "Denies the set_skip_taskbar command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-skip-taskbar" - }, - { - "description": "Denies the set_theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-theme" - }, - { - "description": "Denies the set_title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title" - }, - { - "description": "Denies the set_title_bar_style command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-title-bar-style" - }, - { - "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-set-visible-on-all-workspaces" - }, - { - "description": "Denies the show command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-show" - }, - { - "description": "Denies the start_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-dragging" - }, - { - "description": "Denies the start_resize_dragging command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-start-resize-dragging" - }, - { - "description": "Denies the theme command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-theme" - }, - { - "description": "Denies the title command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-title" - }, - { - "description": "Denies the toggle_maximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-toggle-maximize" - }, - { - "description": "Denies the unmaximize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unmaximize" - }, - { - "description": "Denies the unminimize command without any pre-configured scope.", - "type": "string", - "const": "core:window:deny-unminimize" - }, - { - "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", - "type": "string", - "const": "dialog:default" - }, - { - "description": "Enables the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-ask" - }, - { - "description": "Enables the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-confirm" - }, - { - "description": "Enables the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-message" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-open" - }, - { - "description": "Enables the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:allow-save" - }, - { - "description": "Denies the ask command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-ask" - }, - { - "description": "Denies the confirm command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-confirm" - }, - { - "description": "Denies the message command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-message" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-open" - }, - { - "description": "Denies the save command without any pre-configured scope.", - "type": "string", - "const": "dialog:deny-save" - }, - { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", - "type": "string", - "const": "fs:default" - }, - { - "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta" - }, - { - "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.", - "type": "string", - "const": "fs:allow-app-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the application folders.", - "type": "string", - "const": "fs:allow-app-read" - }, - { - "description": "This allows full recursive read access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-read-recursive" - }, - { - "description": "This allows non-recursive write access to the application folders.", - "type": "string", - "const": "fs:allow-app-write" - }, - { - "description": "This allows full recursive write access to the complete application folders, files and subdirectories.", - "type": "string", - "const": "fs:allow-app-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appcache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCACHE` folder.", - "type": "string", - "const": "fs:allow-appcache-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appcache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appconfig-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:allow-appconfig-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appconfig-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-appdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPDATA` folder.", - "type": "string", - "const": "fs:allow-appdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-appdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applocaldata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:allow-applocaldata-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applocaldata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-applog-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-read" - }, - { - "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$APPLOG` folder.", - "type": "string", - "const": "fs:allow-applog-write" - }, - { - "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-applog-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-audio-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-read" - }, - { - "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$AUDIO` folder.", - "type": "string", - "const": "fs:allow-audio-write" - }, - { - "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-audio-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-cache-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-read" - }, - { - "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CACHE` folder.", - "type": "string", - "const": "fs:allow-cache-write" - }, - { - "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-cache-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-config-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-read" - }, - { - "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$CONFIG` folder.", - "type": "string", - "const": "fs:allow-config-write" - }, - { - "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-config-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-data-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-read" - }, - { - "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DATA` folder.", - "type": "string", - "const": "fs:allow-data-write" - }, - { - "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-data-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-desktop-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-read" - }, - { - "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DESKTOP` folder.", - "type": "string", - "const": "fs:allow-desktop-write" - }, - { - "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-desktop-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-document-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:allow-document-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-document-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-download-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-read" - }, - { - "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:allow-download-write" - }, - { - "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-download-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-exe-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-read" - }, - { - "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$EXE` folder.", - "type": "string", - "const": "fs:allow-exe-write" - }, - { - "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-exe-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-font-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-read" - }, - { - "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$FONT` folder.", - "type": "string", - "const": "fs:allow-font-write" - }, - { - "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-font-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-home-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-read" - }, - { - "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$HOME` folder.", - "type": "string", - "const": "fs:allow-home-write" - }, - { - "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-home-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-localdata-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:allow-localdata-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-localdata-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-log-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-read" - }, - { - "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$LOG` folder.", - "type": "string", - "const": "fs:allow-log-write" - }, - { - "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-log-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-picture-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-read" - }, - { - "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PICTURE` folder.", - "type": "string", - "const": "fs:allow-picture-write" - }, - { - "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-picture-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-public-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-read" - }, - { - "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$PUBLIC` folder.", - "type": "string", - "const": "fs:allow-public-write" - }, - { - "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-public-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-resource-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-read" - }, - { - "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RESOURCE` folder.", - "type": "string", - "const": "fs:allow-resource-write" - }, - { - "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-resource-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-runtime-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-read" - }, - { - "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$RUNTIME` folder.", - "type": "string", - "const": "fs:allow-runtime-write" - }, - { - "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-runtime-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-temp-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMP` folder.", - "type": "string", - "const": "fs:allow-temp-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-temp-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-template-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-read" - }, - { - "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:allow-template-write" - }, - { - "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-template-write-recursive" - }, - { - "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta" - }, - { - "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.", - "type": "string", - "const": "fs:allow-video-meta-recursive" - }, - { - "description": "This allows non-recursive read access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-read" - }, - { - "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-read-recursive" - }, - { - "description": "This allows non-recursive write access to the `$VIDEO` folder.", - "type": "string", - "const": "fs:allow-video-write" - }, - { - "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.", - "type": "string", - "const": "fs:allow-video-write-recursive" - }, - { - "description": "This denies access to dangerous Tauri relevant files and folders by default.", - "type": "string", - "const": "fs:deny-default" - }, - { - "description": "Enables the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-copy-file" - }, - { - "description": "Enables the create command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-create" - }, - { - "description": "Enables the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-exists" - }, - { - "description": "Enables the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-fstat" - }, - { - "description": "Enables the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-ftruncate" - }, - { - "description": "Enables the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-lstat" - }, - { - "description": "Enables the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-mkdir" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-open" - }, - { - "description": "Enables the read command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read" - }, - { - "description": "Enables the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-dir" - }, - { - "description": "Enables the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-file" - }, - { - "description": "Enables the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file" - }, - { - "description": "Enables the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines" - }, - { - "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-read-text-file-lines-next" - }, - { - "description": "Enables the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-remove" - }, - { - "description": "Enables the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-rename" - }, - { - "description": "Enables the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-seek" - }, - { - "description": "Enables the size command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-size" - }, - { - "description": "Enables the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-stat" - }, - { - "description": "Enables the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-truncate" - }, - { - "description": "Enables the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-unwatch" - }, - { - "description": "Enables the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-watch" - }, - { - "description": "Enables the write command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write" - }, - { - "description": "Enables the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-file" - }, - { - "description": "Enables the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:allow-write-text-file" - }, - { - "description": "This permissions allows to create the application specific directories.\n", - "type": "string", - "const": "fs:create-app-specific-dirs" - }, - { - "description": "Denies the copy_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-copy-file" - }, - { - "description": "Denies the create command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-create" - }, - { - "description": "Denies the exists command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-exists" - }, - { - "description": "Denies the fstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-fstat" - }, - { - "description": "Denies the ftruncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-ftruncate" - }, - { - "description": "Denies the lstat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-lstat" - }, - { - "description": "Denies the mkdir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-mkdir" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-open" - }, - { - "description": "Denies the read command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read" - }, - { - "description": "Denies the read_dir command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-dir" - }, - { - "description": "Denies the read_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-file" - }, - { - "description": "Denies the read_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file" - }, - { - "description": "Denies the read_text_file_lines command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines" - }, - { - "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-read-text-file-lines-next" - }, - { - "description": "Denies the remove command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-remove" - }, - { - "description": "Denies the rename command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-rename" - }, - { - "description": "Denies the seek command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-seek" - }, - { - "description": "Denies the size command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-size" - }, - { - "description": "Denies the stat command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-stat" - }, - { - "description": "Denies the truncate command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-truncate" - }, - { - "description": "Denies the unwatch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-unwatch" - }, - { - "description": "Denies the watch command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-watch" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-linux" - }, - { - "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", - "type": "string", - "const": "fs:deny-webview-data-windows" - }, - { - "description": "Denies the write command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write" - }, - { - "description": "Denies the write_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-file" - }, - { - "description": "Denies the write_text_file command without any pre-configured scope.", - "type": "string", - "const": "fs:deny-write-text-file" - }, - { - "description": "This enables all read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-all" - }, - { - "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", - "type": "string", - "const": "fs:read-app-specific-dirs-recursive" - }, - { - "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-dirs" - }, - { - "description": "This enables file read related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-files" - }, - { - "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:read-meta" - }, - { - "description": "An empty permission you can use to modify the global scope.", - "type": "string", - "const": "fs:scope" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the application folders.", - "type": "string", - "const": "fs:scope-app" - }, - { - "description": "This scope permits to list all files and folders in the application directories.", - "type": "string", - "const": "fs:scope-app-index" - }, - { - "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", - "type": "string", - "const": "fs:scope-app-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", - "type": "string", - "const": "fs:scope-appcache" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", - "type": "string", - "const": "fs:scope-appcache-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appcache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", - "type": "string", - "const": "fs:scope-appconfig" - }, - { - "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", - "type": "string", - "const": "fs:scope-appconfig-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appconfig-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", - "type": "string", - "const": "fs:scope-appdata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", - "type": "string", - "const": "fs:scope-appdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-appdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", - "type": "string", - "const": "fs:scope-applocaldata" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", - "type": "string", - "const": "fs:scope-applocaldata-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applocaldata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", - "type": "string", - "const": "fs:scope-applog" - }, - { - "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", - "type": "string", - "const": "fs:scope-applog-index" - }, - { - "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-applog-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", - "type": "string", - "const": "fs:scope-audio" - }, - { - "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", - "type": "string", - "const": "fs:scope-audio-index" - }, - { - "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-audio-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", - "type": "string", - "const": "fs:scope-cache" - }, - { - "description": "This scope permits to list all files and folders in the `$CACHE`folder.", - "type": "string", - "const": "fs:scope-cache-index" - }, - { - "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-cache-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", - "type": "string", - "const": "fs:scope-config" - }, - { - "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", - "type": "string", - "const": "fs:scope-config-index" - }, - { - "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-config-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", - "type": "string", - "const": "fs:scope-data" - }, - { - "description": "This scope permits to list all files and folders in the `$DATA`folder.", - "type": "string", - "const": "fs:scope-data-index" - }, - { - "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-data-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", - "type": "string", - "const": "fs:scope-desktop" - }, - { - "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", - "type": "string", - "const": "fs:scope-desktop-index" - }, - { - "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-desktop-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", - "type": "string", - "const": "fs:scope-document" - }, - { - "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", - "type": "string", - "const": "fs:scope-document-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-document-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", - "type": "string", - "const": "fs:scope-download" - }, - { - "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", - "type": "string", - "const": "fs:scope-download-index" - }, - { - "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-download-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", - "type": "string", - "const": "fs:scope-exe" - }, - { - "description": "This scope permits to list all files and folders in the `$EXE`folder.", - "type": "string", - "const": "fs:scope-exe-index" - }, - { - "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-exe-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", - "type": "string", - "const": "fs:scope-font" - }, - { - "description": "This scope permits to list all files and folders in the `$FONT`folder.", - "type": "string", - "const": "fs:scope-font-index" - }, - { - "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-font-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", - "type": "string", - "const": "fs:scope-home" - }, - { - "description": "This scope permits to list all files and folders in the `$HOME`folder.", - "type": "string", - "const": "fs:scope-home-index" - }, - { - "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-home-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", - "type": "string", - "const": "fs:scope-localdata" - }, - { - "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", - "type": "string", - "const": "fs:scope-localdata-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-localdata-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", - "type": "string", - "const": "fs:scope-log" - }, - { - "description": "This scope permits to list all files and folders in the `$LOG`folder.", - "type": "string", - "const": "fs:scope-log-index" - }, - { - "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-log-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", - "type": "string", - "const": "fs:scope-picture" - }, - { - "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", - "type": "string", - "const": "fs:scope-picture-index" - }, - { - "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-picture-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", - "type": "string", - "const": "fs:scope-public" - }, - { - "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", - "type": "string", - "const": "fs:scope-public-index" - }, - { - "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-public-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", - "type": "string", - "const": "fs:scope-resource" - }, - { - "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", - "type": "string", - "const": "fs:scope-resource-index" - }, - { - "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-resource-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", - "type": "string", - "const": "fs:scope-runtime" - }, - { - "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", - "type": "string", - "const": "fs:scope-runtime-index" - }, - { - "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-runtime-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", - "type": "string", - "const": "fs:scope-temp" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMP`folder.", - "type": "string", - "const": "fs:scope-temp-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-temp-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", - "type": "string", - "const": "fs:scope-template" - }, - { - "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", - "type": "string", - "const": "fs:scope-template-index" - }, - { - "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-template-recursive" - }, - { - "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", - "type": "string", - "const": "fs:scope-video" - }, - { - "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", - "type": "string", - "const": "fs:scope-video-index" - }, - { - "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", - "type": "string", - "const": "fs:scope-video-recursive" - }, - { - "description": "This enables all write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-all" - }, - { - "description": "This enables all file write related commands without any pre-configured accessible paths.", - "type": "string", - "const": "fs:write-files" - }, - { - "description": "Allows the moveWindow and handleIconState APIs", - "type": "string", - "const": "positioner:default" - }, - { - "description": "Enables the move_window command without any pre-configured scope.", - "type": "string", - "const": "positioner:allow-move-window" - }, - { - "description": "Enables the move_window_constrained command without any pre-configured scope.", - "type": "string", - "const": "positioner:allow-move-window-constrained" - }, - { - "description": "Enables the set_tray_icon_state command without any pre-configured scope.", - "type": "string", - "const": "positioner:allow-set-tray-icon-state" - }, - { - "description": "Denies the move_window command without any pre-configured scope.", - "type": "string", - "const": "positioner:deny-move-window" - }, - { - "description": "Denies the move_window_constrained command without any pre-configured scope.", - "type": "string", - "const": "positioner:deny-move-window-constrained" - }, - { - "description": "Denies the set_tray_icon_state command without any pre-configured scope.", - "type": "string", - "const": "positioner:deny-set-tray-icon-state" - }, - { - "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality without any specific\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n", - "type": "string", - "const": "shell:default" - }, - { - "description": "Enables the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-execute" - }, - { - "description": "Enables the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-kill" - }, - { - "description": "Enables the open command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-open" - }, - { - "description": "Enables the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-spawn" - }, - { - "description": "Enables the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:allow-stdin-write" - }, - { - "description": "Denies the execute command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-execute" - }, - { - "description": "Denies the kill command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-kill" - }, - { - "description": "Denies the open command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-open" - }, - { - "description": "Denies the spawn command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-spawn" - }, - { - "description": "Denies the stdin_write command without any pre-configured scope.", - "type": "string", - "const": "shell:deny-stdin-write" - }, - { - "description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n", - "type": "string", - "const": "window-state:default" - }, - { - "description": "Enables the filename command without any pre-configured scope.", - "type": "string", - "const": "window-state:allow-filename" - }, - { - "description": "Enables the restore_state command without any pre-configured scope.", - "type": "string", - "const": "window-state:allow-restore-state" - }, - { - "description": "Enables the save_window_state command without any pre-configured scope.", - "type": "string", - "const": "window-state:allow-save-window-state" - }, - { - "description": "Denies the filename command without any pre-configured scope.", - "type": "string", - "const": "window-state:deny-filename" - }, - { - "description": "Denies the restore_state command without any pre-configured scope.", - "type": "string", - "const": "window-state:deny-restore-state" - }, - { - "description": "Denies the save_window_state command without any pre-configured scope.", - "type": "string", - "const": "window-state:deny-save-window-state" - } - ] - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "ShellScopeEntryAllowedArg": { - "description": "A command argument allowed to be executed by the webview API.", - "anyOf": [ - { - "description": "A non-configurable argument that is passed to the command in the order it was specified.", - "type": "string" - }, - { - "description": "A variable that is set while calling the command from the webview API.", - "type": "object", - "required": [ - "validator" - ], - "properties": { - "raw": { - "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", - "default": false, - "type": "boolean" - }, - "validator": { - "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", - "type": "string" - } - }, - "additionalProperties": false - } - ] - }, - "ShellScopeEntryAllowedArgs": { - "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", - "anyOf": [ - { - "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", - "type": "boolean" - }, - { - "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", - "type": "array", - "items": { - "$ref": "#/definitions/ShellScopeEntryAllowedArg" - } - } - ] - } - } -} \ No newline at end of file diff --git a/src-tauri/gen/schemas/plugin-manifests.json b/src-tauri/gen/schemas/plugin-manifests.json deleted file mode 100644 index 8af6336fc..000000000 --- a/src-tauri/gen/schemas/plugin-manifests.json +++ /dev/null @@ -1 +0,0 @@ -{"app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version"]},"permissions":{"allow-app-hide":{"version":null,"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]},"scope":{}},"allow-app-show":{"version":null,"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]},"scope":{}},"allow-name":{"version":null,"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]},"scope":{}},"allow-tauri-version":{"version":null,"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]},"scope":{}},"allow-version":{"version":null,"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]},"scope":{}},"deny-app-hide":{"version":null,"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]},"scope":{}},"deny-app-show":{"version":null,"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]},"scope":{}},"deny-name":{"version":null,"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]},"scope":{}},"deny-tauri-version":{"version":null,"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]},"scope":{}},"deny-version":{"version":null,"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":null,"permissions":{"allow-ask":{"version":null,"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]},"scope":{}},"allow-confirm":{"version":null,"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]},"scope":{}},"allow-message":{"version":null,"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]},"scope":{}},"allow-open":{"version":null,"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]},"scope":{}},"allow-save":{"version":null,"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]},"scope":{}},"deny-ask":{"version":null,"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]},"scope":{}},"deny-confirm":{"version":null,"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]},"scope":{}},"deny-message":{"version":null,"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]},"scope":{}},"deny-open":{"version":null,"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]},"scope":{}},"deny-save":{"version":null,"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"version":null,"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]},"scope":{}},"allow-emit-to":{"version":null,"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]},"scope":{}},"allow-listen":{"version":null,"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]},"scope":{}},"allow-unlisten":{"version":null,"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]},"scope":{}},"deny-emit":{"version":null,"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]},"scope":{}},"deny-emit-to":{"version":null,"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]},"scope":{}},"deny-listen":{"version":null,"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]},"scope":{}},"deny-unlisten":{"version":null,"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"fs":{"default_permission":{"identifier":"default","description":"# Tauri `fs` default permissions\n\nThis configuration file defines the default permissions granted\nto the filesystem.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n","permissions":["read-all","scope-app-recursive","deny-default"]},"permissions":{"allow-copy-file":{"version":null,"identifier":"allow-copy-file","description":"Enables the copy_file command without any pre-configured scope.","commands":{"allow":["copy_file"],"deny":[]},"scope":{}},"allow-create":{"version":null,"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]},"scope":{}},"allow-exists":{"version":null,"identifier":"allow-exists","description":"Enables the exists command without any pre-configured scope.","commands":{"allow":["exists"],"deny":[]},"scope":{}},"allow-fstat":{"version":null,"identifier":"allow-fstat","description":"Enables the fstat command without any pre-configured scope.","commands":{"allow":["fstat"],"deny":[]},"scope":{}},"allow-ftruncate":{"version":null,"identifier":"allow-ftruncate","description":"Enables the ftruncate command without any pre-configured scope.","commands":{"allow":["ftruncate"],"deny":[]},"scope":{}},"allow-lstat":{"version":null,"identifier":"allow-lstat","description":"Enables the lstat command without any pre-configured scope.","commands":{"allow":["lstat"],"deny":[]},"scope":{}},"allow-mkdir":{"version":null,"identifier":"allow-mkdir","description":"Enables the mkdir command without any pre-configured scope.","commands":{"allow":["mkdir"],"deny":[]},"scope":{}},"allow-open":{"version":null,"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]},"scope":{}},"allow-read":{"version":null,"identifier":"allow-read","description":"Enables the read command without any pre-configured scope.","commands":{"allow":["read"],"deny":[]},"scope":{}},"allow-read-dir":{"version":null,"identifier":"allow-read-dir","description":"Enables the read_dir command without any pre-configured scope.","commands":{"allow":["read_dir"],"deny":[]},"scope":{}},"allow-read-file":{"version":null,"identifier":"allow-read-file","description":"Enables the read_file command without any pre-configured scope.","commands":{"allow":["read_file"],"deny":[]},"scope":{}},"allow-read-text-file":{"version":null,"identifier":"allow-read-text-file","description":"Enables the read_text_file command without any pre-configured scope.","commands":{"allow":["read_text_file"],"deny":[]},"scope":{}},"allow-read-text-file-lines":{"version":null,"identifier":"allow-read-text-file-lines","description":"Enables the read_text_file_lines command without any pre-configured scope.","commands":{"allow":["read_text_file_lines"],"deny":[]},"scope":{}},"allow-read-text-file-lines-next":{"version":null,"identifier":"allow-read-text-file-lines-next","description":"Enables the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":["read_text_file_lines_next"],"deny":[]},"scope":{}},"allow-remove":{"version":null,"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]},"scope":{}},"allow-rename":{"version":null,"identifier":"allow-rename","description":"Enables the rename command without any pre-configured scope.","commands":{"allow":["rename"],"deny":[]},"scope":{}},"allow-seek":{"version":null,"identifier":"allow-seek","description":"Enables the seek command without any pre-configured scope.","commands":{"allow":["seek"],"deny":[]},"scope":{}},"allow-stat":{"version":null,"identifier":"allow-stat","description":"Enables the stat command without any pre-configured scope.","commands":{"allow":["stat"],"deny":[]},"scope":{}},"allow-truncate":{"version":null,"identifier":"allow-truncate","description":"Enables the truncate command without any pre-configured scope.","commands":{"allow":["truncate"],"deny":[]},"scope":{}},"allow-unwatch":{"version":null,"identifier":"allow-unwatch","description":"Enables the unwatch command without any pre-configured scope.","commands":{"allow":["unwatch"],"deny":[]},"scope":{}},"allow-watch":{"version":null,"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]},"scope":{}},"allow-write":{"version":null,"identifier":"allow-write","description":"Enables the write command without any pre-configured scope.","commands":{"allow":["write"],"deny":[]},"scope":{}},"allow-write-file":{"version":null,"identifier":"allow-write-file","description":"Enables the write_file command without any pre-configured scope.","commands":{"allow":["write_file"],"deny":[]},"scope":{}},"allow-write-text-file":{"version":null,"identifier":"allow-write-text-file","description":"Enables the write_text_file command without any pre-configured scope.","commands":{"allow":["write_text_file"],"deny":[]},"scope":{}},"deny-copy-file":{"version":null,"identifier":"deny-copy-file","description":"Denies the copy_file command without any pre-configured scope.","commands":{"allow":[],"deny":["copy_file"]},"scope":{}},"deny-create":{"version":null,"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]},"scope":{}},"deny-exists":{"version":null,"identifier":"deny-exists","description":"Denies the exists command without any pre-configured scope.","commands":{"allow":[],"deny":["exists"]},"scope":{}},"deny-fstat":{"version":null,"identifier":"deny-fstat","description":"Denies the fstat command without any pre-configured scope.","commands":{"allow":[],"deny":["fstat"]},"scope":{}},"deny-ftruncate":{"version":null,"identifier":"deny-ftruncate","description":"Denies the ftruncate command without any pre-configured scope.","commands":{"allow":[],"deny":["ftruncate"]},"scope":{}},"deny-lstat":{"version":null,"identifier":"deny-lstat","description":"Denies the lstat command without any pre-configured scope.","commands":{"allow":[],"deny":["lstat"]},"scope":{}},"deny-mkdir":{"version":null,"identifier":"deny-mkdir","description":"Denies the mkdir command without any pre-configured scope.","commands":{"allow":[],"deny":["mkdir"]},"scope":{}},"deny-open":{"version":null,"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]},"scope":{}},"deny-read":{"version":null,"identifier":"deny-read","description":"Denies the read command without any pre-configured scope.","commands":{"allow":[],"deny":["read"]},"scope":{}},"deny-read-dir":{"version":null,"identifier":"deny-read-dir","description":"Denies the read_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["read_dir"]},"scope":{}},"deny-read-file":{"version":null,"identifier":"deny-read-file","description":"Denies the read_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_file"]},"scope":{}},"deny-read-text-file":{"version":null,"identifier":"deny-read-text-file","description":"Denies the read_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file"]},"scope":{}},"deny-read-text-file-lines":{"version":null,"identifier":"deny-read-text-file-lines","description":"Denies the read_text_file_lines command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines"]},"scope":{}},"deny-read-text-file-lines-next":{"version":null,"identifier":"deny-read-text-file-lines-next","description":"Denies the read_text_file_lines_next command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text_file_lines_next"]},"scope":{}},"deny-remove":{"version":null,"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]},"scope":{}},"deny-rename":{"version":null,"identifier":"deny-rename","description":"Denies the rename command without any pre-configured scope.","commands":{"allow":[],"deny":["rename"]},"scope":{}},"deny-seek":{"version":null,"identifier":"deny-seek","description":"Denies the seek command without any pre-configured scope.","commands":{"allow":[],"deny":["seek"]},"scope":{}},"deny-stat":{"version":null,"identifier":"deny-stat","description":"Denies the stat command without any pre-configured scope.","commands":{"allow":[],"deny":["stat"]},"scope":{}},"deny-truncate":{"version":null,"identifier":"deny-truncate","description":"Denies the truncate command without any pre-configured scope.","commands":{"allow":[],"deny":["truncate"]},"scope":{}},"deny-unwatch":{"version":null,"identifier":"deny-unwatch","description":"Denies the unwatch command without any pre-configured scope.","commands":{"allow":[],"deny":["unwatch"]},"scope":{}},"deny-watch":{"version":null,"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]},"scope":{}},"deny-webview-data-linux":{"version":null,"identifier":"deny-webview-data-linux","description":"This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]},"scope":{}},"deny-webview-data-windows":{"version":null,"identifier":"deny-webview-data-windows","description":"This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.","commands":{"allow":[],"deny":[]},"scope":{}},"deny-write":{"version":null,"identifier":"deny-write","description":"Denies the write command without any pre-configured scope.","commands":{"allow":[],"deny":["write"]},"scope":{}},"deny-write-file":{"version":null,"identifier":"deny-write-file","description":"Denies the write_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_file"]},"scope":{}},"deny-write-text-file":{"version":null,"identifier":"deny-write-text-file","description":"Denies the write_text_file command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text_file"]},"scope":{}},"read-all":{"version":null,"identifier":"read-all","description":"This enables all read related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists","watch","unwatch"],"deny":[]},"scope":{}},"read-dirs":{"version":null,"identifier":"read-dirs","description":"This enables directory read and file metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists"],"deny":[]},"scope":{}},"read-files":{"version":null,"identifier":"read-files","description":"This enables file read related commands without any pre-configured accessible paths.","commands":{"allow":["read_file","read","open","read_text_file","read_text_file_lines","read_text_file_lines_next","seek","stat","lstat","fstat","exists"],"deny":[]},"scope":{}},"read-meta":{"version":null,"identifier":"read-meta","description":"This enables all index or metadata related commands without any pre-configured accessible paths.","commands":{"allow":["read_dir","stat","lstat","fstat","exists"],"deny":[]},"scope":{}},"scope":{"version":null,"identifier":"scope","description":"An empty permission you can use to modify the global scope.","commands":{"allow":[],"deny":[]},"scope":{}},"scope-app":{"version":null,"identifier":"scope-app","description":"This scope permits access to all files and list content of top level directories in the `$APP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APP/*"}]}},"scope-app-index":{"version":null,"identifier":"scope-app-index","description":"This scope permits to list all files and folders in the `$APP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APP/"}]}},"scope-app-recursive":{"version":null,"identifier":"scope-app-recursive","description":"This scope recursive access to the complete `$APP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APP/**"}]}},"scope-appcache":{"version":null,"identifier":"scope-appcache","description":"This scope permits access to all files and list content of top level directories in the `$APPCACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE/*"}]}},"scope-appcache-index":{"version":null,"identifier":"scope-appcache-index","description":"This scope permits to list all files and folders in the `$APPCACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE/"}]}},"scope-appcache-recursive":{"version":null,"identifier":"scope-appcache-recursive","description":"This scope recursive access to the complete `$APPCACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCACHE/**"}]}},"scope-appconfig":{"version":null,"identifier":"scope-appconfig","description":"This scope permits access to all files and list content of top level directories in the `$APPCONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG/*"}]}},"scope-appconfig-index":{"version":null,"identifier":"scope-appconfig-index","description":"This scope permits to list all files and folders in the `$APPCONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG/"}]}},"scope-appconfig-recursive":{"version":null,"identifier":"scope-appconfig-recursive","description":"This scope recursive access to the complete `$APPCONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPCONFIG/**"}]}},"scope-appdata":{"version":null,"identifier":"scope-appdata","description":"This scope permits access to all files and list content of top level directories in the `$APPDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA/*"}]}},"scope-appdata-index":{"version":null,"identifier":"scope-appdata-index","description":"This scope permits to list all files and folders in the `$APPDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA/"}]}},"scope-appdata-recursive":{"version":null,"identifier":"scope-appdata-recursive","description":"This scope recursive access to the complete `$APPDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPDATA/**"}]}},"scope-applocaldata":{"version":null,"identifier":"scope-applocaldata","description":"This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA/*"}]}},"scope-applocaldata-index":{"version":null,"identifier":"scope-applocaldata-index","description":"This scope permits to list all files and folders in the `$APPLOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA/"}]}},"scope-applocaldata-recursive":{"version":null,"identifier":"scope-applocaldata-recursive","description":"This scope recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOCALDATA/**"}]}},"scope-applog":{"version":null,"identifier":"scope-applog","description":"This scope permits access to all files and list content of top level directories in the `$APPLOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG/*"}]}},"scope-applog-index":{"version":null,"identifier":"scope-applog-index","description":"This scope permits to list all files and folders in the `$APPLOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG/"}]}},"scope-applog-recursive":{"version":null,"identifier":"scope-applog-recursive","description":"This scope recursive access to the complete `$APPLOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$APPLOG/**"}]}},"scope-audio":{"version":null,"identifier":"scope-audio","description":"This scope permits access to all files and list content of top level directories in the `$AUDIO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO/*"}]}},"scope-audio-index":{"version":null,"identifier":"scope-audio-index","description":"This scope permits to list all files and folders in the `$AUDIO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO/"}]}},"scope-audio-recursive":{"version":null,"identifier":"scope-audio-recursive","description":"This scope recursive access to the complete `$AUDIO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$AUDIO/**"}]}},"scope-cache":{"version":null,"identifier":"scope-cache","description":"This scope permits access to all files and list content of top level directories in the `$CACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE/*"}]}},"scope-cache-index":{"version":null,"identifier":"scope-cache-index","description":"This scope permits to list all files and folders in the `$CACHE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE/"}]}},"scope-cache-recursive":{"version":null,"identifier":"scope-cache-recursive","description":"This scope recursive access to the complete `$CACHE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CACHE/**"}]}},"scope-config":{"version":null,"identifier":"scope-config","description":"This scope permits access to all files and list content of top level directories in the `$CONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG/*"}]}},"scope-config-index":{"version":null,"identifier":"scope-config-index","description":"This scope permits to list all files and folders in the `$CONFIG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG/"}]}},"scope-config-recursive":{"version":null,"identifier":"scope-config-recursive","description":"This scope recursive access to the complete `$CONFIG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$CONFIG/**"}]}},"scope-data":{"version":null,"identifier":"scope-data","description":"This scope permits access to all files and list content of top level directories in the `$DATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA/*"}]}},"scope-data-index":{"version":null,"identifier":"scope-data-index","description":"This scope permits to list all files and folders in the `$DATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA/"}]}},"scope-data-recursive":{"version":null,"identifier":"scope-data-recursive","description":"This scope recursive access to the complete `$DATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DATA/**"}]}},"scope-desktop":{"version":null,"identifier":"scope-desktop","description":"This scope permits access to all files and list content of top level directories in the `$DESKTOP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP/*"}]}},"scope-desktop-index":{"version":null,"identifier":"scope-desktop-index","description":"This scope permits to list all files and folders in the `$DESKTOP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP/"}]}},"scope-desktop-recursive":{"version":null,"identifier":"scope-desktop-recursive","description":"This scope recursive access to the complete `$DESKTOP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DESKTOP/**"}]}},"scope-document":{"version":null,"identifier":"scope-document","description":"This scope permits access to all files and list content of top level directories in the `$DOCUMENT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT/*"}]}},"scope-document-index":{"version":null,"identifier":"scope-document-index","description":"This scope permits to list all files and folders in the `$DOCUMENT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT/"}]}},"scope-document-recursive":{"version":null,"identifier":"scope-document-recursive","description":"This scope recursive access to the complete `$DOCUMENT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOCUMENT/**"}]}},"scope-download":{"version":null,"identifier":"scope-download","description":"This scope permits access to all files and list content of top level directories in the `$DOWNLOAD`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD/*"}]}},"scope-download-index":{"version":null,"identifier":"scope-download-index","description":"This scope permits to list all files and folders in the `$DOWNLOAD`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD/"}]}},"scope-download-recursive":{"version":null,"identifier":"scope-download-recursive","description":"This scope recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$DOWNLOAD/**"}]}},"scope-exe":{"version":null,"identifier":"scope-exe","description":"This scope permits access to all files and list content of top level directories in the `$EXE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE/*"}]}},"scope-exe-index":{"version":null,"identifier":"scope-exe-index","description":"This scope permits to list all files and folders in the `$EXE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE/"}]}},"scope-exe-recursive":{"version":null,"identifier":"scope-exe-recursive","description":"This scope recursive access to the complete `$EXE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$EXE/**"}]}},"scope-font":{"version":null,"identifier":"scope-font","description":"This scope permits access to all files and list content of top level directories in the `$FONT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT/*"}]}},"scope-font-index":{"version":null,"identifier":"scope-font-index","description":"This scope permits to list all files and folders in the `$FONT`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT/"}]}},"scope-font-recursive":{"version":null,"identifier":"scope-font-recursive","description":"This scope recursive access to the complete `$FONT` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$FONT/**"}]}},"scope-home":{"version":null,"identifier":"scope-home","description":"This scope permits access to all files and list content of top level directories in the `$HOME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME/*"}]}},"scope-home-index":{"version":null,"identifier":"scope-home-index","description":"This scope permits to list all files and folders in the `$HOME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME/"}]}},"scope-home-recursive":{"version":null,"identifier":"scope-home-recursive","description":"This scope recursive access to the complete `$HOME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$HOME/**"}]}},"scope-localdata":{"version":null,"identifier":"scope-localdata","description":"This scope permits access to all files and list content of top level directories in the `$LOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA/*"}]}},"scope-localdata-index":{"version":null,"identifier":"scope-localdata-index","description":"This scope permits to list all files and folders in the `$LOCALDATA`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA/"}]}},"scope-localdata-recursive":{"version":null,"identifier":"scope-localdata-recursive","description":"This scope recursive access to the complete `$LOCALDATA` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOCALDATA/**"}]}},"scope-log":{"version":null,"identifier":"scope-log","description":"This scope permits access to all files and list content of top level directories in the `$LOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG/*"}]}},"scope-log-index":{"version":null,"identifier":"scope-log-index","description":"This scope permits to list all files and folders in the `$LOG`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG/"}]}},"scope-log-recursive":{"version":null,"identifier":"scope-log-recursive","description":"This scope recursive access to the complete `$LOG` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$LOG/**"}]}},"scope-picture":{"version":null,"identifier":"scope-picture","description":"This scope permits access to all files and list content of top level directories in the `$PICTURE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE/*"}]}},"scope-picture-index":{"version":null,"identifier":"scope-picture-index","description":"This scope permits to list all files and folders in the `$PICTURE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE/"}]}},"scope-picture-recursive":{"version":null,"identifier":"scope-picture-recursive","description":"This scope recursive access to the complete `$PICTURE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PICTURE/**"}]}},"scope-public":{"version":null,"identifier":"scope-public","description":"This scope permits access to all files and list content of top level directories in the `$PUBLIC`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC/*"}]}},"scope-public-index":{"version":null,"identifier":"scope-public-index","description":"This scope permits to list all files and folders in the `$PUBLIC`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC/"}]}},"scope-public-recursive":{"version":null,"identifier":"scope-public-recursive","description":"This scope recursive access to the complete `$PUBLIC` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$PUBLIC/**"}]}},"scope-resource":{"version":null,"identifier":"scope-resource","description":"This scope permits access to all files and list content of top level directories in the `$RESOURCE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE/*"}]}},"scope-resource-index":{"version":null,"identifier":"scope-resource-index","description":"This scope permits to list all files and folders in the `$RESOURCE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE/"}]}},"scope-resource-recursive":{"version":null,"identifier":"scope-resource-recursive","description":"This scope recursive access to the complete `$RESOURCE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RESOURCE/**"}]}},"scope-runtime":{"version":null,"identifier":"scope-runtime","description":"This scope permits access to all files and list content of top level directories in the `$RUNTIME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME/*"}]}},"scope-runtime-index":{"version":null,"identifier":"scope-runtime-index","description":"This scope permits to list all files and folders in the `$RUNTIME`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME/"}]}},"scope-runtime-recursive":{"version":null,"identifier":"scope-runtime-recursive","description":"This scope recursive access to the complete `$RUNTIME` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$RUNTIME/**"}]}},"scope-temp":{"version":null,"identifier":"scope-temp","description":"This scope permits access to all files and list content of top level directories in the `$TEMP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP/*"}]}},"scope-temp-index":{"version":null,"identifier":"scope-temp-index","description":"This scope permits to list all files and folders in the `$TEMP`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP/"}]}},"scope-temp-recursive":{"version":null,"identifier":"scope-temp-recursive","description":"This scope recursive access to the complete `$TEMP` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMP/**"}]}},"scope-template":{"version":null,"identifier":"scope-template","description":"This scope permits access to all files and list content of top level directories in the `$TEMPLATE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE/*"}]}},"scope-template-index":{"version":null,"identifier":"scope-template-index","description":"This scope permits to list all files and folders in the `$TEMPLATE`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE/"}]}},"scope-template-recursive":{"version":null,"identifier":"scope-template-recursive","description":"This scope recursive access to the complete `$TEMPLATE` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$TEMPLATE/**"}]}},"scope-video":{"version":null,"identifier":"scope-video","description":"This scope permits access to all files and list content of top level directories in the `$VIDEO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO/*"}]}},"scope-video-index":{"version":null,"identifier":"scope-video-index","description":"This scope permits to list all files and folders in the `$VIDEO`folder.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO/"}]}},"scope-video-recursive":{"version":null,"identifier":"scope-video-recursive","description":"This scope recursive access to the complete `$VIDEO` folder, including sub directories and files.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"path":"$VIDEO/**"}]}},"write-all":{"version":null,"identifier":"write-all","description":"This enables all write related commands without any pre-configured accessible paths.","commands":{"allow":["mkdir","create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]},"scope":{}},"write-files":{"version":null,"identifier":"write-files","description":"This enables all file write related commands without any pre-configured accessible paths.","commands":{"allow":["create","copy_file","remove","rename","truncate","ftruncate","write","write_file","write_text_file"],"deny":[]},"scope":{}}},"permission_sets":{"allow-app-meta":{"identifier":"allow-app-meta","description":"This allows read access to metadata of the `$APP` folder, including file listing and statistics.","permissions":["read-meta","scope-app-index"]},"allow-app-meta-recursive":{"identifier":"allow-app-meta-recursive","description":"This allows read access to metadata of the `$APP` folder, including file listing and statistics.","permissions":["read-meta","scope-app-recursive"]},"allow-app-read":{"identifier":"allow-app-read","description":"This allows non-recursive read access to the `$APP` folder.","permissions":["read-all","scope-app"]},"allow-app-read-recursive":{"identifier":"allow-app-read-recursive","description":"This allows full recursive read access to the complete `$APP` folder, files and subdirectories.","permissions":["read-all","scope-app-recursive"]},"allow-app-write":{"identifier":"allow-app-write","description":"This allows non-recursive write access to the `$APP` folder.","permissions":["write-all","scope-app"]},"allow-app-write-recursive":{"identifier":"allow-app-write-recursive","description":"This allows full recusrive write access to the complete `$APP` folder, files and subdirectories.","permissions":["write-all","scope-app-recursive"]},"allow-appcache-meta":{"identifier":"allow-appcache-meta","description":"This allows read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-index"]},"allow-appcache-meta-recursive":{"identifier":"allow-appcache-meta-recursive","description":"This allows read access to metadata of the `$APPCACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-appcache-recursive"]},"allow-appcache-read":{"identifier":"allow-appcache-read","description":"This allows non-recursive read access to the `$APPCACHE` folder.","permissions":["read-all","scope-appcache"]},"allow-appcache-read-recursive":{"identifier":"allow-appcache-read-recursive","description":"This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["read-all","scope-appcache-recursive"]},"allow-appcache-write":{"identifier":"allow-appcache-write","description":"This allows non-recursive write access to the `$APPCACHE` folder.","permissions":["write-all","scope-appcache"]},"allow-appcache-write-recursive":{"identifier":"allow-appcache-write-recursive","description":"This allows full recusrive write access to the complete `$APPCACHE` folder, files and subdirectories.","permissions":["write-all","scope-appcache-recursive"]},"allow-appconfig-meta":{"identifier":"allow-appconfig-meta","description":"This allows read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-index"]},"allow-appconfig-meta-recursive":{"identifier":"allow-appconfig-meta-recursive","description":"This allows read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-appconfig-recursive"]},"allow-appconfig-read":{"identifier":"allow-appconfig-read","description":"This allows non-recursive read access to the `$APPCONFIG` folder.","permissions":["read-all","scope-appconfig"]},"allow-appconfig-read-recursive":{"identifier":"allow-appconfig-read-recursive","description":"This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["read-all","scope-appconfig-recursive"]},"allow-appconfig-write":{"identifier":"allow-appconfig-write","description":"This allows non-recursive write access to the `$APPCONFIG` folder.","permissions":["write-all","scope-appconfig"]},"allow-appconfig-write-recursive":{"identifier":"allow-appconfig-write-recursive","description":"This allows full recusrive write access to the complete `$APPCONFIG` folder, files and subdirectories.","permissions":["write-all","scope-appconfig-recursive"]},"allow-appdata-meta":{"identifier":"allow-appdata-meta","description":"This allows read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-index"]},"allow-appdata-meta-recursive":{"identifier":"allow-appdata-meta-recursive","description":"This allows read access to metadata of the `$APPDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-appdata-recursive"]},"allow-appdata-read":{"identifier":"allow-appdata-read","description":"This allows non-recursive read access to the `$APPDATA` folder.","permissions":["read-all","scope-appdata"]},"allow-appdata-read-recursive":{"identifier":"allow-appdata-read-recursive","description":"This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["read-all","scope-appdata-recursive"]},"allow-appdata-write":{"identifier":"allow-appdata-write","description":"This allows non-recursive write access to the `$APPDATA` folder.","permissions":["write-all","scope-appdata"]},"allow-appdata-write-recursive":{"identifier":"allow-appdata-write-recursive","description":"This allows full recusrive write access to the complete `$APPDATA` folder, files and subdirectories.","permissions":["write-all","scope-appdata-recursive"]},"allow-applocaldata-meta":{"identifier":"allow-applocaldata-meta","description":"This allows read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-index"]},"allow-applocaldata-meta-recursive":{"identifier":"allow-applocaldata-meta-recursive","description":"This allows read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-applocaldata-recursive"]},"allow-applocaldata-read":{"identifier":"allow-applocaldata-read","description":"This allows non-recursive read access to the `$APPLOCALDATA` folder.","permissions":["read-all","scope-applocaldata"]},"allow-applocaldata-read-recursive":{"identifier":"allow-applocaldata-read-recursive","description":"This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-applocaldata-recursive"]},"allow-applocaldata-write":{"identifier":"allow-applocaldata-write","description":"This allows non-recursive write access to the `$APPLOCALDATA` folder.","permissions":["write-all","scope-applocaldata"]},"allow-applocaldata-write-recursive":{"identifier":"allow-applocaldata-write-recursive","description":"This allows full recusrive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-applocaldata-recursive"]},"allow-applog-meta":{"identifier":"allow-applog-meta","description":"This allows read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-index"]},"allow-applog-meta-recursive":{"identifier":"allow-applog-meta-recursive","description":"This allows read access to metadata of the `$APPLOG` folder, including file listing and statistics.","permissions":["read-meta","scope-applog-recursive"]},"allow-applog-read":{"identifier":"allow-applog-read","description":"This allows non-recursive read access to the `$APPLOG` folder.","permissions":["read-all","scope-applog"]},"allow-applog-read-recursive":{"identifier":"allow-applog-read-recursive","description":"This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["read-all","scope-applog-recursive"]},"allow-applog-write":{"identifier":"allow-applog-write","description":"This allows non-recursive write access to the `$APPLOG` folder.","permissions":["write-all","scope-applog"]},"allow-applog-write-recursive":{"identifier":"allow-applog-write-recursive","description":"This allows full recusrive write access to the complete `$APPLOG` folder, files and subdirectories.","permissions":["write-all","scope-applog-recursive"]},"allow-audio-meta":{"identifier":"allow-audio-meta","description":"This allows read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-index"]},"allow-audio-meta-recursive":{"identifier":"allow-audio-meta-recursive","description":"This allows read access to metadata of the `$AUDIO` folder, including file listing and statistics.","permissions":["read-meta","scope-audio-recursive"]},"allow-audio-read":{"identifier":"allow-audio-read","description":"This allows non-recursive read access to the `$AUDIO` folder.","permissions":["read-all","scope-audio"]},"allow-audio-read-recursive":{"identifier":"allow-audio-read-recursive","description":"This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["read-all","scope-audio-recursive"]},"allow-audio-write":{"identifier":"allow-audio-write","description":"This allows non-recursive write access to the `$AUDIO` folder.","permissions":["write-all","scope-audio"]},"allow-audio-write-recursive":{"identifier":"allow-audio-write-recursive","description":"This allows full recusrive write access to the complete `$AUDIO` folder, files and subdirectories.","permissions":["write-all","scope-audio-recursive"]},"allow-cache-meta":{"identifier":"allow-cache-meta","description":"This allows read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-index"]},"allow-cache-meta-recursive":{"identifier":"allow-cache-meta-recursive","description":"This allows read access to metadata of the `$CACHE` folder, including file listing and statistics.","permissions":["read-meta","scope-cache-recursive"]},"allow-cache-read":{"identifier":"allow-cache-read","description":"This allows non-recursive read access to the `$CACHE` folder.","permissions":["read-all","scope-cache"]},"allow-cache-read-recursive":{"identifier":"allow-cache-read-recursive","description":"This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.","permissions":["read-all","scope-cache-recursive"]},"allow-cache-write":{"identifier":"allow-cache-write","description":"This allows non-recursive write access to the `$CACHE` folder.","permissions":["write-all","scope-cache"]},"allow-cache-write-recursive":{"identifier":"allow-cache-write-recursive","description":"This allows full recusrive write access to the complete `$CACHE` folder, files and subdirectories.","permissions":["write-all","scope-cache-recursive"]},"allow-config-meta":{"identifier":"allow-config-meta","description":"This allows read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-index"]},"allow-config-meta-recursive":{"identifier":"allow-config-meta-recursive","description":"This allows read access to metadata of the `$CONFIG` folder, including file listing and statistics.","permissions":["read-meta","scope-config-recursive"]},"allow-config-read":{"identifier":"allow-config-read","description":"This allows non-recursive read access to the `$CONFIG` folder.","permissions":["read-all","scope-config"]},"allow-config-read-recursive":{"identifier":"allow-config-read-recursive","description":"This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["read-all","scope-config-recursive"]},"allow-config-write":{"identifier":"allow-config-write","description":"This allows non-recursive write access to the `$CONFIG` folder.","permissions":["write-all","scope-config"]},"allow-config-write-recursive":{"identifier":"allow-config-write-recursive","description":"This allows full recusrive write access to the complete `$CONFIG` folder, files and subdirectories.","permissions":["write-all","scope-config-recursive"]},"allow-data-meta":{"identifier":"allow-data-meta","description":"This allows read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-index"]},"allow-data-meta-recursive":{"identifier":"allow-data-meta-recursive","description":"This allows read access to metadata of the `$DATA` folder, including file listing and statistics.","permissions":["read-meta","scope-data-recursive"]},"allow-data-read":{"identifier":"allow-data-read","description":"This allows non-recursive read access to the `$DATA` folder.","permissions":["read-all","scope-data"]},"allow-data-read-recursive":{"identifier":"allow-data-read-recursive","description":"This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.","permissions":["read-all","scope-data-recursive"]},"allow-data-write":{"identifier":"allow-data-write","description":"This allows non-recursive write access to the `$DATA` folder.","permissions":["write-all","scope-data"]},"allow-data-write-recursive":{"identifier":"allow-data-write-recursive","description":"This allows full recusrive write access to the complete `$DATA` folder, files and subdirectories.","permissions":["write-all","scope-data-recursive"]},"allow-desktop-meta":{"identifier":"allow-desktop-meta","description":"This allows read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-index"]},"allow-desktop-meta-recursive":{"identifier":"allow-desktop-meta-recursive","description":"This allows read access to metadata of the `$DESKTOP` folder, including file listing and statistics.","permissions":["read-meta","scope-desktop-recursive"]},"allow-desktop-read":{"identifier":"allow-desktop-read","description":"This allows non-recursive read access to the `$DESKTOP` folder.","permissions":["read-all","scope-desktop"]},"allow-desktop-read-recursive":{"identifier":"allow-desktop-read-recursive","description":"This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["read-all","scope-desktop-recursive"]},"allow-desktop-write":{"identifier":"allow-desktop-write","description":"This allows non-recursive write access to the `$DESKTOP` folder.","permissions":["write-all","scope-desktop"]},"allow-desktop-write-recursive":{"identifier":"allow-desktop-write-recursive","description":"This allows full recusrive write access to the complete `$DESKTOP` folder, files and subdirectories.","permissions":["write-all","scope-desktop-recursive"]},"allow-document-meta":{"identifier":"allow-document-meta","description":"This allows read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-index"]},"allow-document-meta-recursive":{"identifier":"allow-document-meta-recursive","description":"This allows read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.","permissions":["read-meta","scope-document-recursive"]},"allow-document-read":{"identifier":"allow-document-read","description":"This allows non-recursive read access to the `$DOCUMENT` folder.","permissions":["read-all","scope-document"]},"allow-document-read-recursive":{"identifier":"allow-document-read-recursive","description":"This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["read-all","scope-document-recursive"]},"allow-document-write":{"identifier":"allow-document-write","description":"This allows non-recursive write access to the `$DOCUMENT` folder.","permissions":["write-all","scope-document"]},"allow-document-write-recursive":{"identifier":"allow-document-write-recursive","description":"This allows full recusrive write access to the complete `$DOCUMENT` folder, files and subdirectories.","permissions":["write-all","scope-document-recursive"]},"allow-download-meta":{"identifier":"allow-download-meta","description":"This allows read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-index"]},"allow-download-meta-recursive":{"identifier":"allow-download-meta-recursive","description":"This allows read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.","permissions":["read-meta","scope-download-recursive"]},"allow-download-read":{"identifier":"allow-download-read","description":"This allows non-recursive read access to the `$DOWNLOAD` folder.","permissions":["read-all","scope-download"]},"allow-download-read-recursive":{"identifier":"allow-download-read-recursive","description":"This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["read-all","scope-download-recursive"]},"allow-download-write":{"identifier":"allow-download-write","description":"This allows non-recursive write access to the `$DOWNLOAD` folder.","permissions":["write-all","scope-download"]},"allow-download-write-recursive":{"identifier":"allow-download-write-recursive","description":"This allows full recusrive write access to the complete `$DOWNLOAD` folder, files and subdirectories.","permissions":["write-all","scope-download-recursive"]},"allow-exe-meta":{"identifier":"allow-exe-meta","description":"This allows read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-index"]},"allow-exe-meta-recursive":{"identifier":"allow-exe-meta-recursive","description":"This allows read access to metadata of the `$EXE` folder, including file listing and statistics.","permissions":["read-meta","scope-exe-recursive"]},"allow-exe-read":{"identifier":"allow-exe-read","description":"This allows non-recursive read access to the `$EXE` folder.","permissions":["read-all","scope-exe"]},"allow-exe-read-recursive":{"identifier":"allow-exe-read-recursive","description":"This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.","permissions":["read-all","scope-exe-recursive"]},"allow-exe-write":{"identifier":"allow-exe-write","description":"This allows non-recursive write access to the `$EXE` folder.","permissions":["write-all","scope-exe"]},"allow-exe-write-recursive":{"identifier":"allow-exe-write-recursive","description":"This allows full recusrive write access to the complete `$EXE` folder, files and subdirectories.","permissions":["write-all","scope-exe-recursive"]},"allow-font-meta":{"identifier":"allow-font-meta","description":"This allows read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-index"]},"allow-font-meta-recursive":{"identifier":"allow-font-meta-recursive","description":"This allows read access to metadata of the `$FONT` folder, including file listing and statistics.","permissions":["read-meta","scope-font-recursive"]},"allow-font-read":{"identifier":"allow-font-read","description":"This allows non-recursive read access to the `$FONT` folder.","permissions":["read-all","scope-font"]},"allow-font-read-recursive":{"identifier":"allow-font-read-recursive","description":"This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.","permissions":["read-all","scope-font-recursive"]},"allow-font-write":{"identifier":"allow-font-write","description":"This allows non-recursive write access to the `$FONT` folder.","permissions":["write-all","scope-font"]},"allow-font-write-recursive":{"identifier":"allow-font-write-recursive","description":"This allows full recusrive write access to the complete `$FONT` folder, files and subdirectories.","permissions":["write-all","scope-font-recursive"]},"allow-home-meta":{"identifier":"allow-home-meta","description":"This allows read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-index"]},"allow-home-meta-recursive":{"identifier":"allow-home-meta-recursive","description":"This allows read access to metadata of the `$HOME` folder, including file listing and statistics.","permissions":["read-meta","scope-home-recursive"]},"allow-home-read":{"identifier":"allow-home-read","description":"This allows non-recursive read access to the `$HOME` folder.","permissions":["read-all","scope-home"]},"allow-home-read-recursive":{"identifier":"allow-home-read-recursive","description":"This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.","permissions":["read-all","scope-home-recursive"]},"allow-home-write":{"identifier":"allow-home-write","description":"This allows non-recursive write access to the `$HOME` folder.","permissions":["write-all","scope-home"]},"allow-home-write-recursive":{"identifier":"allow-home-write-recursive","description":"This allows full recusrive write access to the complete `$HOME` folder, files and subdirectories.","permissions":["write-all","scope-home-recursive"]},"allow-localdata-meta":{"identifier":"allow-localdata-meta","description":"This allows read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-index"]},"allow-localdata-meta-recursive":{"identifier":"allow-localdata-meta-recursive","description":"This allows read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.","permissions":["read-meta","scope-localdata-recursive"]},"allow-localdata-read":{"identifier":"allow-localdata-read","description":"This allows non-recursive read access to the `$LOCALDATA` folder.","permissions":["read-all","scope-localdata"]},"allow-localdata-read-recursive":{"identifier":"allow-localdata-read-recursive","description":"This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["read-all","scope-localdata-recursive"]},"allow-localdata-write":{"identifier":"allow-localdata-write","description":"This allows non-recursive write access to the `$LOCALDATA` folder.","permissions":["write-all","scope-localdata"]},"allow-localdata-write-recursive":{"identifier":"allow-localdata-write-recursive","description":"This allows full recusrive write access to the complete `$LOCALDATA` folder, files and subdirectories.","permissions":["write-all","scope-localdata-recursive"]},"allow-log-meta":{"identifier":"allow-log-meta","description":"This allows read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-index"]},"allow-log-meta-recursive":{"identifier":"allow-log-meta-recursive","description":"This allows read access to metadata of the `$LOG` folder, including file listing and statistics.","permissions":["read-meta","scope-log-recursive"]},"allow-log-read":{"identifier":"allow-log-read","description":"This allows non-recursive read access to the `$LOG` folder.","permissions":["read-all","scope-log"]},"allow-log-read-recursive":{"identifier":"allow-log-read-recursive","description":"This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.","permissions":["read-all","scope-log-recursive"]},"allow-log-write":{"identifier":"allow-log-write","description":"This allows non-recursive write access to the `$LOG` folder.","permissions":["write-all","scope-log"]},"allow-log-write-recursive":{"identifier":"allow-log-write-recursive","description":"This allows full recusrive write access to the complete `$LOG` folder, files and subdirectories.","permissions":["write-all","scope-log-recursive"]},"allow-picture-meta":{"identifier":"allow-picture-meta","description":"This allows read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-index"]},"allow-picture-meta-recursive":{"identifier":"allow-picture-meta-recursive","description":"This allows read access to metadata of the `$PICTURE` folder, including file listing and statistics.","permissions":["read-meta","scope-picture-recursive"]},"allow-picture-read":{"identifier":"allow-picture-read","description":"This allows non-recursive read access to the `$PICTURE` folder.","permissions":["read-all","scope-picture"]},"allow-picture-read-recursive":{"identifier":"allow-picture-read-recursive","description":"This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["read-all","scope-picture-recursive"]},"allow-picture-write":{"identifier":"allow-picture-write","description":"This allows non-recursive write access to the `$PICTURE` folder.","permissions":["write-all","scope-picture"]},"allow-picture-write-recursive":{"identifier":"allow-picture-write-recursive","description":"This allows full recusrive write access to the complete `$PICTURE` folder, files and subdirectories.","permissions":["write-all","scope-picture-recursive"]},"allow-public-meta":{"identifier":"allow-public-meta","description":"This allows read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-index"]},"allow-public-meta-recursive":{"identifier":"allow-public-meta-recursive","description":"This allows read access to metadata of the `$PUBLIC` folder, including file listing and statistics.","permissions":["read-meta","scope-public-recursive"]},"allow-public-read":{"identifier":"allow-public-read","description":"This allows non-recursive read access to the `$PUBLIC` folder.","permissions":["read-all","scope-public"]},"allow-public-read-recursive":{"identifier":"allow-public-read-recursive","description":"This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["read-all","scope-public-recursive"]},"allow-public-write":{"identifier":"allow-public-write","description":"This allows non-recursive write access to the `$PUBLIC` folder.","permissions":["write-all","scope-public"]},"allow-public-write-recursive":{"identifier":"allow-public-write-recursive","description":"This allows full recusrive write access to the complete `$PUBLIC` folder, files and subdirectories.","permissions":["write-all","scope-public-recursive"]},"allow-resource-meta":{"identifier":"allow-resource-meta","description":"This allows read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-index"]},"allow-resource-meta-recursive":{"identifier":"allow-resource-meta-recursive","description":"This allows read access to metadata of the `$RESOURCE` folder, including file listing and statistics.","permissions":["read-meta","scope-resource-recursive"]},"allow-resource-read":{"identifier":"allow-resource-read","description":"This allows non-recursive read access to the `$RESOURCE` folder.","permissions":["read-all","scope-resource"]},"allow-resource-read-recursive":{"identifier":"allow-resource-read-recursive","description":"This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["read-all","scope-resource-recursive"]},"allow-resource-write":{"identifier":"allow-resource-write","description":"This allows non-recursive write access to the `$RESOURCE` folder.","permissions":["write-all","scope-resource"]},"allow-resource-write-recursive":{"identifier":"allow-resource-write-recursive","description":"This allows full recusrive write access to the complete `$RESOURCE` folder, files and subdirectories.","permissions":["write-all","scope-resource-recursive"]},"allow-runtime-meta":{"identifier":"allow-runtime-meta","description":"This allows read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-index"]},"allow-runtime-meta-recursive":{"identifier":"allow-runtime-meta-recursive","description":"This allows read access to metadata of the `$RUNTIME` folder, including file listing and statistics.","permissions":["read-meta","scope-runtime-recursive"]},"allow-runtime-read":{"identifier":"allow-runtime-read","description":"This allows non-recursive read access to the `$RUNTIME` folder.","permissions":["read-all","scope-runtime"]},"allow-runtime-read-recursive":{"identifier":"allow-runtime-read-recursive","description":"This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["read-all","scope-runtime-recursive"]},"allow-runtime-write":{"identifier":"allow-runtime-write","description":"This allows non-recursive write access to the `$RUNTIME` folder.","permissions":["write-all","scope-runtime"]},"allow-runtime-write-recursive":{"identifier":"allow-runtime-write-recursive","description":"This allows full recusrive write access to the complete `$RUNTIME` folder, files and subdirectories.","permissions":["write-all","scope-runtime-recursive"]},"allow-temp-meta":{"identifier":"allow-temp-meta","description":"This allows read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-index"]},"allow-temp-meta-recursive":{"identifier":"allow-temp-meta-recursive","description":"This allows read access to metadata of the `$TEMP` folder, including file listing and statistics.","permissions":["read-meta","scope-temp-recursive"]},"allow-temp-read":{"identifier":"allow-temp-read","description":"This allows non-recursive read access to the `$TEMP` folder.","permissions":["read-all","scope-temp"]},"allow-temp-read-recursive":{"identifier":"allow-temp-read-recursive","description":"This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.","permissions":["read-all","scope-temp-recursive"]},"allow-temp-write":{"identifier":"allow-temp-write","description":"This allows non-recursive write access to the `$TEMP` folder.","permissions":["write-all","scope-temp"]},"allow-temp-write-recursive":{"identifier":"allow-temp-write-recursive","description":"This allows full recusrive write access to the complete `$TEMP` folder, files and subdirectories.","permissions":["write-all","scope-temp-recursive"]},"allow-template-meta":{"identifier":"allow-template-meta","description":"This allows read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-index"]},"allow-template-meta-recursive":{"identifier":"allow-template-meta-recursive","description":"This allows read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.","permissions":["read-meta","scope-template-recursive"]},"allow-template-read":{"identifier":"allow-template-read","description":"This allows non-recursive read access to the `$TEMPLATE` folder.","permissions":["read-all","scope-template"]},"allow-template-read-recursive":{"identifier":"allow-template-read-recursive","description":"This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["read-all","scope-template-recursive"]},"allow-template-write":{"identifier":"allow-template-write","description":"This allows non-recursive write access to the `$TEMPLATE` folder.","permissions":["write-all","scope-template"]},"allow-template-write-recursive":{"identifier":"allow-template-write-recursive","description":"This allows full recusrive write access to the complete `$TEMPLATE` folder, files and subdirectories.","permissions":["write-all","scope-template-recursive"]},"allow-video-meta":{"identifier":"allow-video-meta","description":"This allows read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-index"]},"allow-video-meta-recursive":{"identifier":"allow-video-meta-recursive","description":"This allows read access to metadata of the `$VIDEO` folder, including file listing and statistics.","permissions":["read-meta","scope-video-recursive"]},"allow-video-read":{"identifier":"allow-video-read","description":"This allows non-recursive read access to the `$VIDEO` folder.","permissions":["read-all","scope-video"]},"allow-video-read-recursive":{"identifier":"allow-video-read-recursive","description":"This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["read-all","scope-video-recursive"]},"allow-video-write":{"identifier":"allow-video-write","description":"This allows non-recursive write access to the `$VIDEO` folder.","permissions":["write-all","scope-video"]},"allow-video-write-recursive":{"identifier":"allow-video-write-recursive","description":"This allows full recusrive write access to the complete `$VIDEO` folder, files and subdirectories.","permissions":["write-all","scope-video-recursive"]},"deny-default":{"identifier":"deny-default","description":"This denies access to dangerous Tauri relevant files and folders by default.","permissions":["deny-webview-data-linux","deny-webview-data-windows"]}},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","properties":{"path":{"type":"string"}},"required":["path"],"title":"Entry","type":"object"}},"menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":[]},"permissions":{"allow-append":{"version":null,"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]},"scope":{}},"allow-create-default":{"version":null,"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]},"scope":{}},"allow-get":{"version":null,"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]},"scope":{}},"allow-insert":{"version":null,"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]},"scope":{}},"allow-is-checked":{"version":null,"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]},"scope":{}},"allow-is-enabled":{"version":null,"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]},"scope":{}},"allow-items":{"version":null,"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]},"scope":{}},"allow-new":{"version":null,"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]},"scope":{}},"allow-popup":{"version":null,"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]},"scope":{}},"allow-prepend":{"version":null,"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]},"scope":{}},"allow-remove":{"version":null,"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]},"scope":{}},"allow-remove-at":{"version":null,"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]},"scope":{}},"allow-set-accelerator":{"version":null,"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]},"scope":{}},"allow-set-as-app-menu":{"version":null,"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]},"scope":{}},"allow-set-as-help-menu-for-nsapp":{"version":null,"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]},"scope":{}},"allow-set-as-window-menu":{"version":null,"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]},"scope":{}},"allow-set-as-windows-menu-for-nsapp":{"version":null,"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]},"scope":{}},"allow-set-checked":{"version":null,"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]},"scope":{}},"allow-set-enabled":{"version":null,"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]},"scope":{}},"allow-set-icon":{"version":null,"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]},"scope":{}},"allow-set-text":{"version":null,"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]},"scope":{}},"allow-text":{"version":null,"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]},"scope":{}},"deny-append":{"version":null,"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]},"scope":{}},"deny-create-default":{"version":null,"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]},"scope":{}},"deny-get":{"version":null,"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]},"scope":{}},"deny-insert":{"version":null,"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]},"scope":{}},"deny-is-checked":{"version":null,"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]},"scope":{}},"deny-is-enabled":{"version":null,"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]},"scope":{}},"deny-items":{"version":null,"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]},"scope":{}},"deny-new":{"version":null,"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]},"scope":{}},"deny-popup":{"version":null,"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]},"scope":{}},"deny-prepend":{"version":null,"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]},"scope":{}},"deny-remove":{"version":null,"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]},"scope":{}},"deny-remove-at":{"version":null,"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]},"scope":{}},"deny-set-accelerator":{"version":null,"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]},"scope":{}},"deny-set-as-app-menu":{"version":null,"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]},"scope":{}},"deny-set-as-help-menu-for-nsapp":{"version":null,"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]},"scope":{}},"deny-set-as-window-menu":{"version":null,"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]},"scope":{}},"deny-set-as-windows-menu-for-nsapp":{"version":null,"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]},"scope":{}},"deny-set-checked":{"version":null,"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]},"scope":{}},"deny-set-enabled":{"version":null,"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]},"scope":{}},"deny-set-icon":{"version":null,"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]},"scope":{}},"deny-set-text":{"version":null,"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]},"scope":{}},"deny-text":{"version":null,"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"version":null,"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]},"scope":{}},"allow-dirname":{"version":null,"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]},"scope":{}},"allow-extname":{"version":null,"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]},"scope":{}},"allow-is-absolute":{"version":null,"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]},"scope":{}},"allow-join":{"version":null,"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]},"scope":{}},"allow-normalize":{"version":null,"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]},"scope":{}},"allow-resolve":{"version":null,"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]},"scope":{}},"allow-resolve-directory":{"version":null,"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]},"scope":{}},"deny-basename":{"version":null,"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]},"scope":{}},"deny-dirname":{"version":null,"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]},"scope":{}},"deny-extname":{"version":null,"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]},"scope":{}},"deny-is-absolute":{"version":null,"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]},"scope":{}},"deny-join":{"version":null,"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]},"scope":{}},"deny-normalize":{"version":null,"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]},"scope":{}},"deny-resolve":{"version":null,"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]},"scope":{}},"deny-resolve-directory":{"version":null,"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"positioner":{"default_permission":{"identifier":"default","description":"Allows the move_window command","permissions":["allow-move-window"]},"permissions":{"allow-move-window":{"version":null,"identifier":"allow-move-window","description":"Enables the move_window command without any pre-configured scope.","commands":{"allow":["move_window"],"deny":[]},"scope":{}},"deny-move-window":{"version":null,"identifier":"deny-move-window","description":"Denies the move_window command without any pre-configured scope.","commands":{"allow":[],"deny":["move_window"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-close"]},"permissions":{"allow-close":{"version":null,"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]},"scope":{}},"deny-close":{"version":null,"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"shell":{"default_permission":null,"permissions":{"allow-execute":{"version":null,"identifier":"allow-execute","description":"Enables the execute command without any pre-configured scope.","commands":{"allow":["execute"],"deny":[]},"scope":{}},"allow-kill":{"version":null,"identifier":"allow-kill","description":"Enables the kill command without any pre-configured scope.","commands":{"allow":["kill"],"deny":[]},"scope":{}},"allow-open":{"version":null,"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]},"scope":{}},"allow-stdin-write":{"version":null,"identifier":"allow-stdin-write","description":"Enables the stdin_write command without any pre-configured scope.","commands":{"allow":["stdin_write"],"deny":[]},"scope":{}},"deny-execute":{"version":null,"identifier":"deny-execute","description":"Denies the execute command without any pre-configured scope.","commands":{"allow":[],"deny":["execute"]},"scope":{}},"deny-kill":{"version":null,"identifier":"deny-kill","description":"Denies the kill command without any pre-configured scope.","commands":{"allow":[],"deny":["kill"]},"scope":{}},"deny-open":{"version":null,"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]},"scope":{}},"deny-stdin-write":{"version":null,"identifier":"deny-stdin-write","description":"Denies the stdin_write command without any pre-configured scope.","commands":{"allow":[],"deny":["stdin_write"]},"scope":{}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","definitions":{"ShellAllowedArg":{"anyOf":[{"description":"A non-configurable argument that is passed to the command in the order it was specified.","type":"string"},{"additionalProperties":false,"description":"A variable that is set while calling the command from the webview API.","properties":{"validator":{"description":"[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\n[regex]: https://docs.rs/regex/latest/regex/#syntax","type":"string"}},"required":["validator"],"type":"object"}],"description":"A command argument allowed to be executed by the webview API."},"ShellAllowedArgs":{"anyOf":[{"description":"Use a simple boolean to allow all or disable all arguments to this command configuration.","type":"boolean"},{"description":"A specific set of [`ShellAllowedArg`] that are valid to call for the command configuration.","items":{"$ref":"#/definitions/ShellAllowedArg"},"type":"array"}],"description":"A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration."}},"description":"A command allowed to be executed by the webview API.","properties":{"args":{"allOf":[{"$ref":"#/definitions/ShellAllowedArgs"}],"description":"The allowed arguments for the command execution."},"command":{"description":"The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"},"name":{"description":"The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.","type":"string"},"sidecar":{"description":"If this command is a sidecar command.","type":"boolean"}},"required":["args","command","name","sidecar"],"title":"Entry","type":"object"}},"tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":[]},"permissions":{"allow-new":{"version":null,"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]},"scope":{}},"allow-set-icon":{"version":null,"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]},"scope":{}},"allow-set-icon-as-template":{"version":null,"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]},"scope":{}},"allow-set-menu":{"version":null,"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]},"scope":{}},"allow-set-show-menu-on-left-click":{"version":null,"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]},"scope":{}},"allow-set-temp-dir-path":{"version":null,"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]},"scope":{}},"allow-set-title":{"version":null,"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]},"scope":{}},"allow-set-tooltip":{"version":null,"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]},"scope":{}},"allow-set-visible":{"version":null,"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]},"scope":{}},"deny-new":{"version":null,"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]},"scope":{}},"deny-set-icon":{"version":null,"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]},"scope":{}},"deny-set-icon-as-template":{"version":null,"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]},"scope":{}},"deny-set-menu":{"version":null,"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]},"scope":{}},"deny-set-show-menu-on-left-click":{"version":null,"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]},"scope":{}},"deny-set-temp-dir-path":{"version":null,"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]},"scope":{}},"deny-set-title":{"version":null,"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]},"scope":{}},"deny-set-tooltip":{"version":null,"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]},"scope":{}},"deny-set-visible":{"version":null,"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-create-webview":{"version":null,"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]},"scope":{}},"allow-create-webview-window":{"version":null,"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]},"scope":{}},"allow-internal-toggle-devtools":{"version":null,"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]},"scope":{}},"allow-print":{"version":null,"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]},"scope":{}},"allow-set-webview-focus":{"version":null,"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]},"scope":{}},"allow-set-webview-position":{"version":null,"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]},"scope":{}},"allow-set-webview-size":{"version":null,"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]},"scope":{}},"allow-webview-close":{"version":null,"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]},"scope":{}},"allow-webview-position":{"version":null,"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]},"scope":{}},"allow-webview-size":{"version":null,"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]},"scope":{}},"deny-create-webview":{"version":null,"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]},"scope":{}},"deny-create-webview-window":{"version":null,"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]},"scope":{}},"deny-internal-toggle-devtools":{"version":null,"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]},"scope":{}},"deny-print":{"version":null,"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]},"scope":{}},"deny-set-webview-focus":{"version":null,"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]},"scope":{}},"deny-set-webview-position":{"version":null,"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]},"scope":{}},"deny-set-webview-size":{"version":null,"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]},"scope":{}},"deny-webview-close":{"version":null,"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]},"scope":{}},"deny-webview-position":{"version":null,"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]},"scope":{}},"deny-webview-size":{"version":null,"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-title","allow-current-monitor","allow-primary-monitor","allow-available-monitors","allow-theme","allow-internal-toggle-maximize","allow-internal-on-mousemove","allow-internal-on-mousedown"]},"permissions":{"allow-available-monitors":{"version":null,"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]},"scope":{}},"allow-center":{"version":null,"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]},"scope":{}},"allow-close":{"version":null,"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]},"scope":{}},"allow-create":{"version":null,"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]},"scope":{}},"allow-current-monitor":{"version":null,"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]},"scope":{}},"allow-destroy":{"version":null,"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]},"scope":{}},"allow-hide":{"version":null,"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]},"scope":{}},"allow-inner-position":{"version":null,"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]},"scope":{}},"allow-inner-size":{"version":null,"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]},"scope":{}},"allow-internal-on-mousedown":{"version":null,"identifier":"allow-internal-on-mousedown","description":"Enables the internal_on_mousedown command without any pre-configured scope.","commands":{"allow":["internal_on_mousedown"],"deny":[]},"scope":{}},"allow-internal-on-mousemove":{"version":null,"identifier":"allow-internal-on-mousemove","description":"Enables the internal_on_mousemove command without any pre-configured scope.","commands":{"allow":["internal_on_mousemove"],"deny":[]},"scope":{}},"allow-internal-toggle-maximize":{"version":null,"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]},"scope":{}},"allow-is-closable":{"version":null,"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]},"scope":{}},"allow-is-decorated":{"version":null,"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]},"scope":{}},"allow-is-focused":{"version":null,"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]},"scope":{}},"allow-is-fullscreen":{"version":null,"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]},"scope":{}},"allow-is-maximizable":{"version":null,"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]},"scope":{}},"allow-is-maximized":{"version":null,"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]},"scope":{}},"allow-is-minimizable":{"version":null,"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]},"scope":{}},"allow-is-minimized":{"version":null,"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]},"scope":{}},"allow-is-resizable":{"version":null,"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]},"scope":{}},"allow-is-visible":{"version":null,"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]},"scope":{}},"allow-maximize":{"version":null,"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]},"scope":{}},"allow-minimize":{"version":null,"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]},"scope":{}},"allow-outer-position":{"version":null,"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]},"scope":{}},"allow-outer-size":{"version":null,"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]},"scope":{}},"allow-primary-monitor":{"version":null,"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]},"scope":{}},"allow-request-user-attention":{"version":null,"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]},"scope":{}},"allow-scale-factor":{"version":null,"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]},"scope":{}},"allow-set-always-on-bottom":{"version":null,"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]},"scope":{}},"allow-set-always-on-top":{"version":null,"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]},"scope":{}},"allow-set-closable":{"version":null,"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]},"scope":{}},"allow-set-content-protected":{"version":null,"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]},"scope":{}},"allow-set-cursor-grab":{"version":null,"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]},"scope":{}},"allow-set-cursor-icon":{"version":null,"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]},"scope":{}},"allow-set-cursor-position":{"version":null,"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]},"scope":{}},"allow-set-cursor-visible":{"version":null,"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]},"scope":{}},"allow-set-decorations":{"version":null,"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]},"scope":{}},"allow-set-effects":{"version":null,"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]},"scope":{}},"allow-set-focus":{"version":null,"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]},"scope":{}},"allow-set-fullscreen":{"version":null,"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]},"scope":{}},"allow-set-icon":{"version":null,"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]},"scope":{}},"allow-set-ignore-cursor-events":{"version":null,"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]},"scope":{}},"allow-set-max-size":{"version":null,"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]},"scope":{}},"allow-set-maximizable":{"version":null,"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]},"scope":{}},"allow-set-min-size":{"version":null,"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]},"scope":{}},"allow-set-minimizable":{"version":null,"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]},"scope":{}},"allow-set-position":{"version":null,"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]},"scope":{}},"allow-set-progress-bar":{"version":null,"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]},"scope":{}},"allow-set-resizable":{"version":null,"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]},"scope":{}},"allow-set-shadow":{"version":null,"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]},"scope":{}},"allow-set-size":{"version":null,"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]},"scope":{}},"allow-set-skip-taskbar":{"version":null,"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]},"scope":{}},"allow-set-title":{"version":null,"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]},"scope":{}},"allow-set-visible-on-all-workspaces":{"version":null,"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]},"scope":{}},"allow-show":{"version":null,"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]},"scope":{}},"allow-start-dragging":{"version":null,"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]},"scope":{}},"allow-theme":{"version":null,"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]},"scope":{}},"allow-title":{"version":null,"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]},"scope":{}},"allow-toggle-maximize":{"version":null,"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]},"scope":{}},"allow-unmaximize":{"version":null,"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]},"scope":{}},"allow-unminimize":{"version":null,"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]},"scope":{}},"deny-available-monitors":{"version":null,"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]},"scope":{}},"deny-center":{"version":null,"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]},"scope":{}},"deny-close":{"version":null,"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]},"scope":{}},"deny-create":{"version":null,"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]},"scope":{}},"deny-current-monitor":{"version":null,"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]},"scope":{}},"deny-destroy":{"version":null,"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]},"scope":{}},"deny-hide":{"version":null,"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]},"scope":{}},"deny-inner-position":{"version":null,"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]},"scope":{}},"deny-inner-size":{"version":null,"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]},"scope":{}},"deny-internal-on-mousedown":{"version":null,"identifier":"deny-internal-on-mousedown","description":"Denies the internal_on_mousedown command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_on_mousedown"]},"scope":{}},"deny-internal-on-mousemove":{"version":null,"identifier":"deny-internal-on-mousemove","description":"Denies the internal_on_mousemove command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_on_mousemove"]},"scope":{}},"deny-internal-toggle-maximize":{"version":null,"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]},"scope":{}},"deny-is-closable":{"version":null,"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]},"scope":{}},"deny-is-decorated":{"version":null,"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]},"scope":{}},"deny-is-focused":{"version":null,"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]},"scope":{}},"deny-is-fullscreen":{"version":null,"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]},"scope":{}},"deny-is-maximizable":{"version":null,"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]},"scope":{}},"deny-is-maximized":{"version":null,"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]},"scope":{}},"deny-is-minimizable":{"version":null,"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]},"scope":{}},"deny-is-minimized":{"version":null,"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]},"scope":{}},"deny-is-resizable":{"version":null,"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]},"scope":{}},"deny-is-visible":{"version":null,"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]},"scope":{}},"deny-maximize":{"version":null,"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]},"scope":{}},"deny-minimize":{"version":null,"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]},"scope":{}},"deny-outer-position":{"version":null,"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]},"scope":{}},"deny-outer-size":{"version":null,"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]},"scope":{}},"deny-primary-monitor":{"version":null,"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]},"scope":{}},"deny-request-user-attention":{"version":null,"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]},"scope":{}},"deny-scale-factor":{"version":null,"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]},"scope":{}},"deny-set-always-on-bottom":{"version":null,"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]},"scope":{}},"deny-set-always-on-top":{"version":null,"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]},"scope":{}},"deny-set-closable":{"version":null,"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]},"scope":{}},"deny-set-content-protected":{"version":null,"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]},"scope":{}},"deny-set-cursor-grab":{"version":null,"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]},"scope":{}},"deny-set-cursor-icon":{"version":null,"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]},"scope":{}},"deny-set-cursor-position":{"version":null,"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]},"scope":{}},"deny-set-cursor-visible":{"version":null,"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]},"scope":{}},"deny-set-decorations":{"version":null,"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]},"scope":{}},"deny-set-effects":{"version":null,"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]},"scope":{}},"deny-set-focus":{"version":null,"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]},"scope":{}},"deny-set-fullscreen":{"version":null,"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]},"scope":{}},"deny-set-icon":{"version":null,"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]},"scope":{}},"deny-set-ignore-cursor-events":{"version":null,"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]},"scope":{}},"deny-set-max-size":{"version":null,"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]},"scope":{}},"deny-set-maximizable":{"version":null,"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]},"scope":{}},"deny-set-min-size":{"version":null,"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]},"scope":{}},"deny-set-minimizable":{"version":null,"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]},"scope":{}},"deny-set-position":{"version":null,"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]},"scope":{}},"deny-set-progress-bar":{"version":null,"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]},"scope":{}},"deny-set-resizable":{"version":null,"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]},"scope":{}},"deny-set-shadow":{"version":null,"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]},"scope":{}},"deny-set-size":{"version":null,"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]},"scope":{}},"deny-set-skip-taskbar":{"version":null,"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]},"scope":{}},"deny-set-title":{"version":null,"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]},"scope":{}},"deny-set-visible-on-all-workspaces":{"version":null,"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]},"scope":{}},"deny-show":{"version":null,"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]},"scope":{}},"deny-start-dragging":{"version":null,"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]},"scope":{}},"deny-theme":{"version":null,"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]},"scope":{}},"deny-title":{"version":null,"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]},"scope":{}},"deny-toggle-maximize":{"version":null,"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]},"scope":{}},"deny-unmaximize":{"version":null,"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]},"scope":{}},"deny-unminimize":{"version":null,"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null},"window-state":{"default_permission":null,"permissions":{"allow-restore-window-state":{"version":null,"identifier":"allow-restore-window-state","description":"Enables the restore_window_state command without any pre-configured scope.","commands":{"allow":["restore_window_state"],"deny":[]},"scope":{}},"allow-save-window-state":{"version":null,"identifier":"allow-save-window-state","description":"Enables the save_window_state command without any pre-configured scope.","commands":{"allow":["save_window_state"],"deny":[]},"scope":{}},"deny-restore-window-state":{"version":null,"identifier":"deny-restore-window-state","description":"Denies the restore_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["restore_window_state"]},"scope":{}},"deny-save-window-state":{"version":null,"identifier":"deny-save-window-state","description":"Denies the save_window_state command without any pre-configured scope.","commands":{"allow":[],"deny":["save_window_state"]},"scope":{}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns deleted file mode 100644 index 6ef70f3ad..000000000 Binary files a/src-tauri/icons/icon.icns and /dev/null differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico deleted file mode 100644 index 2171bb312..000000000 Binary files a/src-tauri/icons/icon.ico and /dev/null differ diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs deleted file mode 100644 index 34962a1b1..000000000 --- a/src-tauri/src/main.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -use tauri::{webview::Url, AppHandle, WebviewBuilder, WebviewUrl, WindowBuilder}; -use tauri::{Manager, Runtime}; -use tauri_plugin_positioner::{Position, WindowExt}; -use tauri_plugin_window_state::{StateFlags, WindowExt as WindowStateExt}; - -// ๐Ÿš€ PRODUCTION - -#[cfg(not(dev))] -fn main() { - let port = 44999; - let url = format!("http://localhost:{}", port).parse().unwrap(); - - let builder = default_builder().plugin(tauri_plugin_localhost::Builder::new(port).build()); - - setup(url, builder) -} - -// ๐Ÿ’ฃ DEVELOPMENT - -#[cfg(dev)] -fn main() { - let port = 8000; - let url = format!("http://localhost:{}", port).parse().unwrap(); - - let builder = default_builder(); - - setup(url, builder) -} - -// BUILDER - -fn default_builder() -> tauri::Builder { - tauri::Builder::default() - .plugin(tauri_plugin_fs::init()) - .plugin(tauri_plugin_shell::init()) - .plugin(tauri_plugin_dialog::init()) - .plugin(tauri_plugin_window_state::Builder::default().build()) -} - -// WINDOWS - -fn build_window(app: &AppHandle, url: Url) { - let monitor = app.primary_monitor().unwrap(); - - let height; - let width; - - match monitor { - Some(m) => { - height = (m.size().height as f64 / m.scale_factor()) - 80.0; - width = (m.size().width as f64 / m.scale_factor()) - 40.0; - } - - None => { - height = 675.0; - width = 1080.0; - } - } - - let mut window_builder = WindowBuilder::new(app, "main") - .title("Diffuse") - .theme(None) - .inner_size(width, height); - - window_builder = title_styles(window_builder); - - let window = window_builder.build().unwrap(); - window.move_window(Position::Center).unwrap(); - window.restore_state(StateFlags::all()).unwrap(); - - let webview_builder = WebviewBuilder::new("main", WebviewUrl::External(url)) - .auto_resize() - .enable_clipboard_access() - .user_agent("Chrome"); - - window - .add_child( - webview_builder, - tauri::LogicalPosition::new(0, 0), - window.inner_size().unwrap(), - ) - .unwrap(); - - window.set_resizable(true).unwrap(); -} - -#[cfg(target_os = "macos")] -fn title_styles>(builder: WindowBuilder) -> WindowBuilder { - return builder - .title_bar_style(tauri::TitleBarStyle::Overlay) - .hidden_title(true); -} - -#[cfg(not(target_os = "macos"))] -fn title_styles>(builder: WindowBuilder) -> WindowBuilder { - return builder; -} - -// SETUP - -fn setup(url: Url, builder: tauri::Builder) { - builder - .setup(move |app| { - build_window(app.handle(), url); - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json deleted file mode 100644 index c4006b07e..000000000 --- a/src-tauri/tauri.conf.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "app": { - "security": { - "csp": null - }, - "windows": [], - "withGlobalTauri": true - }, - "build": { - "beforeBuildCommand": "", - "beforeDevCommand": "", - "devUrl": "http://localhost:8000", - "frontendDist": "../dist" - }, - "bundle": { - "active": true, - "category": "Music", - "icon": [ - "icons/icon.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "targets": "all" - }, - "identifier": "com.icidasset.diffuse", - "plugins": {}, - "productName": "Diffuse", - "version": "3.5.0" -} \ No newline at end of file diff --git a/src/Core/Brain.elm b/src/Core/Brain.elm deleted file mode 100644 index 081e1d0be..000000000 --- a/src/Core/Brain.elm +++ /dev/null @@ -1,273 +0,0 @@ -module Brain exposing (main) - -import Alien -import Brain.Common.State as Common -import Brain.Other.State as Other -import Brain.Ports as Ports -import Brain.Sources.Processing.State as Processing -import Brain.Sources.Processing.Types as Processing -import Brain.Tracks.State as Tracks -import Brain.Types exposing (..) -import Brain.User.State as User -import Brain.User.Types as User -import Debouncer.Basic as Debouncer -import Json.Decode as Json -import Return -import Return.Ext as Return -import Sources.Processing as Processing -import Task -import Time -import Time.Ext as Time -import Url -import User.Layer as User - - - --- ๐Ÿง  - - -main : Program Flags Model Msg -main = - Platform.worker - { init = init - , update = update - , subscriptions = subscriptions - } - - - --- ๐ŸŒณ - - -init : Flags -> ( Model, Cmd Msg ) -init flags = - let - hypDebouncer = - 2.5 - |> Debouncer.fromSeconds - |> Debouncer.debounce - |> Debouncer.accumulateWith Debouncer.allInputs - |> Debouncer.toDebouncer - - initialUrl = - flags.initialUrl - |> Url.fromString - |> Maybe.withDefault - { protocol = Url.Http - , host = "" - , port_ = Nothing - , path = "" - , query = Nothing - , fragment = Nothing - } - in - ( ----------------------------------------- - -- Initial model - ----------------------------------------- - { currentTime = Time.default - , hypaethralDebouncer = hypDebouncer - , hypaethralRetrieval = Nothing - , hypaethralStorage = [] - , hypaethralUserData = User.emptyHypaethralData - , origin = "ORIGIN_UNKNOWN" - , processingStatus = Processing.NotProcessing - , userSyncMethod = Nothing - } - ----------------------------------------- - -- Initial command - ----------------------------------------- - , Cmd.batch - [ Task.perform SetCurrentTime Time.now - , User.initialCommand initialUrl - ] - ) - - - --- ๐Ÿ“ฃ - - -update : Msg -> Manager -update msg = - case msg of - Bypass -> - Return.singleton - - Cmd a -> - Return.communicate a - - ----------------------------------------- - -- Tracks - ----------------------------------------- - DownloadTracks a -> - Tracks.download a - - GotSearchResults a -> - Tracks.gotSearchResults a - - MakeArtworkTrackUrls a -> - Tracks.makeArtworkTrackUrls a - - RemoveTracksBySourceId a -> - Tracks.removeBySourceId a - - RemoveTracksFromCache a -> - Tracks.removeFromCache a - - ReplaceTrackTags a -> - Tracks.replaceTags a - - Search a -> - Tracks.search a - - StoreTracksInCache a -> - Tracks.storeInCache a - - SyncTrackTags a -> - Tracks.syncTrackTags a - - UpdateSearchIndex a -> - Tracks.updateSearchIndex a - - ----------------------------------------- - -- ๐Ÿฆ‰ Nested - ----------------------------------------- - ProcessingMsg a -> - Processing.update a - - UserMsg a -> - User.update a - - ----------------------------------------- - -- ๐Ÿ“ญ Other - ----------------------------------------- - RefreshedAccessToken a -> - Other.refreshedAccessToken a - - SetCurrentTime a -> - Other.setCurrentTime a - - ToCache a -> - Other.toCache a - - - --- ๐Ÿ“ฐ - - -subscriptions : Model -> Sub Msg -subscriptions _ = - Sub.batch - [ Ports.fromAlien alien - , Ports.makeArtworkTrackUrls MakeArtworkTrackUrls - , Ports.refreshedAccessToken RefreshedAccessToken - , Ports.receiveSearchResults GotSearchResults - , Ports.receiveTags (ProcessingMsg << Processing.TagsStep) - , Ports.replaceTags ReplaceTrackTags - - -- - , Time.every (60 * 1000) SetCurrentTime - ] - - - --- ๐Ÿ‘ฝ - - -alien : Alien.Event -> Msg -alien event = - case ( event.error, Alien.tagFromString event.tag ) of - ( Nothing, Just tag ) -> - translateAlienData tag event.data - - ( Just err, Just tag ) -> - translateAlienError tag event.data err - - _ -> - Bypass - - -translateAlienData : Alien.Tag -> Json.Value -> Msg -translateAlienData tag data = - case tag of - Alien.EnclosedData -> - UserMsg (User.EnclosedDataRetrieved data) - - Alien.SearchTracks -> - Search data - - ----------------------------------------- - -- From UI - ----------------------------------------- - Alien.DownloadTracks -> - DownloadTracks data - - Alien.ProcessSources -> - ProcessingMsg (Processing.Process data) - - Alien.RefreshedAccessToken -> - RefreshedAccessToken data - - Alien.RemoveEncryptionKey -> - UserMsg User.RemoveEncryptionKey - - Alien.RemoveTracksBySourceId -> - RemoveTracksBySourceId data - - Alien.RemoveTracksFromCache -> - RemoveTracksFromCache data - - Alien.SaveEnclosedUserData -> - UserMsg (User.SaveEnclosedData data) - - Alien.SaveFavourites -> - UserMsg (User.SaveFavourites data) - - Alien.SavePlaylists -> - UserMsg (User.SavePlaylists data) - - Alien.SaveProgress -> - UserMsg (User.SaveProgress data) - - Alien.SaveSettings -> - UserMsg (User.SaveSettings data) - - Alien.SaveSources -> - UserMsg (User.SaveSources data) - - Alien.SaveTracks -> - UserMsg (User.SaveTracks data) - - Alien.SetSyncMethod -> - UserMsg (User.SetSyncMethod data) - - Alien.StopProcessing -> - ProcessingMsg Processing.StopProcessing - - Alien.StoreTracksInCache -> - StoreTracksInCache data - - Alien.SyncTrackTags -> - SyncTrackTags data - - Alien.ToCache -> - ToCache data - - Alien.UnsetSyncMethod -> - UserMsg User.UnsetSyncMethod - - Alien.UpdateEncryptionKey -> - UserMsg (User.UpdateEncryptionKey data) - - _ -> - Bypass - - -translateAlienError : Alien.Tag -> Json.Value -> String -> Msg -translateAlienError tag _ err = - case err of - "db is undefined" -> - Common.reportUICmdMsg tag "Can't connect to the browser's IndexedDB. FYI, this is __not supported in Firefox's private mode__." - - _ -> - Common.reportUICmdMsg tag err diff --git a/src/Core/Brain/Common/State.elm b/src/Core/Brain/Common/State.elm deleted file mode 100644 index ea7ffe8aa..000000000 --- a/src/Core/Brain/Common/State.elm +++ /dev/null @@ -1,110 +0,0 @@ -module Brain.Common.State exposing (..) - -import Alien -import Brain.Ports as Ports -import Brain.Types exposing (..) -import Json.Decode as Json -import Return.Ext as Return -import Task -import TaskPort -import TaskPort.Extra as TaskPort - - - --- ๐Ÿ›  - - -attemptPortTask : (a -> Msg) -> Task.Task TaskPort.Error a -> Cmd Msg -attemptPortTask mapFn = - Task.attempt (reportPortErrorToUI mapFn) - - -attemptTask : (a -> Msg) -> Task.Task String a -> Cmd Msg -attemptTask mapFn = - Task.attempt (reportErrorToUI mapFn) - - - --- GIVE - - -giveUI : Alien.Tag -> Json.Value -> Manager -giveUI tag data = - data - |> giveUICmd tag - |> Return.communicate - - -giveUICmd : Alien.Tag -> Json.Value -> Cmd Msg -giveUICmd tag data = - data - |> Alien.broadcast tag - |> Ports.toUI - - -giveUICmdMsg : Alien.Tag -> Json.Value -> Msg -giveUICmdMsg tag data = - data - |> giveUICmd tag - |> Cmd - - - --- NUDGE - - -nudgeUI : Alien.Tag -> Manager -nudgeUI = - nudgeUICmd >> Return.communicate - - -nudgeUICmd : Alien.Tag -> Cmd Msg -nudgeUICmd tag = - tag - |> Alien.trigger - |> Ports.toUI - - -nudgeUICmdMsg : Alien.Tag -> Msg -nudgeUICmdMsg = - nudgeUICmd >> Cmd - - - --- REPORT - - -reportErrorToUI : (a -> Msg) -> Result String a -> Msg -reportErrorToUI mapFn result = - case result of - Ok value -> - mapFn value - - Err error -> - reportUICmdMsg Alien.ReportError error - - -reportPortErrorToUI : (a -> Msg) -> Result TaskPort.Error a -> Msg -reportPortErrorToUI mapFn = - Result.mapError TaskPort.errorToStringCustom >> reportErrorToUI mapFn - - -reportUI : Alien.Tag -> String -> Manager -reportUI tag error = - error - |> reportUICmd tag - |> Return.communicate - - -reportUICmd : Alien.Tag -> String -> Cmd Msg -reportUICmd tag error = - error - |> Alien.report tag - |> Ports.toUI - - -reportUICmdMsg : Alien.Tag -> String -> Msg -reportUICmdMsg tag error = - error - |> reportUICmd tag - |> Cmd diff --git a/src/Core/Brain/Other/State.elm b/src/Core/Brain/Other/State.elm deleted file mode 100644 index 16a3ffabf..000000000 --- a/src/Core/Brain/Other/State.elm +++ /dev/null @@ -1,79 +0,0 @@ -module Brain.Other.State exposing (..) - -import Alien -import Brain.Common.State as Common -import Brain.Ports as Ports -import Brain.Task.Ports -import Brain.Types exposing (..) -import Dict -import Json.Decode as Json -import List.Extra as List -import Return exposing (return) -import Return.Ext as Return -import Sources exposing (Service(..)) -import Sources.Encoding -import Sources.Refresh.AccessToken -import Time - - - --- ๐Ÿ”ฑ - - -refreshedAccessToken : Json.Value -> Manager -refreshedAccessToken value model = - case Json.decodeValue Sources.Refresh.AccessToken.portArgumentsDecoder value of - Ok portArguments -> - case portArguments.service of - Google -> - model.hypaethralUserData.sources - |> List.find (.id >> (==) portArguments.sourceId) - |> Maybe.map - (\source -> - source.data - |> Dict.insert "accessToken" portArguments.accessToken - |> Dict.insert "expiresAt" (String.fromInt portArguments.expiresAt) - |> (\newData -> { source | data = newData }) - ) - |> Maybe.map - (\source -> - source - |> Sources.Encoding.encode - |> Alien.broadcast Alien.UpdateSourceData - |> Ports.toUI - ) - |> Maybe.withDefault Cmd.none - |> return model - - _ -> - Return.singleton model - - Err err -> - Common.reportUI Alien.ToCache (Json.errorToString err) model - - -setCurrentTime : Time.Posix -> Manager -setCurrentTime time model = - Return.singleton { model | currentTime = time } - - -{-| Save alien data to cache. --} -toCache : Json.Value -> Manager -toCache data = - case Json.decodeValue Alien.hostDecoder data of - Ok alienEvent -> - case Alien.tagFromString alienEvent.tag of - Just tag -> - alienEvent.data - |> Brain.Task.Ports.toCache tag - |> Common.attemptPortTask (always Bypass) - |> Return.communicate - - Nothing -> - Common.reportUI Alien.ToCache "Failed to decode alien tag" - - Err err -> - err - |> Json.errorToString - |> Common.reportUI Alien.ToCache diff --git a/src/Core/Brain/Ports.elm b/src/Core/Brain/Ports.elm deleted file mode 100644 index 14526a926..000000000 --- a/src/Core/Brain/Ports.elm +++ /dev/null @@ -1,83 +0,0 @@ -port module Brain.Ports exposing (..) - -import Alien -import Json.Encode as Json -import Sources.Processing exposing (ContextForTags, ContextForTagsSync) - - - --- ๐Ÿ“ฃ - - -port downloadTracks : Json.Value -> Cmd msg - - -port removeTracksFromCache : Json.Value -> Cmd msg - - -port requestSearch : String -> Cmd msg - - -port requestTags : ContextForTags -> Cmd msg - - -port storeTracksInCache : Json.Value -> Cmd msg - - -port syncTags : ContextForTagsSync -> Cmd msg - - -port toUI : Alien.Event -> Cmd msg - - -port updateSearchIndex : Json.Value -> Cmd msg - - - --- ๐Ÿ“ฃ โ–‘โ–‘ USER LAYER SERVICES - - -port deconstructRemoteStorage : () -> Cmd msg - - -port provideArtworkTrackUrls : Json.Value -> Cmd msg - - -port requestDropbox : Alien.Event -> Cmd msg - - -port requestIpfs : Alien.Event -> Cmd msg - - -port requestRemoteStorage : Alien.Event -> Cmd msg - - -port toDropbox : Alien.Event -> Cmd msg - - -port toIpfs : Alien.Event -> Cmd msg - - -port toRemoteStorage : Alien.Event -> Cmd msg - - - --- ๐Ÿ“ฐ - - -port fromAlien : (Alien.Event -> msg) -> Sub msg - - -port makeArtworkTrackUrls : (Json.Value -> msg) -> Sub msg - - -port receiveSearchResults : (List String -> msg) -> Sub msg - - -port refreshedAccessToken : (Json.Value -> msg) -> Sub msg - - -port receiveTags : (ContextForTags -> msg) -> Sub msg - - -port replaceTags : (ContextForTagsSync -> msg) -> Sub msg diff --git a/src/Core/Brain/Sources/Processing/Common.elm b/src/Core/Brain/Sources/Processing/Common.elm deleted file mode 100644 index 39b501ff2..000000000 --- a/src/Core/Brain/Sources/Processing/Common.elm +++ /dev/null @@ -1,86 +0,0 @@ -module Brain.Sources.Processing.Common exposing (..) - -import Alien -import Brain.Common.State as Common -import Brain.Types exposing (Manager) -import Dict.Ext as Dict -import Http exposing (Error(..)) -import Json.Encode as Encode -import List.Extra as List -import Maybe.Extra as Maybe -import Sources exposing (Service, Source) -import Sources.Processing exposing (..) -import Sources.Services as Services -import Tracks exposing (Track) - - - --- ๐Ÿ”ฑ - - -contextToTagsContext : Context -> ContextForTags -contextToTagsContext context = - { amount = List.length context.filePaths - , nextFilePaths = context.filePaths - , receivedFilePaths = [] - , receivedTags = [] - , sourceId = context.source.id - , urlsForTags = [] - } - - -isProcessing : Status -> Bool -isProcessing status = - case status of - Processing _ _ -> - True - - NotProcessing -> - False - - -reportHttpError : Source -> Http.Error -> Manager -reportHttpError source err = - reportError - source - (translateHttpError source.service err) - - -reportError : Source -> String -> Manager -reportError source error = - [ ( "sourceId", Encode.string source.id ) - , ( "sourceName", Encode.string (Dict.fetch "name" "Unnamed" source.data) ) - , ( "error", Encode.string error ) - ] - |> Encode.object - |> Common.giveUI Alien.ReportProcessingError - - -tracksFromTagsContext : ContextForTags -> List Track -tracksFromTagsContext context = - context.receivedTags - |> List.zip context.receivedFilePaths - |> List.filter (Tuple.second >> Maybe.isJust) - |> List.map (Tuple.mapSecond (Maybe.withDefault Tracks.emptyTags)) - |> List.map (Tracks.makeTrack context.sourceId) - - -translateHttpError : Service -> Http.Error -> String -translateHttpError service err = - case err of - NetworkError -> - "Cannot connect to this source" - - Timeout -> - "Source did not respond (timeout)" - - BadUrl _ -> - "Diffuse error, invalid url was used" - - BadStatus _ -> - "Got a faulty response from this source. Use the developer console to get more info." - - BadBody response -> - response - |> Services.parseErrorResponse service - |> Maybe.withDefault (translateHttpError service <| BadStatus 0) diff --git a/src/Core/Brain/Sources/Processing/State.elm b/src/Core/Brain/Sources/Processing/State.elm deleted file mode 100644 index 53982918b..000000000 --- a/src/Core/Brain/Sources/Processing/State.elm +++ /dev/null @@ -1,236 +0,0 @@ -module Brain.Sources.Processing.State exposing (..) - -import Alien -import Brain.Common.State as Common -import Brain.Sources.Processing.Common exposing (..) -import Brain.Sources.Processing.Steps as Steps -import Brain.Sources.Processing.Types as Processing exposing (..) -import Brain.Tracks.State as Tracks -import Brain.Types exposing (..) -import Dict.Ext as Dict -import Http -import Json.Decode as Json -import Json.Encode as Encode -import List.Extra as List -import Return exposing (..) -import Sources exposing (Source) -import Sources.Processing exposing (..) -import Sources.Processing.Encoding as Processing - - - --- ๐Ÿ“ฃ - - -update : Processing.Msg -> Manager -update msg = - case msg of - Process a -> - process a - - NextInLine -> - nextInLine - - StopProcessing -> - stopProcessing - - ----------------------------------------- - -- Steps - ----------------------------------------- - PrepareStep a b -> - prepareStep a b - - TreeStep a b -> - treeStep a b - - TreeStepRemoveTracks a b -> - treeStepRemoveTracks a b - - TagsStep a -> - tagsStep a - - - --- ๐Ÿ”ฑ - - -process : Json.Value -> Manager -process json = - -- Only proceed to the processing if we got all the necessary data, - -- otherwise report an error in the UI. - case Json.decodeValue Processing.argumentsDecoder json of - Ok arguments -> - process_ arguments - - Err err -> - Common.reportUI Alien.ProcessSources (Json.errorToString err) - - - -{- If already processing, do nothing. - If there are no sources, do nothing. - If there are sources, start processing the first source. --} - - -process_ : { origin : String, sources : List Source } -> Manager -process_ { origin, sources } model = - let - tracks = - model.hypaethralUserData.tracks - - filter s = - List.filter (.sourceId >> (==) s.id) tracks - - all = - sources - |> List.sortBy (.data >> Dict.fetch "name" "") - |> List.map (\s -> ( s, filter s )) - in - case - ( isProcessing model.processingStatus || List.isEmpty sources - , List.uncons all - ) - of - ( False, Just ( ( s, t ), future ) ) -> - return - { model | origin = origin, processingStatus = Processing ( s, t ) future } - (Steps.takeFirstStep origin model.currentTime s) - - _ -> - Return.singleton model - - - -{- If not processing, do nothing. - If there are no sources left, do nothing. - If there are sources left, start processing the next source in line. --} - - -nextInLine : Manager -nextInLine model = - case model.processingStatus of - Processing ( processedSource, _ ) (( source, tracks ) :: rest) -> - source - |> Steps.takeFirstStep model.origin model.currentTime - |> return - { model | processingStatus = Processing ( source, tracks ) rest } - |> andThen - (processedSource.id - |> Encode.string - |> Common.giveUI Alien.FinishedProcessingSource - ) - - _ -> - Common.nudgeUI - Alien.FinishedProcessingSources - { model | processingStatus = NotProcessing } - - - -{- STOP! -} - - -stopProcessing : Manager -stopProcessing model = - Return.singleton { model | processingStatus = NotProcessing } - - - --- PHASE 1 ----------- --- Prepare for processing. - - -prepareStep : Context -> Result Http.Error String -> Manager -prepareStep context result model = - case result of - Ok response -> - model.currentTime - |> Steps.takePrepareStep context response - |> return model - - Err err -> - model - |> nextInLine - |> andThen (reportHttpError context.source err) - - - --- PHASE 2 ----------- --- Make a file list/tree. - - -treeStep : Context -> Result Http.Error String -> Manager -treeStep context result model = - case result of - Ok response -> - case model.processingStatus of - Processing ( _, tracks ) rest -> - return - { model | processingStatus = Processing ( context.source, tracks ) rest } - (Steps.takeTreeStep context response tracks model.currentTime) - - NotProcessing -> - Return.singleton model - - Err err -> - model - |> nextInLine - |> andThen (reportHttpError context.source err) - - -treeStepRemoveTracks : String -> List String -> Manager -treeStepRemoveTracks sourceId filePaths model = - let - encodedData = - Encode.object - [ ( "filePaths", Encode.list Encode.string filePaths ) - , ( "sourceId", Encode.string sourceId ) - ] - in - model - |> Common.giveUI Alien.RemoveTracksByPath encodedData - |> andThen (Tracks.removeByPaths { sourceId = sourceId, paths = filePaths }) - - - --- PHASE 3 ----------- --- Get the tags for each file in the file list. - - -tagsStep : ContextForTags -> Manager -tagsStep tagsContext model = - let - maybeCmd = - case model.processingStatus of - Processing ( source, _ ) _ -> - Steps.takeTagsStep model.currentTime tagsContext source - - NotProcessing -> - Just Cmd.none - - tracksToAdd = - tagsContext - |> tracksFromTagsContext - |> List.map (\track -> { track | insertedAt = model.currentTime }) - - amountLeft = - List.length tagsContext.nextFilePaths - - progressPercentage = - 0.05 + 0.95 * (1 - toFloat amountLeft / toFloat tagsContext.amount) - - progress = - [ ( "progress", Encode.float progressPercentage ) - , ( "sourceId", Encode.string tagsContext.sourceId ) - ] - in - maybeCmd - |> Maybe.map (return model) - |> Maybe.withDefault (nextInLine model) - |> andThen (Tracks.add tracksToAdd) - |> andThen (Common.giveUI Alien.ReportProcessingProgress <| Encode.object progress) diff --git a/src/Core/Brain/Sources/Processing/Steps.elm b/src/Core/Brain/Sources/Processing/Steps.elm deleted file mode 100644 index 61f1d55ab..000000000 --- a/src/Core/Brain/Sources/Processing/Steps.elm +++ /dev/null @@ -1,327 +0,0 @@ -module Brain.Sources.Processing.Steps exposing - ( takeFirstStep - , takePrepareStep - , takeTagsStep - , takeTreeStep - ) - -{-| Processing. - - ## How it works - - This describes the process for a single source. - - 1. Prepare the source for processing. - -> For example, a source could get an access token first. - 2. Get a file tree/list from the source - -> This can happen in multiple steps as with Amazon S3. - A command is issued for each step of this process. - 3. Get the tags (ie. metadata) for each file that we found. - -> This also happens in multiple steps, so that we can flush - every x tracks while processing. - A command is issued for each step of this process. - --} - -import Alien -import Brain.Ports as Ports -import Brain.Sources.Processing.Common exposing (..) -import Brain.Sources.Processing.Types exposing (..) -import Brain.Types as Brain exposing (..) -import List.Extra as List -import Set -import Sources exposing (Source) -import Sources.Encoding -import Sources.Processing exposing (..) -import Sources.Services as Services -import Task.Extra exposing (do) -import Time -import Tracks exposing (Track) - - - --- SETTINGS - - -{-| How much tags do we want to process -before we send them back to Elm. - - eg. After we got the tags for 20 tracks, - we store these and continue with the rest. - --} -tagsBatchSize : Int -tagsBatchSize = - 20 - - - --- 1st STEP - - -takeFirstStep : String -> Time.Posix -> Source -> Cmd Brain.Msg -takeFirstStep origin currentTime source = - let - initialContext = - { filePaths = [] - , origin = origin - , preparationMarker = TheBeginning - , source = source - , treeMarker = TheBeginning - } - in - prepare initialContext currentTime - - - --- 2nd STEP - - -takePrepareStep : Context -> String -> Time.Posix -> Cmd Brain.Msg -takePrepareStep context response currentTime = - context - |> handlePreparationResponse response currentTime - |> intoPreparationCommands currentTime - - - --- 3rd STEP - - -takeTreeStep : Context -> String -> List Track -> Time.Posix -> Cmd Brain.Msg -takeTreeStep context response associatedTracks currentTime = - context - |> handleTreeResponse response - |> intoTreeCommand associatedTracks currentTime - - - --- 4th STEP - - -takeTagsStep : Time.Posix -> ContextForTags -> Source -> Maybe (Cmd Brain.Msg) -takeTagsStep currentTime tagsCtx source = - let - ( filesToProcess, nextFiles ) = - List.splitAt tagsBatchSize tagsCtx.nextFilePaths - - newTagsCtx = - { amount = tagsCtx.amount - , nextFilePaths = nextFiles - , receivedFilePaths = filesToProcess - , receivedTags = [] - , sourceId = source.id - , urlsForTags = makeTrackUrls currentTime source filesToProcess - } - in - if List.isEmpty filesToProcess then - Nothing - - else - Just (getTags newTagsCtx) - - - ------------------------------------------ --- ใŠ™๏ธ ------------------------------------------ --- PREPARE - - -prepare : Context -> Time.Posix -> Cmd Brain.Msg -prepare context currentTime = - let - maybePreparationCommand = - Services.prepare - context.source.service - context.origin - context.source.data - context.preparationMarker - (ProcessingMsg << PrepareStep context) - in - case maybePreparationCommand of - Just cmd -> - cmd - - Nothing -> - -- Some services don't need to prepare for processing. - -- ๐Ÿš€ - makeTree context currentTime - - -handlePreparationResponse : String -> Time.Posix -> Context -> Context -handlePreparationResponse response currentTime context = - let - answer = - Services.parsePreparationResponse - context.source.service - response - currentTime - context.source.data - context.preparationMarker - - source = - context.source - in - { context - | preparationMarker = answer.marker - , source = { source | data = answer.sourceData } - } - - -intoPreparationCommands : Time.Posix -> Context -> Cmd Brain.Msg -intoPreparationCommands currentTime context = - case context.preparationMarker of - TheBeginning -> - Cmd.none - - -- Still preparing, - -- carry on. - -- - InProgress _ -> - prepare context currentTime - - -- The preparation is completed, - -- continue to the next step. - -- - TheEnd -> - let - updatedSource = - context.source - in - Cmd.batch - [ -- Make a file tree, the next step. - -- ๐Ÿš€ - makeTree context currentTime - - -- Update source data. - , updatedSource - |> Sources.Encoding.encode - |> Alien.broadcast Alien.UpdateSourceData - |> Ports.toUI - ] - - - --- TREE - - -makeTree : Context -> Time.Posix -> Cmd Brain.Msg -makeTree context currentTime = - Services.makeTree - context.source.service - context.source.data - context.treeMarker - currentTime - (ProcessingMsg << TreeStep context) - - -handleTreeResponse : String -> Context -> Context -handleTreeResponse response context = - let - parsingFunc = - Services.parseTreeResponse context.source.service - - parsedResponse = - parsingFunc response context.treeMarker - in - { context - | filePaths = context.filePaths ++ parsedResponse.filePaths - , treeMarker = parsedResponse.marker - } - - -intoTreeCommand : List Track -> Time.Posix -> Context -> Cmd Brain.Msg -intoTreeCommand associatedTracks currentTime context = - case context.treeMarker of - TheBeginning -> - Cmd.none - - -- Still building the tree, - -- carry on. - -- - InProgress _ -> - makeTree context currentTime - - -- The tree's been build, - -- continue to the next step. - -- - TheEnd -> - let - filteredFiles = - Services.postProcessTree context.source.service context.filePaths - - postContext = - { context | filePaths = filteredFiles } - - pathsSourceOfTruth = - postContext.filePaths - - pathsCurrent = - List.map .path associatedTracks - - ( pathsAdded, pathsRemoved ) = - separate pathsCurrent pathsSourceOfTruth - in - Cmd.batch - [ -- Get tags from tracks, the next step. - -- ๐Ÿš€ - postContext - |> (\ctx -> { ctx | filePaths = pathsAdded }) - |> contextToTagsContext - |> TagsStep - |> ProcessingMsg - |> do - - -- Remove tracks - , if not (List.isEmpty pathsRemoved) then - pathsRemoved - |> TreeStepRemoveTracks context.source.id - |> ProcessingMsg - |> do - - else - Cmd.none - ] - - -separate : List String -> List String -> ( List String, List String ) -separate current srcOfTruth = - let - setCurrent = - Set.fromList current - - setSrcOfTruth = - Set.fromList srcOfTruth - in - ( -- Added - -------- - Set.diff setSrcOfTruth setCurrent |> Set.toList - , -- Removed - ---------- - Set.diff setCurrent setSrcOfTruth |> Set.toList - ) - - - --- TAGS - - -getTags : ContextForTags -> Cmd Brain.Msg -getTags = - Ports.requestTags - - -makeTrackUrls : Time.Posix -> Source -> List String -> List TagUrls -makeTrackUrls currentTime source filePaths = - let - maker = - Services.makeTrackUrl source.service - - mapFn = - \path -> - { getUrl = maker currentTime source.id source.data Get path - , headUrl = maker currentTime source.id source.data Head path - } - in - List.map mapFn filePaths diff --git a/src/Core/Brain/Sources/Processing/Types.elm b/src/Core/Brain/Sources/Processing/Types.elm deleted file mode 100644 index 843003bd7..000000000 --- a/src/Core/Brain/Sources/Processing/Types.elm +++ /dev/null @@ -1,22 +0,0 @@ -module Brain.Sources.Processing.Types exposing (..) - -import Http -import Json.Decode as Json -import Sources.Processing exposing (..) - - - --- ๐Ÿ“ฃ - - -type Msg - = Process Json.Value - | NextInLine - | StopProcessing - ----------------------------------------- - -- Steps - ----------------------------------------- - | PrepareStep Context (Result Http.Error String) - | TreeStep Context (Result Http.Error String) - | TreeStepRemoveTracks String (List String) - | TagsStep ContextForTags diff --git a/src/Core/Brain/Task/Ports.elm b/src/Core/Brain/Task/Ports.elm deleted file mode 100644 index 452d5891a..000000000 --- a/src/Core/Brain/Task/Ports.elm +++ /dev/null @@ -1,79 +0,0 @@ -module Brain.Task.Ports exposing (..) - -import Alien -import Json.Decode -import Json.Encode -import TaskPort - - - --- CACHE - - -fromCache : Alien.Tag -> Json.Decode.Decoder value -> TaskPort.Task (Maybe value) -fromCache tag decoder = - TaskPort.call - { function = "fromCache" - , valueDecoder = Json.Decode.maybe decoder - , argsEncoder = Json.Encode.string - } - (Alien.tagToString tag) - - -fromCacheWithSuffix : Alien.Tag -> String -> Json.Decode.Decoder value -> TaskPort.Task (Maybe value) -fromCacheWithSuffix tag suffix decoder = - TaskPort.call - { function = "fromCache" - , valueDecoder = Json.Decode.maybe decoder - , argsEncoder = Json.Encode.string - } - (Alien.tagToString tag ++ "_" ++ suffix) - - -removeCache : Alien.Tag -> TaskPort.Task () -removeCache tag = - TaskPort.call - { function = "removeCache" - , valueDecoder = TaskPort.ignoreValue - , argsEncoder = Json.Encode.string - } - (Alien.tagToString tag) - - -toCache : Alien.Tag -> Json.Encode.Value -> TaskPort.Task () -toCache tag = - let - key = - Alien.tagToString tag - in - TaskPort.call - { function = "toCache" - , valueDecoder = TaskPort.ignoreValue - , argsEncoder = \v -> Json.Encode.object [ ( "key", Json.Encode.string key ), ( "value", v ) ] - } - - -toCacheWithSuffix : Alien.Tag -> String -> Json.Encode.Value -> TaskPort.Task () -toCacheWithSuffix tag suffix = - let - key = - Alien.tagToString tag ++ "_" ++ suffix - in - TaskPort.call - { function = "toCache" - , valueDecoder = TaskPort.ignoreValue - , argsEncoder = \v -> Json.Encode.object [ ( "key", Json.Encode.string key ), ( "value", v ) ] - } - - - --- CRYPTO - - -fabricateSecretKey : String -> TaskPort.Task () -fabricateSecretKey = - TaskPort.call - { function = "fabricateSecretKey" - , valueDecoder = Json.Decode.succeed () - , argsEncoder = Json.Encode.string - } diff --git a/src/Core/Brain/Tracks/State.elm b/src/Core/Brain/Tracks/State.elm deleted file mode 100644 index 966065a54..000000000 --- a/src/Core/Brain/Tracks/State.elm +++ /dev/null @@ -1,347 +0,0 @@ -module Brain.Tracks.State exposing (..) - -import Alien -import Brain.Common.State as Common -import Brain.Ports as Ports -import Brain.Types exposing (..) -import Brain.User.State as User -import Dict -import Dict.Ext as Dict -import Json.Decode as Json exposing (Decoder) -import Json.Encode -import List.Extra as List -import Queue -import Return exposing (andThen, return) -import Return.Ext as Return -import Sources exposing (Source) -import Sources.Processing exposing (ContextForTagsSync, HttpMethod(..), TagUrls) -import Sources.Services -import Time -import Tracks exposing (Track) -import Tracks.Encoding - - - --- ๐Ÿ”ฑ - - -add : List Track -> Manager -add list model = - case list of - [] -> - Return.singleton model - - tracks -> - model - |> User.saveTracksAndUpdateSearchIndex - (List.append model.hypaethralUserData.tracks tracks) - |> andThen - (tracks - |> Json.Encode.list Tracks.Encoding.encodeTrack - |> Common.giveUI Alien.AddTracks - ) - - -download : Json.Value -> Manager -download json model = - let - { prefixTrackNumber, trackIds, zipName } = - json - |> Json.decodeValue downloadParamsDecoder - |> Result.withDefault - { prefixTrackNumber = False - , trackIds = [] - , zipName = "failed-to-decode-json" - } - in - model.hypaethralUserData.tracks - |> Tracks.pick trackIds - |> List.indexedMap Tuple.pair - |> Json.Encode.list - (\( idx, track ) -> - Json.Encode.object - [ ( "filename" - , [ if prefixTrackNumber then - (idx + 1) - |> String.fromInt - |> String.padLeft 2 '0' - |> (\s -> s ++ " - ") - - else - "" - , track.tags.artist - |> Maybe.map (\a -> a ++ " - ") - |> Maybe.withDefault "" - , track.tags.title - ] - |> String.concat - |> Json.Encode.string - ) - - -- - , ( "path" - , Json.Encode.string track.path - ) - , ( "url" - , track - |> Queue.makeTrackUrl - model.currentTime - model.hypaethralUserData.sources - |> Json.Encode.string - ) - ] - ) - |> (\encodedTracks -> - Json.Encode.object - [ ( "name", Json.Encode.string zipName ) - , ( "tracks", encodedTracks ) - ] - ) - |> Ports.downloadTracks - |> return model - - -gotSearchResults : List String -> Manager -gotSearchResults results = - Common.giveUI Alien.SearchTracks (Json.Encode.list Json.Encode.string results) - - -makeArtworkTrackUrls : Json.Value -> Manager -makeArtworkTrackUrls json model = - json - |> Json.decodeValue - (Json.dict Json.string) - |> Result.map - (\dict -> - let - maybeSource = - Maybe.andThen - (\trackSourceId -> - List.find - (.id >> (==) trackSourceId) - model.hypaethralUserData.sources - ) - (Dict.get "trackSourceId" dict) - - trackPath = - Dict.fetch "trackPath" "" dict - - mkTrackUrl = - makeTrackUrl model.currentTime trackPath maybeSource - in - dict - |> Dict.insert "trackGetUrl" (mkTrackUrl Get) - |> Dict.insert "trackHeadUrl" (mkTrackUrl Head) - |> Json.Encode.dict identity Json.Encode.string - |> Ports.provideArtworkTrackUrls - |> return model - ) - |> Result.withDefault - (Return.singleton model) - - -removeByPaths : { sourceId : String, paths : List String } -> Manager -removeByPaths args model = - User.saveTracksAndUpdateSearchIndex - (model.hypaethralUserData.tracks - |> Tracks.removeByPaths args - |> .kept - ) - model - - -removeBySourceId : Json.Value -> Manager -removeBySourceId data model = - case Json.decodeValue Json.string data of - Ok sourceId -> - User.saveTracksAndUpdateSearchIndex - (model.hypaethralUserData.tracks - |> Tracks.removeBySourceId sourceId - |> .kept - ) - model - - Err _ -> - Return.singleton model - - -removeFromCache : Json.Value -> Manager -removeFromCache data = - Return.communicate (Ports.removeTracksFromCache data) - - -replaceTags : ContextForTagsSync -> Manager -replaceTags context model = - model.hypaethralUserData.tracks - |> List.foldr - (\track ( acc, trackIds, tags ) -> - case List.elemIndex track.id trackIds of - Just idx -> - let - newTags = - tags - |> List.getAt idx - |> Maybe.andThen identity - |> Maybe.withDefault track.tags - in - ( { track | tags = newTags } :: acc - , List.removeAt idx trackIds - , List.removeAt idx tags - ) - - Nothing -> - ( track :: acc - , trackIds - , tags - ) - ) - ( [] - , context.trackIds - , context.receivedTags - ) - |> (\( a, _, _ ) -> - User.saveTracksAndUpdateSearchIndex a model - ) - |> andThen - (\m -> - m.hypaethralUserData.tracks - |> Json.Encode.list Tracks.Encoding.encodeTrack - |> (\data -> Common.giveUI Alien.ReloadTracks data m) - ) - - -search : Json.Value -> Manager -search encodedSearchTerm = - encodedSearchTerm - |> Json.decodeValue Json.string - |> Result.map Ports.requestSearch - |> Result.withDefault Cmd.none - |> Return.communicate - - -storeInCache : Json.Value -> Manager -storeInCache data = - Return.communicate (Ports.storeTracksInCache data) - - -syncTrackTags : Json.Value -> Manager -syncTrackTags data model = - let - result = - Json.decodeValue - (Json.list <| - Json.map3 - (\path sourceId trackId -> - { path = path - , sourceId = sourceId - , trackId = trackId - } - ) - (Json.field "path" Json.string) - (Json.field "sourceId" Json.string) - (Json.field "trackId" Json.string) - ) - data - - ( sources, _ ) = - result - |> Result.withDefault [] - |> List.foldl - (\{ sourceId } ( dict, acc ) -> - if List.member sourceId acc then - ( dict, acc ) - - else - case List.find (.id >> (==) sourceId) model.hypaethralUserData.sources of - Just source -> - ( Dict.insert sourceId source dict, sourceId :: acc ) - - Nothing -> - ( dict, sourceId :: acc ) - ) - ( Dict.empty, [] ) - in - case result of - Ok list -> - list - |> List.foldr - (\{ path, sourceId, trackId } ( accPaths, accUrls, accIds ) -> - sources - |> Dict.get sourceId - |> Maybe.map - (tagUrls model.currentTime path) - |> Maybe.map - (\urls -> - ( path :: accPaths, urls :: accUrls, trackId :: accIds ) - ) - |> Maybe.withDefault - ( accPaths, accUrls, accIds ) - ) - ( [], [], [] ) - |> (\( accPaths, accUrls, accIds ) -> - Ports.syncTags - { receivedFilePaths = accPaths - , receivedTags = [] - , trackIds = accIds - , urlsForTags = accUrls - } - ) - |> return model - - Err _ -> - Return.singleton model - - -updateSearchIndex : Json.Value -> Manager -updateSearchIndex data = - Return.communicate (Ports.updateSearchIndex data) - - - --- โš—๏ธ - - -downloadParamsDecoder : - Decoder - { prefixTrackNumber : Bool - , trackIds : List String - , zipName : String - } -downloadParamsDecoder = - Json.map3 - (\a b c -> - { prefixTrackNumber = a - , trackIds = b - , zipName = c - } - ) - (Json.field "prefixTrackNumber" <| Json.bool) - (Json.field "trackIds" <| Json.list Json.string) - (Json.field "zipName" <| Json.string) - - -makeTrackUrl : Time.Posix -> String -> Maybe Source -> HttpMethod -> String -makeTrackUrl timestamp trackPath maybeSource httpMethod = - case maybeSource of - Just source -> - Sources.Services.makeTrackUrl - source.service - timestamp - source.id - source.data - httpMethod - trackPath - - Nothing -> - "" - - -tagUrls : Time.Posix -> String -> Source -> TagUrls -tagUrls currentTime path source = - let - maker = - Sources.Services.makeTrackUrl source.service currentTime source.id source.data - in - { getUrl = maker Get path - , headUrl = maker Head path - } diff --git a/src/Core/Brain/Types.elm b/src/Core/Brain/Types.elm deleted file mode 100644 index 0fcde6b0d..000000000 --- a/src/Core/Brain/Types.elm +++ /dev/null @@ -1,76 +0,0 @@ -module Brain.Types exposing (..) - -import Brain.Sources.Processing.Types as Processing -import Brain.User.Types as User -import Debouncer.Basic exposing (Debouncer) -import Json.Decode as Json -import List.Zipper exposing (Zipper) -import Management -import Sources.Processing as Processing -import Time -import User.Layer as User exposing (HypaethralBaggage, HypaethralBit) - - - --- ๐Ÿง  - - -type alias Flags = - { initialUrl : String } - - - --- ๐ŸŒณ - - -type alias Model = - { currentTime : Time.Posix - , hypaethralDebouncer : Debouncer HypaethralBit (List HypaethralBit) - , hypaethralRetrieval : Maybe (Zipper ( HypaethralBit, Json.Value, HypaethralBaggage )) - , hypaethralStorage : List { bit : HypaethralBit, saving : Bool } - , hypaethralUserData : User.HypaethralData - , origin : String - , processingStatus : Processing.Status - , userSyncMethod : Maybe User.Method - } - - - --- ๐Ÿ“ฃ - - -type Msg - = Bypass - | Cmd (Cmd Msg) - ----------------------------------------- - -- Tracks - ----------------------------------------- - | DownloadTracks Json.Value - | GotSearchResults (List String) - | MakeArtworkTrackUrls Json.Value - | RemoveTracksBySourceId Json.Value - | RemoveTracksFromCache Json.Value - | ReplaceTrackTags Processing.ContextForTagsSync - | Search Json.Value - | StoreTracksInCache Json.Value - | SyncTrackTags Json.Value - | UpdateSearchIndex Json.Value - ----------------------------------------- - -- ๐Ÿฆ‰ Nested - ----------------------------------------- - | ProcessingMsg Processing.Msg - | UserMsg User.Msg - ----------------------------------------- - -- ๐Ÿ“ญ Other - ----------------------------------------- - | RefreshedAccessToken Json.Value - | SetCurrentTime Time.Posix - | ToCache Json.Value - - -type alias Organizer model = - Management.Manager Msg model - - -type alias Manager = - Organizer Model diff --git a/src/Core/Brain/User/Hypaethral.elm b/src/Core/Brain/User/Hypaethral.elm deleted file mode 100644 index af1505b0d..000000000 --- a/src/Core/Brain/User/Hypaethral.elm +++ /dev/null @@ -1,128 +0,0 @@ -module Brain.User.Hypaethral exposing (..) - -import Alien -import Brain.Task.Ports -import Json.Decode -import Json.Encode -import Task exposing (Task) -import TaskPort -import TaskPort.Extra as TaskPort -import User.Layer exposing (..) - - - --- RETRIEVAL - - -retrieveDropbox : String -> HypaethralBit -> Task String (Maybe Json.Decode.Value) -retrieveDropbox accessToken bit = - [ ( "fileName", fileName bit ) - , ( "token", Json.Encode.string accessToken ) - ] - |> TaskPort.call - { function = "fromDropbox" - , valueDecoder = Json.Decode.maybe Json.Decode.value - , argsEncoder = Json.Encode.object - } - |> Task.mapError TaskPort.errorToStringCustom - - -retrieveIpfs : String -> HypaethralBit -> Task String (Maybe Json.Decode.Value) -retrieveIpfs apiOrigin bit = - [ ( "fileName", fileName bit ) - , ( "apiOrigin", Json.Encode.string apiOrigin ) - ] - |> TaskPort.call - { function = "fromIpfs" - , valueDecoder = Json.Decode.maybe Json.Decode.value - , argsEncoder = Json.Encode.object - } - |> Task.mapError TaskPort.errorToStringCustom - - -retrieveRemoteStorage : { token : String, userAddress : String } -> HypaethralBit -> Task String (Maybe Json.Decode.Value) -retrieveRemoteStorage { token, userAddress } bit = - [ ( "fileName", fileName bit ) - , ( "token", Json.Encode.string token ) - , ( "userAddress", Json.Encode.string userAddress ) - ] - |> TaskPort.call - { function = "fromRemoteStorage" - , valueDecoder = Json.Decode.maybe Json.Decode.value - , argsEncoder = Json.Encode.object - } - |> Task.mapError TaskPort.errorToStringCustom - - -retrieveLocal : HypaethralBit -> Task String (Maybe Json.Decode.Value) -retrieveLocal bit = - Json.Decode.value - |> Brain.Task.Ports.fromCacheWithSuffix - Alien.SyncLocal - (hypaethralBitFileName bit) - |> Task.mapError TaskPort.errorToStringCustom - - - --- STORAGE - - -saveDropbox : String -> HypaethralBit -> Json.Decode.Value -> Task String () -saveDropbox accessToken bit data = - [ ( "fileName", fileName bit ) - , ( "data", data ) - , ( "token", Json.Encode.string accessToken ) - ] - |> TaskPort.call - { function = "toDropbox" - , valueDecoder = TaskPort.ignoreValue - , argsEncoder = Json.Encode.object - } - |> Task.mapError TaskPort.errorToStringCustom - - -saveIpfs : String -> HypaethralBit -> Json.Decode.Value -> Task String () -saveIpfs apiOrigin bit data = - [ ( "apiOrigin", Json.Encode.string apiOrigin ) - , ( "fileName", fileName bit ) - , ( "data", data ) - ] - |> TaskPort.call - { function = "toIpfs" - , valueDecoder = TaskPort.ignoreValue - , argsEncoder = Json.Encode.object - } - |> Task.mapError TaskPort.errorToStringCustom - - -saveRemoteStorage : { token : String, userAddress : String } -> HypaethralBit -> Json.Decode.Value -> Task String () -saveRemoteStorage { token, userAddress } bit data = - [ ( "fileName", fileName bit ) - , ( "data", data ) - , ( "token", Json.Encode.string token ) - , ( "userAddress", Json.Encode.string userAddress ) - ] - |> TaskPort.call - { function = "toRemoteStorage" - , valueDecoder = TaskPort.ignoreValue - , argsEncoder = Json.Encode.object - } - |> Task.mapError TaskPort.errorToStringCustom - - -saveLocal : HypaethralBit -> Json.Decode.Value -> Task String () -saveLocal bit data = - data - |> Brain.Task.Ports.toCacheWithSuffix - Alien.SyncLocal - (hypaethralBitFileName bit) - |> Task.mapError TaskPort.errorToStringCustom - - - --- ๐Ÿ›  - - -fileName : HypaethralBit -> Json.Decode.Value -fileName = - Json.Encode.string << hypaethralBitFileName diff --git a/src/Core/Brain/User/State.elm b/src/Core/Brain/User/State.elm deleted file mode 100644 index 5cc12c14c..000000000 --- a/src/Core/Brain/User/State.elm +++ /dev/null @@ -1,749 +0,0 @@ -module Brain.User.State exposing (..) - -import Alien -import Brain.Common.State as Common -import Brain.Ports as Ports -import Brain.Task.Ports -import Brain.Types as Brain exposing (..) -import Brain.User.Hypaethral as Hypaethral -import Brain.User.Types as User exposing (..) -import Debouncer.Basic as Debouncer -import EverySet -import Json.Decode as Decode -import Json.Encode as Json -import Playlists.Encoding as Playlists -import Return exposing (andThen, return) -import Return.Ext as Return -import Settings -import Sources.Encoding as Sources -import Syncing -import Syncing.Services.Dropbox.Token -import Task exposing (Task) -import TaskPort.Extra as TaskPort -import Time -import Tracks exposing (Track) -import Tracks.Encoding as Tracks -import Url exposing (Url) -import Url.Ext as Url -import User.Layer as User exposing (..) -import User.Layer.Methods.Dropbox as Dropbox - - - --- ๐ŸŒณ - - -initialCommand : Url -> Cmd Brain.Msg -initialCommand uiUrl = - case Url.action uiUrl of - _ -> - Cmd.batch - [ loadEnclosedData - , loadLocalHypaethralData - { initialUrl = uiUrl - , methodTask = loadSyncMethod - } - ] - - -{-| Loads the "enclosed" data from cache and sends it to the UI. --} -loadEnclosedData : Cmd Brain.Msg -loadEnclosedData = - Decode.value - |> Brain.Task.Ports.fromCache Alien.EnclosedData - |> Task.map (Maybe.withDefault Json.null) - |> Common.attemptPortTask (Common.giveUICmdMsg Alien.LoadEnclosedUserData) - - -{-| Loads the "sync method". --} -loadSyncMethod : Task String (Maybe Method) -loadSyncMethod = - Decode.value - |> Brain.Task.Ports.fromCache Alien.SyncMethod - |> Task.mapError TaskPort.errorToStringCustom - |> Task.map (Maybe.andThen decodeMethod) - - -{-| Loads the "sync method" and "hypaethral" data, -see `Commence` Msg what happens next. --} -loadLocalHypaethralData : { initialUrl : Url, methodTask : Task String (Maybe Method) } -> Cmd Brain.Msg -loadLocalHypaethralData { initialUrl, methodTask } = - methodTask - |> Task.andThen - (\maybeMethod -> - Hypaethral.retrieveLocal - |> User.retrieveHypaethralData - |> Task.map - (\bits -> - bits - |> List.map (\( a, b ) -> ( hypaethralBitKey a, Maybe.withDefault Json.null b )) - |> Json.object - ) - |> Task.map (Tuple.pair maybeMethod) - ) - |> Common.attemptTask - (\( maybeMethod, hypaethralJson ) -> - hypaethralJson - |> User.decodeHypaethralData - |> Result.map - (\hypaethralData -> - Commence - maybeMethod - initialUrl - ( hypaethralJson - , hypaethralData - ) - ) - |> Result.mapError Decode.errorToString - |> Common.reportErrorToUI UserMsg - ) - - - --- ๐Ÿ“ฃ - - -update : User.Msg -> Manager -update msg = - case msg of - Commence a b c -> - commence a b c - - SetSyncMethod a -> - setSyncMethod a - - Sync -> - sync { initialTask = Nothing } - - UnsetSyncMethod -> - unsetSyncMethod - - ----------------------------------------- - -- x. Data - ----------------------------------------- - RetrieveEnclosedData -> - retrieveEnclosedData - - EnclosedDataRetrieved a -> - enclosedDataRetrieved a - - SaveEnclosedData a -> - saveEnclosedData a - - ----------------------------------------- - -- y. Data - ----------------------------------------- - -- The hypaethral user data is received in pieces, - -- pieces which are "cached" here in the web worker. - -- - -- The reasons for this are: - -- 1. Lesser performance penalty on the UI when saving data - -- (ie. this avoids having to encode/decode everything each time) - -- 2. The data can be used in the web worker (brain) as well. - -- (eg. for track-search index) - -- - SaveFavourites a -> - saveFavourites a - - SavePlaylists a -> - savePlaylists a - - SaveProgress a -> - saveProgress a - - SaveSettings a -> - saveSettings a - - SaveSources a -> - saveSources a - - SaveTracks a -> - saveTracks a - - ----------------------------------------- - -- z. Data - ----------------------------------------- - FinishedSyncing -> - finishedSyncing - - GotHypaethralData a -> - gotHypaethralData a - - SaveHypaethralDataBits a -> - saveHypaethralDataBits a - - SaveHypaethralDataSlowly a -> - saveHypaethralDataSlowly a - - ----------------------------------------- - -- z. Secret Key - ----------------------------------------- - RemoveEncryptionKey -> - removeEncryptionKey - - UpdateEncryptionKey a -> - updateEncryptionKey a - - ----------------------------------------- - -- ๐Ÿ“ญ Other - ----------------------------------------- - RefreshedDropboxTokens a b c -> - refreshedDropboxTokens a b c - - - --- ๐Ÿ”ฑ - - -commence : Maybe Method -> Url -> ( Json.Value, HypaethralData ) -> Manager -commence maybeMethod initialUrl ( hypaethralJson, hypaethralData ) model = - -- ๐Ÿš€ - -- Initiated from `initialCommand`. - -- Loaded the used-sync method and the local hypaethral data. - { model | userSyncMethod = maybeMethod } - |> sendHypaethralDataToUI hypaethralJson hypaethralData - |> andThen - (case Url.action initialUrl of - _ -> - sync { initialTask = Nothing } - ) - - -setSyncMethod : Json.Value -> Manager -setSyncMethod json model = - -- ๐Ÿค - -- Set & store method, - -- and retrieve data. - let - decoder = - Decode.map2 - (\a b -> ( a, b )) - (Decode.field "method" <| Decode.map methodFromString Decode.string) - (Decode.field "passphrase" <| Decode.maybe Decode.string) - in - case Decode.decodeValue decoder json of - Ok ( Just method, Just passphrase ) -> - let - initialTask = - passphrase - |> Brain.Task.Ports.fabricateSecretKey - |> Task.mapError TaskPort.errorToStringCustom - in - { model | userSyncMethod = Just method } - |> sync { initialTask = Just initialTask } - |> andThen (saveMethod method) - - Ok ( Just method, Nothing ) -> - { model | userSyncMethod = Just method } - |> sync { initialTask = Nothing } - |> andThen (saveMethod method) - - Ok ( Nothing, _ ) -> - Return.singleton { model | userSyncMethod = Nothing } - - Err _ -> - Return.singleton model - - -sync : { initialTask : Maybe (Task.Task String ()) } -> Manager -sync { initialTask } model = - model - |> syncCommand (Maybe.withDefault (Task.succeed ()) initialTask) - |> return model - |> andThen - (case model.userSyncMethod of - Just method -> - Common.giveUI Alien.StartedSyncing (encodeMethod method) - - Nothing -> - Return.singleton - ) - - -syncCommand : Task.Task String a -> Model -> Cmd Brain.Msg -syncCommand initialTask model = - let - localData = - model.hypaethralUserData - - attemptSync args = - args - |> Syncing.task - initialTask - { localData = localData - , saveLocal = Hypaethral.saveLocal - } - |> Common.attemptTask - (\maybe -> - case maybe of - Just data -> - UserMsg (GotHypaethralData data) - - Nothing -> - UserMsg FinishedSyncing - ) - in - case model.userSyncMethod of - Just (Dropbox { accessToken, expiresAt, refreshToken }) -> - if - Syncing.Services.Dropbox.Token.isExpired - { currentTime = model.currentTime - , expiresAt = expiresAt - } - then - refreshDropboxTokens - model.currentTime - Sync - initialTask - refreshToken - - else - attemptSync - { retrieve = Hypaethral.retrieveDropbox accessToken - , save = Hypaethral.saveDropbox accessToken - } - - Just (Ipfs { apiOrigin }) -> - attemptSync - { retrieve = Hypaethral.retrieveIpfs apiOrigin - , save = Hypaethral.saveIpfs apiOrigin - } - - Just (RemoteStorage args) -> - attemptSync - { retrieve = Hypaethral.retrieveRemoteStorage args - , save = Hypaethral.saveRemoteStorage args - } - - Nothing -> - Cmd.none - - -unsetSyncMethod : Manager -unsetSyncMethod model = - -- ๐Ÿ’€ - -- Unset & remove stored method. - [ Common.attemptPortTask (always Brain.Bypass) (Brain.Task.Ports.removeCache Alien.SyncMethod) - , Common.attemptPortTask (always Brain.Bypass) (Brain.Task.Ports.removeCache Alien.SecretKey) - - -- - , case model.userSyncMethod of - Just (Dropbox _) -> - Cmd.none - - Just (Ipfs _) -> - Cmd.none - - Just (RemoteStorage _) -> - Ports.deconstructRemoteStorage () - - Nothing -> - Cmd.none - ] - |> Cmd.batch - |> return { model | userSyncMethod = Nothing } - - - --- ๐Ÿ”ฑ โ–‘โ–‘ DATA - ENCLOSED - - -enclosedDataRetrieved : Json.Value -> Manager -enclosedDataRetrieved json = - Common.giveUI Alien.LoadEnclosedUserData json - - -retrieveEnclosedData : Manager -retrieveEnclosedData = - Decode.value - |> Brain.Task.Ports.fromCache Alien.EnclosedData - |> Common.attemptPortTask - (\maybe -> - case maybe of - Just json -> - Brain.UserMsg (EnclosedDataRetrieved json) - - Nothing -> - Brain.Bypass - ) - |> Return.communicate - - -saveEnclosedData : Json.Value -> Manager -saveEnclosedData json = - json - |> Brain.Task.Ports.toCache Alien.EnclosedData - |> Common.attemptPortTask (always Brain.Bypass) - |> Return.communicate - - - --- ๐Ÿ”ฑ โ–‘โ–‘ DATA - HYPAETHRAL - - -finishedSyncing : Manager -finishedSyncing model = - case model.userSyncMethod of - Just userSyncMethod -> - Common.giveUI Alien.SyncMethod (encodeMethod userSyncMethod) model - - Nothing -> - Return.singleton model - - -gotHypaethralData : HypaethralData -> Manager -gotHypaethralData hypaethralData model = - model - |> sendHypaethralDataToUI (User.encodeHypaethralData hypaethralData) hypaethralData - |> andThen finishedSyncing - - -saveAllHypaethralDataTask : HypaethralData -> Method -> Task String () -saveAllHypaethralDataTask userData method = - let - save = - saveHypaethralDataBitsTask (ModifiedAt :: User.allHypaethralBits) userData - in - case method of - Dropbox { accessToken } -> - save (Hypaethral.saveDropbox accessToken) - - Ipfs { apiOrigin } -> - save (Hypaethral.saveIpfs apiOrigin) - - RemoteStorage a -> - save (Hypaethral.saveRemoteStorage a) - - -saveHypaethralDataBitsTask : List HypaethralBit -> HypaethralData -> (HypaethralBit -> Json.Value -> Task String ()) -> Task String () -saveHypaethralDataBitsTask bits userData saveFn = - [ -------- - -- LOCAL - -------- - List.map - (\bit -> - Hypaethral.saveLocal bit (encodeHypaethralBit bit userData) - ) - bits - , --------- - -- REMOTE - --------- - List.map - (\bit -> - saveFn bit (encodeHypaethralBit bit userData) - ) - bits - ] - |> List.concat - |> List.foldl - (\nextTask -> Task.andThen (\_ -> nextTask)) - (Task.succeed ()) - - -{-| Save different parts of hypaethral data, -one part at a time. --} -saveHypaethralDataBits : List HypaethralBit -> Manager -saveHypaethralDataBits bitsWithoutModifiedAt model = - let - bits = - ModifiedAt :: bitsWithoutModifiedAt - - userData = - model.hypaethralUserData - - updatedUserData = - { userData | modifiedAt = Just model.currentTime } - - updatedModel = - { model | hypaethralUserData = updatedUserData } - - save saveFn = - Time.now - |> Task.andThen - (\currentTime -> - saveHypaethralDataBitsTask - bits - { updatedUserData | modifiedAt = Just currentTime } - saveFn - ) - |> Common.attemptTask (always Brain.Bypass) - |> return updatedModel - in - case model.userSyncMethod of - Just (Dropbox { accessToken, expiresAt, refreshToken }) -> - if - Syncing.Services.Dropbox.Token.isExpired - { currentTime = model.currentTime - , expiresAt = expiresAt - } - then - refreshToken - |> refreshDropboxTokens - model.currentTime - (SaveHypaethralDataBits bits) - (Task.succeed ()) - |> return model - - else - save (Hypaethral.saveDropbox accessToken) - - Just (Ipfs { apiOrigin }) -> - save (Hypaethral.saveIpfs apiOrigin) - - Just (RemoteStorage args) -> - save (Hypaethral.saveRemoteStorage args) - - Nothing -> - -- Only save locally - save (\_ _ -> Task.succeed ()) - - -saveHypaethralDataBitWithDebounce : HypaethralBit -> Manager -saveHypaethralDataBitWithDebounce bit = - bit - |> Debouncer.provideInput - |> saveHypaethralDataSlowly - - -saveHypaethralDataSlowly : Debouncer.Msg HypaethralBit -> Manager -saveHypaethralDataSlowly debouncerMsg model = - let - ( m, c, e ) = - Debouncer.update debouncerMsg model.hypaethralDebouncer - - bits = - e - |> Maybe.withDefault [] - |> EverySet.fromList - |> EverySet.toList - in - c - |> Cmd.map (SaveHypaethralDataSlowly >> UserMsg) - |> return { model | hypaethralDebouncer = m } - |> (if not (List.isEmpty bits) then - andThen (saveHypaethralDataBits bits) - - else - identity - ) - - -sendHypaethralDataToUI : Json.Value -> HypaethralData -> Manager -sendHypaethralDataToUI encodedData decodedData model = - [ encodedData - |> Alien.broadcast Alien.LoadHypaethralUserData - |> Ports.toUI - - -- - , decodedData.tracks - |> Json.list Tracks.encodeTrack - |> Ports.updateSearchIndex - ] - |> Cmd.batch - |> return { model | hypaethralUserData = decodedData } - - - --- ๐Ÿ”ฑ โ–‘โ–‘ DATA - HYPAETHRAL BITS - - -saveFavourites : Json.Value -> Manager -saveFavourites value model = - value - |> Decode.decodeValue (Decode.list Tracks.favouriteDecoder) - |> Result.withDefault model.hypaethralUserData.favourites - |> hypaethralLenses.setFavourites model - |> saveHypaethralDataBitWithDebounce Favourites - - -savePlaylists : Json.Value -> Manager -savePlaylists value model = - value - |> Decode.decodeValue (Decode.list Playlists.decoder) - |> Result.withDefault model.hypaethralUserData.playlists - |> hypaethralLenses.setPlaylists model - |> saveHypaethralDataBitWithDebounce Playlists - - -saveProgress : Json.Value -> Manager -saveProgress value model = - value - |> Decode.decodeValue (Decode.dict Decode.float) - |> Result.withDefault model.hypaethralUserData.progress - |> hypaethralLenses.setProgress model - |> saveHypaethralDataBitWithDebounce Progress - - -saveSettings : Json.Value -> Manager -saveSettings value model = - value - |> Decode.decodeValue (Decode.map Just Settings.decoder) - |> Result.withDefault model.hypaethralUserData.settings - |> hypaethralLenses.setSettings model - |> saveHypaethralDataBitWithDebounce Settings - - -saveSources : Json.Value -> Manager -saveSources value model = - value - |> Decode.decodeValue (Decode.list Sources.decoder) - |> Result.withDefault model.hypaethralUserData.sources - |> hypaethralLenses.setSources model - |> saveHypaethralDataBitWithDebounce Sources - - -saveTracks : Json.Value -> Manager -saveTracks value model = - saveTracksAndUpdateSearchIndex - (value - |> Decode.decodeValue (Decode.list Tracks.trackDecoder) - |> Result.withDefault model.hypaethralUserData.tracks - ) - model - - -saveTracksAndUpdateSearchIndex : List Track -> Manager -saveTracksAndUpdateSearchIndex tracks model = - tracks - -- Store in model - |> hypaethralLenses.setTracks model - -- Update search index - |> Return.communicate - (tracks - |> Json.list Tracks.encodeTrack - |> Ports.updateSearchIndex - ) - -- Save with delay - |> andThen (saveHypaethralDataBitWithDebounce Tracks) - - - --- ๐Ÿ”ฑ โ–‘โ–‘ DATA - HYPAETHRAL LENSES - - -hypaethralLenses = - { setFavourites = makeHypaethralLens (\h f -> { h | favourites = f }) - , setPlaylists = makeHypaethralLens (\h p -> { h | playlists = p }) - , setProgress = makeHypaethralLens (\h p -> { h | progress = p }) - , setSettings = makeHypaethralLens (\h s -> { h | settings = s }) - , setSources = makeHypaethralLens (\h s -> { h | sources = s }) - , setTracks = makeHypaethralLens (\h t -> { h | tracks = t }) - } - - -makeHypaethralLens : (HypaethralData -> a -> HypaethralData) -> Model -> a -> Model -makeHypaethralLens setter model value = - { model | hypaethralUserData = setter model.hypaethralUserData value } - - - --- ๐Ÿ”ฑ โ–‘โ–‘ METHOD - - -saveMethod : Method -> Manager -saveMethod method model = - method - |> encodeMethod - |> Brain.Task.Ports.toCache Alien.SyncMethod - |> Common.attemptPortTask (always Brain.Bypass) - |> return { model | userSyncMethod = Just method } - - - --- ๐Ÿ”ฑ โ–‘โ–‘ SECRET KEY - - -removeEncryptionKey : Manager -removeEncryptionKey model = - Alien.SecretKey - |> Brain.Task.Ports.removeCache - |> Task.mapError TaskPort.errorToStringCustom - |> Task.andThen (\_ -> Time.now) - |> Task.andThen - (\currentTime -> - case model.userSyncMethod of - Just method -> - let - data = - model.hypaethralUserData - in - saveAllHypaethralDataTask { data | modifiedAt = Just currentTime } method - - Nothing -> - Task.succeed () - ) - |> Common.attemptTask (always Brain.Bypass) - |> return model - - -updateEncryptionKey : Json.Value -> Manager -updateEncryptionKey json model = - case Decode.decodeValue Decode.string json of - Ok passphrase -> - passphrase - |> Brain.Task.Ports.fabricateSecretKey - |> Task.mapError TaskPort.errorToStringCustom - |> Task.andThen (\_ -> Time.now) - |> Task.andThen - (\currentTime -> - case model.userSyncMethod of - Just method -> - let - data = - model.hypaethralUserData - in - saveAllHypaethralDataTask { data | modifiedAt = Just currentTime } method - - Nothing -> - Task.succeed () - ) - |> Common.attemptTask (always Brain.Bypass) - |> return model - - Err _ -> - Return.singleton model - - - --- ๐Ÿ“ญ โ–‘โ–‘ OTHER - - -refreshDropboxTokens : Time.Posix -> User.Msg -> Task.Task String a -> String -> Cmd Brain.Msg -refreshDropboxTokens currentTime msg initialTask refreshToken = - initialTask - |> Task.andThen - (\_ -> Dropbox.refreshAccessToken refreshToken) - |> Task.attempt - (\result -> - case result of - Ok tokens -> - msg - |> RefreshedDropboxTokens - { currentTime = Time.posixToMillis currentTime // 1000 - , refreshToken = refreshToken - } - tokens - |> UserMsg - - Err err -> - Common.reportUICmdMsg Alien.ReportError err - ) - - -refreshedDropboxTokens : - { currentTime : Int, refreshToken : String } - -> Dropbox.Tokens - -> User.Msg - -> Manager -refreshedDropboxTokens { currentTime, refreshToken } tokens msg model = - { accessToken = tokens.accessToken - , expiresAt = currentTime + tokens.expiresIn - , refreshToken = refreshToken - } - |> Dropbox - |> (\m -> saveMethod m model) - |> andThen (update msg) diff --git a/src/Core/Brain/User/Types.elm b/src/Core/Brain/User/Types.elm deleted file mode 100644 index a03a76db9..000000000 --- a/src/Core/Brain/User/Types.elm +++ /dev/null @@ -1,52 +0,0 @@ -module Brain.User.Types exposing (..) - -import Debouncer.Basic as Debouncer -import Json.Decode as Json -import Url exposing (Url) -import User.Layer as User exposing (HypaethralBit, HypaethralData) -import User.Layer.Methods.Dropbox as Dropbox - - - --- ๐Ÿ“ฃ - - -type Msg - = Commence (Maybe User.Method) Url ( Json.Value, HypaethralData ) - ----------------------------------------- - -- Method - ----------------------------------------- - | SetSyncMethod Json.Value - | Sync - | UnsetSyncMethod - ----------------------------------------- - -- Enclosed Data - ----------------------------------------- - | RetrieveEnclosedData - | EnclosedDataRetrieved Json.Value - | SaveEnclosedData Json.Value - ----------------------------------------- - -- Hypaethral Data, pt. 1 - ----------------------------------------- - | SaveFavourites Json.Value - | SavePlaylists Json.Value - | SaveProgress Json.Value - | SaveSettings Json.Value - | SaveSources Json.Value - | SaveTracks Json.Value - ----------------------------------------- - -- Hypaethral Data, pt. 2 - ----------------------------------------- - | FinishedSyncing - | GotHypaethralData HypaethralData - | SaveHypaethralDataBits (List HypaethralBit) - | SaveHypaethralDataSlowly (Debouncer.Msg HypaethralBit) - ----------------------------------------- - -- Encryption - ----------------------------------------- - | RemoveEncryptionKey - | UpdateEncryptionKey Json.Value - ----------------------------------------- - -- ๐Ÿ“ญ Other - ----------------------------------------- - | RefreshedDropboxTokens { currentTime : Int, refreshToken : String } Dropbox.Tokens Msg diff --git a/src/Core/Themes/Sunrise/Alfred/View.elm b/src/Core/Themes/Sunrise/Alfred/View.elm deleted file mode 100644 index 0c2bc804e..000000000 --- a/src/Core/Themes/Sunrise/Alfred/View.elm +++ /dev/null @@ -1,281 +0,0 @@ -module Themes.Sunrise.Alfred.View exposing (view) - -import Alfred exposing (..) -import Chunky exposing (..) -import Color exposing (Color) -import Conditional exposing (ifThenElse) -import Html exposing (Html, text) -import Html.Attributes exposing (attribute, autofocus, id, placeholder, style, type_) -import Html.Events exposing (onInput) -import Html.Ext exposing (onTapStopPropagation) -import Json.Decode -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import UI.Types as UI - - - --- ๐Ÿ—บ - - -view : Maybe (Alfred UI.Msg) -> Maybe Color -> Html UI.Msg -view maybeInstance extractedBackdropColor = - let - bgColor = - Maybe.unwrap "inherit" Color.toCssString extractedBackdropColor - in - case maybeInstance of - Just instance -> - let - hasResults = - List.sum (List.map (.items >> List.length) instance.results) > 0 - in - chunk - [ "inset-0" - , "flex" - , "flex-col" - , "fixed" - , "items-center" - , "px-3" - , "cursor-pointer" - , "z-50" - ] - [ ----------------------------------------- - -- Message - ----------------------------------------- - chunk - [ "italic" - , "leading-normal" - , "mt-12" - , "opacity-75" - , "text-center" - , "text-white" - - -- Dark mode - ------------ - , "dark:text-base07" - ] - [ text instance.message ] - - ----------------------------------------- - -- Search - ----------------------------------------- - , brick - [ Html.Events.custom - "tap" - (Json.Decode.succeed - { message = UI.Bypass - , stopPropagation = True - , preventDefault = True - } - ) - ] - [ "text-sm" - , "max-w-xl" - , "mt-8" - , "w-full" - ] - [ slab - Html.input - [ autofocus True - , id "diffuse__alfred" - , onInput UI.GotAlfredInput - , type_ "text" - - -- - , attribute "spellcheck" "false" - - -- - , case instance.operation of - Query -> - placeholder "Type to search" - - QueryOrMutation -> - placeholder "Type to search or create" - - Mutation -> - placeholder "Type to create" - ] - [ "border" - , "bg-white" - , "block" - , "leading-normal" - , "opacity-95" - , "outline-none" - , "p-4" - , "rounded-t" - , "shadow-md" - , "text-xl" - , "tracking-tad-closer" - , "w-full" - - -- - , if not hasResults then - "rounded-b" - - else - "" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - , "dark:border-base00" - ] - [] - ] - - ----------------------------------------- - -- Results - ----------------------------------------- - , brick - [ id "alfred__results" ] - [ "bg-white" - , "border-t-0" - , "leading-tight" - , "max-w-xl" - , "mb-32" - , "opacity-95" - , "overflow-x-hidden" - , "overflow-y-auto" - , "rounded-b" - , "shadow-md" - , "smooth-scrolling" - , "text-nearly-sm" - , "w-full" - - -- - , ifThenElse hasResults "border" "" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - , "dark:border-base00" - ] - (instance.results - |> List.foldl - (\group ( acc, indexBase ) -> - case List.length group.items of - 0 -> - ( acc, indexBase ) - - x -> - ( groupView bgColor instance group indexBase :: acc - , indexBase + x - ) - ) - ( [], 0 ) - |> Tuple.first - |> List.reverse - ) - ] - - Nothing -> - nothing - - -groupView bgColor instance group indexBase = - raw - [ case group.name of - Just name -> - chunk - [ "all-small-caps" - , "antialiased" - , "font-semibold" - , "leading-tight" - , "mb-2" - , "mx-2" - , "mt-5" - , "opacity-60" - , "px-3" - , "text-sm" - , "tracking-wider" - ] - [ Html.text name ] - - Nothing -> - Html.text "" - , raw - (List.indexedMap - (\i -> itemView bgColor instance <| indexBase + i) - group.items - ) - ] - - -itemView bgColor instance idx item = - brick - [ onTapStopPropagation (UI.SelectAlfredItem idx) - - -- - , if idx == instance.focus then - id "alfred__results__focus" - - else - id ("alfred__results__" ++ String.fromInt idx) - - -- - , if idx == instance.focus then - style "background-color" bgColor - - else - style "" "" - ] - (List.concat - [ [ "flex" - , "items-center" - , "m-2" - , "p-3" - , "relative" - , "rounded" - ] - - -- - , if idx == instance.focus then - [ "text-white" - , "dark:opacity-80" - , "dark:text-base07" - ] - - else - [ "text-inherit" ] - - -- - -- , if modBy 2 idx == 0 then - -- [] - -- else - -- [ "bg-gray-100", "dark:bg-base01-15" ] - ] - ) - [ case item.icon of - Just icon -> - slab - Html.span - [] - [ "inline-block" - , "mr-2" - , "w-5" - ] - [ icon Inherit - ] - - Nothing -> - text "" - - -- - , slab - Html.span - [] - [ "flex-1", "inline-block", "pt-px" ] - [ text item.title ] - - -- - , if idx == instance.focus then - chunk - [ "leading-0", "ml-2" ] - [ Icons.keyboard_return 13 Inherit - ] - - else - nothing - ] diff --git a/src/Core/Themes/Sunrise/Console.elm b/src/Core/Themes/Sunrise/Console.elm deleted file mode 100644 index 5629bccab..000000000 --- a/src/Core/Themes/Sunrise/Console.elm +++ /dev/null @@ -1,312 +0,0 @@ -module Themes.Sunrise.Console exposing (view) - -import Chunky exposing (..) -import Conditional exposing (..) -import Html exposing (Html, text) -import Html.Attributes exposing (style, title) -import Html.Events exposing (on, onClick) -import Json.Decode as Decode -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import UI.Audio.Types exposing (AudioLoadingState(..), NowPlaying, nowPlayingIdentifiedTrack) -import UI.Queue.Types as Queue -import UI.Tracks.Types as Tracks -import UI.Types exposing (Msg(..)) - - - --- ๐Ÿ—บ - - -view : - Maybe NowPlaying - -> Bool - -> Bool - -> Html Msg -view nowPlaying repeat shuffle = - chunk - [ "antialiased" - , "mt-1" - , "text-center" - , "w-full" - - -- - , "lg:max-w-insulation" - ] - [ ----------------------------------------- - -- Now Playing - ----------------------------------------- - chunk - [ "text-sm" - , "italic" - , "leading-normal" - , "py-4" - , "text-white" - ] - [ case Maybe.map .loadingState nowPlaying of - Nothing -> - text "Diffuse" - - Just Loading -> - text "Loading track ..." - - Just Loaded -> - case Maybe.map nowPlayingIdentifiedTrack nowPlaying of - Just ( _, { tags } ) -> - slab - Html.span - [ onClick (TracksMsg Tracks.ScrollToNowPlaying) - , title "Scroll to track" - ] - [ "cursor-pointer" ] - [ case tags.artist of - Just artist -> - text (artist ++ " - " ++ tags.title) - - Nothing -> - text tags.title - ] - - Nothing -> - text "Diffuse" - - ----------------------------------------- - -- Errors - ----------------------------------------- - Just DecodeError -> - text "(!) An error occurred while decoding the audio" - - Just NetworkError -> - text "Waiting until your internet connection comes back online ..." - - Just NotSupportedError -> - text "(!) Your browser does not support playing this type of audio" - - -- Just NotSupportedOrMissing -> - -- text "The audio is missing or is in a format not supported by your browser." - ] - - ----------------------------------------- - -- Progress Bar - ----------------------------------------- - , let - maybeDuration = - Maybe.andThen .duration nowPlaying - - maybePosition = - Maybe.map .playbackPosition nowPlaying - - progress = - case ( maybeDuration, maybePosition ) of - ( Just duration, Just position ) -> - if duration <= 0 then - 0 - - else - (position / duration) - |> (*) 100 - |> min 100 - |> max 0 - - _ -> - 0 - in - brick - (case nowPlaying of - Just { item } -> - item.identifiedTrack - |> Tuple.second - |> .id - |> (\id -> - \float -> Seek { progress = float, trackId = id } - ) - |> clickLocationDecoder - |> on "click" - |> List.singleton - - Nothing -> - [] - ) - [ "cursor-pointer" - , "py-1" - ] - [ brick - [ style "background-color" "rgba(255, 255, 255, 0.25)" - , style "height" "3px" - ] - [ "rounded-sm" - , "select-none" - ] - [ brick - [ style "background-color" "rgba(255, 255, 255, 0.325)" - , style "height" "3px" - , style "width" (String.fromFloat progress ++ "%") - ] - [ "progressBarValue" - , "rounded-sm" - ] - [] - ] - ] - - ----------------------------------------- - -- Buttons - ----------------------------------------- - , chunk - [ "flex" - , "justify-between" - , "mb-3" - , "mt-4" - , "select-none" - , "text-white-90" - - -- - , "sm:justify-center" - ] - [ button "Toggle repeat" - (smallLight repeat) - (icon Icons.repeat 18) - (QueueMsg Queue.ToggleRepeat) - - -- - , button - "Play previous track" - lightPlaceHolder - (icon Icons.fast_rewind 20) - (QueueMsg Queue.Rewind) - - -- - , let - isPlaying = - Maybe.unwrap False .isPlaying nowPlaying - in - button - "" - (largeLight isPlaying) - play - TogglePlay - - -- - , button - "Play next track" - lightPlaceHolder - (icon Icons.fast_forward 20) - (QueueMsg Queue.Shift) - - -- - , button - "Toggle shuffle" - (smallLight shuffle) - (icon Icons.shuffle 18) - (QueueMsg Queue.ToggleShuffle) - ] - ] - - -button : String -> Html msg -> Html msg -> msg -> Html msg -button t light content msg = - brick - [ onClick msg - , title t - ] - [ "cursor-pointer" - , "flex" - , "flex-col" - , "items-center" - , "px-1" - - -- - , "sm:mx-8" - ] - [ brick - [ style "height" "4px" ] - [] - [ light ] - , brick - [ style "height" "25px" ] - [ "flex" - , "items-center" - , "my-2" - ] - [ content ] - ] - - -smallLight : Bool -> Html msg -smallLight isOn = - brick - [ style "height" "4px" - , style "width" "4px" - - -- - , style "background-color" <| - ifThenElse - isOn - "rgb(157, 174, 255)" - "rgba(255, 255, 255, 0.25)" - ] - [ "rounded-full" ] - [] - - -largeLight : Bool -> Html msg -largeLight isOn = - brick - [ style "height" "4px" - , style "left" "-2px" - , style "width" "17px" - - -- - , style "background-color" <| - ifThenElse - isOn - "rgb(198, 254, 153)" - "rgba(255, 255, 255, 0.25)" - ] - [ "relative", "rounded-full" ] - [] - - -lightPlaceHolder : Html msg -lightPlaceHolder = - Html.div - [ style "height" "4px" ] - [] - - -play : Html msg -play = - brick - [ style "font-size" "11.25px" - , style "letter-spacing" "3.75px" - ] - [ "font-bold" - , "font-display" - , "relative" - , "whitespace-nowrap" - ] - [ text "PLAY" ] - - - --- โš—๏ธ - - -icon : (Int -> Coloring -> Html msg) -> Int -> Html msg -icon iconFunction int = - iconFunction int Inherit - - - --- EVENTS - - -clickLocationDecoder : (Float -> msg) -> Decode.Decoder msg -clickLocationDecoder message = - Decode.map message - (Decode.map2 - (\a b -> a / b) - (Decode.at [ "offsetX" ] Decode.float) - (Decode.at [ "currentTarget", "clientWidth" ] Decode.float) - ) diff --git a/src/Core/Themes/Sunrise/ContextMenu.elm b/src/Core/Themes/Sunrise/ContextMenu.elm deleted file mode 100644 index 777675e80..000000000 --- a/src/Core/Themes/Sunrise/ContextMenu.elm +++ /dev/null @@ -1,131 +0,0 @@ -module Themes.Sunrise.ContextMenu exposing (view) - -import Chunky exposing (..) -import Conditional exposing (..) -import ContextMenu exposing (..) -import Html exposing (Html, text) -import Html.Attributes exposing (style) -import Html.Events exposing (custom) -import Json.Decode -import Material.Icons.Types exposing (Coloring(..)) -import UI.Types as UI exposing (Msg) - - - --- ๐Ÿ—บ - - -view : Float -> Maybe (ContextMenu Msg) -> Html Msg -view viewportWidth m = - case m of - Just (ContextMenu items coordinates) -> - let - ( height, width ) = - ( 250, 170 ) - - left = - coordinates.x - |> max (width / 2 + 12) - |> min (viewportWidth - width / 2 - 12) - - top = - coordinates.y - |> max (height / 2 + 12) - in - brick - [ style "left" (String.fromFloat left ++ "px") - , style "top" (String.fromFloat top ++ "px") - - -- - , style "min-width" "170px" - ] - [ "absolute" - , "bg-white" - , "leading-loose" - , "opacity-95" - , "overflow-hidden" - , "-translate-x-1/2" - , "-translate-y-1/2" - , "rounded" - , "shadow-md" - , "select-none" - , "text-almost-sm" - , "transform" - , "z-50" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - , "dark:border" - , "dark:border-base00" - ] - (List.map - (\item -> - case item of - Item i -> - itemView i - - Divider -> - -- NOTE: Not needed at the moment - nothing - ) - items - ) - - Nothing -> - nothing - - -itemView : ContextMenu.ItemProperties Msg -> Html Msg -itemView { icon, label, msg, active } = - brick - [ custom - "tap" - (Json.Decode.succeed - { message = UI.MsgViaContextMenu msg - , stopPropagation = True - , preventDefault = True - } - ) - ] - [ "border-b" - , "cursor-pointer" - , "pl-4" - , "pr-8" - , "py-3" - , "truncate" - - -- - , "last:border-transparent" - - -- - , ifThenElse active "antialiased" "subpixel-antialiased" - , ifThenElse active "border-transparent" "border-gray-200" - , ifThenElse active "bg-base00" "bg-transparent" - , ifThenElse active "text-white" "text-inherit" - , ifThenElse active "font-semibold" "font-normal" - - -- Dark mode - ------------ - , "dark:border-base00" - - -- - , ifThenElse active "dark:bg-base07" "dark:bg-transparent" - , ifThenElse active "dark:text-darkest-hour" "dark:text-inherit" - ] - [ inline - [ "align-middle" - , "inline-block" - , "leading-0" - ] - [ icon 14 Inherit ] - , inline - [ "align-middle" - , "inline-block" - , "leading-0" - , "ml-2" - , "pl-1" - , "relative" - ] - [ text label ] - ] diff --git a/src/Core/Themes/Sunrise/Kit.elm b/src/Core/Themes/Sunrise/Kit.elm deleted file mode 100644 index bf7e19961..000000000 --- a/src/Core/Themes/Sunrise/Kit.elm +++ /dev/null @@ -1,704 +0,0 @@ -module Themes.Sunrise.Kit exposing (..) - -import Chunky exposing (..) -import Color -import Conditional exposing (ifThenElse) -import Html exposing (Html) -import Html.Attributes as A exposing (href, style) -import Html.Events exposing (onClick, onInput) -import Icons exposing (Icon) -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Svg - - - --- COLORS - - -colorKit = - { base00 = rgb 45 45 45 - , base01 = rgb 63 63 63 - , base02 = rgb 79 79 79 - , base03 = rgb 119 119 119 - , base04 = rgb 140 140 140 - , base05 = rgb 163 163 163 - , base06 = rgb 186 186 186 - , base07 = rgb 232 232 232 - , base08 = rgb 239 97 85 - , base09 = rgb 249 155 21 - , base0A = rgb 254 196 24 - , base0B = rgb 72 182 133 - , base0C = rgb 91 196 191 - , base0D = rgb 6 182 239 - , base0E = rgb 129 91 164 - , base0F = rgb 233 107 168 - } - - -colors = - { -- States - success = colorKit.base0B - , error = colorKit.base08 - , warning = colorKit.base0A - - -- Gray - , gray_100 = Color.hsl 0 0 0.988 - , gray_200 = Color.hsl 0 0 0.973 - , gray_300 = Color.hsl 0 0 0.933 - , gray_400 = Color.hsl 0 0 0.882 - , gray_500 = Color.hsl 0 0 0.863 - , gray_600 = Color.hsl 0 0 0.776 - - -- Other - , background = rgb 2 7 14 - , selection = colorKit.base04 - , text = colorKit.base01 - } - - -rgb = - Color.rgb255 - - - --- ๐Ÿฑ โ–‘โ–‘ BUTTON - - -type ButtonColor - = Accent - | Blank - | Gray - - -type ButtonType - = Filled - | IconOnly - | Normal - - -button : ButtonType -> msg -> Html msg -> Html msg -button = - buttonWithColor Gray - - -buttonLink : String -> ButtonType -> Html msg -> Html msg -buttonLink theHref buttonType = - buttonWithOptions Html.a [ href theHref ] Accent buttonType Nothing - - -buttonLinkWithColor : ButtonColor -> String -> ButtonType -> Html msg -> Html msg -buttonLinkWithColor color theHref buttonType = - buttonWithOptions Html.a [ href theHref ] color buttonType Nothing - - -buttonWithColor : ButtonColor -> ButtonType -> msg -> Html msg -> Html msg -buttonWithColor color buttonType msg = - buttonWithOptions Html.button [] color buttonType (Just msg) - - -buttonWithOptions : - (List (Html.Attribute msg) -> List (Html msg) -> Html msg) - -> List (Html.Attribute msg) - -> ButtonColor - -> ButtonType - -> Maybe msg - -> Html msg - -> Html msg -buttonWithOptions tag attributes buttonColor buttonType maybeMsg child = - let - defaultClasses = - [ "antialiased" - , "border-2" - , "cursor-pointer" - , "font-semibold" - , "inline-block" - , "leading-relaxed" - , "no-underline" - , "py-2" - , "px-4" - , "rounded" - , "text-center" - , "text-sm" - - -- - , "fixate:bg-white" - , "fixate:border-black" - , "fixate:text-black" - ] - - specificClasses = - case buttonType of - Filled -> - case buttonColor of - Accent -> - [ "bg-accent-btn" - , "border-transparent" - , "text-white-90" - ] - - Blank -> - [ "bg-white" - , "border-transparent" - , "text-accent-light" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - , "dark:text-accent-dark" - ] - - Gray -> - [ "bg-base04" - , "border-transparent" - , "text-white" - - -- Dark mode - ------------ - , "dark:bg-base05" - ] - - _ -> - case buttonColor of - Accent -> - [ "bg-transparent" - , "border-accent-btn" - , "text-accent-btn" - ] - - Blank -> - [ "bg-transparent" - , "border-white" - , "text-white" - ] - - Gray -> - [ "bg-transparent" - , "border-base04" - , "text-base04" - - -- Dark mode - ------------ - , "dark:border-base05" - , "dark:text-base05" - ] - in - slab - tag - (case maybeMsg of - Just msg -> - attributes ++ [ onClick msg ] - - Nothing -> - attributes - ) - (List.append - defaultClasses - specificClasses - ) - [ case buttonType of - IconOnly -> - inline - [ "align-middle" - , "inline-block" - , "leading-none" - , "pointer-events-none" - , "text-0" - ] - [ child ] - - _ -> - inline - [ "align-middle" - , "inline-block" - , "leading-none" - , "pointer-events-none" - ] - [ child ] - ] - - - --- ๐Ÿฑ โ–‘โ–‘ OTHER - - -askForInput : { question : String, info : List (Html msg) } -> Html msg -askForInput { question, info } = - Html.span - [] - [ chunk - [ "font-semibold", "pt-1" ] - [ Html.text question ] - , case info of - [] -> - Html.text "" - - _ -> - chunk - [ "italic", "mt-2", "text-sm" ] - info - ] - - -canister : List (Html msg) -> Html msg -canister children = - chunk - [ "mx-1", "px-4", "pb-4" ] - children - - -canisterForm : List (Html msg) -> Html msg -canisterForm children = - chunk - [ "mx-1", "px-4", "pb-4", "w-full" ] - children - - -centeredContent : List (Html msg) -> Html msg -centeredContent children = - chunk - [ "flex" - , "flex-grow" - , "items-stretch" - , "overflow-hidden" - , "relative" - ] - [ Html.map never logoBackdrop - , chunk - [ "flex" - , "flex-col" - , "flex-grow" - , "items-center" - , "justify-center" - , "max-w-full" - , "relative" - , "z-10" - ] - children - ] - - -checkbox : { checked : Bool, toggleMsg : msg } -> Html msg -checkbox opts = - brick - [ onClick opts.toggleMsg - , style "left" "-3px" - ] - [ "inline-block", "cursor-pointer", "relative" ] - [ if opts.checked then - Icons.check_box 22 Inherit - - else - Icons.check_box_outline_blank 22 Inherit - ] - - -focusScreen : { icon : Icon msg, iconHref : Maybe String, text : List (Html msg), textHref : Maybe String } -> List (Html msg) -> Html msg -focusScreen { icon, iconHref, text, textHref } nodes = - [ slab - (case iconHref of - Just _ -> - Html.a - - Nothing -> - Html.div - ) - (case iconHref of - Just h -> - [ href h ] - - Nothing -> - [] - ) - [ "block" - , "opacity-30" - , "text-inherit" - ] - [ icon 64 Inherit ] - , slab - (case iconHref of - Just _ -> - Html.a - - Nothing -> - Html.div - ) - (case textHref of - Just h -> - [ href h ] - - Nothing -> - [] - ) - [ "block" - , "leading-normal" - , "mt-2" - , "opacity-40" - , "text-center" - , "text-inherit" - ] - text - , chunk - [ "max-w-full" - , "mt-4" - ] - nodes - ] - |> chunk - [ "flex" - , "flex-col" - , "items-center" - , "max-h-full" - , "overflow-y-auto" - , "px-4" - , "py-8" - , "w-full" - ] - |> List.singleton - |> centeredContent - - -h1 : String -> Html msg -h1 text = - slab - Html.h1 - [ style "font-size" "13.5px" ] - [ "all-small-caps" - , "antialiased" - , "bg-base06" - , "inline-block" - , "font-semibold" - , "leading-tight" - , "m-0" - , "-top-px" - , "overflow-hidden" - , "pointer-events-none" - , "px-2" - , "py-1" - , "relative" - , "rounded-b" - , "uppercase" - , "text-sm" - , "text-white" - - -- Dark mode - ------------ - , "dark:bg-base01" - , "dark:text-base05" - ] - [ Html.text text ] - - -h2 : String -> Html msg -h2 text = - slab - Html.h2 - [] - [ "font-bold" - , "font-display" - , "leading-tight" - , "mb-8" - , "mt-4" - , "mx-auto" - , "text-2xl" - , "text-center" - ] - [ Html.text text ] - - -h3 : String -> Html msg -h3 text = - slab - Html.h2 - [] - [ "antialiased" - , "font-bold" - , "font-display" - , "leading-tight" - , "mb-8" - , "mt-4" - , "text-xl" - ] - [ Html.text text ] - - -inlineIcon : (Int -> Coloring -> Svg.Svg msg) -> Html msg -inlineIcon icon = - inline - [ "align-sub" - , "inline-block" - , "leading-0" - , "mr-1" - , "text-0" - ] - [ icon 14 Inherit ] - - -intro : Html msg -> Html msg -intro child = - slab - Html.p - [ style "line-height" "1.75" ] - [ "mb-6" - , "mt-3" - , "text-base05" - , "text-sm" - - -- Dark mode - ------------ - , "dark:text-base03" - ] - [ child ] - - -label : List (Html.Attribute msg) -> String -> Html msg -label attributes t = - slab - Html.label - (style "font-size" "11.25px" :: attributes) - [ "antialiased" - , "block" - , "font-bold" - , "leading-normal" - , "opacity-90" - , "uppercase" - ] - [ Html.text t ] - - -link : { label : String, url : String } -> Html msg -link params = - slab - Html.a - [ A.href params.url - , A.target "_blank" - ] - [ "border-b-2" - , "border-base04" - , "inline-block" - , "leading-none" - , "no-underline" - , "text-inherit" - ] - [ Html.text params.label ] - - -logoBackdrop : Html Never -logoBackdrop = - chunk - [ "logo-backdrop" - - -- - , "absolute" - , "bg-cover" - , "bg-no-repeat" - , "h-0" - , "left-full" - , "opacity-025" - , "pt-full" - , "top-0" - , "z-0" - - -- Dark mode - ------------ - , "dark:opacity-40" - ] - [] - - -receptacle : { scrolling : Bool } -> List (Html msg) -> Html msg -receptacle { scrolling } = - chunk - [ "absolute" - , "bg-white" - , "flex" - , "flex-col" - , "inset-0" - , "overflow-x-hidden" - , "scrolling-touch" - , "z-50" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - - -- - , ifThenElse scrolling "overflow-y-auto" "overflow-y-hidden" - ] - - -select : (String -> msg) -> List (Html msg) -> Html msg -select inputHandler options = - chunk - [ "max-w-md" - , "mx-auto" - , "relative" - , "text-base05" - , "w-full" - - -- - , "focus-within:text-black" - - -- Dark mode - ------------ - , "dark:text-gray-600" - , "dark:focus-within:text-base07" - ] - [ slab - Html.select - [ onInput inputHandler ] - [ "appearance-none" - , "border-b" - , "border-l-0" - , "border-r-0" - , "border-t-0" - , "border-gray-400" - , "bg-transparent" - , "block" - , "leading-normal" - , "m-0" - , "outline-none" - , "py-2" - , "px-0" - , "rounded-none" - , "text-base01" - , "text-lg" - , "w-full" - - -- - , "focus:border-black" - - -- Dark mode - ------------ - , "dark:border-base02" - , "dark:text-gray-600" - - -- - , "dark:focus:border-base07" - ] - options - , chunk - [ "absolute" - , "-translate-y-1/2" - , "mt-px" - , "right-0" - , "text-0" - , "top-1/2" - , "transform" - ] - [ Icons.keyboard_arrow_down 20 Inherit ] - ] - - -textArea : List (Html.Attribute msg) -> Html msg -textArea attributes = - slab - Html.textarea - attributes - [ "bg-white" - , "block" - , "leading-normal" - , "mb-4" - , "p-4" - , "resize-none" - , "rounded" - , "text-base01" - , "text-sm" - , "w-full" - - -- - , "placeholder:text-base01" - , "placeholder:text-opacity-40" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - , "dark:text-gray-600" - - -- - , "dark:placeholder:text-gray-600" - , "dark:placeholder:text-opacity-30" - ] - [] - - -textButton : { label : String, onClick : msg } -> Html msg -textButton params = - slab - Html.button - [ onClick params.onClick ] - [ "appearance-none" - , "bg-transparent" - , "border-base04" - , "border-b-2" - , "text-inherit" - , "leading-tight" - , "m-0" - , "p-0" - , "cursor-pointer" - ] - [ Html.text params.label ] - - -textField : List (Html.Attribute msg) -> Html msg -textField attributes = - slab - Html.input - attributes - [ "appearance-none" - , "border-b" - , "border-l-0" - , "border-r-0" - , "border-t-0" - , "border-gray-400" - , "bg-transparent" - , "block" - , "leading-normal" - , "mt-1" - , "py-2" - , "rounded-none" - , "text-base01" - , "text-sm" - , "w-full" - - -- - , "focus:border-black" - , "focus:outline-none" - , "placeholder:text-base01" - , "placeholder:text-opacity-40" - - -- Dark mode - ------------ - , "dark:border-base02" - , "dark:text-gray-600" - - -- - , "dark:focus:border-base07" - , "dark:placeholder:text-gray-600" - , "dark:placeholder:text-opacity-30" - ] - [] - - -textFieldAlt : List (Html.Attribute msg) -> Html msg -textFieldAlt attributes = - slab - Html.input - attributes - [ "bg-white" - , "block" - , "leading-normal" - , "mb-3" - , "p-3" - , "resize-none" - , "rounded" - , "text-base01" - , "text-sm" - , "w-full" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - , "dark:text-gray-600" - ] - [] diff --git a/src/Core/Themes/Sunrise/List.elm b/src/Core/Themes/Sunrise/List.elm deleted file mode 100644 index b0da73a07..000000000 --- a/src/Core/Themes/Sunrise/List.elm +++ /dev/null @@ -1,174 +0,0 @@ -module Themes.Sunrise.List exposing (Action, Item, Variant(..), view) - -import Chunky exposing (..) -import Conditional exposing (..) -import Html exposing (Html) -import Html.Attributes exposing (title) -import Html.Events exposing (onClick) -import Html.Events.Extra.Mouse as Mouse exposing (onWithOptions) -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import UI.DnD as DnD -import VirtualDom - - - --- ๐ŸŒณ - - -type alias Action msg = - { icon : Int -> Coloring -> VirtualDom.Node msg - , msg : Maybe (Mouse.Event -> msg) - , title : String - } - - -type alias Item msg = - { label : Html msg - , actions : List (Action msg) - , msg : Maybe msg - , isSelected : Bool - } - - -type Variant context msg - = Normal - | Draggable (DnD.Environment context msg) - - - --- โ›ฉ - - -view : Variant Int msg -> List (Item msg) -> Html msg -view variant items = - chunk - [ "antialiased", "font-semibold", "leading-snug", "text-nearly-sm" ] - (List.indexedMap (item variant) items) - - - ------------------------------------------ --- ใŠ™๏ธ ------------------------------------------ - - -item : Variant Int msg -> Int -> Item msg -> Html msg -item variant idx { label, actions, msg, isSelected } = - let - dragTarget = - case variant of - Normal -> - False - - Draggable env -> - DnD.isDraggingOver idx env.model - in - chunky - [ "border-t" - - -- - , if dragTarget then - "border-base03" - - else - "border-transparent" - - -- Dark mode - ------------ - , if dragTarget then - "dark:border-gray-300" - - else - "dark:border-transparent" - ] - <| - chunk - [ "border-b" - , "border-gray-200" - , "flex" - , "items-center" - - -- - , ifThenElse (Maybe.isJust msg) "cursor-pointer" "cursor-default" - , ifThenElse isSelected "text-base03" "text-inherit" - - -- Dark mode - ------------ - , "dark:border-base00" - , ifThenElse isSelected "dark:text-gray-300" "dark:text-inherit" - ] - [ -- Label - -------- - brick - (case variant of - Normal -> - case msg of - Just m -> - [ onClick m ] - - Nothing -> - [] - - Draggable env -> - List.append - (case ( isSelected, msg ) of - ( True, _ ) -> - [ DnD.listenToStart env idx ] - - ( False, Just m ) -> - [ onClick m ] - - ( False, Nothing ) -> - [] - ) - (DnD.listenToEnterLeave env idx) - ) - [ "flex-grow" - , "py-4" - , "overflow-hidden" - ] - [ label ] - - -- Actions - ---------- - , chunk - [ "flex" - , "items-center" - - -- - , case variant of - Normal -> - "pointer-events-auto" - - Draggable env -> - if DnD.isDragging env.model then - "pointer-events-none" - - else - "pointer-events-auto" - ] - (List.map actionView actions) - ] - - -actionView : Action msg -> Html msg -actionView action = - brick - (case action.msg of - Just msg -> - [ title action.title - - -- - , onWithOptions "click" { stopPropagation = True, preventDefault = True } msg - ] - - Nothing -> - [ title action.title ] - ) - [ "leading-0" - , "ml-1" - , "pl-1" - , ifThenElse (Maybe.isJust action.msg) "cursor-pointer" "cursor-default" - ] - [ action.icon 16 Inherit ] diff --git a/src/Core/Themes/Sunrise/Navigation.elm b/src/Core/Themes/Sunrise/Navigation.elm deleted file mode 100644 index 001a1a974..000000000 --- a/src/Core/Themes/Sunrise/Navigation.elm +++ /dev/null @@ -1,246 +0,0 @@ -module Themes.Sunrise.Navigation exposing (global, local, localWithTabindex) - -import Alfred exposing (Alfred) -import Chunky exposing (..) -import Common -import Conditional exposing (..) -import Html exposing (Html, text) -import Html.Attributes exposing (href, style, tabindex, target, title) -import Html.Events exposing (onClick) -import Html.Events.Extra.Mouse as Mouse -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import UI.Navigation exposing (..) -import UI.Page as Page exposing (Page) - - - --- GLOBAL - - -global : List ( Page, String ) -> Maybe (Alfred reply) -> Page -> Html msg -global items alfred activePage = - brick - [ style "font-size" "11.25px" ] - [ "antialiased" - , "font-semibold" - , "mb-16" - , "mt-8" - , "text-xs" - , "tracking-widest" - , "uppercase" - - -- - , ifThenElse (Maybe.isJust alfred) "opacity-0" "opacity-100" - ] - (List.indexedMap - (globalItem activePage <| List.length items) - items - ) - - -globalItem : Page -> Int -> Int -> ( Page, String ) -> Html msg -globalItem activePage totalItems idx ( page, label ) = - let - isActivePage = - Page.sameBase page activePage - - isLastItem = - idx + 1 == totalItems - in - chunk - [ "inline-block" - , ifThenElse isLastItem "mr-0" "mr-1" - ] - [ slab - Html.a - [ href (Page.toString page) ] - [ "inline-block" - , "leading-normal" - , "no-underline" - , "cursor-pointer" - , "pt-2" - - -- - , ifThenElse isActivePage "border-b" "border-b-0" - , ifThenElse isActivePage "border-base01-15" "border-transparent" - , ifThenElse isActivePage "text-base01" "text-base01-55" - , ifThenElse isLastItem "mr-0" "mr-8" - - -- - , "focus:border-black-50" - , "focus:outline-none" - , "focus:text-black" - ] - [ text label ] - ] - - - --- LOCAL - - -local : List ( Icon msg, Label, Action msg ) -> Html msg -local = - localWithTabindex 0 - - -localWithTabindex : Int -> List ( Icon msg, Label, Action msg ) -> Html msg -localWithTabindex tabindex_ items = - brick - [ style "font-size" "12.5px" ] - [ "antialiased" - , "border-b" - , "border-gray-300" - - -- Dark mode - ------------ - , "dark:border-base01" - ] - [ chunk - [ "flex" ] - (items - |> List.reverse - |> List.map (localItem tabindex_ { amount = List.length items }) - |> List.reverse - ) - ] - - -localItem : Int -> { amount : Int } -> ( Icon msg, Label, Action msg ) -> Html msg -localItem tabindex_ { amount } ( Icon icon, Label labelText labelType, action ) = - slab - (case action of - NavigateToPage _ -> - Html.a - - OpenLinkInNewPage _ -> - Html.a - - PerformMsg _ -> - Html.button - - PerformMsgWithMouseEvent _ -> - Html.button - ) - [ case action of - NavigateToPage page -> - href (Page.toString page) - - OpenLinkInNewPage link -> - href link - - PerformMsg msg -> - onClick msg - - PerformMsgWithMouseEvent msg -> - Mouse.onClick msg - - -- - , case labelType of - Hidden -> - title labelText - - Shown -> - title "" - - -- - , case action of - OpenLinkInNewPage _ -> - target "_blank" - - _ -> - target "_self" - - -- - , tabindex tabindex_ - ] - [ "bg-transparent" - , "border-gray-300" - , "border-r" - , "cursor-pointer" - , "flex-basis-0" - , "font-semibold" - , "leading-none" - , "no-underline" - , "px-4" - , "py-3" - , "text-base02" - - -- - , ifThenElse - (labelText == Common.backToIndex && labelType == Hidden && amount > 1) - "flex-shrink-0" - "flex-grow" - - -- - , ifThenElse - (labelText == Common.backToIndex && labelType == Hidden && amount > 1) - "overflow-visible" - "overflow-hidden" - - -- - , "fixate:text-black" - , "last:border-r-0" - - -- Responsive - ------------- - , "sm:overflow-visible" - - -- - , ifThenElse - (labelType == Hidden) - "sm:flex-shrink-0" - "sm:flex-grow" - - -- - , ifThenElse - (labelType == Hidden) - "sm:flex-grow-0" - "sm:flex-grow" - - -- Dark mode - ------------ - , "dark:border-base01" - , "dark:text-base06" - - -- - , "dark:fixate:text-base07" - ] - [ chunk - [ "border-b" - , "border-t" - , "border-transparent" - , "flex" - , "items-center" - , "justify-center" - , "mt-px" - , "pt-px" - ] - [ inline - [ "flex-shrink-0" ] - [ icon 16 Inherit ] - - -- - , case labelType of - Hidden -> - nothing - - Shown -> - slab - Html.span - [] - [ "hidden" - , "leading-tight" - , "ml-1" - , "transform" - , "translate-y-px" - , "truncate" - - -- Responsive - ------------- - , "sm:inline-block" - ] - [ text labelText ] - ] - ] diff --git a/src/Core/Themes/Sunrise/Notifications.elm b/src/Core/Themes/Sunrise/Notifications.elm deleted file mode 100644 index b28bf72e2..000000000 --- a/src/Core/Themes/Sunrise/Notifications.elm +++ /dev/null @@ -1,133 +0,0 @@ -module Themes.Sunrise.Notifications exposing (view) - -import Chunky exposing (..) -import Color exposing (Color) -import Color.Manipulate -import Conditional exposing (ifThenElse) -import Html exposing (Html, text) -import Html.Attributes exposing (class, rel, style) -import Html.Ext exposing (onDoubleTap, onTap) -import Html.Lazy -import Maybe.Extra as Maybe -import Notifications exposing (..) -import UI.Notifications exposing (Model) -import UI.Types exposing (Msg(..)) - - - --- ๐Ÿ—บ - - -view : Maybe Color -> Model -> Html Msg -view extractedBackdropColor collection = - let - manipulatedColor = - Maybe.map - (Color.Manipulate.darken 0.125) - extractedBackdropColor - in - collection - |> List.reverse - |> List.map (Html.Lazy.lazy2 notificationView manipulatedColor) - |> chunk - [ "notifications" - - -- - , "absolute" - , "break-all" - , "bottom-0" - , "flex" - , "flex-col" - , "items-end" - , "leading-snug" - , "mb-4" - , "mr-3" - , "right-0" - , "text-sm" - , "z-50" - ] - - -notificationView : Maybe Color -> Notification Msg -> Html Msg -notificationView extractedBackdropColor notification = - let - kind = - Notifications.kind notification - - options = - Notifications.options notification - - id = - Notifications.id notification - - dismissMsg = - DismissNotification { id = id } - in - brick - [ if options.sticky then - onDoubleTap dismissMsg - - else - onTap dismissMsg - - -- - , rel (String.fromInt id) - - -- - , case kind of - Casual -> - Maybe.unwrap - (class "bg-white-20") - (style "background-color" << Color.toCssString) - extractedBackdropColor - - Error -> - class "bg-base08" - - Success -> - class "bg-base0b" - ] - [ "duration-200" - , "max-w-xs" - , "mt-2" - , "p-4" - , "rounded" - , "text-white-90" - - -- - , ifThenElse options.sticky "cursor-pointer" "cursor-default" - , ifThenElse options.sticky "select-none" "select-auto" - - -- - , if options.wasDismissed then - "transition" - - else - "transition-colors" - - -- - , if options.wasDismissed then - "opacity-0" - - else - "opacity-100" - ] - [ chunk - [ "mt-px", "pt-px" ] - [ contents notification ] - - -- - , if options.sticky && kind /= Casual then - chunk - [ "cursor-pointer" - , "italic" - , "mt-2" - , "opacity-60" - , "select-none" - , "text-xs" - ] - [ text "Double click to dismiss" ] - - else - nothing - ] diff --git a/src/Core/Themes/Sunrise/Playlists/View.elm b/src/Core/Themes/Sunrise/Playlists/View.elm deleted file mode 100644 index bef64870c..000000000 --- a/src/Core/Themes/Sunrise/Playlists/View.elm +++ /dev/null @@ -1,473 +0,0 @@ -module Themes.Sunrise.Playlists.View exposing (view) - -import Chunky exposing (..) -import Color exposing (Color) -import Common -import Html exposing (Html, text) -import Html.Attributes exposing (href, placeholder, style, value) -import Html.Events exposing (onInput, onSubmit) -import List.Extra as List -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import Playlists exposing (..) -import Themes.Sunrise.Kit as Kit exposing (ButtonType(..)) -import Themes.Sunrise.List -import Themes.Sunrise.Navigation as Navigation -import UI.Navigation exposing (..) -import UI.Page as Page -import UI.Playlists.Page exposing (..) -import UI.Types exposing (..) -import Url - - - --- ๐Ÿ—บ - - -view : Page -> List Playlist -> Maybe Playlist -> Maybe { oldName : String, newName : String } -> Maybe Color -> Bool -> Html Msg -view page playlists selectedPlaylist editContext bgColor authMethodSupportsPublicData = - Kit.receptacle - { scrolling = True } - (case page of - Edit encodedName -> - let - filtered = - List.filter - (.autoGenerated >> Maybe.isNothing) - playlists - in - encodedName - |> Url.percentDecode - |> Maybe.andThen (\n -> List.find (.name >> (==) n) filtered) - |> Maybe.map (edit editContext) - |> Maybe.withDefault [ nothing ] - - Index -> - index playlists selectedPlaylist bgColor authMethodSupportsPublicData - - NewCollection -> - newCollection - - NewPlaylist -> - newPlaylist - ) - - - --- INDEX - - -index : List Playlist -> Maybe Playlist -> Maybe Color -> Bool -> List (Html Msg) -index playlists selectedPlaylist bgColor authMethodSupportsPublicData = - let - isSelected playlist = - Maybe.map (\s -> ( s.autoGenerated, s.name )) selectedPlaylist == Just ( playlist.autoGenerated, playlist.name ) - - ( customCollections, customPlaylists ) = - playlists - |> List.filterNot (.autoGenerated >> Maybe.isJust) - |> List.sortBy lowercaseName - |> List.partition .collection - - customPlaylistListItem playlist = - if isSelected playlist then - selectedPlaylistListItem playlist bgColor - - else - { label = text playlist.name - , actions = - List.append - (if authMethodSupportsPublicData then - [ { icon = - if playlist.public then - Icons.public - - else - Icons.public_off - , msg = - Just (\_ -> TogglePlaylistVisibility playlist) - , title = - if playlist.public then - "Make private" - - else - "Make public" - } - ] - - else - [] - ) - [ { icon = Icons.more_vert - , msg = Just (ShowPlaylistListMenu playlist) - , title = "Menu" - } - ] - , msg = Just (ActivatePlaylist playlist) - , isSelected = False - } - - directoryCollections = - playlists - |> List.filter (.autoGenerated >> Maybe.isJust) - |> List.sortBy lowercaseName - |> List.uniqueBy .name - - directoryPlaylistListItem playlist = - if isSelected playlist then - selectedPlaylistListItem playlist bgColor - - else - { label = text playlist.name - , actions = - [ { icon = Icons.more_vert - , msg = Just (ShowPlaylistListMenu playlist) - , title = "Menu" - } - ] - , msg = Just (ActivatePlaylist playlist) - , isSelected = False - } - in - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label Common.backToIndex Hidden - , NavigateToPage Page.Index - ) - , ( Icon Icons.add - , Label "Create a new collection" Shown - , NavigateToPage (Page.Playlists NewCollection) - ) - , ( Icon Icons.add - , Label "Create a new playlist" Shown - , NavigateToPage (Page.Playlists NewPlaylist) - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , if List.isEmpty playlists then - chunk - [ "relative" ] - [ chunk - [ "absolute", "left-0", "top-0" ] - [ Kit.canister [ Kit.h1 "Playlists" ] ] - ] - - else - Kit.canister - [ Kit.h1 "Collections & Playlists" - - -- Intro - -------- - , intro - - -- Custom Collections - --------------------- - , if List.isEmpty customCollections then - nothing - - else - raw - [ category "Your Collections" - , Themes.Sunrise.List.view - Themes.Sunrise.List.Normal - (List.map customPlaylistListItem customCollections) - ] - - -- Custom Playlists - ------------------- - , if List.isEmpty customPlaylists then - nothing - - else - raw - [ category "Your Playlists" - , Themes.Sunrise.List.view - Themes.Sunrise.List.Normal - (List.map customPlaylistListItem customPlaylists) - ] - - -- Directory Collections - ------------------------ - , if List.isEmpty directoryCollections then - nothing - - else - raw - [ category "Autogenerated Directory Collections" - , Themes.Sunrise.List.view - Themes.Sunrise.List.Normal - (List.map directoryPlaylistListItem directoryCollections) - ] - ] - - -- - , if List.isEmpty playlists then - Kit.centeredContent - [ slab - Html.a - [ href (Page.toString <| Page.Playlists NewPlaylist) ] - [ "block" - , "opacity-30" - , "text-inherit" - ] - [ Icons.waves 64 Inherit ] - , slab - Html.a - [ href (Page.toString <| Page.Playlists NewPlaylist) ] - [ "block" - , "leading-normal" - , "mt-2" - , "opacity-40" - , "text-center" - , "text-inherit" - ] - [ text "No playlists found, create one" - , lineBreak - , text "or enable directory collections." - ] - ] - - else - nothing - ] - - -intro : Html Msg -intro = - [ text "Collections and playlists are not tied to the sources of its tracks, " - , text "same goes for favourites. " - , lineBreak - , text "There's also directory collections, which are derived from root directories." - ] - |> raw - |> Kit.intro - - -category : String -> Html Msg -category cat = - chunk - [ "antialiased" - , "font-display" - , "mb-3" - , "mt-10" - , "text-base05" - , "text-xxs" - , "truncate" - , "uppercase" - - -- Dark mode - ------------ - , "dark:text-base04" - ] - [ Kit.inlineIcon Icons.folder - , inline [ "font-bold", "ml-2" ] [ text cat ] - ] - - -selectedPlaylistListItem : Playlist -> Maybe Color -> Themes.Sunrise.List.Item Msg -selectedPlaylistListItem playlist bgColor = - let - selectionColor = - Maybe.withDefault Kit.colors.selection bgColor - in - { label = - brick - [ selectionColor - |> Color.toCssString - |> style "color" - ] - [] - [ text playlist.name ] - , actions = - [ { icon = \size _ -> Icons.check size (Color selectionColor) - , msg = Just (always DeactivatePlaylist) - , title = "Selected playlist" - } - , { icon = Icons.more_vert - , msg = Just (ShowPlaylistListMenu playlist) - , title = "Menu" - } - ] - , msg = Just DeactivatePlaylist - , isSelected = False - } - - - --- NEW - - -newCollection : List (Html Msg) -newCollection = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label "Back to list" Hidden - , NavigateToPage (Page.Playlists Index) - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , [ Kit.h2 "Name your collection" - - -- - , [ onInput SetPlaylistCreationContext - , placeholder "The Classics" - ] - |> Kit.textField - |> chunky [ "max-w-md", "mx-auto" ] - - -- Button - --------- - , chunk - [ "mt-10" ] - [ Kit.button - Normal - Bypass - (text "Create collection") - ] - ] - |> Kit.canisterForm - |> List.singleton - |> Kit.centeredContent - |> List.singleton - |> slab - Html.form - [ onSubmit CreateCollection ] - [ "flex" - , "flex-grow" - , "text-center" - ] - ] - - -newPlaylist : List (Html Msg) -newPlaylist = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label "Back to list" Hidden - , NavigateToPage (Page.Playlists Index) - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , [ Kit.h2 "Name your playlist" - - -- - , [ onInput SetPlaylistCreationContext - , placeholder "Sunset" - ] - |> Kit.textField - |> chunky [ "max-w-md", "mx-auto" ] - - -- Button - --------- - , chunk - [ "mt-10" ] - [ Kit.button - Normal - Bypass - (text "Create playlist") - ] - ] - |> Kit.canisterForm - |> List.singleton - |> Kit.centeredContent - |> List.singleton - |> slab - Html.form - [ onSubmit CreatePlaylist ] - [ "flex" - , "flex-grow" - , "text-center" - ] - ] - - - --- EDIT - - -edit : Maybe { oldName : String, newName : String } -> Playlist -> List (Html Msg) -edit editContext playlist = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label "Back to list" Hidden - , NavigateToPage (Page.Playlists Index) - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , [ Kit.h2 "Name your playlist" - - -- - , [ onInput (SetPlaylistModificationContext playlist.name) - , placeholder "The Classics" - - -- - , case editContext of - Just { oldName, newName } -> - if playlist.name == oldName then - value newName - - else - value playlist.name - - Nothing -> - value playlist.name - ] - |> Kit.textField - |> chunky [ "max-w-md", "mx-auto" ] - - -- Button - --------- - , chunk - [ "mt-10" ] - [ Kit.button - Normal - Bypass - (text "Save") - ] - ] - |> Kit.canisterForm - |> List.singleton - |> Kit.centeredContent - |> List.singleton - |> slab - Html.form - [ onSubmit ModifyPlaylist ] - [ "flex" - , "flex-grow" - , "text-center" - ] - ] - - - --- ๐Ÿ›  - - -lowercaseName : Playlist -> String -lowercaseName = - .name >> String.toLower diff --git a/src/Core/Themes/Sunrise/Queue/View.elm b/src/Core/Themes/Sunrise/Queue/View.elm deleted file mode 100644 index b6ac02ad8..000000000 --- a/src/Core/Themes/Sunrise/Queue/View.elm +++ /dev/null @@ -1,309 +0,0 @@ -module Themes.Sunrise.Queue.View exposing (view) - -import Chunky exposing (..) -import Common -import Conditional exposing (..) -import Html exposing (Html, text) -import Html.Attributes exposing (href) -import Html.Lazy as Lazy -import Icons -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Queue exposing (..) -import Themes.Sunrise.Kit as Kit -import Themes.Sunrise.List -import Themes.Sunrise.Navigation as Navigation -import UI.DnD as DnD -import UI.Navigation exposing (..) -import UI.Page as Page -import UI.Queue.Page as Queue exposing (Page(..)) -import UI.Queue.Types exposing (..) -import UI.Sources.Page -import UI.Types as UI exposing (..) - - - --- ๐Ÿ—บ - - -view : Queue.Page -> UI.Model -> Html UI.Msg -view page model = - case page of - History -> - Lazy.lazy2 - historyView - model.playedPreviously - model.dnd - - Index -> - Lazy.lazy3 - futureView - model.playingNext - model.selectedQueueItem - model.dnd - - - --- ๐Ÿ—บ โ–‘โ–‘ FUTURE - - -futureView : List Queue.Item -> Maybe Queue.Item -> DnD.Model Int -> Html UI.Msg -futureView playingNext selectedQueueItem dnd = - Kit.receptacle - { scrolling = not (DnD.isDragging dnd) } - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label Common.backToIndex Hidden - , NavigateToPage Page.Index - ) - , ( Icon Icons.history - , Label "History" Shown - , NavigateToPage (Page.Queue History) - ) - , ( Icon Icons.clear - , Label "Clear" Shown - , PerformMsg (QueueMsg Clear) - ) - , ( Icon Icons.more_horiz - , Label "Menu" Hidden - , PerformMsgWithMouseEvent (QueueMsg << ShowFutureNavigationMenu) - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , if List.isEmpty playingNext then - chunk - [ "relative" ] - [ chunk - [ "absolute", "left-0", "top-0" ] - [ Kit.canister [ Kit.h1 "Up next" ] ] - ] - - else - Kit.canister - [ Kit.h1 "Up next" - , playingNext - |> List.indexedMap (futureItem selectedQueueItem) - |> Themes.Sunrise.List.view - (Themes.Sunrise.List.Draggable - { model = dnd - , toMsg = UI.DnD - } - ) - |> chunky [ "mt-3" ] - ] - - -- - , if List.isEmpty playingNext then - Kit.centeredContent - [ slab - Html.a - [ href (Page.toString <| Page.Sources UI.Sources.Page.New) ] - [ "text-inherit", "block", "opacity-30" ] - [ Icons.music_note 64 Inherit ] - , slab - Html.a - [ href (Page.toString <| Page.Sources UI.Sources.Page.New) ] - [ "text-inherit", "block", "leading-normal", "mt-2", "opacity-40", "text-center" ] - [ text "Nothing here yet," - , lineBreak - , text "add some music first." - ] - ] - - else - nothing - ] - - -futureItem : Maybe Item -> Int -> Queue.Item -> Themes.Sunrise.List.Item UI.Msg -futureItem selectedQueueItem idx item = - let - ( identifiers, track ) = - item.identifiedTrack - - isSelected = - selectedQueueItem - |> Maybe.map (.identifiedTrack >> Tuple.first >> .indexInList) - |> (==) (Just identifiers.indexInList) - - iconFn = - if item.manualEntry then - identity - - else - Icons.wrapped subtleFutureIconClasses - in - { label = - inline - [ "block" - , "truncate" - - -- - , if item.manualEntry || isSelected then - "text-inherit" - - else - "text-base05" - - -- Dark mode - ------------ - , if item.manualEntry || isSelected then - "dark:text-inherit" - - else - "dark:text-base04" - ] - [ inline - [ "inline-block" - , "mr-2" - , "opacity-60" - , "text-xs" - ] - [ text (String.fromInt <| idx + 1), text "." ] - , case track.tags.artist of - Just artist -> - text (artist ++ " - " ++ track.tags.title) - - Nothing -> - text track.tags.title - ] - , actions = - [ -- Remove - --------- - { icon = - if item.manualEntry then - iconFn Icons.remove_circle_outline - - else - iconFn Icons.not_interested - , msg = - { index = idx, item = item } - |> RemoveItem - |> QueueMsg - |> always - |> Just - , title = - ifThenElse item.manualEntry "Remove" "Ignore" - } - - -- Menu - ------- - , { icon = - iconFn Icons.more_vert - , msg = - Just (QueueMsg << ShowFutureMenu item { index = idx }) - , title = - "Menu" - } - ] - , msg = Just (QueueMsg <| Select item) - , isSelected = isSelected - } - - -subtleFutureIconClasses : List String -subtleFutureIconClasses = - [ "text-gray-500" - - -- Dark mode - ------------ - , "dark:text-base02" - ] - - - --- ๐Ÿ—บ โ–‘โ–‘ HISTORY - - -historyView : List Queue.Item -> DnD.Model Int -> Html UI.Msg -historyView playedPreviously dnd = - Kit.receptacle - { scrolling = not (DnD.isDragging dnd) } - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label Common.backToIndex Hidden - , NavigateToPage Page.Index - ) - , ( Icon Icons.update - , Label "Up next" Shown - , NavigateToPage (Page.Queue Index) - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , if List.isEmpty playedPreviously then - chunk - [ "relative" ] - [ chunk - [ "absolute", "left-0", "top-0" ] - [ Kit.canister [ Kit.h1 "History" ] ] - ] - - else - Kit.canister - [ Kit.h1 "History" - , playedPreviously - |> List.reverse - |> List.indexedMap historyItem - |> Themes.Sunrise.List.view Themes.Sunrise.List.Normal - |> chunky [ "mt-3" ] - ] - - -- - , if List.isEmpty playedPreviously then - Kit.centeredContent - [ chunk - [ "opacity-30" ] - [ Icons.music_note 64 Inherit ] - , chunk - [ "leading-normal", "mt-2", "opacity-40", "text-center" ] - [ text "Nothing here yet," - , lineBreak - , text "play some music first." - ] - ] - - else - nothing - ] - - -historyItem : Int -> Queue.Item -> Themes.Sunrise.List.Item UI.Msg -historyItem idx ({ identifiedTrack } as item) = - let - ( _, track ) = - identifiedTrack - in - { label = - inline - [ "block", "truncate" ] - [ inline - [ "inline-block", "text-xs", "mr-2" ] - [ text (String.fromInt <| idx + 1), text "." ] - , case track.tags.artist of - Just artist -> - text (artist ++ " - " ++ track.tags.title) - - Nothing -> - text track.tags.title - ] - , actions = - [ { icon = Icons.more_vert - , msg = Just (QueueMsg << ShowHistoryMenu item) - , title = "Menu" - } - ] - , msg = Nothing - , isSelected = False - } diff --git a/src/Core/Themes/Sunrise/Settings.elm b/src/Core/Themes/Sunrise/Settings.elm deleted file mode 100644 index 6eaa3bc11..000000000 --- a/src/Core/Themes/Sunrise/Settings.elm +++ /dev/null @@ -1,450 +0,0 @@ -module Themes.Sunrise.Settings exposing (Dependencies, view) - -import Chunky exposing (..) -import Color exposing (Color) -import Common exposing (ServiceWorkerStatus(..)) -import Conditional exposing (ifThenElse) -import DateFormat as Date -import Html exposing (Html, text) -import Html.Attributes as A exposing (..) -import Html.Events as E exposing (onClick) -import Html.Lazy -import LastFm -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import Themes.Sunrise.Kit as Kit -import Themes.Sunrise.Navigation as Navigation -import Themes.Sunrise.Settings.Data -import Themes.Sunrise.Settings.Sync -import Time -import UI.Backdrop as Backdrop exposing (backgroundPositioning) -import UI.Navigation exposing (..) -import UI.Page as Page -import UI.Settings.Page as Settings exposing (..) -import UI.Sources.Types as Sources -import UI.Tracks.Types as Tracks -import UI.Types exposing (Msg(..)) -import User.Layer - - - --- ๐Ÿ—บ - - -type alias Dependencies = - { syncMethod : Maybe User.Layer.Method - , buildTimestamp : Int - , chosenBackgroundImage : Maybe String - , coverSelectionReducesPool : Bool - , currentTimeZone : Time.Zone - , extractedBackdropColor : Maybe Color - , hideDuplicateTracks : Bool - , lastFm : LastFm.Model - , processAutomatically : Bool - , rememberProgress : Bool - , serviceWorkerStatus : ServiceWorkerStatus - , version : String - } - - -view : Settings.Page -> Dependencies -> Html Msg -view page deps = - case page of - Data -> - Themes.Sunrise.Settings.Data.view deps.syncMethod - - Index -> - Kit.receptacle { scrolling = True } (index deps) - - Sync -> - Themes.Sunrise.Settings.Sync.view deps.syncMethod - - - --- INDEX - - -index : Dependencies -> List (Html Msg) -index deps = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.account_circle - , Label "Data & Sync" Shown - , NavigateToPage (Page.Settings Sync) - ) - , ( Icon Icons.brush - , Label "Change theme" Shown - , PerformMsg AssistWithChangingTheme - ) - , ( Icon Icons.help_outline - , Label "Help" Shown - , OpenLinkInNewPage "about/" - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , deps - |> content - |> chunk [ "pb-4" ] - |> List.singleton - |> Kit.canister - ] - - -content : Dependencies -> List (Html Msg) -content deps = - [ ----------------------------------------- - -- Title - ----------------------------------------- - Kit.h1 "Settings" - - ----------------------------------------- - -- Version - ----------------------------------------- - , chunk - [ "mt-6" ] - [ chunk - [ "flex" - , "flex-col" - , "items-center" - , "justify-center" - , "text-base05" - , "text-xs" - - -- Dark mode - ------------ - , "dark:text-base03" - ] - [ slab - Html.img - [ A.src "images/diffuse-dark.svg" - , A.width 160 - ] - [ "dark:hidden" ] - [] - - -- - , slab - Html.img - [ A.src "images/diffuse-light.svg" - , A.width 160 - ] - [ "hidden dark:block" ] - [] - - -- - , chunk - [ "italic", "mt-3", "text-center" ] - [ text "Version " - , text deps.version - , lineBreak - , text "Built on " - , deps.buildTimestamp - |> (*) 1000 - |> Time.millisToPosix - |> Date.format - [ Date.monthNameAbbreviated - , Date.text " " - , Date.dayOfMonthSuffix - , Date.text " " - , Date.yearNumber - , Date.text ", " - , Date.hourMilitaryFixed - , Date.text ":" - , Date.minuteFixed - , Date.text ":" - , Date.secondFixed - ] - deps.currentTimeZone - |> text - - -- - , chunk - [ "not-italic", "mt-3" ] - [ case deps.serviceWorkerStatus of - InstallingInitial -> - inline - [ "inline-flex", "items-center" ] - [ text "Setting up service worker" - , inline [ "ml-1" ] [ Icons.downloading 12 Inherit ] - ] - - InstallingNew -> - inline - [ "inline-flex", "items-center" ] - [ text "Installing new version" - , inline [ "ml-1" ] [ Icons.downloading 12 Inherit ] - ] - - WaitingForActivation -> - inline - [] - [ text "Update available" - , brick - [ Maybe.unwrap - (class "bg-white-20") - (style "background-color" << Color.toCssString) - deps.extractedBackdropColor - - -- - , E.onClick ReloadApp - ] - [ "bg-base06" - , "cursor-pointer" - , "inline-block" - , "leading-none" - , "ml-1" - , "p-1" - , "rounded" - , "text-white" - - -- Dark mode - ------------ - , "dark:bg-base01" - ] - [ text "Reload app" ] - ] - - Activated -> - nothing - ] - ] - ] - ] - - ----------------------------------------- - -- Background - ----------------------------------------- - , chunk - [ "mt-6" ] - [ label "Background Image" - , Html.Lazy.lazy backgroundImage deps.chosenBackgroundImage - ] - - ----------------------------------------- - -- Row 1 - ----------------------------------------- - , chunk - [ "flex", "flex-wrap", "pt-2" ] - [ chunk - [ "w-full", "md:w-1/2" ] - [ label "Downloaded tracks" - , Kit.buttonWithColor - Kit.Gray - Kit.Normal - (TracksMsg Tracks.ClearCache) - (text "Clear cache") - ] - - -- Last.fm - ---------- - , chunk - [ "w-1/2" ] - [ label "Last.fm scrobbling" - - -- - , case ( deps.lastFm.authenticating, deps.lastFm.sessionKey ) of - ( _, Just _ ) -> - Kit.checkbox - { checked = True - , toggleMsg = DisconnectLastFm - } - - ( True, Nothing ) -> - Kit.buttonWithColor - Kit.Gray - Kit.Normal - Bypass - (text "Connecting") - - ( False, Nothing ) -> - Kit.buttonWithColor - Kit.Gray - Kit.Normal - ConnectLastFm - (text "Connect") - ] - ] - - ----------------------------------------- - -- Row 2 - ----------------------------------------- - , chunk - [ "flex", "flex-wrap" ] - [ chunk - [ "w-full", "md:w-1/2" ] - [ label "Hide Duplicates" - , Kit.checkbox - { checked = deps.hideDuplicateTracks - , toggleMsg = TracksMsg Tracks.ToggleHideDuplicates - } - ] - , chunk - [ "w-full", "md:w-1/2" ] - [ label "Process sources automatically" - , Kit.checkbox - { checked = deps.processAutomatically - , toggleMsg = SourcesMsg Sources.ToggleProcessAutomatically - } - ] - ] - - ----------------------------------------- - -- Row 3 - ----------------------------------------- - , chunk - [ "flex", "flex-wrap" ] - [ chunk - [ "w-full", "md:w-1/2" ] - [ label "Remember position on long tracks" - , Kit.checkbox - { checked = deps.rememberProgress - , toggleMsg = ToggleRememberProgress - } - ] - , chunk - [ "w-full", "md:w-1/2" ] - [ label "Cover selection reduces track pool" - , Kit.checkbox - { checked = deps.coverSelectionReducesPool - , toggleMsg = TracksMsg Tracks.ToggleCoverSelectionReducesPool - } - ] - ] - ] - - -label : String -> Html msg -label l = - chunk - [ "mb-3", "mt-6", "pb-px" ] - [ Kit.label [] l ] - - - --- BACKGROUND IMAGE - - -backgroundImage : Maybe String -> Html Msg -backgroundImage chosenBackground = - chunk - [ "flex", "flex-wrap" ] - (List.map - (\( filename, _ ) -> - let - isActive = - chosenBackground == Just filename - in - brick - [ onClick (ChooseBackdrop filename) ] - [ "cursor-pointer" - , "h-0" - , "overflow-hidden" - , "pt-1/8" - , "relative" - , "w-1/5" - - -- - , "md:pt-1/16" - , "md:w-1/10" - ] - [ if isActive then - chunk - [ "absolute" - , "bg-base04" - , "inset-0" - , "mb-1" - , "mr-1" - , "rounded-sm" - , "z-10" - - -- - , "sm:mb-2" - , "sm:mr-2" - - -- - , "md:mb-1" - , "md:mr-1" - ] - [] - - else - nothing - - -- - , brick - [ backgroundPositioning filename - - -- - , ")" - |> String.append filename - |> String.append "url(images/Background/Thumbnails/" - |> style "background-image" - ] - [ "absolute" - , "bg-cover" - , "inset-0" - , "mb-1" - , "mr-1" - , "rounded-sm" - , "z-20" - - -- - , "sm:mb-2" - , "sm:mr-2" - - -- - , "md:mb-1" - , "md:mr-1" - - -- - , ifThenElse isActive "opacity-20" "opacity-100" - ] - [] - - -- - , if isActive then - chunk - [ "absolute" - , "inset-0" - , "flex" - , "font-semibold" - , "items-center" - , "justify-center" - , "leading-snug" - , "mb-1" - , "mr-1" - , "px-2" - , "text-center" - , "text-white" - , "text-xs" - , "z-30" - - -- - , "sm:mb-2" - , "sm:mr-2" - - -- - , "md:mb-1" - , "md:mr-1" - - -- Dark mode - ------------ - , "dark:text-base07" - ] - [ chunk - [ "mt-px" ] - [ Icons.check 16 Inherit ] - ] - - else - nothing - ] - ) - Backdrop.options - ) diff --git a/src/Core/Themes/Sunrise/Settings/Common.elm b/src/Core/Themes/Sunrise/Settings/Common.elm deleted file mode 100644 index 9c763cb26..000000000 --- a/src/Core/Themes/Sunrise/Settings/Common.elm +++ /dev/null @@ -1,20 +0,0 @@ -module Themes.Sunrise.Settings.Common exposing (..) - -import Material.Icons.Round as Icons -import UI.Navigation exposing (..) -import UI.Syncing.Types as Syncing -import UI.Types - - - --- SYNCING - - -changePassphrase method = - ( Icon Icons.lock - , Label "Change Passphrase" Shown - , method - |> Syncing.ShowUpdateEncryptionKeyScreen - |> UI.Types.SyncingMsg - |> PerformMsg - ) diff --git a/src/Core/Themes/Sunrise/Settings/Data.elm b/src/Core/Themes/Sunrise/Settings/Data.elm deleted file mode 100644 index 743d11427..000000000 --- a/src/Core/Themes/Sunrise/Settings/Data.elm +++ /dev/null @@ -1,89 +0,0 @@ -module Themes.Sunrise.Settings.Data exposing (view) - -import Chunky exposing (..) -import Html exposing (Html, text) -import Material.Icons.Round as Icons -import Themes.Sunrise.Kit as Kit exposing (ButtonColor(..), ButtonType(..)) -import Themes.Sunrise.Navigation as Navigation -import Themes.Sunrise.Settings.Common exposing (changePassphrase) -import UI.Navigation exposing (..) -import UI.Page as Page -import UI.Settings.Page exposing (Page(..)) -import UI.Types exposing (Msg(..)) -import User.Layer exposing (Method(..)) - - - --- ๐Ÿ—บ - - -view : Maybe Method -> Html Msg -view activeMethod = - Kit.receptacle - { scrolling = True } - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - (case activeMethod of - Just (Dropbox d) -> - [ changePassphrase (Dropbox d) ] - - Just (Ipfs i) -> - [ changePassphrase (Ipfs i) ] - - Just (RemoteStorage r) -> - [ changePassphrase (RemoteStorage r) ] - - Nothing -> - [] - ) - |> List.append - [ ( Icon Icons.arrow_back - , Label "Settings" Hidden - , NavigateToPage (Page.Settings Index) - ) - , ( Icon Icons.account_circle - , Label "Storage Service" Shown - , NavigateToPage (Page.Settings Sync) - ) - ] - |> Navigation.local - - ----------------------------------------- - -- Content - ----------------------------------------- - , chunk - [ "relative" ] - [ chunk - [ "absolute", "left-0", "top-0" ] - [ Kit.canister [ Kit.h1 "Data Backup" ] ] - ] - - -- - , Kit.focusScreen - { icon = Icons.archive - , iconHref = Nothing - , text = - "You can download a snapshot of your user data, or recover an account by uploading a snapshot." - |> text - |> List.singleton - |> chunk [ "max-w-sm" ] - |> List.singleton - , textHref = Nothing - } - [ chunk - [ "flex", "space-x-2.5" ] - [ Kit.button - Normal - RequestImport - (text "Import snapshot") - - -- - , Kit.buttonWithColor - Accent - Filled - Export - (text "Download snapshot") - ] - ] - ] diff --git a/src/Core/Themes/Sunrise/Settings/Sync.elm b/src/Core/Themes/Sunrise/Settings/Sync.elm deleted file mode 100644 index 28969f2b1..000000000 --- a/src/Core/Themes/Sunrise/Settings/Sync.elm +++ /dev/null @@ -1,190 +0,0 @@ -module Themes.Sunrise.Settings.Sync exposing (view) - -import Chunky exposing (..) -import Html exposing (Html, text) -import Material.Icons.Round as Icons -import Maybe.Extra as Maybe -import Themes.Sunrise.Kit as Kit -import Themes.Sunrise.List -import Themes.Sunrise.Navigation as Navigation -import Themes.Sunrise.Settings.Common exposing (changePassphrase) -import UI.Navigation exposing (..) -import UI.Page as Page -import UI.Settings.Page exposing (Page(..)) -import UI.Svg.Elements -import UI.Syncing.Common exposing (startDropbox, startIpfs, startRemoteStorage) -import UI.Syncing.Types as Syncing -import UI.Types exposing (Msg(..)) -import User.Layer exposing (Method(..), dropboxMethod, ipfsMethod, methodName, remoteStorageMethod) - - - --- ๐Ÿ—บ - - -view : Maybe Method -> Html Msg -view activeMethod = - Kit.receptacle - { scrolling = True } - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - (case activeMethod of - Just (Dropbox d) -> - [ changePassphrase (Dropbox d) ] - - Just (Ipfs i) -> - [ changePassphrase (Ipfs i) ] - - Just (RemoteStorage r) -> - [ changePassphrase (RemoteStorage r) ] - - Nothing -> - [] - ) - |> List.append - [ ( Icon Icons.arrow_back - , Label "Settings" Hidden - , NavigateToPage (Page.Settings Index) - ) - , ( Icon Icons.archive - , Label "Data Backup" Shown - , NavigateToPage (Page.Settings Data) - ) - ] - |> Navigation.local - - ----------------------------------------- - -- Content - ----------------------------------------- - , chunk - [ "relative" ] - [ chunk - [ "absolute", "left-0", "top-0" ] - [ Kit.canister [ Kit.h1 "Storage Service" ] ] - ] - - -- - , Kit.focusScreen - { icon = Icons.account_circle - , iconHref = Nothing - , text = - "By default, your playlists, favourites and other data are stored locally on your device. To ensure it is backed up, and to keep it in sync across all your connected devices, choose one of the following storage services. You can switch services at any point." - |> text - |> List.singleton - |> chunk [ "max-w-md" ] - |> List.singleton - , textHref = Nothing - } - [ [ dropboxMethod - , remoteStorageMethod - , ipfsMethod - ] - |> List.map (methodView activeMethod) - |> Themes.Sunrise.List.view Themes.Sunrise.List.Normal - |> List.singleton - |> chunk [ "max-w-full", "w-96" ] - ] - ] - - -methodInfoAction : Bool -> Maybe Method -> Method -> Themes.Sunrise.List.Action Msg -methodInfoAction isSelected activeMethod method = - { icon = - \a b -> - inline - [ opacity isSelected activeMethod ] - [ Icons.help a b ] - , msg = - case method of - Dropbox _ -> - Just (\_ -> OpenUrlOnNewPage "https://dropbox.com") - - RemoteStorage _ -> - Just (\_ -> OpenUrlOnNewPage "https://remotestorage.io") - - Ipfs _ -> - Just (\_ -> OpenUrlOnNewPage "https://ipfs.io") - , title = - "Learn more about " ++ methodName method - } - - -methodView activeMethod method = - let - isSelected = - case ( activeMethod, method ) of - ( Just (Dropbox _), Dropbox _ ) -> - True - - ( Just (RemoteStorage _), RemoteStorage _ ) -> - True - - ( Just (Ipfs _), Ipfs _ ) -> - True - - _ -> - False - - label = - methodLabel activeMethod method isSelected - in - { label = - case method of - Dropbox _ -> - label UI.Svg.Elements.dropboxLogo - - RemoteStorage _ -> - label UI.Svg.Elements.remoteStorageLogo - - Ipfs _ -> - label UI.Svg.Elements.ipfsLogo - , actions = - [ if isSelected then - { icon = Icons.cancel - , msg = Just (\_ -> SyncingMsg Syncing.StopSync) - , title = "Stop syncing with " ++ methodName method - } - - else - methodInfoAction isSelected activeMethod method - ] - , msg = - if isSelected then - Just (SyncingMsg Syncing.StopSync) - - else if Maybe.isJust activeMethod then - Nothing - - else - case method of - Dropbox _ -> - Just startDropbox - - RemoteStorage _ -> - Just startRemoteStorage - - Ipfs _ -> - Just startIpfs - , isSelected = - isSelected - } - - -methodLabel activeMethod method isSelected icon = - inline - [ "flex", "items-center", opacity isSelected activeMethod ] - [ inline [ "inline-block", "mr-3" ] [ Html.map never (icon 15) ] - , Html.text (methodName method) - ] - - -opacity isSelected activeMethod = - if isSelected then - "opacity-100" - - else if Maybe.isJust activeMethod then - "opacity-50" - - else - "opacity-100" diff --git a/src/Core/Themes/Sunrise/Sources/Form.elm b/src/Core/Themes/Sunrise/Sources/Form.elm deleted file mode 100644 index 2cf19ac45..000000000 --- a/src/Core/Themes/Sunrise/Sources/Form.elm +++ /dev/null @@ -1,536 +0,0 @@ -module Themes.Sunrise.Sources.Form exposing (..) - -import Chunky exposing (..) -import Common exposing (boolFromString, boolToString) -import Conditional exposing (..) -import Dict.Ext as Dict -import Html exposing (Html, text) -import Html.Attributes as A exposing (attribute, for, name, placeholder, required, selected, type_, value) -import Html.Events exposing (onInput, onSubmit) -import List.Extra as List -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Sources exposing (..) -import Sources.Services as Services -import Themes.Sunrise.Kit as Kit exposing (ButtonType(..), select) -import Themes.Sunrise.Navigation as Navigation -import UI.Navigation exposing (..) -import UI.Page as Page -import UI.Sources.Page as Sources -import UI.Sources.Types exposing (..) - - - --- NEW - - -type alias Arguments = - { onboarding : Bool } - - -new : Arguments -> Form -> List (Html Msg) -new args model = - case model.step of - Where -> - newWhere args model - - How -> - newHow model - - By -> - newBy model - - -newWhere : Arguments -> Form -> List (Html Msg) -newWhere { onboarding } { context } = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label "Back to list" Hidden - -- - , if onboarding then - NavigateToPage Page.Index - - else - NavigateToPage (Page.Sources Sources.Index) - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , (\h -> - form TakeStep - [ Kit.canisterForm h ] - ) - [ Kit.h2 "Where is your music stored?" - - -- Dropdown - ----------- - , let - contextServiceKey = - Services.typeToKey context.service - in - Services.labels - |> List.map - (\( k, l ) -> - Html.option - [ value k, selected (contextServiceKey == k) ] - [ text l ] - ) - |> select SelectService - - -- Button - --------- - , chunk - [ "mt-10" ] - [ Kit.button - IconOnly - Bypass - (Icons.arrow_forward 17 Inherit) - ] - ] - ] - - -newHow : Form -> List (Html Msg) -newHow { context } = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label "Take a step back" Shown - , PerformMsg TakeStepBackwards - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , (\h -> - form TakeStep - [ chunk - [ "text-left", "w-full" ] - [ Kit.canister h ] - ] - ) - [ Kit.h3 "Where exactly?" - - -- Note - ------- - , note context.service - - -- Fields - --------- - , let - properties = - Services.properties context.service - - dividingPoint = - toFloat (List.length properties) / 2 - - ( listA, listB ) = - List.splitAt (ceiling dividingPoint) properties - in - chunk - [ "flex", "pt-3" ] - [ chunk - [ "flex-grow", "pr-4" ] - (List.map (renderProperty context) listA) - , chunk - [ "flex-grow", "pl-4" ] - (List.map (renderProperty context) listB) - ] - - -- Button - --------- - , chunk - [ "mt-3", "text-center" ] - [ Kit.button - IconOnly - Bypass - (Icons.arrow_forward 17 Inherit) - ] - ] - ] - - -howNote : List (Html Msg) -> Html Msg -howNote = - chunk - [ "text-sm" - , "italic" - , "leading-normal" - , "max-w-lg" - , "mb-8" - ] - - -newBy : Form -> List (Html Msg) -newBy { context } = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label "Take a step back" Shown - , PerformMsg TakeStepBackwards - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , (\h -> - form AddSourceUsingForm - [ Kit.canisterForm h ] - ) - [ Kit.h2 "One last thing" - , Kit.label [] "What are we going to call this source?" - - -- Input - -------- - , let - nameValue = - Dict.fetch "name" "" context.data - in - chunk - [ "flex" - , "max-w-md" - , "mt-8" - , "mx-auto" - , "justify-center" - , "w-full" - ] - [ Kit.textField - [ name "name" - , onInput (SetFormData "name") - , value nameValue - ] - ] - - -- Note - ------- - , chunk - [ "mt-16" ] - (case context.service of - AmazonS3 -> - corsWarning "CORS__S3" - - AzureBlob -> - corsWarning "CORS__Azure" - - AzureFile -> - corsWarning "CORS__Azure" - - Dropbox -> - [] - - Google -> - [] - - Ipfs -> - corsWarning "CORS__IPFS" - - WebDav -> - corsWarning "CORS__WebDAV" - ) - - -- Button - --------- - , Kit.button - Normal - Bypass - (text "Add source") - ] - ] - - -corsWarning : String -> List (Html Msg) -corsWarning id = - [ chunk - [ "text-sm", "flex", "items-center", "justify-center", "leading-snug", "opacity-50" ] - [ Kit.inlineIcon Icons.warning - , inline - [ "font-semibold" ] - [ text "Make sure CORS is enabled" ] - ] - , chunk - [ "text-sm", "leading-snug", "mb-8", "mt-1", "opacity-50" ] - [ text "You can find the instructions over " - , Kit.link { label = "here", url = "about/cors/#" ++ id } - ] - ] - - - --- EDIT - - -edit : Form -> List (Html Msg) -edit { context } = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label "Go Back" Shown - , PerformMsg ReturnToIndex - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , (\h -> - form EditSourceUsingForm - [ chunk - [ "text-left", "w-full" ] - [ Kit.canister h ] - ] - ) - [ Kit.h3 "Edit source" - - -- Note - ------- - , note context.service - - -- Fields - --------- - , let - properties = - Services.properties context.service - - dividingPoint = - toFloat (List.length properties) / 2 - - ( listA, listB ) = - List.splitAt (ceiling dividingPoint) properties - in - chunk - [ "flex", "pt-3" ] - [ chunk - [ "flex-grow", "pr-4" ] - (List.map (renderProperty context) listA) - , chunk - [ "flex-grow", "pl-4" ] - (List.map (renderProperty context) listB) - ] - - -- Button - --------- - , chunk - [ "mt-3", "text-center" ] - [ Kit.button - Normal - Bypass - (text "Save") - ] - ] - ] - - - --- PROPERTIES - - -renderProperty : Source -> Property -> Html Msg -renderProperty context property = - chunk - [ "mb-8" ] - [ Kit.label - [ for property.key ] - property.label - - -- - , if - (property.placeholder == boolToString True) - || (property.placeholder == boolToString False) - then - let - bool = - context.data - |> Dict.fetch property.key property.placeholder - |> boolFromString - in - chunk - [ "mt-2", "pt-1" ] - [ Kit.checkbox - { checked = bool - , toggleMsg = - bool - |> not - |> boolToString - |> SetFormData property.key - } - ] - - else - Kit.textField - [ name property.key - , onInput (SetFormData property.key) - , placeholder property.placeholder - , required (property.label |> String.toLower |> String.contains "optional" |> not) - , type_ (ifThenElse property.password "password" "text") - , value (Dict.fetch property.key "" context.data) - - -- - , attribute "spellcheck" "false" - ] - ] - - - --- โš—๏ธ - - -form : Msg -> List (Html Msg) -> Html Msg -form msg html = - slab - Html.form - [ onSubmit msg ] - [ "flex" - , "flex-grow" - , "flex-shrink-0" - , "text-center" - ] - [ Kit.centeredContent html ] - - -note : Service -> Html Msg -note service = - case service of - AmazonS3 -> - nothing - - AzureBlob -> - nothing - - AzureFile -> - nothing - - Dropbox -> - howNote - [ inline - [ "font-semibold" ] - [ text "If you don't know what any of this is, " - , text "continue to the next screen." - ] - , text " Changing the app key allows you to use your own Dropbox app." - , text " Also, make sure you verified your email address on Dropbox," - , text " or this might not work." - ] - - Google -> - howNote - [ inline - [ "font-semibold" ] - [ text "If you don't know what any of this is, " - , text "continue to the next screen." - ] - , text " Changing the client stuff allows you to use your own Google OAuth client." - , text " Disclaimer: " - , text "The Google Drive API is fairly slow and limited, " - , text "consider using a different service." - ] - - Ipfs -> - howNote - [ inline - [ "font-semibold" ] - [ text "Diffuse will use the ipfs.io gateway by default" ] - , text "." - , lineBreak - , inline - [] - [ text "There are also " - , Html.a - [ A.href "https://ipfs.github.io/public-gateway-checker/" - , A.class "underline" - , A.target "_blank" - ] - [ text "other public gateways" ] - , text " you can choose from." - ] - , lineBreak - , text "If you would like to use another gateway, please provide it below." - ] - - WebDav -> - howNote - [ inline - [ "font-semibold" ] - [ Kit.inlineIcon Icons.warning - , text "This app requires a proper implementation of " - , Kit.link - { label = "CORS" - , url = "https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" - } - , text " on the server side." - ] - , lineBreak - , text " WebDAV servers usually don't implement" - , text " CORS properly, if at all." - , lineBreak - , text " Some servers, like " - , Kit.link - { label = "this one" - , url = "https://github.com/hacdias/webdav" - } - , text ", do. You can find the configuration for that server " - , Kit.link - { label = "here" - , url = "about/cors/#CORS__WebDAV" - } - , text "." - ] - - - --- RENAME - - -rename : Form -> List (Html Msg) -rename { context } = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - Navigation.local - [ ( Icon Icons.arrow_back - , Label "Go Back" Shown - , PerformMsg ReturnToIndex - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , (\h -> - form RenameSourceUsingForm - [ Kit.canisterForm h ] - ) - [ Kit.h2 "Name your source" - - -- Input - -------- - , [ name "name" - , onInput (SetFormData "name") - , value (Dict.fetch "name" "" context.data) - ] - |> Kit.textField - |> chunky [ "max-w-md", "mx-auto" ] - - -- Button - --------- - , chunk - [ "mt-10" ] - [ Kit.button - Normal - Bypass - (text "Save") - ] - ] - ] diff --git a/src/Core/Themes/Sunrise/Sources/View.elm b/src/Core/Themes/Sunrise/Sources/View.elm deleted file mode 100644 index 4c21f7ee2..000000000 --- a/src/Core/Themes/Sunrise/Sources/View.elm +++ /dev/null @@ -1,292 +0,0 @@ -module Themes.Sunrise.Sources.View exposing (view) - -import Chunky exposing (..) -import Conditional exposing (ifThenElse) -import Dict.Ext as Dict -import Html exposing (Html, text) -import Html.Attributes exposing (href) -import Html.Lazy as Lazy -import List.Extra as List -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Sources exposing (..) -import Themes.Sunrise.Kit as Kit -import Themes.Sunrise.List -import Themes.Sunrise.Navigation as Navigation -import Themes.Sunrise.Sources.Form as Form -import UI.Navigation exposing (..) -import UI.Page as Page -import UI.Sources.Page as Sources exposing (..) -import UI.Sources.Types exposing (Msg(..)) -import UI.Types as UI exposing (Model) - - - --- ๐ŸŒณ - - -type alias PartialModel = - { processingContext : List ( String, Float ) - , processingError : Maybe { error : String, sourceId : String } - , sources : List Source - } - - - --- ๐Ÿ—บ - - -view : Sources.Page -> Model -> Html UI.Msg -view page model = - Html.map UI.SourcesMsg - (case page of - Index -> - Lazy.lazy2 - (\a b -> receptacle <| index a b) - (List.length model.tracks.untouched) - { processingContext = model.processingContext - , processingError = model.processingError - , sources = model.sources - } - - Edit _ -> - lazyForm model <| Form.edit - - New -> - lazyForm model <| Form.new { onboarding = False } - - NewOnboarding -> - lazyForm model <| Form.new { onboarding = True } - - NewThroughRedirect _ _ -> - lazyForm model <| Form.new { onboarding = False } - - Rename _ -> - lazyForm model <| Form.rename - ) - - -lazyForm model formView = - Lazy.lazy (formView >> receptacle) model.sourceForm - - -receptacle = - Kit.receptacle { scrolling = True } - - - --- INDEX - - -index : Int -> PartialModel -> List (Html Msg) -index amountOfTracks model = - [ ----------------------------------------- - -- Navigation - ----------------------------------------- - if List.isEmpty model.sources then - Navigation.local - [ ( Icon Icons.add - , Label "Add a new source" Shown - , NavigateToPage (Page.Sources New) - ) - ] - - else - Navigation.local - [ ( Icon Icons.add - , Label "Add a new source" Shown - , NavigateToPage (Page.Sources New) - ) - - -- Process - ---------- - , if List.isEmpty model.processingContext then - ( Icon Icons.sync - , Label "Process sources" Shown - , PerformMsg Process - ) - - else - ( Icon Icons.sync - , Label "Stop processing ..." Shown - , PerformMsg StopProcessing - ) - ] - - ----------------------------------------- - -- Content - ----------------------------------------- - , if List.isEmpty model.sources then - chunk - [ "relative" ] - [ chunk - [ "absolute", "left-0", "top-0" ] - [ Kit.canister [ Kit.h1 "Sources" ] ] - ] - - else - Kit.canister - [ Kit.h1 "Sources" - - -- Intro - -------- - , intro amountOfTracks - - -- List - ------- - , model.sources - |> List.sortBy - lowercaseName - |> List.map - (\source -> - { label = Html.text (Dict.fetch "name" "" source.data) - , actions = sourceActions model.processingContext model.processingError source - , msg = Nothing - , isSelected = False - } - ) - |> Themes.Sunrise.List.view Themes.Sunrise.List.Normal - ] - - -- - , if List.isEmpty model.sources then - Kit.centeredContent - [ slab - Html.a - [ href (Page.toString <| Page.Sources New) ] - [ "block" - , "opacity-30" - , "text-inherit" - ] - [ Icons.music_note 64 Inherit ] - , slab - Html.a - [ href (Page.toString <| Page.Sources New) ] - [ "block" - , "leading-normal" - , "mt-2" - , "opacity-40" - , "text-center" - , "text-inherit" - ] - [ text "A source is a place where music is stored," - , lineBreak - , text "add one so you can play some music " - , inline - [ "align-middle", "inline-block", "-mt-px" ] - [ Icons.add 14 Inherit ] - ] - ] - - else - nothing - ] - - -intro : Int -> Html Msg -intro amountOfTracks = - [ text "A source is a place where your music is stored." - , lineBreak - , text "By connecting a source, the application will scan it and keep a list of all the music in it." - , lineBreak - , text "You currently have " - , text (String.fromInt amountOfTracks) - , text " " - , text (ifThenElse (amountOfTracks == 1) "track" "tracks") - , text " in your collection." - ] - |> raw - |> Kit.intro - - -sourceActions : List ( String, Float ) -> Maybe { error : String, sourceId : String } -> Source -> List (Themes.Sunrise.List.Action Msg) -sourceActions processingContext processingError source = - let - processIndex = - List.findIndex (Tuple.first >> (==) source.id) processingContext - - process = - Maybe.andThen (\idx -> List.getAt idx processingContext) processIndex - in - List.append - (case ( process, processingError ) of - ( Just ( _, progress ), _ ) -> - [ { icon = - \_ _ -> - if progress < 0.05 then - inline - [ "inline-block", "opacity-70", "px-1" ] - [ case processIndex of - Just 0 -> - Html.text "Listing" - - _ -> - Html.text "Waiting" - ] - - else - progress - |> (*) 100 - |> round - |> String.fromInt - |> (\s -> s ++ "%") - |> Html.text - |> List.singleton - |> inline [ "inline-block", "opacity-70", "px-1" ] - , msg = Nothing - , title = "" - } - , { icon = Icons.sync - , msg = Nothing - , title = "Currently processing" - } - ] - - ( Nothing, Just { error, sourceId } ) -> - if sourceId == source.id then - [ { icon = \size _ -> Icons.error_outline size (Color Kit.colors.error) - , msg = Nothing - , title = error - } - ] - - else - [] - - _ -> - [] - ) - [ { icon = - if source.enabled then - Icons.check - - else - Icons.block - , msg = - { sourceId = source.id } - |> ToggleActivation - |> always - |> Just - , title = - if source.enabled then - "Enabled (click to disable)" - - else - "Disabled (click to enable)" - } - - -- - , { icon = Icons.more_vert - , msg = Just (SourceContextMenu source) - , title = "Menu" - } - ] - - - --- ๐Ÿ›  - - -lowercaseName : Source -> String -lowercaseName = - .data >> Dict.fetch "name" "" >> String.toLower diff --git a/src/Core/Themes/Sunrise/Syncing/View.elm b/src/Core/Themes/Sunrise/Syncing/View.elm deleted file mode 100644 index b2843ca80..000000000 --- a/src/Core/Themes/Sunrise/Syncing/View.elm +++ /dev/null @@ -1,187 +0,0 @@ -module Themes.Sunrise.Syncing.View exposing (view) - -import Chunky exposing (..) -import Html exposing (Html, text) -import Html.Attributes as A exposing (attribute, placeholder, style, value) -import Html.Events as E exposing (onSubmit) -import Html.Events.Extra exposing (onClickStopPropagation) -import Html.Extra as Html -import Html.Lazy as Lazy -import Json.Decode -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Themes.Sunrise.Kit as Kit -import UI.Syncing.Types as Syncing exposing (..) -import UI.Types as UI exposing (..) - - - --- ๐Ÿ—บ - - -view : Model -> Html UI.Msg -view = - Html.map SyncingMsg << Lazy.lazy view_ << .syncing - - -view_ : State -> Html Syncing.Msg -view_ state = - case state of - InputScreen _ opts -> - Kit.receptacle - { scrolling = False } - [ inputScreen opts ] - - NewEncryptionKeyScreen method pass -> - Kit.receptacle - { scrolling = False } - [ encryptionKeyScreen - { ableToCancel = False - , withEncryption = ActivateSyncWithPassphrase method (Maybe.withDefault "" pass) - , withoutEncryption = ActivateSync method - } - ] - - UpdateEncryptionKeyScreen method pass -> - Kit.receptacle - { scrolling = False } - [ encryptionKeyScreen - { ableToCancel = True - , withEncryption = UpdateEncryptionKey method (Maybe.withDefault "" pass) - , withoutEncryption = RemoveEncryptionKey method - } - ] - - _ -> - Html.nothing - - - --- ENCRYPTION KEY - - -encryptionKeyScreen : { ableToCancel : Bool, withEncryption : Syncing.Msg, withoutEncryption : Syncing.Msg } -> Html Syncing.Msg -encryptionKeyScreen { ableToCancel, withEncryption, withoutEncryption } = - Kit.focusScreen - { icon = Icons.lock - , iconHref = Nothing - , text = [ text "Optional passphrase to encrypt/decrypt your data" ] - , textHref = Nothing - } - [ slab - Html.form - [ onSubmit withEncryption ] - [ "flex" - , "flex-col" - , "max-w-xs" - , "px-3" - , "w-screen" - - -- - , "sm:px-0" - ] - [ Kit.textArea - [ attribute "autocapitalize" "none" - , attribute "autocomplete" "off" - , attribute "autocorrect" "off" - , attribute "rows" "4" - , attribute "spellcheck" "false" - - -- - , placeholder "anQLS9Usw24gxUi11IgVBg76z8SCWZgLKkoWIeJ1ClVmBHLRlaiA0CtvONVAMGritbgd3U45cPTxrhFU0WXaOAa8pVt186KyEccfUNyAq97" - - -- - , style "-webkit-text-security" "disc" - - -- - , A.class "shadow" - , E.onInput KeepPassphraseInMemory - ] - , chunk - [ "flex" - , "items-stretch" - , "space-x-2.5" - ] - [ Kit.buttonWithOptions - Html.button - [ A.class "flex-1" ] - Kit.Gray - Kit.Filled - (Just Syncing.Bypass) - (text "Continue") - - -- - , if ableToCancel then - Kit.buttonWithOptions - Html.button - [ A.class "flex-1" - , E.stopPropagationOn "click" (Json.Decode.succeed ( CancelInput, True )) - ] - Kit.Gray - Kit.Normal - Nothing - (text "Cancel") - - else - nothing - ] - , brick - [ onClickStopPropagation withoutEncryption ] - [ "cursor-pointer" - , "flex" - , "items-center" - , "justify-center" - , "leading-snug" - , "mt-3" - , "opacity-50" - , "text-xs" - ] - [ inline [ "inline-block", "leading-none", "mr-2" ] [ Icons.warning 13 Inherit ] - , text "Continue without encryption" - ] - ] - ] - - - --- INPUT SCREEN - - -inputScreen : Question -> Html Syncing.Msg -inputScreen question = - Kit.focusScreen - { icon = question.icon - , iconHref = Nothing - , text = [ Kit.askForInput question.question ] - , textHref = Nothing - } - [ slab - Html.form - [ onSubmit ConfirmInput ] - [ "flex" - , "flex-col" - , "max-w-xs" - , "px-3" - , "w-screen" - - -- - , "sm:px-0" - ] - [ Kit.textFieldAlt - [ attribute "autocapitalize" "off" - , placeholder question.placeholder - , A.class "shadow" - , E.onInput Input - , value question.value - ] - , Kit.button - Kit.Filled - Syncing.Bypass - (text "Continue") - ] - ] - - - --- SPEECH BUBBLE --- ๐Ÿ–ผ diff --git a/src/Core/Themes/Sunrise/Theme.elm b/src/Core/Themes/Sunrise/Theme.elm deleted file mode 100644 index 9bce3a782..000000000 --- a/src/Core/Themes/Sunrise/Theme.elm +++ /dev/null @@ -1,292 +0,0 @@ -module Themes.Sunrise.Theme exposing (theme) - -import Alfred exposing (Alfred) -import Chunky exposing (..) -import Common exposing (Switch(..)) -import Conditional exposing (..) -import ContextMenu exposing (ContextMenu) -import Html exposing (Html, section) -import Html.Attributes exposing (class, style) -import Html.Events exposing (on) -import Html.Lazy as Lazy -import Json.Decode -import Material.Icons as Icons -import Maybe.Extra as Maybe -import Theme exposing (Theme) -import Themes.Sunrise.Alfred.View as Alfred -import Themes.Sunrise.Console -import Themes.Sunrise.ContextMenu as ContextMenu -import Themes.Sunrise.Navigation as Navigation -import Themes.Sunrise.Notifications as Notifications -import Themes.Sunrise.Playlists.View as Playlists -import Themes.Sunrise.Queue.View as Queue -import Themes.Sunrise.Settings as Settings -import Themes.Sunrise.Sources.View as Sources -import Themes.Sunrise.Syncing.View as Syncing -import Themes.Sunrise.Tracks.View as Tracks -import UI.Backdrop as Backdrop -import UI.Page as Page -import UI.Settings.Page -import UI.Sources.Page -import UI.Syncing.Common as Syncing -import UI.Types exposing (..) -import User.Layer - - -theme : Theme Msg Model -theme = - { id = "sunrise" - , title = "Sunrise" - , icon = Icons.wb_sunny - , view = view - } - - - --- ใŠ™๏ธ - - -view : Model -> Html Msg -view model = - section - (if Maybe.isJust model.contextMenu || Maybe.isJust model.alfred then - [ on "tap" (Json.Decode.succeed HideOverlay) ] - - else if model.showVolumeSlider then - [ on "tap" (Json.Decode.succeed <| ToggleVolumeSlider Off) ] - - else if model.isDragging then - [ class "dragging-something" - , on "mouseup" (Json.Decode.succeed StoppedDragging) - , on "touchcancel" (Json.Decode.succeed StoppedDragging) - , on "touchend" (Json.Decode.succeed StoppedDragging) - ] - - else if Maybe.isJust model.selectedQueueItem then - [ on "tap" (Json.Decode.succeed RemoveQueueSelection) ] - - else if not (List.isEmpty model.selectedTrackIndexes) then - [ on "tap" (Json.Decode.succeed RemoveTrackSelection) ] - - else - [] - ) - [ ----------------------------------------- - -- Alfred - ----------------------------------------- - Lazy.lazy2 Alfred.view model.alfred model.extractedBackdropColor - - ----------------------------------------- - -- Backdrop - ----------------------------------------- - , Lazy.lazy Backdrop.view model - - ----------------------------------------- - -- Context Menu - ----------------------------------------- - , Lazy.lazy2 ContextMenu.view model.viewport.width model.contextMenu - - ----------------------------------------- - -- Notifications - ----------------------------------------- - , Lazy.lazy2 Notifications.view model.extractedBackdropColor model.notifications - - ----------------------------------------- - -- Overlay - ----------------------------------------- - , Lazy.lazy2 overlay model.alfred model.contextMenu - - ----------------------------------------- - -- Content - ----------------------------------------- - , content - { justifyCenter = False - , scrolling = not model.isDragging - } - (defaultScreen model) - ] - - -defaultScreen : Model -> List (Html Msg) -defaultScreen model = - [ Lazy.lazy2 - (Navigation.global - [ ( Page.Index, "Tracks" ) - , ( Page.Sources UI.Sources.Page.Index, "Sources" ) - , ( Page.Settings UI.Settings.Page.Index, "Settings" ) - ] - ) - model.alfred - model.page - - ----------------------------------------- - -- Main - ----------------------------------------- - , vessel - [ Tracks.view model - - -- Pages - -------- - , case model.page of - Page.Index -> - nothing - - Page.Playlists subPage -> - Lazy.lazy6 - Playlists.view - subPage - model.playlists - model.selectedPlaylist - model.editPlaylistContext - model.extractedBackdropColor - (model.syncing - |> Syncing.extractMethod - |> Maybe.unwrap False User.Layer.methodSupportsPublicData - ) - - Page.Queue subPage -> - Queue.view subPage model - - Page.Settings subPage -> - Lazy.lazy2 Settings.view - subPage - { buildTimestamp = model.buildTimestamp - , chosenBackgroundImage = model.chosenBackdrop - , coverSelectionReducesPool = model.coverSelectionReducesPool - , currentTimeZone = model.currentTimeZone - , extractedBackdropColor = model.extractedBackdropColor - , hideDuplicateTracks = model.hideDuplicates - , lastFm = model.lastFm - , processAutomatically = model.processAutomatically - , rememberProgress = model.rememberProgress - , serviceWorkerStatus = model.serviceWorkerStatus - , syncMethod = Syncing.extractMethod model.syncing - , version = model.version - } - - Page.Sources subPage -> - Sources.view subPage model - - -- Syncing - ---------- - , Syncing.view model - ] - - ----------------------------------------- - -- Controls - ----------------------------------------- - , Themes.Sunrise.Console.view - model.nowPlaying - model.repeat - model.shuffle - ] - - - --- ๐Ÿ—บ โ–‘โ–‘ BITS - - -content : { justifyCenter : Bool, scrolling : Bool } -> List (Html Msg) -> Html Msg -content { justifyCenter, scrolling } nodes = - brick - [ on "focusout" (Json.Decode.succeed Blur) - , on "focusin" inputFocusDecoder - ] - [ "overflow-x-hidden" - , "relative" - , "screen-height" - , "scrolling-touch" - , "w-screen" - , "z-10" - - -- - , ifThenElse scrolling "overflow-y-auto" "overflow-y-hidden" - ] - [ brick - [ style "min-width" "280px" ] - [ "flex" - , "flex-col" - , "items-center" - , "h-full" - , "px-3" - - -- - , "md:px-8" - , "lg:px-16" - - -- - , ifThenElse justifyCenter "justify-center" "justify-start" - ] - nodes - ] - - -inputFocusDecoder : Json.Decode.Decoder Msg -inputFocusDecoder = - Json.Decode.string - |> Json.Decode.at [ "target", "tagName" ] - |> Json.Decode.andThen - (\targetTagName -> - case targetTagName of - "INPUT" -> - Json.Decode.succeed FocusedOnInput - - "TEXTAREA" -> - Json.Decode.succeed FocusedOnInput - - _ -> - Json.Decode.fail "NOT_INPUT" - ) - - -overlay : Maybe (Alfred Msg) -> Maybe (ContextMenu Msg) -> Html Msg -overlay maybeAlfred maybeContextMenu = - let - isShown = - Maybe.isJust maybeAlfred || Maybe.isJust maybeContextMenu - in - brick - [] - [ "inset-0" - , "bg-darkest-hour" - , "duration-500" - , "ease-in-out" - , "fixed" - , "transition-opacity" - , "z-30" - - -- - , ifThenElse isShown "pointer-events-auto" "pointer-events-none" - , ifThenElse isShown "opacity-50" "opacity-0" - ] - [] - - -vessel : List (Html Msg) -> Html Msg -vessel = - brick - [ style "-webkit-mask-image" "-webkit-radial-gradient(white, black)" ] - [ "bg-white" - , "flex" - , "flex-col" - , "flex-grow" - , "overflow-hidden" - , "relative" - , "rounded" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - ] - >> bricky - [ style "min-height" "296px" ] - [ "flex" - , "flex-grow" - , "rounded" - , "shadow-lg" - , "w-full" - - -- - , "lg:max-w-insulation" - , "lg:min-w-3xl" - ] diff --git a/src/Core/Themes/Sunrise/Tracks/Scene.elm b/src/Core/Themes/Sunrise/Tracks/Scene.elm deleted file mode 100644 index ac2859411..000000000 --- a/src/Core/Themes/Sunrise/Tracks/Scene.elm +++ /dev/null @@ -1,75 +0,0 @@ -module Themes.Sunrise.Tracks.Scene exposing (..) - -import Chunky exposing (..) -import Conditional exposing (..) -import Html exposing (Html, text) -import Html.Attributes as A -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Tracks - - - --- ๐Ÿ—บ - - -group : { index : Int } -> Tracks.Identifiers -> Html msg -group { index } identifiers = - let - groupName = - identifiers.group - |> Maybe.map .name - |> Maybe.withDefault "Unknown" - in - brick - [ A.style "height" "18px" ] - [ "box-content" - , "font-display" - , "font-semibold" - , "leading-normal" - , "pb-3" - , "px-4" - , "text-base04" - , "text-xxs" - , "tracking-tad-further" - , "truncate" - - -- - , ifThenElse (0 == index) "pt-3" "pt-4" - ] - [ groupIcon - , inline [ "align-middle" ] [ text groupName ] - ] - - -shadow : Html msg -shadow = - chunk - [ "h-10" - , "left-0" - , "-mt-10" - , "-translate-y-full" - , "opacity-30" - , "right-0" - , "shadow-md" - , "sticky" - , "top-0" - , "transform" - , "z-10" - - -- Dark mode - ------------ - , "dark:shadow-md-darker" - ] - [] - - - --- ใŠ™๏ธ - - -groupIcon : Html msg -groupIcon = - inline - [ "align-middle", "inline-block", "leading-0", "pr-2" ] - [ Icons.library_music 16 Inherit ] diff --git a/src/Core/Themes/Sunrise/Tracks/Scene/Covers.elm b/src/Core/Themes/Sunrise/Tracks/Scene/Covers.elm deleted file mode 100644 index d814c36b5..000000000 --- a/src/Core/Themes/Sunrise/Tracks/Scene/Covers.elm +++ /dev/null @@ -1,881 +0,0 @@ -module Themes.Sunrise.Tracks.Scene.Covers exposing (containerId, scrollToNowPlaying, scrollToTop, view) - -import Browser.Dom as Dom -import Chunky exposing (..) -import Color exposing (Color) -import Conditional exposing (ifThenElse) -import Coordinates -import Dict exposing (Dict) -import Html exposing (Html, text) -import Html.Attributes as A exposing (class, id, style, tabindex) -import Html.Events as E -import Html.Events.Extra.Mouse as Mouse -import Html.Lazy -import InfiniteList -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import Task -import Themes.Sunrise.Tracks.Scene as Scene -import Themes.Sunrise.Tracks.Scene.List -import Tracks exposing (..) -import UI.Tracks.Types exposing (Msg(..)) -import UI.Types as UI exposing (Msg(..)) - - - --- ๐Ÿ—บ - - -type alias Dependencies = - { bgColor : Maybe Color - , cachedCovers : Maybe (Dict String String) - , covers : List Cover - , darkMode : Bool - , favouritesOnly : Bool - , infiniteList : InfiniteList.Model - , isVisible : Bool - , nowPlaying : Maybe IdentifiedTrack - , selectedCover : Maybe Cover - , selectedTrackIndexes : List Int - , sortBy : SortBy - , sortDirection : SortDirection - , viewportHeight : Float - , viewportWidth : Float - } - - -type alias ItemDependencies = - { cachedCovers : Maybe (Dict String String) - , columns : Int - , containerWidth : Int - , nowPlaying : Maybe IdentifiedTrack - , sortBy : SortBy - } - - -view : Dependencies -> Html Msg -view deps = - Html.Lazy.lazy view_ deps - - -view_ : Dependencies -> Html Msg -view_ deps = - chunk - [ "flex" - , "flex-basis-0" - , "flex-col" - , "flex-grow" - , "relative" - ] - [ collectionView deps - , case deps.selectedCover of - Just cover -> - singleCoverView cover deps - - Nothing -> - nothing - ] - - - --- ๐Ÿž โ–‘โ–‘ COLLECTION - - -collectionView : Dependencies -> Html Msg -collectionView deps = - let - amountOfCovers = - List.length deps.covers - in - brick - (tabindex (ifThenElse deps.isVisible 0 -1) :: viewAttributes) - [ "flex-basis-0" - , "flex-grow" - , "outline-none" - , "overflow-x-hidden" - , "overflow-y-auto" - , "relative" - , "scrolling-touch" - , "text-almost-sm" - ] - [ Scene.shadow - , chunk - [ "bg-white" - , "flex" - , "items-center" - , "pt-5" - , "px-5" - , "relative" - , "z-20" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - ] - [ sortGroupButtons deps.sortBy - - -- - , chunk - [ "flex" - , "flex-auto" - , "items-center" - , "justify-end" - , "text-base05" - , "text-right" - , "text-xs" - ] - [ text (String.fromInt amountOfCovers) - , case deps.sortBy of - Album -> - text " albums" - - Artist -> - text " artists" - - _ -> - nothing - , text " " - , slab - Html.span - [ deps.sortBy - |> SortBy - |> TracksMsg - |> E.onClick - - -- - , case deps.sortDirection of - Asc -> - A.title "Sorted alphabetically ascending" - - Desc -> - A.title "Sorted alphabetically descending" - ] - [ "cursor-pointer" - , "ml-1" - , "opacity-60" - ] - [ case deps.sortDirection of - Asc -> - Icons.arrow_downward 16 Inherit - - Desc -> - Icons.arrow_upward 16 Inherit - ] - ] - ] - - -- - , infiniteListView deps - ] - - -containerId : String -containerId = - "diffuse__track-covers" - - -scrollToNowPlaying : Float -> List Cover -> IdentifiedTrack -> Cmd Msg -scrollToNowPlaying viewportWidth covers nowPlaying = - let - columns = - determineColumns viewportWidth - - containerWidth = - determineContainerWidth viewportWidth - - rowHeightArgs = - { columns = columns - , containerWidth = containerWidth - } - - { rows, nowPlayingRowIndex } = - coverRows (Just nowPlaying) columns covers - in - case nowPlayingRowIndex of - Just idx -> - rows - |> List.take idx - |> List.foldl (\a -> (+) <| dynamicRowHeight rowHeightArgs 0 a) 0 - |> toFloat - |> (+) 11 - |> Dom.setViewportOf containerId 0 - |> Task.attempt (always Bypass) - - Nothing -> - Cmd.none - - -scrollToTop : Cmd Msg -scrollToTop = - Task.attempt (always UI.Bypass) (Dom.setViewportOf containerId 0 0) - - -viewAttributes : List (Html.Attribute Msg) -viewAttributes = - [ InfiniteList.onScroll (InfiniteListMsg >> TracksMsg) - , id containerId - ] - - - --- ๐Ÿž โ–‘โ–‘ SINGLE COVER - - -singleCoverView : Cover -> Dependencies -> Html Msg -singleCoverView cover deps = - let - derivedColors = - Themes.Sunrise.Tracks.Scene.List.deriveColors - { bgColor = deps.bgColor - , darkMode = deps.darkMode - } - - columns = - determineColumns deps.viewportWidth - - condensedView = - columns < 4 - in - brick - [ tabindex (ifThenElse deps.isVisible 0 -1) ] - [ "absolute" - , "bg-white" - , "flex-basis-0" - , "flex-grow" - , "inset-0" - , "leading-tight" - , "outline-none" - , "overflow-x-hidden" - , "overflow-y-auto" - , "text-almost-sm" - , "z-30" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - ] - [ chunk - [ "flex" - , "font-semibold" - , "h-8" - , "items-center" - , "leading-none" - , "-ml-2" - , "mt-5" - , "px-5" - ] - [ headerButton - [ E.onClick (TracksMsg DeselectCover) ] - { active = False - , label = Icons.arrow_back 16 Inherit - } - - -- - , headerButton - [ Mouse.onClick (showCoverMenu cover) ] - { active = True - , label = Icons.more_horiz 16 Inherit - } - ] - - -- - , chunk - [ "mb-6" - , "-top-px" - , "mt-4" - , "relative" - - -- - , ifThenElse condensedView "block" "flex" - , ifThenElse condensedView "mx-5" "ml-5" - ] - [ itemView - { clickable = False, horizontal = condensedView } - (compileItemDependencies deps) - cover - - -- - , cover.tracks - |> List.indexedMap - (Themes.Sunrise.Tracks.Scene.List.defaultItemView - { derivedColors = derivedColors - , favouritesOnly = deps.favouritesOnly - , nowPlaying = deps.nowPlaying - , roundedCorners = True - , selectedTrackIndexes = deps.selectedTrackIndexes - , showAlbum = not cover.sameAlbum - , showArtist = deps.sortBy /= Artist && not cover.sameArtist - , showGroup = False - } - 0 - ) - |> chunk - [ ifThenElse condensedView "px-px" "px-0" ] - |> List.singleton - |> chunk - [ "flex-auto" - , "flex-basis-0" - , "overflow-hidden" - , "select-none" - - -- - , ifThenElse condensedView "-mx-5" "mx-5" - , ifThenElse condensedView "px-1" "px-0" - ] - ] - ] - - - --- ๐Ÿง• - - -headerButton attributes { active, label } = - brick - attributes - [ "cursor-pointer" - , "inline-flex" - , "h-8" - , "items-center" - , "overflow-hidden" - , "px-2" - , "rounded" - - -- - , ifThenElse active "bg-gray-300" "bg-transparent" - , ifThenElse active "dark:bg-base01" "dark:bg-transparent" - ] - [ chunk - [ "mt-px", "pt-px" ] - [ label ] - ] - - -showCoverMenu : Cover -> Mouse.Event -> Msg -showCoverMenu cover = - .clientPos - >> Coordinates.fromTuple - >> (TracksMsg << ShowCoverMenuWithSmallDelay cover) - - - --- SORTING - - -sortGroupButtons : SortBy -> Html Msg -sortGroupButtons sortBy = - chunk - [ "flex" - , "h-8" - , "items-center" - , "leading-none" - , "mr-3" - , "text-xs" - , "tracking-tad-further" - ] - [ sortGroupButton - { current = sortBy, btn = Artist } - (chunk - [ "inline-flex", "items-center" ] - [ inline [ "mr-px" ] [ Icons.people_alt 16 Inherit ] - , inline [ "ml-1", "mt-px", "pl-px", "pt-px" ] [ text "Artists" ] - ] - ) - - -- - , sortGroupButton - { current = sortBy, btn = Album } - (chunk - [ "inline-flex", "items-center" ] - [ inline [ "mr-px" ] [ Icons.album 16 Inherit ] - , inline [ "ml-1", "mt-px", "pt-px" ] [ text "Albums" ] - ] - ) - ] - - -sortGroupButton : { current : SortBy, btn : SortBy } -> Html Msg -> Html Msg -sortGroupButton { current, btn } label = - headerButton - [ btn - |> SortBy - |> TracksMsg - |> E.onClick - - -- - , class "mr-1" - ] - { active = current == btn - , label = label - } - - - --- INFINITE LIST - - -infiniteListView : Dependencies -> Html Msg -infiniteListView deps = - let - itemDeps = - compileItemDependencies deps - - rowHeightArgs = - { columns = itemDeps.columns - , containerWidth = itemDeps.containerWidth - } - in - { itemView = rowView itemDeps - , itemHeight = InfiniteList.withVariableHeight (dynamicRowHeight rowHeightArgs) - , containerHeight = round deps.viewportHeight - 262 - } - |> InfiniteList.config - |> InfiniteList.withCustomContainer infiniteListContainer - |> (\config -> - InfiniteList.view - config - deps.infiniteList - (deps.covers - |> coverRows Nothing itemDeps.columns - |> .rows - ) - ) - - -infiniteListContainer : - List ( String, String ) - -> List (Html msg) - -> Html msg -infiniteListContainer styles = - styles - |> List.filterMap - (\( k, v ) -> - if k == "padding" then - Nothing - - else - Just (style k v) - ) - |> List.append listStyles - |> Html.div - - -compileItemDependencies : Dependencies -> ItemDependencies -compileItemDependencies deps = - { cachedCovers = deps.cachedCovers - , columns = determineColumns deps.viewportWidth - , containerWidth = determineContainerWidth deps.viewportWidth - , nowPlaying = deps.nowPlaying - , sortBy = deps.sortBy - } - - -listStyles : List (Html.Attribute msg) -listStyles = - [ class "leading-tight" - , class "pl-5" - , class "pt-4" - ] - - - --- ROWS - - -determineContainerWidth : Float -> Int -determineContainerWidth viewportWidth = - min 768 (round viewportWidth - 32) - - -dynamicRowHeight : { columns : Int, containerWidth : Int } -> Int -> List Cover -> Int -dynamicRowHeight { columns, containerWidth } _ coverRow = - let - rowHeight = - (containerWidth - 16) // columns + (46 + 16) - in - let - shouldRenderGroup = - coverRow - |> List.head - |> Maybe.andThen (.tracks >> List.head) - |> Maybe.map (Tuple.first >> Tracks.shouldRenderGroup) - |> Maybe.withDefault False - in - if shouldRenderGroup then - 42 + rowHeight - - else - rowHeight - - -coverRows : - Maybe IdentifiedTrack - -> Int - -> List Cover - -> { nowPlayingRowIndex : Maybe Int, rows : List (List Cover) } -coverRows maybeNowPlaying columns covers = - covers - |> List.foldl - (\cover { collection, current, nowPlayingRowIdx, trackGroup } -> - let - trackGroupCurr = - cover.identifiedTrackCover - |> Tuple.first - |> .group - |> Maybe.map .name - - npr addition = - case ( maybeNowPlaying, nowPlayingRowIdx ) of - ( Just ( _, t ), Nothing ) -> - if List.member t.id cover.trackIds then - Just (List.length collection + ifThenElse addition 1 0) - - else - Nothing - - _ -> - nowPlayingRowIdx - in - if List.length current < columns && (Maybe.isNothing trackGroup || trackGroupCurr == trackGroup) then - { collection = collection - , current = current ++ [ cover ] - , nowPlayingRowIdx = npr False - , trackGroup = trackGroupCurr - } - - else - { collection = collection ++ [ current ] - , current = [ cover ] - , nowPlayingRowIdx = npr True - , trackGroup = trackGroupCurr - } - ) - { current = [] - , collection = [] - , nowPlayingRowIdx = Nothing - , trackGroup = Nothing - } - |> (\foldResult -> - { nowPlayingRowIndex = foldResult.nowPlayingRowIdx - , rows = foldResult.collection ++ [ foldResult.current ] - } - ) - - -rowView : - ItemDependencies - -> Int - -> Int - -> List Cover - -> Html Msg -rowView itemDeps _ idx row = - let - maybeIdentifiers = - row - |> List.head - |> Maybe.andThen (.tracks >> List.head) - |> Maybe.map Tuple.first - - shouldRenderGroup = - maybeIdentifiers - |> Maybe.map Tracks.shouldRenderGroup - |> Maybe.withDefault False - in - raw - [ case ( shouldRenderGroup, maybeIdentifiers ) of - ( True, Just identifiers ) -> - chunk - [ "-ml-4" ] - [ Scene.group { index = idx } identifiers ] - - _ -> - nothing - - -- - , chunk - [ "flex", "flex-wrap" ] - (List.map (itemView { clickable = True, horizontal = False } itemDeps) row) - ] - - - --- ITEMS / COLUMNS - - -determineColumns : Float -> Int -determineColumns viewportWidth = - let - containerWidth = - determineContainerWidth viewportWidth - in - if containerWidth < 260 then - 1 - - else if containerWidth < 480 then - 2 - - else if containerWidth <= 590 then - 3 - - else - 4 - - -type alias ItemViewOptions = - { clickable : Bool, horizontal : Bool } - - -itemView : ItemViewOptions -> ItemDependencies -> Cover -> Html Msg -itemView options deps cover = - chunk - [ "antialiased" - , "flex-shrink-0" - , "font-semibold" - - -- - , ifThenElse options.horizontal "flex" "block" - , ifThenElse options.horizontal "mb-0" "mb-5" - - -- - , case ( options.horizontal, deps.columns ) of - ( True, _ ) -> - "w-auto" - - ( False, 1 ) -> - "w-full" - - ( False, 2 ) -> - "w-1/2" - - ( False, 3 ) -> - "w-1/3" - - _ -> - "w-1/4" - ] - [ coverView options deps cover - , metadataView options deps cover - ] - - -coverView : ItemViewOptions -> ItemDependencies -> Cover -> Html Msg -coverView { clickable, horizontal } { cachedCovers, nowPlaying } cover = - let - nowPlayingId = - Maybe.unwrap "" (Tuple.second >> .id) nowPlaying - - missingTracks = - List.any - (Tuple.first >> .isMissing) - cover.tracks - - maybeBlobUrlFromCache = - cachedCovers - |> Maybe.withDefault Dict.empty - |> Dict.get cover.key - - hasBackgroundImage = - Maybe.isJust maybeBlobUrlFromCache && not missingTracks - - bgOrDataAttributes = - case ( missingTracks, maybeBlobUrlFromCache ) of - ( True, _ ) -> - [] - - ( False, Just blobUrl ) -> - [ A.style "background-image" ("url('" ++ blobUrl ++ "')") - ] - - ( False, Nothing ) -> - if Maybe.isJust cachedCovers then - let - ( identifiers, track ) = - cover.identifiedTrackCover - in - [ A.attribute "data-key" cover.key - , A.attribute "data-filename" identifiers.filename - , A.attribute "data-path" track.path - , A.attribute "data-source-id" track.sourceId - , A.attribute "data-various-artists" (ifThenElse cover.variousArtists "t" "f") - ] - - else - [] - in - chunk - [ "flex-shrink-0" - , "mr-5" - , "relative" - - -- - , ifThenElse clickable "cursor-pointer" "cursor-default" - , ifThenElse horizontal "h-32" "h-0" - , ifThenElse horizontal "mb-4" "pt-full" - , ifThenElse horizontal "w-32" "w-auto" - ] - [ brick - (List.append - bgOrDataAttributes - (if clickable then - [ E.onClick (TracksMsg <| SelectCover cover) - , Mouse.onContextMenu (showCoverMenu cover) - ] - - else - [] - ) - ) - [ "absolute" - , "bg-cover" - , "bg-gray-300" - , "mb-5" - , "inset-0" - , "rounded-md" - , "shadow" - - -- - , ifThenElse horizontal "h-32" "h-auto" - - -- Dark mode - ------------ - , "dark:bg-white-025" - ] - [ if not hasBackgroundImage then - chunk - [ "absolute" - , "left-1/2" - , "-translate-x-1/2" - , "-translate-y-1/2" - , "text-gray-400" - , "top-1/2" - , "transform" - - -- Dark mode - ------------ - , "dark:text-base01" - ] - [ Icons.album 26 Inherit ] - - else - nothing - - -- Now playing? - , if List.member nowPlayingId cover.trackIds then - let - dropShadow = - "drop-shadow(hsla(0, 0%, 0%, 0.275) 0px 0px 2.5px)" - in - brick - [ style "-webkit-filter" dropShadow - , style "filter" dropShadow - ] - [ "absolute" - , "bottom-0" - , "mb-3" - , "mr-3" - , "right-0" - , "text-white" - ] - [ Icons.headset 16 Inherit ] - - else - nothing - ] - ] - - -metadataView : ItemViewOptions -> ItemDependencies -> Cover -> Html Msg -metadataView { clickable, horizontal } { sortBy } cover = - let - { identifiedTrackCover } = - cover - - ( _, track ) = - identifiedTrackCover - - missingTracks = - List.any - (Tuple.first >> .isMissing) - cover.tracks - in - brick - (if clickable then - [ E.onClick (TracksMsg <| SelectCover cover) - , Mouse.onContextMenu (showCoverMenu cover) - ] - - else - [] - ) - [ "mr-5" - , "relative" - , "tracking-tad-closer" - , "z-10" - - -- - , ifThenElse clickable "cursor-pointer" "cursor-default" - , ifThenElse horizontal "mt-0" "-mt-5" - , ifThenElse horizontal "overflow-hidden" "overflow-auto" - , ifThenElse horizontal "pt-0" "pt-2" - ] - [ chunk - [ "mt-px" - , "pt-px" - , "truncate" - ] - [ case sortBy of - Album -> - if missingTracks then - text "Missing tracks" - - else - text (Maybe.withDefault "Unknown album" track.tags.album) - - Artist -> - if missingTracks then - text "Missing tracks" - - else - text (Maybe.withDefault "Unknown artist" track.tags.artist) - - _ -> - nothing - ] - - -- - , chunk - [ "mt-px" - , "pt-px" - , "text-base05" - , "text-xs" - , "truncate" - ] - [ case sortBy of - Album -> - if cover.variousArtists then - text "Various Artists" - - else if not missingTracks && Maybe.isJust track.tags.artist then - text (Maybe.withDefault "" track.tags.artist) - - else - case List.length cover.trackIds of - 1 -> - text "1 track" - - n -> - text (String.fromInt n ++ " tracks") - - Artist -> - case List.length cover.trackIds of - 1 -> - text "1 track" - - n -> - text (String.fromInt n ++ " tracks") - - _ -> - nothing - ] - ] diff --git a/src/Core/Themes/Sunrise/Tracks/Scene/List.elm b/src/Core/Themes/Sunrise/Tracks/Scene/List.elm deleted file mode 100644 index f3dd46eaf..000000000 --- a/src/Core/Themes/Sunrise/Tracks/Scene/List.elm +++ /dev/null @@ -1,851 +0,0 @@ -module Themes.Sunrise.Tracks.Scene.List exposing (Dependencies, DerivedColors, containerId, defaultItemView, deriveColors, scrollToNowPlaying, scrollToTop, view) - -import Browser.Dom as Dom -import Chunky exposing (..) -import Color exposing (Color) -import Color.Manipulate as Color -import Conditional exposing (ifThenElse) -import Coordinates -import Html exposing (Html, text) -import Html.Attributes exposing (class, id, style, tabindex) -import Html.Events -import Html.Events.Extra.Mouse as Mouse -import Html.Lazy -import InfiniteList -import Json.Decode as Decode -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import Task -import Themes.Sunrise.Kit as Kit -import Themes.Sunrise.Tracks.Scene as Scene -import Tracks exposing (..) -import UI.DnD as DnD -import UI.Queue.Types as Queue -import UI.Tracks.Types exposing (Msg(..)) -import UI.Types as UI exposing (Msg(..)) - - - --- ๐Ÿ—บ - - -type alias Dependencies = - { bgColor : Maybe Color - , darkMode : Bool - , height : Float - , isTouchDevice : Bool - , isVisible : Bool - , showAlbum : Bool - } - - -type alias DerivedColors = - { background : String - , subtle : String - , text : String - } - - -view : Dependencies -> List IdentifiedTrack -> InfiniteList.Model -> Bool -> Maybe IdentifiedTrack -> Maybe String -> SortBy -> SortDirection -> List Int -> Maybe (DnD.Model Int) -> Html Msg -view deps harvest infiniteList favouritesOnly nowPlaying searchTerm sortBy sortDirection selectedTrackIndexes maybeDnD = - brick - (tabindex (ifThenElse deps.isVisible 0 -1) :: viewAttributes) - [ "flex-basis-0" - , "flex-grow" - , "outline-none" - , "overflow-x-hidden" - , "relative" - , "select-none" - , "scrolling-touch" - , "text-xs" - - -- - , "md:text-almost-sm" - - -- - , case maybeDnD of - Just dnd -> - if deps.isTouchDevice && DnD.isDragging dnd then - "overflow-y-hidden" - - else - "overflow-y-auto" - - Nothing -> - "overflow-y-auto" - ] - [ Scene.shadow - - -- Header - --------- - , Html.Lazy.lazy4 - header - (Maybe.isJust maybeDnD) - deps.showAlbum - sortBy - sortDirection - - -- List - ------- - , Html.Lazy.lazy7 - infiniteListView - deps - harvest - infiniteList - favouritesOnly - searchTerm - ( nowPlaying, selectedTrackIndexes ) - maybeDnD - ] - - -containerId : String -containerId = - "diffuse__track-list" - - -scrollToNowPlaying : List IdentifiedTrack -> IdentifiedTrack -> Cmd Msg -scrollToNowPlaying harvest ( identifiers, _ ) = - harvest - |> List.take identifiers.indexInList - |> List.foldl (\a -> (+) <| dynamicRowHeight 0 a) 0 - |> (\n -> 22 - toFloat rowHeight / 2 + 2 + toFloat n) - |> Dom.setViewportOf containerId 0 - |> Task.attempt (always Bypass) - - -scrollToTop : Cmd Msg -scrollToTop = - Task.attempt (always UI.Bypass) (Dom.setViewportOf containerId 0 0) - - -viewAttributes : List (Html.Attribute Msg) -viewAttributes = - [ InfiniteList.onScroll (InfiniteListMsg >> TracksMsg) - , id containerId - , class "overscroll-none" - ] - - - --- HEADERS - - -header : Bool -> Bool -> SortBy -> SortDirection -> Html Msg -header isPlaylist showAlbum sortBy sortDirection = - let - sortIcon = - (if sortDirection == Desc then - Icons.expand_less - - else - Icons.expand_more - ) - 15 - Inherit - - maybeSortIcon s = - ifThenElse (sortBy == s) (Just sortIcon) Nothing - in - chunk - [ "antialiased" - , "bg-white" - , "border-b" - , "border-gray-300" - , "flex" - , "font-semibold" - , "relative" - , "text-base06" - , "text-xxs" - , "z-20" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - , "dark:border-base01" - , "dark:text-base03" - ] - (if isPlaylist && showAlbum then - [ headerColumn "" 4.5 Nothing Bypass - , headerColumn "#" 4.5 Nothing Bypass - , headerColumn "Title" 36.0 Nothing Bypass - , headerColumn "Artist" 27.5 Nothing Bypass - , headerColumn "Album" 27.5 Nothing Bypass - ] - - else if isPlaylist then - [ headerColumn "" 4.5 Nothing Bypass - , headerColumn "#" 4.5 Nothing Bypass - , headerColumn "Title" 49.75 Nothing Bypass - , headerColumn "Artist" 41.25 Nothing Bypass - ] - - else if showAlbum then - [ headerColumn "" 4.5 Nothing Bypass - , headerColumn "Title" 37.5 (maybeSortIcon Title) (TracksMsg <| SortBy Title) - , headerColumn "Artist" 29.0 (maybeSortIcon Artist) (TracksMsg <| SortBy Artist) - , headerColumn "Album" 29.0 (maybeSortIcon Album) (TracksMsg <| SortBy Album) - ] - - else - [ headerColumn "" 5.75 Nothing Bypass - , headerColumn "Title" 51.25 (maybeSortIcon Title) (TracksMsg <| SortBy Title) - , headerColumn "Artist" 43 (maybeSortIcon Artist) (TracksMsg <| SortBy Artist) - ] - ) - - - --- HEADER COLUMN - - -headerColumn : String -> Float -> Maybe (Html Msg) -> Msg -> Html Msg -headerColumn text_ width maybeSortIcon msg = - brick - [ Html.Events.onClick msg - - -- - , style "min-width" columnMinWidth - , style "width" (String.fromFloat width ++ "%") - ] - [ "border-l" - , "border-gray-300" - , "leading-relaxed" - , "pl-2" - , "pr-2" - , "pt-px" - , "relative" - - -- - , case msg of - Bypass -> - "cursor-default" - - _ -> - "cursor-pointer" - - -- - , "first:border-l-0" - , "first:cursor-default" - , "first:pl-4" - , "last:pr-4" - - -- Dark mode - ------------ - , "dark:border-base01" - ] - [ chunk - [ "mt-px", "opacity-90", "pt-px" ] - [ Html.text text_ ] - , case maybeSortIcon of - Just sortIcon -> - chunk - [ "absolute" - , "-translate-y-1/2" - , "mr-1" - , "opacity-90" - , "right-0" - , "top-1/2" - , "transform" - ] - [ sortIcon ] - - Nothing -> - nothing - ] - - - --- INFINITE LIST - - -infiniteListView : Dependencies -> List IdentifiedTrack -> InfiniteList.Model -> Bool -> Maybe String -> ( Maybe IdentifiedTrack, List Int ) -> Maybe (DnD.Model Int) -> Html Msg -infiniteListView deps harvest infiniteList favouritesOnly searchTerm ( nowPlaying, selectedTrackIndexes ) maybeDnD = - let - derivedColors = - deriveColors { bgColor = deps.bgColor, darkMode = deps.darkMode } - in - { itemView = - case maybeDnD of - Just dnd -> - playlistItemView - favouritesOnly - nowPlaying - searchTerm - selectedTrackIndexes - dnd - deps.showAlbum - deps.darkMode - derivedColors - - _ -> - defaultItemView - { derivedColors = derivedColors - , favouritesOnly = favouritesOnly - , nowPlaying = nowPlaying - , roundedCorners = False - , selectedTrackIndexes = selectedTrackIndexes - , showAlbum = deps.showAlbum - , showArtist = True - , showGroup = True - } - - -- - , itemHeight = InfiniteList.withVariableHeight dynamicRowHeight - , containerHeight = round deps.height - } - |> InfiniteList.config - |> InfiniteList.withCustomContainer infiniteListContainer - |> (\config -> - InfiniteList.view - config - infiniteList - harvest - ) - - -infiniteListContainer : - List ( String, String ) - -> List (Html msg) - -> Html msg -infiniteListContainer styles = - styles - |> List.filterMap - (\( k, v ) -> - if k == "padding" then - Nothing - - else - Just (style k v) - ) - |> List.append listStyles - |> Html.div - - -deriveColors : { bgColor : Maybe Color, darkMode : Bool } -> DerivedColors -deriveColors { bgColor, darkMode } = - let - color = - Maybe.withDefault Kit.colors.text bgColor - in - if darkMode then - { background = Color.toCssString color - , subtle = Color.toCssString (Color.darken 0.1 color) - , text = Color.toCssString (Color.darken 0.475 color) - } - - else - { background = Color.toCssString (Color.fadeOut 0.625 color) - , subtle = Color.toCssString (Color.fadeOut 0.575 color) - , text = Color.toCssString (Color.darken 0.3 color) - } - - -listStyles : List (Html.Attribute msg) -listStyles = - [ class "pb-2 pt-1" ] - - -dynamicRowHeight : Int -> IdentifiedTrack -> Int -dynamicRowHeight _ ( i, t ) = - if Tracks.shouldRenderGroup i then - 16 + 18 + 12 + rowHeight - - else - rowHeight - - - --- INFINITE LIST ITEM - - -defaultItemView : - { derivedColors : DerivedColors - , favouritesOnly : Bool - , nowPlaying : Maybe IdentifiedTrack - , roundedCorners : Bool - , selectedTrackIndexes : List Int - , showAlbum : Bool - , showArtist : Bool - , showGroup : Bool - } - -> Int - -> Int - -> IdentifiedTrack - -> Html Msg -defaultItemView args _ idx identifiedTrack = - let - { derivedColors, favouritesOnly, nowPlaying, roundedCorners, selectedTrackIndexes, showAlbum, showArtist, showGroup } = - args - - ( identifiers, track ) = - identifiedTrack - - shouldRenderGroup = - showGroup && Tracks.shouldRenderGroup identifiers - - isSelected = - List.member idx selectedTrackIndexes - - isOddRow = - modBy 2 idx == 1 - - rowIdentifiers = - { isMissing = identifiers.isMissing - , isNowPlaying = Maybe.unwrap False (isNowPlaying identifiedTrack) nowPlaying - , isSelected = isSelected - } - - favIdentifiers = - { indexInList = identifiers.indexInList - , isFavourite = identifiers.isFavourite - , isNowPlaying = rowIdentifiers.isNowPlaying - , isSelected = isSelected - } - in - Html.div - [] - [ if shouldRenderGroup then - Scene.group { index = idx } identifiers - - else - nothing - - -- - , brick - (List.concat - [ rowStyles idx rowIdentifiers derivedColors - - -- - , List.append - (if isSelected then - [ touchContextMenuEvent identifiedTrack Nothing ] - - else - [] - ) - [ mouseContextMenuEvent identifiedTrack - , playEvent identifiedTrack - , selectEvent idx - ] - ] - ) - [ "flex" - , "items-center" - - -- - , ifThenElse identifiers.isMissing "cursor-default" "cursor-pointer" - , ifThenElse isSelected "font-semibold" "font-normal" - , ifThenElse roundedCorners "rounded" "border-r-0" - - -- - , ifThenElse - isOddRow - "bg-white" - "bg-gray-100" - - -- Dark mode - ------------ - , ifThenElse - isOddRow - "dark:bg-darkest-hour" - "dark:bg-near-darkest-hour" - ] - (if not showArtist && not showAlbum then - [ favouriteColumn "5.75%" favouritesOnly favIdentifiers derivedColors - , otherColumn "94.25%" False track.tags.title - ] - - else if not showArtist && showAlbum then - [ favouriteColumn "5.75%" favouritesOnly favIdentifiers derivedColors - , otherColumn "51.25%" False track.tags.title - , otherColumn "43%" False (Maybe.withDefault fallbackAlbum track.tags.album) - ] - - else if showArtist && not showAlbum then - [ favouriteColumn "5.75%" favouritesOnly favIdentifiers derivedColors - , otherColumn "51.25%" False track.tags.title - , otherColumn "43%" False (Maybe.withDefault fallbackArtist track.tags.artist) - ] - - else - [ favouriteColumn defFavColWidth favouritesOnly favIdentifiers derivedColors - , otherColumn "37.5%" False track.tags.title - , otherColumn "29.0%" False (Maybe.withDefault fallbackArtist track.tags.artist) - , otherColumn "29.0%" True (Maybe.withDefault fallbackAlbum track.tags.album) - ] - ) - ] - - -playlistItemView : Bool -> Maybe IdentifiedTrack -> Maybe String -> List Int -> DnD.Model Int -> Bool -> Bool -> DerivedColors -> Int -> Int -> IdentifiedTrack -> Html Msg -playlistItemView favouritesOnly nowPlaying _ selectedTrackIndexes dnd showAlbum darkMode derivedColors _ idx identifiedTrack = - let - ( identifiers, track ) = - identifiedTrack - - listIdx = - identifiers.indexInList - - dragEnv = - { model = dnd - , toMsg = DnD - } - - isSelected = - List.member idx selectedTrackIndexes - - isOddRow = - modBy 2 idx == 1 - - rowIdentifiers = - { isMissing = identifiers.isMissing - , isNowPlaying = Maybe.unwrap False (isNowPlaying identifiedTrack) nowPlaying - , isSelected = isSelected - } - - favIdentifiers = - { indexInList = identifiers.indexInList - , isFavourite = identifiers.isFavourite - , isNowPlaying = rowIdentifiers.isNowPlaying - , isSelected = isSelected - } - in - brick - (List.concat - [ rowStyles idx rowIdentifiers derivedColors - - -- - , List.append - (if isSelected then - [ touchContextMenuEvent identifiedTrack (Just dragEnv) - , DnD.listenToStart dragEnv listIdx - ] - - else - [] - ) - [ mouseContextMenuEvent identifiedTrack - , playEvent identifiedTrack - , selectEvent idx - ] - - -- - , DnD.listenToEnterLeave dragEnv listIdx - - -- - , if DnD.isBeingDraggedOver listIdx dnd then - [ dragIndicator darkMode ] - - else - [] - ] - ) - [ "flex" - , "items-center" - - -- - , ifThenElse identifiers.isMissing "cursor-default" "cursor-pointer" - , ifThenElse isSelected "font-semibold" "font-normal" - - -- - , ifThenElse - isOddRow - "bg-white" - "bg-gray-100" - - -- Dark mode - ------------ - , ifThenElse - isOddRow - "dark:bg-darkest-hour" - "dark:bg-near-darkest-hour" - ] - (if showAlbum then - [ favouriteColumn defFavColWidth favouritesOnly favIdentifiers derivedColors - , playlistIndexColumn (Maybe.withDefault 0 identifiers.indexInPlaylist) - , otherColumn "36.0%" False track.tags.title - , otherColumn "27.5%" False (Maybe.withDefault fallbackArtist track.tags.artist) - , otherColumn "27.5%" True (Maybe.withDefault fallbackAlbum track.tags.album) - ] - - else - [ favouriteColumn defFavColWidth favouritesOnly favIdentifiers derivedColors - , playlistIndexColumn (Maybe.withDefault 0 identifiers.indexInPlaylist) - , otherColumn "49.75%" False track.tags.title - , otherColumn "41.25%" False (Maybe.withDefault fallbackArtist track.tags.artist) - ] - ) - - -mouseContextMenuEvent : IdentifiedTrack -> Html.Attribute Msg -mouseContextMenuEvent ( i, _ ) = - Html.Events.custom - "contextmenu" - (Decode.map - (\event -> - { message = - if event.keys.shift then - Bypass - - else - event.clientPos - |> Coordinates.fromTuple - |> ShowTracksMenuWithSmallDelay - (Just i.indexInList) - { alt = event.keys.alt } - |> TracksMsg - - -- - , stopPropagation = True - , preventDefault = True - } - ) - Mouse.eventDecoder - ) - - -touchContextMenuEvent : IdentifiedTrack -> Maybe (DnD.Environment Int Msg) -> Html.Attribute Msg -touchContextMenuEvent ( i, _ ) maybeDragEnv = - Html.Events.custom - "longtap" - (Decode.map2 - (\x y -> - { message = - -- Only show menu when not dragging something - case Maybe.andThen (.model >> DnD.modelTarget) maybeDragEnv of - Just _ -> - Bypass - - Nothing -> - { x = x, y = y } - |> ShowTracksMenu - (Just i.indexInList) - { alt = False } - |> TracksMsg - - -- - , stopPropagation = False - , preventDefault = False - } - ) - (Decode.field "x" Decode.float) - (Decode.field "y" Decode.float) - ) - - -playEvent : IdentifiedTrack -> Html.Attribute Msg -playEvent ( i, t ) = - Html.Events.custom - "dbltap" - (Decode.succeed - { message = - if i.isMissing then - Bypass - - else - ( i, t ) - |> Queue.InjectFirstAndPlay - |> QueueMsg - - -- - , stopPropagation = True - , preventDefault = True - } - ) - - -selectEvent : Int -> Html.Attribute Msg -selectEvent idx = - Html.Events.custom - "tap" - (Decode.map2 - (\shiftKey button -> - { message = - case button of - 0 -> - { shiftKey = shiftKey } - |> MarkAsSelected idx - |> TracksMsg - - _ -> - Bypass - - -- - , stopPropagation = True - , preventDefault = False - } - ) - (Decode.at [ "originalEvent", "shiftKey" ] Decode.bool) - (Decode.oneOf - [ Decode.at [ "originalEvent", "button" ] Decode.int - , Decode.succeed 0 - ] - ) - ) - - - --- ROWS - - -rowHeight : Int -rowHeight = - 35 - - -rowStyles : Int -> { isMissing : Bool, isNowPlaying : Bool, isSelected : Bool } -> DerivedColors -> List (Html.Attribute msg) -rowStyles _ { isMissing, isNowPlaying } derivedColors = - let - bgColor = - if isNowPlaying then - derivedColors.background - - else - "" - - color = - if isNowPlaying then - derivedColors.text - - else if isMissing then - rowFontColors.gray - - else - "" - in - [ style "background-color" bgColor - , style "color" color - , style "height" (String.fromInt rowHeight ++ "px") - ] - - - --- COLUMNS - - -defFavColWidth = - "4.5%" - - -columnMinWidth = - "28px" - - -favouriteColumn : String -> Bool -> { isFavourite : Bool, indexInList : Int, isNowPlaying : Bool, isSelected : Bool } -> DerivedColors -> Html Msg -favouriteColumn columnWidth favouritesOnly identifiers derivedColors = - brick - (List.append - [ style "width" columnWidth - , identifiers.indexInList - |> ToggleFavourite - |> TracksMsg - |> Html.Events.onClick - ] - (favouriteColumnStyles favouritesOnly identifiers derivedColors) - ) - [ "flex-shrink-0" - , "font-normal" - , "pl-4" - , "text-gray-500" - - -- Dark mode - ------------ - , "dark:text-base02" - ] - [ if identifiers.isFavourite then - text "t" - - else - text "f" - ] - - -favouriteColumnStyles : Bool -> { isFavourite : Bool, indexInList : Int, isNowPlaying : Bool, isSelected : Bool } -> DerivedColors -> List (Html.Attribute msg) -favouriteColumnStyles favouritesOnly { isFavourite, isNowPlaying } derivedColors = - let - color = - if isNowPlaying && isFavourite then - derivedColors.text - - else if isNowPlaying then - derivedColors.subtle - - else if favouritesOnly || not isFavourite then - "" - - else - favColors.red - in - [ style "color" color - , style "font-family" "or-favourites" - , style "min-width" columnMinWidth - ] - - -playlistIndexColumn : Int -> Html msg -playlistIndexColumn indexInPlaylist = - brick - (otherColumnStyles "4.5%") - [ "pl-2" - , "pr-2" - , "pointer-events-none" - , "truncate" - ] - [ indexInPlaylist - |> (+) 1 - |> String.fromInt - |> text - ] - - -otherColumn : String -> Bool -> String -> Html msg -otherColumn width _ text_ = - brick - (otherColumnStyles width) - [ "pl-2" - , "pr-2" - , "pointer-events-none" - , "truncate" - - -- - , "last:pr-4" - ] - [ text text_ ] - - -otherColumnStyles : String -> List (Html.Attribute msg) -otherColumnStyles columnWidth = - [ style "min-width" columnMinWidth - , style "width" columnWidth - ] - - - --- ๐Ÿ–ผ - - -favColors = - { gray = Color.toCssString (Color.rgb255 220 220 220) - , red = Color.toCssString Kit.colorKit.base08 - } - - -rowFontColors = - { gray = Color.toCssString Kit.colorKit.base04 - , white = Color.toCssString (Color.rgb 1 1 1) - } - - -dragIndicator : Bool -> Html.Attribute msg -dragIndicator darkMode = - let - color = - if darkMode then - Kit.colors.gray_300 - - else - Kit.colorKit.base03 - in - style "box-shadow" ("0 1px 0 0 " ++ Color.toCssString color ++ " inset") diff --git a/src/Core/Themes/Sunrise/Tracks/View.elm b/src/Core/Themes/Sunrise/Tracks/View.elm deleted file mode 100644 index 9ba7b00fd..000000000 --- a/src/Core/Themes/Sunrise/Tracks/View.elm +++ /dev/null @@ -1,582 +0,0 @@ -module Themes.Sunrise.Tracks.View exposing (view) - -import Chunky exposing (..) -import Color exposing (Color) -import Common exposing (Switch(..)) -import Conditional exposing (ifThenElse) -import Html exposing (Html, text) -import Html.Attributes as A exposing (attribute, placeholder, style, tabindex, title, value) -import Html.Events as E exposing (onClick, onInput) -import Html.Events.Extra.Mouse as Mouse -import Html.Lazy exposing (..) -import Keyboard exposing (Key(..)) -import Material.Icons.Round as Icons -import Material.Icons.Types exposing (Coloring(..)) -import Maybe.Extra as Maybe -import Playlists exposing (Playlist) -import Themes.Sunrise.Kit as Kit -import Themes.Sunrise.Navigation as Navigation -import Themes.Sunrise.Tracks.Scene.Covers -import Themes.Sunrise.Tracks.Scene.List -import Tracks exposing (..) -import UI.Audio.Types exposing (nowPlayingIdentifiedTrack) -import UI.Navigation exposing (..) -import UI.Page as Page -import UI.Playlists.Page -import UI.Queue.Page -import UI.Sources.Page as Sources -import UI.Syncing.Common as Syncing -import UI.Syncing.Types as Syncing -import UI.Tracks.Types exposing (..) -import UI.Types as UI exposing (..) -import User.Layer as User - - - --- ๐ŸŒณ - - -type alias NavigationProperties = - { bgColor : Maybe Color - , favouritesOnly : Bool - , grouping : Maybe Grouping - , isOnIndexPage : Bool - , pressedShift : Bool - , scene : Scene - , searchTerm : Maybe String - , selectedPlaylist : Maybe Playlist - , showVolumeSlider : Bool - , volume : Float - } - - - --- ๐Ÿ—บ - - -view : Model -> Html UI.Msg -view model = - let - isOnIndexPage = - model.page == Page.Index - in - chunk - viewClasses - [ lazy - navigation - { bgColor = model.extractedBackdropColor - , favouritesOnly = model.favouritesOnly - , grouping = model.grouping - , isOnIndexPage = isOnIndexPage - , pressedShift = List.member Shift model.pressedKeys - , scene = model.scene - , searchTerm = model.searchTerm - , selectedPlaylist = model.selectedPlaylist - , showVolumeSlider = model.showVolumeSlider - , volume = model.eqSettings.volume - } - - -- - , if List.isEmpty model.tracks.harvested then - lazy5 - noTracksView - (List.map Tuple.first model.processingContext) - (List.length model.sources) - (List.length model.tracks.harvested) - (List.length model.favourites) - (Syncing.extractMethod model.syncing) - - else - case model.scene of - Covers -> - Themes.Sunrise.Tracks.Scene.Covers.view - { bgColor = model.extractedBackdropColor - , cachedCovers = model.cachedCovers - , covers = model.covers.harvested - , darkMode = model.darkMode - , favouritesOnly = model.favouritesOnly - , infiniteList = model.infiniteList - , isVisible = isOnIndexPage - , nowPlaying = Maybe.map nowPlayingIdentifiedTrack model.nowPlaying - , selectedCover = model.selectedCover - , selectedTrackIndexes = model.selectedTrackIndexes - , sortBy = model.sortBy - , sortDirection = model.sortDirection - , viewportHeight = model.viewport.height - , viewportWidth = model.viewport.width - } - - List -> - model.selectedPlaylist - |> Maybe.andThen - (\playlist -> - if playlist.collection || Maybe.isJust playlist.autoGenerated then - Nothing - - else - Just model.dnd - ) - |> Themes.Sunrise.Tracks.Scene.List.view - { bgColor = model.extractedBackdropColor - , darkMode = model.darkMode - , height = model.viewport.height - , isTouchDevice = model.isTouchDevice - , isVisible = isOnIndexPage - , showAlbum = model.viewport.width >= 720 - } - model.tracks.harvested - model.infiniteList - model.favouritesOnly - (Maybe.map nowPlayingIdentifiedTrack model.nowPlaying) - model.searchTerm - model.sortBy - model.sortDirection - model.selectedTrackIndexes - ] - - -viewClasses : List String -viewClasses = - [ "flex" - , "flex-col" - , "flex-grow" - , "relative" - ] - - -navigation : NavigationProperties -> Html UI.Msg -navigation { bgColor, favouritesOnly, grouping, isOnIndexPage, pressedShift, scene, searchTerm, selectedPlaylist, showVolumeSlider, volume } = - let - tabindex_ = - ifThenElse isOnIndexPage 0 -1 - in - chunk - [ "relative", "sm:flex" ] - [ ----------------------------------------- - -- Part 1 - ----------------------------------------- - slab - (Html.node "search") - [ attribute "role" "search" ] - [ "border-b" - , "border-r" - , "border-gray-300" - , "flex" - , "flex-grow" - , "h-12" - , "mt-px" - , "px-1" - , "overflow-hidden" - , "relative" - , "text-gray-600" - - -- Responsive - ------------- - , "sm:h-auto" - , "sm:px-0" - - -- Dark mode - ------------ - , "dark:border-base01" - , "dark:text-base04" - ] - [ -- Input - -------- - slab - Html.input - [ attribute "autocorrect" "off" - , attribute "autocapitalize" "none" - , attribute "spellcheck" "false" - , onInput (TracksMsg << SetSearchTerm) - , placeholder "Search" - , tabindex tabindex_ - , value (Maybe.withDefault "" searchTerm) - ] - [ "bg-transparent" - , "border-none" - , "flex-grow" - , "h-full" - , "min-w-0" - , "ml-1" - , "mt-px" - , "outline-none" - , "pl-8" - , "pr-2" - , "pt-px" - , "text-base02" - , "text-sm" - , "w-full" - - -- Dark mode - ------------ - , "dark:text-base06" - ] - [] - - -- Search icon - -------------- - , chunk - [ "absolute" - , "bottom-0" - , "flex" - , "items-center" - , "left-0" - , "ml-3" - , "mt-px" - , "pl-1" - , "top-0" - , "z-0" - - -- Responsive - ------------- - , "sm:pl-0" - ] - [ Icons.search 16 Inherit ] - - -- Actions - ---------- - , chunk - [ "flex" - , "items-center" - , "mr-3" - , "mt-px" - , "pt-px" - , "space-x-4" - - -- Responsive - ------------- - , "sm:space-x-2" - ] - [ -- 1 - case searchTerm of - Just _ -> - brick - [ onClick (TracksMsg ClearSearch) - , title "Clear search" - ] - [ "cursor-pointer" - , "mt-px" - ] - [ Icons.clear 16 Inherit ] - - Nothing -> - nothing - - -- 2 - , brick - [ onClick (TracksMsg ToggleFavouritesOnly) - , title "Toggle favourites-only" - ] - [ "cursor-pointer" ] - [ if favouritesOnly then - Icons.favorite 16 (Color Kit.colorKit.base08) - - else - Icons.favorite_border 16 Inherit - ] - - -- 3 - , case scene of - Covers -> - brick - [ attribute "title" "Switch to list view" - , List - |> ChangeScene - |> TracksMsg - |> onClick - ] - [ "ml-6" - , "mr-px" - , "cursor-pointer" - ] - [ chunk - [ "pl-1" ] - [ Icons.notes 18 Inherit ] - ] - - List -> - brick - [ attribute "title" "Switch to cover view" - , Covers - |> ChangeScene - |> TracksMsg - |> onClick - ] - [ "cursor-pointer" - , "mr-px" - ] - [ chunk - [ "pl-1" ] - [ Icons.burst_mode 20 Inherit ] - ] - - -- 4 - , brick - [ Mouse.onClick (TracksMsg << ShowViewMenu grouping) - , title "View settings" - ] - [ "cursor-pointer" ] - [ Icons.more_vert 16 Inherit ] - - -- 5 - , case selectedPlaylist of - Just playlist -> - brick - [ onClick DeselectPlaylist - , title "Deactivate playlist" - - -- - , bgColor - |> Maybe.withDefault Kit.colorKit.base01 - |> Color.toCssString - |> style "background-color" - ] - [ "antialiased" - , "cursor-pointer" - , "duration-500" - , "font-bold" - , "leading-none" - , "px-1" - , "py-1" - , "rounded" - , "truncate" - , "text-white-90" - , "text-xxs" - , "transition" - - -- Dark mode - ------------ - , "dark:text-white-60" - ] - [ chunk - [ "px-px", "pt-px" ] - [ text playlist.name ] - ] - - Nothing -> - nothing - ] - ] - , ----------------------------------------- - -- Part 2 - ----------------------------------------- - Navigation.localWithTabindex - tabindex_ - [ ( Icon Icons.waves - , Label "Collections & Playlists" Hidden - , if pressedShift then - PerformMsg AssistWithSelectingPlaylist - - else - NavigateToPage (Page.Playlists UI.Playlists.Page.Index) - ) - , ( Icon Icons.schedule - , Label "Queue" Hidden - , if pressedShift then - PerformMsg (ChangeUrlUsingPage <| Page.Queue UI.Queue.Page.History) - - else - NavigateToPage (Page.Queue UI.Queue.Page.Index) - ) - , ( if volume == 0 then - Icon Icons.volume_off - - else if volume < 0.5 then - Icon Icons.volume_down - - else - Icon Icons.volume_up - , Label "Volume" Hidden - , if pressedShift then - if volume == 0 then - PerformMsg (AdjustVolume 0.5) - - else - PerformMsg (AdjustVolume 0) - - else - PerformMsg (ToggleVolumeSlider <| ifThenElse showVolumeSlider Off On) - ) - ] - , ----------------------------------------- - -- Part 3 - ----------------------------------------- - if showVolumeSlider then - chunk - [ "absolute" - , "bg-white" - , "px-4" - , "py-3" - , "right-0" - , "rounded-bl" - , "shadow-lg" - , "top-full" - , "z-40" - - -- Dark mode - ------------ - , "dark:bg-darkest-hour" - , "dark:shadow-[rgba(0,0,0,.175)]" - ] - [ chunk - [ "leading-[0px]" - , "my-1" - , "pt-px" - , "text-[0px]" - ] - [ slab - Html.input - [ A.type_ "range" - , A.min "0" - , A.max "1" - , A.step "0.0125" - , A.value (String.fromFloat volume) - - -- - , E.onBlur SaveEnclosedUserData - , E.onInput (String.toFloat >> Maybe.unwrap Bypass AdjustVolume) - ] - [ "range-slider" ] - [] - ] - ] - - else - nothing - ] - - -noTracksView : List String -> Int -> Int -> Int -> Maybe User.Method -> Html UI.Msg -noTracksView processingContext amountOfSources amountOfTracks _ userLayerMethod = - chunk - [ "no-tracks-view" - - -- - , "flex" - , "flex-grow" - ] - [ Kit.centeredContent - [ if List.length processingContext > 0 then - message "Processing Tracks" - - else if amountOfSources == 0 then - chunk - [ "flex" - , "flex-col" - , "items-center" - , "justify-center" - , "px-3" - ] - [ slab - Html.img - [ A.src "images/diffuse-dark.svg" - , A.width 190 - ] - [ "dark:hidden" ] - [] - - -- - , slab - Html.img - [ A.src "images/diffuse-light.svg" - , A.width 190 - ] - [ "hidden dark:block" ] - [] - - -- - , chunk - [ "italic" - , "max-w-sm" - , "mt-6" - , "text-base05" - , "text-center" - , "text-sm" - - -- Dark mode - ------------ - , "dark:text-base03" - ] - [ Html.text "Play music" - , inline [ "not-italic", "font-normal", "inline-block", "mx-1", "pr-px" ] [ Html.text " โ™ซ " ] - , Html.text """from your Dropbox, -IPFS node, Amazon S3 bucket, or any other -cloud/distributed storage service you use. - """ - ] - - -- - , chunk - [ "flex", "mt-5", "space-x-3" ] - [ Kit.button - Kit.Normal - InsertDemo - (Html.text "Insert Demo") - , Kit.buttonWithColor - Kit.Accent - Kit.Filled - (ChangeUrlUsingPage <| Page.Sources Sources.New) - (Html.text "Add Music") - , case userLayerMethod of - Just method -> - Kit.buttonWithColor - Kit.Gray - Kit.Filled - (SyncingMsg Syncing.StopSync) - (text "Stop syncing") - - Nothing -> - Kit.buttonWithOptions - Html.button - [ Mouse.onClick (SyncingMsg << Syncing.ShowSyncDataMenu) ] - Kit.Gray - Kit.Filled - Nothing - (Html.text "Sync data") - ] - - -- - , chunk - [ "mt-4" ] - [ slab - Html.a - [ A.href "about/" - , A.target "_blank" - ] - [ "border-b" - , "border-current" - , "inline-block" - , "leading-snug" - , "text-base05" - , "text-xxs" - , "tracking-widest" - , "uppercase" - - -- Dark mode - ------------ - , "dark:text-base03" - ] - [ Html.text "Learn more" ] - ] - ] - - else if amountOfTracks == 0 then - message "No tracks found" - - else - message "No sources available" - ] - ] - - -message : String -> Html UI.Msg -message m = - chunk - [ "border-b-2" - , "border-current-color" - , "text-sm" - , "font-semibold" - , "leading-snug" - , "pb-1" - ] - [ text m ] diff --git a/src/Core/UI.elm b/src/Core/UI.elm deleted file mode 100644 index cbcf546d4..000000000 --- a/src/Core/UI.elm +++ /dev/null @@ -1,741 +0,0 @@ -module UI exposing (main) - -import Alien -import Browser -import Browser.Events -import Browser.Navigation as Nav -import Common exposing (ServiceWorkerStatus(..), Switch(..)) -import Debouncer.Basic as Debouncer -import Dict -import Equalizer -import InfiniteList -import Json.Decode as Json -import Keyboard -import LastFm -import Maybe.Extra as Maybe -import Notifications -import Random -import Return -import Task -import Time -import Tracks -import UI.Adjunct as Adjunct -import UI.Alfred.State as Alfred -import UI.Audio.State as Audio -import UI.Backdrop as Backdrop -import UI.Common.State as Common -import UI.DnD as DnD -import UI.Equalizer.State as Equalizer -import UI.Interface.State as Interface -import UI.Other.State as Other -import UI.Page as Page -import UI.Playlists.State as Playlists -import UI.Ports as Ports -import UI.Queue.State as Queue -import UI.Queue.Types as Queue -import UI.Routing.State as Routing -import UI.Services.State as Services -import UI.Sources.Form -import UI.Sources.State as Sources -import UI.Sources.Types as Sources -import UI.Syncing.State as Syncing -import UI.Syncing.Types as Syncing -import UI.Tracks.State as Tracks -import UI.Tracks.Types as Tracks -import UI.Types exposing (..) -import UI.User.State.Export as User -import UI.User.State.Import as User -import UI.View exposing (view) -import Url exposing (Url) -import Url.Ext as Url - - - --- โ›ฉ - - -main : Program Flags Model Msg -main = - Browser.application - { init = init - , view = view - , update = update - , subscriptions = subscriptions - , onUrlChange = UrlChanged - , onUrlRequest = LinkClicked - } - - - --- ๐ŸŒณ - - -init : Flags -> Url -> Nav.Key -> ( Model, Cmd Msg ) -init flags url key = - let - rewrittenUrl = - Page.rewriteUrl url - - maybePage = - Page.fromUrl rewrittenUrl - - page = - Maybe.withDefault Page.Index maybePage - - serviceWorkerStatus = - if flags.isInstallingServiceWorker then - InstallingInitial - - else - Activated - in - { buildTimestamp = flags.buildTimestamp - , confirmation = Nothing - , currentTime = Time.millisToPosix flags.initialTime - , currentTimeZone = Time.utc - , darkMode = flags.darkMode - , downloading = Nothing - , dnd = DnD.initialModel - , focusedOnInput = False - , isDragging = False - , isLoading = True - , isOnline = flags.isOnline - , isTauri = flags.isTauri - , isTouchDevice = False - , lastFm = LastFm.initialModel - , navKey = key - , page = page - , pressedKeys = [] - , processAutomatically = True - , serviceWorkerStatus = serviceWorkerStatus - , theme = Nothing - , uuidSeed = Random.initialSeed flags.initialTime - , url = url - , version = flags.version - , viewport = flags.viewport - - ----------------------------------------- - -- Audio - ----------------------------------------- - , audioElements = [] - , nowPlaying = Nothing - , progress = Dict.empty - , rememberProgress = True - - ----------------------------------------- - -- Backdrop - ----------------------------------------- - , chosenBackdrop = Nothing - , extractedBackdropColor = Nothing - , fadeInBackdrop = True - , loadedBackdrops = [] - - ----------------------------------------- - -- Debouncing - ----------------------------------------- - , preloadDebouncer = - 30 - |> Debouncer.fromSeconds - |> Debouncer.debounce - |> Debouncer.toDebouncer - , progressDebouncer = - 30 - |> Debouncer.fromSeconds - |> Debouncer.throttle - |> Debouncer.emitWhenUnsettled Nothing - |> Debouncer.toDebouncer - , resizeDebouncer = - 0.25 - |> Debouncer.fromSeconds - |> Debouncer.debounce - |> Debouncer.toDebouncer - , searchDebouncer = - 0.5 - |> Debouncer.fromSeconds - |> Debouncer.debounce - |> Debouncer.toDebouncer - - ----------------------------------------- - -- Equalizer - ----------------------------------------- - , eqSettings = Equalizer.defaultSettings - , showVolumeSlider = False - - ----------------------------------------- - -- Instances - ----------------------------------------- - , alfred = Nothing - , contextMenu = Nothing - , notifications = [] - - ----------------------------------------- - -- Playlists - ----------------------------------------- - , editPlaylistContext = Nothing - , lastModifiedPlaylist = Nothing - , newPlaylistContext = Nothing - , playlists = [] - , playlistToActivate = Nothing - , selectedPlaylist = Nothing - - ----------------------------------------- - -- Queue - ----------------------------------------- - , dontPlay = [] - , playedPreviously = [] - , playingNext = [] - , selectedQueueItem = Nothing - - -- - , repeat = False - , shuffle = False - - ----------------------------------------- - -- Sources - ----------------------------------------- - , processingContext = [] - , processingError = Nothing - , processingNotificationId = Nothing - , sources = [] - , sourceForm = UI.Sources.Form.initialModel - - ----------------------------------------- - -- Tracks - ----------------------------------------- - , cachedCovers = Nothing - , cachedTracks = [] - , cachedTracksOnly = False - , cachingTracksInProgress = [] - , covers = { arranged = [], harvested = [] } - , coverSelectionReducesPool = True - , favourites = [] - , favouritesOnly = False - , grouping = Nothing - , hideDuplicates = False - , scene = Tracks.Covers - , searchResults = Nothing - , searchTerm = Nothing - , selectedCover = Nothing - , selectedTrackIndexes = [] - , sortBy = Tracks.Album - , sortDirection = Tracks.Asc - , tracks = Tracks.emptyCollection - - -- List scene - ------------- - , infiniteList = InfiniteList.init - - ----------------------------------------- - -- ๐Ÿฆ‰ Nested - ----------------------------------------- - , syncing = Syncing.initialModel url - } - |> Routing.transition - page - |> Return.command - (url - |> Syncing.initialCommand - |> Cmd.map SyncingMsg - ) - |> Return.command - (if Maybe.isNothing maybePage then - Routing.resetUrl key url page - - else - case Url.action url of - [ "authenticate", "dropbox" ] -> - Routing.resetUrl key url page - - _ -> - Cmd.none - ) - |> Return.command - (Task.perform SetCurrentTime Time.now) - |> Return.command - (Task.perform SetCurrentTimeZone Time.here) - - - --- ๐Ÿ“ฃ - - -update : Msg -> Model -> ( Model, Cmd Msg ) -update msg = - case msg of - Bypass -> - Return.singleton - - ----------------------------------------- - -- Alfred - ----------------------------------------- - AssignAlfred a -> - Alfred.assign a - - GotAlfredInput a -> - Alfred.gotInput a - - SelectAlfredItem a -> - Alfred.runAction a - - ----------------------------------------- - -- Audio - ----------------------------------------- - AudioDurationChange a -> - Audio.durationChange a - - AudioEnded a -> - Audio.ended a - - AudioError a -> - Audio.error a - - AudioHasLoaded a -> - Audio.hasLoaded a - - AudioIsLoading a -> - Audio.isLoading a - - AudioPlaybackStateChanged a -> - Audio.playbackStateChanged a - - AudioPreloadDebounce a -> - Audio.preloadDebounce update a - - AudioTimeUpdated a -> - Audio.timeUpdated a - - NoteProgress a -> - Audio.noteProgress a - - NoteProgressDebounce a -> - Audio.noteProgressDebounce update a - - Pause -> - Audio.pause - - Play -> - Audio.play - - Seek a -> - Audio.seek a - - Stop -> - Audio.stop - - TogglePlay -> - Audio.playPause - - ToggleRememberProgress -> - Audio.toggleRememberProgress - - ----------------------------------------- - -- Backdrop - ----------------------------------------- - ExtractedBackdropColor a -> - Backdrop.extractedBackdropColor a - - ChooseBackdrop a -> - Backdrop.chooseBackdrop a - - LoadBackdrop a -> - Backdrop.loadBackdrop a - - ----------------------------------------- - -- Equalizer - ----------------------------------------- - AdjustVolume a -> - Equalizer.adjustVolume a - - ToggleVolumeSlider a -> - Equalizer.toggleVolumeSlider a - - ----------------------------------------- - -- Interface - ----------------------------------------- - AssistWithChangingTheme -> - Interface.assistWithChangingTheme - - Blur -> - Interface.blur - - ChangeTheme a -> - Interface.changeTheme a - - ContextMenuConfirmation a b -> - Interface.contextMenuConfirmation a b - - CopyToClipboard a -> - Interface.copyToClipboard a - - DismissNotification a -> - Common.dismissNotification a - - DnD a -> - Interface.dnd a - - FocusedOnInput -> - Interface.focusedOnInput - - HideOverlay -> - Interface.hideOverlay - - LostWindowFocus -> - Interface.lostWindowFocus - - MsgViaContextMenu a -> - Interface.msgViaContextMenu a - - PreferredColorSchemaChanged a -> - Interface.preferredColorSchemaChanged a - - RemoveNotification a -> - Interface.removeNotification a - - RemoveQueueSelection -> - Interface.removeQueueSelection - - RemoveTrackSelection -> - Interface.removeTrackSelection - - ResizeDebounce a -> - Interface.resizeDebounce update a - - ResizedWindow a -> - Interface.resizedWindow a - - SearchDebounce a -> - Interface.searchDebounce update a - - SetIsTouchDevice a -> - Interface.setIsTouchDevice a - - ShowNotification a -> - Common.showNotification a - - StoppedDragging -> - Interface.stoppedDragging - - ToggleLoadingScreen a -> - Common.toggleLoadingScreen a - - ----------------------------------------- - -- Playlists - ----------------------------------------- - ActivatePlaylist a -> - Playlists.activate a - - AddTracksToPlaylist a -> - Playlists.addTracksToPlaylist a - - AssistWithAddingTracksToCollection a -> - Playlists.assistWithAddingTracksToCollection a - - AssistWithAddingTracksToPlaylist a -> - Playlists.assistWithAddingTracksToPlaylist a - - AssistWithSelectingPlaylist -> - Playlists.assistWithSelectingPlaylist - - ConvertCollectionToPlaylist a -> - Playlists.convertCollectionToPlaylist a - - ConvertPlaylistToCollection a -> - Playlists.convertPlaylistToCollection a - - CreateCollection -> - Playlists.createCollection - - CreatePlaylist -> - Playlists.createPlaylist - - DeactivatePlaylist -> - Playlists.deactivate - - DeletePlaylist a -> - Playlists.delete a - - DeselectPlaylist -> - Playlists.deselect - - ModifyPlaylist -> - Playlists.modify - - MoveTrackInSelectedPlaylist a -> - Playlists.moveTrackInSelected a - - RemoveTracksFromPlaylist a b -> - Playlists.removeTracks a b - - SelectPlaylist a -> - Playlists.select a - - SetPlaylistCreationContext a -> - Playlists.setCreationContext a - - SetPlaylistModificationContext a b -> - Playlists.setModificationContext a b - - ShowPlaylistListMenu a b -> - Playlists.showListMenu a b - - TogglePlaylistVisibility a -> - Playlists.toggleVisibility a - - ----------------------------------------- - -- Routing - ----------------------------------------- - ChangeUrlUsingPage a -> - Common.changeUrlUsingPage a - - LinkClicked a -> - Routing.linkClicked a - - OpenUrlOnNewPage a -> - Routing.openUrlOnNewPage a - - PageChanged a -> - Routing.transition a - - UrlChanged a -> - Routing.urlChanged a - - ----------------------------------------- - -- Services - ----------------------------------------- - ConnectLastFm -> - Services.connectLastFm - - DisconnectLastFm -> - Services.disconnectLastFm - - GotLastFmSession a -> - Services.gotLastFmSession a - - Scrobble a -> - Services.scrobble a - - ----------------------------------------- - -- User - ----------------------------------------- - Export -> - User.export - - ImportFile a -> - User.importFile a - - ImportJson a -> - User.importJson a - - InsertDemo -> - User.insertDemo - - LoadEnclosedUserData a -> - User.loadEnclosedUserData a - - LoadHypaethralUserData a -> - User.loadHypaethralUserData a - - RequestImport -> - User.requestImport - - SaveEnclosedUserData -> - User.saveEnclosedUserData - - ----------------------------------------- - -- โš—๏ธ Adjunct - ----------------------------------------- - KeyboardMsg a -> - Adjunct.keyboardInput a - - ----------------------------------------- - -- ๐Ÿฆ‰ Nested - ----------------------------------------- - SyncingMsg a -> - Syncing.update a - - QueueMsg a -> - Queue.update a - - SourcesMsg a -> - Sources.update a - - TracksMsg a -> - Tracks.update a - - ----------------------------------------- - -- ๐Ÿ“ญ Other - ----------------------------------------- - InstalledServiceWorker -> - Other.installedServiceWorker - - InstallingServiceWorker -> - Other.installingServiceWorker - - RedirectToBrain a -> - Other.redirectToBrain a - - ReloadApp -> - Other.reloadApp - - SetCurrentTime a -> - Other.setCurrentTime a - - SetCurrentTimeZone a -> - Other.setCurrentTimeZone a - - SetIsOnline a -> - Other.setIsOnline a - - - --- ๐Ÿ“ฐ - - -subscriptions : Model -> Sub Msg -subscriptions _ = - Sub.batch - [ Ports.fromAlien alien - - ----------------------------------------- - -- Audio - ----------------------------------------- - , Ports.audioDurationChange AudioDurationChange - , Ports.audioEnded AudioEnded - , Ports.audioError AudioError - , Ports.audioPlaybackStateChanged AudioPlaybackStateChanged - , Ports.audioIsLoading AudioIsLoading - , Ports.audioHasLoaded AudioHasLoaded - , Ports.audioTimeUpdated AudioTimeUpdated - , Ports.requestPause (always Pause) - , Ports.requestPlay (always Play) - , Ports.requestPlayPause (always TogglePlay) - , Ports.requestStop (always Stop) - - ----------------------------------------- - -- Backdrop - ----------------------------------------- - , Ports.setAverageBackgroundColor ExtractedBackdropColor - - ----------------------------------------- - -- Interface - ----------------------------------------- - , Browser.Events.onResize Interface.onResize - , Ports.indicateTouchDevice (\_ -> SetIsTouchDevice True) - , Ports.lostWindowFocus (always LostWindowFocus) - , Ports.preferredColorSchemaChanged PreferredColorSchemaChanged - , Ports.showErrorNotification (Notifications.error >> ShowNotification) - , Ports.showStickyErrorNotification (Notifications.stickyError >> ShowNotification) - - ----------------------------------------- - -- Queue - ----------------------------------------- - , Ports.requestNext (\_ -> QueueMsg Queue.Shift) - , Ports.requestPrevious (\_ -> QueueMsg Queue.Rewind) - - ----------------------------------------- - -- Services - ----------------------------------------- - , Ports.scrobble Scrobble - - ----------------------------------------- - -- Tracks - ----------------------------------------- - , Ports.downloadTracksFinished (\_ -> TracksMsg Tracks.DownloadFinished) - , Ports.insertCoverCache (TracksMsg << Tracks.InsertCoverCache) - - ----------------------------------------- - -- ๐Ÿ“ญ Other - ----------------------------------------- - , Ports.installedNewServiceWorker (\_ -> InstalledServiceWorker) - , Ports.installingNewServiceWorker (\_ -> InstallingServiceWorker) - , Ports.refreshedAccessToken (Alien.broadcast Alien.RefreshedAccessToken >> RedirectToBrain) - , Ports.setIsOnline SetIsOnline - , Sub.map KeyboardMsg Keyboard.subscriptions - , Time.every (60 * 1000) SetCurrentTime - ] - - - --- ๐Ÿ‘ฝ - - -alien : Alien.Event -> Msg -alien event = - case ( event.error, Alien.tagFromString event.tag ) of - ( Nothing, Just tag ) -> - translateAlienData tag event.data - - ( Just err, Just tag ) -> - translateAlienError tag event.data err - - _ -> - Bypass - - -translateAlienData : Alien.Tag -> Json.Value -> Msg -translateAlienData tag data = - case tag of - Alien.AddTracks -> - TracksMsg (Tracks.Add data) - - Alien.FinishedProcessingSource -> - SourcesMsg (Sources.FinishedProcessingSource data) - - Alien.FinishedProcessingSources -> - SourcesMsg Sources.FinishedProcessing - - Alien.GotCachedCover -> - TracksMsg (Tracks.GotCachedCover data) - - Alien.HideLoadingScreen -> - ToggleLoadingScreen Off - - Alien.LoadEnclosedUserData -> - LoadEnclosedUserData data - - Alien.LoadHypaethralUserData -> - LoadHypaethralUserData data - - Alien.ReloadTracks -> - TracksMsg (Tracks.Reload data) - - Alien.RemoveTracksByPath -> - TracksMsg (Tracks.RemoveByPaths data) - - Alien.ReportProcessingError -> - SourcesMsg (Sources.ReportProcessingError data) - - Alien.ReportProcessingProgress -> - SourcesMsg (Sources.ReportProcessingProgress data) - - Alien.SearchTracks -> - TracksMsg (Tracks.SetSearchResults data) - - Alien.StartedSyncing -> - SyncingMsg (Syncing.StartedSyncing data) - - Alien.StoreTracksInCache -> - TracksMsg (Tracks.StoredInCache data Nothing) - - Alien.SyncMethod -> - SyncingMsg (Syncing.GotSyncMethod data) - - Alien.UpdateSourceData -> - SourcesMsg (Sources.UpdateSourceData data) - - _ -> - Bypass - - -translateAlienError : Alien.Tag -> Json.Value -> String -> Msg -translateAlienError tag data err = - case tag of - Alien.StoreTracksInCache -> - TracksMsg (Tracks.StoredInCache data <| Just err) - - _ -> - if String.startsWith "There seems to be existing data that's encrypted, I will need the passphrase" err then - SyncingMsg (Syncing.NeedEncryptionKey { error = err }) - - else - ShowNotification (Notifications.stickyError err) diff --git a/src/Core/UI/Adjunct.elm b/src/Core/UI/Adjunct.elm deleted file mode 100644 index 9d8832c93..000000000 --- a/src/Core/UI/Adjunct.elm +++ /dev/null @@ -1,185 +0,0 @@ -module UI.Adjunct exposing (..) - -import Keyboard -import Maybe.Extra as Maybe -import Return -import UI.Alfred.State as Alfred -import UI.Audio.State as Audio -import UI.Commands.State as Commands -import UI.Common.State as Common -import UI.Interface.State exposing (hideOverlay) -import UI.Page as Page -import UI.Playlists.Page as Playlists -import UI.Playlists.State as Playlists -import UI.Queue.Page as Queue -import UI.Queue.State as Queue -import UI.Settings.Page as Settings -import UI.Sources.Page as Sources -import UI.Tracks.State as Tracks -import UI.Types exposing (..) - - - --- ๐Ÿ“ฃ - - -keyboardInput : Keyboard.Msg -> Manager -keyboardInput msg model = - (\m -> - let - skip = - Return.singleton m - in - if m.focusedOnInput && Maybe.isNothing model.alfred then - case m.pressedKeys of - [ Keyboard.Escape ] -> - hideOverlay m - - _ -> - skip - - else if Maybe.isJust model.alfred then - case m.pressedKeys of - [ Keyboard.ArrowDown ] -> - Alfred.selectNext m - - [ Keyboard.ArrowUp ] -> - Alfred.selectPrevious m - - [ Keyboard.Enter ] -> - Alfred.runSelectedAction m - - [ Keyboard.Escape ] -> - hideOverlay m - - -- Meta key - -- - [ Keyboard.Character "K", Keyboard.Meta ] -> - Commands.showPalette m - - -- Ctrl key - -- - [ Keyboard.Character "K", Keyboard.Control ] -> - Commands.showPalette m - - [ Keyboard.Character "L", Keyboard.Control ] -> - Playlists.assistWithSelectingPlaylist m - - _ -> - skip - - else - case m.pressedKeys of - [ Keyboard.Character "[", Keyboard.Control ] -> - Queue.rewind m - - [ Keyboard.Character "]", Keyboard.Control ] -> - Queue.shift m - - [ Keyboard.Character "{", Keyboard.Shift, Keyboard.Control ] -> - case m.nowPlaying of - Just { duration, item, playbackPosition } -> - case duration of - Just d -> - Audio.seek - { trackId = (Tuple.second item.identifiedTrack).id - , progress = (playbackPosition - 10) / d - } - m - - Nothing -> - Return.singleton m - - Nothing -> - Return.singleton m - - [ Keyboard.Character "}", Keyboard.Shift, Keyboard.Control ] -> - case m.nowPlaying of - Just { duration, item, playbackPosition } -> - case duration of - Just d -> - Audio.seek - { trackId = (Tuple.second item.identifiedTrack).id - , progress = (playbackPosition + 10) / d - } - m - - Nothing -> - Return.singleton m - - Nothing -> - Return.singleton m - - -- Meta key - -- - [ Keyboard.Character "K", Keyboard.Meta ] -> - Commands.showPalette m - - -- Ctrl key - -- - [ Keyboard.Character "K", Keyboard.Control ] -> - Commands.showPalette m - - [ Keyboard.Character "L", Keyboard.Control ] -> - Playlists.assistWithSelectingPlaylist m - - [ Keyboard.Character "N", Keyboard.Control ] -> - Tracks.scrollToNowPlaying m - - [ Keyboard.Character "P", Keyboard.Control ] -> - Audio.playPause m - - [ Keyboard.Character "R", Keyboard.Control ] -> - Queue.toggleRepeat m - - [ Keyboard.Character "S", Keyboard.Control ] -> - Queue.toggleShuffle m - - -- - [ Keyboard.Character "1", Keyboard.Control ] -> - Common.changeUrlUsingPage Page.Index m - - [ Keyboard.Character "2", Keyboard.Control ] -> - Common.changeUrlUsingPage (Page.Playlists Playlists.Index) m - - [ Keyboard.Character "3", Keyboard.Control ] -> - Common.changeUrlUsingPage (Page.Queue Queue.Index) m - - [ Keyboard.Character "8", Keyboard.Control ] -> - Common.changeUrlUsingPage (Page.Sources Sources.Index) m - - [ Keyboard.Character "9", Keyboard.Control ] -> - Common.changeUrlUsingPage (Page.Settings Settings.Index) m - - -- - [ Keyboard.Escape ] -> - if Maybe.isJust m.contextMenu then - Return.singleton { m | contextMenu = Nothing } - - else if Maybe.isJust m.confirmation then - Return.singleton { m | confirmation = Nothing } - - else if Maybe.isJust m.alfred then - Return.singleton { m | alfred = Nothing } - - else if Maybe.isJust m.selectedCover then - Return.singleton { m | selectedCover = Nothing } - - else - case m.page of - Page.Playlists Playlists.Index -> - Return.singleton { m | page = Page.Index } - - Page.Playlists _ -> - Return.singleton { m | page = Page.Playlists Playlists.Index } - - Page.Queue _ -> - Return.singleton { m | page = Page.Index } - - _ -> - skip - - _ -> - skip - ) - { model | pressedKeys = Keyboard.update msg model.pressedKeys } diff --git a/src/Core/UI/Alfred/State.elm b/src/Core/UI/Alfred/State.elm deleted file mode 100644 index 0e646ebd4..000000000 --- a/src/Core/UI/Alfred/State.elm +++ /dev/null @@ -1,162 +0,0 @@ -module UI.Alfred.State exposing (..) - -import Alfred exposing (Alfred) -import Browser.Dom as Dom -import Keyboard -import Process -import Return exposing (return) -import Return.Ext as Return -import Task -import UI.Types as UI exposing (Manager) - - - --- ๐Ÿ“ฃ - - -assign : Alfred UI.Msg -> Manager -assign instance model = - let - pressedKeys = - List.filter - (\k -> - case k of - Keyboard.Meta -> - True - - Keyboard.Control -> - True - - _ -> - False - ) - model.pressedKeys - in - 250 - |> Process.sleep - |> Task.andThen (\_ -> Dom.focus "diffuse__alfred") - |> Task.andThen (\_ -> Dom.setViewportOf "alfred__results" 0 0) - |> Task.attempt (\_ -> UI.Bypass) - -- The "K" key seems to stick when using CMD + K, - -- aka. Meta key + K, to show the command palette. - -- https://github.com/ohanhi/keyboard/issues/14 - |> return { model | alfred = Just instance, pressedKeys = pressedKeys } - - -gotInput : String -> Manager -gotInput searchTerm model = - model.alfred - |> Maybe.map (determineResults searchTerm) - |> (\a -> Return.singleton { model | alfred = a }) - - -runAction : Int -> Manager -runAction index model = - case model.alfred of - Just instance -> - { result = Alfred.getAt index instance - , searchTerm = instance.searchTerm - } - |> instance.action - |> List.map Return.task - |> Cmd.batch - |> return { model | alfred = Nothing } - - Nothing -> - Return.singleton { model | alfred = Nothing } - - -runSelectedAction : Manager -runSelectedAction model = - case model.alfred of - Just instance -> - runAction instance.focus model - - Nothing -> - Return.singleton model - - -scrollToFocus : Manager -scrollToFocus model = - let - task = - Task.map3 - (\innerE outerE outerV -> - outerV.viewport.y + innerE.element.y - outerE.element.y - 9 - ) - (Dom.getElement "alfred__results__focus") - (Dom.getElement "alfred__results") - (Dom.getViewportOf "alfred__results") - in - task - |> Task.andThen (\a -> Dom.setViewportOf "alfred__results" 0 a) - |> Task.attempt (\_ -> UI.Bypass) - |> return model - - -selectNext : Manager -selectNext model = - case model.alfred of - Just instance -> - let - total = - Alfred.length instance - in - instance - |> (\i -> { i | focus = min (i.focus + 1) (total - 1) }) - |> (\i -> { model | alfred = Just i }) - |> scrollToFocus - - Nothing -> - Return.singleton model - - -selectPrevious : Manager -selectPrevious model = - case model.alfred of - Just instance -> - instance - |> (\i -> { i | focus = max (i.focus - 1) 0 }) - |> (\i -> { model | alfred = Just i }) - |> scrollToFocus - - Nothing -> - Return.singleton model - - - --- โš—๏ธ - - -determineResults : String -> Alfred UI.Msg -> Alfred UI.Msg -determineResults searchTerm alfred = - let - lowerSearchTerm = - searchTerm - |> String.toLower - |> String.trim - in - if String.length lowerSearchTerm > 0 then - { alfred - | focus = 0 - , searchTerm = - Just searchTerm - , results = - List.map - (\group -> - group.items - |> List.filter - (.title >> String.toLower >> String.contains lowerSearchTerm) - |> (\items -> - { group | items = items } - ) - ) - alfred.index - } - - else - { alfred - | focus = 0 - , searchTerm = Nothing - , results = alfred.index - } diff --git a/src/Core/UI/Audio/State.elm b/src/Core/UI/Audio/State.elm deleted file mode 100644 index ffe0dfe78..000000000 --- a/src/Core/UI/Audio/State.elm +++ /dev/null @@ -1,374 +0,0 @@ -module UI.Audio.State exposing (..) - -import Base64 -import Common exposing (boolToString) -import Debouncer.Basic as Debouncer -import Dict -import LastFm -import List.Extra as List -import MediaSession -import Return exposing (return) -import Return.Ext as Return exposing (communicate) -import Tracks -import UI.Audio.Types exposing (..) -import UI.Common.State as Common -import UI.Common.Types exposing (DebounceManager) -import UI.Ports as Ports -import UI.Queue.State as Queue -import UI.Types as UI exposing (Manager, Msg(..)) -import UI.User.State.Export as User - - - --- ๐Ÿ“ฃ โ–‘โ–‘ EVENTS - - -durationChange : DurationChangeEvent -> Manager -durationChange { trackId, duration } = - onlyIfMatchesNowPlaying - { trackId = trackId } - (\nowPlaying -> - if nowPlaying.duration == Nothing || nowPlaying.duration /= Just duration then - durationChange_ { trackId = trackId, duration = duration } nowPlaying - - else - -- Ignore repeating events - Return.singleton - ) - - -durationChange_ : DurationChangeEvent -> NowPlaying -> Manager -durationChange_ { trackId, duration } nowPlaying model = - let - ( identifiers, track ) = - nowPlaying.item.identifiedTrack - - maybeCover = - List.find - (\c -> List.member trackId c.trackIds) - model.covers.arranged - - coverPrep = - Maybe.map - (\cover -> - { cacheKey = Base64.encode (Tracks.coverKey cover.variousArtists track) - , trackFilename = identifiers.filename - , trackPath = track.path - , trackSourceId = track.sourceId - , variousArtists = boolToString cover.variousArtists - } - ) - maybeCover - - coverLoaded = - case ( maybeCover, model.cachedCovers ) of - ( Just cover, Just cachedCovers ) -> - let - key = - Base64.encode (Tracks.coverKey cover.variousArtists track) - in - Dict.member key cachedCovers - - _ -> - False - - metadata = - { album = track.tags.album - , artist = track.tags.artist - , title = track.tags.title - - -- - , coverPrep = coverPrep - } - in - model - |> replaceNowPlaying { nowPlaying | coverLoaded = coverLoaded, duration = Just duration } - |> Return.command (Ports.setMediaSessionMetadata metadata) - |> Return.command (Ports.resetScrobbleTimer { duration = duration, trackId = trackId }) - |> Return.andThen (notifyScrobblersOfTrackPlaying { duration = duration }) - - -error : ErrorAudioEvent -> Manager -error { trackId, code } = - onlyIfMatchesNowPlaying - { trackId = trackId } - (\nowPlaying -> - replaceNowPlaying - (case code of - 2 -> - { nowPlaying | loadingState = NetworkError } - - 3 -> - { nowPlaying | loadingState = DecodeError } - - 4 -> - { nowPlaying | loadingState = NotSupportedError } - - _ -> - nowPlaying - ) - ) - - -ended : GenericAudioEvent -> Manager -ended { trackId } = - onlyIfMatchesNowPlaying - { trackId = trackId } - (\nowPlaying model -> - if model.repeat then - Return.command - (case nowPlaying.duration of - Just duration -> - Ports.resetScrobbleTimer { duration = duration, trackId = trackId } - - Nothing -> - Cmd.none - ) - (play model) - - else - Return.andThen - (if Maybe.map (\d -> Tracks.shouldNoteProgress { duration = d }) nowPlaying.duration == Just True then - noteProgress { trackId = trackId, progress = 1.0 } - - else - Return.singleton - ) - (Queue.shift model) - ) - - -hasLoaded : GenericAudioEvent -> Manager -hasLoaded { trackId } = - onlyIfMatchesNowPlaying - { trackId = trackId } - (\nowPlaying -> - replaceNowPlaying { nowPlaying | loadingState = Loaded } - ) - - -isLoading : GenericAudioEvent -> Manager -isLoading { trackId } = - onlyIfMatchesNowPlaying - { trackId = trackId } - (\nowPlaying -> - replaceNowPlaying { nowPlaying | loadingState = Loading } - ) - - -playbackStateChanged : PlaybackStateEvent -> Manager -playbackStateChanged { trackId, isPlaying } = - onlyIfMatchesNowPlaying - { trackId = trackId } - (\nowPlaying model -> - { model | nowPlaying = Just { nowPlaying | isPlaying = isPlaying } } - |> Return.singleton - |> Return.command - (if isPlaying then - Ports.startScrobbleTimer () - - else - Ports.pauseScrobbleTimer () - ) - |> Return.command - (Ports.setMediaSessionPlaybackState - (if isPlaying then - MediaSession.states.playing - - else - MediaSession.states.paused - ) - ) - ) - - -timeUpdated : TimeUpdatedEvent -> Manager -timeUpdated { trackId, currentTime, duration } = - onlyIfMatchesNowPlaying - { trackId = trackId } - (\nowPlaying model -> - let - dur = - Maybe.withDefault 0 duration - in - { model | nowPlaying = Just { nowPlaying | duration = duration, playbackPosition = currentTime } } - |> (if Tracks.shouldNoteProgress { duration = dur } then - { trackId = trackId - , progress = currentTime / dur - } - |> NoteProgress - |> Debouncer.provideInput - |> NoteProgressDebounce - |> Return.task - |> Return.communicate - - else - Return.singleton - ) - |> Return.command - (case duration of - Just d -> - Ports.setMediaSessionPositionState - { currentTime = currentTime - , duration = d - } - - Nothing -> - Cmd.none - ) - ) - - - --- ๐Ÿ“ฃ โ–‘โ–‘ COMMANDS - - -pause : Manager -pause model = - case model.nowPlaying of - Just { item } -> - communicate - (Ports.pause - { trackId = (Tuple.second item.identifiedTrack).id - } - ) - model - - Nothing -> - Return.singleton model - - -playPause : Manager -playPause model = - case model.nowPlaying of - Just { isPlaying } -> - if isPlaying then - pause model - - else - play model - - Nothing -> - play model - - -play : Manager -play model = - case model.nowPlaying of - Just { item } -> - communicate - (Ports.play - { trackId = (Tuple.second item.identifiedTrack).id - , volume = model.eqSettings.volume - } - ) - model - - Nothing -> - Queue.shift model - - -seek : { trackId : String, progress : Float } -> Manager -seek { trackId, progress } = - { percentage = progress, trackId = trackId } - |> Ports.seek - |> Return.communicate - - -stop : Manager -stop model = - model.audioElements - |> List.filter (.isPreload >> (==) True) - |> (\a -> { model | audioElements = a }) - |> Queue.changeActiveItem Nothing - |> Return.effect_ - (\m -> - Ports.renderAudioElements - { items = m.audioElements - , play = Nothing - , volume = m.eqSettings.volume - } - ) - - - --- ๐Ÿ“ฃ - - -noteProgress : { trackId : String, progress : Float } -> Manager -noteProgress { trackId, progress } model = - let - updatedProgressTable = - if not model.rememberProgress then - model.progress - - else if progress > 0.975 then - Dict.remove trackId model.progress - - else - Dict.insert trackId progress model.progress - in - if model.rememberProgress then - User.saveProgress { model | progress = updatedProgressTable } - - else - Return.singleton model - - -noteProgressDebounce : DebounceManager -noteProgressDebounce = - Common.debounce - .progressDebouncer - (\d m -> { m | progressDebouncer = d }) - UI.NoteProgressDebounce - - -notifyScrobblersOfTrackPlaying : { duration : Float } -> Manager -notifyScrobblersOfTrackPlaying { duration } model = - case model.nowPlaying of - Just { item } -> - { duration = round duration - , msg = UI.Bypass - , track = Tuple.second item.identifiedTrack - } - |> LastFm.nowPlaying model.lastFm - |> return model - - Nothing -> - Return.singleton model - - -preloadDebounce : DebounceManager -preloadDebounce = - Common.debounce - .preloadDebouncer - (\d m -> { m | preloadDebouncer = d }) - UI.AudioPreloadDebounce - - -toggleRememberProgress : Manager -toggleRememberProgress model = - User.saveSettings { model | rememberProgress = not model.rememberProgress } - - - --- ๐Ÿ› ๏ธ - - -onlyIfMatchesNowPlaying : { trackId : String } -> (NowPlaying -> Manager) -> Manager -onlyIfMatchesNowPlaying { trackId } fn model = - case model.nowPlaying of - Just ({ item } as nowPlaying) -> - if trackId == (Tuple.second item.identifiedTrack).id then - fn nowPlaying model - - else - Return.singleton model - - Nothing -> - Return.singleton model - - -replaceNowPlaying : NowPlaying -> Manager -replaceNowPlaying np model = - Return.singleton { model | nowPlaying = Just np } diff --git a/src/Core/UI/Audio/Types.elm b/src/Core/UI/Audio/Types.elm deleted file mode 100644 index 6b58a0174..000000000 --- a/src/Core/UI/Audio/Types.elm +++ /dev/null @@ -1,69 +0,0 @@ -module UI.Audio.Types exposing (..) - -import Queue -import Tracks exposing (IdentifiedTrack) - - - --- ๐ŸŒณ - - -type AudioLoadingState - = Loading - | Loaded - -- Errors - | DecodeError - | NetworkError - | NotSupportedError - - -type alias CoverPrep = - { cacheKey : String - , trackFilename : String - , trackPath : String - , trackSourceId : String - , variousArtists : String - } - - -type alias NowPlaying = - { coverLoaded : Bool - , duration : Maybe Float - , isPlaying : Bool - , item : Queue.Item - , loadingState : AudioLoadingState - , playbackPosition : Float - } - - - --- ๐ŸŒณ โ–‘โ–‘ EVENTS - - -type alias DurationChangeEvent = - { trackId : String, duration : Float } - - -type alias ErrorAudioEvent = - { trackId : String, code : Int } - - -type alias GenericAudioEvent = - { trackId : String } - - -type alias PlaybackStateEvent = - { trackId : String, isPlaying : Bool } - - -type alias TimeUpdatedEvent = - { trackId : String, currentTime : Float, duration : Maybe Float } - - - --- ๐Ÿ› ๏ธ - - -nowPlayingIdentifiedTrack : NowPlaying -> IdentifiedTrack -nowPlayingIdentifiedTrack { item } = - item.identifiedTrack diff --git a/src/Core/UI/Backdrop.elm b/src/Core/UI/Backdrop.elm deleted file mode 100644 index 2bc1481c4..000000000 --- a/src/Core/UI/Backdrop.elm +++ /dev/null @@ -1,251 +0,0 @@ -module UI.Backdrop exposing (..) - -import Chunky exposing (..) -import Color -import Html exposing (Html) -import Html.Attributes exposing (src, style) -import Html.Events exposing (on) -import Html.Lazy as Lazy -import Json.Decode -import Return exposing (return) -import UI.Ports as Ports -import UI.Types exposing (..) -import UI.User.State.Export as User - - - --- โ›ฉ - - -default : String -default = - "21.jpg" - - -options : List ( String, String ) -options = - [ ( "1.jpg", "Option 1" ) - , ( "2.jpg", "Option 2" ) - , ( "3.jpg", "Option 3" ) - , ( "4.jpg", "Option 4" ) - , ( "5.jpg", "Option 5" ) - , ( "6.jpg", "Option 6" ) - , ( "7.jpg", "Option 7" ) - , ( "8.jpg", "Option 8" ) - , ( "9.jpg", "Option 9" ) - , ( "10.jpg", "Option 10" ) - , ( "11.jpg", "Option 11" ) - , ( "12.jpg", "Option 12" ) - , ( "13.jpg", "Option 13" ) - , ( "14.jpg", "Option 14" ) - , ( "15.jpg", "Option 15" ) - , ( "16.jpg", "Option 16" ) - , ( "17.jpg", "Option 17" ) - , ( "18.jpg", "Option 18" ) - , ( "19.jpg", "Option 19" ) - , ( "20.jpg", "Option 20" ) - , ( "21.jpg", "Option 21 (default)" ) - , ( "22.jpg", "Option 22" ) - , ( "23.jpg", "Option 23" ) - , ( "24.jpg", "Option 24" ) - , ( "25.jpg", "Option 25" ) - , ( "26.jpg", "Option 26" ) - , ( "27.jpg", "Option 27" ) - , ( "28.jpg", "Option 28" ) - , ( "29.jpg", "Option 29" ) - , ( "30.jpg", "Option 30" ) - ] - - - --- ๐Ÿ“ฃ - - -extractedBackdropColor : { r : Int, g : Int, b : Int } -> Manager -extractedBackdropColor { r, g, b } model = - Return.singleton { model | extractedBackdropColor = Just (Color.rgb255 r g b) } - - -chooseBackdrop : String -> Manager -chooseBackdrop backdrop model = - User.saveSettings { model | chosenBackdrop = Just backdrop } - - -loadBackdrop : String -> Manager -loadBackdrop backdrop model = - return - { model | loadedBackdrops = model.loadedBackdrops ++ [ backdrop ] } - (Ports.pickAverageBackgroundColor backdrop) - - -setDefault : Manager -setDefault model = - Return.singleton { model | chosenBackdrop = Just default } - - - --- ๐Ÿ—บ - - -view : Model -> Html Msg -view model = - chunk - [ "fixed" - , "-inset-px" - , "z-0" - ] - [ Lazy.lazy chosen model.chosenBackdrop - , Lazy.lazy2 loaded model.loadedBackdrops model.fadeInBackdrop - - -- Shadow - --------- - , brick - [ style "background" "linear-gradient(#0000, rgba(0, 0, 0, 0.175))" ] - [ "absolute" - , "bottom-0" - , "h-64" - , "inset-x-0" - , "z-10" - ] - [] - ] - - -backgroundImage : String -> Html.Attribute msg -backgroundImage backdrop = - style "background-image" ("url(images/Background/" ++ backdrop ++ ")") - - -backgroundPositioning : String -> Html.Attribute msg -backgroundPositioning filename = - case filename of - "2.jpg" -> - style "background-position" "center 68%" - - "3.jpg" -> - style "background-position" "center 30%" - - "4.jpg" -> - style "background-position" "center 96.125%" - - "6.jpg" -> - style "background-position" "center 40%" - - "11.jpg" -> - style "background-position" "center 67.25%" - - "19.jpg" -> - style "background-position" "center 13%" - - "20.jpg" -> - style "background-position" "center 39.75%" - - "21.jpg" -> - style "background-position" "center 52.5%" - - "22.jpg" -> - style "background-position" "center top" - - "23.jpg" -> - style "background-position" "center 92.5%" - - "24.jpg" -> - style "background-position" "center top" - - "25.jpg" -> - style "background-position" "center 50%" - - "27.jpg" -> - style "background-position" "center top" - - _ -> - style "background-position" "center bottom" - - - ------------------------------------------ --- ใŠ™๏ธ ------------------------------------------ - - -chosen : Maybe String -> Html Msg -chosen maybeChosen = - case maybeChosen of - Just c -> - let - loadingDecoder = - c - |> LoadBackdrop - |> Json.Decode.succeed - in - slab - Html.img - [ on "load" loadingDecoder - , src ("images/Background/" ++ c) - , style "opacity" "0.00001" - ] - [ "fixed" - , "h-px" - , "left-full" - , "overflow-hidden" - , "top-full" - , "w-px" - ] - [] - - Nothing -> - nothing - - -loaded : List String -> Bool -> Html Msg -loaded list fadeIn = - let - amount = - List.length list - - indexedMapFn idx = - image fadeIn (idx + 1 < amount) - in - list - |> List.indexedMap indexedMapFn - |> raw - - -image : Bool -> Bool -> String -> Html msg -image fadeIn isPrevious loadedBackdrop = - let - defaultClasses = - [ "bg-cover" - , "fixed" - , "inset-0" - - -- Opacity - ---------- - , if isPrevious || not fadeIn then - "opacity-100" - - else - "opacity-0" - ] - - animationClasses = - if not isPrevious && fadeIn then - [ "animation-2s" - , "animation-delay-50ms" - , "animation-fadeIn" - , "animation-fill-forwards" - , "animation-once" - ] - - else - [] - in - brick - [ backgroundImage loadedBackdrop - , backgroundPositioning loadedBackdrop - ] - (List.append - defaultClasses - animationClasses - ) - [] diff --git a/src/Core/UI/Commands/Alfred.elm b/src/Core/UI/Commands/Alfred.elm deleted file mode 100644 index 1fab0dcd9..000000000 --- a/src/Core/UI/Commands/Alfred.elm +++ /dev/null @@ -1,492 +0,0 @@ -module UI.Commands.Alfred exposing (commands, palette) - -import Alfred exposing (..) -import Conditional exposing (ifThenElse) -import List.Extra as List -import Material.Icons.Round as Icons -import Playlists.Matching -import Tracks exposing (Grouping(..), SortBy(..)) -import UI.Page as Page -import UI.Queue.Types as Queue -import UI.Sources.Page as Sources -import UI.Sources.Types as Sources -import UI.Tracks.Types as Tracks -import UI.Types as UI - - -palette : UI.Model -> Alfred UI.Msg -palette model = - Alfred.create - { action = action - , index = commands model - , message = "Run a command." - , operation = Query - } - - - --- โ›ฐ - - -commands : UI.Model -> List (Alfred.Group UI.Msg) -commands model = - [ { name = Just "Currently playing", items = nowPlayingCommands model } - , { name = Just "Track selection", items = selectionCommands model } - , { name = Just "Collection / Playlist", items = playlistCommands model } - , { name = Just "Tracks", items = tracksCommands model } - , { name = Just "View", items = viewCommands model } - , { name = Just "Playback", items = playbackCommands model } - , { name = Just "Sources", items = sourcesCommands model } - , { name = Just "Data", items = dataCommands model } - , { name = Just "Misc", items = miscCommands model } - ] - - - --- - - -dataCommands model = - [ { icon = Just (Icons.offline_bolt 16) - , title = "Clear tracks cache" - , value = Command (UI.TracksMsg Tracks.ClearCache) - } - , { icon = Just (Icons.save 16) - , title = "Download data snapshot" - , value = Command UI.Export - } - , { icon = Just (Icons.save 16) - , title = "Import data snapshot (โš ๏ธ will override current data)" - , value = Command UI.RequestImport - } - ] - - -miscCommands model = - [ { icon = Just (Icons.help 16) - , title = "Open help" - , value = Command (UI.OpenUrlOnNewPage "./about/#UI") - } - ] - - -nowPlayingCommands : UI.Model -> List (Item UI.Msg) -nowPlayingCommands model = - case model.nowPlaying of - Just { item } -> - let - ( queueItemIdentifiers, _ ) = - item.identifiedTrack - - identifiedTrack = - model.tracks.harvested - |> List.getAt queueItemIdentifiers.indexInList - |> Maybe.withDefault item.identifiedTrack - - ( identifiers, track ) = - identifiedTrack - in - [ { icon = Just (Icons.search 16) - , title = "Show current track in list" - , value = Command (UI.TracksMsg Tracks.ScrollToNowPlaying) - } - - -- - , { icon = Just (Icons.favorite 14) - , title = ifThenElse identifiers.isFavourite "Remove favourite" "Mark as favourite" - , value = Command (UI.TracksMsg <| Tracks.ToggleFavourite identifiers.indexInList) - } - - -- - , { icon = Just (Icons.queue_music 16) - , title = "Add current track to collection" - , value = Command (UI.AssistWithAddingTracksToCollection <| [ identifiedTrack ]) - } - - -- - , { icon = Just (Icons.queue_music 16) - , title = "Add current track to playlist" - , value = Command (UI.AssistWithAddingTracksToPlaylist <| [ identifiedTrack ]) - } - - -- - , { icon = Just (Icons.offline_bolt 16) - , title = "Add current track to cache" - , value = - [ track ] - |> Tracks.StoreInCache - |> UI.TracksMsg - |> Command - } - ] - - Nothing -> - [] - - -playbackCommands model = - [ if Maybe.map .isPlaying model.nowPlaying == Just True then - { icon = Just (Icons.pause 16) - , title = "Pause" - , value = Command UI.TogglePlay - } - - else - { icon = Just (Icons.play_arrow 16) - , title = "Play" - , value = Command UI.TogglePlay - } - - -- - , { icon = Just (Icons.fast_rewind 18) - , title = "Previous track" - , value = Command (UI.QueueMsg Queue.Rewind) - } - , { icon = Just (Icons.fast_forward 18) - , title = "Next track" - , value = Command (UI.QueueMsg Queue.Shift) - } - , { icon = Just (Icons.repeat 16) - , title = toggle model.repeat "repeat" - , value = Command (UI.QueueMsg Queue.ToggleRepeat) - } - , { icon = Just (Icons.shuffle 16) - , title = toggle model.shuffle "shuffle" - , value = Command (UI.QueueMsg Queue.ToggleShuffle) - } - ] - - -playlistCommands model = - let - selection = - case model.selectedPlaylist of - Just playlist -> - let - identifiedTracksFromPlaylist = - model.tracks.identified - |> Playlists.Matching.match playlist - |> Tuple.first - - tracksFromPlaylist = - identifiedTracksFromPlaylist - |> (if playlist.collection then - identity - - else - Tracks.sortByIndexInPlaylist - ) - |> List.map Tuple.second - in - [ { icon = Just (Icons.waves 16) - , title = "Deactivate " ++ ifThenElse playlist.collection "collection" "playlist" - , value = Command UI.DeselectPlaylist - } - - -- - , { icon = Just (Icons.update 16) - , title = "Add to queue" - , value = - { inFront = False, tracks = identifiedTracksFromPlaylist } - |> Queue.AddTracks - |> UI.QueueMsg - |> Command - } - - -- - , { icon = Just (Icons.offline_bolt 16) - , title = "Store in cache" - , value = - tracksFromPlaylist - |> Tracks.StoreInCache - |> UI.TracksMsg - |> Command - } - - -- - , { icon = Just (Icons.archive 16) - , title = "Download as zip file" - , value = - tracksFromPlaylist - |> Tracks.Download - { prefixTrackNumber = not playlist.collection - , zipName = playlist.name - } - |> UI.TracksMsg - |> Command - } - - -- - , { icon = Just (Icons.waves 16) - , title = - if playlist.collection then - "Convert to playlist" - - else - "Convert to collection" - , value = - if playlist.collection then - { name = playlist.name } - |> UI.ConvertCollectionToPlaylist - |> Command - - else - { name = playlist.name } - |> UI.ConvertPlaylistToCollection - |> Command - } - ] - - Nothing -> - [] - in - [ { icon = Just (Icons.waves 16) - , title = - case model.selectedPlaylist of - Just _ -> - "Select other collection or playlist" - - Nothing -> - "Select collection or playlist" - , value = Command UI.AssistWithSelectingPlaylist - } - ] - ++ selection - - -selectionCommands model = - let - ( selection, _, amountOfFavs ) = - List.foldr - (\( i, t ) ( acc, selected, favouriteCounter ) -> - case List.findIndex ((==) i.indexInList) selected of - Just s -> - ( ( i, t ) :: acc - , List.removeAt s selected - , ifThenElse i.isFavourite (favouriteCounter + 1) favouriteCounter - ) - - Nothing -> - ( acc, selected, favouriteCounter ) - ) - ( [] - , model.selectedTrackIndexes - , 0 - ) - model.tracks.harvested - in - case selection of - [] -> - [] - - tracks -> - List.concat - [ [ { icon = Just (Icons.queue_music 16) - , title = "Add current selection to collection" - , value = Command (UI.AssistWithAddingTracksToCollection tracks) - } - , { icon = Just (Icons.queue_music 16) - , title = "Add current selection to playlist" - , value = Command (UI.AssistWithAddingTracksToPlaylist tracks) - } - ] - - -- - , if amountOfFavs > 0 then - [ { icon = Just (Icons.favorite 14) - , title = "Remove current selection from favourites" - , value = Command (UI.TracksMsg <| Tracks.RemoveFavourites tracks) - } - ] - - else - [] - - -- - , if amountOfFavs < List.length selection then - [ { icon = Just (Icons.favorite 14) - , title = "Add current selection to favourites" - , value = Command (UI.TracksMsg <| Tracks.AddFavourites tracks) - } - ] - - else - [] - ] - - -sourcesCommands model = - [ { icon = Just (Icons.sync 16) - , title = "Process sources" - , value = Command (UI.SourcesMsg Sources.Process) - } - - -- - , { icon = Just (Icons.add 16) - , title = "Add new source" - , value = Command (UI.ChangeUrlUsingPage <| Page.Sources Sources.New) - } - ] - - -tracksCommands model = - let - groupCommands = - [ AddedOn, Directory, FirstAlphaCharacter, TrackYear ] - |> (case model.grouping of - Just group -> - List.remove group - - Nothing -> - identity - ) - |> List.map - (\group -> - { icon = - Just (Icons.library_music 16) - , title = - case group of - AddedOn -> - "Group by processing date" - - Directory -> - "Group by directory" - - FirstAlphaCharacter -> - "Group by first letter" - - TrackYear -> - "Group by track year" - , value = - Command (UI.TracksMsg <| Tracks.GroupBy group) - } - ) - |> (\list -> - case model.grouping of - Just _ -> - { icon = Just (Icons.library_music 16) - , title = "Disable grouping" - , value = Command (UI.TracksMsg Tracks.DisableGrouping) - } - :: list - - Nothing -> - list - ) - in - [ { icon = Just (Icons.favorite 14) - , title = toggle model.favouritesOnly "favourites-only mode" - , value = Command (UI.TracksMsg Tracks.ToggleFavouritesOnly) - } - - -- - , { icon = Just (Icons.filter_list 16) - , title = toggle model.cachedTracksOnly "cached-tracks-only mode" - , value = Command (UI.TracksMsg Tracks.ToggleCachedOnly) - } - ] - ++ groupCommands - ++ [ { icon = Just (Icons.filter_list 16) - , title = - if model.hideDuplicates then - "Allow duplicates" - - else - "Remove duplicates" - , value = Command (UI.TracksMsg Tracks.ToggleHideDuplicates) - } - - -- - , { icon = Just (Icons.photo 16) - , title = - if model.coverSelectionReducesPool then - "Track pool is limited to selected cover (Select to disable)" - - else - "Track pool is not restricted by selected cover (Select to enable)" - , value = Command (UI.TracksMsg Tracks.ToggleCoverSelectionReducesPool) - } - ] - - -viewCommands model = - let - sortCommands = - (case Maybe.andThen .autoGenerated model.selectedPlaylist of - Nothing -> - [] - - _ -> - case model.scene of - Tracks.Covers -> - [ Album, Artist ] - - Tracks.List -> - [ Album, Artist, Title ] - ) - |> List.remove - model.sortBy - |> List.map - (\sortBy -> - { icon = - Just (Icons.sort 16) - , title = - case sortBy of - Artist -> - "Sort tracks by artist" - - Album -> - "Sort tracks by album" - - PlaylistIndex -> - "Sort tracks by playlist index" - - Title -> - "Sort tracks by title" - , value = - Command (UI.TracksMsg <| Tracks.SortBy sortBy) - } - ) - in - [ case model.scene of - Tracks.Covers -> - { icon = Just (Icons.notes 16) - , title = "Switch to list view" - , value = Command (UI.TracksMsg <| Tracks.ChangeScene Tracks.List) - } - - Tracks.List -> - { icon = Just (Icons.burst_mode 18) - , title = "Switch to cover view" - , value = Command (UI.TracksMsg <| Tracks.ChangeScene Tracks.Covers) - } - - -- - , { icon = Just (Icons.sort 16) - , title = "Change sort direction" - , value = Command (UI.TracksMsg <| Tracks.SortBy model.sortBy) - } - ] - ++ sortCommands - ++ [ { icon = Just (Icons.brush 14) - , title = "Change application theme" - , value = Command UI.AssistWithChangingTheme - } - ] - - - --- ใŠ™๏ธ - - -action { result } = - case Maybe.andThen (.value >> Alfred.command) result of - Just msg -> - [ msg ] - - Nothing -> - [] - - -toggle bool suffix = - ifThenElse bool "Disable" "Enable" ++ " " ++ suffix diff --git a/src/Core/UI/Commands/State.elm b/src/Core/UI/Commands/State.elm deleted file mode 100644 index 3e6237811..000000000 --- a/src/Core/UI/Commands/State.elm +++ /dev/null @@ -1,16 +0,0 @@ -module UI.Commands.State exposing (..) - -import UI.Alfred.State as Alfred -import UI.Commands.Alfred -import UI.Types exposing (Manager) - - - --- ๐Ÿ“ฃ - - -showPalette : Manager -showPalette model = - Alfred.assign - (UI.Commands.Alfred.palette model) - model diff --git a/src/Core/UI/Common/State.elm b/src/Core/UI/Common/State.elm deleted file mode 100644 index 7774a1ff1..000000000 --- a/src/Core/UI/Common/State.elm +++ /dev/null @@ -1,149 +0,0 @@ -module UI.Common.State exposing (..) - -import Browser.Navigation as Nav -import Common exposing (..) -import ContextMenu exposing (ContextMenu) -import Debouncer.Basic as Debouncer exposing (Debouncer) -import List.Extra as List -import Maybe.Extra as Maybe -import Monocle.Lens as Lens exposing (Lens) -import Notifications exposing (Notification) -import Return exposing (return) -import UI.Notifications -import UI.Page as Page exposing (Page) -import UI.Playlists.Directory -import UI.Syncing.Types as Syncing -import UI.Types as UI exposing (Manager, Model, Msg) -import User.Layer exposing (Method) - - - --- ๐Ÿ“ฃ - - -changeUrlUsingPage : Page -> Manager -changeUrlUsingPage page model = - page - |> Page.toString - |> Nav.pushUrl model.navKey - |> return model - - -debounce : (Model -> Debouncer Msg Msg) -> (Debouncer Msg Msg -> Model -> Model) -> (Debouncer.Msg Msg -> Msg) -> (Msg -> Model -> ( Model, Cmd Msg )) -> Debouncer.Msg Msg -> Manager -debounce getter setter debouncerMsgContainer update debouncerMsg model = - let - ( subModel, subCmd, emittedMsg ) = - Debouncer.update debouncerMsg (getter model) - - mappedCmd = - Cmd.map debouncerMsgContainer subCmd - - updatedModel = - setter subModel model - in - case emittedMsg of - Just emitted -> - updatedModel - |> update emitted - |> Return.command mappedCmd - - Nothing -> - return updatedModel mappedCmd - - -dismissNotification : { id : Int } -> Manager -dismissNotification options model = - options - |> UI.Notifications.dismiss model.notifications - |> Return.map (\n -> { model | notifications = n }) - - -forceTracksRerender : Manager -forceTracksRerender model = - -- - -- TODO: - -- - -- let - -- containerId = - -- case model.scene of - -- Tracks.Covers -> - -- UI.Tracks.Scene.Covers.containerId - -- Tracks.List -> - -- UI.Tracks.Scene.List.containerId - -- in - -- Browser.Dom.setViewportOf containerId 0 1 - -- |> Task.attempt (always UI.Bypass) - -- |> return model - -- - Return.singleton model - - -generateDirectoryPlaylists : Manager -generateDirectoryPlaylists model = - let - nonDirectoryPlaylists = - List.filterNot - (.autoGenerated >> Maybe.isJust) - model.playlists - - directoryPlaylists = - UI.Playlists.Directory.generate - model.sources - model.tracks.untouched - in - [ nonDirectoryPlaylists - , directoryPlaylists - ] - |> List.concat - |> (\c -> { model | playlists = c }) - |> Return.singleton - - -showContextMenuWithModel : UI.Model -> ContextMenu Msg -> ( UI.Model, Cmd UI.Msg ) -showContextMenuWithModel model contextMenu = - Return.singleton { model | contextMenu = Just contextMenu } - - -showNotification : Notification Msg -> Manager -showNotification notification model = - model.notifications - |> UI.Notifications.show notification - |> Return.map (\n -> { model | notifications = n }) - - -showNotificationWithModel : UI.Model -> Notification Msg -> ( UI.Model, Cmd UI.Msg ) -showNotificationWithModel model notification = - showNotification notification model - - -showSyncingNotification : Method -> Manager -showSyncingNotification method model = - let - notification = - Notifications.stickyCasual "Syncing user data ..." - - syncing = - Syncing.Syncing { method = method, notificationId = Notifications.id notification } - in - showNotification - notification - { model | syncing = syncing } - - -toggleLoadingScreen : Switch -> Manager -toggleLoadingScreen switch model = - case switch of - On -> - Return.singleton { model | isLoading = True } - - Off -> - Return.singleton { model | isLoading = False } - - - --- ๐Ÿ›  - - -modifySingleton : Lens a b -> (b -> b) -> a -> ( a, Cmd msg ) -modifySingleton lens modifier = - Lens.modify lens modifier >> Return.singleton diff --git a/src/Core/UI/Common/Types.elm b/src/Core/UI/Common/Types.elm deleted file mode 100644 index 4853e7a43..000000000 --- a/src/Core/UI/Common/Types.elm +++ /dev/null @@ -1,12 +0,0 @@ -module UI.Common.Types exposing (..) - -import Debouncer.Basic as Debouncer -import UI.Types exposing (Manager, Model, Msg) - - - --- ๐ŸŒณ - - -type alias DebounceManager = - (Msg -> Model -> ( Model, Cmd Msg )) -> Debouncer.Msg Msg -> Manager diff --git a/src/Core/UI/Demo.elm b/src/Core/UI/Demo.elm deleted file mode 100644 index 2b1c8b799..000000000 --- a/src/Core/UI/Demo.elm +++ /dev/null @@ -1,203 +0,0 @@ -module UI.Demo exposing (tape) - -import Base64 -import Dict -import Json.Encode as Json -import Sources exposing (Service(..), Source) -import Sources.Encoding as Sources -import Time -import Tracks exposing (Favourite, Track) -import Tracks.Encoding as Tracks - - - --- โ›ฉ - - -tape : Time.Posix -> Json.Value -tape currentTime = - Json.object - [ ( "favourites", Json.list Tracks.encodeFavourite favourites ) - , ( "sources", Json.list Sources.encode sources ) - , ( "tracks", Json.list Tracks.encodeTrack <| tracks currentTime ) - ] - - - --- ใŠ™๏ธ - - -favourites : List Favourite -favourites = - [ { artist = Just "James Blake" - , title = "Essential Mix (09-17-2011)" - } - ] - - -sources : List Source -sources = - let - key = - "QUtJQTZPUTNFVk1BWFZDRFFINkI=" - |> Base64.decode - |> Result.withDefault "" - - secret = - "Z0hPQkdHRzU1aXc0a0RDbjdjWlRJYTVTUDRZWnpERkRzQnFCYWI4Mg==" - |> Base64.decode - |> Result.withDefault "" - in - [ { id = "15076402187342" - , data = - Dict.fromList - [ ( "accessKey", key ) - , ( "bucketName", "ongaku-ryoho-demo" ) - , ( "directoryPath", "/" ) - , ( "name", "Demo" ) - , ( "region", "us-east-1" ) - , ( "secretKey", secret ) - ] - , directoryPlaylists = True - , enabled = True - , service = AmazonS3 - } - ] - - -tracks : Time.Posix -> List Track -tracks insertedAt = - [ { id = "MTUwNzY0MDIxODczNDIvL0ZyZWUgbXVzaWMvKFNXTDAxMyktb3JpSmFudXMtV0VCLTIwMTQtRlJFRS8wMS1ib25pdGEubXAz" - , path = "Free music/(SWL013)-oriJanus-WEB-2014-FREE/01-bonita.mp3" - , sourceId = "15076402187342" - , insertedAt = insertedAt - , tags = - { disc = 1 - , nr = 1 - - -- - , album = Just "Soulection White Label: 013" - , artist = Just "oriJanus" - , title = "Bonita" - - -- - , genre = Just "Soulection" - , picture = Nothing - , year = Nothing - } - } - , { id = "MTUwNzY0MDIxODczNDIvL0ZyZWUgbXVzaWMvKFNXTDAxMyktb3JpSmFudXMtV0VCLTIwMTQtRlJFRS8wMi02Lm1wMw" - , path = "Free music/(SWL013)-oriJanus-WEB-2014-FREE/02-6.mp3" - , sourceId = "15076402187342" - , insertedAt = insertedAt - , tags = - { disc = 1 - , nr = 2 - - -- - , album = Just "Soulection White Label: 013" - , artist = Just "oriJanus" - , title = "6" - - -- - , genre = Just "Soulection" - , picture = Nothing - , year = Nothing - } - } - , { id = "MTUwNzY0MDIxODczNDIvL0ZyZWUgbXVzaWMvKFNXTDAxMyktb3JpSmFudXMtV0VCLTIwMTQtRlJFRS8wMy1ob3RfcmVtaXhfZnQuX3Rlay5sdW5fJl96aWtvbW8ubXAz" - , path = "Free music/(SWL013)-oriJanus-WEB-2014-FREE/03-hot_remix_ft._tek.lun_&_zikomo.mp3" - , sourceId = "15076402187342" - , insertedAt = insertedAt - , tags = - { disc = 1 - , nr = 3 - - -- - , album = Just "Soulection White Label: 013" - , artist = Just "oriJanus" - , title = "Hot Remix ft. Tek.Lun & Zikomo" - - -- - , genre = Just "Soulection" - , picture = Nothing - , year = Nothing - } - } - , { id = "MTUwNzY0MDIxODczNDIvL0ZyZWUgbXVzaWMvQ29tX1RydWlzZS1DaGVtaWNhbF9MZWdzLTIwMTItRlJFRS8wMS1jb21fdHJ1aXNlLWNoZW1pY2FsX2xlZ3MubXAz" - , path = "Free music/Com_Truise-Chemical_Legs-2012-FREE/01-com_truise-chemical_legs.mp3" - , sourceId = "15076402187342" - , insertedAt = insertedAt - , tags = - { disc = 1 - , nr = 9 - - -- - , album = Just "Adult Swim Singles Project 2012" - , artist = Just "Com Truise" - , title = "Chemical Legs" - - -- - , genre = Nothing - , picture = Nothing - , year = Just 2012 - } - } - , { id = "MTUwNzY0MDIxODczNDIvL0ZyZWUgbXVzaWMvTWFudWVsZV9BdHplbmlfLV8wNF8tX0xpdHRsZV9TdGFyLm1wMw" - , path = "Free music/Manuele_Atzeni_-_04_-_Little_Star.mp3" - , sourceId = "15076402187342" - , insertedAt = insertedAt - , tags = - { disc = 1 - , nr = 4 - - -- - , album = Just "The Miyazaki Tour EP" - , artist = Just "Manuele Atzeni" - , title = "Little Star" - - -- - , genre = Just "Funk" - , picture = Nothing - , year = Nothing - } - } - , { id = "MTUwNzY0MDIxODczNDIvL0ZyZWUgbXVzaWMvUGF0cmlja19MZWVfLV8wMl8tX1F1aXR0aW5fVGltZS5tcDM" - , path = "Free music/Patrick_Lee_-_02_-_Quittin_Time.mp3" - , sourceId = "15076402187342" - , insertedAt = insertedAt - , tags = - { disc = 1 - , nr = 2 - - -- - , album = Just "The Last Thing" - , artist = Just "Patrick Lee" - , title = "Quittin' Time" - - -- - , genre = Just "Electronic" - , picture = Nothing - , year = Nothing - } - } - , { id = "MTUwNzY0MDIxODczNDIvL1JhZGlvL2phbWVzX2JsYWtlLWVzc2VudGlhbF9taXgtc2F0LTA5LTE3LTIwMTEubXAz" - , path = "Radio/james_blake-essential_mix-sat-09-17-2011.mp3" - , sourceId = "15076402187342" - , insertedAt = insertedAt - , tags = - { disc = 1 - , nr = 1 - - -- - , album = Just "Essential Mix-SAT-09-17" - , artist = Just "James Blake" - , title = "Essential Mix (09-17-2011)" - - -- - , genre = Just "Electronic" - , picture = Nothing - , year = Nothing - } - } - ] diff --git a/src/Core/UI/DnD.elm b/src/Core/UI/DnD.elm deleted file mode 100644 index 4ae11ed29..000000000 --- a/src/Core/UI/DnD.elm +++ /dev/null @@ -1,245 +0,0 @@ -module UI.DnD exposing (Environment, Model, Msg, hasDropped, initialModel, isBeingDraggedOver, isDragging, isDraggingOver, listenToEnterLeave, listenToStart, modelSubject, modelTarget, startDragging, stoppedDragging, update) - -import Html exposing (Attribute) -import Html.Events.Extra.Mouse as Mouse -import Html.Events.Extra.Pointer as Pointer - - - --- ๐ŸŒณ - - -type Model context - = NotDragging - | Dragging { subject : context } - | DraggingOver { subject : context, target : context } - | Dropped { subject : context, target : context } - - -type Msg context - = Start context - | Enter context - | Leave context - | Stop - - -type alias Environment context msg = - { model : Model context - , toMsg : Msg context -> msg - } - - -type alias Response = - { initiated : Bool } - - -initialModel : Model context -initialModel = - NotDragging - - - --- ๐Ÿ“ฃ - - -update : Msg context -> Model context -> ( Model context, Response ) -update msg model = - ( ------------------------------------ - -- Model - ------------------------------------ - case msg of - Start context -> - Dragging { subject = context } - - Enter context -> - case model of - NotDragging -> - NotDragging - - Dragging { subject } -> - DraggingOver { subject = subject, target = context } - - DraggingOver { subject } -> - DraggingOver { subject = subject, target = context } - - Dropped _ -> - NotDragging - - Leave context -> - case model of - NotDragging -> - NotDragging - - Dragging env -> - Dragging env - - DraggingOver { subject, target } -> - if context == target then - Dragging { subject = subject } - - else - model - - Dropped _ -> - NotDragging - - Stop -> - case model of - DraggingOver { subject, target } -> - if subject /= target then - Dropped { subject = subject, target = target } - - else - NotDragging - - _ -> - NotDragging - ------------------------------------ - -- Response - ------------------------------------ - , case msg of - Start _ -> - { initiated = True } - - _ -> - { initiated = False } - ) - - - --- ๐Ÿ”ฑ โ–‘โ–‘ EVENTS & MESSAGES - - -listenToStart : Environment context msg -> context -> Attribute msg -listenToStart { toMsg } context = - Pointer.onWithOptions - "pointerdown" - { stopPropagation = True - , preventDefault = False - } - (\event -> - case ( event.pointer.button, event.isPrimary ) of - ( Mouse.MainButton, True ) -> - toMsg (Start context) - - _ -> - toMsg Stop - ) - - -listenToEnterLeave : Environment context msg -> context -> List (Attribute msg) -listenToEnterLeave { model, toMsg } context = - case model of - NotDragging -> - [] - - _ -> - [ context - |> Enter - |> toMsg - |> always - |> Pointer.onEnter - , context - |> Leave - |> toMsg - |> always - |> Pointer.onLeave - ] - - -startDragging : context -> Msg context -startDragging = - Start - - -stoppedDragging : Msg context -stoppedDragging = - Stop - - - --- ๐Ÿ”ฑ โ–‘โ–‘ MODEL - - -isBeingDraggedOver : context -> Model context -> Bool -isBeingDraggedOver context model = - case model of - DraggingOver { target } -> - context == target - - _ -> - False - - -isDragging : Model context -> Bool -isDragging model = - case model of - NotDragging -> - False - - Dragging _ -> - True - - DraggingOver _ -> - True - - Dropped _ -> - False - - -isDraggingOver : context -> Model context -> Bool -isDraggingOver context model = - case model of - NotDragging -> - False - - Dragging _ -> - False - - DraggingOver { target } -> - target == context - - Dropped _ -> - False - - -hasDropped : Model context -> Bool -hasDropped model = - case model of - Dropped _ -> - True - - _ -> - False - - -modelSubject : Model context -> Maybe context -modelSubject model = - case model of - NotDragging -> - Nothing - - Dragging { subject } -> - Just subject - - DraggingOver { subject } -> - Just subject - - Dropped { subject } -> - Just subject - - -modelTarget : Model context -> Maybe context -modelTarget model = - case model of - NotDragging -> - Nothing - - Dragging _ -> - Nothing - - DraggingOver { target } -> - Just target - - Dropped { target } -> - Just target diff --git a/src/Core/UI/Equalizer/State.elm b/src/Core/UI/Equalizer/State.elm deleted file mode 100644 index d5425338d..000000000 --- a/src/Core/UI/Equalizer/State.elm +++ /dev/null @@ -1,61 +0,0 @@ -module UI.Equalizer.State exposing (..) - -import Common exposing (Switch(..)) -import Equalizer exposing (..) -import Return exposing (return) -import UI.Ports as Ports -import UI.Types exposing (..) - - - --- ๐Ÿ“ฃ - - -adjustVolume : Float -> Manager -adjustVolume volume model = - let - settings = - model.eqSettings - in - return - { model | eqSettings = { settings | volume = volume } } - (adjustKnobUsingPort Volume volume) - - -toggleVolumeSlider : Switch -> Manager -toggleVolumeSlider switch model = - case switch of - On -> - Return.singleton { model | showVolumeSlider = True } - - Off -> - Return.singleton { model | showVolumeSlider = False } - - - --- โš—๏ธ - - -adjustKnobUsingPort : Knob -> Float -> Cmd Msg -adjustKnobUsingPort knobType value = - Ports.adjustEqualizerSetting - { value = value - , knob = - case knobType of - Low -> - "LOW" - - Mid -> - "MID" - - High -> - "HIGH" - - Volume -> - "VOLUME" - } - - -adjustAllKnobs : Settings -> Cmd Msg -adjustAllKnobs eqSettings = - adjustKnobUsingPort Volume eqSettings.volume diff --git a/src/Core/UI/Interface/State.elm b/src/Core/UI/Interface/State.elm deleted file mode 100644 index ac80ec940..000000000 --- a/src/Core/UI/Interface/State.elm +++ /dev/null @@ -1,261 +0,0 @@ -module UI.Interface.State exposing (..) - -import Alfred -import Debouncer.Basic as Debouncer -import Maybe.Extra as Maybe -import Notifications -import Return exposing (return) -import Return.Ext as Return -import Theme -import Tracks -import UI.Alfred.State as Alfred -import UI.Common.State as Common -import UI.Common.Types exposing (DebounceManager) -import UI.DnD as DnD -import UI.Page as Page -import UI.Playlists.State as Playlists -import UI.Ports as Ports -import UI.Queue.State as Queue -import UI.Theme -import UI.Types exposing (..) -import UI.User.State.Export exposing (saveEnclosedUserData) - - - --- ๐Ÿ”ฑ - - -assistWithChangingTheme : Manager -assistWithChangingTheme model = - { action = - \{ result } -> - case result of - Just { value } -> - value - |> Alfred.command - |> Maybe.map List.singleton - |> Maybe.withDefault [] - - Nothing -> - [] - , index = - [ { name = Just "Themes" - , items = - List.map - (\theme -> - { icon = Just (theme.icon 16) - , title = theme.title - , value = Alfred.Command (ChangeTheme { id = theme.id }) - } - ) - UI.Theme.list - } - ] - , message = "Choose a theme." - , operation = Alfred.Query - } - |> Alfred.create - |> (\a -> Alfred.assign a model) - - -blur : Manager -blur model = - Return.singleton { model | focusedOnInput = False, pressedKeys = [] } - - -changeTheme : Theme.Id -> Manager -changeTheme id model = - saveEnclosedUserData { model | theme = Just id } - - -contextMenuConfirmation : String -> Msg -> Manager -contextMenuConfirmation conf msg model = - return - { model | confirmation = Just conf } - (Return.task msg) - - -copyToClipboard : String -> Manager -copyToClipboard string = - string - |> Ports.copyToClipboard - |> Return.communicate - - -resizeDebounce : DebounceManager -resizeDebounce = - Common.debounce - .resizeDebouncer - (\d m -> { m | resizeDebouncer = d }) - ResizeDebounce - - -dnd : DnD.Msg Int -> Manager -dnd dragMsg model = - let - ( d, { initiated } ) = - DnD.update dragMsg model.dnd - - m = - if initiated then - { model | dnd = d, isDragging = True } - - else - { model | dnd = d } - in - if DnD.hasDropped d then - case m.page of - Page.Queue _ -> - let - ( from, to ) = - ( Maybe.withDefault 0 <| DnD.modelSubject d - , Maybe.withDefault 0 <| DnD.modelTarget d - ) - - newFuture = - Queue.moveItem - { from = from, to = to, shuffle = m.shuffle } - m.playingNext - in - Queue.fill { m | playingNext = newFuture } - - Page.Index -> - let - trackCanBeMoved = - not m.favouritesOnly && Maybe.isNothing m.searchTerm - in - case m.scene of - Tracks.Covers -> - -- TODO - Return.singleton m - - Tracks.List -> - if trackCanBeMoved then - Playlists.moveTrackInSelected - { to = Maybe.withDefault 0 (DnD.modelTarget d) } - m - - else - "Can't move tracks in a playlist whilst using favourites-only mode, or while searching." - |> Notifications.casual - |> Common.showNotificationWithModel m - - _ -> - Return.singleton m - - else - Return.singleton m - - -focusedOnInput : Manager -focusedOnInput model = - Return.singleton { model | focusedOnInput = True } - - -hideOverlay : Manager -hideOverlay model = - if Maybe.isJust model.contextMenu then - Return.singleton { model | contextMenu = Nothing } - - else if Maybe.isJust model.confirmation then - Return.singleton { model | confirmation = Nothing } - - else if Maybe.isJust model.alfred then - Return.singleton { model | alfred = Nothing } - - else - Return.singleton model - - -lostWindowFocus : Manager -lostWindowFocus model = - Return.singleton { model | focusedOnInput = False, pressedKeys = [] } - - -preferredColorSchemaChanged : { dark : Bool } -> Manager -preferredColorSchemaChanged { dark } model = - Return.singleton { model | darkMode = dark } - - -msgViaContextMenu : Msg -> Manager -msgViaContextMenu msg model = - return - (case msg of - ContextMenuConfirmation _ _ -> - model - - _ -> - { model | confirmation = Nothing, contextMenu = Nothing } - ) - (Return.task msg) - - -removeNotification : { id : Int } -> Manager -removeNotification { id } model = - model.notifications - |> List.filter (Notifications.id >> (/=) id) - |> (\n -> { model | notifications = n }) - |> Return.singleton - - -removeQueueSelection : Manager -removeQueueSelection model = - Return.singleton { model | selectedQueueItem = Nothing } - - -removeTrackSelection : Manager -removeTrackSelection model = - Return.singleton { model | selectedTrackIndexes = [] } - - -resizedWindow : ( Int, Int ) -> Manager -resizedWindow ( width, height ) model = - Return.singleton - { model - | contextMenu = Nothing - , viewport = { height = toFloat height, width = toFloat width } - } - - -searchDebounce : DebounceManager -searchDebounce = - Common.debounce - .searchDebouncer - (\d m -> { m | searchDebouncer = d }) - SearchDebounce - - -setIsTouchDevice : Bool -> Manager -setIsTouchDevice bool model = - Return.singleton { model | isTouchDevice = bool } - - -stoppedDragging : Manager -stoppedDragging model = - let - notDragging = - { model | isDragging = False } - in - -- Depending on where we stopped dragging something, - -- do the appropriate thing. - case model.page of - Page.Queue _ -> - dnd DnD.stoppedDragging notDragging - - Page.Index -> - dnd DnD.stoppedDragging notDragging - - _ -> - Return.singleton notDragging - - - --- MESSAGES - - -onResize : Int -> Int -> Msg -onResize w h = - ( w, h ) - |> ResizedWindow - |> Debouncer.provideInput - |> ResizeDebounce diff --git a/src/Core/UI/Navigation.elm b/src/Core/UI/Navigation.elm deleted file mode 100644 index 28c80194e..000000000 --- a/src/Core/UI/Navigation.elm +++ /dev/null @@ -1,30 +0,0 @@ -module UI.Navigation exposing (Action(..), Icon(..), Label(..), LabelType(..)) - -import Html.Events.Extra.Mouse as Mouse -import Material.Icons.Types exposing (Coloring) -import Svg exposing (Svg) -import UI.Page exposing (Page) - - - --- ๐ŸŒณ - - -type Action msg - = NavigateToPage Page - | OpenLinkInNewPage String - | PerformMsg msg - | PerformMsgWithMouseEvent (Mouse.Event -> msg) - - -type Icon msg - = Icon (Int -> Coloring -> Svg msg) - - -type Label - = Label String LabelType - - -type LabelType - = Hidden - | Shown diff --git a/src/Core/UI/Notifications.elm b/src/Core/UI/Notifications.elm deleted file mode 100644 index 106df8c5b..000000000 --- a/src/Core/UI/Notifications.elm +++ /dev/null @@ -1,66 +0,0 @@ -module UI.Notifications exposing (Model, dismiss, show, showWithModel) - -import Notifications exposing (..) -import Process -import Task -import UI.Types exposing (Msg(..)) - - - --- ๐ŸŒณ - - -type alias Model = - List (Notification Msg) - - - --- ๐Ÿ“ฃ - - -dismiss : Model -> { id : Int } -> ( Model, Cmd Msg ) -dismiss collection { id } = - ( List.map - (\notification -> - if Notifications.id notification == id then - Notifications.dismiss notification - - else - notification - ) - collection - , Task.perform - (\_ -> RemoveNotification { id = id }) - (Process.sleep 500) - ) - - -show : Notification Msg -> Model -> ( Model, Cmd Msg ) -show notification collection = - let - existingNotificationIds = - List.map Notifications.id collection - in - if List.member (Notifications.id notification) existingNotificationIds then - -- Don't show duplicate notifications - ( collection - , Cmd.none - ) - - else - ( notification :: collection - -- Hide notification after a certain amount of time, - -- unless it's a sticky notification. - , if (Notifications.options notification).sticky then - Cmd.none - - else - Task.perform - (\_ -> DismissNotification { id = Notifications.id notification }) - (Process.sleep 7500) - ) - - -showWithModel : Model -> Notification Msg -> ( Model, Cmd Msg ) -showWithModel model notification = - show notification model diff --git a/src/Core/UI/Other/State.elm b/src/Core/UI/Other/State.elm deleted file mode 100644 index 72e8c9098..000000000 --- a/src/Core/UI/Other/State.elm +++ /dev/null @@ -1,96 +0,0 @@ -module UI.Other.State exposing (..) - -import Alien -import Common exposing (ServiceWorkerStatus(..)) -import Dict -import Notifications -import Return exposing (return) -import Time -import UI.Common.State as Common -import UI.Ports as Ports -import UI.Types exposing (..) - - - --- ๐Ÿ”ฑ - - -installedServiceWorker : Manager -installedServiceWorker model = - case model.serviceWorkerStatus of - InstallingNew -> - Return.singleton { model | serviceWorkerStatus = WaitingForActivation } - - _ -> - Return.singleton { model | serviceWorkerStatus = Activated } - - -installingServiceWorker : Manager -installingServiceWorker model = - Return.singleton { model | serviceWorkerStatus = InstallingNew } - - -redirectToBrain : Alien.Event -> Manager -redirectToBrain event model = - return model (Ports.toBrain event) - - -reloadApp : Manager -reloadApp model = - return model (Ports.reloadApp ()) - - -setIsOnline : Bool -> Manager -setIsOnline bool model = - { model | isOnline = bool } - |> Return.singleton - |> Return.command - (case model.nowPlaying of - Just { isPlaying, item } -> - let - trackId = - (Tuple.second item.identifiedTrack).id - in - Ports.reloadAudioNodeIfNeeded - { play = isPlaying - , progress = - if model.rememberProgress then - Dict.get trackId model.progress - - else - Nothing - , trackId = trackId - } - - Nothing -> - Cmd.none - ) - |> Return.andThen - (case ( model.isOnline, bool ) of - ( False, True ) -> - syncHypaethralData - - _ -> - Return.singleton - ) - - -setCurrentTime : Time.Posix -> Manager -setCurrentTime time model = - Return.singleton { model | currentTime = time } - - -setCurrentTimeZone : Time.Zone -> Manager -setCurrentTimeZone zone model = - Return.singleton { model | currentTimeZone = zone } - - - --- โš—๏ธ - - -syncHypaethralData : Manager -syncHypaethralData model = - model - |> Common.showNotification (Notifications.casual "Syncing") - |> Return.command (Ports.toBrain <| Alien.trigger Alien.SyncHypaethralData) diff --git a/src/Core/UI/Page.elm b/src/Core/UI/Page.elm deleted file mode 100644 index 8ebb0a062..000000000 --- a/src/Core/UI/Page.elm +++ /dev/null @@ -1,245 +0,0 @@ -module UI.Page exposing (Page(..), fromUrl, rewriteUrl, sameBase, sources, toString) - -import Maybe.Extra as Maybe -import Sources exposing (Service(..)) -import UI.Playlists.Page as Playlists -import UI.Queue.Page as Queue -import UI.Settings.Page as Settings -import UI.Sources.Page as Sources -import Url exposing (Url) -import Url.Ext as Url -import Url.Parser exposing (..) -import Url.Parser.Query as Query - - - --- ๐ŸŒณ - - -type Page - = Index - | Queue Queue.Page - | Playlists Playlists.Page - | Settings Settings.Page - | Sources Sources.Page - - - --- ๐Ÿ”ฑ - - -fromUrl : Url -> Maybe Page -fromUrl = - parse route - - -rewriteUrl : Url -> Url -rewriteUrl url = - if Maybe.unwrap False (String.contains "path=") url.query then - -- Sometimes we have to use this kind of routing when doing redirections - let - maybePath = - Url.extractQueryParam "path" url - - path = - Maybe.withDefault "" maybePath - in - if - Maybe.unwrap - False - (\f -> String.contains "token=" f || String.contains "code=" f) - url.fragment - then - -- For some oauth stuff, replace the query with the fragment - { url | path = path, query = url.fragment } - - else - { url | path = path } - - else - -- Otherwise do hash-based routing and replace the path with the fragment - { url | path = Maybe.withDefault "" url.fragment } - - -toString : Page -> String -toString = - toString_ >> (++) "#/" - - -toString_ : Page -> String -toString_ page = - case page of - Index -> - "" - - ----------------------------------------- - -- Playlists - ----------------------------------------- - Playlists Playlists.Index -> - "playlists" - - Playlists Playlists.NewCollection -> - "playlists/new/collection" - - Playlists Playlists.NewPlaylist -> - "playlists/new/playlist" - - Playlists (Playlists.Edit playlistName) -> - "playlists/edit/" ++ playlistName - - ----------------------------------------- - -- Queue - ----------------------------------------- - Queue Queue.History -> - "queue/history" - - Queue Queue.Index -> - "queue" - - ----------------------------------------- - -- Settings - ----------------------------------------- - Settings Settings.Data -> - "settings/data" - - Settings Settings.Index -> - "settings" - - Settings Settings.Sync -> - "settings/sync" - - ----------------------------------------- - -- Sources - ----------------------------------------- - Sources (Sources.Edit sourceId) -> - "sources/edit/" ++ sourceId - - Sources Sources.Index -> - "sources" - - Sources Sources.New -> - "sources/new" - - Sources Sources.NewOnboarding -> - "sources/welcome" - - Sources (Sources.NewThroughRedirect Dropbox _) -> - "sources/new/dropbox" - - Sources (Sources.NewThroughRedirect Google _) -> - "sources/new/google" - - Sources (Sources.NewThroughRedirect _ _) -> - "sources/new" - - Sources (Sources.Rename sourceId) -> - "sources/rename/" ++ sourceId - - -{-| Are the bases of these two pages the same? --} -sameBase : Page -> Page -> Bool -sameBase a b = - case ( a, b ) of - ( Playlists _, Playlists _ ) -> - True - - ( Queue _, Queue _ ) -> - True - - ( Settings _, Settings _ ) -> - True - - ( Sources _, Sources _ ) -> - True - - ( Index, Playlists _ ) -> - True - - ( Index, Queue _ ) -> - True - - _ -> - a == b - - - --- ๐Ÿ”ฑ โ–‘โ–‘ SPECIFIC - - -sources : Page -> Maybe Sources.Page -sources page = - case page of - Sources s -> - Just s - - _ -> - Nothing - - - --- โš—๏ธ - - -route : Parser (Page -> a) a -route = - oneOf - [ map Index top - - ----------------------------------------- - -- Playlists - ----------------------------------------- - , map (Playlists Playlists.Index) (s "playlists") - , map (Playlists << Playlists.Edit) (s "playlists" s "edit" string) - , map (Playlists Playlists.NewCollection) (s "playlists" s "new" s "collection") - , map (Playlists Playlists.NewPlaylist) (s "playlists" s "new" s "playlist") - - ----------------------------------------- - -- Queue - ----------------------------------------- - , map (Queue Queue.Index) (s "queue") - , map (Queue Queue.History) (s "queue" s "history") - - ----------------------------------------- - -- Settings - ----------------------------------------- - , map (Settings Settings.Data) (s "settings" s "data") - , map (Settings Settings.Sync) (s "settings" s "sync") - , map (Settings Settings.Index) (s "settings") - - ----------------------------------------- - -- Sources - ----------------------------------------- - , map (Sources Sources.Index) (s "sources") - , map (Sources << Sources.Edit) (s "sources" s "edit" string) - , map (Sources Sources.New) (s "sources" s "new") - , map (Sources Sources.NewOnboarding) (s "sources" s "welcome") - , map (Sources << Sources.Rename) (s "sources" s "rename" string) - - -- Oauth - -------- - , map - (\token state -> - { codeOrToken = token, state = state } - |> Sources.NewThroughRedirect Dropbox - |> Sources - ) - (s "sources" - s "new" - s "dropbox" - Query.string "access_token" - Query.string "state" - ) - , map - (\code state -> - { codeOrToken = code, state = state } - |> Sources.NewThroughRedirect Google - |> Sources - ) - (s "sources" - s "new" - s "google" - Query.string "code" - Query.string "state" - ) - ] diff --git a/src/Core/UI/Playlists/Alfred.elm b/src/Core/UI/Playlists/Alfred.elm deleted file mode 100644 index f14130b86..000000000 --- a/src/Core/UI/Playlists/Alfred.elm +++ /dev/null @@ -1,144 +0,0 @@ -module UI.Playlists.Alfred exposing (create, select) - -import Alfred exposing (..) -import Conditional exposing (ifThenElse) -import Dict -import Dict.Extra as Dict -import List.Extra as List -import Material.Icons.Round as Icons -import Playlists exposing (..) -import Tracks exposing (IdentifiedTrack) -import UI.Types as UI - - - --- CREATE - - -create : { collectionMode : Bool } -> List IdentifiedTrack -> List Playlist -> Alfred UI.Msg -create { collectionMode } tracks playlists = - let - index = - makeIndex playlists - - subject = - ifThenElse collectionMode "collection" "playlist" - in - Alfred.create - { action = createAction collectionMode tracks - , index = index - , message = - if List.length tracks == 1 then - "Choose or create a " ++ subject ++ " to add this track to." - - else - "Choose or create a " ++ subject ++ " to add these tracks to." - , operation = QueryOrMutation - } - - -createAction : Bool -> List IdentifiedTrack -> Alfred.Action UI.Msg -createAction collectionMode tracks ctx = - let - playlistTracks = - Tracks.toPlaylistTracks tracks - in - case ctx.result of - Just result -> - -- Add to playlist - -- - case Alfred.stringValue result.value of - Just playlistName -> - [ UI.AddTracksToPlaylist - { collection = collectionMode - , playlistName = playlistName - , tracks = playlistTracks - } - ] - - Nothing -> - [] - - Nothing -> - -- Create playlist, - -- if given a search term. - -- - case ctx.searchTerm of - Just searchTerm -> - [ UI.AddTracksToPlaylist - { collection = collectionMode - , playlistName = searchTerm - , tracks = playlistTracks - } - ] - - Nothing -> - [] - - - --- SELECT - - -select : List Playlist -> Alfred UI.Msg -select playlists = - let - index = - makeIndex playlists - in - Alfred.create - { action = selectAction playlists - , index = index - , message = "Select a playlist to play tracks from." - , operation = Query - } - - -selectAction : List Playlist -> Alfred.Action UI.Msg -selectAction playlists { result } = - case Maybe.andThen (\r -> List.find (.name >> Just >> (==) (stringValue r.value)) playlists) result of - Just playlist -> - [ UI.SelectPlaylist playlist ] - - Nothing -> - [] - - - --- ใŠ™๏ธ - - -makeIndex playlists = - playlists - |> Dict.groupBy - (\p -> - case ( p.autoGenerated, p.collection ) of - ( Just _, _ ) -> - "9 - AutoGenerated Directory Playlists" - - ( Nothing, True ) -> - "1 - Your Collections" - - ( Nothing, False ) -> - "2 - Your Playlists" - ) - |> Dict.toList - |> List.map - (\( k, v ) -> - ( String.dropLeft 4 k - , v - |> List.uniqueBy .name - |> List.map - (\playlist -> - { icon = Just (Icons.queue_music 16) - , title = playlist.name - , value = Alfred.StringValue playlist.name - } - ) - |> List.sortBy (.title >> String.toLower) - ) - ) - |> List.map - (\( k, v ) -> - { name = Just k, items = v } - ) diff --git a/src/Core/UI/Playlists/ContextMenu.elm b/src/Core/UI/Playlists/ContextMenu.elm deleted file mode 100644 index 989544c23..000000000 --- a/src/Core/UI/Playlists/ContextMenu.elm +++ /dev/null @@ -1,201 +0,0 @@ -module UI.Playlists.ContextMenu exposing (listMenu) - -import ContextMenu exposing (..) -import Coordinates exposing (Coordinates) -import Html.Events.Extra.Mouse -import Material.Icons.Round as Icons -import Playlists exposing (Playlist) -import Playlists.Matching -import Tracks exposing (IdentifiedTrack) -import UI.Page -import UI.Playlists.Page -import UI.Queue.Types as Queue -import UI.Tracks.Types as Tracks -import UI.Types exposing (Msg(..)) -import Url - - - --- ๐Ÿ”ฑ - - -listMenu : Playlist -> List IdentifiedTrack -> Maybe String -> Coordinates -> ContextMenu Msg -listMenu playlist allTracks confirmation coordinates = - let - identifiedTracksFromPlaylist = - case playlist.autoGenerated of - Just _ -> - List.filter - (Tuple.second >> Tracks.matchesAutoGeneratedPlaylist playlist) - allTracks - - Nothing -> - allTracks - |> Playlists.Matching.match playlist - |> Tuple.first - - tracksFromPlaylist = - identifiedTracksFromPlaylist - |> (if playlist.collection then - identity - - else - Tracks.sortByIndexInPlaylist - ) - |> List.map Tuple.second - - menuMsg = - ShowPlaylistListMenu - playlist - { button = Html.Events.Extra.Mouse.MainButton - , clientPos = Coordinates.toTuple coordinates - , keys = { alt = False, ctrl = False, shift = False } - , offsetPos = ( 0, 0 ) - , pagePos = ( 0, 0 ) - , screenPos = ( 0, 0 ) - } - in - case playlist.autoGenerated of - Just _ -> - ContextMenu - [ addToQueue identifiedTracksFromPlaylist - , convertToRegularCollection tracksFromPlaylist playlist - , downloadAsZip tracksFromPlaylist playlist - , storeInCache tracksFromPlaylist - ] - coordinates - - Nothing -> - ContextMenu - [ addToQueue identifiedTracksFromPlaylist - , downloadAsZip tracksFromPlaylist playlist - , removePlaylist menuMsg confirmation playlist - , renamePlaylist playlist - , storeInCache tracksFromPlaylist - , convert playlist - ] - coordinates - - - --- ITEMS - - -addToQueue identifiedTracks = - Item - { icon = Icons.update - , label = "Add to queue" - , msg = - { inFront = False, tracks = identifiedTracks } - |> Queue.AddTracks - |> QueueMsg - - -- - , active = False - } - - -convert playlist = - Item - { icon = Icons.waves - , label = - if playlist.collection then - "Convert to playlist" - - else - "Convert to collection" - , msg = - if playlist.collection then - ConvertCollectionToPlaylist { name = playlist.name } - - else - ConvertPlaylistToCollection { name = playlist.name } - - -- - , active = False - } - - -convertToRegularCollection tracksFromPlaylist playlist = - Item - { icon = Icons.waves - , label = "Save as regular collection" - , msg = - AddTracksToPlaylist - { collection = True - , playlistName = playlist.name - , tracks = List.map Tracks.playlistTrackFromTrack tracksFromPlaylist - } - - -- - , active = False - } - - -downloadAsZip tracksFromPlaylist playlist = - Item - { icon = Icons.archive - , label = "Download as zip file" - , msg = - tracksFromPlaylist - |> Tracks.Download - { prefixTrackNumber = not playlist.collection - , zipName = playlist.name - } - |> TracksMsg - - -- - , active = False - } - - -removePlaylist menuMsg confirmation playlist = - let - playlistId = - "Playlist - " ++ playlist.name - - askForConfirmation = - confirmation == Just playlistId - in - Item - { icon = Icons.delete - , label = - if askForConfirmation then - "Are you sure?" - - else - "Remove playlist" - , msg = - if askForConfirmation then - DeletePlaylist { playlistName = playlist.name } - - else - ContextMenuConfirmation playlistId menuMsg - , active = - askForConfirmation - } - - -renamePlaylist playlist = - Item - { icon = Icons.font_download - , label = "Rename playlist" - , msg = - playlist.name - |> Url.percentEncode - |> UI.Playlists.Page.Edit - |> UI.Page.Playlists - |> ChangeUrlUsingPage - - -- - , active = False - } - - -storeInCache tracksFromPlaylist = - Item - { icon = Icons.offline_bolt - , label = "Store in cache" - , msg = TracksMsg (Tracks.StoreInCache tracksFromPlaylist) - , active = False - } diff --git a/src/Core/UI/Playlists/Directory.elm b/src/Core/UI/Playlists/Directory.elm deleted file mode 100644 index addc860c0..000000000 --- a/src/Core/UI/Playlists/Directory.elm +++ /dev/null @@ -1,99 +0,0 @@ -module UI.Playlists.Directory exposing (generate) - -import Dict exposing (Dict) -import List.Extra as List -import Playlists exposing (..) -import Set exposing (Set) -import Sources exposing (Source) -import String.Ext as String -import Tracks exposing (Track) - - - --- ๐Ÿ”ฑ - - -generate : List Source -> List Track -> List Playlist -generate sources tracks = - let - sourceDirectories = - List.foldl - (\s -> - if s.enabled && s.directoryPlaylists then - s.data - |> Dict.get "directoryPath" - |> Maybe.map fixPath - |> Maybe.withDefault "" - |> Dict.insert s.id - - else - identity - ) - Dict.empty - sources - - playlistNames = - List.foldr - (reducer sourceDirectories) - Set.empty - tracks - in - playlistNames - |> Set.toList - |> List.map - (\n -> - let - s = - String.split "/" n - in - { autoGenerated = Just { level = List.length s - 1 } - , collection = True - , name = Maybe.withDefault n (List.last s) - , public = False - , tracks = [] - } - ) - - -fixPath : String -> String -fixPath string = - string - |> String.chopStart "/" - |> (\s -> - if String.isEmpty s || String.endsWith "/" s then - s - - else - s ++ "/" - ) - - - --- ใŠ™๏ธ - - -reducer : Dict String String -> Track -> Set String -> Set String -reducer sourceDirectories track = - case Dict.get track.sourceId sourceDirectories of - Just prefix -> - let - path = - String.dropLeft (String.length prefix) track.path - in - case String.split "/" path of - "" :: _ :: _ -> - identity - - a :: _ :: _ -> - case prefix of - "" -> - Set.insert a - - _ -> - Set.insert (prefix ++ "/" ++ a) - - _ -> - identity - - Nothing -> - identity diff --git a/src/Core/UI/Playlists/Page.elm b/src/Core/UI/Playlists/Page.elm deleted file mode 100644 index b7b2264af..000000000 --- a/src/Core/UI/Playlists/Page.elm +++ /dev/null @@ -1,10 +0,0 @@ -module UI.Playlists.Page exposing (Page(..)) - --- ๐ŸŒณ - - -type Page - = Edit String - | Index - | NewCollection - | NewPlaylist diff --git a/src/Core/UI/Playlists/State.elm b/src/Core/UI/Playlists/State.elm deleted file mode 100644 index 65345db60..000000000 --- a/src/Core/UI/Playlists/State.elm +++ /dev/null @@ -1,539 +0,0 @@ -module UI.Playlists.State exposing (..) - -import Conditional exposing (ifThenElse) -import Coordinates -import Html.Events.Extra.Mouse as Mouse -import List.Ext as List -import List.Extra as List -import Maybe.Extra as Maybe -import Notifications -import Playlists exposing (..) -import Return exposing (andThen) -import Tracks exposing (IdentifiedTrack) -import Tracks.Collection -import UI.Alfred.State as Alfred -import UI.Common.State as Common -import UI.Page as Page -import UI.Playlists.Alfred -import UI.Playlists.ContextMenu as Playlists -import UI.Playlists.Page exposing (..) -import UI.Tracks.State as Tracks -import UI.Types exposing (..) -import UI.User.State.Export as User - - - --- ๐Ÿ”ฑ - - -activate : Playlist -> Manager -activate playlist model = - model - |> select playlist - |> andThen (Common.changeUrlUsingPage Page.Index) - - -addTracksToPlaylist : { collection : Bool, playlistName : String, tracks : List PlaylistTrackWithoutMetadata } -> Manager -addTracksToPlaylist { collection, playlistName, tracks } model = - let - properPlaylistName = - String.trim playlistName - - playlistIndex = - List.findIndex - (\p -> Maybe.isNothing p.autoGenerated && p.name == properPlaylistName) - model.playlists - - ( tracksAlreadyInPlaylist, newTracks ) = - playlistIndex - |> Maybe.andThen - (\a -> - if collection then - Just a - - else - Nothing - ) - |> Maybe.andThen (\idx -> List.getAt idx model.playlists) - |> Maybe.map - (\p -> - List.foldl - (\track ( a, b, c ) -> - case - List.findIndex - (\x -> - track.title == x.title && track.album == x.album && track.artist == x.artist - ) - c - of - Just idx -> - ( track :: a, b, List.removeAt idx c ) - - Nothing -> - ( a, track :: b, c ) - ) - ( [], [], p.tracks ) - tracks - ) - |> Maybe.map (\( a, b, _ ) -> ( a, b )) - |> Maybe.withDefault ( [], tracks ) - |> Tuple.mapSecond - (List.map - (\track -> - let - newTrack : PlaylistTrack - newTrack = - { album = track.album - , artist = track.artist - , title = track.title - - -- - , insertedAt = model.currentTime - } - in - newTrack - ) - ) - - newInventory = - case playlistIndex of - Just idx -> - List.updateAt - idx - (\p -> { p | tracks = p.tracks ++ newTracks }) - model.playlists - - Nothing -> - { autoGenerated = Nothing - , collection = collection - , name = properPlaylistName - , public = False - , tracks = newTracks - } - :: model.playlists - - newModel = - { model - | playlists = newInventory - , lastModifiedPlaylist = - Just - { collection = collection - , name = properPlaylistName - } - } - - subject = - ifThenElse collection "collection" "playlist" - in - case newTracks of - [] -> - if collection then - (case tracksAlreadyInPlaylist of - [ t ] -> - "__" ++ t.title ++ "__ was" - - l -> - "__" ++ String.fromInt (List.length l) ++ " tracks__ were" - ) - |> (\s -> s ++ " already added to the __" ++ properPlaylistName ++ "__ collection") - |> Notifications.casual - |> Common.showNotificationWithModel model - - else - Return.singleton model - - _ -> - (case newTracks of - [ t ] -> - "Added __" ++ t.title ++ "__" - - l -> - "Added __" ++ String.fromInt (List.length l) ++ " tracks__" - ) - |> (\s -> s ++ " to the __" ++ properPlaylistName ++ "__ " ++ subject) - |> Notifications.success - |> Common.showNotificationWithModel newModel - |> andThen User.savePlaylists - - -assistWithAddingTracksToCollection : List IdentifiedTrack -> Manager -assistWithAddingTracksToCollection tracks model = - model.playlists - |> List.filter (\p -> p.autoGenerated == Nothing && p.collection == True) - |> UI.Playlists.Alfred.create { collectionMode = True } tracks - |> (\a -> Alfred.assign a model) - - -assistWithAddingTracksToPlaylist : List IdentifiedTrack -> Manager -assistWithAddingTracksToPlaylist tracks model = - model.playlists - |> List.filter (\p -> p.autoGenerated == Nothing && p.collection == False) - |> UI.Playlists.Alfred.create { collectionMode = False } tracks - |> (\a -> Alfred.assign a model) - - -assistWithSelectingPlaylist : Manager -assistWithSelectingPlaylist model = - model.playlists - |> UI.Playlists.Alfred.select - |> (\a -> Alfred.assign a model) - - -convertCollectionToPlaylist : { name : String } -> Manager -convertCollectionToPlaylist { name } model = - case - List.findIndex - (\p -> Maybe.isNothing p.autoGenerated && p.name == name) - model.playlists - of - Just playlistIndex -> - model.playlists - |> List.updateAt - playlistIndex - (\p -> { p | collection = False }) - |> (\newInventory -> - { model - | playlists = newInventory - , selectedPlaylist = - Maybe.map - (\p -> - if p.name == name then - { p | collection = False } - - else - p - ) - model.selectedPlaylist - } - ) - |> Return.singleton - |> andThen User.savePlaylists - - Nothing -> - Return.singleton model - - -convertPlaylistToCollection : { name : String } -> Manager -convertPlaylistToCollection { name } model = - case - List.findIndex - (\p -> Maybe.isNothing p.autoGenerated && p.name == name) - model.playlists - of - Just playlistIndex -> - model.playlists - |> List.updateAt - playlistIndex - (\p -> { p | collection = True }) - |> (\newInventory -> - { model - | playlists = newInventory - , selectedPlaylist = - Maybe.map - (\p -> - if p.name == name then - { p | collection = True } - - else - p - ) - model.selectedPlaylist - } - ) - |> Return.singleton - |> andThen User.savePlaylists - - Nothing -> - Return.singleton model - - -create : { collection : Bool } -> Manager -create { collection } model = - case model.newPlaylistContext of - Just playlistName -> - let - alreadyExists = - List.find - (.name >> (==) playlistName) - (List.filterNot (.autoGenerated >> Maybe.isJust) model.playlists) - - playlist = - { autoGenerated = Nothing - , collection = collection - , name = playlistName - , public = False - , tracks = [] - } - in - case alreadyExists of - Just existingPlaylist -> - (if existingPlaylist.collection then - "There's already a collection using this name" - - else - "There's already a playlist using this name" - ) - |> Notifications.error - |> Common.showNotificationWithModel model - - Nothing -> - { model - | lastModifiedPlaylist = - Just - { collection = playlist.collection - , name = playlist.name - } - , newPlaylistContext = Nothing - , playlists = playlist :: model.playlists - } - |> User.savePlaylists - |> andThen redirectToPlaylistIndexPage - - Nothing -> - Return.singleton model - - -createCollection : Manager -createCollection = - create { collection = True } - - -createPlaylist : Manager -createPlaylist = - create { collection = False } - - -deactivate : Manager -deactivate = - deselect - - -deselect : Manager -deselect model = - { model | selectedPlaylist = Nothing } - |> Tracks.reviseCollection Tracks.Collection.arrange - |> andThen User.saveEnclosedUserData - - -delete : { playlistName : String } -> Manager -delete { playlistName } model = - let - selectedPlaylist = - Maybe.map - (\p -> ( p.autoGenerated, p.name )) - model.selectedPlaylist - - ( selectedPlaylistChanged, newSelectedPlaylist ) = - if selectedPlaylist == Just ( Nothing, playlistName ) then - ( True, Nothing ) - - else - ( False, model.selectedPlaylist ) - in - model.playlists - |> List.filter - (\p -> - if Maybe.isJust p.autoGenerated then - True - - else - p.name /= playlistName - ) - |> (\col -> - { model - | playlists = col - , selectedPlaylist = newSelectedPlaylist - } - ) - |> (if selectedPlaylistChanged then - Tracks.reviseCollection Tracks.Collection.arrange - - else - Return.singleton - ) - |> andThen User.savePlaylists - - -modify : Manager -modify model = - case model.editPlaylistContext of - Just { oldName, newName } -> - let - properName = - String.trim newName - - validName = - String.isEmpty properName == False - - ( autoGenerated, notAutoGenerated ) = - List.partition (.autoGenerated >> Maybe.isJust) model.playlists - - alreadyExists = - List.find - (.name >> (==) properName) - notAutoGenerated - - newCollection = - List.map - (\p -> ifThenElse (p.name == oldName) { p | name = properName } p) - notAutoGenerated - in - case alreadyExists of - Just existingPlaylist -> - (if existingPlaylist.collection then - "There's already a collection using this name" - - else - "There's already a playlist using this name" - ) - |> Notifications.error - |> Common.showNotificationWithModel - { model | editPlaylistContext = Nothing } - - Nothing -> - if validName then - { model - | editPlaylistContext = Nothing - , lastModifiedPlaylist = - case model.lastModifiedPlaylist of - Just l -> - if l.name == oldName then - Just { l | name = newName } - - else - Just l - - Nothing -> - Nothing - , playlists = newCollection ++ autoGenerated - } - |> User.savePlaylists - |> andThen redirectToPlaylistIndexPage - - else - redirectToPlaylistIndexPage model - - Nothing -> - redirectToPlaylistIndexPage model - - -moveTrackInSelected : { to : Int } -> Manager -moveTrackInSelected { to } model = - case model.selectedPlaylist of - Just playlist -> - let - moveParams = - { from = Maybe.withDefault 0 (List.head model.selectedTrackIndexes) - , to = to - , amount = List.length model.selectedTrackIndexes - } - - updatedPlaylist = - { playlist | tracks = List.move moveParams playlist.tracks } - - updatedPlaylistCollection = - List.map - (\p -> - ifThenElse - (p.autoGenerated == Nothing && p.name == updatedPlaylist.name) - updatedPlaylist - p - ) - model.playlists - in - { model - | playlists = updatedPlaylistCollection - , selectedPlaylist = Just updatedPlaylist - } - |> Tracks.reviseCollection Tracks.Collection.arrange - |> andThen User.savePlaylists - - Nothing -> - Return.singleton model - - -removeTracks : Playlist -> List IdentifiedTrack -> Manager -removeTracks playlist tracks model = - let - updatedPlaylist = - Tracks.removeFromPlaylist tracks playlist - in - model.playlists - |> List.map - (\p -> - if p.name == playlist.name then - updatedPlaylist - - else - p - ) - |> (\c -> { model | playlists = c }) - |> select updatedPlaylist - |> andThen User.savePlaylists - - -select : Playlist -> Manager -select playlist model = - { model | page = Page.Index, selectedPlaylist = Just playlist } - |> Tracks.reviseCollection Tracks.Collection.arrange - |> andThen User.saveEnclosedUserData - - -setCreationContext : String -> Manager -setCreationContext playlistName model = - Return.singleton { model | newPlaylistContext = Just playlistName } - - -setModificationContext : String -> String -> Manager -setModificationContext oldName newName model = - let - context = - { oldName = oldName - , newName = newName - } - in - Return.singleton { model | editPlaylistContext = Just context } - - -showListMenu : Playlist -> Mouse.Event -> Manager -showListMenu playlist mouseEvent model = - let - coordinates = - Coordinates.fromTuple mouseEvent.clientPos - - contextMenu = - Playlists.listMenu - playlist - model.tracks.identified - model.confirmation - coordinates - in - Return.singleton { model | contextMenu = Just contextMenu } - - -toggleVisibility : Playlist -> Manager -toggleVisibility playlist model = - let - updatedPlaylist = - { playlist | public = not playlist.public } - in - model.playlists - |> List.map - (\p -> - if p.name == playlist.name then - updatedPlaylist - - else - p - ) - |> (\c -> { model | playlists = c }) - |> User.savePlaylists - - - --- ใŠ™๏ธ - - -redirectToPlaylistIndexPage : Manager -redirectToPlaylistIndexPage = - Common.changeUrlUsingPage (Page.Playlists Index) diff --git a/src/Core/UI/Ports.elm b/src/Core/UI/Ports.elm deleted file mode 100644 index ab535ea07..000000000 --- a/src/Core/UI/Ports.elm +++ /dev/null @@ -1,181 +0,0 @@ -port module UI.Ports exposing (..) - -import Alien -import Json.Encode as Json -import Queue -import UI.Audio.Types as Audio - - - --- ๐Ÿ“ฃ - - -port activeQueueItemChanged : Maybe Queue.EngineItem -> Cmd msg - - -port adjustEqualizerSetting : { knob : String, value : Float } -> Cmd msg - - -port copyToClipboard : String -> Cmd msg - - -port downloadJsonUsingTauri : { filename : String, json : String } -> Cmd msg - - -port loadAlbumCovers : { list : Bool, coverView : Bool } -> Cmd msg - - -port openUrlOnNewPage : String -> Cmd msg - - -port pause : { trackId : String } -> Cmd msg - - -port pauseScrobbleTimer : () -> Cmd msg - - -port pickAverageBackgroundColor : String -> Cmd msg - - -port play : { trackId : String, volume : Float } -> Cmd msg - - -port reloadAudioNodeIfNeeded : { play : Bool, progress : Maybe Float, trackId : String } -> Cmd msg - - -port preloadAudio : Queue.EngineItem -> Cmd msg - - -port reloadApp : () -> Cmd msg - - -port renderAudioElements : { items : List Queue.EngineItem, play : Maybe String, volume : Float } -> Cmd msg - - -port resetScrobbleTimer : { duration : Float, trackId : String } -> Cmd msg - - -port seek : { percentage : Float, trackId : String } -> Cmd msg - - -port sendTask : Json.Value -> Cmd msg - - -port setMediaSessionArtwork : { blobUrl : String, imageType : String } -> Cmd msg - - -port setMediaSessionMetadata : { album : Maybe String, artist : Maybe String, title : String, coverPrep : Maybe Audio.CoverPrep } -> Cmd msg - - -port setMediaSessionPlaybackState : String -> Cmd msg - - -port setMediaSessionPositionState : { currentTime : Float, duration : Float } -> Cmd msg - - -port startScrobbleTimer : () -> Cmd msg - - -port toBrain : Alien.Event -> Cmd msg - - - --- ๐Ÿ“ฐ - - -port audioDurationChange : (Audio.DurationChangeEvent -> msg) -> Sub msg - - -port audioEnded : (Audio.GenericAudioEvent -> msg) -> Sub msg - - -port audioError : (Audio.ErrorAudioEvent -> msg) -> Sub msg - - -port audioPlaybackStateChanged : (Audio.PlaybackStateEvent -> msg) -> Sub msg - - -port audioIsLoading : (Audio.GenericAudioEvent -> msg) -> Sub msg - - -port audioHasLoaded : (Audio.GenericAudioEvent -> msg) -> Sub msg - - -port audioTimeUpdated : (Audio.TimeUpdatedEvent -> msg) -> Sub msg - - -port downloadTracksFinished : (() -> msg) -> Sub msg - - -port fromAlien : (Alien.Event -> msg) -> Sub msg - - -port lostWindowFocus : (() -> msg) -> Sub msg - - -port indicateTouchDevice : (() -> msg) -> Sub msg - - -port insertCoverCache : (Json.Value -> msg) -> Sub msg - - -port installedNewServiceWorker : (() -> msg) -> Sub msg - - -port installingNewServiceWorker : (() -> msg) -> Sub msg - - -port refreshedAccessToken : (Json.Value -> msg) -> Sub msg - - -port preferredColorSchemaChanged : ({ dark : Bool } -> msg) -> Sub msg - - -port receiveTask : (Json.Value -> msg) -> Sub msg - - -port requestNext : (() -> msg) -> Sub msg - - -port requestPause : (() -> msg) -> Sub msg - - -port requestPlay : (() -> msg) -> Sub msg - - -port requestPlayPause : (() -> msg) -> Sub msg - - -port requestPrevious : (() -> msg) -> Sub msg - - -port requestStop : (() -> msg) -> Sub msg - - -port scrobble : ({ duration : Int, timestamp : Int, trackId : String } -> msg) -> Sub msg - - -port setAverageBackgroundColor : ({ r : Int, g : Int, b : Int } -> msg) -> Sub msg - - -port setIsOnline : (Bool -> msg) -> Sub msg - - -port showErrorNotification : (String -> msg) -> Sub msg - - -port showStickyErrorNotification : (String -> msg) -> Sub msg - - - --- ๐Ÿ”ฑ - - -giveBrain : Alien.Tag -> Json.Value -> Cmd msg -giveBrain tag data = - toBrain (Alien.broadcast tag data) - - -nudgeBrain : Alien.Tag -> Cmd msg -nudgeBrain tag = - toBrain (Alien.trigger tag) diff --git a/src/Core/UI/Queue/ContextMenu.elm b/src/Core/UI/Queue/ContextMenu.elm deleted file mode 100644 index 864df73ae..000000000 --- a/src/Core/UI/Queue/ContextMenu.elm +++ /dev/null @@ -1,155 +0,0 @@ -module UI.Queue.ContextMenu exposing (futureMenu, futureNavigationMenu, historyMenu) - -import ContextMenu exposing (..) -import Coordinates exposing (Coordinates) -import Material.Icons.Round as Icons -import Queue -import UI.Queue.Types as Queue -import UI.Tracks.ContextMenu -import UI.Types exposing (Msg(..)) - - - --- ๐Ÿ”ฑ - - -futureMenu : - { cached : List String, cachingInProgress : List String, itemIndex : Int } - -> Queue.Item - -> Coordinates - -> ContextMenu Msg -futureMenu { cached, cachingInProgress, itemIndex } item = - let - tracks = - [ item.identifiedTrack ] - in - ContextMenu - [ Item - { icon = Icons.update - , label = "Move to the top" - , msg = - { index = itemIndex } - |> Queue.MoveItemToFirst - |> QueueMsg - - -- - , active = False - } - , Item - { icon = Icons.update - , label = "Move to the end of my picks" - , msg = - { index = itemIndex } - |> Queue.MoveItemToLast - |> QueueMsg - - -- - , active = False - } - , Item - { icon = Icons.waves - , label = "Add to collection" - , msg = AssistWithAddingTracksToCollection tracks - , active = False - } - , Item - { icon = Icons.waves - , label = "Add to playlist" - , msg = AssistWithAddingTracksToPlaylist tracks - , active = False - } - , UI.Tracks.ContextMenu.cacheAction - { cached = cached, cachingInProgress = cachingInProgress } - tracks - ] - - -futureNavigationMenu : { manualEntries : List Queue.Item } -> Coordinates -> ContextMenu Msg -futureNavigationMenu { manualEntries } = - [ [ Item - { icon = Icons.not_interested - , label = "Reset ignored" - , msg = QueueMsg Queue.ResetIgnored - - -- - , active = False - } - ] - , -- - if List.isEmpty manualEntries then - [] - - else - [ Item - { icon = Icons.waves - , label = "Add queue picks to collection" - , msg = - manualEntries - |> List.map .identifiedTrack - |> AssistWithAddingTracksToCollection - , active = False - } - , Item - { icon = Icons.waves - , label = "Add queue picks to playlist" - , msg = - manualEntries - |> List.map .identifiedTrack - |> AssistWithAddingTracksToPlaylist - , active = False - } - ] - ] - |> List.concat - |> ContextMenu - - -historyMenu : - { cached : List String, cachingInProgress : List String } - -> Queue.Item - -> Coordinates - -> ContextMenu Msg -historyMenu { cached, cachingInProgress } item = - let - tracks = - [ item.identifiedTrack ] - in - ContextMenu - [ Item - { icon = Icons.update - , label = "Play next" - , msg = - { inFront = True, tracks = tracks } - |> Queue.AddTracks - |> QueueMsg - - -- - , active = False - } - , Item - { icon = Icons.update - , label = "Add to queue" - , msg = - { inFront = False, tracks = tracks } - |> Queue.AddTracks - |> QueueMsg - - -- - , active = False - } - , Item - { icon = Icons.waves - , label = "Add to collection" - , msg = AssistWithAddingTracksToCollection tracks - , active = False - } - , Item - { icon = Icons.waves - , label = "Add to playlist" - , msg = AssistWithAddingTracksToPlaylist tracks - , active = False - } - , UI.Tracks.ContextMenu.cacheAction - { cached = cached, cachingInProgress = cachingInProgress } - tracks - ] diff --git a/src/Core/UI/Queue/Fill.elm b/src/Core/UI/Queue/Fill.elm deleted file mode 100644 index 6fb44e6b0..000000000 --- a/src/Core/UI/Queue/Fill.elm +++ /dev/null @@ -1,251 +0,0 @@ -module UI.Queue.Fill exposing (State, cleanAutoGenerated, ordered, queueLength, shuffled) - -{-| These functions will return a new list for the `future` property. --} - -import Array -import List.Extra as List -import Maybe.Ext as Maybe -import Maybe.Extra as Maybe -import Queue exposing (Item, makeItem) -import Random exposing (Generator, Seed) -import Time -import Tracks exposing (IdentifiedTrack) - - - --- โ›ฉ - - -queueLength : Int -queueLength = - 30 - - - --- ๐ŸŒณ - - -type alias State = - { activeItem : Maybe Item - , future : List Item - , ignored : List Item - , past : List Item - } - - - --- ๐Ÿ”ฑ โ–‘โ–‘ ORDERED - - -ordered : Time.Posix -> List IdentifiedTrack -> State -> List Item -ordered _ unfilteredTracks state = - let - tracks = - state.ignored - |> List.map itemTrackId - |> Tuple.pair [] - |> purifier unfilteredTracks - |> Tuple.first - - manualEntries = - List.filter (.manualEntry >> (==) True) state.future - - remaining = - max (queueLength - List.length manualEntries) 0 - - focus = - Maybe.preferFirst (List.last manualEntries) state.activeItem - in - case focus of - Just item -> - let - maybeNowPlayingIndex = - List.findIndex - (Tracks.isNowPlaying item.identifiedTrack) - tracks - in - maybeNowPlayingIndex - |> Maybe.map (\idx -> List.drop (idx + 1) tracks) - |> Maybe.withDefault tracks - |> List.take remaining - |> (\a -> - let - actualRemaining = - remaining - List.length a - - n = - Maybe.withDefault (List.length tracks) maybeNowPlayingIndex - in - a ++ List.take (min n actualRemaining) tracks - ) - |> List.map (makeItem False) - |> List.append manualEntries - - Nothing -> - tracks - |> List.take remaining - |> List.map (makeItem False) - |> List.append manualEntries - - - --- ๐Ÿ”ฑ โ–‘โ–‘ SHUFFLED - - -shuffled : Time.Posix -> List IdentifiedTrack -> State -> List Item -shuffled timestamp unfilteredTracks state = - let - idsToIgnoreWithoutPast = - [ state.ignored - , Maybe.unwrap [] List.singleton state.activeItem - ] - |> List.map (List.map itemTrackId) - |> List.concat - |> List.unique - - ( tracksWithPast, idsToIgnoreWithoutPastAfterFilter ) = - purifier unfilteredTracks ( [], idsToIgnoreWithoutPast ) - - idsToIgnoreWithPast = - List.unique (idsToIgnoreWithoutPastAfterFilter ++ List.map itemTrackId state.past) - - ( tracksWithoutPast, idsToIgnoreWithPastAfterFilter ) = - purifier tracksWithPast ( [], idsToIgnoreWithPast ) - - idsToIgnoreWithFuture = - List.unique (idsToIgnoreWithoutPastAfterFilter ++ List.map itemTrackId state.future) - - ( tracksList, _ ) = - purifier - (case tracksWithoutPast of - [] -> - tracksWithPast - - t -> - t - ) - ( [], idsToIgnoreWithFuture ) - - tracks = - Array.fromList tracksList - - amountOfTracks = - Array.length tracks - - generator = - Random.int 0 (amountOfTracks - 1) - - toAmount = - max (queueLength - List.length state.future) 0 - - howMany = - min toAmount amountOfTracks - in - if howMany > 0 then - timestamp - |> Time.posixToMillis - |> Random.initialSeed - |> generateIndexes generator howMany [] - |> List.foldl - (\idx acc -> - case Array.get idx tracks of - Just track -> - makeItem False track :: acc - - Nothing -> - acc - ) - [] - |> List.append state.future - - else - state.future - - - --- ๐Ÿ”ฑ - - -cleanAutoGenerated : Bool -> String -> List Item -> List Item -cleanAutoGenerated shuffle trackId future = - if shuffle then - List.filterNot - (\i -> i.manualEntry == False && itemTrackId i == trackId) - future - - else - future - - - --- ใŠ™๏ธ - - -{-| Generated random indexes. - - `squirrel` = accumulator, ie. collected indexes - --} -generateIndexes : Generator Int -> Int -> List Int -> Seed -> List Int -generateIndexes generator howMany squirrel seed = - let - ( index, newSeed ) = - Random.step generator seed - in - if List.member index squirrel then - generateIndexes generator howMany squirrel newSeed - - else if howMany - 1 > 0 then - generateIndexes generator (howMany - 1) (index :: squirrel) newSeed - - else - index :: squirrel - - - --- PURIFY - - -purifier : - List IdentifiedTrack - -> ( List IdentifiedTrack, List String ) - -> ( List IdentifiedTrack, List String ) -purifier tracks ( acc, idsToIgnore ) = - case idsToIgnore of - [] -> - -- Nothing more to ignore, - -- stop here. - ( acc ++ tracks, [] ) - - _ -> - case tracks of - [] -> - -- No more tracks left, - -- end of the road. - ( acc, idsToIgnore ) - - (( _, track ) as identifiedTrack) :: rest -> - case List.elemIndex track.id idsToIgnore of - Just ignoreIdx -> - -- It's a track to ignore, - -- remove it from the ignore list and carry on. - purifier - rest - ( acc, List.removeAt ignoreIdx idsToIgnore ) - - Nothing -> - -- It's not a track to ignore, - -- add it to the to-keep list and carry on. - purifier - rest - ( identifiedTrack :: acc, idsToIgnore ) - - - --- COMMON - - -itemTrackId : Item -> String -itemTrackId = - .identifiedTrack >> Tuple.second >> .id diff --git a/src/Core/UI/Queue/Page.elm b/src/Core/UI/Queue/Page.elm deleted file mode 100644 index c641eb1a8..000000000 --- a/src/Core/UI/Queue/Page.elm +++ /dev/null @@ -1,8 +0,0 @@ -module UI.Queue.Page exposing (Page(..)) - --- ๐ŸŒณ - - -type Page - = Index - | History diff --git a/src/Core/UI/Queue/State.elm b/src/Core/UI/Queue/State.elm deleted file mode 100644 index 2dc2f7ed8..000000000 --- a/src/Core/UI/Queue/State.elm +++ /dev/null @@ -1,584 +0,0 @@ -module UI.Queue.State exposing (..) - -import Coordinates -import Debouncer.Basic as Debouncer -import Dict -import Html.Events.Extra.Mouse as Mouse -import List.Extra as List -import Notifications -import Queue exposing (..) -import Return exposing (andThen) -import Return.Ext as Return -import Tracks exposing (..) -import UI.Audio.Types exposing (AudioLoadingState(..)) -import UI.Common.State as Common -import UI.Ports as Ports -import UI.Queue.ContextMenu as Queue -import UI.Queue.Fill as Fill -import UI.Queue.Types as Queue exposing (..) -import UI.Types exposing (..) -import UI.User.State.Export exposing (..) - - - --- ๐Ÿ“ฃ - - -update : Queue.Msg -> Manager -update msg = - case msg of - Clear -> - clear - - PreloadNext -> - preloadNext - - Reset -> - reset - - ResetIgnored -> - resetIgnored - - Rewind -> - rewind - - Shift -> - shift - - Select a -> - select a - - ShowFutureMenu a b c -> - showFutureMenu a b c - - ShowFutureNavigationMenu a -> - showFutureNavigationMenu a - - ShowHistoryMenu a b -> - showHistoryMenu a b - - ToggleRepeat -> - toggleRepeat - - ToggleShuffle -> - toggleShuffle - - ------------------------------------ - -- Future - ------------------------------------ - AddTracks a -> - addTracks a - - InjectFirst a b -> - injectFirst a b - - InjectLast a b -> - injectLast a b - - InjectFirstAndPlay a -> - injectFirstAndPlay a - - MoveItemToFirst a -> - moveItemToFirst a - - MoveItemToLast a -> - moveItemToLast a - - RemoveItem a -> - removeItem a - - - --- ๐Ÿ›  - - -changeActiveItem : Maybe Item -> Manager -changeActiveItem maybeItem model = - let - maybeNowPlaying = - Maybe.map - (\item -> - { coverLoaded = False - , duration = Nothing - , isPlaying = False - , item = item - , loadingState = Loading - , playbackPosition = 0 - } - ) - maybeItem - in - maybeItem - |> Maybe.map (.identifiedTrack >> Tuple.second) - |> Maybe.map - (Queue.makeEngineItem - False - model.currentTime - model.sources - model.cachedTracks - (if model.rememberProgress then - model.progress - - else - Dict.empty - ) - ) - |> Maybe.map insertTrack - |> Maybe.withDefault Return.singleton - |> (\fn -> fn { model | nowPlaying = maybeNowPlaying }) - |> andThen fill - - -clear : Manager -clear model = - fill { model | playingNext = [] } - - -fill : Manager -fill model = - let - ( availableTracks, timestamp ) = - ( case ( model.selectedCover, model.coverSelectionReducesPool ) of - ( Just cover, True ) -> - Tuple.first <| List.foldl coverTracksHarvester ( [], cover.trackIds ) model.tracks.harvested - - _ -> - model.tracks.harvested - -- - , model.currentTime - ) - - nonMissingTracks = - List.filter - (Tuple.second >> .id >> (/=) Tracks.missingId) - availableTracks - in - model - |> (\m -> - -- Empty the ignored list when we are ignoring all the tracks - if List.length model.dontPlay == List.length nonMissingTracks then - { m | dontPlay = [] } - - else - m - ) - |> (\m -> - if m.shuffle && List.length model.playingNext >= Fill.queueLength then - m - - else - let - fillState = - { activeItem = Maybe.map .item m.nowPlaying - , future = m.playingNext - , ignored = m.dontPlay - , past = m.playedPreviously - } - in - -- Fill using the appropiate method - if m.shuffle then - { m | playingNext = Fill.shuffled timestamp nonMissingTracks fillState } - - else - { m | playingNext = Fill.ordered timestamp nonMissingTracks fillState } - ) - |> Return.communicate - (Queue.PreloadNext - |> QueueMsg - |> Debouncer.provideInput - |> AudioPreloadDebounce - |> Return.task - ) - - -insertTrack : EngineItem -> Manager -insertTrack item model = - item - |> (\engineItem -> - if - List.any - (\a -> engineItem.trackId == a.trackId) - model.audioElements - then - List.map - (\a -> - if engineItem.trackId == a.trackId then - { a | isPreload = False } - - else - a - ) - model.audioElements - - else - model.audioElements ++ [ engineItem ] - ) - |> List.filter - (\a -> - if item.isPreload then - True - - else if a.trackId /= item.trackId && not a.isPreload then - False - - else - True - ) - |> (\a -> { model | audioElements = a }) - |> Return.singleton - |> Return.effect_ - (\m -> - Ports.renderAudioElements - { items = m.audioElements - , play = - if item.isPreload then - Nothing - - else - Just item.trackId - , volume = m.eqSettings.volume - } - ) - - -preloadNext : Manager -preloadNext model = - case List.head model.playingNext of - Just item -> - item - |> .identifiedTrack - |> Tuple.second - |> Queue.makeEngineItem - True - model.currentTime - model.sources - model.cachedTracks - (if model.rememberProgress then - model.progress - - else - Dict.empty - ) - |> (\engineItem -> - insertTrack engineItem model - ) - - Nothing -> - Return.singleton model - - -rewind : Manager -rewind model = - changeActiveItem - (List.last model.playedPreviously) - { model - | playingNext = - model.nowPlaying - |> Maybe.map (\{ item } -> item :: model.playingNext) - |> Maybe.withDefault model.playingNext - , playedPreviously = - model.playedPreviously - |> List.init - |> Maybe.withDefault [] - } - - -{-| Renew the queue, meaning that the auto-generated items in the queue are removed and new items are added. --} -reset : Manager -reset model = - model.playingNext - |> List.filter (.manualEntry >> (==) True) - |> (\f -> { model | playingNext = f }) - |> fill - - -resetIgnored : Manager -resetIgnored model = - fill { model | dontPlay = [] } - - -select : Item -> Manager -select item model = - Return.singleton { model | selectedQueueItem = Just item } - - -shift : Manager -shift model = - changeActiveItem - (List.head model.playingNext) - { model - | playingNext = - model.playingNext - |> List.drop 1 - , playedPreviously = - model.nowPlaying - |> Maybe.map (.item >> List.singleton) - |> Maybe.map (List.append model.playedPreviously) - |> Maybe.withDefault model.playedPreviously - } - - -showFutureMenu : Item -> { index : Int } -> Mouse.Event -> Manager -showFutureMenu item { index } mouseEvent model = - mouseEvent.clientPos - |> Coordinates.fromTuple - |> Queue.futureMenu - { cached = model.cachedTracks - , cachingInProgress = model.cachingTracksInProgress - , itemIndex = index - } - item - |> Just - |> (\c -> { model | contextMenu = c }) - |> Return.singleton - - -showFutureNavigationMenu : Mouse.Event -> Manager -showFutureNavigationMenu mouseEvent model = - mouseEvent.clientPos - |> Coordinates.fromTuple - |> Queue.futureNavigationMenu { manualEntries = List.filter .manualEntry model.playingNext } - |> Just - |> (\c -> { model | contextMenu = c }) - |> Return.singleton - - -showHistoryMenu : Item -> Mouse.Event -> Manager -showHistoryMenu item mouseEvent model = - mouseEvent.clientPos - |> Coordinates.fromTuple - |> Queue.historyMenu - { cached = model.cachedTracks - , cachingInProgress = model.cachingTracksInProgress - } - item - |> Just - |> (\c -> { model | contextMenu = c }) - |> Return.singleton - - -toggleRepeat : Manager -toggleRepeat model = - saveEnclosedUserData { model | repeat = not model.repeat } - - -toggleShuffle : Manager -toggleShuffle model = - { model | shuffle = not model.shuffle } - |> reset - |> andThen saveEnclosedUserData - - - --- ๐Ÿ›  โ–‘โ–‘ FUTURE - - -addTracks : { inFront : Bool, tracks : List IdentifiedTrack } -> Manager -addTracks { inFront, tracks } = - (if inFront then - injectFirst - - else - injectLast - ) - { showNotification = True } - tracks - - -{-| Add an item in front of the queue. --} -injectFirst : { showNotification : Bool } -> List IdentifiedTrack -> Manager -injectFirst { showNotification } identifiedTracks model = - let - ( items, tracks ) = - ( List.map (makeItem True) identifiedTracks - , List.map Tuple.second identifiedTracks - ) - - cleanedFuture = - List.foldl - (.id >> Fill.cleanAutoGenerated model.shuffle) - model.playingNext - tracks - - notification = - case tracks of - [ t ] -> - ("__" ++ t.tags.title ++ "__ will be played next") - |> Notifications.casual - - list -> - list - |> List.length - |> String.fromInt - |> (\s -> "__" ++ s ++ " tracks__ will be played next") - |> Notifications.casual - in - { model | playingNext = items ++ cleanedFuture } - |> (if showNotification then - Common.showNotification notification - - else - Return.singleton - ) - |> andThen fill - - -injectFirstAndPlay : IdentifiedTrack -> Manager -injectFirstAndPlay identifiedTrack model = - model - |> injectFirst { showNotification = False } [ identifiedTrack ] - |> andThen shift - - -{-| Add an item after the last manual entry -(ie. after the last injected item). --} -injectLast : { showNotification : Bool } -> List IdentifiedTrack -> Manager -injectLast { showNotification } identifiedTracks model = - let - ( items, tracks ) = - ( List.map (makeItem True) identifiedTracks - , List.map Tuple.second identifiedTracks - ) - - cleanedFuture = - List.foldl - (.id >> Fill.cleanAutoGenerated model.shuffle) - model.playingNext - tracks - - manualItems = - cleanedFuture - |> List.filter (.manualEntry >> (==) True) - |> List.length - - newFuture = - [] - ++ List.take manualItems cleanedFuture - ++ items - ++ List.drop manualItems cleanedFuture - - notification = - case tracks of - [ t ] -> - ("__" ++ t.tags.title ++ "__ was added to the queue") - |> Notifications.casual - - list -> - list - |> List.length - |> String.fromInt - |> (\s -> "__" ++ s ++ " tracks__ were added to the queue") - |> Notifications.casual - in - { model | playingNext = newFuture } - |> (if showNotification then - Common.showNotification notification - - else - Return.singleton - ) - |> andThen fill - - -moveItemToFirst : { index : Int } -> Manager -moveItemToFirst { index } model = - model.playingNext - |> moveItem { from = index, to = 0, shuffle = model.shuffle } - |> (\f -> { model | playingNext = f }) - |> fill - - -moveItemToLast : { index : Int } -> Manager -moveItemToLast { index } model = - let - to = - model.playingNext - |> List.filter (.manualEntry >> (==) True) - |> List.length - in - model.playingNext - |> moveItem { from = index, to = to, shuffle = model.shuffle } - |> (\f -> { model | playingNext = f }) - |> fill - - -removeItem : { index : Int, item : Item } -> Manager -removeItem { index, item } model = - let - newFuture = - List.removeAt index model.playingNext - - newIgnored = - if item.manualEntry then - model.dontPlay - - else - item :: model.dontPlay - in - fill { model | playingNext = newFuture, dontPlay = newIgnored } - - - --- โš—๏ธ - - -coverTracksHarvester : - IdentifiedTrack - -> ( List IdentifiedTrack, List String ) - -> ( List IdentifiedTrack, List String ) -coverTracksHarvester ( i, t ) ( acc, coverTrackIds ) = - case List.findIndex ((==) t.id) coverTrackIds of - Just idx -> - ( acc ++ [ ( i, t ) ] - , List.removeAt idx coverTrackIds - ) - - Nothing -> - ( acc - , coverTrackIds - ) - - -moveItem : { from : Int, to : Int, shuffle : Bool } -> List Item -> List Item -moveItem { from, to, shuffle } collection = - let - subjectItem = - collection - |> List.getAt from - |> Maybe.map (\s -> { s | manualEntry = True }) - - fixedTarget = - if to > from then - to - 1 - - else - to - in - collection - |> List.removeAt from - |> List.indexedFoldr - (\idx existingItem acc -> - if idx == fixedTarget then - case subjectItem of - Just itemToInsert -> - List.append [ itemToInsert, existingItem ] acc - - Nothing -> - existingItem :: acc - - else if idx < fixedTarget then - { existingItem | manualEntry = True } :: acc - - else - existingItem :: acc - ) - [] - |> (if shuffle then - identity - - else - List.filter (.manualEntry >> (==) True) - ) diff --git a/src/Core/UI/Queue/Types.elm b/src/Core/UI/Queue/Types.elm deleted file mode 100644 index 9cc99e02e..000000000 --- a/src/Core/UI/Queue/Types.elm +++ /dev/null @@ -1,34 +0,0 @@ -module UI.Queue.Types exposing (..) - -import Html.Events.Extra.Mouse as Mouse -import Queue exposing (Item) -import Tracks exposing (IdentifiedTrack) - - - --- ๐Ÿ“ฃ - - -type Msg - = Clear - | PreloadNext - | Reset - | ResetIgnored - | Rewind - | Select Item - | Shift - | ShowFutureMenu Item { index : Int } Mouse.Event - | ShowFutureNavigationMenu Mouse.Event - | ShowHistoryMenu Item Mouse.Event - | ToggleRepeat - | ToggleShuffle - ------------------------------------ - -- Future - ------------------------------------ - | AddTracks { inFront : Bool, tracks : List IdentifiedTrack } - | InjectFirst { showNotification : Bool } (List IdentifiedTrack) - | InjectLast { showNotification : Bool } (List IdentifiedTrack) - | InjectFirstAndPlay IdentifiedTrack - | MoveItemToFirst { index : Int } - | MoveItemToLast { index : Int } - | RemoveItem { index : Int, item : Item } diff --git a/src/Core/UI/Routing/State.elm b/src/Core/UI/Routing/State.elm deleted file mode 100644 index b8b512278..000000000 --- a/src/Core/UI/Routing/State.elm +++ /dev/null @@ -1,161 +0,0 @@ -module UI.Routing.State exposing (linkClicked, openUrlOnNewPage, resetUrl, transition, urlChanged) - -import Browser exposing (UrlRequest) -import Browser.Navigation as Nav -import List.Extra as List -import Monocle.Lens as Lens -import Return exposing (return) -import Sources -import Sources.Services.Dropbox -import Sources.Services.Google -import UI.Common.State as Common -import UI.Page as Page exposing (Page) -import UI.Ports as Ports -import UI.Sources.Form -import UI.Sources.Page -import UI.Sources.State as Sources -import UI.Sources.Types as Sources -import UI.Types as UI exposing (Manager) -import Url exposing (Url) - - - --- ๐Ÿ”ฑ - - -linkClicked : UrlRequest -> Manager -linkClicked urlRequest model = - case urlRequest of - Browser.Internal urlWithFragment -> - let - url = - if urlWithFragment.fragment == Just "/" then - { urlWithFragment | fragment = Nothing } - - else - urlWithFragment - in - if url.path /= model.url.path then - return model (Nav.load url.path) - - else - return model (Nav.pushUrl model.navKey <| Url.toString url) - - Browser.External href -> - return model (Nav.load href) - - -openUrlOnNewPage : String -> Manager -openUrlOnNewPage url model = - url - |> Ports.openUrlOnNewPage - |> return model - - -urlChanged : Url -> Manager -urlChanged url model = - let - rewrittenUrl = - Page.rewriteUrl { url | query = Nothing } - in - case ( url.query, Page.fromUrl rewrittenUrl ) of - ( Nothing, Just page ) -> - transition page { model | page = page, url = url } - - ( Just _, Just page ) -> - return model (resetUrl model.navKey url page) - - _ -> - return model (resetUrl model.navKey url Page.Index) - - - --- TRANSITIONING - - -transition : Page -> Manager -transition page model = - case page of - ----------------------------------------- - -- Sources.NewThroughRedirect - ----------------------------------------- - Page.Sources (UI.Sources.Page.NewThroughRedirect service args) -> - let - ( form, defaultContext ) = - ( model.sourceForm - , UI.Sources.Form.defaultContext - ) - in - { defaultContext - | data = - case service of - Sources.Dropbox -> - Sources.Services.Dropbox.authorizationSourceData args - - Sources.Google -> - Sources.Services.Google.authorizationSourceData args - - _ -> - defaultContext.data - , service = - service - } - |> (\c -> { form | context = c, step = Sources.By }) - |> (\f -> { model | sourceForm = f }) - |> Return.singleton - - ----------------------------------------- - -- Sources.Edit - ----------------------------------------- - Page.Sources (UI.Sources.Page.Edit sourceId) -> - loadSourceForForm sourceId model - - ----------------------------------------- - -- Sources.Rename - ----------------------------------------- - Page.Sources (UI.Sources.Page.Rename sourceId) -> - loadSourceForForm sourceId model - - ----------------------------------------- - -- ๐Ÿ“ญ - ----------------------------------------- - _ -> - Return.singleton model - - - --- ๐Ÿš€ - - -resetUrl : Nav.Key -> Url -> Page.Page -> Cmd UI.Msg -resetUrl key url page = - Nav.replaceUrl key (url.path ++ Page.toString page) - - - --- ใŠ™๏ธ - - -loadSourceForForm : String -> Manager -loadSourceForForm sourceId model = - let - isLoading = - model.isLoading - - maybeSource = - List.find (.id >> (==) sourceId) model.sources - in - case ( isLoading, maybeSource ) of - ( False, Just source ) -> - model - |> Lens.modify Sources.formLens (\m -> { m | context = source }) - |> Return.singleton - - ( False, Nothing ) -> - Return.singleton model - - ( True, _ ) -> - -- Redirect away from edit-source page - Common.changeUrlUsingPage - (Page.Sources UI.Sources.Page.Index) - model diff --git a/src/Core/UI/Services/State.elm b/src/Core/UI/Services/State.elm deleted file mode 100644 index b0c7cdc04..000000000 --- a/src/Core/UI/Services/State.elm +++ /dev/null @@ -1,75 +0,0 @@ -module UI.Services.State exposing (..) - -import Browser.Navigation as Nav -import Common -import Http -import LastFm -import Notifications -import Return exposing (andThen, return) -import String.Ext as String -import UI.Common.State exposing (showNotification) -import UI.Types exposing (Manager, Msg(..)) -import UI.User.State.Export as User -import Url - - - --- ๐Ÿ”ฑ - - -connectLastFm : Manager -connectLastFm model = - model.url - |> Common.urlOrigin - |> String.addSuffix "?action=authenticate/lastfm" - |> Url.percentEncode - |> String.append "&cb=" - |> String.append - (String.append - "http://www.last.fm/api/auth/?api_key=" - LastFm.apiKey - ) - |> Nav.load - |> return model - - -disconnectLastFm : Manager -disconnectLastFm model = - User.saveSettings { model | lastFm = LastFm.disconnect model.lastFm } - - -gotLastFmSession : Result Http.Error String -> Manager -gotLastFmSession result model = - case result of - Err _ -> - showNotification - (Notifications.stickyError "Could not connect with Last.fm") - { model | lastFm = LastFm.failedToAuthenticate model.lastFm } - - Ok sessionKey -> - { model | lastFm = LastFm.gotSessionKey sessionKey model.lastFm } - |> showNotification - (Notifications.success "Connected successfully with Last.fm") - |> andThen - User.saveSettings - - -scrobble : { duration : Int, timestamp : Int, trackId : String } -> Manager -scrobble { duration, timestamp, trackId } model = - case Maybe.map (.item >> .identifiedTrack) model.nowPlaying of - Just ( _, track ) -> - if trackId == track.id then - ( model - , LastFm.scrobble model.lastFm - { duration = duration - , msg = Bypass - , timestamp = timestamp - , track = track - } - ) - - else - Return.singleton model - - Nothing -> - Return.singleton model diff --git a/src/Core/UI/Settings/Page.elm b/src/Core/UI/Settings/Page.elm deleted file mode 100644 index b0a94c940..000000000 --- a/src/Core/UI/Settings/Page.elm +++ /dev/null @@ -1,9 +0,0 @@ -module UI.Settings.Page exposing (Page(..)) - --- ๐ŸŒณ - - -type Page - = Data - | Index - | Sync diff --git a/src/Core/UI/Sources/ContextMenu.elm b/src/Core/UI/Sources/ContextMenu.elm deleted file mode 100644 index 15dd745ec..000000000 --- a/src/Core/UI/Sources/ContextMenu.elm +++ /dev/null @@ -1,84 +0,0 @@ -module UI.Sources.ContextMenu exposing (sourceMenu) - -import Conditional exposing (ifThenElse) -import ContextMenu exposing (..) -import Coordinates exposing (Coordinates) -import Material.Icons.Round as Icons -import Sources exposing (Source) -import UI.Page -import UI.Sources.Page -import UI.Sources.Types as Sources -import UI.Types exposing (Msg(..)) - - - --- ๐Ÿ”ฑ - - -sourceMenu : Source -> Coordinates -> ContextMenu Msg -sourceMenu source = - ContextMenu - [ Item - { icon = ifThenElse source.directoryPlaylists Icons.folder Icons.folder_open - , label = ifThenElse source.directoryPlaylists "Disable Directory Playlists" "Enable Directory Playlists" - , msg = - { sourceId = source.id } - |> Sources.ToggleDirectoryPlaylists - |> SourcesMsg - - -- - , active = False - } - - -- - , Item - { icon = Icons.edit - , label = "Edit source" - , msg = - source.id - |> UI.Sources.Page.Edit - |> UI.Page.Sources - |> ChangeUrlUsingPage - - -- - , active = False - } - - -- - , Item - { icon = Icons.sync - , label = "Process source" - , msg = - [ source ] - |> Sources.ProcessSpecific - |> SourcesMsg - - -- - , active = False - } - - -- - , Item - { icon = Icons.delete - , label = "Remove source" - , msg = - { sourceId = source.id } - |> Sources.RemoveFromCollection - |> SourcesMsg - , active = False - } - - -- - , Item - { icon = Icons.font_download - , label = "Rename source" - , msg = - source.id - |> UI.Sources.Page.Rename - |> UI.Page.Sources - |> ChangeUrlUsingPage - - -- - , active = False - } - ] diff --git a/src/Core/UI/Sources/Form.elm b/src/Core/UI/Sources/Form.elm deleted file mode 100644 index 9f675e4a9..000000000 --- a/src/Core/UI/Sources/Form.elm +++ /dev/null @@ -1,31 +0,0 @@ -module UI.Sources.Form exposing (..) - -import Sources exposing (..) -import Sources.Services as Services -import UI.Sources.Types exposing (..) - - - --- ๐ŸŒณ - - -initialModel : Form -initialModel = - { step = Where - , context = defaultContext - } - - -defaultContext : Source -defaultContext = - { id = "CHANGE_ME_PLEASE" - , data = Services.initialData defaultService - , directoryPlaylists = True - , enabled = True - , service = defaultService - } - - -defaultService : Service -defaultService = - Dropbox diff --git a/src/Core/UI/Sources/Page.elm b/src/Core/UI/Sources/Page.elm deleted file mode 100644 index 47c35bf57..000000000 --- a/src/Core/UI/Sources/Page.elm +++ /dev/null @@ -1,16 +0,0 @@ -module UI.Sources.Page exposing (Page(..)) - -import Sources exposing (Service) - - - --- ๐ŸŒณ - - -type Page - = Index - | Edit String - | New - | NewOnboarding - | NewThroughRedirect Service { codeOrToken : Maybe String, state : Maybe String } - | Rename String diff --git a/src/Core/UI/Sources/Query.elm b/src/Core/UI/Sources/Query.elm deleted file mode 100644 index 00f8993ce..000000000 --- a/src/Core/UI/Sources/Query.elm +++ /dev/null @@ -1,68 +0,0 @@ -module UI.Sources.Query exposing (..) - -import Dict -import Json.Decode as Decode -import Sources exposing (Source) -import Url exposing (Url) -import Url.Parser as Url -import Url.Parser.Query as Query - - -requestedAddition : Url -> Bool -requestedAddition url = - case Url.parse (urlParser identity) url of - Nothing -> - False - - Just [] -> - False - - Just (_ :: _) -> - True - - -sourcesFromUrl : Url -> List Source -sourcesFromUrl url = - url - |> Url.parse (urlParser <| List.filterMap fromUrl) - |> Maybe.withDefault [] - - - --- ๐Ÿ”ฌ - - -fromUrl : String -> Maybe Source -fromUrl json = - json - |> Decode.decodeString sourceParser - |> Result.toMaybe - - -urlParser individualParser = - individualParser - |> Query.custom "source" - |> Url.query - - -sourceParser : Decode.Decoder Source -sourceParser = - Decode.andThen - (\{ service, data } -> - if Dict.member "name" data then - Decode.succeed - { id = "FILL_IN_LATER" - , data = data - , directoryPlaylists = True - , enabled = True - , service = service - } - - else - Decode.fail "Missing `name` in `data` dictionary" - ) - <| - Decode.map2 - (\s d -> { service = s, data = d }) - (Decode.field "kind" Sources.serviceDecoder) - (Decode.field "data" <| Decode.dict Decode.string) diff --git a/src/Core/UI/Sources/State.elm b/src/Core/UI/Sources/State.elm deleted file mode 100644 index 10c8f592c..000000000 --- a/src/Core/UI/Sources/State.elm +++ /dev/null @@ -1,581 +0,0 @@ -module UI.Sources.State exposing (..) - -import Alien -import Browser.Navigation as Nav -import Common -import Conditional exposing (ifThenElse) -import Coordinates -import Dict -import Dict.Ext as Dict -import Html.Events.Extra.Mouse as Mouse -import Json.Decode as Json -import Json.Encode -import Monocle.Lens as Lens -import Notifications -import Return exposing (andThen, return) -import Sources exposing (..) -import Sources.Encoding as Sources -import Sources.Services as Services -import Sources.Services.Dropbox -import Sources.Services.Google -import Tracks.Collection -import UI.Common.State as Common -import UI.Page as Page -import UI.Ports as Ports -import UI.Sources.ContextMenu as Sources -import UI.Sources.Form as Form -import UI.Sources.Page as Sources -import UI.Sources.Query -import UI.Sources.Types exposing (..) -import UI.Tracks.State as Tracks -import UI.Types as UI exposing (Manager, Model) -import UI.User.State.Export as User - - - --- ๐ŸŒณ - - -formLens = - { get = .sourceForm - , set = \form m -> { m | sourceForm = form } - } - - -formContextLens = - Lens.compose - formLens - { get = .context - , set = \context m -> { m | context = context } - } - - -formStepLens = - Lens.compose - formLens - { get = .step - , set = \step m -> { m | step = step } - } - - - --- ๐Ÿ“ฃ - - -update : Msg -> Manager -update msg = - case msg of - Bypass -> - Return.singleton - - -- - FinishedProcessingSource a -> - finishedProcessingSource a - - FinishedProcessing -> - finishedProcessing - - Process -> - process - - ProcessSpecific a -> - processSpecific a - - ReportProcessingError a -> - reportProcessingError a - - ReportProcessingProgress a -> - reportProcessingProgress a - - StopProcessing -> - stopProcessing - - ----------------------------------------- - -- Collection - ----------------------------------------- - AddToCollection a -> - addToCollection a - - RemoveFromCollection a -> - removeFromCollection a - - UpdateSourceData a -> - updateSourceData a - - ----------------------------------------- - -- Form - ----------------------------------------- - AddSourceUsingForm -> - addSourceUsingForm - - EditSourceUsingForm -> - editSourceUsingForm - - RenameSourceUsingForm -> - renameSourceUsingForm - - ReturnToIndex -> - returnToIndex - - SelectService a -> - selectService a - - SetFormData a b -> - setFormData a b - - TakeStep -> - takeStep - - TakeStepBackwards -> - takeStepBackwards - - ----------------------------------------- - -- Individual - ----------------------------------------- - SourceContextMenu a b -> - sourceContextMenu a b - - ToggleActivation a -> - toggleActivation a - - ToggleDirectoryPlaylists a -> - toggleDirectoryPlaylists a - - ToggleProcessAutomatically -> - toggleProcessAutomatically - - - --- ๐Ÿ”ฑ - - -addSourcesFromUrl : Manager -addSourcesFromUrl model = - case UI.Sources.Query.sourcesFromUrl model.url of - [] -> - Return.singleton model - - sources -> - sources - |> List.foldl - (\s -> andThen <| addToCollection s) - (Return.singleton model) - |> Return.command - (Nav.replaceUrl - model.navKey - (model.url.path ++ Page.toString model.page) - ) - - -finishedProcessing : Manager -finishedProcessing model = - (case model.processingNotificationId of - Just id -> - Common.dismissNotification { id = id } - - Nothing -> - Return.singleton - ) - { model | processingContext = [] } - - -finishedProcessingSource : Json.Value -> Manager -finishedProcessingSource json model = - case Json.decodeValue Json.string json of - Ok sourceId -> - model.processingContext - |> List.filter (Tuple.first >> (/=) sourceId) - |> (\newContext -> { model | processingContext = newContext }) - |> Return.singleton - - Err _ -> - Return.singleton model - - -process : Manager -process model = - case sourcesToProcess model of - [] -> - Return.singleton model - - toProcess -> - if model.isOnline then - processSpecific toProcess model - - else - toProcess - |> List.filter Sources.worksOffline - |> (\s -> - case s of - [] -> - Return.singleton model - - _ -> - processSpecific s model - ) - - -processSpecific : List Source -> Manager -processSpecific toProcess model = - let - notification = - Notifications.stickyCasual "Processing sources ..." - - notificationId = - Notifications.id notification - - newNotifications = - List.filter - (\n -> Notifications.kind n /= Notifications.Error) - model.notifications - - processingContext = - toProcess - |> List.sortBy (.data >> Dict.fetch "name" "") - |> List.map (\{ id } -> ( id, 0 )) - - newModel = - { model - | notifications = newNotifications - , processingContext = processingContext - , processingError = Nothing - , processingNotificationId = Just notificationId - } - in - [ ( "origin" - , Json.Encode.string (Common.urlOrigin model.url) - ) - , ( "sources" - , Json.Encode.list Sources.encode toProcess - ) - ] - |> Json.Encode.object - |> Alien.broadcast Alien.ProcessSources - |> Ports.toBrain - |> return newModel - |> andThen (Common.showNotification notification) - - -reportProcessingError : Json.Value -> Manager -reportProcessingError json model = - case Json.decodeValue (Json.dict Json.string) json of - Ok dict -> - let - args = - { error = Dict.fetch "error" "" dict - , sourceId = Dict.fetch "sourceId" "" dict - } - in - [] - |> Notifications.errorWithCode - ("Could not process the _" - ++ Dict.fetch "sourceName" "" dict - ++ "_ source. I got the following response from the source:" - ) - (Dict.fetch "error" "missingError" dict) - |> Common.showNotificationWithModel - { model | processingError = Just args } - - Err _ -> - "Could not decode processing error" - |> Notifications.stickyError - |> Common.showNotificationWithModel model - - -reportProcessingProgress : Json.Value -> Manager -reportProcessingProgress json model = - case - Json.decodeValue - (Json.map2 - (\p s -> - { progress = p - , sourceId = s - } - ) - (Json.field "progress" Json.float) - (Json.field "sourceId" Json.string) - ) - json - of - Ok { progress, sourceId } -> - model.processingContext - |> List.map - (\( sid, pro ) -> - ifThenElse (sid == sourceId) - ( sid, progress ) - ( sid, pro ) - ) - |> (\processingContext -> - { model | processingContext = processingContext } - ) - |> Return.singleton - - Err _ -> - "Could not decode processing progress" - |> Notifications.stickyError - |> Common.showNotificationWithModel model - - -stopProcessing : Manager -stopProcessing model = - case model.processingNotificationId of - Just notificationId -> - Alien.StopProcessing - |> Alien.trigger - |> Ports.toBrain - |> return - { model - | processingContext = [] - , processingNotificationId = Nothing - } - |> andThen (Common.dismissNotification { id = notificationId }) - - Nothing -> - Return.singleton model - - - --- COLLECTION - - -addToCollection : Source -> Manager -addToCollection unsuitableSource model = - let - source = - setProperId - (List.length model.sources + 1) - model.currentTime - unsuitableSource - in - { model | sources = model.sources ++ [ source ] } - |> User.saveSources - |> andThen (processSpecific [ source ]) - - -removeFromCollection : { sourceId : String } -> Manager -removeFromCollection { sourceId } model = - model.sources - |> List.filter (.id >> (/=) sourceId) - |> (\c -> { model | sources = c }) - |> Return.singleton - |> andThen User.saveSources - |> andThen (Tracks.removeBySourceId sourceId) - - -updateSourceData : Json.Value -> Manager -updateSourceData json model = - json - |> Sources.decode - |> Maybe.map - (\source -> - List.map - (\s -> - if s.id == source.id then - source - - else - s - ) - model.sources - ) - |> Maybe.map (\col -> { model | sources = col }) - |> Maybe.withDefault model - |> User.saveSources - - - --- FORM - - -addSourceUsingForm : Manager -addSourceUsingForm model = - let - context = - model.sourceForm.context - - cleanContext = - { context | data = Dict.map (always String.trim) context.data } - in - model - |> formLens.set Form.initialModel - |> addToCollection cleanContext - |> andThen returnToIndex - - -editSourceUsingForm : Manager -editSourceUsingForm model = - model - |> formLens.set Form.initialModel - |> replaceSourceInCollection model.sourceForm.context - |> andThen process - |> andThen returnToIndex - - -renameSourceUsingForm : Manager -renameSourceUsingForm model = - model - |> formLens.set Form.initialModel - |> replaceSourceInCollection model.sourceForm.context - |> andThen returnToIndex - - -returnToIndex : Manager -returnToIndex = - Common.changeUrlUsingPage (Page.Sources Sources.Index) - - -selectService : String -> Manager -selectService serviceKey model = - case Services.keyToType serviceKey of - Just service -> - model - |> Lens.modify - formContextLens - (\c -> - { c - | data = Services.initialData service - , service = service - } - ) - |> Return.singleton - - Nothing -> - Return.singleton model - - -setFormData : String -> String -> Manager -setFormData key value model = - model - |> Lens.modify - formContextLens - (\context -> - context.data - |> Dict.insert key value - |> (\data -> { context | data = data }) - ) - |> Return.singleton - - -takeStep : Manager -takeStep model = - let - form = - formLens.get model - in - case ( form.step, form.context.service ) of - ( How, Dropbox ) -> - form.context.data - |> Sources.Services.Dropbox.authorizationUrl - |> externalAuthorization model - - ( How, Google ) -> - form.context.data - |> Sources.Services.Google.authorizationUrl - |> externalAuthorization model - - _ -> - model - |> Lens.modify formStepLens takeStepForwards - |> Return.singleton - - -takeStepBackwards : Manager -takeStepBackwards = - Lens.modify formStepLens takeStepBackwards_ >> Return.singleton - - - --- INDIVIDUAL - - -sourceContextMenu : Source -> Mouse.Event -> Manager -sourceContextMenu source mouseEvent model = - mouseEvent.clientPos - |> Coordinates.fromTuple - |> Sources.sourceMenu source - |> Common.showContextMenuWithModel model - - -toggleActivation : { sourceId : String } -> Manager -toggleActivation { sourceId } model = - model.sources - |> List.map - (\source -> - if source.id == sourceId then - { source | enabled = not source.enabled } - - else - source - ) - |> (\collection -> { model | sources = collection }) - |> Tracks.reviseCollection Tracks.Collection.identify - |> andThen Common.forceTracksRerender - |> andThen Common.generateDirectoryPlaylists - |> andThen User.saveSources - - -toggleDirectoryPlaylists : { sourceId : String } -> Manager -toggleDirectoryPlaylists { sourceId } model = - model.sources - |> List.map - (\source -> - if source.id == sourceId then - { source | directoryPlaylists = not source.directoryPlaylists } - - else - source - ) - |> (\collection -> { model | sources = collection }) - |> User.saveSources - |> andThen Common.generateDirectoryPlaylists - - -toggleProcessAutomatically : Manager -toggleProcessAutomatically model = - User.saveSettings { model | processAutomatically = not model.processAutomatically } - - - --- โš—๏ธ - - -externalAuthorization : Model -> (String -> String) -> ( Model, Cmd UI.Msg ) -externalAuthorization model urlBuilder = - model.url - |> Common.urlOrigin - |> urlBuilder - |> Nav.load - |> return model - - -replaceSourceInCollection : Source -> Manager -replaceSourceInCollection source model = - model.sources - |> List.map (\s -> ifThenElse (s.id == source.id) source s) - |> (\s -> { model | sources = s }) - |> User.saveSources - - -sourcesToProcess : Model -> List Source -sourcesToProcess model = - List.filter (.enabled >> (==) True) model.sources - - -takeStepForwards : FormStep -> FormStep -takeStepForwards currentStep = - case currentStep of - Where -> - How - - _ -> - By - - -takeStepBackwards_ : FormStep -> FormStep -takeStepBackwards_ currentStep = - case currentStep of - By -> - How - - _ -> - Where diff --git a/src/Core/UI/Sources/Types.elm b/src/Core/UI/Sources/Types.elm deleted file mode 100644 index f3d6e515b..000000000 --- a/src/Core/UI/Sources/Types.elm +++ /dev/null @@ -1,61 +0,0 @@ -module UI.Sources.Types exposing (..) - -import Html.Events.Extra.Mouse as Mouse -import Json.Decode as Json -import Sources exposing (..) - - - --- ๐ŸŒณ - - -type alias Form = - { context : Source - , step : FormStep - } - - -type FormStep - = Where - | How - | By - - - --- ๐Ÿ“ฃ - - -type Msg - = Bypass - -- - | FinishedProcessingSource Json.Value - | FinishedProcessing - | Process - | ProcessSpecific (List Source) - | ReportProcessingError Json.Value - | ReportProcessingProgress Json.Value - | StopProcessing - ----------------------------------------- - -- Collection - ----------------------------------------- - | AddToCollection Source - | RemoveFromCollection { sourceId : String } - | UpdateSourceData Json.Value - ----------------------------------------- - -- Form - ----------------------------------------- - | AddSourceUsingForm - | EditSourceUsingForm - | RenameSourceUsingForm - | ReturnToIndex - | SelectService String - | SetFormData String String - | TakeStep - | TakeStepBackwards - ----------------------------------------- - -- Individual - ----------------------------------------- - | SourceContextMenu Source Mouse.Event - | ToggleActivation { sourceId : String } - | ToggleDirectoryPlaylists { sourceId : String } - | ToggleProcessAutomatically diff --git a/src/Core/UI/Svg/Elements.elm b/src/Core/UI/Svg/Elements.elm deleted file mode 100644 index 359ef4799..000000000 --- a/src/Core/UI/Svg/Elements.elm +++ /dev/null @@ -1,118 +0,0 @@ -module UI.Svg.Elements exposing (dropboxLogo, ipfsLogo, loading, loadingWithSize, remoteStorageLogo) - -import Svg exposing (..) -import Svg.Attributes exposing (..) - - - --- LOGOS - - -ipfsLogo : Int -> Svg Never -ipfsLogo size = - svg - [ height (String.fromInt size) - , viewBox "0 0 511.99999 511.99998" - , width (String.fromInt size) - ] - [ -- Group 1 - ---------- - g - [ transform "translate(-50.017 -515.51)" ] - [ Svg.path - [ d "m283.13 546.35-160.74 92.806c0.32126 2.8543 0.32125 5.7352 0 8.5894l160.75 92.806c13.554-10.001 32.043-10.001 45.597 0l160.75-92.807c-0.32126-2.8543-0.32293-5.7338-0.001-8.588l-160.74-92.806c-13.554 10.001-32.044 10.001-45.599 0zm221.79 127.03-160.92 93.84c1.884 16.739-7.3611 32.751-22.799 39.489l0.18062 184.58c2.6325 1.1489 5.1267 2.5886 7.438 4.294l160.75-92.805c-1.884-16.739 7.3611-32.752 22.799-39.49v-185.61c-2.6325-1.1489-5.1281-2.5886-7.4394-4.294zm-397.81 1.0315c-2.3112 1.7054-4.8054 3.1465-7.438 4.2954v185.61c15.438 6.7378 24.683 22.75 22.799 39.489l160.74 92.806c2.3112-1.7054 4.8069-3.1465 7.4394-4.2954v-185.61c-15.438-6.7378-24.683-22.75-22.799-39.489l-160.74-92.81z" - , fill "currentColor" - ] - [] - ] - - -- Group 2 - ---------- - , g - [ fill "currentColor" - , transform "translate(0 -196.66)" - ] - [ Svg.path - [ d "m256 708.66 221.7-128v-256l-221.7 128v256z" - , fillOpacity "1" - ] - [] - , Svg.path - [ d "m256 708.66v-256l-221.7-128v256l221.7 128z" - , fillOpacity ".75" - ] - [] - , Svg.path - [ d "m34.298 324.66 221.7 128 221.7-128-221.7-128-221.7 128z" - , fillOpacity ".5" - ] - [] - ] - ] - - -dropboxLogo : Int -> Svg Never -dropboxLogo size = - svg - [ height (String.fromInt size) - , viewBox "0 0 43 40" - , width (String.fromInt size) - ] - [ Svg.path - [ d "m12.5 0l-12.5 8.1 8.7 7 12.5-7.8-8.7-7.3zm-12.5 21.9l12.5 8.2 8.7-7.3-12.5-7.7-8.7 6.8zm21.2 0.9l8.8 7.3 12.4-8.1-8.6-6.9-12.6 7.7zm21.2-14.7l-12.4-8.1-8.8 7.3 12.6 7.8 8.6-7zm-21.1 16.3l-8.8 7.3-3.7-2.5v2.8l12.5 7.5 12.5-7.5v-2.8l-3.8 2.5-8.7-7.3z" - - -- - , fill "currentColor" - ] - [] - ] - - -remoteStorageLogo : Int -> Svg Never -remoteStorageLogo size = - svg - [ clipRule "evenodd" - , fillRule "evenodd" - , height (String.fromInt size) - , imageRendering "optimizeQuality" - , shapeRendering "geometricPrecision" - , textRendering "geometricPrecision" - , viewBox "0 0 739 853" - , width (String.fromInt size) - ] - [ polygon - [ points "370,754 0,542 0,640 185,747 370,853 554,747 739,640 739,525 739,525 739,476 739,427 739,378 653,427 370,589 86,427 86,427 86,361 185,418 370,524 554,418 653,361 739,311 739,213 739,213 554,107 370,0 185,107 58,180 144,230 228,181 370,100 511,181 652,263 370,425 87,263 87,263 0,213 0,213 0,311 0,378 0,427 0,476 86,525 185,582 370,689 554,582 653,525 653,590 653,592" - , fill "currentColor" - ] - [] - ] - - - --- LOADING ANIMATION - - -loading : Svg Never -loading = - loadingWithSize 29 - - -loadingWithSize : Int -> Svg Never -loadingWithSize size = - svg - [ class "loading-animation" - , height (String.fromInt size) - , viewBox "0 0 30 30" - , width (String.fromInt size) - ] - [ circle - [ class "loading-animation__circle" - , cx "15" - , cy "15" - , fill "none" - , r "14" - , strokeLinecap "round" - , strokeWidth "2" - ] - [] - ] diff --git a/src/Core/UI/Syncing/Common.elm b/src/Core/UI/Syncing/Common.elm deleted file mode 100644 index 6eab02541..000000000 --- a/src/Core/UI/Syncing/Common.elm +++ /dev/null @@ -1,69 +0,0 @@ -module UI.Syncing.Common exposing (..) - -import Chunky exposing (..) -import Html -import Svg -import UI.Svg.Elements -import UI.Syncing.Types exposing (..) -import UI.Types exposing (Msg(..)) -import User.Layer exposing (Method, dropboxMethod, remoteStorageMethod) - - - --- ๐Ÿš€ - - -startDropbox : Msg -startDropbox = - SyncingMsg (TriggerExternalAuth dropboxMethod "") - - -startIpfs : Msg -startIpfs = - SyncingMsg PingIpfs - - -startRemoteStorage : Msg -startRemoteStorage = - { icon = \size _ -> Svg.map never (UI.Svg.Elements.remoteStorageLogo size) - , placeholder = "example@5apps.com" - , question = - { question = - "What's your user address?" - , info = - [ Html.text "The format is " - , inline - [] - [ Html.text "username@server.domain" ] - ] - } - , value = "" - } - |> AskForInput remoteStorageMethod - |> SyncingMsg - - - --- ๐Ÿ›  - - -extractMethod : State -> Maybe Method -extractMethod state = - case state of - Synced method -> - Just method - - Syncing { method } -> - Just method - - InputScreen method _ -> - Just method - - NewEncryptionKeyScreen method _ -> - Just method - - UpdateEncryptionKeyScreen method _ -> - Just method - - NotSynced -> - Nothing diff --git a/src/Core/UI/Syncing/ContextMenu.elm b/src/Core/UI/Syncing/ContextMenu.elm deleted file mode 100644 index aa32f896b..000000000 --- a/src/Core/UI/Syncing/ContextMenu.elm +++ /dev/null @@ -1,37 +0,0 @@ -module UI.Syncing.ContextMenu exposing (syncDataMenu) - -import ContextMenu exposing (..) -import Coordinates exposing (Coordinates) -import Svg -import UI.Svg.Elements -import UI.Syncing.Common exposing (startDropbox, startIpfs, startRemoteStorage) -import UI.Types exposing (Msg) -import User.Layer exposing (dropboxMethod, ipfsMethod, methodName, remoteStorageMethod) - - - --- ๐Ÿ”ฑ - - -syncDataMenu : Coordinates -> ContextMenu Msg -syncDataMenu = - ContextMenu - [ Item - { icon = \_ _ -> Svg.map never (UI.Svg.Elements.dropboxLogo 16) - , label = methodName dropboxMethod - , msg = startDropbox - , active = False - } - , Item - { icon = \_ _ -> Svg.map never (UI.Svg.Elements.remoteStorageLogo 16) - , label = methodName remoteStorageMethod - , msg = startRemoteStorage - , active = False - } - , Item - { icon = \_ _ -> Svg.map never (UI.Svg.Elements.ipfsLogo 16) - , label = methodName ipfsMethod - , msg = startIpfs - , active = False - } - ] diff --git a/src/Core/UI/Syncing/State.elm b/src/Core/UI/Syncing/State.elm deleted file mode 100644 index c8de179ec..000000000 --- a/src/Core/UI/Syncing/State.elm +++ /dev/null @@ -1,642 +0,0 @@ -module UI.Syncing.State exposing (..) - -import Alien -import Base64 -import Binary -import Browser.Navigation as Nav -import Common -import Coordinates -import Dict -import Html -import Html.Attributes -import Html.Events.Extra.Mouse as Mouse -import Http -import Http.Ext as Http -import Json.Decode as Json -import Json.Encode -import Lens.Ext as Lens -import Management -import Maybe.Extra as Maybe -import Monocle.Lens exposing (Lens) -import Notifications -import Return exposing (andThen, return) -import SHA -import String.Ext as String -import Svg -import Time -import UI.Backdrop as Backdrop -import UI.Common.State as Common exposing (showNotification, showNotificationWithModel) -import UI.Ports as Ports -import UI.Sources.State as Sources -import UI.Svg.Elements -import UI.Syncing.ContextMenu as Syncing -import UI.Syncing.Types as Syncing exposing (..) -import UI.Types as UI exposing (..) -import Url exposing (Protocol(..), Url) -import Url.Ext as Url -import UrlBase64 -import User.Layer exposing (..) -import User.Layer.Methods.Dropbox as Dropbox -import User.Layer.Methods.RemoteStorage as RemoteStorage - - - --- โ›ฉ - - -minimumPassphraseLength : Int -minimumPassphraseLength = - 16 - - -passphraseLengthErrorMessage : String -passphraseLengthErrorMessage = - "Your passphrase should be atleast *16 characters* long." - - - --- ๐ŸŒณ - - -initialModel : Url -> Syncing.State -initialModel url = - case Url.action url of - [ "authenticate", "remotestorage", encodedUserAddress ] -> - let - dict = - Url.queryDictionary { url | query = url.fragment } - - userAddress = - encodedUserAddress - |> Url.percentDecode - |> Maybe.andThen (UrlBase64.decode Base64.decode >> Result.toMaybe) - |> Maybe.withDefault encodedUserAddress - in - case Dict.get "access_token" dict of - Just t -> - NewEncryptionKeyScreen - (RemoteStorage - { userAddress = userAddress - , token = t - } - ) - Nothing - - Nothing -> - NotSynced - - _ -> - NotSynced - - -initialCommand : Url -> Cmd Syncing.Msg -initialCommand url = - case Url.action url of - [ "authenticate", "dropbox" ] -> - case Dict.get "code" (Url.queryDictionary url) of - Just code -> - Dropbox.exchangeAuthCode - ExchangeDropboxAuthCode - url - code - - _ -> - Cmd.none - - _ -> - Cmd.none - - -lens : Lens UI.Model Syncing.State -lens = - { get = .syncing - , set = \a m -> { m | syncing = a } - } - - - --- ๐Ÿ“ฃ - - -update : Syncing.Msg -> Manager -update msg = - case msg of - Syncing.Bypass -> - Return.singleton - - ActivateSync a -> - activateSync a - - ActivateSyncWithPassphrase a b -> - activateSyncWithPassphrase a b - - BootFailure a -> - bootFailure a - - ExchangeDropboxAuthCode a -> - exchangeDropboxAuthCode a - - GotSyncMethod a -> - gotSyncMethod a - - RemoteStorageWebfinger a b -> - remoteStorageWebfinger a b - - ShowSyncDataMenu a -> - showSyncDataMenu a - - StartedSyncing a -> - startedSyncing a - - StopSync -> - stopSync - - TriggerExternalAuth a b -> - externalAuth a b - - ----------------------------------------- - -- Encryption - ----------------------------------------- - KeepPassphraseInMemory a -> - keepPassphraseInMemory a - - NeedEncryptionKey a -> - needEncryptionKey a - - RemoveEncryptionKey a -> - removeEncryptionKey a - - ShowNewEncryptionKeyScreen a -> - showNewEncryptionKeyScreen a - - ShowUpdateEncryptionKeyScreen a -> - showUpdateEncryptionKeyScreen a - - UpdateEncryptionKey a b -> - updateEncryptionKey a b - - ----------------------------------------- - -- IPFS - ----------------------------------------- - PingIpfs -> - pingIpfs - - PingIpfsCallback a -> - pingIpfsCallback a - - PingOtherIpfs a -> - pingOtherIpfs a - - PingOtherIpfsCallback a b -> - pingOtherIpfsCallback a b - - ----------------------------------------- - -- More Input - ----------------------------------------- - AskForInput a b -> - askForInput a b - - CancelInput -> - cancelInput - - ConfirmInput -> - confirmInput - - Input a -> - input a - - -organize : Organizer Syncing.State -> Manager -organize = - Management.organize lens - - -replaceState : Syncing.State -> Manager -replaceState state = - lens.set state >> Return.singleton - - - --- ๐Ÿ”ฑ - - -activateSync : Method -> Manager -activateSync method model = - [ ( "method", encodeMethod method ) - , ( "passphrase", Json.Encode.null ) - ] - |> Json.Encode.object - |> Alien.broadcast Alien.SetSyncMethod - |> Ports.toBrain - -- - |> return model - - -activateSyncWithPassphrase : Method -> String -> Manager -activateSyncWithPassphrase method passphrase model = - if String.length passphrase < minimumPassphraseLength then - passphraseLengthErrorMessage - |> Notifications.error - |> Common.showNotificationWithModel model - - else - [ ( "method", encodeMethod method ) - , ( "passphrase", Json.Encode.string <| hashPassphrase passphrase ) - ] - |> Json.Encode.object - |> Alien.broadcast Alien.SetSyncMethod - |> Ports.toBrain - -- - |> return model - - -bootFailure : String -> Manager -bootFailure err model = - model - |> showNotification (Notifications.error err) - |> andThen Backdrop.setDefault - - -externalAuth : Method -> String -> Manager -externalAuth method string model = - case method of - Dropbox _ -> - [ ( "client_id", Dropbox.clientId ) - , ( "redirect_uri", Dropbox.redirectUri model.url ) - , ( "response_type", "code" ) - , ( "token_access_type", "offline" ) - ] - |> Common.queryString - |> String.append "https://www.dropbox.com/oauth2/authorize" - |> Nav.load - |> return model - - RemoteStorage _ -> - string - |> RemoteStorage.parseUserAddress - |> Maybe.map (RemoteStorage.webfingerRequest RemoteStorageWebfinger model.url.protocol) - |> Maybe.map (Cmd.map SyncingMsg) - |> Maybe.unwrap - (RemoteStorage.userAddressError - |> Notifications.error - |> Common.showNotificationWithModel model - ) - (return model) - - _ -> - Return.singleton model - - -exchangeDropboxAuthCode : Result Http.Error Dropbox.Tokens -> Manager -exchangeDropboxAuthCode result model = - case result of - Ok tokens -> - case tokens.refreshToken of - Just refreshToken -> - Nothing - |> NewEncryptionKeyScreen - (Dropbox - { accessToken = tokens.accessToken - , expiresAt = Time.posixToMillis model.currentTime // 1000 + tokens.expiresIn - , refreshToken = refreshToken - } - ) - |> Lens.replace lens model - |> Return.singleton - - Nothing -> - "Missing refresh token in Dropbox code exchange flow." - |> Notifications.stickyError - |> showNotificationWithModel - (Lens.replace lens model NotSynced) - - Err err -> - [] - |> Notifications.errorWithCode - "Failed to authenticate with Dropbox" - (Http.errorToString err) - |> showNotificationWithModel - (Lens.replace lens model NotSynced) - - -gotSyncMethod : Json.Value -> Manager -gotSyncMethod json model = - let - afterwards a = - andThen - (\m -> - if m.processAutomatically then - Sources.process m - - else - Return.singleton m - ) - (case model.syncing of - Syncing { notificationId } -> - Common.dismissNotification { id = notificationId } a - - _ -> - Return.singleton a - ) - in - -- ๐Ÿง  told me which auth method we're using, - -- so we can tell the user in the UI. - case decodeMethod json of - Just method -> - model - |> replaceState (Synced method) - |> andThen afterwards - - Nothing -> - afterwards model - - -remoteStorageWebfinger : RemoteStorage.Attributes -> Result Http.Error String -> Manager -remoteStorageWebfinger remoteStorage result model = - case result of - Ok oauthOrigin -> - let - origin = - Common.urlOrigin model.url - in - remoteStorage - |> RemoteStorage.oauthAddress - { oauthOrigin = oauthOrigin - , origin = origin - } - |> Nav.load - |> return model - - Err _ -> - RemoteStorage.webfingerError - |> Notifications.error - |> showNotificationWithModel model - - -showSyncDataMenu : Mouse.Event -> Manager -showSyncDataMenu mouseEvent model = - mouseEvent.clientPos - |> Coordinates.fromTuple - |> Syncing.syncDataMenu - |> Common.showContextMenuWithModel model - - -startedSyncing : Json.Value -> Manager -startedSyncing json = - -- ๐Ÿง  started syncing - case decodeMethod json of - Just method -> - Common.showSyncingNotification method - - Nothing -> - Return.singleton - - -stopSync : Manager -stopSync model = - Alien.UnsetSyncMethod - |> Alien.trigger - |> Ports.toBrain - |> return model - |> andThen (replaceState NotSynced) - - - --- ENCRYPTION - - -keepPassphraseInMemory : String -> Manager -keepPassphraseInMemory passphrase model = - (\state -> - case state of - NewEncryptionKeyScreen method _ -> - NewEncryptionKeyScreen method (Just passphrase) - - UpdateEncryptionKeyScreen method _ -> - UpdateEncryptionKeyScreen method (Just passphrase) - - s -> - s - ) - |> Lens.adjust lens model - |> Return.singleton - - -needEncryptionKey : { error : String } -> Manager -needEncryptionKey { error } model = - (case lens.get model of - Syncing { notificationId } -> - Common.dismissNotification { id = notificationId } model - - m -> - replaceState m model - ) - |> andThen - (error - |> Notifications.stickyError - |> Common.showNotification - ) - |> andThen - stopSync - - -removeEncryptionKey : Method -> Manager -removeEncryptionKey method model = - Alien.RemoveEncryptionKey - |> Alien.trigger - |> Ports.toBrain - -- - |> return - (lens.set (Synced method) model) - |> andThen - ("Saving data without encryption ..." - |> Notifications.success - |> Common.showNotification - ) - |> andThen - Common.forceTracksRerender - - -showNewEncryptionKeyScreen : Method -> Manager -showNewEncryptionKeyScreen method = - replaceState (NewEncryptionKeyScreen method Nothing) - - -showUpdateEncryptionKeyScreen : Method -> Manager -showUpdateEncryptionKeyScreen method = - replaceState (UpdateEncryptionKeyScreen method Nothing) - - -updateEncryptionKey : Method -> String -> Manager -updateEncryptionKey method passphrase model = - if String.length passphrase < minimumPassphraseLength then - passphraseLengthErrorMessage - |> Notifications.error - |> Common.showNotificationWithModel model - - else - passphrase - |> hashPassphrase - |> Json.Encode.string - |> Alien.broadcast Alien.UpdateEncryptionKey - |> Ports.toBrain - -- - |> return - (lens.set (Synced method) model) - |> andThen - ("Encrypting data with new passphrase ..." - |> Notifications.success - |> Common.showNotification - ) - |> andThen - Common.forceTracksRerender - - - --- IPFS - - -pingIpfs : Manager -pingIpfs model = - case model.url.protocol of - Https -> - """ - Unfortunately the local IPFS API doesn't work with HTTPS. - Install the [IPFS Companion](https://github.com/ipfs-shipyard/ipfs-companion#release-channel) browser extension to get around this issue - (and make sure it redirects to the local gateway). - """ - |> Notifications.error - |> Common.showNotificationWithModel model - - Http -> - { url = "//localhost:5001/api/v0/id" - , expect = Http.expectWhatever (SyncingMsg << PingIpfsCallback) - , body = Http.emptyBody - } - |> Http.post - |> return model - - -pingIpfsCallback : Result Http.Error () -> Manager -pingIpfsCallback result = - case result of - Ok _ -> - { apiOrigin = "//localhost:5001" } - |> Ipfs - |> showNewEncryptionKeyScreen - - Err _ -> - askForInput - (Ipfs { apiOrigin = "" }) - { icon = \size _ -> Svg.map never (UI.Svg.Elements.ipfsLogo size) - , placeholder = "//localhost:5001" - , question = - { question = - "Where's your IPFS API located?" - , info = - [ Html.text "You can find this address on the IPFS Web UI." - , Html.br [] [] - , Html.text "Most likely you'll also need to setup CORS." - , Html.br [] [] - , Html.text "You can find the instructions for that " - , Html.a - [ Html.Attributes.class "border-b border-current-color font-semibold inline-block leading-tight" - , Html.Attributes.href "about/cors/#CORS__IPFS" - , Html.Attributes.target "_blank" - ] - [ Html.text "here" ] - ] - } - , value = "//localhost:5001" - } - - -pingOtherIpfs : String -> Manager -pingOtherIpfs origin model = - { url = origin ++ "/api/v0/id" - , expect = Http.expectWhatever (SyncingMsg << PingOtherIpfsCallback origin) - , body = Http.emptyBody - } - |> Http.post - |> return model - - -pingOtherIpfsCallback : String -> Result Http.Error () -> Manager -pingOtherIpfsCallback origin result = - case result of - Ok _ -> - { apiOrigin = origin } - |> Ipfs - |> showNewEncryptionKeyScreen - - Err _ -> - "Can't reach this IPFS API, maybe it's offline? Or I don't have access?" - |> Notifications.error - |> Common.showNotification - - - --- MORE INPUT - - -askForInput : Method -> Question -> Manager -askForInput method question = - question - |> InputScreen method - |> replaceState - - -cancelInput : Manager -cancelInput model = - case lens.get model of - InputScreen _ _ -> - replaceState NotSynced model - - NewEncryptionKeyScreen _ _ -> - replaceState NotSynced model - - UpdateEncryptionKeyScreen method _ -> - replaceState (Synced method) model - - m -> - replaceState m model - - -confirmInput : Manager -confirmInput model = - case lens.get model of - InputScreen (Ipfs _) { value } -> - pingOtherIpfs (String.chopEnd "/" value) model - - InputScreen (RemoteStorage r) { value } -> - externalAuth (RemoteStorage r) value model - - _ -> - Return.singleton model - - -input : String -> Manager -input string model = - (\state -> - case state of - InputScreen method opts -> - InputScreen method { opts | value = string } - - s -> - s - ) - |> Lens.adjust lens model - |> Return.singleton - - - --- ๐Ÿ›  - - -hashPassphrase : String -> String -hashPassphrase phrase = - phrase - |> Binary.fromStringAsUtf8 - |> SHA.sha256 - |> Binary.toHex - |> String.toLower diff --git a/src/Core/UI/Syncing/Types.elm b/src/Core/UI/Syncing/Types.elm deleted file mode 100644 index ba5431af8..000000000 --- a/src/Core/UI/Syncing/Types.elm +++ /dev/null @@ -1,74 +0,0 @@ -module UI.Syncing.Types exposing (Msg(..), Question, State(..)) - -import Html exposing (Html) -import Html.Events.Extra.Mouse as Mouse -import Http -import Json.Decode as Json -import Material.Icons.Types exposing (Coloring) -import Svg exposing (Svg) -import User.Layer exposing (Method) -import User.Layer.Methods.Dropbox as Dropbox -import User.Layer.Methods.RemoteStorage as RemoteStorage - - - --- ๐ŸŒณ - - -type State - = NotSynced - | Synced Method - | Syncing { method : Method, notificationId : Int } - | InputScreen Method Question - | NewEncryptionKeyScreen Method (Maybe String) - | UpdateEncryptionKeyScreen Method (Maybe String) - - -type alias Question = - { icon : Int -> Coloring -> Svg Msg - , placeholder : String - , question : { question : String, info : List (Html Msg) } - , value : String - } - - - --- ๐Ÿ“ฃ - - -type Msg - = Bypass - -- - | ActivateSync Method - | ActivateSyncWithPassphrase Method String - | BootFailure String - | ExchangeDropboxAuthCode (Result Http.Error Dropbox.Tokens) - | GotSyncMethod Json.Value - | RemoteStorageWebfinger RemoteStorage.Attributes (Result Http.Error String) - | ShowSyncDataMenu Mouse.Event - | StartedSyncing Json.Value - | StopSync - | TriggerExternalAuth Method String - ----------------------------------------- - -- Encryption - ----------------------------------------- - | KeepPassphraseInMemory String - | NeedEncryptionKey { error : String } - | RemoveEncryptionKey Method - | ShowNewEncryptionKeyScreen Method - | ShowUpdateEncryptionKeyScreen Method - | UpdateEncryptionKey Method String - ----------------------------------------- - -- IPFS - ----------------------------------------- - | PingIpfs - | PingIpfsCallback (Result Http.Error ()) - | PingOtherIpfs String - | PingOtherIpfsCallback String (Result Http.Error ()) - ----------------------------------------- - -- More Input - ----------------------------------------- - | AskForInput Method Question - | CancelInput - | ConfirmInput - | Input String diff --git a/src/Core/UI/Theme.elm b/src/Core/UI/Theme.elm deleted file mode 100644 index 7a43d0220..000000000 --- a/src/Core/UI/Theme.elm +++ /dev/null @@ -1,113 +0,0 @@ -module UI.Theme exposing (..) - -import Chunky exposing (..) -import Dict exposing (Dict) -import Html exposing (Html) -import Theme exposing (Theme) -import Themes.Sunrise.Theme as Sunrise -import Themes.Sunrise.Tracks.Scene.Covers -import Themes.Sunrise.Tracks.Scene.List -import Tracks exposing (IdentifiedTrack, Scene) -import UI.Svg.Elements -import UI.Types exposing (Model, Msg) - - - --- ๐Ÿ”ฎ - - -list : List (Theme Msg Model) -list = - [ Sunrise.theme - ] - - -default : Theme Msg Model -default = - Sunrise.theme - - - --- ๐Ÿšง - - -dict : Dict String (Theme Msg Model) -dict = - list - |> List.map - (\theme -> - ( theme.id, theme ) - ) - |> Dict.fromList - - -view : Model -> Html Msg -view model = - if model.isLoading then - loadingAnimation - - else - case model.theme of - Just { id } -> - case Dict.get id dict of - Just theme -> - theme.view model - - Nothing -> - default.view model - - Nothing -> - default.view model - - -loadingAnimation = - chunk - [ "flex" - , "flex-col" - , "items-center" - , "justify-center" - , "screen-height" - , "w-screen" - ] - [ loadingAnimationSvg - , chunk - [ "italic" - , "mt-5" - , "text-white" - , "text-opacity-30" - ] - [ Html.text "Transmitting particles" ] - ] - - -loadingAnimationSvg = - Html.map never UI.Svg.Elements.loading - - - --- TODO - - -scrollTracksToTop : Scene -> Cmd Msg -scrollTracksToTop scene = - case scene of - Tracks.Covers -> - Themes.Sunrise.Tracks.Scene.List.scrollToTop - - Tracks.List -> - Themes.Sunrise.Tracks.Scene.Covers.scrollToTop - - -scrollToNowPlaying : Scene -> IdentifiedTrack -> Model -> Cmd Msg -scrollToNowPlaying scene ( identifiers, track ) model = - case scene of - Tracks.Covers -> - Themes.Sunrise.Tracks.Scene.Covers.scrollToNowPlaying - model.viewport.width - model.covers.harvested - ( identifiers, track ) - - Tracks.List -> - Themes.Sunrise.Tracks.Scene.List.scrollToNowPlaying - model.tracks.harvested - ( identifiers, track ) diff --git a/src/Core/UI/Tracks/ContextMenu.elm b/src/Core/UI/Tracks/ContextMenu.elm deleted file mode 100644 index d9d37d5a6..000000000 --- a/src/Core/UI/Tracks/ContextMenu.elm +++ /dev/null @@ -1,377 +0,0 @@ -module UI.Tracks.ContextMenu exposing (cacheAction, trackMenu, viewMenu) - -import Conditional exposing (ifThenElse) -import ContextMenu exposing (..) -import Coordinates exposing (Coordinates) -import Material.Icons.Round as Icons -import Maybe.Extra as Maybe -import Playlists exposing (Playlist) -import Queue -import Sources exposing (Source) -import Time -import Tracks exposing (Grouping(..), IdentifiedTrack) -import UI.Queue.Types as Queue -import UI.Tracks.Types as Tracks -import UI.Types exposing (Msg(..)) - - - --- TRACK MENU - - -trackMenu : - { cached : List String - , cachingInProgress : List String - , currentTime : Time.Posix - , lastModifiedPlaylistName : Maybe { collection : Bool, name : String } - , selectedPlaylist : Maybe Playlist - , showAlternativeMenu : Bool - , sources : List Source - } - -> List IdentifiedTrack - -> Coordinates - -> ContextMenu Msg -trackMenu { cached, cachingInProgress, currentTime, selectedPlaylist, lastModifiedPlaylistName, showAlternativeMenu, sources } tracks = - if showAlternativeMenu then - [ alternativeMenuActions - currentTime - sources - tracks - ] - |> List.concat - |> ContextMenu - - else - [ queueActions - tracks - - -- - , playlistActions - { selectedPlaylist = selectedPlaylist - , lastModifiedPlaylistName = lastModifiedPlaylistName - } - tracks - - -- - , cacheAction - { cached = cached - , cachingInProgress = cachingInProgress - } - tracks - |> List.singleton - ] - |> List.concat - |> ContextMenu - - -alternativeMenuActions : - Time.Posix - -> List Source - -> List IdentifiedTrack - -> List (ContextMenu.Item Msg) -alternativeMenuActions timestamp sources tracks = - case tracks of - [ ( _, t ) ] -> - [ Item - { icon = Icons.link - , label = "Copy temporary url" - , msg = CopyToClipboard (Queue.makeTrackUrl timestamp sources t) - , active = False - } - - -- - , Item - { icon = Icons.sync - , label = "Sync tags" - , msg = TracksMsg (Tracks.SyncTags [ t ]) - , active = False - } - ] - - _ -> - [] - - -cacheAction : - { cached : List String, cachingInProgress : List String } - -> List IdentifiedTrack - -> ContextMenu.Item Msg -cacheAction { cached, cachingInProgress } tracks = - case tracks of - [ ( _, t ) ] -> - if List.member t.id cached then - Item - { icon = Icons.offline_bolt - , label = "Remove from cache" - , msg = - tracks - |> List.map Tuple.second - |> Tracks.RemoveFromCache - |> TracksMsg - - -- - , active = False - } - - else if List.member t.id cachingInProgress then - Item - { icon = Icons.offline_bolt - , label = "Downloading ..." - , msg = Bypass - , active = True - } - - else - Item - { icon = Icons.offline_bolt - , label = "Store in cache" - , msg = - tracks - |> List.map Tuple.second - |> Tracks.StoreInCache - |> TracksMsg - - -- - , active = False - } - - _ -> - Item - { icon = Icons.offline_bolt - , label = "Store in cache" - , msg = - tracks - |> List.map Tuple.second - |> Tracks.StoreInCache - |> TracksMsg - - -- - , active = False - } - - -playlistActions : - { selectedPlaylist : Maybe Playlist - , lastModifiedPlaylistName : Maybe { collection : Bool, name : String } - } - -> List IdentifiedTrack - -> List (ContextMenu.Item Msg) -playlistActions { selectedPlaylist, lastModifiedPlaylistName } tracks = - let - maybeCustomPlaylist = - Maybe.andThen - (\p -> - case p.autoGenerated of - Just _ -> - Nothing - - Nothing -> - Just p - ) - selectedPlaylist - - maybeAddToLastModifiedPlaylist { collection } = - Maybe.andThen - (\l -> - if Maybe.map .name selectedPlaylist /= Just l.name && l.collection == collection then - justAnItem - { icon = Icons.waves - , label = "Add to \"" ++ l.name ++ "\"" - , msg = - AddTracksToPlaylist - { collection = collection - , playlistName = l.name - , tracks = Tracks.toPlaylistTracks tracks - } - - -- - , active = False - } - - else - Nothing - ) - lastModifiedPlaylistName - in - case maybeCustomPlaylist of - ----------------------------------------- - -- In a custom playlist - ----------------------------------------- - Just playlist -> - Maybe.values - [ maybeAddToLastModifiedPlaylist { collection = True } - , justAnItem - { icon = Icons.waves - , label = - if playlist.collection then - "Remove from collection" - - else - "Remove from playlist" - , msg = RemoveTracksFromPlaylist playlist tracks - - -- - , active = False - } - , justAnItem - { icon = Icons.waves - , label = - if playlist.collection then - "Add to another collection" - - else - "Add to collection" - , msg = AssistWithAddingTracksToCollection tracks - - -- - , active = False - } - , maybeAddToLastModifiedPlaylist { collection = False } - , justAnItem - { icon = Icons.waves - , label = - if playlist.collection then - "Add to playlist" - - else - "Add to another playlist" - , msg = AssistWithAddingTracksToPlaylist tracks - - -- - , active = False - } - ] - - ----------------------------------------- - -- Otherwise - ----------------------------------------- - _ -> - Maybe.values - [ maybeAddToLastModifiedPlaylist { collection = True } - , justAnItem - { icon = Icons.waves - , label = "Add to collection" - , msg = AssistWithAddingTracksToCollection tracks - , active = False - } - , maybeAddToLastModifiedPlaylist { collection = False } - , justAnItem - { icon = Icons.waves - , label = "Add to playlist" - , msg = AssistWithAddingTracksToPlaylist tracks - , active = False - } - ] - - -queueActions : List IdentifiedTrack -> List (ContextMenu.Item Msg) -queueActions identifiedTracks = - [ Item - { icon = Icons.update - , label = "Play next" - , msg = - { inFront = True, tracks = identifiedTracks } - |> Queue.AddTracks - |> QueueMsg - - -- - , active = False - } - , Item - { icon = Icons.update - , label = "Add to queue" - , msg = - { inFront = False, tracks = identifiedTracks } - |> Queue.AddTracks - |> QueueMsg - - -- - , active = False - } - ] - - - --- VIEW MENU - - -viewMenu : Bool -> Maybe Grouping -> Coordinates -> ContextMenu Msg -viewMenu onlyCachedTracks maybeGrouping = - ContextMenu - [ groupByDirectory (maybeGrouping == Just Directory) - , groupByFirstAlphaCharacter (maybeGrouping == Just FirstAlphaCharacter) - , groupByProcessingDate (maybeGrouping == Just AddedOn) - , groupByTrackYear (maybeGrouping == Just TrackYear) - - -- - , Item - { icon = Icons.filter_list - , label = "Cached tracks only" - , active = onlyCachedTracks - , msg = TracksMsg Tracks.ToggleCachedOnly - } - ] - - -groupByDirectory isActive = - Item - { icon = ifThenElse isActive Icons.clear Icons.library_music - , label = "Group by directory" - , active = isActive - - -- - , msg = - if isActive then - TracksMsg Tracks.DisableGrouping - - else - TracksMsg (Tracks.GroupBy Directory) - } - - -groupByFirstAlphaCharacter isActive = - Item - { icon = ifThenElse isActive Icons.clear Icons.library_music - , label = "Group by first letter" - , active = isActive - - -- - , msg = - if isActive then - TracksMsg Tracks.DisableGrouping - - else - TracksMsg (Tracks.GroupBy FirstAlphaCharacter) - } - - -groupByProcessingDate isActive = - Item - { icon = ifThenElse isActive Icons.clear Icons.library_music - , label = "Group by processing date" - , active = isActive - - -- - , msg = - if isActive then - TracksMsg Tracks.DisableGrouping - - else - TracksMsg (Tracks.GroupBy AddedOn) - } - - -groupByTrackYear isActive = - Item - { icon = ifThenElse isActive Icons.clear Icons.library_music - , label = "Group by track year" - , active = isActive - - -- - , msg = - if isActive then - TracksMsg Tracks.DisableGrouping - - else - TracksMsg (Tracks.GroupBy TrackYear) - } diff --git a/src/Core/UI/Tracks/Covers.elm b/src/Core/UI/Tracks/Covers.elm deleted file mode 100644 index 31fec7a83..000000000 --- a/src/Core/UI/Tracks/Covers.elm +++ /dev/null @@ -1,344 +0,0 @@ -module UI.Tracks.Covers exposing (..) - -import Base64 -import Conditional exposing (ifThenElse) -import Dict -import Maybe.Extra as Maybe -import Tracks exposing (..) - - - --- ๐Ÿ”ฑ - - -generate : - SortBy - -> Tracks.Collection - -> CoverCollection -generate sortBy tracks = - let - groupFn = - coverGroup sortBy - - makeCoverFn = - makeCover sortBy - in - tracks.arranged - |> List.foldr - (\identifiedTrack { covers, gathering } -> - let - group = - groupFn identifiedTrack - - ( identifiers, track ) = - identifiedTrack - - { artist, album } = - track.tags - in - if group /= gathering.previousGroup then - -- New group, make cover for previous group - let - collection = - makeCoverFn gathering covers - in - { gathering = - { acc = [ identifiedTrack ] - , accIds = [ track.id ] - , previousGroup = group - , previousTrack = track - - -- - , currentAlbumSequence = Just ( identifiedTrack, 1 ) - , largestAlbumSequence = Nothing - - -- - , currentAlbumFavsSequence = Just ( identifiedTrack, ifThenElse identifiers.isFavourite 1 0 ) - , largestAlbumFavsSequence = Nothing - - -- - , currentArtistSequence = Just ( identifiedTrack, 1 ) - , largestArtistSequence = Nothing - } - , covers = - collection - } - - else - -- Same group - { gathering = - { acc = identifiedTrack :: gathering.acc - , accIds = track.id :: gathering.accIds - , previousGroup = group - , previousTrack = track - - -- Album sequence - ----------------- - , currentAlbumSequence = - if album /= gathering.previousTrack.tags.album then - Just ( identifiedTrack, 1 ) - - else - increaseSequence gathering.currentAlbumSequence - - -- - , largestAlbumSequence = - if album /= gathering.previousTrack.tags.album then - resolveLargestSequence - gathering.currentAlbumSequence - gathering.largestAlbumSequence - - else - gathering.largestAlbumSequence - - -- Album favourites sequence - ---------------------------- - , currentAlbumFavsSequence = - if album /= gathering.previousTrack.tags.album then - Just ( identifiedTrack, ifThenElse identifiers.isFavourite 1 0 ) - - else if identifiers.isFavourite then - increaseSequence gathering.currentAlbumFavsSequence - - else - gathering.currentAlbumFavsSequence - - -- - , largestAlbumFavsSequence = - if album /= gathering.previousTrack.tags.album then - resolveLargestSequence - gathering.currentAlbumFavsSequence - gathering.largestAlbumFavsSequence - - else - gathering.largestAlbumFavsSequence - - -- Artist sequence - ------------------ - , currentArtistSequence = - if artist /= gathering.previousTrack.tags.artist then - Just ( identifiedTrack, 1 ) - - else - increaseSequence gathering.currentArtistSequence - - -- - , largestArtistSequence = - if artist /= gathering.previousTrack.tags.artist then - resolveLargestSequence - gathering.currentArtistSequence - gathering.largestArtistSequence - - else - gathering.largestArtistSequence - } - , covers = - covers - } - ) - { covers = - [] - , gathering = - { acc = [] - , accIds = [] - , previousGroup = "" - , previousTrack = emptyTrack - - -- - , currentAlbumSequence = Nothing - , largestAlbumSequence = Nothing - , currentAlbumFavsSequence = Nothing - , largestAlbumFavsSequence = Nothing - , currentArtistSequence = Nothing - , largestArtistSequence = Nothing - } - } - |> (\{ covers, gathering } -> - makeCoverFn gathering covers - ) - |> (\collection -> - { arranged = collection, harvested = [] } - ) - - -harvest : - Maybe Cover - -> SortBy - -> Tracks.Collection - -> CoverCollection - -> ( CoverCollection, Maybe Cover ) -harvest previouslySelectedCover sortBy tracks covers = - let - groupFn = - coverGroup sortBy - - ( groups, tracksPerGroup ) = - List.foldr - (\identifiedTrack ( acc, dict ) -> - let - group = - groupFn identifiedTrack - in - ( if Dict.member group dict == False then - group :: acc - - else - acc - -- - , Dict.update group - (Maybe.unwrap [ identifiedTrack ] ((::) identifiedTrack) >> Just) - dict - ) - ) - ( [], Dict.empty ) - tracks.harvested - in - covers.arranged - |> List.foldr - (\cover ( acc, sel ) -> - if List.member cover.group groups then - let - groupTracks = - Maybe.withDefault [] (Dict.get cover.group tracksPerGroup) - - trackIds = - List.map (Tuple.second >> .id) groupTracks - - harvestedCover = - { cover | tracks = groupTracks, trackIds = trackIds } - in - case ( previouslySelectedCover, sel ) of - ( Just pre, Nothing ) -> - ( harvestedCover :: acc - , if pre.key == harvestedCover.key then - Just harvestedCover - - else - Nothing - ) - - ( Just _, Just s ) -> - ( harvestedCover :: acc - , Just s - ) - - ( Nothing, _ ) -> - ( harvestedCover :: acc - , Nothing - ) - - else - ( acc - , sel - ) - ) - ( [] - , Nothing - ) - |> Tuple.mapFirst - (\h -> { covers | harvested = h }) - - - --- โš—๏ธ - - -makeCover sortBy_ gathering collection = - let - closedGathering = - { gathering - | largestAlbumSequence = - resolveLargestSequence - gathering.currentAlbumSequence - gathering.largestAlbumSequence - - -- - , largestAlbumFavsSequence = - resolveLargestSequence - gathering.currentAlbumFavsSequence - gathering.largestAlbumFavsSequence - - -- - , largestArtistSequence = - resolveLargestSequence - gathering.currentArtistSequence - gathering.largestArtistSequence - } - in - case closedGathering.acc of - [] -> - collection - - fallback :: _ -> - makeCoverWithFallback sortBy_ closedGathering fallback :: collection - - -makeCoverWithFallback _ gathering fallback = - let - amountOfTracks = - List.length gathering.accIds - - group = - gathering.previousGroup - - identifiedTrack : IdentifiedTrack - identifiedTrack = - gathering.largestAlbumFavsSequence - |> Maybe.orElse gathering.largestAlbumSequence - |> Maybe.map Tuple.first - |> Maybe.withDefault fallback - - ( _, track ) = - identifiedTrack - - ( largestAlbumSequence, largestArtistSequence ) = - ( Maybe.unwrap 0 Tuple.second gathering.largestAlbumSequence - , Maybe.unwrap 0 Tuple.second gathering.largestArtistSequence - ) - - ( sameAlbum, sameArtist ) = - ( largestAlbumSequence == amountOfTracks - , largestArtistSequence == amountOfTracks - ) - - isVariousArtists = - False - || (amountOfTracks > 4 && largestArtistSequence < 3) - || (Maybe.map String.toLower track.tags.artist == Just "va") - in - { key = Base64.encode (coverKey isVariousArtists track) - , identifiedTrackCover = identifiedTrack - - -- - , group = group - , sameAlbum = sameAlbum - , sameArtist = sameArtist - - -- - , trackIds = gathering.accIds - , tracks = gathering.acc - , variousArtists = isVariousArtists - } - - - --- โš—๏ธ โ–‘โ–‘ SEQUENCES - - -increaseSequence = - Maybe.map (Tuple.mapSecond ((+) 1)) - - -resolveLargestSequence curr state = - case ( curr, state ) of - ( Just ( _, c ), Just ( _, s ) ) -> - ifThenElse (c > s) curr state - - ( Just _, Nothing ) -> - curr - - ( Nothing, Just _ ) -> - state - - ( Nothing, Nothing ) -> - Nothing diff --git a/src/Core/UI/Tracks/State.elm b/src/Core/UI/Tracks/State.elm deleted file mode 100644 index 4b9a8863a..000000000 --- a/src/Core/UI/Tracks/State.elm +++ /dev/null @@ -1,1122 +0,0 @@ -module UI.Tracks.State exposing (..) - -import Alien -import Base64 -import Common exposing (..) -import ContextMenu -import Coordinates exposing (Coordinates) -import Debouncer.Basic as Debouncer -import Dict -import Html.Events.Extra.Mouse as Mouse -import InfiniteList -import Json.Decode as Json -import Json.Encode -import Keyboard -import List.Ext as List -import List.Extra as List -import Maybe.Extra as Maybe -import Notifications -import Playlists exposing (Playlist) -import Queue -import Return exposing (andThen, return) -import Return.Ext as Return -import Sources -import Task.Extra as Task -import Tracks exposing (..) -import Tracks.Collection as Collection -import Tracks.Encoding as Encoding -import Tracks.Favourites as Favourites -import UI.Common.State as Common exposing (showNotification) -import UI.DnD as DnD -import UI.Page -import UI.Ports as Ports -import UI.Queue.State as Queue -import UI.Theme -import UI.Tracks.ContextMenu as Tracks -import UI.Tracks.Covers as Covers -import UI.Tracks.Types as Tracks exposing (..) -import UI.Types exposing (Manager, Model, Msg(..)) -import UI.User.State.Export as User -import User.Layer exposing (HypaethralData) - - - --- ๐Ÿ“ฃ - - -update : Tracks.Msg -> Manager -update msg = - case msg of - Download a b -> - download a b - - DownloadFinished -> - downloadFinished - - Harvest -> - harvest - - MarkAsSelected a b -> - markAsSelected a b - - ScrollToNowPlaying -> - scrollToNowPlaying - - SyncTags a -> - syncTags a - - ToggleCachedOnly -> - toggleCachedOnly - - ToggleCoverSelectionReducesPool -> - toggleCoverSelectionReducesPool - - ToggleFavouritesOnly -> - toggleFavouritesOnly - - ToggleHideDuplicates -> - toggleHideDuplicates - - ----------------------------------------- - -- Cache - ----------------------------------------- - ClearCache -> - clearCache - - RemoveFromCache a -> - removeFromCache a - - StoreInCache a -> - storeInCache a - - StoredInCache a b -> - storedInCache a b - - --------- - -- Covers - --------- - GotCachedCover a -> - gotCachedCover a - - InsertCoverCache a -> - insertCoverCache a - - ----------------------------------------- - -- Collection - ----------------------------------------- - Add a -> - add a - - AddFavourites a -> - addFavourites a - - Reload a -> - reload a - - RemoveByPaths a -> - removeByPaths a - - RemoveBySourceId a -> - removeBySourceId a - - RemoveFavourites a -> - removeFavourites a - - SortBy a -> - sortBy a - - ToggleFavourite a -> - toggleFavourite a - - ----------------------------------------- - -- Groups - ----------------------------------------- - DisableGrouping -> - disableGrouping - - GroupBy a -> - groupBy a - - ----------------------------------------- - -- Menus - ----------------------------------------- - ShowCoverMenu a b -> - showCoverMenu a b - - ShowCoverMenuWithSmallDelay a b -> - showCoverMenuWithDelay a b - - ShowTracksMenu a b c -> - showTracksMenu a b c - - ShowTracksMenuWithSmallDelay a b c -> - showTracksMenuWithDelay a b c - - ShowViewMenu a b -> - showViewMenu a b - - ----------------------------------------- - -- Scenes - ----------------------------------------- - ChangeScene a -> - changeScene a - - DeselectCover -> - deselectCover - - InfiniteListMsg a -> - infiniteListMsg a - - SelectCover a -> - selectCover a - - ----------------------------------------- - -- Search - ----------------------------------------- - ClearSearch -> - clearSearch - - Search -> - search - - SetSearchResults a -> - setSearchResults a - - SetSearchTerm a -> - setSearchTerm a - - - --- ๐Ÿ”ฑ - - -add : Json.Value -> Manager -add encodedTracks model = - reviseCollection - (encodedTracks - |> Json.decodeValue (Json.list Encoding.trackDecoder) - |> Result.withDefault [] - |> Collection.add - ) - model - - -addFavourites : List IdentifiedTrack -> Manager -addFavourites = - manageFavourites AddToFavourites - - -afterInitialLoad : Manager -afterInitialLoad model = - Common.toggleLoadingScreen Off model - - -changeScene : Scene -> Manager -changeScene scene model = - (case scene of - Covers -> - Ports.loadAlbumCovers { list = True, coverView = True } - - List -> - Cmd.none - ) - |> return { model | scene = scene, selectedCover = Nothing } - |> andThen - (if model.coverSelectionReducesPool then - Queue.reset - - else - Return.singleton - ) - |> andThen Common.forceTracksRerender - |> andThen User.saveEnclosedUserData - - -clearCache : Manager -clearCache model = - model.cachedTracks - |> Json.Encode.list Json.Encode.string - |> Alien.broadcast Alien.RemoveTracksFromCache - |> Ports.toBrain - |> return { model | cachedTracks = [] } - |> andThen harvest - |> andThen User.saveEnclosedUserData - |> andThen - ("Tracks cache was cleared" - |> Notifications.casual - |> Common.showNotification - ) - - -clearSearch : Manager -clearSearch model = - { model | searchResults = Nothing, searchTerm = Nothing } - |> reviseCollection Collection.harvest - |> andThen User.saveEnclosedUserData - - -deselectCover : Manager -deselectCover model = - (if model.coverSelectionReducesPool then - Queue.reset - - else - Return.singleton - ) - { model | selectedCover = Nothing } - - -download : { prefixTrackNumber : Bool, zipName : String } -> List Track -> Manager -download { prefixTrackNumber, zipName } tracks model = - let - notification = - Notifications.stickyCasual "Downloading tracks ..." - - downloading = - Just { notificationId = Notifications.id notification } - in - [ ( "prefixTrackNumber", Json.Encode.bool prefixTrackNumber ) - , ( "trackIds" - , tracks - |> List.map .id - |> Json.Encode.list Json.Encode.string - ) - , ( "zipName", Json.Encode.string zipName ) - ] - |> Json.Encode.object - |> Alien.broadcast Alien.DownloadTracks - |> Ports.toBrain - |> return { model | downloading = downloading } - |> andThen (Common.showNotification notification) - - -downloadFinished : Manager -downloadFinished model = - case model.downloading of - Just { notificationId } -> - Common.dismissNotification - { id = notificationId } - { model | downloading = Nothing } - - Nothing -> - Return.singleton model - - -disableGrouping : Manager -disableGrouping model = - { model | grouping = Nothing } - |> reviseCollection Collection.arrange - |> andThen User.saveEnclosedUserData - - -failedToStoreInCache : List String -> Manager -failedToStoreInCache trackIds m = - showNotification - (Notifications.error "Failed to store track in cache") - { m | cachingTracksInProgress = List.without trackIds m.cachingTracksInProgress } - - -finishedStoringInCache : List String -> Manager -finishedStoringInCache trackIds model = - { model - | cachedTracks = model.cachedTracks ++ trackIds - , cachingTracksInProgress = List.without trackIds model.cachingTracksInProgress - } - |> (\m -> - -- When a context menu of a track is open, - -- it should be "rerendered" in case - -- the track is no longer being downloaded. - case m.contextMenu of - Just contextMenu -> - let - isTrackContextMenu = - ContextMenu.anyItem - (.label >> (==) "Downloading ...") - contextMenu - - coordinates = - ContextMenu.coordinates contextMenu - in - if isTrackContextMenu then - showTracksMenu Nothing { alt = False } coordinates m - - else - Return.singleton m - - Nothing -> - Return.singleton m - ) - |> andThen harvest - |> andThen User.saveEnclosedUserData - - -generateCovers : Manager -generateCovers model = - model.tracks - |> Covers.generate model.sortBy - |> (\c -> { model | covers = c }) - |> Return.singleton - - -gotCachedCover : Json.Value -> Manager -gotCachedCover json model = - let - cachedCovers = - Maybe.withDefault Dict.empty model.cachedCovers - - decodedValue = - Json.decodeValue - (Json.map3 - (\i k u -> ( i, k, u )) - (Json.field "imageType" Json.string) - (Json.field "key" Json.string) - (Json.field "url" Json.string) - ) - json - in - decodedValue - |> Result.map (\( _, key, url ) -> Dict.insert key url cachedCovers) - |> Result.map (\dict -> { model | cachedCovers = Just dict }) - |> Result.withDefault model - |> (\m -> - case ( m.nowPlaying, decodedValue ) of - ( Just nowPlaying, Ok val ) -> - let - ( imageType, key, url ) = - val - - ( _, track ) = - nowPlaying.item.identifiedTrack - - hasntLoadedYet = - nowPlaying.coverLoaded == False - - ( keyA, keyB ) = - ( Base64.encode (Tracks.coverKey False track) - , Base64.encode (Tracks.coverKey True track) - ) - - keyMatches = - keyA == key || keyB == key - in - if hasntLoadedYet && keyMatches then - ( m, Ports.setMediaSessionArtwork { blobUrl = url, imageType = imageType } ) - - else - Return.singleton m - - _ -> - Return.singleton m - ) - - -groupBy : Tracks.Grouping -> Manager -groupBy grouping model = - { model | grouping = Just grouping } - |> reviseCollection Collection.arrange - |> andThen User.saveEnclosedUserData - - -harvest : Manager -harvest = - reviseCollection Collection.harvest - - -harvestCovers : Manager -harvestCovers model = - model.covers - |> Covers.harvest model.selectedCover model.sortBy model.tracks - |> (\( c, s ) -> { model | covers = c, selectedCover = s }) - |> Return.communicate (Ports.loadAlbumCovers { list = True, coverView = True }) - - -infiniteListMsg : InfiniteList.Model -> Manager -infiniteListMsg infiniteList model = - return - { model | infiniteList = infiniteList } - (Ports.loadAlbumCovers { list = True, coverView = False }) - - -insertCoverCache : Json.Value -> Manager -insertCoverCache json model = - json - |> Json.decodeValue (Json.dict Json.string) - |> Result.map (\dict -> { model | cachedCovers = Just dict }) - |> Result.withDefault model - |> Return.singleton - - -manageFavourites : FavouritesManagementAction -> List IdentifiedTrack -> Manager -manageFavourites action tracks model = - let - newFavourites = - (case action of - AddToFavourites -> - Favourites.completeFavouritesList - - RemoveFromFavourites -> - Favourites.removeFromFavouritesList - ) - tracks - model.favourites - - effect collection = - collection - |> Collection.map - (case action of - AddToFavourites -> - Favourites.completeTracksList tracks - - RemoveFromFavourites -> - Favourites.removeFromTracksList tracks - ) - |> (if model.favouritesOnly then - Collection.harvest - - else - identity - ) - - selectedCover = - Maybe.map - (\cover -> - cover.tracks - |> (case action of - AddToFavourites -> - Favourites.completeTracksList tracks - - RemoveFromFavourites -> - Favourites.removeFromTracksList tracks - ) - |> (\a -> { cover | tracks = a }) - ) - model.selectedCover - in - { model | favourites = newFavourites, selectedCover = selectedCover } - |> reviseCollection effect - |> andThen User.saveFavourites - |> (if model.scene == Covers then - andThen generateCovers >> andThen harvestCovers - - else - identity - ) - - -markAsSelected : Int -> { shiftKey : Bool } -> Manager -markAsSelected indexInList { shiftKey } model = - let - selection = - if shiftKey then - model.selectedTrackIndexes - |> List.head - |> Maybe.map - (\n -> - if n > indexInList then - List.range indexInList n - - else - List.range n indexInList - ) - |> Maybe.withDefault [ indexInList ] - - else - [ indexInList ] - in - Return.singleton { model | selectedTrackIndexes = selection } - - -reload : Json.Value -> Manager -reload encodedTracks model = - reviseCollection - (encodedTracks - |> Json.decodeValue (Json.list Encoding.trackDecoder) - |> Result.withDefault model.tracks.untouched - |> Collection.replace - ) - model - - -removeByPaths : Json.Value -> Manager -removeByPaths encodedParams model = - let - decoder = - Json.map2 - Tuple.pair - (Json.field "filePaths" <| Json.list Json.string) - (Json.field "sourceId" Json.string) - - ( paths, sourceId ) = - encodedParams - |> Json.decodeValue decoder - |> Result.withDefault ( [], missingId ) - - { kept, removed } = - Tracks.removeByPaths - { sourceId = sourceId, paths = paths } - model.tracks.untouched - - newCollection = - { emptyCollection | untouched = kept } - in - { model | tracks = newCollection } - |> reviseCollection Collection.identify - |> andThen (removeFromCache removed) - - -removeBySourceId : String -> Manager -removeBySourceId sourceId model = - let - { kept, removed } = - Tracks.removeBySourceId sourceId model.tracks.untouched - - newCollection = - { emptyCollection | untouched = kept } - in - sourceId - |> Json.Encode.string - |> Alien.broadcast Alien.RemoveTracksBySourceId - |> Ports.toBrain - |> return { model | tracks = newCollection } - |> andThen (reviseCollection Collection.identify) - |> andThen (removeFromCache removed) - - -removeFavourites : List IdentifiedTrack -> Manager -removeFavourites = - manageFavourites RemoveFromFavourites - - -removeFromCache : List Track -> Manager -removeFromCache tracks model = - let - trackIds = - List.map .id tracks - in - trackIds - |> Json.Encode.list Json.Encode.string - |> Alien.broadcast Alien.RemoveTracksFromCache - |> Ports.toBrain - |> return { model | cachedTracks = List.without trackIds model.cachedTracks } - |> andThen harvest - |> andThen User.saveEnclosedUserData - - -reviseCollection : (Parcel -> Parcel) -> Manager -reviseCollection collector model = - resolveParcel - (model - |> makeParcel - |> collector - ) - model - - -search : Manager -search model = - case ( model.searchTerm, model.searchResults ) of - ( Just term, _ ) -> - term - |> String.trim - |> Json.Encode.string - |> Ports.giveBrain Alien.SearchTracks - |> return model - - ( Nothing, Just _ ) -> - reviseCollection Collection.harvest { model | searchResults = Nothing } - - ( Nothing, Nothing ) -> - Return.singleton model - - -selectCover : Cover -> Manager -selectCover cover model = - { model | selectedCover = Just cover } - |> (if model.coverSelectionReducesPool then - Queue.reset - - else - Return.singleton - ) - |> Return.command (Ports.loadAlbumCovers { list = False, coverView = True }) - - -setSearchResults : Json.Value -> Manager -setSearchResults json model = - case model.searchTerm of - Just _ -> - json - |> Json.decodeValue (Json.list Json.string) - |> Result.withDefault [] - |> (\results -> { model | searchResults = Just results }) - |> reviseCollection Collection.harvest - |> andThen afterInitialLoad - - Nothing -> - Return.singleton model - - -setSearchTerm : String -> Manager -setSearchTerm term model = - (case String.trim term of - "" -> - { model | searchTerm = Nothing } - - _ -> - { model | searchTerm = Just term } - ) - |> Return.communicate - (Search - |> TracksMsg - |> Debouncer.provideInput - |> SearchDebounce - |> Task.do - ) - |> Return.andThen User.saveEnclosedUserData - - -showCoverMenu : Cover -> Coordinates -> Manager -showCoverMenu cover coordinates model = - let - menuDependencies = - { cached = model.cachedTracks - , cachingInProgress = model.cachingTracksInProgress - , currentTime = model.currentTime - , selectedPlaylist = model.selectedPlaylist - , lastModifiedPlaylistName = model.lastModifiedPlaylist - , showAlternativeMenu = False - , sources = model.sources - } - in - coordinates - |> Tracks.trackMenu menuDependencies cover.tracks - |> Common.showContextMenuWithModel model - - -showCoverMenuWithDelay : Cover -> Coordinates -> Manager -showCoverMenuWithDelay a b model = - Tracks.ShowCoverMenu a b - |> TracksMsg - |> Task.doDelayed 250 - |> return model - - -showTracksMenu : Maybe Int -> { alt : Bool } -> Coordinates -> Manager -showTracksMenu maybeTrackIndex { alt } coordinates model = - let - selection = - case maybeTrackIndex of - Just trackIndex -> - if List.isEmpty model.selectedTrackIndexes then - [ trackIndex ] - - else if List.member trackIndex model.selectedTrackIndexes == False then - [ trackIndex ] - - else - model.selectedTrackIndexes - - Nothing -> - model.selectedTrackIndexes - - menuDependencies = - { cached = model.cachedTracks - , cachingInProgress = model.cachingTracksInProgress - , currentTime = model.currentTime - , selectedPlaylist = model.selectedPlaylist - , lastModifiedPlaylistName = model.lastModifiedPlaylist - , showAlternativeMenu = alt - , sources = model.sources - } - - tracks = - List.pickIndexes selection model.tracks.harvested - in - coordinates - |> Tracks.trackMenu menuDependencies tracks - |> Common.showContextMenuWithModel - { model - | dnd = DnD.initialModel - , selectedTrackIndexes = selection - } - - -showTracksMenuWithDelay : Maybe Int -> { alt : Bool } -> Coordinates -> Manager -showTracksMenuWithDelay a b c model = - Tracks.ShowTracksMenu a b c - |> TracksMsg - |> Task.doDelayed 250 - |> return model - - -showViewMenu : Maybe Grouping -> Mouse.Event -> Manager -showViewMenu maybeGrouping mouseEvent model = - mouseEvent.clientPos - |> Coordinates.fromTuple - |> Tracks.viewMenu model.cachedTracksOnly maybeGrouping - |> Common.showContextMenuWithModel model - - -scrollToNowPlaying : Manager -scrollToNowPlaying model = - model.nowPlaying - |> Maybe.map - (.item >> .identifiedTrack >> Tuple.second >> .id) - |> Maybe.andThen - (\id -> - List.find - (Tuple.second >> .id >> (==) id) - model.tracks.harvested - ) - |> Maybe.map - (\( identifiers, track ) -> - case model.scene of - Covers -> - if List.member Keyboard.Shift model.pressedKeys then - return - { model | selectedCover = Nothing } - (UI.Theme.scrollToNowPlaying Covers ( identifiers, track ) model) - - else - model.covers.harvested - |> List.find (\cover -> List.member track.id cover.trackIds) - |> Maybe.unwrap model (\cover -> { model | selectedCover = Just cover }) - |> Return.communicate (Ports.loadAlbumCovers { list = True, coverView = True }) - - List -> - return - { model | selectedCover = Nothing } - (UI.Theme.scrollToNowPlaying List ( identifiers, track ) model) - ) - |> Maybe.map - (UI.Page.Index - |> Common.changeUrlUsingPage - |> andThen - ) - |> Maybe.withDefault - (Return.singleton model) - - -sortBy : SortBy -> Manager -sortBy property model = - let - sortDir = - if model.sortBy /= property then - Asc - - else if model.sortDirection == Asc then - Desc - - else - Asc - in - { model | sortBy = property, sortDirection = sortDir } - |> reviseCollection Collection.arrange - |> andThen User.saveEnclosedUserData - - -storeInCache : List Track -> Manager -storeInCache tracks model = - let - trackIds = - List.map .id tracks - - notification = - case tracks of - [ t ] -> - ("__" ++ t.tags.title ++ "__ will be stored in the cache") - |> Notifications.casual - - list -> - list - |> List.length - |> String.fromInt - |> (\s -> "__" ++ s ++ " tracks__ will be stored in the cache") - |> Notifications.casual - in - tracks - |> Json.Encode.list - (\track -> - Json.Encode.object - [ ( "trackId" - , Json.Encode.string track.id - ) - , ( "url" - , track - |> Queue.makeTrackUrl - model.currentTime - model.sources - |> Json.Encode.string - ) - ] - ) - |> Alien.broadcast Alien.StoreTracksInCache - |> Ports.toBrain - |> return { model | cachingTracksInProgress = model.cachingTracksInProgress ++ trackIds } - |> andThen (Common.showNotification notification) - - -storedInCache : Json.Value -> Maybe String -> Manager -storedInCache json maybeError = - case - ( maybeError - , Json.decodeValue (Json.list Json.string) json - ) - of - ( Nothing, Ok list ) -> - finishedStoringInCache list - - ( Nothing, Err err ) -> - err - |> Json.errorToString - |> Notifications.error - |> Common.showNotification - - ( Just _, Ok trackIds ) -> - failedToStoreInCache trackIds - - ( Just err, Err _ ) -> - err - |> Notifications.error - |> Common.showNotification - - -syncTags : List Track -> Manager -syncTags tracks = - tracks - |> Json.Encode.list - (\track -> - Json.Encode.object - [ ( "path", Json.Encode.string track.path ) - , ( "sourceId", Json.Encode.string track.sourceId ) - , ( "trackId", Json.Encode.string track.id ) - ] - ) - |> Alien.broadcast Alien.SyncTrackTags - |> Ports.toBrain - |> Return.communicate - - -toggleCachedOnly : Manager -toggleCachedOnly model = - { model | cachedTracksOnly = not model.cachedTracksOnly } - |> reviseCollection Collection.harvest - |> andThen User.saveEnclosedUserData - |> andThen Common.forceTracksRerender - - -toggleCoverSelectionReducesPool : Manager -toggleCoverSelectionReducesPool model = - { model | coverSelectionReducesPool = not model.coverSelectionReducesPool } - |> Queue.reset - |> andThen User.saveSettings - - -toggleFavourite : Int -> Manager -toggleFavourite index model = - case List.getAt index model.tracks.harvested of - Just ( i, t ) -> - let - newFavourites = - Favourites.toggleInFavouritesList ( i, t ) model.favourites - - effect collection = - collection - |> Collection.map (Favourites.toggleInTracksList t) - |> (if model.favouritesOnly then - Collection.harvest - - else - identity - ) - - selectedCover = - Maybe.map - (\cover -> - cover.tracks - |> Favourites.toggleInTracksList t - |> (\a -> { cover | tracks = a }) - ) - model.selectedCover - in - { model | favourites = newFavourites, selectedCover = selectedCover } - |> reviseCollection effect - |> andThen User.saveFavourites - |> (if model.scene == Covers then - andThen generateCovers >> andThen harvestCovers - - else - identity - ) - - Nothing -> - Return.singleton model - - -toggleFavouritesOnly : Manager -toggleFavouritesOnly model = - { model | favouritesOnly = not model.favouritesOnly } - |> reviseCollection Collection.harvest - |> andThen User.saveEnclosedUserData - - -toggleHideDuplicates : Manager -toggleHideDuplicates model = - { model | hideDuplicates = not model.hideDuplicates } - |> reviseCollection Collection.arrange - |> andThen User.saveSettings - - - --- ๐Ÿ“ฃ โ–‘โ–‘ PARCEL - - -makeParcel : Model -> Parcel -makeParcel model = - ( { cached = model.cachedTracks - , cachedOnly = model.cachedTracksOnly - , enabledSourceIds = Sources.enabledSourceIds model.sources - , favourites = model.favourites - , favouritesOnly = model.favouritesOnly - , grouping = model.grouping - , hideDuplicates = model.hideDuplicates - , searchResults = model.searchResults - , selectedPlaylist = model.selectedPlaylist - , sortBy = model.sortBy - , sortDirection = model.sortDirection - } - , model.tracks - ) - - -resolveParcel : Parcel -> Manager -resolveParcel ( deps, newCollection ) model = - let - scrollObj = - Json.Encode.object - [ ( "scrollTop", Json.Encode.int 0 ) ] - - scrollEvent = - Json.Encode.object - [ ( "target", scrollObj ) ] - - newScrollContext = - scrollContext model - - collectionChanged = - Collection.tracksChanged - model.tracks.untouched - newCollection.untouched - - arrangementChanged = - if collectionChanged then - True - - else - Collection.identifiedTracksChanged - model.tracks.arranged - newCollection.arranged - - harvestChanged = - if arrangementChanged then - True - - else - Collection.identifiedTracksChanged - model.tracks.harvested - newCollection.harvested - - scrollContextChanged = - newScrollContext /= model.tracks.scrollContext - - modelWithNewCollection = - (if model.scene == List && scrollContextChanged then - \m -> { m | infiniteList = InfiniteList.updateScroll scrollEvent m.infiniteList } - - else - identity - ) - { model - | tracks = - { newCollection | scrollContext = newScrollContext } - , selectedTrackIndexes = - if collectionChanged || harvestChanged then - [] - - else - model.selectedTrackIndexes - } - in - (if collectionChanged then - whenCollectionChanges - - else if arrangementChanged then - whenArrangementChanges - - else if harvestChanged then - whenHarvestChanges - - else - identity - ) - ( modelWithNewCollection - ----------------------------------------- - -- Command - ----------------------------------------- - , if scrollContextChanged then - UI.Theme.scrollTracksToTop model.scene - - else - Cmd.none - ) - - -whenHarvestChanges = - andThen harvestCovers >> andThen Queue.reset - - -whenArrangementChanges = - andThen generateCovers >> whenHarvestChanges - - -whenCollectionChanges = - andThen search >> andThen Common.generateDirectoryPlaylists >> whenArrangementChanges - - -scrollContext : Model -> String -scrollContext model = - String.concat - [ Maybe.withDefault "" <| model.searchTerm - , Maybe.withDefault "" <| Maybe.map .name model.selectedPlaylist - ] - - - --- ๐Ÿ“ฃ โ–‘โ–‘ USER DATA - - -importHypaethral : HypaethralData -> Maybe Playlist -> Manager -importHypaethral data selectedPlaylist model = - { model - | favourites = data.favourites - , selectedPlaylist = selectedPlaylist - , tracks = { emptyCollection | untouched = data.tracks } - } - |> reviseCollection Collection.identify - |> andThen search - |> (case model.searchTerm of - Just _ -> - identity - - Nothing -> - andThen afterInitialLoad - ) - - - --- ใŠ™๏ธ - - -type FavouritesManagementAction - = AddToFavourites - | RemoveFromFavourites diff --git a/src/Core/UI/Tracks/Types.elm b/src/Core/UI/Tracks/Types.elm deleted file mode 100644 index eecaf6e86..000000000 --- a/src/Core/UI/Tracks/Types.elm +++ /dev/null @@ -1,74 +0,0 @@ -module UI.Tracks.Types exposing (..) - -import Coordinates exposing (Coordinates) -import Html.Events.Extra.Mouse as Mouse -import InfiniteList -import Json.Decode as Json -import Tracks exposing (..) - - - --- ๐Ÿ“ฃ - - -type Msg - = Download { prefixTrackNumber : Bool, zipName : String } (List Track) - | DownloadFinished - | Harvest - | MarkAsSelected Int { shiftKey : Bool } - | ScrollToNowPlaying - | SyncTags (List Track) - | ToggleCachedOnly - | ToggleFavouritesOnly - | ToggleHideDuplicates - ----------------------------------------- - -- Cache - ----------------------------------------- - | ClearCache - | RemoveFromCache (List Track) - | StoreInCache (List Track) - | StoredInCache Json.Value (Maybe String) - --------- - -- Covers - --------- - | GotCachedCover Json.Value - | InsertCoverCache Json.Value - ----------------------------------------- - -- Collection - ----------------------------------------- - | Add Json.Value - | AddFavourites (List IdentifiedTrack) - | Reload Json.Value - | RemoveByPaths Json.Value - | RemoveBySourceId String - | RemoveFavourites (List IdentifiedTrack) - | SortBy SortBy - | ToggleFavourite Int - | ToggleCoverSelectionReducesPool - ----------------------------------------- - -- Groups - ----------------------------------------- - | DisableGrouping - | GroupBy Grouping - ----------------------------------------- - -- Menus - ----------------------------------------- - | ShowCoverMenu Cover Coordinates - | ShowCoverMenuWithSmallDelay Cover Coordinates - | ShowTracksMenu (Maybe Int) { alt : Bool } Coordinates - | ShowTracksMenuWithSmallDelay (Maybe Int) { alt : Bool } Coordinates - | ShowViewMenu (Maybe Grouping) Mouse.Event - ----------------------------------------- - -- Scenes - ----------------------------------------- - | ChangeScene Scene - | DeselectCover - | InfiniteListMsg InfiniteList.Model - | SelectCover Cover - ----------------------------------------- - -- Search - ----------------------------------------- - | ClearSearch - | Search - | SetSearchResults Json.Value - | SetSearchTerm String diff --git a/src/Core/UI/Types.elm b/src/Core/UI/Types.elm deleted file mode 100644 index a48bfe630..000000000 --- a/src/Core/UI/Types.elm +++ /dev/null @@ -1,332 +0,0 @@ -module UI.Types exposing (..) - -import Alfred exposing (Alfred) -import Alien -import Browser -import Browser.Navigation as Nav -import Color exposing (Color) -import Common exposing (ServiceWorkerStatus, Switch) -import ContextMenu exposing (ContextMenu) -import Coordinates exposing (Viewport) -import Debouncer.Basic as Debouncer exposing (Debouncer) -import Dict exposing (Dict) -import Equalizer -import File exposing (File) -import Html.Events.Extra.Mouse as Mouse -import Http -import InfiniteList -import Json.Decode -import Keyboard -import LastFm -import Management -import Notifications exposing (Notification) -import Playlists exposing (Playlist, PlaylistTrackWithoutMetadata) -import Queue -import Random -import Sources exposing (Source) -import Theme -import Time -import Tracks exposing (..) -import UI.Audio.Types exposing (DurationChangeEvent, ErrorAudioEvent, GenericAudioEvent, NowPlaying, PlaybackStateEvent, TimeUpdatedEvent) -import UI.DnD as DnD -import UI.Page exposing (Page) -import UI.Queue.Types as Queue -import UI.Sources.Types as Sources -import UI.Syncing.Types as Syncing -import UI.Tracks.Types as Tracks -import Url exposing (Url) - - - --- โ›ฉ - - -type alias Flags = - { buildTimestamp : Int - , darkMode : Bool - , initialTime : Int - , isInstallingServiceWorker : Bool -- ie. Installing SW for the first time - , isOnline : Bool - , isTauri : Bool - , version : String - , viewport : Viewport - } - - - --- ๐ŸŒณ - - -type alias Model = - { buildTimestamp : Int - , confirmation : Maybe String - , currentTime : Time.Posix - , currentTimeZone : Time.Zone - , darkMode : Bool - , downloading : Maybe { notificationId : Int } - , dnd : DnD.Model Int - , focusedOnInput : Bool - , isDragging : Bool - , isLoading : Bool - , isOnline : Bool - , isTauri : Bool - , isTouchDevice : Bool - , lastFm : LastFm.Model - , navKey : Nav.Key - , page : Page - , pressedKeys : List Keyboard.Key - , processAutomatically : Bool - , serviceWorkerStatus : ServiceWorkerStatus - , theme : Maybe Theme.Id - , uuidSeed : Random.Seed - , url : Url - , version : String - , viewport : Viewport - - ----------------------------------------- - -- Audio - ----------------------------------------- - , audioElements : List Queue.EngineItem - , nowPlaying : Maybe NowPlaying - , progress : Dict String Float - , rememberProgress : Bool - - ----------------------------------------- - -- Backdrop - ----------------------------------------- - , chosenBackdrop : Maybe String - , extractedBackdropColor : Maybe Color - , fadeInBackdrop : Bool - , loadedBackdrops : List String - - ----------------------------------------- - -- Debouncing - ----------------------------------------- - , preloadDebouncer : Debouncer Msg Msg - , progressDebouncer : Debouncer Msg Msg - , resizeDebouncer : Debouncer Msg Msg - , searchDebouncer : Debouncer Msg Msg - - ----------------------------------------- - -- Equalizer - ----------------------------------------- - , eqSettings : Equalizer.Settings - , showVolumeSlider : Bool - - ----------------------------------------- - -- Instances - ----------------------------------------- - , alfred : Maybe (Alfred Msg) - , contextMenu : Maybe (ContextMenu Msg) - , notifications : List (Notification Msg) - - ----------------------------------------- - -- Playlists - ----------------------------------------- - , editPlaylistContext : Maybe { oldName : String, newName : String } - , lastModifiedPlaylist : Maybe { collection : Bool, name : String } - , newPlaylistContext : Maybe String - , playlists : List Playlist - , playlistToActivate : Maybe String - , selectedPlaylist : Maybe Playlist - - ----------------------------------------- - -- Queue - ----------------------------------------- - , dontPlay : List Queue.Item - , playedPreviously : List Queue.Item - , playingNext : List Queue.Item - , selectedQueueItem : Maybe Queue.Item - - -- Settings - ----------- - , repeat : Bool - , shuffle : Bool - - ----------------------------------------- - -- Sources - ----------------------------------------- - , processingContext : List ( String, Float ) - , processingError : Maybe { error : String, sourceId : String } - , processingNotificationId : Maybe Int - , sourceForm : Sources.Form - , sources : List Source - - ----------------------------------------- - -- Tracks - ----------------------------------------- - , cachedCovers : Maybe (Dict String String) - , cachedTracks : List String - , cachedTracksOnly : Bool - , cachingTracksInProgress : List String - , covers : { arranged : List Tracks.Cover, harvested : List Tracks.Cover } - , coverSelectionReducesPool : Bool - , favourites : List Favourite - , favouritesOnly : Bool - , grouping : Maybe Grouping - , hideDuplicates : Bool - , scene : Scene - , searchResults : Maybe (List String) - , searchTerm : Maybe String - , selectedCover : Maybe Cover - , selectedTrackIndexes : List Int - , sortBy : SortBy - , sortDirection : SortDirection - , tracks : Tracks.Collection - - -- List scene - ------------- - , infiniteList : InfiniteList.Model - - ----------------------------------------- - -- ๐Ÿฆ‰ Nested - ----------------------------------------- - , syncing : Syncing.State - } - - - --- ๐Ÿ“ฃ - - -type Msg - = Bypass - ----------------------------------------- - -- Alfred - ----------------------------------------- - | AssignAlfred (Alfred Msg) - | GotAlfredInput String - | SelectAlfredItem Int - ----------------------------------------- - -- Audio - ----------------------------------------- - | AudioDurationChange DurationChangeEvent - | AudioError ErrorAudioEvent - | AudioEnded GenericAudioEvent - | AudioHasLoaded GenericAudioEvent - | AudioIsLoading GenericAudioEvent - | AudioPlaybackStateChanged PlaybackStateEvent - | AudioPreloadDebounce (Debouncer.Msg Msg) - | AudioTimeUpdated TimeUpdatedEvent - | NoteProgress { trackId : String, progress : Float } - | NoteProgressDebounce (Debouncer.Msg Msg) - | Pause - | Play - | Seek { trackId : String, progress : Float } - | Stop - | TogglePlay - | ToggleRememberProgress - ----------------------------------------- - -- Backdrop - ----------------------------------------- - | ExtractedBackdropColor { r : Int, g : Int, b : Int } - | ChooseBackdrop String - | LoadBackdrop String - ----------------------------------------- - -- Equalizer - ----------------------------------------- - | AdjustVolume Float - | ToggleVolumeSlider Switch - ----------------------------------------- - -- Interface - ----------------------------------------- - | AssistWithChangingTheme - | Blur - | ChangeTheme Theme.Id - | ContextMenuConfirmation String Msg - | CopyToClipboard String - | DismissNotification { id : Int } - | DnD (DnD.Msg Int) - | FocusedOnInput - | HideOverlay - | LostWindowFocus - | MsgViaContextMenu Msg - | PreferredColorSchemaChanged { dark : Bool } - | RemoveNotification { id : Int } - | RemoveQueueSelection - | RemoveTrackSelection - | ResizeDebounce (Debouncer.Msg Msg) - | ResizedWindow ( Int, Int ) - | SearchDebounce (Debouncer.Msg Msg) - | ShowNotification (Notification Msg) - | SetIsTouchDevice Bool - | StoppedDragging - | ToggleLoadingScreen Switch - ----------------------------------------- - -- Playlists - ----------------------------------------- - | ActivatePlaylist Playlist - | AddTracksToPlaylist { collection : Bool, playlistName : String, tracks : List PlaylistTrackWithoutMetadata } - | AssistWithAddingTracksToCollection (List IdentifiedTrack) - | AssistWithAddingTracksToPlaylist (List IdentifiedTrack) - | AssistWithSelectingPlaylist - | ConvertCollectionToPlaylist { name : String } - | ConvertPlaylistToCollection { name : String } - | CreateCollection - | CreatePlaylist - | DeactivatePlaylist - | DeletePlaylist { playlistName : String } - | DeselectPlaylist - | ModifyPlaylist - | MoveTrackInSelectedPlaylist { to : Int } - | RemoveTracksFromPlaylist Playlist (List IdentifiedTrack) - | SelectPlaylist Playlist - | SetPlaylistCreationContext String - | SetPlaylistModificationContext String String - | ShowPlaylistListMenu Playlist Mouse.Event - | TogglePlaylistVisibility Playlist - ----------------------------------------- - -- Routing - ----------------------------------------- - | ChangeUrlUsingPage Page - | LinkClicked Browser.UrlRequest - | OpenUrlOnNewPage String - | PageChanged Page - | UrlChanged Url - ----------------------------------------- - -- Services - ----------------------------------------- - | ConnectLastFm - | DisconnectLastFm - | GotLastFmSession (Result Http.Error String) - | Scrobble { duration : Int, timestamp : Int, trackId : String } - ----------------------------------------- - -- User - ----------------------------------------- - | Export - | ImportFile File - | ImportJson String - | InsertDemo - | LoadEnclosedUserData Json.Decode.Value - | LoadHypaethralUserData Json.Decode.Value - | RequestImport - | SaveEnclosedUserData - ----------------------------------------- - -- โš—๏ธ Adjunct - ----------------------------------------- - | KeyboardMsg Keyboard.Msg - ----------------------------------------- - -- ๐Ÿฆ‰ Nested - ----------------------------------------- - | SyncingMsg Syncing.Msg - | QueueMsg Queue.Msg - | SourcesMsg Sources.Msg - | TracksMsg Tracks.Msg - ----------------------------------------- - -- ๐Ÿ“ญ Other - ----------------------------------------- - | InstalledServiceWorker - | InstallingServiceWorker - | RedirectToBrain Alien.Event - | ReloadApp - | SetCurrentTime Time.Posix - | SetCurrentTimeZone Time.Zone - | SetIsOnline Bool - - -type alias Organizer model = - Management.Manager Msg model - - -type alias Manager = - Organizer Model diff --git a/src/Core/UI/User/State/Export.elm b/src/Core/UI/User/State/Export.elm deleted file mode 100644 index 5154319d5..000000000 --- a/src/Core/UI/User/State/Export.elm +++ /dev/null @@ -1,136 +0,0 @@ -module UI.User.State.Export exposing (..) - -import Alien -import File.Download -import Json.Encode -import List.Extra as List -import Maybe.Extra as Maybe -import Playlists.Encoding as Playlists -import Return exposing (return) -import Settings exposing (Settings) -import Sources.Encoding as Sources -import Tracks.Encoding as Tracks -import UI.Ports as Ports -import UI.Types exposing (..) -import User.Layer exposing (..) - - - --- ๐Ÿ”ฑ - - -export model = - { favourites = model.favourites - , playlists = List.filterNot (.autoGenerated >> Maybe.isJust) model.playlists - , progress = model.progress - , settings = Just (gatherSettings model) - , sources = model.sources - , tracks = model.tracks.untouched - - -- - , modifiedAt = Just model.currentTime - } - |> encodeHypaethralData - |> Json.Encode.encode 2 - |> (if model.isTauri then - \json -> Ports.downloadJsonUsingTauri { filename = "diffuse.json", json = json } - - else - File.Download.string "diffuse.json" "application/json" - ) - |> return model - - -gatherSettings : Model -> Settings -gatherSettings { chosenBackdrop, coverSelectionReducesPool, hideDuplicates, lastFm, processAutomatically, rememberProgress } = - { backgroundImage = chosenBackdrop - , coverSelectionReducesPool = coverSelectionReducesPool - , hideDuplicates = hideDuplicates - , lastFm = lastFm.sessionKey - , processAutomatically = processAutomatically - , rememberProgress = rememberProgress - } - - -saveEnclosedUserData : Manager -saveEnclosedUserData model = - { cachedTracks = model.cachedTracks - , equalizerSettings = model.eqSettings - , grouping = model.grouping - , onlyShowCachedTracks = model.cachedTracksOnly - , onlyShowFavourites = model.favouritesOnly - , repeat = model.repeat - , scene = model.scene - , searchTerm = model.searchTerm - , selectedPlaylist = Maybe.map .name model.selectedPlaylist - , shuffle = model.shuffle - , sortBy = model.sortBy - , sortDirection = model.sortDirection - , theme = model.theme - } - |> encodeEnclosedData - |> Alien.broadcast Alien.SaveEnclosedUserData - |> Ports.toBrain - |> Return.return model - - -saveFavourites : Manager -saveFavourites model = - model.favourites - |> Json.Encode.list Tracks.encodeFavourite - |> Alien.broadcast Alien.SaveFavourites - |> Ports.toBrain - |> return model - - -saveModifiedAt : Manager -saveModifiedAt = - -- Handled by ๐Ÿง  - Return.singleton - - -savePlaylists : Manager -savePlaylists model = - model.playlists - |> List.filterNot (.autoGenerated >> Maybe.isJust) - |> Json.Encode.list Playlists.encode - |> Alien.broadcast Alien.SavePlaylists - |> Ports.toBrain - |> return model - - -saveProgress : Manager -saveProgress model = - model.progress - |> Json.Encode.dict identity Json.Encode.float - |> Alien.broadcast Alien.SaveProgress - |> Ports.toBrain - |> return model - - -saveSettings : Manager -saveSettings model = - model - |> gatherSettings - |> Settings.encode - |> Alien.broadcast Alien.SaveSettings - |> Ports.toBrain - |> return model - - -saveSources : Manager -saveSources model = - model.sources - |> Json.Encode.list Sources.encode - |> Alien.broadcast Alien.SaveSources - |> Ports.toBrain - |> Return.return model - - -saveTracks : Manager -saveTracks model = - model.tracks.untouched - |> Json.Encode.list Tracks.encodeTrack - |> Alien.broadcast Alien.SaveTracks - |> Ports.toBrain - |> return model diff --git a/src/Core/UI/User/State/Import.elm b/src/Core/UI/User/State/Import.elm deleted file mode 100644 index 5905d71a8..000000000 --- a/src/Core/UI/User/State/Import.elm +++ /dev/null @@ -1,232 +0,0 @@ -module UI.User.State.Import exposing (..) - -import File exposing (File) -import File.Select -import Json.Decode -import Json.Encode -import LastFm -import List.Extra as List -import Maybe.Extra as Maybe -import Notifications -import Process -import Return exposing (andThen, return) -import Return.Ext exposing (communicate) -import Task -import UI.Backdrop as Backdrop -import UI.Common.State as Common exposing (showNotification) -import UI.Demo as Demo -import UI.Equalizer.State as Equalizer -import UI.Page as Page -import UI.Playlists.Directory -import UI.Sources.State as Sources -import UI.Tracks.State as Tracks -import UI.Types as UI exposing (..) -import UI.User.State.Export as User -import Url.Ext as Url -import User.Layer exposing (..) - - - --- ๐Ÿ”ฑ - - -importFile : File -> Manager -importFile file model = - 250 - |> Process.sleep - |> Task.andThen (\_ -> File.toString file) - |> Task.perform UI.ImportJson - |> return { model | isLoading = True } - - -importJson : String -> Manager -importJson json model = - json - -- Load data on main thread (this app) - |> Json.Decode.decodeString Json.Decode.value - |> Result.withDefault Json.Encode.null - |> (\j -> importHypaethral j model) - -- Show notification - |> andThen - ("Imported data successfully!" - |> Notifications.success - |> showNotification - ) - -- Clear tracks cache - |> andThen Tracks.clearCache - -- Redirect to index page - |> andThen Common.forceTracksRerender - |> andThen (Common.changeUrlUsingPage Page.Index) - ----------------------------- - -- Save all the imported data - ----------------------------- - |> saveAllHypaethralData - - -insertDemo : Manager -insertDemo model = - model - |> loadHypaethralUserData (Demo.tape model.currentTime) - |> saveAllHypaethralData - - -loadEnclosedUserData : Json.Decode.Value -> Manager -loadEnclosedUserData = - importEnclosed - - -loadHypaethralUserData : Json.Decode.Value -> Manager -loadHypaethralUserData json model = - model - |> importHypaethral json - |> andThen - (\m -> - case Url.action m.url of - [ "authenticate", "lastfm" ] -> - { authenticating = True - , sessionKey = Nothing - } - |> (\n -> { m | lastFm = n }) - |> communicate (LastFm.authenticationCommand GotLastFmSession m.url) - - _ -> - Return.singleton m - ) - |> andThen - Sources.addSourcesFromUrl - - -requestImport : Manager -requestImport model = - ImportFile - |> File.Select.file [ "application/json" ] - |> return model - - - --- โš—๏ธ โ–‘โ–‘ HYPAETHRAL DATA - - -importHypaethral : Json.Decode.Value -> Manager -importHypaethral value model = - case decodeHypaethralData value of - Ok data -> - let - chosenBackdrop = - data.settings - |> Maybe.andThen .backgroundImage - |> Maybe.withDefault Backdrop.default - |> Just - - newPlaylistsCollection = - List.append - data.playlists - (UI.Playlists.Directory.generate data.sources data.tracks) - - selectedPlaylist = - Maybe.andThen - (\n -> List.find (.name >> (==) n) newPlaylistsCollection) - model.playlistToActivate - - lastFmModel = - model.lastFm - in - Tracks.importHypaethral - data - selectedPlaylist - { model - | chosenBackdrop = chosenBackdrop - , coverSelectionReducesPool = Maybe.unwrap True .coverSelectionReducesPool data.settings - , hideDuplicates = Maybe.unwrap False .hideDuplicates data.settings - , lastFm = { lastFmModel | sessionKey = Maybe.andThen .lastFm data.settings } - , playlists = newPlaylistsCollection - , playlistToActivate = Nothing - , processAutomatically = Maybe.unwrap True .processAutomatically data.settings - , progress = data.progress - , rememberProgress = Maybe.unwrap True .rememberProgress data.settings - , sources = data.sources - } - - Err err -> - err - |> Json.Decode.errorToString - |> Notifications.error - |> Common.showNotificationWithModel model - - -saveAllHypaethralData : ( Model, Cmd Msg ) -> ( Model, Cmd Msg ) -saveAllHypaethralData return = - List.foldl - (\( _, bit ) -> - case bit of - Favourites -> - andThen User.saveFavourites - - ModifiedAt -> - andThen User.saveModifiedAt - - Playlists -> - andThen User.savePlaylists - - Progress -> - andThen User.saveProgress - - Settings -> - andThen User.saveSettings - - Sources -> - andThen User.saveSources - - Tracks -> - andThen User.saveTracks - ) - return - hypaethralBit.list - - - --- โš—๏ธ โ–‘โ–‘ ENCLOSED DATA - - -importEnclosed : Json.Decode.Value -> Manager -importEnclosed value model = - let - equalizerSettings = - model.eqSettings - in - case decodeEnclosedData value of - Ok data -> - let - newEqualizerSettings = - { equalizerSettings - | low = data.equalizerSettings.low - , mid = data.equalizerSettings.mid - , high = data.equalizerSettings.high - , volume = data.equalizerSettings.volume - } - in - ( { model - | eqSettings = newEqualizerSettings - , playlistToActivate = data.selectedPlaylist - , repeat = data.repeat - , shuffle = data.shuffle - - -- Tracks - , cachedTracks = data.cachedTracks - , cachedTracksOnly = data.onlyShowCachedTracks - , favouritesOnly = data.onlyShowFavourites - , grouping = data.grouping - , scene = data.scene - , searchTerm = data.searchTerm - , sortBy = data.sortBy - , sortDirection = data.sortDirection - , theme = data.theme - } - -- - , Equalizer.adjustAllKnobs newEqualizerSettings - ) - - Err err -> - ("Failed to decode enclosed data: " ++ Json.Decode.errorToString err) - |> Notifications.error - |> Common.showNotificationWithModel model diff --git a/src/Core/UI/View.elm b/src/Core/UI/View.elm deleted file mode 100644 index 1f3cefd3c..000000000 --- a/src/Core/UI/View.elm +++ /dev/null @@ -1,16 +0,0 @@ -module UI.View exposing (view) - -import Browser -import UI.Theme -import UI.Types exposing (Model, Msg) - - - --- ๐Ÿ—บ - - -view : Model -> Browser.Document Msg -view model = - { title = "Diffuse" - , body = [ UI.Theme.view model ] - } diff --git a/src/Css/About.css b/src/Css/About.css deleted file mode 100644 index 066df2cf9..000000000 --- a/src/Css/About.css +++ /dev/null @@ -1,146 +0,0 @@ -@charset "UTF-8"; - -/* Imports - ------- */ - -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; - -@import "Fonts.css"; -@import "Logo.css"; - -/* Basic - ----- */ - -p a, -ul a, -ol a { - @apply underline; - text-underline-offset: 2px; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - @apply font-bold leading-tight antialiased; - font-family: "Playfair Display", serif; - letter-spacing: -0.005em; -} - -h1 { - display: none; -} - -h2 { - @apply mb-6 mt-12 text-2xl; -} - -h3 { - @apply mb-5 mt-8 text-xl; -} - -h4 { - @apply mb-4 mt-6 text-lg; -} - -h5 { - @apply mb-5 mt-6 text-base; -} - -p { - @apply my-3 leading-relaxed; -} - -strong { - @apply font-semibold; -} - -/* Blockquote - ---------- */ - -blockquote { - @apply font-display mb-12 ml-0 mt-12 max-w-xl pl-0 font-bold tracking-tight; -} - -blockquote p { - @apply text-5xl leading-tight; - background: url(images/Background/21.jpg); - background-position: center 33%; - background-size: cover; - color: #b7c2d4; - - background-clip: text; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; -} - -/* Code - ---- */ - -pre { - @apply overflow-x-auto overflow-y-hidden rounded border-2 border-gray-400 p-6 text-sm leading-relaxed; -} - -code { - @apply font-mono; -} - -@screen dark { - pre { - @apply border-base01; - } -} - -.hljs-comment, -.language-shell .hljs-meta, -.language-shell .hljs-meta + .bash { - opacity: 0.5; -} - -.hljs-string { - @apply text-base0b; -} - -/* Links - ----- */ - -h5 + p a { - @apply mb-2 inline-block; -} - -/* Lists - ----- */ - -ul, -ol { - @apply ml-3 leading-relaxed; -} - -ul li, -ol li { - @apply mb-1; -} - -ol { - counter-reset: ol; -} - -ol li::before { - @apply text-base06 inline-block pr-3; - counter-increment: ol; - content: counters(ol, ".") "."; -} - -ul li::before { - @apply text-base06 inline-block pr-3; - content: "โ€ข"; -} - -ul p, -ol p { - @apply my-0 inline; -} diff --git a/src/Css/Application.css b/src/Css/Application.css deleted file mode 100644 index cb70cee67..000000000 --- a/src/Css/Application.css +++ /dev/null @@ -1,257 +0,0 @@ -@charset "UTF-8"; - -/* Imports - ------- */ - -@import "tailwindcss/base"; -@import "tailwindcss/components"; -@import "tailwindcss/utilities"; - -@import "Fonts.css"; -@import "Logo.css"; -@import "Notifications.css"; - -/* ๐Ÿ›  - ----- */ - -@layer utilities { - .scrolling-touch { - -webkit-overflow-scrolling: touch; - } - - .scrolling-auto { - -webkit-overflow-scrolling: auto; - } -} - -/* Base - ---- */ - -:root { - color-scheme: light dark; -} - -html, -body { - overflow: hidden; - overscroll-behavior: none; -} - -html, -body, -.screen-height { - height: 100dvh; -} - -body { - @apply text-base01; - - background-color: rgb(29, 29, 29); - background-image: url(images/ocean.jpg); - font-feature-settings: kern, liga; - font-weight: 400; - min-width: 300px; - text-rendering: optimizeLegibility; -} - -*:active { - outline: none; -} - -.align-sub { - vertical-align: sub; -} - -.all-small-caps { - font-variant-caps: all-small-caps; -} - -.flex-basis-0 { - flex-basis: 0; -} - -.smooth-scrolling { - scroll-behavior: smooth; -} - -@screen dark { - body { - @apply text-gray-600; - } -} - -/* Buttons - ------- */ - -button { - color: inherit; - font-family: inherit; -} - -/* Dragging - -------- */ - -.dragging-something { - @apply select-none; - cursor: grabbing; -} - -.dragging-something * { - cursor: grabbing !important; -} - -/* Forms - ----- */ - -input::placeholder, -textarea::placeholder { - color: rgba(0, 0, 0, 0.275); - opacity: 1; -} - -input:user-invalid, -textarea:user-invalid { - @apply border-base08; - box-shadow: none; - outline: none; -} - -select:-moz-focusring { - color: transparent; - text-shadow: 0 0 0 rgb(63, 63, 63); -} - -@screen dark { - input::placeholder, - textarea::placeholder { - @apply text-base03; - opacity: 1; - } - - select:-moz-focusring { - color: transparent; - text-shadow: 0 0 0 rgb(232, 232, 232); - } -} - -/* Loading - ------- */ - -.loading-animation { - animation: loading-rotator 2s linear infinite; -} - -.loading-animation__circle { - animation: - loading-dash 1.5s ease-in-out infinite, - loading-colors 6s ease-in-out infinite; - stroke-dasharray: 1, 86.25; - stroke-dashoffset: 0; - transform-origin: center; -} - -@keyframes loading-rotator { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} - -@keyframes loading-colors { - 0% { - stroke: rgb(248, 164, 167); - } - - 25% { - stroke: rgb(254, 196, 24); - } - - 50% { - stroke: rgb(72, 182, 133); - } - - 75% { - stroke: rgb(6, 182, 239); - } - - 100% { - stroke: rgb(248, 164, 167); - } -} - -@keyframes loading-dash { - 0% { - stroke-dasharray: 1, 86.25; - stroke-dashoffset: 0; - } - - 50% { - stroke-dasharray: 64.6875, 86.25; - stroke-dashoffset: -21.5625; - } - - 100% { - stroke-dasharray: 64.6875, 86.25; - stroke-dashoffset: -86.25; - } -} - -/* Overrides - --------- */ - -.no-tracks-view > div { - /* CSS fix for a scrolling issue: - Element would appear scrolled even though it's not (virtual-dom issue probably) - */ - overflow: visible !important; -} - -/* Range slider - ------------ */ - -.range-slider { - @apply w-24 appearance-none rounded bg-gray-400; - height: 4px; -} - -.range-slider::-webkit-slider-thumb { - @apply bg-base02 appearance-none; - border-radius: 100%; - height: 10px; - width: 10px; -} - -.range-slider::-moz-range-thumb { - @apply bg-darkest-hour appearance-none; - border-radius: 100%; - height: 10px; - width: 10px; -} - -.range-slider::-ms-thumb { - @apply bg-darkest-hour appearance-none; - border-radius: 100%; - height: 10px; - width: 10px; -} - -@screen dark { - .range-slider { - @apply bg-base01; - } - - .range-slider::-webkit-slider-thumb { - @apply bg-base07; - } - - .range-slider::-moz-range-thumb { - @apply bg-base07; - } - - .range-slider::-ms-thumb { - @apply bg-base07; - } -} diff --git a/src/Css/Fonts.css b/src/Css/Fonts.css deleted file mode 100644 index d19748e2a..000000000 --- a/src/Css/Fonts.css +++ /dev/null @@ -1,222 +0,0 @@ -/* Hack */ - - -@font-face { - font-family: "Hack"; - src: url("fonts/Hack/regular-subset.woff2") format("woff2"); - font-weight: 400; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Hack"; - src: url("fonts/Hack/bold-subset.woff2") format("woff2"); - font-weight: 700; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - - -/* Source Sans Pro */ - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/roman.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/light.woff2") format("woff2"); - font-weight: 300; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/italic.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/light-italic.woff2") format("woff2"); - font-weight: 300; - font-stretch: normal; - font-style: italic; - font-display: swap; -} - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/roman.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/regular.woff2") format("woff2"); - font-weight: 400; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/italic.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/italic.woff2") format("woff2"); - font-weight: 400; - font-stretch: normal; - font-style: italic; - font-display: swap; -} - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/roman.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/semibold.woff2") format("woff2"); - font-weight: 600; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/italic.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/semibold-italic.woff2") format("woff2"); - font-weight: 600; - font-stretch: normal; - font-style: italic; - font-display: swap; -} - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/roman.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/bold.woff2") format("woff2"); - font-weight: 700; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/italic.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/bold-italic.woff2") format("woff2"); - font-weight: 700; - font-stretch: normal; - font-style: italic; - font-display: swap; -} - - -@font-face { - font-family: "Source Sans Pro"; - src: url("fonts/Source Sans Pro/Variable/roman.woff2") format("woff2-variations"), - url("fonts/Source Sans Pro/Old/black.woff2") format("woff2"); - font-weight: 900; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - - -/* Montserrat */ - - -@font-face { - font-family: "Montserrat"; - src: url("fonts/Montserrat/Variable/variable.woff2") format("woff2-variations"), - url("fonts/Montserrat/Old/light.woff2") format("woff2"); - font-weight: 300; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Montserrat"; - src: url("fonts/Montserrat/Variable/variable.woff2") format("woff2-variations"), - url("fonts/Montserrat/Old/regular.woff2") format("woff2"); - font-weight: 400; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Montserrat"; - src: url("fonts/Montserrat/Variable/variable.woff2") format("woff2-variations"), - url("fonts/Montserrat/Old/medium.woff2") format("woff2"); - font-weight: 500; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Montserrat"; - src: url("fonts/Montserrat/Variable/variable.woff2") format("woff2-variations"), - url("fonts/Montserrat/Old/semibold.woff2") format("woff2"); - font-weight: 600; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Montserrat"; - src: url("fonts/Montserrat/Variable/variable.woff2") format("woff2-variations"), - url("fonts/Montserrat/Old/bold.woff2") format("woff2"); - font-weight: 700; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - -@font-face { - font-family: "Montserrat"; - src: url("fonts/Montserrat/Variable/variable.woff2") format("woff2-variations"), - url("fonts/Montserrat/Old/extrabold.woff2") format("woff2"); - font-weight: 800; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - - -/* Playfair Display */ - - -@font-face { - font-family: "Playfair Display"; - src: url("fonts/Playfair Display/bold.woff2") format("woff2"); - font-weight: 700; - font-stretch: normal; - font-style: normal; - font-display: swap; -} - - - -/* Icons */ - - -@font-face { - font-family: "or-favourites"; - src: - url(data:application/x-font-woff;charset=utf-8;base64,d09GMgABAAAAAALMAA0AAAAABtgAAAJ3AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGh4GYACCZhEICoIkgiALEAABNgIkAxoEIAWDYwc/G90FyK4GbGMY+iEaIl0ZXnqKj/KnMxo7rFjbeJ5aK9/v7pldlgeU6EhAReQQhOPyqSgCF2HRqQMjoqPX6vfTvRLxyy8hvfxwgdgCG18WlkhGZ3wxVyCZK1Ax/aiqC+B/4mbcWICdj4mBZGkigQYYTgsk7LKxKMD9f/5g/fla58se9MEfLNSfcvcO9O3SWls3IbDt4mQLsPO506mPDqZYAkVELxoKan2ddJQfZfAFrM4/QkypmmLtKeRVvX0rsyIZCEj3I9S5bSDwhL5gg4FzYGqoaGI/qeHF70Bi9gvts99VMQVAVNKTCqSggHQPjarzL3rg45FF1mAA5uhxD7wHNEAQUaTWurrhQYB4+hIlu2a3I8xOXQvzO3CizrFj67snzG8Dsray+jlA9h5ezAsG025eelIV85r3AwDU71dzp4caxei26MzamixWl2hW+WUHJJl1NZnROUYue2qoNFe4OdrTX+dfx9KE/vahFdXFL1cVr2ofSZppTAzr8pckcBbsbPn493jRlIeqiT2AI78PQBDeHHyt7bBO/T80b+bKaag/KzBUQBjfHpqKuPLt0DdAbgJQ0LilHmHgCD1QAAAw91dAzGwgUAycRKCaeYRAM/NWQGfSDeiR3c1ja3GjL20k1iCiirkDVLUmG2iIiuos2aV6a87fMnaYFn0GRZLqMqrPiEFyw7ghePoi8puJy+TDfAi4hhONUOkyCO+o1YPOhTW69BZu0BC5Pho+mCgxcD9rjEXFDHTaBX00hvkQcRrcoC7llF4+uk3wUaRHnzJ9wImCp+nYcd4p6un78cMlgEKfGJCipKJHXNbQi6qJfhkzHtHIY2JiGAA=) format("woff2"), url(data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAWQAAsAAAAABUQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDxEM82NtYXAAAAFoAAAAXAAAAFwAsAEYZ2FzcAAAAcQAAAAIAAAACAAAABBnbHlmAAABzAAAATAAAAEwWmanp2hlYWQAAAL8AAAANgAAADYOgV4yaGhlYQAAAzQAAAAkAAAAJAdsA8dobXR4AAADWAAAABgAAAAYDgAArGxvY2EAAANwAAAADgAAAA4AwACAbWF4cAAAA4AAAAAgAAAAIAAJADduYW1lAAADoAAAAc4AAAHOtR+CC3Bvc3QAAAVwAAAAIAAAACAAAwAAAAMDVQGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAQAAAAHQDwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEAEAAAAAMAAgAAgAEAAEAIABmAHT//f//AAAAAAAgAGYAdP/9//8AAf/j/57/kQADAAEAAAAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAACAFYAHQOqAysAGAA0AAAlPgM1NCYjIgYHIy4BIyIGFRQeAh8BEzIeAhUUDgIPAScuAzU0PgIzMhYXPgECBEx9WTBVQTFWEVAQVjJAVjBZfE0EwDFWPyQ0X4ZTPj5Sh180JD9VMjdmIyJmk0V0aF4vQFQ5Kys5VEAvXmh0RQQCnCQ/VjE9cXR/Szg2S392cT0xVj8kMigoMgAAAAABAFYAHQOqAysAGwAAJScuAzU0PgIzMhYXPgEzMh4CFRQOAgcCAD5Sh180JD9VMjdmIyJmODFWPyQ0X4ZTHThLf3RxPTFWPyQyKCgyJD9WMT1xdn9LAAAAAQAAAAEAALY04pNfDzz1AAsEAAAAAADV3I0FAAAAANXcjQUAAAAAA6oDKwAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADqgABAAAAAAAAAAAAAAAAAAAABgQAAAAAAAAAAAAAAAIAAAAEAABWBAAAVgAAAAAACgAUAB4AbACYAAAAAQAAAAYANQACAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAA0AAAABAAAAAAACAAcAlgABAAAAAAADAA0ASAABAAAAAAAEAA0AqwABAAAAAAAFAAsAJwABAAAAAAAGAA0AbwABAAAAAAAKABoA0gADAAEECQABABoADQADAAEECQACAA4AnQADAAEECQADABoAVQADAAEECQAEABoAuAADAAEECQAFABYAMgADAAEECQAGABoAfAADAAEECQAKADQA7G9yLWZhdm91cml0ZXMAbwByAC0AZgBhAHYAbwB1AHIAaQB0AGUAc1ZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMG9yLWZhdm91cml0ZXMAbwByAC0AZgBhAHYAbwB1AHIAaQB0AGUAc29yLWZhdm91cml0ZXMAbwByAC0AZgBhAHYAbwB1AHIAaQB0AGUAc1JlZ3VsYXIAUgBlAGcAdQBsAGEAcm9yLWZhdm91cml0ZXMAbwByAC0AZgBhAHYAbwB1AHIAaQB0AGUAc0ZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format("woff"); - font-weight: normal; - font-stretch: normal; - font-style: normal; -} diff --git a/src/Css/Logo.css b/src/Css/Logo.css deleted file mode 100644 index 075cb26f7..000000000 --- a/src/Css/Logo.css +++ /dev/null @@ -1,15 +0,0 @@ -.logo { - opacity: 0.85; -} - -.logo img { - width: 154px; -} - -.logo-backdrop { - background-image: url("images/diffuse__icon-dark.svg"); - background-position: -43.5% 98px; - transform-origin: left top; - transform: rotate(90deg); - width: 105vh; -} diff --git a/src/Css/Notifications.css b/src/Css/Notifications.css deleted file mode 100644 index 303fdc96b..000000000 --- a/src/Css/Notifications.css +++ /dev/null @@ -1,29 +0,0 @@ -.notifications { - font-size: 13px; - line-height: 1.35; -} - -.notifications a, -.notifications em { - border-bottom: 1px solid rgba(255, 255, 255, 0.45); - display: inline-block; -} - -.notifications a { - color: inherit; - font-weight: 600; - text-decoration: none; -} - -.notifications p { - margin: 0; - padding: 0; -} - -.notifications em { - font-weight: inherit; -} - -.notifications strong { - font-weight: 600; -} diff --git a/src/Javascript/Brain/application.ts b/src/Javascript/Brain/application.ts deleted file mode 100644 index eeb2156dc..000000000 --- a/src/Javascript/Brain/application.ts +++ /dev/null @@ -1,24 +0,0 @@ -import "./index.d" - -// @ts-ignore -import { Elm } from "brain.elm.js" - - -// ๐Ÿš€ - - -const flags: Record = location - .hash - .substring(1) - .split("&") - .reduce((acc, flag) => { - const [k, v] = flag.split("=") - return { ...acc, [k]: v } - }, {}) - - -export const load = () => Elm.Brain.init({ - flags: { - initialUrl: decodeURIComponent(flags.appHref) || "" - } -}) diff --git a/src/Javascript/Brain/artwork.ts b/src/Javascript/Brain/artwork.ts deleted file mode 100644 index fd8e9c896..000000000 --- a/src/Javascript/Brain/artwork.ts +++ /dev/null @@ -1,218 +0,0 @@ -// -// Album Covers -// (โ—•โ€ฟโ—•โœฟ) - -import * as Uint8arrays from "uint8arrays" - -import * as processing from "./processing" -import { type App } from "./elm/types" -import { transformUrl } from "../urls" -import { toCache } from "./common" -import { type CoverPrep } from "../common" - - -// ๐ŸŒณ - - -type CoverPrepWithUrls = CoverPrep & { - trackGetUrl: string - trackHeadUrl: string -} - - - -// ๐Ÿ”๏ธ - - -let artworkQueue: CoverPrep[] = [] -let app: App - - - -// ๐Ÿš€ - - -export function init(a: App) { - app = a - - app.ports.provideArtworkTrackUrls.subscribe(provideArtworkTrackUrls) -} - - - -// PORTS - - -function provideArtworkTrackUrls(prep: CoverPrepWithUrls) { - find(prep).then(blob => { - return toCache(`coverCache.${prep.cacheKey}`, blob).then(_ => blob) - }) - .then((blob: Blob) => { - const url = URL.createObjectURL(blob) - - self.postMessage({ - tag: "GOT_CACHED_COVER", - data: { imageType: blob.type, key: prep.cacheKey, url: url }, - error: null - }) - }) - .catch(err => { - if (err === "No artwork found") { - // Indicate that we've tried to find artwork, - // so that we don't try to find it each time we launch the app. - return toCache(`coverCache.${prep.cacheKey}`, "TRIED") - - } else { - // Something went wrong - console.error(err) - return toCache(`coverCache.${prep.cacheKey}`, "TRIED") - - } - }) - .catch(() => { - console.warn("Failed to download artwork for ", prep) - }) - .finally(shiftQueue) -} - - - -// ๐Ÿ› ๏ธ - - -export function download(list: CoverPrep[]) { - const exe = !artworkQueue[0] - artworkQueue = artworkQueue.concat(list) - if (exe) shiftQueue() -} - - -function shiftQueue() { - const next = artworkQueue.shift() - - if (next) { - app.ports.makeArtworkTrackUrls.send(next) - } else { - self.postMessage({ - action: "FINISHED_DOWNLOADING_ARTWORK", - data: null - }) - } -} - - - -// ใŠ™๏ธ - - -const REJECT = () => Promise.reject("No artwork found") - - -function decodeCacheKey(cacheKey: string) { - return Uint8arrays.toString( - Uint8arrays.fromString(cacheKey, "base64"), - "utf8" - ) -} - - -function find(prep: CoverPrepWithUrls) { - return findUsingTags(prep) - .then(a => a ? a : findUsingMusicBrainz(prep)) - .then(a => a ? a : findUsingLastFm(prep)) - .then(a => a ? a : REJECT()) - .then(a => a.type.startsWith("image/") ? a : REJECT()) -} - - - -// 1. TAGS - - -async function findUsingTags(prep: CoverPrepWithUrls) { - return Promise.all( - [ - transformUrl(prep.trackHeadUrl, app), - transformUrl(prep.trackGetUrl, app) - ] - - ).then(([ headUrl, getUrl ]) => processing.getTags( - headUrl, - getUrl, - prep.trackFilename, - { covers: true } - - )).then(tags => { - return tags?.picture - ? new Blob([ tags.picture.data ], { type: tags.picture.format }) - : null - - }) -} - - - -// 2. MUSIC BRAINZ - - -function findUsingMusicBrainz(prep: CoverPrepWithUrls) { - if (!navigator.onLine) return null - - const parts = decodeCacheKey(prep.cacheKey).split(" --- ") - const artist = parts[ 0 ] - const album = parts[ 1 ] || parts[ 0 ] - - const query = `release:"${album}"` + (prep.variousArtists === "t" ? `` : ` AND artist:"${artist}"`) - const encodedQuery = encodeURIComponent(query) - - return fetch(`https://musicbrainz.org/ws/2/release/?query=${encodedQuery}&fmt=json`) - .then(r => r.json()) - .then(r => musicBrainzCover(r.releases)) -} - - -function musicBrainzCover(remainingReleases) { - const release = remainingReleases[ 0 ] - if (!release) return null - - return fetch( - `https://coverartarchive.org/release/${release.id}/front-500` - ).then( - r => r.blob() - ).then( - r => r && r.type.startsWith("image/") - ? r - : musicBrainzCover(remainingReleases.slice(1)) - ).catch( - () => musicBrainzCover(remainingReleases.slice(1)) - ) -} - - - -// 3. LAST FM - - -function findUsingLastFm(prep: CoverPrepWithUrls) { - if (!navigator.onLine) return null - - const query = encodeURIComponent( - decodeCacheKey(prep.cacheKey).replace(" --- ", " ") - ) - - return fetch(`https://ws.audioscrobbler.com/2.0/?method=album.search&album=${query}&api_key=4f0fe85b67baef8bb7d008a8754a95e5&format=json`) - .then(r => r.json()) - .then(r => lastFmCover(r.results.albummatches.album)) -} - - -function lastFmCover(remainingMatches) { - const album = remainingMatches[ 0 ] - const url = album ? album.image[ album.image.length - 1 ][ "#text" ] : null - - return url && url !== "" - ? fetch(url) - .then(r => r.blob()) - .catch(_ => lastFmCover(remainingMatches.slice(1))) - : album && lastFmCover(remainingMatches.slice(1)) -} diff --git a/src/Javascript/Brain/common.ts b/src/Javascript/Brain/common.ts deleted file mode 100644 index f61bedd52..000000000 --- a/src/Javascript/Brain/common.ts +++ /dev/null @@ -1,117 +0,0 @@ -// -// Common stuff -// ส•โ€ขแดฅโ€ขส” - - -import * as crypto from "../crypto" -import { db } from "../common" - - -export const SECRET_KEY_LOCATION = "SECRET_KEY" - - -// ๐Ÿ”ฑ - - -export function isLocalHost(url: string) { - return ( - url.startsWith("localhost") || - url.startsWith("localhost") || - url.startsWith("127.0.0.1") || - url.startsWith("127.0.0.1") - ) -} - - -export function parseJsonIfNeeded(a: unknown) { - if (typeof a === "string") return JSON.parse(a) - return a -} - - -export function reportError(app, event) { - return e => { - const err = e ? e.message || e : null - if (err) { - console.error(err, e.stack) - app.ports.fromAlien.send({ tag: event.tag, data: null, error: err }) - } - } -} - - -export function sendData(app, event, opts: any = {}) { - return data => { - app.ports.fromAlien.send({ - tag: event.tag, - data: (opts && opts.parseJSON && typeof data === "string") - ? JSON.parse(data) - : (data || null), - error: null - }) - } -} - - - -// Cache -// ----- - -export function removeCache(key: string): Promise { - return db().removeItem(key) -} - - -export function fromCache(key: string): Promise { - return db().getItem(key) -} - - -export function toCache(key: string, data: unknown): Promise { - return db().setItem(key, data) -} - - - -// Crypto -// ------ - -export function decryptIfNeeded(data: unknown): Promise { - if (typeof data !== "string") { - return Promise.resolve(data) - - } else if (typeof data === "string" && (data.startsWith("{") || data.startsWith("["))) { - return Promise.resolve(data) - - } else if (data.length < 15 && Number.isInteger(parseInt(data, 10))) { - return Promise.resolve(data) - - } else { - return data - ? getSecretKey().then(secretKey => { - if (!secretKey) throw new Error("There seems to be existing data that's encrypted, I will need the passphrase (ie. encryption key) to continue.") - return crypto.decrypt(secretKey, data) - }) - : Promise.resolve(null) - - } -} - - -export async function encryptIfPossible(unencryptedData: string): Promise { - return unencryptedData - ? getSecretKey().then(secretKey => - secretKey - ? crypto.encrypt(secretKey, unencryptedData) - : unencryptedData - ) - : unencryptedData -} - - -export { encryptIfPossible as encryptWithSecretKey } - - -export function getSecretKey(): Promise { - return db().getItem(SECRET_KEY_LOCATION) -} diff --git a/src/Javascript/Brain/elm/types.ts b/src/Javascript/Brain/elm/types.ts deleted file mode 100644 index e5a05984a..000000000 --- a/src/Javascript/Brain/elm/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type App = any // TODO: ElmApp - - -export type ElmPorts = { - // โ† Elm - // ... - - // โ†’ Elm - fromAlien: PortToElm -} diff --git a/src/Javascript/Brain/index.d.ts b/src/Javascript/Brain/index.d.ts deleted file mode 100644 index d1860d30d..000000000 --- a/src/Javascript/Brain/index.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ElmPorts } from "./elm/types" - - -export { } - - -declare const Elm: { Brain: ElmMain } -declare const BUILD_TIMESTAMP: string - - -declare module "elm-taskport" { - const install: () => void - const register: (a: string, b: (arg: any) => any) => void -} diff --git a/src/Javascript/Brain/index.ts b/src/Javascript/Brain/index.ts deleted file mode 100644 index 4444ef914..000000000 --- a/src/Javascript/Brain/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -// -// Brain -// ๐Ÿง  -// -// This worker is responsible for everything non-UI. - -import * as Application from "./application" -import * as Artwork from "./artwork" -import * as Processing from "./processing" -import * as Search from "./search" -import * as User from "./user" -import * as TaskPorts from "./task-ports" -import * as Tracks from "./tracks" -import * as UI from "./ui" - - -// ๐Ÿš€ - -TaskPorts.register() -User.TaskPorts.register() - -const app = Application.load() -const brain = self as unknown as Worker - -// ๐Ÿ–ผ๏ธ - -UI.link(brain, app) - -// โšก -Artwork.init(app) -Processing.init(app) -Search.init(app) -Tracks.init(app) - -User.Ports.register(app) - -// ๐Ÿ›ซ - -brain.postMessage({ action: "READY" }) diff --git a/src/Javascript/Brain/processing.ts b/src/Javascript/Brain/processing.ts deleted file mode 100644 index 4c625e658..000000000 --- a/src/Javascript/Brain/processing.ts +++ /dev/null @@ -1,303 +0,0 @@ -// -// Processing -// โ™ช(ยดฮต๏ฝ€ ) -// -// Audio processing, getting metadata, etc. - -import type { IAudioMetadata } from "music-metadata" -import type { GeneralTrack, MediaInfoResult } from "mediainfo.js" -import type { ITokenizer } from "strtok3" - -import * as Uint8arrays from "uint8arrays" -import { type App } from "./elm/types" -import { transformUrl } from "../urls" - - -// ๐Ÿ”๏ธ - - -const ENCODING_ISSUE_REPLACE_CHAR = 'โ–ฉ'; - -let app: App - - - -// ๐Ÿš€ - - -export function init(a: App) { - app = a - - app.ports.requestTags.subscribe(requestTags) - app.ports.syncTags.subscribe(syncTags) -} - - - -// Ports -// ----- - - -function requestTags(context) { - processContext(context, app).then(newContext => { - app.ports.receiveTags.send(newContext) - }) -} - - -function syncTags(context) { - processContext(context, app).then(newContext => { - app.ports.replaceTags.send(newContext) - }) -} - - - -// Contexts -// -------- - - -export async function processContext(context, app) { - const initialPromise = Promise.resolve([]); - - return context.urlsForTags - .reduce((accumulator, urls, idx) => { - return accumulator.then((col) => { - const filename = context.receivedFilePaths[idx].split("/").reverse()[0]; - - return Promise.all([transformUrl(urls.headUrl, app), transformUrl(urls.getUrl, app)]) - .then(([headUrl, getUrl]) => { - return getTags(headUrl, getUrl, filename, { covers: false }); - }) - .then((r) => { - return col.concat(r); - }) - .catch((e) => { - console.warn(e); - return col.concat(null); - }); - }); - }, initialPromise) - .then((col) => { - context.receivedTags = col; - return context; - }); -} - - - -// Tags - General -// -------------- - - -type Tags = { - disc: number; - nr: number; - album: string | null; - artist: string | null; - title: string; - genre: string | null; - year: number | null; - picture: { data: Uint8Array; format: string } | null; -}; - -export async function getTags( - headUrl: string, - getUrl: string, - filename: string, - { covers }: { covers: boolean }, -) { - const musicMetadata = await import("music-metadata"); - const httpTokenizer = await import("@tokenizer/http"); - const rangeTokenizer = await import("@tokenizer/range"); - - let tokenizer: ITokenizer; - let mmResult; - - try { - const httpClient = new httpTokenizer.HttpClient(headUrl, { resolveUrl: false }); - httpClient.resolvedUrl = getUrl - - tokenizer = await rangeTokenizer.tokenizer(httpClient); - - mmResult = await musicMetadata - .parseFromTokenizer(tokenizer, { skipCovers: !covers }) - .catch((err) => { - console.warn(err); - return null; - }); - } catch (err) { - console.warn(err); - } - - const mmTags = mmResult && pickTagsFromMusicMetadata(filename, mmResult); - if (mmTags) return mmTags; - - const miResult = await (await mediaInfoClient(covers)) - .analyzeData(getSize(headUrl), readChunk(getUrl)) - .catch((err) => { - console.warn(err); - return null; - }); - - const miTags = miResult && pickTagsFromMediaInfo(filename, miResult); - if (miTags) return miTags; - - return fallbackTags(filename); -} - -function fallbackTags(filename: string): Tags { - const filenameWithoutExt = filename.replace(/\.\w+$/, ""); - - return { - disc: 1, - nr: 1, - album: null, - artist: null, - title: filenameWithoutExt, - genre: null, - year: null, - picture: null, - }; -} - -// Tags - Media Info -// ----------------- - -const getSize = (headUrl: string) => async (): Promise => { - const response = await fetch(headUrl, { method: "HEAD" }); - - if (!response.ok) { - throw new Error(`HTTP error status=${response.status}: ${response.statusText}`); - } - - const l = response.headers.get("Content-Length"); - - if (l) { - return parseInt(l, 10); - } else { - throw new Error("HTTP response doesn't have a Content-Length"); - } -}; - -const readChunk = - (getUrl: string) => - async (chunkSize: number, offset: number): Promise => { - if (chunkSize === 0) return new Uint8Array(); - - const from = offset; - const to = offset + chunkSize; - - const start = to < from ? to : from; - const end = to < from ? from : to; - - const response = await fetch(getUrl, { - method: "GET", - headers: { - Range: `bytes=${start}-${end}`, - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error status=${response.status}: ${response.statusText}`); - } - - return new Uint8Array(await response.arrayBuffer()); - }; - -function pickTagsFromMediaInfo(filename: string, result: MediaInfoResult): Tags | null { - const tagsRaw = result?.media?.track?.filter((t) => t["@type"] === "General")[0]; - const tags = tagsRaw === undefined ? undefined : tagsRaw as GeneralTrack; - if (tags === undefined) return null; - - let artist = typeof tags.Performer == "string" ? tags.Performer : null; - let album = typeof tags.Album == "string" ? tags.Album : null; - - let title = - typeof tags.Track == "string" ? tags.Track : typeof tags.Title == "string" ? tags.Title : null; - - if (!artist && !title) return null; - - // TODO: Encoding issues with mediainfo.js - // https://github.com/buzz/mediainfo.js/issues/150 - if (artist?.includes("๏ฟฝ")) artist = artist.replace("๏ฟฝ", ENCODING_ISSUE_REPLACE_CHAR) - if (album?.includes("๏ฟฝ")) album = album.replace("๏ฟฝ", ENCODING_ISSUE_REPLACE_CHAR) - if (title?.includes("๏ฟฝ")) title = title.replace("๏ฟฝ", ENCODING_ISSUE_REPLACE_CHAR) - - if (artist && artist.includes(" / ")) { - artist = artist - .split(" / ") - .filter((a) => a.trim() !== "") - .join(", "); - } - - const year = tags.Recorded_Date ? new Date(Date.parse(tags.Recorded_Date)).getFullYear() : null; - - return { - disc: tags.Part_Position || 1, - nr: tags.Track_Position || 1, - album: album, - artist: artist, - title: title || filename.replace(/\.\w+$/, ""), - genre: tags.Genre || null, - year: year !== null && isNaN(year) ? null : year, - picture: tags.Cover_Data - ? { - data: Uint8arrays.fromString(tags.Cover_Data.split(" / ")[0], "base64pad"), - format: tags.Cover_Mime || "image/jpeg", - } - : null, - }; -} - - -// Tags - Music Metadata -// --------------------- - - -function pickTagsFromMusicMetadata(filename: string, result: IAudioMetadata): Tags | null { - const tags = result && result.common; - if (!tags) return null; - - const artist = tags.artist && tags.artist.length ? tags.artist : null; - const title = tags.title && tags.title.length ? tags.title : null; - - if (!artist && !title) return null; - - return { - disc: tags.disk.no || 1, - nr: tags.track.no || 1, - album: tags.album && tags.album.length ? tags.album : null, - artist: artist, - title: title || filename.replace(/\.\w+$/, ""), - genre: (tags.genre && tags.genre[0]) || null, - year: tags.year || null, - picture: - tags.picture && tags.picture[0] - ? { data: tags.picture[0].data, format: tags.picture[0].format } - : null, - }; -} - - - -// ๐Ÿ› ๏ธ - - -let client - - -async function mediaInfoClient(covers: boolean) { - const MediaInfoFactory = await import("mediainfo.js").then((a) => a.default); - - if (client) return client - - client = await MediaInfoFactory({ - coverData: covers, - locateFile: () => { - return "../../wasm/media-info.wasm"; - }, - }); - - return client -} diff --git a/src/Javascript/Brain/search.ts b/src/Javascript/Brain/search.ts deleted file mode 100644 index b2b978b5a..000000000 --- a/src/Javascript/Brain/search.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { App } from "./elm/types" - - -// ๐Ÿ”๏ธ - - -let app: App - - - -// ๐Ÿš€ - - -export function init(a: App) { - app = a - - app.ports.requestSearch.subscribe(requestSearch) - app.ports.updateSearchIndex.subscribe(updateSearchIndex) -} - - -const search = new Worker( - "../../search.js", - { type: "module" } -) - - -search.onmessage = event => { - switch (event.data.action) { - case "PERFORM_SEARCH": - app.ports.receiveSearchResults.send(event.data.data) - break - } -} - - - -// PORTS - - -function requestSearch(searchTerm: string) { - search.postMessage({ - action: "PERFORM_SEARCH", - data: searchTerm - }) -} - - -function updateSearchIndex(tracksJson: string) { - search.postMessage({ - action: "UPDATE_SEARCH_INDEX", - data: tracksJson - }) -} diff --git a/src/Javascript/Brain/task-ports.ts b/src/Javascript/Brain/task-ports.ts deleted file mode 100644 index ea7d6bf9e..000000000 --- a/src/Javascript/Brain/task-ports.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-ignore -import * as TaskPort from "elm-taskport" - -import { fromCache, removeCache, toCache } from "./common" - - -export function register() { - TaskPort.install() - - TaskPort.register("fromCache", fromCache) - TaskPort.register("removeCache", removeCache) - TaskPort.register("toCache", ({ key, value }) => toCache(key, value)) -} diff --git a/src/Javascript/Brain/tracks.ts b/src/Javascript/Brain/tracks.ts deleted file mode 100644 index 530eaa916..000000000 --- a/src/Javascript/Brain/tracks.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { App } from "./elm/types" -import { db } from "../common" -import { reportError } from "./common" -import { transformUrl } from "../urls" - - -// ๐Ÿ”๏ธ - - -let app: App - - - -// ๐Ÿš€ - - -export function init(a: App) { - app = a - - app.ports.downloadTracks.subscribe(downloadTracks) - app.ports.removeTracksFromCache.subscribe(removeTracksFromCache) - app.ports.storeTracksInCache.subscribe(storeTracksInCache) -} - - - -// PORTS - - -function downloadTracks(group) { - self.postMessage({ - action: "DOWNLOAD_TRACKS", - data: group - }) -} - - -function removeTracksFromCache(trackIds) { - trackIds.reduce( - (acc, id) => acc.then(_ => db("tracks").removeItem(id)), - Promise.resolve() - - ).catch( - _ => reportError - (app, { tag: "REMOVE_TRACKS_FROM_CACHE" }) - ("Failed to remove tracks from cache") - - ) -} - - -function storeTracksInCache(list) { - list.reduce( - (acc, item) => { - return acc - .then(_ => transformUrl(item.url, app)) - .then(fetch) - .then(r => r.blob()) - .then(b => db("tracks").setItem(item.trackId, b)) - }, - Promise.resolve() - - ).then( - _ => self.postMessage({ - tag: "STORE_TRACKS_IN_CACHE", - data: list.map(l => l.trackId), - error: null - }) - - ).catch( - err => { - console.error(err) - self.postMessage({ - tag: "STORE_TRACKS_IN_CACHE", - data: list.map(l => l.trackId), - error: err.message || err - }) - } - - ) -} diff --git a/src/Javascript/Brain/ui.ts b/src/Javascript/Brain/ui.ts deleted file mode 100644 index 46c104f44..000000000 --- a/src/Javascript/Brain/ui.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { App } from "./elm/types" -import * as Artwork from "./artwork" - - -export function link(worker: Worker, app: App) { - app.ports.toUI.subscribe(event => { - worker.postMessage(event) - }) - - worker.onmessage = event => { - if (event.data.action) return handleAction(event.data.action, event.data.data) - if (event.data.tag) return app.ports.fromAlien.send(event.data) - } - - - function handleAction(action: string, data: unknown) { - switch (action) { - case "DOWNLOAD_ARTWORK": return Artwork.download(data) - } - } -} diff --git a/src/Javascript/Brain/user.ts b/src/Javascript/Brain/user.ts deleted file mode 100644 index ae6ffaf37..000000000 --- a/src/Javascript/Brain/user.ts +++ /dev/null @@ -1,185 +0,0 @@ -// -// User -// (ใฅ๏ฝกโ—•โ€ฟโ€ฟโ—•๏ฝก)ใฅ -// -// Related to the user layer. - - -// @ts-ignore -import * as TaskPort from "elm-taskport" - -import type { App } from "./elm/types" - -import * as crypto from "../crypto" - -import { decryptIfNeeded, encryptIfPossible, SECRET_KEY_LOCATION } from "./common" -import { parseJsonIfNeeded, toCache } from "./common" - - -const ports: Record = {} -const taskPorts: Record = {} - - -// Crypto -// ====== - -taskPorts.fabricateSecretKey = async passphrase => { - const data = await crypto.keyFromPassphrase(passphrase) - return toCache(SECRET_KEY_LOCATION, data) -} - - - -// Dropbox -// ------- - -taskPorts.fromDropbox = ({ fileName, token }) => { - return fetch("https://content.dropboxapi.com/2/files/download", { - method: "POST", - headers: { - "Authorization": "Bearer " + token, - "Dropbox-API-Arg": JSON.stringify({ path: "/" + fileName }) - } - }) - .then(r => r.ok ? r.text() : r.json()) - .then(r => r.error ? null : r) - .then(decryptIfNeeded) - .then(parseJsonIfNeeded) -} - - -taskPorts.toDropbox = async ({ fileName, data, token }) => { - const json = JSON.stringify(data) - const params = { - path: "/" + fileName, - mode: "overwrite", - mute: true - } - - return fetch("https://content.dropboxapi.com/2/files/upload", { - method: "POST", - headers: { - "Authorization": "Bearer " + token, - "Content-Type": "application/octet-stream", - "Dropbox-API-Arg": JSON.stringify(params) - }, - body: await encryptIfPossible(json) - }) -} - - - -// IPFS -// ---- - -const IPFS_ROOT = "/Applications/Diffuse/" - - -taskPorts.fromIpfs = ({ apiOrigin, fileName }) => { - const path = IPFS_ROOT + fileName - - return fetch(apiOrigin + "/api/v0/files/read?arg=" + encodeURIComponent(path), { method: "POST" }) - .then(r => r.ok ? r.text() : r.json()) - .then(r => r.Code === 0 ? null : r) - .then(decryptIfNeeded) - .then(parseJsonIfNeeded) -} - - -taskPorts.toIpfs = ({ apiOrigin, fileName, data }) => { - const json = JSON.stringify(data) - const params = new URLSearchParams({ - arg: IPFS_ROOT + fileName, - create: "true", - offset: "0", - parents: "true", - truncate: "true" - }).toString() - - return encryptIfPossible(json).then(possiblyEncryptedData => { - const formData = new FormData() - - formData.append("data", possiblyEncryptedData) - - return fetch( - apiOrigin + "/api/v0/files/write?" + params, - { method: "POST", body: formData } - ) - }) -} - - - -// Remote Storage -// -------------- - -let rs -let rsClient - - -async function remoteStorage(userAddress: string, token: string) { - if (!rs) { - const { default: RemoteStorage } = await import("remotestoragejs") - - rs = new RemoteStorage({ cache: false }) - rs.access.claim("diffuse", "rw") - - rsClient = rs.scope("/diffuse/") - - return new Promise(resolve => { - rs.on("connected", resolve) - rs.connect(userAddress, token) - }) - - } else { - return Promise.resolve() - - } -} - - -ports.deconstructRemoteStorage = _app => _ => { - rs = null - rsClient = null -} - - -taskPorts.fromRemoteStorage = ({ fileName, userAddress, token }) => { - return remoteStorage(userAddress, token) - .then(_ => rsClient.getFile(fileName)) - .then(r => r.data) - .then(decryptIfNeeded) - .then(parseJsonIfNeeded) -} - - -taskPorts.toRemoteStorage = ({ data, fileName, userAddress, token }) => { - const json = JSON.stringify(data) - - return remoteStorage(userAddress, token) - .then(_ => encryptIfPossible(json)) - .then(data => rsClient.storeFile("application/json", fileName, data)) -} - - - -// EXPORT -// ====== - -function registerPorts(app: App) { - Object.keys(ports).forEach(name => { - const fn = ports[ name ](app) - app.ports[ name ].subscribe(fn) - }) -} - -function registerTaskPorts() { - Object.keys(taskPorts).forEach(name => { - const fn = taskPorts[ name ] - TaskPort.register(name, fn) - }) -} - - -export const TaskPorts = { register: registerTaskPorts } -export const Ports = { register: registerPorts } diff --git a/src/Javascript/UI/application.ts b/src/Javascript/UI/application.ts deleted file mode 100644 index 58acdd26d..000000000 --- a/src/Javascript/UI/application.ts +++ /dev/null @@ -1,106 +0,0 @@ -import "./index.d" -import type { App } from "./elm/types" -import { version } from "../../../package.json" - - -// ๐Ÿ”๏ธ - - -let app: App -let channel: BroadcastChannel - - - -// ๐Ÿš€ - - -export const load = ({ isNativeWrapper, reg }: { isNativeWrapper: boolean, reg: ServiceWorkerRegistration }) => Elm.UI.init({ - node: document.getElementById("elm") || undefined, - flags: { - buildTimestamp: BUILD_TIMESTAMP, - darkMode: preferredColorScheme().matches, - initialTime: Date.now(), - isInstallingServiceWorker: !!reg.installing, - isOnline: navigator.onLine, - isTauri: isNativeWrapper, - version, - viewport: { - height: window.innerHeight, - width: window.innerWidth - } - } -}) - - -export function init(a: App, c: BroadcastChannel) { - app = a - channel = c - - app.ports.downloadJsonUsingTauri.subscribe(downloadJsonUsingTauri) - app.ports.openUrlOnNewPage.subscribe(openUrlOnNewPage) - app.ports.reloadApp.subscribe(reloadApp) -} - - - -// ๐ŸŒ— - - -function preferredColorScheme() { - const m = - window.matchMedia && - window.matchMedia("(prefers-color-scheme: dark)") - - m?.addEventListener("change", e => { - app.ports.preferredColorSchemaChanged.send({ dark: e.matches }) - }) - - return m -} - - - -// PORTS - - -async function downloadJsonUsingTauri( - { filename, json }: { filename: string, json: string } -) { - const { save } = await import("@tauri-apps/plugin-dialog") - const { writeTextFile } = await import("@tauri-apps/plugin-fs") - const { BaseDirectory } = await import("@tauri-apps/api/path") - - const filePath = await save({ defaultPath: filename }) - await writeTextFile(filePath || filename, json, { baseDir: BaseDirectory.Download }) -} - - -function openUrlOnNewPage(url: string) { - if (globalThis.__TAURI__) { - globalThis.__TAURI__.shell.open( - url.includes("://") ? url : `${location.origin}/${url.replace(/^\.\//, "")}` - ) - - } else { - window.open(url, "_blank") - - } -} - - -function reloadApp() { - const timeout = setTimeout(async () => { - const reg = await navigator.serviceWorker.getRegistration() - if (reg?.waiting) reg.waiting.postMessage("skipWaiting") - window.location.reload() - }, 250) - - channel.addEventListener("message", event => { - if (event.data === "PONG") { - clearTimeout(timeout) - alert("โš ๏ธ You can only update the app when you have no more than one instance open.") - } - }) - - channel.postMessage("PING") -} diff --git a/src/Javascript/UI/artwork.ts b/src/Javascript/UI/artwork.ts deleted file mode 100644 index 0c5b35127..000000000 --- a/src/Javascript/UI/artwork.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { debounce } from "throttle-debounce" - -import type { App } from "./elm/types" -import { type CoverPrep, db } from "../common" - - - -// ๐Ÿ”๏ธ - - -let app: App -let brain: Worker - - - -// ๐Ÿš€ - - -export function init(a: App, b: Worker) { - app = a - brain = b - - app.ports.loadAlbumCovers.subscribe( - debounce(500, loadAlbumCoversFromDom) - ) - - db().keys().then(cachedCovers) -} - - - -// ๐Ÿ› ๏ธ - - -export function albumCover(coverKey: string): Promise { - return db().getItem(`coverCache.${coverKey}`) -} - - -async function loadAlbumCoversFromDom({ coverView, list }: { coverView: boolean, list: boolean }): Promise { - let nodes: HTMLElement[] = [] - - if (list) nodes = nodes.concat(Array.from( - document.querySelectorAll("#diffuse__track-covers [data-key]") - )) - - if (coverView) nodes = nodes.concat(Array.from( - document.querySelectorAll("#diffuse__track-covers + div [data-key]") - )) - - if (!nodes.length) return; - - const coverPrepList = nodes.reduce((acc: CoverPrep[], node: HTMLElement) => { - const a = { - cacheKey: node.getAttribute("data-key"), - trackFilename: node.getAttribute("data-filename"), - trackPath: node.getAttribute("data-path"), - trackSourceId: node.getAttribute("data-source-id"), - variousArtists: node.getAttribute("data-various-artists") - } - - if (a.cacheKey && a.trackFilename && a.trackPath && a.trackSourceId && a.variousArtists) { - return [...acc, a as CoverPrep] - } else { - return acc - } - }, [] as CoverPrep[]) - - return loadAlbumCovers(coverPrepList) -} - - -export async function loadAlbumCovers(coverPrepList: CoverPrep[]): Promise { - const withoutEarlierAttempts = await coverPrepList.reduce(async ( - acc: Promise, - prep: CoverPrep - ): Promise => { - const arr = await acc - const a = await albumCover(prep.cacheKey) - if (!a) return [...arr, prep] - return arr - }, Promise.resolve([])) - - brain.postMessage({ - action: "DOWNLOAD_ARTWORK", - data: withoutEarlierAttempts - }) -} - - -// Send a dictionary of the cached covers to the app. -async function cachedCovers(keys: string[]) { - const cacheKeys = keys.filter( - k => k.startsWith("coverCache.") - ) - - const cache = await cacheKeys.reduce(async (acc, key) => { - const c = await acc - const blob = await db().getItem(key) - const cacheKey = key.slice(11) - - if (blob && typeof blob !== "string" && blob instanceof Blob) { - c[cacheKey] = URL.createObjectURL(blob) - } - - return c - }, Promise.resolve({})) - - app.ports.insertCoverCache.send(cache) - setTimeout(() => loadAlbumCoversFromDom({ list: true, coverView: true }), 500) -} diff --git a/src/Javascript/UI/audio.ts b/src/Javascript/UI/audio.ts deleted file mode 100644 index 8576176a8..000000000 --- a/src/Javascript/UI/audio.ts +++ /dev/null @@ -1,533 +0,0 @@ -// -// Audio engine -// โ™ช(ยดฮต๏ฝ€ ) - -import type { App } from "./elm/types" - -import Timer from "timer.js" -import { debounce } from "throttle-debounce" -import { CoverPrep, db, mimeType } from "../common" -import { albumCover, loadAlbumCovers } from "./artwork" -import { transformUrl } from "../urls" - - -// ๐Ÿ”๏ธ - - -const silentMp3File = - "data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjM2LjEwMAAAAAAAAAAAAAAA//OEAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAEAAABIADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV6urq6urq6urq6urq6urq6urq6urq6urq6v////////////////////////////////8AAAAATGF2YzU2LjQxAAAAAAAAAAAAAAAAJAAAAAAAAAAAASDs90hvAAAAAAAAAAAAAAAAAAAA//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAMAAAGkAAAAAAAAA0gAAAAARTMu//MUZAYAAAGkAAAAAAAAA0gAAAAAOTku//MUZAkAAAGkAAAAAAAAA0gAAAAANVVV" - - -let app: App -let container: Element | null = null -let scrobbleTimer: Timer | null = null - - - -// ๐Ÿš€ - - -export function init(a: App) { - app = a - - app.ports.adjustEqualizerSetting.subscribe(adjustEqualizerSetting) - app.ports.pause.subscribe(pause) - app.ports.pauseScrobbleTimer.subscribe(pauseScrobbleTimer) - app.ports.play.subscribe(play) - app.ports.reloadAudioNodeIfNeeded.subscribe(reloadAudioNodeIfNeeded) - app.ports.renderAudioElements.subscribe(renderAudioElements) - app.ports.resetScrobbleTimer.subscribe(resetScrobbleTimer) - app.ports.seek.subscribe(seek) - app.ports.setMediaSessionArtwork.subscribe(setMediaSessionArtwork) - app.ports.setMediaSessionMetadata.subscribe(setMediaSessionMetadata) - app.ports.setMediaSessionPlaybackState.subscribe(setMediaSessionPlaybackState) - app.ports.setMediaSessionPositionState.subscribe(setMediaSessionPositionState) - app.ports.startScrobbleTimer.subscribe(startScrobbleTimer) -} - - - -// ๐ŸŒณ - - -/** - * Javascript representation of `Queue.EngineItem` in Elm. - */ -type EngineItem = { - isCached: boolean - isPreload: boolean - progress: number | null - sourceId: string - trackId: string - trackTags: TrackTags - trackPath: string - url: string -} - - -/**/ -type TrackTags = { - disc: number - nr: number - - // Main - album: string | null - artist: string | null - title: string - - // Extra - genre: string | null - picture: string | null - year: number | null -} - - - -// Ports -// ----- - - -function adjustEqualizerSetting({ knob, value }: { knob: string, value: number }): void { - switch (knob) { - case "VOLUME": - Array.from( - document.body.querySelectorAll('#audio-elements audio[data-is-preload="false"]'), - ).forEach((audio) => ((audio as HTMLAudioElement).volume = value)) - break - } -} - - -function pause({ trackId }: { trackId: string }) { - withAudioNode(trackId, (audio) => audio.pause()) -} - - -function pauseScrobbleTimer() { - if (scrobbleTimer) scrobbleTimer.pause() -} - - -function play({ trackId, volume }: { trackId: string, volume: number }) { - withAudioNode(trackId, (audio) => { - audio.volume = volume - audio.muted = false - - if (audio.readyState === 0) audio.load() - if (!audio.isConnected) return - - const promise = audio.play() || Promise.resolve() - const didPreload = audio.getAttribute("data-did-preload") === "true" - const isPreload = audio.getAttribute("data-is-preload") === "true" - - if (didPreload && !isPreload) { - audio.removeAttribute("data-did-preload") - app.ports.audioDurationChange.send({ - trackId: audio.id, - duration: audio.duration, - }) - } - - promise.catch((e) => { - if (!audio.isConnected) return /* The node was removed from the DOM, we can ignore this error */ - const err = "Couldn't play audio automatically. Please resume playback manually." - console.error(err, e) - if (app) app.ports.fromAlien.send({ tag: "", data: null, error: err }) - }) - }) -} - - -async function reloadAudioNodeIfNeeded(args: { play: boolean, progress: number | null, trackId: string }) { - withAudioNode(args.trackId, (audio) => { - if (audio.readyState === 0 || audio.error?.code === 2) { - audio.load() - - if (args.progress) { - audio.setAttribute("data-initial-progress", JSON.stringify(args.progress)) - } - - if (args.play) { - play({ trackId: args.trackId, volume: audio.volume }) - } - } - }) -} - - -async function renderAudioElements(args: { - items: Array - play: string | null - volume: number -}) { - await render(args.items) - if (args.play) play({ trackId: args.play, volume: args.volume }) -} - - -function resetScrobbleTimer({ duration, trackId }: { duration: number, trackId: string }) { - const timestamp = Math.round(Date.now() / 1000) - const scrobbleTimeoutDuration = Math.min(240 + 0.5, duration / 1.95) - - if (scrobbleTimer) scrobbleTimer.stop() - - scrobbleTimer = new Timer({ - onend: () => { - scrobbleTimer = undefined - app.ports.scrobble.send({ - duration: Math.round(duration), - timestamp, - trackId, - }) - } - }) - - scrobbleTimer.start(scrobbleTimeoutDuration) -} - - -function seek({ percentage, trackId }: { percentage: number, trackId: string }) { - withAudioNode(trackId, (audio) => { - if (!isNaN(audio.duration)) { - audio.currentTime = audio.duration * percentage - } - }) -} - - -async function setMediaSessionArtwork({ blobUrl, imageType }: { blobUrl: string, imageType: string }) { - const artwork: MediaImage[] = [{ - src: blobUrl, - type: imageType - }] - - navigator.mediaSession.metadata = new MediaMetadata({ - title: navigator.mediaSession.metadata?.title, - artist: navigator.mediaSession.metadata?.artist, - album: navigator.mediaSession.metadata?.album, - artwork: artwork, - }) -} - - -async function setMediaSessionMetadata({ - album, - artist, - title, - - coverPrep, -}: { - album: string | null - artist: string | null - title: string - - coverPrep: CoverPrep | null -}) { - let artwork: MediaImage[] = [] - - if (coverPrep) { - const blob = await albumCover(coverPrep.cacheKey) - - artwork = blob && typeof blob !== "string" - ? [{ - src: URL.createObjectURL(blob), - type: blob.type - }] - : [] - - if (!blob) { - // Download artwork and set it later - loadAlbumCovers([coverPrep]) - } - } - - navigator.mediaSession.metadata = new MediaMetadata({ - title, - artist: artist || undefined, - album: album || undefined, - artwork: artwork, - }) -} - - -function setMediaSessionPlaybackState(state: MediaSessionPlaybackState) { - if (navigator.mediaSession) navigator.mediaSession.playbackState = state -} - - -function setMediaSessionPositionState({ - currentTime, - duration, -}: { - currentTime: number - duration: number -}) { - try { - navigator?.mediaSession?.setPositionState({ - duration: duration, - position: currentTime, - }) - } catch (_err) { - // - } -} - - -function startScrobbleTimer() { - if (scrobbleTimer) scrobbleTimer.start() -} - - - -// Media Keys -// ---------- - - -if ("mediaSession" in navigator) { - navigator.mediaSession.setActionHandler("play", () => { - app.ports.requestPlay.send(null) - }) - - navigator.mediaSession.setActionHandler("pause", () => { - app.ports.requestPause.send(null) - }) - - navigator.mediaSession.setActionHandler("previoustrack", () => { - app.ports.requestPrevious.send(null) - }) - - navigator.mediaSession.setActionHandler("nexttrack", () => { - app.ports.requestNext.send(null) - }) - - navigator.mediaSession.setActionHandler("seekbackward", (event) => { - const seekOffset = event.seekOffset || 10 - withActiveAudioNode( - (audio) => (audio.currentTime = Math.max(audio.currentTime - seekOffset, 0)), - ) - }) - - navigator.mediaSession.setActionHandler("seekforward", (event) => { - const seekOffset = event.seekOffset || 10 - withActiveAudioNode( - (audio) => (audio.currentTime = Math.min(audio.currentTime + seekOffset, audio.duration)), - ) - }) - - navigator.mediaSession.setActionHandler("seekto", (event) => { - withActiveAudioNode((audio) => (audio.currentTime = event.seekTime || audio.currentTime)) - }) -} - - - -// ๐Ÿ–ผ๏ธ - - -async function render(items: Array) { - if (!container) { - container = document.createElement("div") - container.id = "audio-elements" - container.className = "absolute h-0 invisible left-0 pointer-events-none top-0 w-0" - - document.body.appendChild(container) - } - - const trackIds = items.map((e) => e.trackId) - const existingNodes = {} - - // Manage existing nodes - Array.from(container.querySelectorAll("audio")).map((node: HTMLAudioElement) => { - if (trackIds.includes(node.id)) { - existingNodes[node.id] = node - } else { - node.src = silentMp3File - container?.removeChild(node) - } - }) - - // Adjust existing and add new - await items.reduce(async (acc: Promise, item: EngineItem) => { - await acc - - const existingNode = existingNodes[item.trackId] - - if (existingNode) { - const isPreload = existingNode.getAttribute("data-is-preload") - if (isPreload === "true") existingNode.setAttribute("data-did-preload", "true") - - existingNode.setAttribute( - "data-is-preload", - item.isPreload ? "true" : "false", - ) - } else { - await createElement(item) - } - }, Promise.resolve()) -} - - -export async function createElement(item: EngineItem) { - const url = item.isCached - ? await db("tracks") - .getItem(item.trackId) - .then((blob) => (blob ? URL.createObjectURL(blob as Blob) : item.url)) - : await transformUrl(item.url, app) - - // Mime + SRC - const fileName = item.trackPath.split("/").reverse()[0] - const fileExtMatch = fileName.match(/\.(\w+)$/) - const fileExt = fileExtMatch && fileExtMatch[1] - const mime = fileExt ? mimeType(fileExt) : null - - const source = document.createElement("source") - if (mime) source.setAttribute("type", mime) - source.setAttribute("src", url) - - // Audio node - const audio = new Audio() - audio.setAttribute("id", item.trackId) - audio.setAttribute("crossorigin", "anonymous") - audio.setAttribute("data-initial-progress", JSON.stringify(item.progress)) - audio.setAttribute("data-is-preload", item.isPreload ? "true" : "false") - audio.setAttribute("muted", "true") - audio.setAttribute("preload", "auto") - audio.appendChild(source) - - audio.addEventListener("canplay", canplayEvent) - audio.addEventListener("durationchange", durationchangeEvent) - audio.addEventListener("ended", endedEvent) - audio.addEventListener("error", errorEvent) - audio.addEventListener("pause", pauseEvent) - audio.addEventListener("play", playEvent) - audio.addEventListener("suspend", suspendEvent) - audio.addEventListener("timeupdate", timeupdateEvent) - audio.addEventListener("waiting", debounce(1500, waitingEvent)) - - container?.appendChild(audio) -} - - - -// ๐Ÿ–ผ โ–‘โ–‘ EVENTS - - -function canplayEvent(event: Event) { - const target = event.target as HTMLAudioElement - - if (target.hasAttribute("data-initial-progress") && target.duration && !isNaN(target.duration)) { - const progress = JSON.parse(target.getAttribute("data-initial-progress") as string) - target.currentTime = target.duration * progress - target.removeAttribute("data-initial-progress") - } - - finishedLoading(event) -} - - -function durationchangeEvent(event: Event) { - const target = event.target as HTMLAudioElement - - if (!isNaN(target.duration)) { - app.ports.audioDurationChange.send({ - trackId: target.id, - duration: target.duration, - }) - } -} - -function endedEvent(event: Event) { - app.ports.audioEnded.send({ - trackId: (event.target as HTMLAudioElement).id, - }) -} - -function errorEvent(event: Event) { - const audio = event.target as HTMLAudioElement - - app.ports.audioError.send({ - trackId: audio.id, - code: audio.error?.code || 0 - }) -} - - -function pauseEvent(event: Event) { - app.ports.audioPlaybackStateChanged.send({ - trackId: (event.target as HTMLAudioElement).id, - isPlaying: false, - }) -} - - -function playEvent(event: Event) { - const audio = event.target as HTMLAudioElement - - app.ports.audioPlaybackStateChanged.send({ - trackId: audio.id, - isPlaying: true, - }) - - // In case audio was preloaded: - if (audio.readyState === 4) finishedLoading(event) -} - - -function suspendEvent(event: Event) { - finishedLoading(event) -} - - -function timeupdateEvent(event: Event) { - const target = event.target as HTMLAudioElement - - app.ports.audioTimeUpdated.send({ - trackId: target.id, - currentTime: target.currentTime, - duration: isNaN(target.duration) ? null : target.duration, - }) -} - - -function waitingEvent(event: Event) { - initiateLoading(event) -} - - - -// ๐Ÿ› ๏ธ - - -function finishedLoading(event: Event) { - app.ports.audioHasLoaded.send({ - trackId: (event.target as HTMLAudioElement).id, - }) -} - - -function initiateLoading(event: Event) { - const audio = event.target as HTMLAudioElement - - if (audio.readyState < 4) - app.ports.audioIsLoading.send({ - trackId: audio.id, - }) -} - - -function withActiveAudioNode(fn: (node: HTMLAudioElement) => void): void { - const nonPreloadNodes: HTMLAudioElement[] = Array.from( - document.body.querySelectorAll(`#audio-elements audio[data-is-preload="false"]`), - ) - const playingNodes = nonPreloadNodes.filter((n) => n.paused === false) - const node = playingNodes.length ? playingNodes[0] : nonPreloadNodes[0] - if (node) fn(node) -} - - -function withAudioNode(trackId: string, fn: (node: HTMLAudioElement) => void): void { - const node = document.body.querySelector( - `#audio-elements audio[id="${trackId}"][data-is-preload="false"]`, - ) - if (node) fn(node as HTMLAudioElement) -} diff --git a/src/Javascript/UI/backdrop.ts b/src/Javascript/UI/backdrop.ts deleted file mode 100644 index ee5daff70..000000000 --- a/src/Javascript/UI/backdrop.ts +++ /dev/null @@ -1,46 +0,0 @@ -// ๐Ÿš€ - - -export function init(app) { - app.ports.pickAverageBackgroundColor.subscribe((src: string) => { - const avgColor = pickAverageBackgroundColor(src) - if (avgColor) app.ports.setAverageBackgroundColor.send(avgColor) - }) -} - - - -// ๐Ÿ› ๏ธ - - -function averageColorOfImage(img: HTMLImageElement): { r: number, g: number, b: number } | null { - const canvas = document.createElement("canvas") - const ctx = canvas.getContext("2d") - canvas.width = img.naturalWidth - canvas.height = img.naturalHeight - - if (!ctx) return null - - ctx.drawImage(img, 0, 0) - - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) - const color = { r: 0, g: 0, b: 0 } - - for (let i = 0, l = imageData.data.length; i < l; i += 4) { - color.r += imageData.data[i] - color.g += imageData.data[i + 1] - color.b += imageData.data[i + 2] - } - - color.r = Math.floor(color.r / (imageData.data.length / 4)) - color.g = Math.floor(color.g / (imageData.data.length / 4)) - color.b = Math.floor(color.b / (imageData.data.length / 4)) - - return color -} - - -function pickAverageBackgroundColor(src: string): { r: number, g: number, b: number } | null { - const img = document.querySelector(`img[src$="${src}"]`) - return img ? averageColorOfImage(img as HTMLImageElement) : null -} diff --git a/src/Javascript/UI/brain.ts b/src/Javascript/UI/brain.ts deleted file mode 100644 index bfb6efa2e..000000000 --- a/src/Javascript/UI/brain.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { App } from "./elm/types" -import * as Tracks from "./tracks" - - -export async function load(): Promise { - const brain = new Worker( - "./js/brain/index.js#appHref=" + encodeURIComponent(window.location.href), - { type: "module" } - ) - - await new Promise((resolve, reject) => { - brain.onmessage = event => { - if (event.data.action === "READY") resolve(null) - } - - brain.addEventListener("error", () => { - reject("Failed to load web worker.
If you're using Firefox, you might need to upgrade your browser (version 113 and up) and set `dom.workers.modules.enabled` to `true` in `about:config`") - }) - }) - - // Fin - return brain -} - - -export function link({ app, brain }: { app: App, brain: Worker }) { - function handleAction(action, data, _ports) { - switch (action) { - case "DOWNLOAD_TRACKS": return Tracks.download(data) - } - } - - brain.onmessage = event => { - if (event.data.action) return handleAction(event.data.action, event.data.data, event.ports) - if (event.data.tag) app.ports.fromAlien.send(event.data) - } - - app.ports.toBrain.subscribe(a => brain.postMessage(a)) -} diff --git a/src/Javascript/UI/broadcast.ts b/src/Javascript/UI/broadcast.ts deleted file mode 100644 index 5e4be2118..000000000 --- a/src/Javascript/UI/broadcast.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function channel() { - const bc = new BroadcastChannel(`diffuse-${location.hostname}`) - - bc.addEventListener("message", event => { - switch (event.data) { - case "PING": return bc.postMessage("PONG") - } - }) - - return bc -} diff --git a/src/Javascript/UI/elm/types.ts b/src/Javascript/UI/elm/types.ts deleted file mode 100644 index d4ab9a503..000000000 --- a/src/Javascript/UI/elm/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type App = any // TODO: ElmApp - - -export type ElmPorts = { - // โ† Elm - openUrlOnNewPage: PortFromElm - - // โ†’ Elm - fromAlien: PortToElm - indicateTouchDevice: PortToElm -} diff --git a/src/Javascript/UI/errors.ts b/src/Javascript/UI/errors.ts deleted file mode 100644 index b15472cd4..000000000 --- a/src/Javascript/UI/errors.ts +++ /dev/null @@ -1,20 +0,0 @@ -export function failure(text: string): void { - const note = document.createElement("div") - - note.className = "flex flex-col font-body items-center h-screen italic justify-center leading-relaxed px-4 text-center text-base text-white" - note.innerHTML = ` - - -

- ${text} -

- ` - - document.body.appendChild(note) - - // Remove loader - const elm = document.querySelector("#elm") - elm?.parentNode?.removeChild(elm) -} diff --git a/src/Javascript/UI/index.d.ts b/src/Javascript/UI/index.d.ts deleted file mode 100644 index 6e505a797..000000000 --- a/src/Javascript/UI/index.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ElmPorts } from "./elm/types" - -export { } - -declare global { - const BUILD_TIMESTAMP: string - - const Elm: { UI: ElmMain } - const tocca: any -} diff --git a/src/Javascript/UI/index.ts b/src/Javascript/UI/index.ts deleted file mode 100644 index a725f3d09..000000000 --- a/src/Javascript/UI/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -// -// | (โ€ข โ—กโ€ข)| (โแดฅโส‹) -// -// The bit where we launch the Elm apps & workers, -// and connect the other bits to it. - -import "./pointer-events" - -import * as Application from "./application" -import * as Artwork from "./artwork" -import * as Audio from "./audio" -import * as Backdrop from "./backdrop" -import * as Brain from "./brain" -import * as Broadcast from "./broadcast" -import * as Errors from "./errors" -import * as Misc from "./misc" -import * as ServiceWorker from "./service-worker" -import * as Tracks from "./tracks" - - - -// ๐ŸŒธ - - -const isNativeWrapper = !!globalThis.__TAURI__ - - - -// ๐Ÿš€ - - -ServiceWorker - .load({ isNativeWrapper }) - .then(async (reg: ServiceWorkerRegistration) => { - const brain = await Brain.load() - const app = Application.load({ isNativeWrapper, reg }) - const channel = Broadcast.channel() - - // ๐Ÿง‘โ€๐Ÿญ - ServiceWorker.link({ - app, isNativeWrapper, reg - }) - - // ๐Ÿง  - Brain.link({ - app, brain - }) - - // โšก - Application.init(app, channel) - Artwork.init(app, brain) - Audio.init(app) - Backdrop.init(app) - Misc.init(app) - Tracks.init(app) - }) - .catch( - Errors.failure - ) diff --git a/src/Javascript/UI/misc.ts b/src/Javascript/UI/misc.ts deleted file mode 100644 index 6da919e64..000000000 --- a/src/Javascript/UI/misc.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { App } from "./elm/types" - - -// ๐Ÿ”๏ธ - - -let app: App - - - -// ๐Ÿš€ - - -export function init(a: App) { - app = a - - app.ports.copyToClipboard.subscribe(copyToClipboard) -} - - - -// Clipboard -// --------- - - -async function copyToClipboard(text: string) { - navigator.clipboard.writeText(text) -} - - - -// Focus -// ----- - -window.addEventListener("blur", event => { - if (app && event.target === window) app.ports.lostWindowFocus.send(null) -}) - - - -// Internet Connection -// ------------------- - -window.addEventListener("online", onlineStatusChanged) -window.addEventListener("offline", onlineStatusChanged) - - -function onlineStatusChanged() { - if (app) app.ports.setIsOnline.send(navigator.onLine) -} - - - -// Touch Device -// ------------ - -window.addEventListener("touchstart", function onFirstTouch() { - if (!app) return - app.ports.indicateTouchDevice.send() - window.removeEventListener("touchstart", onFirstTouch, false) -}, false) diff --git a/src/Javascript/UI/pointer-events.ts b/src/Javascript/UI/pointer-events.ts deleted file mode 100644 index 0a1522117..000000000 --- a/src/Javascript/UI/pointer-events.ts +++ /dev/null @@ -1,116 +0,0 @@ -import "./index.d" -import "tocca" - -// Pointer Events -// -------------- -// Thanks to https://github.com/mpizenberg/elm-pep/ - -let enteredElement - - -tocca({ - dbltapThreshold: 400, - tapThreshold: 250 -}) - - -function mousePointerEvent(eventType, mouseEvent) { - const pointerEvent: any = new MouseEvent(eventType, mouseEvent) - pointerEvent.pointerId = 1 - pointerEvent.isPrimary = true - pointerEvent.pointerType = "mouse" - pointerEvent.width = 1 - pointerEvent.height = 1 - pointerEvent.tiltX = 0 - pointerEvent.tiltY = 0 - - "buttons" in mouseEvent && mouseEvent.buttons !== 0 - ? (pointerEvent.pressure = 0.5) - : (pointerEvent.pressure = 0) - - return pointerEvent -} - - -function touchPointerEvent(eventType, touchEvent, touch) { - const pointerEvent: any = new CustomEvent(eventType, { - bubbles: true, - cancelable: true - }) - - pointerEvent.ctrlKey = touchEvent.ctrlKey - pointerEvent.shiftKey = touchEvent.shiftKey - pointerEvent.altKey = touchEvent.altKey - pointerEvent.metaKey = touchEvent.metaKey - - pointerEvent.clientX = touch.clientX - pointerEvent.clientY = touch.clientY - pointerEvent.screenX = touch.screenX - pointerEvent.screenY = touch.screenY - pointerEvent.pageX = touch.pageX - pointerEvent.pageY = touch.pageY - - const rect = touch.target.getBoundingClientRect() - pointerEvent.offsetX = touch.clientX - rect.left - pointerEvent.offsetY = touch.clientY - rect.top - pointerEvent.pointerId = 1 + touch.identifier - - pointerEvent.button = 0 - pointerEvent.buttons = 1 - pointerEvent.movementX = 0 - pointerEvent.movementY = 0 - pointerEvent.region = null - pointerEvent.relatedTarget = null - pointerEvent.x = pointerEvent.clientX - pointerEvent.y = pointerEvent.clientY - - pointerEvent.pointerType = "touch" - pointerEvent.width = 1 - pointerEvent.height = 1 - pointerEvent.tiltX = 0 - pointerEvent.tiltY = 0 - pointerEvent.pressure = 1 - pointerEvent.isPrimary = true - - return pointerEvent -} - - -// Simulate `pointerenter` and `pointerleave` event for non-touch devices -if (!self.PointerEvent) { - document.addEventListener("mouseover", event => { - const section = document.body.querySelector("section") - const isDragging = section && section.classList.contains("dragging-something") - const node = isDragging && document.elementFromPoint(event.clientX, event.clientY) - - if (node && node != enteredElement) { - enteredElement && enteredElement.dispatchEvent(mousePointerEvent("pointerleave", event)) - node.dispatchEvent(mousePointerEvent("pointerenter", event)) - enteredElement = node - } - }) -} - - -// Simulate `pointerenter` and `pointerleave` event for touch devices -document.body.addEventListener("touchmove", event => { - const section = document.body.querySelector("section") - const isDragging = section && section.classList.contains("dragging-something") - const touch = event.touches[0] - - let node - - if (isDragging && touch) { - node = document.elementFromPoint(touch.clientX, touch.clientY) - } - - if (node && node != enteredElement) { - enteredElement && enteredElement.dispatchEvent(touchPointerEvent("pointerleave", event, touch)) - node.dispatchEvent(touchPointerEvent("pointerenter", event, touch)) - enteredElement = node - } - - if (isDragging) { - event.stopPropagation() - } -}) diff --git a/src/Javascript/UI/service-worker.ts b/src/Javascript/UI/service-worker.ts deleted file mode 100644 index 77f3ce0a7..000000000 --- a/src/Javascript/UI/service-worker.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { App } from "./elm/types" - - -/** - * Load: - * - * 1. Redirect to HTTPS if using the `diffuse.sh` domain (subdomains included). - * 2. Fail if not a secure context. - * 3. Set up service worker, ensure it's ready and then continue initialisation. - */ -export async function load({ isNativeWrapper } : { isNativeWrapper: boolean }): Promise { - return new Promise((resolve, reject) => { - if (location.hostname.endsWith("diffuse.sh") && location.protocol === "http:") { - location.href = location.href.replace("http://", "https://") - reject("Just a moment, redirecting to HTTPS.") - - } else if (!self.isSecureContext) { - reject(` - This app only works on a secure context, HTTPS & localhost, and modern browsers. - `) - - } else if ("serviceWorker" in navigator) { - // Service worker - window.addEventListener("load", () => { - navigator.serviceWorker - .getRegistrations() - .then(async registrations => { - const serverIsOnline = navigator.onLine && await fetch(`${location.origin}?ping=1`) - .then(r => r.text()) - .then(a => a === "false" ? false : true) - - if (isNativeWrapper) await Promise.all( - registrations.map(r => r.unregister()) - ) - - if (serverIsOnline) return navigator.serviceWorker.register( - "service-worker.js", - // { type: "module" } - ) - - if (registrations[0]) return registrations - - throw new Error("Web server is offline") - }) - .then(() => navigator.serviceWorker.ready) - .then(resolve) - .catch(err => { - const isFirefox = navigator.userAgent.toLowerCase().includes("firefox") - - console.error(err) - return reject( - location.protocol === "https:" || location.hostname === "localhost" - ? "Failed to start the service worker." + (isFirefox ? " Make sure the setting Delete cookies and site data when Firefox is closed is off, or Diffuse's domain is added as an exception." : "") - : "Failed to start the service worker, try using HTTPS." - ) - }) - }) - - } - }) -} - - -/** - * Link. - */ -export function link( - { app, isNativeWrapper, reg } : { app: App, isNativeWrapper: boolean, reg: ServiceWorkerRegistration } -) { - if (reg.installing) console.log("๐Ÿง‘โ€โœˆ๏ธ Service worker is installing") - const initialInstall = reg.installing - - initialInstall?.addEventListener("statechange", function() { - if (this.state === "activated") { - console.log("๐Ÿง‘โ€โœˆ๏ธ Service worker is activated") - app.ports.installedNewServiceWorker.send(null) - } - }) - - if (reg.waiting) { - console.log("๐Ÿง‘โ€โœˆ๏ธ A new version of Diffuse is available") - app.ports.installingNewServiceWorker.send(null) - app.ports.installedNewServiceWorker.send(null) - } - - if (initialInstall?.state === "activated") { - console.log("๐Ÿง‘โ€โœˆ๏ธ Service worker is activated") - app.ports.installedNewServiceWorker.send(null) - } - - reg.addEventListener("updatefound", () => { - const newWorker = reg.installing - if (!newWorker) return - - // No worker was installed yet, so we'll only want to track the state changes - if (newWorker !== initialInstall) { - console.log("๐Ÿง‘โ€โœˆ๏ธ A new version of Diffuse is available") - app.ports.installingNewServiceWorker.send(null) - } - - newWorker.addEventListener("statechange", (e: any) => { - console.log("๐Ÿง‘โ€โœˆ๏ธ Service worker is", e.target.state) - if (e.target.state === "installed") app.ports.installedNewServiceWorker.send(null) - }) - }) - - // Check for service worker updates and every hour after that - if (!isNativeWrapper && navigator.onLine) { - reg.update() - setInterval(() => reg.update(), 1 * 1000 * 60 * 60) - } -} diff --git a/src/Javascript/UI/tracks.ts b/src/Javascript/UI/tracks.ts deleted file mode 100644 index 644f630e4..000000000 --- a/src/Javascript/UI/tracks.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { App } from "./elm/types" -import { fileExtension } from "../common" -import { transformUrl } from "../urls" - - -// ๐Ÿ”๏ธ - - -let app: App - - - -// ๐Ÿš€ - - -export function init(a: App) { - app = a -} - - - -// ๐Ÿ› ๏ธ - - -export async function download(group) { - const { saveAs } = await import("file-saver").then(a => a.default) - const JSZip = await import("jszip").then(a => a.default) - - const zip = new JSZip() - const folder = zip.folder("Diffuse - " + group.name) - if (!folder) throw new Error("Failed to create ZIP file") - - return group.tracks - .reduce((acc, track) => { - return acc - .then(() => transformUrl(track.url, app)) - .then(fetch) - .then((r: Response) => { - const mimeType = r.headers.get("content-type") - const fileExt = (mimeType ? fileExtension(mimeType) : null) || track.path.match(/\.(\w+)$/)[1] || "unknown-ext" - - return r.blob().then((b: Blob) => folder.file(track.filename + "." + fileExt, b)) - }) - }, Promise.resolve()) - .then(() => zip.generateAsync({ type: "blob" })) - .then((zipFile: Blob) => { - saveAs(zipFile, "Diffuse - " + group.name + ".zip") - app.ports.downloadTracksFinished.send(null) - }) -} diff --git a/src/Javascript/Workers/search.ts b/src/Javascript/Workers/search.ts deleted file mode 100644 index cdbc70850..000000000 --- a/src/Javascript/Workers/search.ts +++ /dev/null @@ -1,157 +0,0 @@ -// -// Search worker -// (โ—ก โ€ฟ โ—ก โœฟ) -// -// This worker is responsible for searching through a `Track` collection. - - -import lunr from "lunr" - - -const FIELDS = ["album", "artist", "title"] - - -lunr.Pipeline.registerFunction( - removeParenthesesFromToken, - "Remove parentheses from token" -) - - -let index: lunr.Index - - - -// Incoming messages -// ----------------- - -self.onmessage = (event: MessageEvent) => { - switch (event.data.action) { - case "PERFORM_SEARCH": - performSearch(event.data.data) - break - - case "UPDATE_SEARCH_INDEX": - updateSearchIndex(event.data.data) - break - } -} - - - -// Mapper -// ------ - -const mapTrack = track => ({ - id: track.id, - album: track.tags.album, - artist: track.tags.artist, - title: track.tags.title, -}) - - - -// Actions -// ------- - -function performSearch(rawSearchTerm: string) { - let results: string[] = - [] - - const searchTerm = rawSearchTerm - .replace(/-\s+/g, "-") - .replace(/\+\s+/g, "+") - .split(/ +/) - .reduce( - ([ acc, previousOperator, previousPrefix ]: [ string[], string, string ], chunk: string): [ string[], string, string ] => { - const operator = (a => a && a[0])( chunk.match(/^(\+|-)/) ) - - let chunkWithoutOperator = chunk.replace(/^(\+|-)/, "").replace(/\*$/, "").trim() - let prefix = (a => a && a[1])( chunkWithoutOperator.match(/^([^:]+:)/) ) - let chunkWithoutPrefix = chunkWithoutOperator.replace(/^([^:]+:)/, "") - - if (prefix && !FIELDS.includes(prefix.slice(0, -1))) { - prefix = null - chunkWithoutPrefix = chunkWithoutOperator.replace(":", "\\:") - chunkWithoutOperator = chunkWithoutPrefix - - } else if (prefix && chunkWithoutPrefix.includes(":")) { - chunkWithoutPrefix = chunkWithoutPrefix.replace(":", "\\:") - chunkWithoutOperator = prefix + chunkWithoutPrefix - - } - - const op = operator || previousOperator - const pr = prefix ? "" : (operator ? "" : previousPrefix) - - return chunkWithoutPrefix.trim().length > 0 - ? [ [ ...acc - , op + pr + chunkWithoutOperator - ] - , op - , prefix || pr - ] - : [ acc, previousOperator, previousPrefix ] - }, - [ [], "+", "" ] - )[0] - .join(" ") - - const searchTermWithAsteriks = - searchTerm - .split(" ") - .map(s => { - if (s.startsWith("-")) return s - return s + "*" - }) - .join(" ") - - if (index) { - results = index - .search(searchTerm) - .map(s => s.ref) - .concat( - index - .search(searchTermWithAsteriks) - .map(s => s.ref) - ) - } - - self.postMessage({ - action: "PERFORM_SEARCH", - data: results - }) -} - - -function updateSearchIndex(input: string | object[]) { - const tracks = (typeof input == "string") - ? JSON.parse(input) - : input - - index = customLunr((builder: lunr.Builder) => { - FIELDS.forEach( - field => builder.field(field) - ) - - ;(tracks || []) - .map(mapTrack) - .forEach(t => builder.add(t)) - }) -} - - - -function customLunr(fn: (b: lunr.Builder) => void) { - const builder = new lunr.Builder - - builder.pipeline.add(removeParenthesesFromToken, lunr.stemmer) - builder.searchPipeline.add(removeParenthesesFromToken, lunr.stemmer) - - fn(builder) - return builder.build() -} - - -function removeParenthesesFromToken(token: lunr.Token): lunr.Token { - return token.update(s => s.replace(/\(|\)/, "")) -} diff --git a/src/Javascript/Workers/service.ts b/src/Javascript/Workers/service.ts deleted file mode 100644 index b467b062d..000000000 --- a/src/Javascript/Workers/service.ts +++ /dev/null @@ -1,182 +0,0 @@ -// -// Service worker -// (โ—ก โ€ฟ โ—ก โœฟ) -// -// This worker is responsible for caching the application -// so it can be used offline and acts as a proxy that -// allows for example, authentication through headers -// when using audio elements. -// -/// - - -const KEY = - /* eslint-disable no-undef *//* @ts-ignore */ - `diffuse-${BUILD_TIMESTAMP}` - - -const EXCLUDE = - [ "_headers" - , "_redirects" - , "CORS" - ] - - -const GOOGLE_DRIVE = "https://www.googleapis.com/drive/" - - - -// ๐Ÿ™ˆ - - -const isNativeWrapper = location.host === "localhost:44999" || location.host === "127.0.0.1:44999" -let googleDriveToken - - - -// ๐Ÿ“ฃ - - -self.addEventListener("activate", () => { - // Remove all caches except the one with the currently used `KEY` - caches.keys().then(keys => { - keys.forEach(k => { - if (k !== KEY) caches.delete(k) - }) - }) -}) - - -self.addEventListener("install", event => { - if (isNativeWrapper) { - return globalThis.skipWaiting() - } - - const href = self.location.href.replace("service-worker.js", "") - const promise = fetch("tree.json") - .then(response => response.json()) - .then(tree => { - const filteredTree = tree - .filter(t => !EXCLUDE.find(u => u === t)) - .filter(u => u.endsWith(".jpg")) - const whatToCache = [ href, `${href.replace(/\/+$/, "")}/about/` ].concat(filteredTree) - return caches.open(KEY).then(c => Promise.all(whatToCache.map(x => c.add(x)))) - }) - - event.waitUntil(promise) -}) - - -self.addEventListener("fetch", fetchEvent => { - const event = fetchEvent as FetchEvent - - const isInternal = - !!event.request.url.match(new RegExp("^" + self.location.origin)) - - // Ping - if (event.request.url.includes("?ping=1")) { - event.respondWith( - (async () => { - const serverIsOnline = await network(event).then(_ => true).catch(_ => false) - return new Response(JSON.stringify(serverIsOnline), { - headers: { "Content-Type": "application/json" } - }) - })() - ) - - // When doing a request with basic authentication in the url, put it in the headers instead - } else if (event.request.url.includes("basic_auth=")) { - const url = new URL(event.request.url) - const token = url.searchParams.get("basic_auth") - - event.respondWith(newRequestWithAuth( - event.request, - url.toString(), - "Basic " + token - )) - - // When doing a request with access token in the url, put it in the headers instead - } else if (event.request.url.includes("bearer_token=")) { - const url = new URL(event.request.url) - const token = url.searchParams.get("bearer_token") - - if (url.href.startsWith(GOOGLE_DRIVE)) googleDriveToken = token - - url.searchParams.delete("bearer_token") - url.search = "?" + url.searchParams.toString() - - event.respondWith(newRequestWithAuth( - event.request, - url.toString(), - "Bearer " + token - )) - - // Use cache if internal request and not using native app - } else if (isInternal) { - event.respondWith( - isNativeWrapper - ? network(event) - : cacheThenNetwork(event) - ) - - } else if (event.request.url && event.request.url.startsWith(GOOGLE_DRIVE) && event.request.url.includes("alt=media")) { - // For some reason Safari starts using the non bearer-token URL while playing audio - event.respondWith( - googleDriveToken - ? newRequestWithAuth( - event.request, - event.request.url.toString(), - "Bearer " + googleDriveToken - ) - : network(event) - ) - - } -}) - - -function cacheThenNetwork(event) { - const url = new URL(event.request.url) - url.search = "" - - return caches - .open(KEY) - .then(cache => cache.match(url)) - .then(match => match || fetch(url)) -} - - -function network(event) { - return fetch(event.request.url) -} - - -addEventListener("message", event => { - if (event.data === "skipWaiting") { - globalThis.skipWaiting() - } -}) - - - -// โš—๏ธ - - -function newRequestWithAuth(request: Request, newUrl: string, authToken: string): Promise { - const newHeaders = new Headers(request.headers) - newHeaders.append("authorization", authToken) - - const newRequest = new Request(request, { headers: newHeaders }) - - const makeFetch = () => fetch(newRequest).then(async r => { - if (r.ok) { - return r - } else { - return r.text().then(text => { - throw new Error(text) - }) - } - }) - - return makeFetch() -} diff --git a/src/Javascript/common.ts b/src/Javascript/common.ts deleted file mode 100644 index e4081a427..000000000 --- a/src/Javascript/common.ts +++ /dev/null @@ -1,67 +0,0 @@ -// -// Common stuff -// ส•โ€ขแดฅโ€ขส” - - -import * as localforage from "localforage" - - -// ๐ŸŒณ - - -export type CoverPrep = { - cacheKey: string - trackFilename: string - trackPath: string - trackSourceId: string - variousArtists: string -} - - - -// FUNCTIONS - - -export function db(storeName: string = "main"): LocalForage { - return localforage.createInstance({ - name: "diffuse", - storeName - }) -} - - -export function fileExtension(mimeType: string): string | undefined { - const audioId = mimeType.toLowerCase().split("/")[ 1 ] - - switch (audioId) { - case "mp3": return "mp3"; - case "mpeg": return "mp3"; - - case "mp4a-latm": return "m4a"; - case "mp4": return "m4a"; - case "x-m4a": return "m4a"; - - case "flac": return "flac"; - case "x-flac": return "flac"; - case "ogg": return "ogg"; - case "opus": return "opus"; - - case "wav": return "wav"; - case "wave": return "wav"; - - case "webm": return "webm"; - } -} - - -export function mimeType(fileExt: string): string | undefined { - switch (fileExt) { - case "mp3": return "audio/mpeg"; - case "mp4": return "audio/mp4"; - case "m4a": return "audio/mp4"; - case "flac": return "audio/flac"; - case "ogg": return "audio/ogg"; - case "wav": return "audio/wave"; - case "webm": return "audio/webm"; - } -} diff --git a/src/Javascript/crypto.ts b/src/Javascript/crypto.ts deleted file mode 100644 index a00e63914..000000000 --- a/src/Javascript/crypto.ts +++ /dev/null @@ -1,83 +0,0 @@ -// -// Cryptography -// \ (โ€ขโ—กโ€ข) / -// -// Data encryption & decryption. - - -import * as Uint8arrays from "uint8arrays" - - -const extractable = false - - -export async function keyFromPassphrase(passphrase: string): Promise { - const baseKey = await crypto.subtle.importKey( - "raw", - Uint8arrays.fromString(passphrase, "utf8"), - { - name: "PBKDF2" - }, - false, - [ "deriveKey" ] - ) - - return await crypto.subtle.deriveKey( - { - name: "PBKDF2", - salt: Uint8arrays.fromString("diffuse", "utf8"), - iterations: 10000, - hash: "SHA-512" - }, - baseKey, - { - name: "AES-GCM", - length: 256 - }, - extractable, - [ "encrypt", "decrypt" ] - ) -} - - -export async function encrypt(key: CryptoKey, string: string): Promise { - const iv = crypto.getRandomValues(new Uint8Array(12)) - - const buf = await crypto.subtle.encrypt( - { - name: "AES-GCM", - iv: iv, - tagLength: 128 - }, - key, - Uint8arrays.fromString(string, "utf8") - ) - - const iv_b64 = Uint8arrays.toString(iv, "base64pad") - const buf_b64 = Uint8arrays.toString(new Uint8Array(buf), "base64pad") - return iv_b64 + buf_b64 -} - - -export async function decrypt(key: CryptoKey, string: string): Promise { - const iv_b64 = string.substring(0, 16) - const buf_b64 = string.substring(16) - - const iv = Uint8arrays.fromString(iv_b64, "base64pad") - const buf = Uint8arrays.fromString(buf_b64, "base64pad") - - const decrypted = await crypto.subtle.decrypt( - { - name: "AES-GCM", - iv: iv, - tagLength: 128 - }, - key, - buf - ) - - return Uint8arrays.toString( - new Uint8Array(decrypted), - "utf8" - ) -} diff --git a/src/Javascript/urls.ts b/src/Javascript/urls.ts deleted file mode 100644 index 5f9fd85f6..000000000 --- a/src/Javascript/urls.ts +++ /dev/null @@ -1,118 +0,0 @@ -// -// URLs -// \ (โ€ขโ—กโ€ข) / -// -// Some URLs are special you know. - - -const EXPIRED_ACCESS_TOKENS = { - GOOGLE: {} -} - - -export async function transformUrl(url, app) { - const parts = url.split("://") - - switch (parts[ 0 ]) { - - case "dropbox": { - const dropboxBits = parts[ 1 ].split("@") - const accessToken = dropboxBits[ 0 ] - const filePath = dropboxBits[ 1 ] - - return fetch( - "https://api.dropboxapi.com/2/files/get_temporary_link", - { - method: "POST" - , body: JSON.stringify({ path: filePath }) - , headers: new Headers({ - "Authorization": "Bearer " + accessToken, - "Content-Type": "application/json" - }) - } - ).then( - response => response.json() - ).then( - response => response.link - ) - } - - case "google": { - let finalAccessToken - - const googleBits = parts[ 1 ].split("@") - const [ accessToken, expiresAtString, refreshToken, clientId, clientSecret, srcId ] = googleBits[ 0 ].split(":") - const fileId = googleBits[ 1 ] - - // Unix timestamp in milliseconds - const inXminutes = Date.now() + 5 * 60 * 1000 // 5 minutes - const expiresAt = parseInt(expiresAtString, 10) - const isAlmostExpired = expiresAt <= inXminutes - - if (EXPIRED_ACCESS_TOKENS.GOOGLE[ accessToken ]) { - const replacement = EXPIRED_ACCESS_TOKENS.GOOGLE[ accessToken ] - - if (replacement.newExpiresAt <= inXminutes) { - finalAccessToken = await refreshGoogleAccessToken({ - app, clientId, clientSecret, refreshToken, srcId, oldToken: accessToken - }) - } else { - finalAccessToken = replacement.newToken - } - - } else if (isAlmostExpired) { - finalAccessToken = await refreshGoogleAccessToken({ - app, clientId, clientSecret, refreshToken, srcId, oldToken: accessToken - }) - - } else { - finalAccessToken = accessToken - - } - - return Promise.resolve( - `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?alt=media&bearer_token=${encodeURIComponent(finalAccessToken)}` - ) - } - - default: - return Promise.resolve(url) - - } -} - - - -// GOOGLE - - -async function refreshGoogleAccessToken({ app, clientId, clientSecret, oldToken, refreshToken, srcId }) { - console.log("๐Ÿ” Refreshing Google Drive access token") - - const url = new URL("https://www.googleapis.com/oauth2/v4/token") - - url.searchParams.set("client_id", clientId) - url.searchParams.set("client_secret", clientSecret) - url.searchParams.set("refresh_token", refreshToken) - url.searchParams.set("grant_type", "refresh_token") - - const serverResponse = await fetch(url, { method: "POST" }).then(r => r.json()) - const newToken = serverResponse.access_token - const newExpiresAt = Date.now() + (serverResponse.expires_in * 1000) - - EXPIRED_ACCESS_TOKENS.GOOGLE[ oldToken ] = { - oldToken, - newToken, - newExpiresAt, - refreshToken - } - - app.ports.refreshedAccessToken.send({ - service: "Google", - sourceId: srcId, - accessToken: newToken, - expiresAt: newExpiresAt - }) - - return serverResponse.access_token -} diff --git a/src/Library/Alfred.elm b/src/Library/Alfred.elm deleted file mode 100644 index 625e93839..000000000 --- a/src/Library/Alfred.elm +++ /dev/null @@ -1,110 +0,0 @@ -module Alfred exposing (..) - -import List.Extra as List -import Material.Icons.Types exposing (Coloring) -import Svg exposing (Svg) - - - --- ๐ŸŒณ - - -type alias Alfred msg = - { action : Action msg - , focus : Int - , index : List (Group msg) - , indexFlattened : List (Item msg) - , message : String - , operation : Operation - , results : List (Group msg) - , searchTerm : Maybe String - } - - -type alias Action msg = - { result : Maybe (Item msg), searchTerm : Maybe String } -> List msg - - -type alias Group msg = - { name : Maybe String, items : List (Item msg) } - - -type alias Item msg = - { icon : Maybe (Coloring -> Svg msg) - , title : String - , value : ItemValue msg - } - - -type ItemValue msg - = Command msg - | StringValue String - - -type Operation - = Query - | QueryOrMutation - | Mutation - - - --- ๐Ÿ›ณ - - -create : - { action : Action msg - , index : List (Group msg) - , message : String - , operation : Operation - } - -> Alfred msg -create { action, index, message, operation } = - { action = action - , focus = 0 - , index = index - , indexFlattened = List.concatMap .items index - , message = message - , operation = operation - , results = index - , searchTerm = Nothing - } - - - --- ๐Ÿ›  - - -command : ItemValue msg -> Maybe msg -command val = - case val of - Command cmd -> - Just cmd - - StringValue _ -> - Nothing - - -stringValue : ItemValue msg -> Maybe String -stringValue val = - case val of - Command _ -> - Nothing - - StringValue string -> - Just string - - - --- ๐Ÿ›  - - -getAt : Int -> Alfred msg -> Maybe (Item msg) -getAt index alfred = - alfred.results - |> List.concatMap .items - |> List.getAt index - - -length : Alfred msg -> Int -length { indexFlattened } = - List.length indexFlattened diff --git a/src/Library/Alien.elm b/src/Library/Alien.elm deleted file mode 100644 index ab08b6bfb..000000000 --- a/src/Library/Alien.elm +++ /dev/null @@ -1,200 +0,0 @@ -module Alien exposing (Event, Tag(..), broadcast, hostDecoder, report, tagDecoder, tagFromJson, tagFromString, tagToJson, tagToString, trigger) - -{-| ๐Ÿ‘ฝ Aliens. - -This involves the incoming and outgoing data. -Including the communication between the different Elm apps/workers. - --} - -import Enum exposing (Enum) -import Json.Decode -import Json.Encode - - - --- ๐ŸŒณ - - -type alias Event = - { tag : String - , data : Json.Encode.Value - , error : Maybe String - } - - -type Tag - = EnclosedData - | SearchTracks - | SecretKey - | SyncLocal - | SyncMethod - ----------------------------------------- - -- from UI - ----------------------------------------- - | DownloadTracks - | ProcessSources - | RefreshedAccessToken - | RemoveEncryptionKey - | RemoveTracksBySourceId - | RemoveTracksFromCache - | SaveEnclosedUserData - | SaveFavourites - | SavePlaylists - | SaveProgress - | SaveSettings - | SaveSources - | SaveTracks - | SetSyncMethod - | StopProcessing - | StoreTracksInCache - | SyncHypaethralData - | SyncTrackTags - | ToCache - | UnsetSyncMethod - | UpdateEncryptionKey - ----------------------------------------- - -- to UI - ----------------------------------------- - | AddTracks - | FinishedProcessingSource - | FinishedProcessingSources - | FinishedSyncing - | GotCachedCover - | HideLoadingScreen - | LoadEnclosedUserData - | LoadHypaethralUserData - | ReloadTracks - | RemoveTracksByPath - | ReportError - | ReportProcessingError - | ReportProcessingProgress - | StartedSyncing - | UpdateSourceData - - -enum : Enum Tag -enum = - Enum.create - [ ( "ENCLOSED_DATA", EnclosedData ) - , ( "SEARCH_TRACKS", SearchTracks ) - , ( "SECRET_KEY", SecretKey ) - , ( "SYNC_LOCAL", SyncLocal ) - , ( "SYNC_METHOD", SyncMethod ) - - ----------------------------------------- - -- From UI - ----------------------------------------- - , ( "DOWNLOAD_TRACKS", DownloadTracks ) - , ( "PROCESS_SOURCES", ProcessSources ) - , ( "REFRESHED_ACCESS_TOKEN", RefreshedAccessToken ) - , ( "REMOVE_ENCRYPTION_KEY", RemoveEncryptionKey ) - , ( "REMOVE_TRACKS_BY_SOURCE_ID", RemoveTracksBySourceId ) - , ( "REMOVE_TRACKS_FROM_CACHE", RemoveTracksFromCache ) - , ( "SAVE_ENCLOSED_USER_DATA", SaveEnclosedUserData ) - , ( "SAVE_FAVOURITES", SaveFavourites ) - , ( "SAVE_PLAYLISTS", SavePlaylists ) - , ( "SAVE_PROGRESS", SaveProgress ) - , ( "SAVE_SETTINGS", SaveSettings ) - , ( "SAVE_SOURCES", SaveSources ) - , ( "SAVE_TRACKS", SaveTracks ) - , ( "SET_SYNC_METHOD", SetSyncMethod ) - , ( "STOP_PROCESSING", StopProcessing ) - , ( "STORE_TRACKS_IN_CACHE", StoreTracksInCache ) - , ( "SYNC_HYPAETHRAL_DATA", SyncHypaethralData ) - , ( "SYNC_TRACK_TAGS", SyncTrackTags ) - , ( "TO_CACHE", ToCache ) - , ( "UNSET_SYNC_METHOD", UnsetSyncMethod ) - , ( "UPDATE_ENCRYPTION_KEY", UpdateEncryptionKey ) - - ----------------------------------------- - -- To UI - ----------------------------------------- - , ( "ADD_TRACKS", AddTracks ) - , ( "FINISHED_PROCESSING_SOURCE", FinishedProcessingSource ) - , ( "FINISHED_PROCESSING_SOURCES", FinishedProcessingSources ) - , ( "GOT_CACHED_COVER", GotCachedCover ) - , ( "HIDE_LOADING_SCREEN", HideLoadingScreen ) - , ( "LOAD_ENCLOSED_USER_DATA", LoadEnclosedUserData ) - , ( "LOAD_HYPAETHRAL_USER_DATA", LoadHypaethralUserData ) - , ( "RELOAD_TRACKS", ReloadTracks ) - , ( "REMOVE_TRACKS_BY_PATH", RemoveTracksByPath ) - , ( "REPORT_ERROR", ReportError ) - , ( "REPORT_PROCESSING_ERROR", ReportProcessingError ) - , ( "REPORT_PROCESSING_PROGRESS", ReportProcessingProgress ) - , ( "STARTED_SYNCING", StartedSyncing ) - , ( "UPDATE_SOURCE_DATA", UpdateSourceData ) - ] - - - --- ๐Ÿ”ฑ - - -broadcast : Tag -> Json.Encode.Value -> Event -broadcast tag data = - { tag = tagToString tag - , data = data - , error = Nothing - } - - -report : Tag -> String -> Event -report tag error = - { tag = tagToString tag - , data = Json.Encode.null - , error = Just error - } - - -trigger : Tag -> Event -trigger tag = - { tag = tagToString tag - , data = Json.Encode.null - , error = Nothing - } - - -tagDecoder : Json.Decode.Decoder Tag -tagDecoder = - enum.decoder - - -tagToJson : Tag -> Json.Encode.Value -tagToJson = - enum.encode - - -tagToString : Tag -> String -tagToString = - enum.toString - - -tagFromJson : Json.Decode.Value -> Result Json.Decode.Error Tag -tagFromJson = - Json.Decode.decodeValue enum.decoder - - -tagFromString : String -> Maybe Tag -tagFromString = - enum.fromString - - - --- โš—๏ธ - - -{-| Decoder for an alien event inside another alient event. --} -hostDecoder : Json.Decode.Decoder Event -hostDecoder = - Json.Decode.map3 - (\tag data error -> - { tag = tag - , data = data - , error = error - } - ) - (Json.Decode.field "tag" Json.Decode.string) - (Json.Decode.field "data" Json.Decode.value) - (Json.Decode.field "error" <| Json.Decode.maybe Json.Decode.string) diff --git a/src/Library/Chunky.elm b/src/Library/Chunky.elm deleted file mode 100644 index 0f1f09c85..000000000 --- a/src/Library/Chunky.elm +++ /dev/null @@ -1,97 +0,0 @@ -module Chunky exposing (brick, bricky, chunk, chunky, inline, lineBreak, nothing, raw, rawy, slab, slaby) - -{-| Chunks, blocks and slabs. - -Convenience functions to build UIs with composable CSS classes. - --} - -import Html exposing (Html) -import Html.Attributes as A - - - --- 1 - - -slab : - (List (Html.Attribute msg) -> List (Html msg) -> Html msg) - -> List (Html.Attribute msg) - -> List String - -> List (Html msg) - -> Html msg -slab typ attributes stylingAttributes children = - typ - (A.class (String.join " " stylingAttributes) :: attributes) - children - - -slaby : - (List (Html.Attribute msg) -> List (Html msg) -> Html msg) - -> List (Html.Attribute msg) - -> List String - -> Html msg - -> Html msg -slaby a b c = - List.singleton >> slab a b c - - - --- 2 - - -brick : List (Html.Attribute msg) -> List String -> List (Html msg) -> Html msg -brick = - slab Html.div - - -bricky : List (Html.Attribute msg) -> List String -> Html msg -> Html msg -bricky a b = - List.singleton >> brick a b - - - --- 3 - - -chunk : List String -> List (Html msg) -> Html msg -chunk = - brick [] - - -chunky : List String -> Html msg -> Html msg -chunky a = - List.singleton >> chunk a - - -inline : List String -> List (Html msg) -> Html msg -inline = - slab Html.span [] - - - --- 4 - - -raw : List (Html msg) -> Html msg -raw = - chunk [] - - -rawy : Html msg -> Html msg -rawy = - List.singleton >> raw - - - --- 5 - - -nothing : Html msg -nothing = - Html.text "" - - -lineBreak : Html msg -lineBreak = - Html.br [] [] diff --git a/src/Library/Common.elm b/src/Library/Common.elm deleted file mode 100644 index 9e74a2df5..000000000 --- a/src/Library/Common.elm +++ /dev/null @@ -1,97 +0,0 @@ -module Common exposing (ServiceWorkerStatus(..), Switch(..), backToIndex, boolFromString, boolToString, queryString, translateHttpResponse, urlOrigin) - -import Http -import Tuple.Ext as Tuple -import Url exposing (Protocol(..), Url) -import Url.Builder as Url - - - --- โ›ฉ - - -backToIndex : String -backToIndex = - "Back to tracks" - - - --- ๐ŸŒณ - - -type ServiceWorkerStatus - = InstallingInitial - | InstallingNew - | WaitingForActivation - | Activated - - -type Switch - = On - | Off - - - --- ๐Ÿ”ฑ - - -boolFromString : String -> Bool -boolFromString string = - case string of - "t" -> - True - - _ -> - False - - -boolToString : Bool -> String -boolToString bool = - if bool then - "t" - - else - "f" - - -queryString : List ( String, String ) -> String -queryString = - List.map (Tuple.uncurry Url.string) >> Url.toQuery - - -translateHttpResponse : Http.Response String -> Result Http.Error String -translateHttpResponse response = - case response of - Http.BadUrl_ u -> - Err (Http.BadUrl u) - - Http.Timeout_ -> - Err Http.Timeout - - Http.NetworkError_ -> - Err Http.NetworkError - - Http.BadStatus_ _ body -> - Err (Http.BadBody body) - - Http.GoodStatus_ _ body -> - Ok body - - -urlOrigin : Url -> String -urlOrigin { host, port_, path, protocol } = - let - scheme = - case protocol of - Http -> - "http://" - - Https -> - "https://" - - thePort = - port_ - |> Maybe.map (String.fromInt >> (++) ":") - |> Maybe.withDefault "" - in - scheme ++ host ++ thePort ++ path diff --git a/src/Library/Conditional.elm b/src/Library/Conditional.elm deleted file mode 100644 index 03bd32dc3..000000000 --- a/src/Library/Conditional.elm +++ /dev/null @@ -1,12 +0,0 @@ -module Conditional exposing (ifThenElse) - --- ๐Ÿ”ฑ - - -ifThenElse : Bool -> a -> a -> a -ifThenElse bool x y = - if bool then - x - - else - y diff --git a/src/Library/ContextMenu.elm b/src/Library/ContextMenu.elm deleted file mode 100644 index c517659d2..000000000 --- a/src/Library/ContextMenu.elm +++ /dev/null @@ -1,54 +0,0 @@ -module ContextMenu exposing (ContextMenu(..), Item(..), ItemProperties, anyItem, coordinates, justAnItem) - -import Coordinates exposing (Coordinates) -import Material.Icons.Types exposing (Coloring) -import Svg exposing (Svg) - - - --- ๐ŸŒณ - - -type ContextMenu msg - = ContextMenu (List (Item msg)) Coordinates - - -type Item msg - = Item (ItemProperties msg) - | Divider - - -type alias ItemProperties msg = - { icon : Int -> Coloring -> Svg msg - , label : String - , msg : msg - , active : Bool - } - - - --- ๐Ÿ”ฑ - - -anyItem : (ItemProperties msg -> Bool) -> ContextMenu msg -> Bool -anyItem fn (ContextMenu items _) = - List.any - (\item -> - case item of - Item props -> - fn props - - Divider -> - False - ) - items - - -coordinates : ContextMenu msg -> Coordinates -coordinates (ContextMenu _ c) = - c - - -justAnItem : ItemProperties msg -> Maybe (Item msg) -justAnItem = - Just << Item diff --git a/src/Library/Coordinates.elm b/src/Library/Coordinates.elm deleted file mode 100644 index 0278081bc..000000000 --- a/src/Library/Coordinates.elm +++ /dev/null @@ -1,29 +0,0 @@ -module Coordinates exposing (..) - --- ๐ŸŒณ - - -type alias Coordinates = - { x : Float, y : Float } - - -type alias Viewport = - { height : Float - , width : Float - } - - - --- ๐Ÿ”ฑ - - -fromTuple : ( Float, Float ) -> Coordinates -fromTuple ( x, y ) = - { x = x - , y = y - } - - -toTuple : Coordinates -> ( Float, Float ) -toTuple { x, y } = - ( x, y ) diff --git a/src/Library/Cryptography/Hmac.elm b/src/Library/Cryptography/Hmac.elm deleted file mode 100644 index 944a4919f..000000000 --- a/src/Library/Cryptography/Hmac.elm +++ /dev/null @@ -1,139 +0,0 @@ -module Cryptography.Hmac exposing (encrypt128, encrypt64) - -{-| Cryptography โ€“ HMAC --} - -import Binary exposing (Bits) - - -type alias HashFunction = - Bits -> Bits - - -{-| HMAC encryption for hashing algorithms with a `blockSize` of 64. -These include: SHA-0, SHA-1, SHA-224, SHA-256, MD5, etc. - - >>> import Binary - >>> import SHA - - >>> Binary.fromStringAsUtf8 "" - ..> |> encrypt64 SHA.sha256 "" - ..> |> Binary.toHex - ..> |> String.toLower - "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad" - - >>> Binary.fromStringAsUtf8 "key" - ..> |> encrypt64 SHA.sha256 "The quick brown fox jumps over the lazy dog" - ..> |> Binary.toHex - ..> |> String.toLower - "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8" - - >>> Binary.fromHex "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b" - ..> |> encrypt64 SHA.sha256 "Hi There" - ..> |> Binary.toHex - ..> |> String.toLower - "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" - - >>> Binary.fromHex "4a656665" - ..> |> encrypt64 SHA.sha256 "what do ya want for nothing?" - ..> |> Binary.toHex - ..> |> String.toLower - "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843" - - >>> Binary.fromHex "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - ..> |> encrypt64 SHA.sha256 "Test Using Larger Than Block-Size Key - Hash Key First" - ..> |> Binary.toHex - ..> |> String.toLower - "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54" - --} -encrypt64 : HashFunction -> String -> Bits -> Bits -encrypt64 = - encrypt (64 * 8) - - -{-| HMAC encryption for hashing algorithms with a `blockSize` of 128. -These include: SHA-384, SHA-512, etc. --} -encrypt128 : HashFunction -> String -> Bits -> Bits -encrypt128 = - encrypt (128 * 8) - - - --- ENCRYPT - - -encrypt : Int -> HashFunction -> String -> Bits -> Bits -encrypt blockSize hash messageString key = - let - keySize = - Binary.width key - - keyWithBlockSize = - if keySize > blockSize then - padRight blockSize (hash key) - - else if keySize < blockSize then - padRight blockSize key - - else - key - - ( binSeqOne, binSeqTwo ) = - Tuple.mapBoth - (Binary.xor keyWithBlockSize) - (Binary.xor keyWithBlockSize) - (padding <| blockSize // 8) - in - messageString - |> Binary.fromString 8 - |> Binary.append binSeqOne - |> hash - |> Binary.append binSeqTwo - |> hash - - -padRight : Int -> Bits -> Bits -padRight int bits = - let - size = - Binary.width bits - in - False - |> List.repeat (int - size) - |> List.append (Binary.toBooleans bits) - |> Binary.fromBooleans - - - --- PADDING - - -padding : Int -> ( Bits, Bits ) -padding blockSize = - case blockSize of - 64 -> - padding64 - - 128 -> - padding128 - - _ -> - ( Binary.concat (List.repeat blockSize <| Binary.fromHex "36") - , Binary.concat (List.repeat blockSize <| Binary.fromHex "5C") - ) - - -padding64 : ( Bits, Bits ) -padding64 = - ( Binary.concat (List.repeat 64 <| Binary.fromHex "36") - , Binary.concat (List.repeat 64 <| Binary.fromHex "5C") - ) - - -padding128 : ( Bits, Bits ) -padding128 = - ( Binary.concat (List.repeat 128 <| Binary.fromHex "36") - , Binary.concat (List.repeat 128 <| Binary.fromHex "5C") - ) diff --git a/src/Library/Dict/Ext.elm b/src/Library/Dict/Ext.elm deleted file mode 100644 index 82be3a63e..000000000 --- a/src/Library/Dict/Ext.elm +++ /dev/null @@ -1,24 +0,0 @@ -module Dict.Ext exposing (fetch, fetchUnknown, unionFlipped) - -import Dict exposing (Dict) - - - --- ๐Ÿ”ฑ - - -unionFlipped : Dict comparable v -> Dict comparable v -> Dict comparable v -unionFlipped a b = - Dict.union b a - - -fetch : comparable -> v -> Dict comparable v -> v -fetch key default dict = - dict - |> Dict.get key - |> Maybe.withDefault default - - -fetchUnknown : comparable -> Dict comparable String -> String -fetchUnknown key dict = - fetch key "MISSING_VALUE" dict diff --git a/src/Library/Equalizer.elm b/src/Library/Equalizer.elm deleted file mode 100644 index 83ffdf1b2..000000000 --- a/src/Library/Equalizer.elm +++ /dev/null @@ -1,68 +0,0 @@ -module Equalizer exposing (..) - -import Coordinates exposing (Coordinates) -import Json.Decode -import Json.Encode - - - --- ๐ŸŒณ - - -type Knob - = Low - | Mid - | High - | Volume - - -type alias KnobOperation = - { knob : Knob - , startingPosition : Coordinates - } - - -type alias Settings = - { low : Float - , mid : Float - , high : Float - , volume : Float - } - - -maxAngle : Float -maxAngle = - 135 - - - --- ๐Ÿ”ฑ - - -defaultSettings : Settings -defaultSettings = - { low = 0 - , mid = 0 - , high = 0 - , volume = 0.5 - } - - -encodeSettings : Settings -> Json.Encode.Value -encodeSettings settings = - Json.Encode.object - [ ( "low", Json.Encode.float settings.low ) - , ( "mid", Json.Encode.float settings.mid ) - , ( "high", Json.Encode.float settings.high ) - , ( "volume", Json.Encode.float settings.volume ) - ] - - -settingsDecoder : Json.Decode.Decoder Settings -settingsDecoder = - Json.Decode.map4 - Settings - (Json.Decode.field "low" Json.Decode.float) - (Json.Decode.field "mid" Json.Decode.float) - (Json.Decode.field "high" Json.Decode.float) - (Json.Decode.field "volume" Json.Decode.float) diff --git a/src/Library/Html/Ext.elm b/src/Library/Html/Ext.elm deleted file mode 100644 index d20b13429..000000000 --- a/src/Library/Html/Ext.elm +++ /dev/null @@ -1,50 +0,0 @@ -module Html.Ext exposing (..) - -import Html exposing (Attribute, Html) -import Html.Events exposing (keyCode, on, preventDefaultOn, stopPropagationOn) -import Json.Decode as Json - - -lineBreak : Html msg -lineBreak = - Html.br [] [] - - -onClickStopPropagation : msg -> Attribute msg -onClickStopPropagation msg = - stopPropagationOn "click" (Json.succeed ( msg, True )) - - -onDoubleTap : msg -> Attribute msg -onDoubleTap msg = - on "dbltap" (Json.succeed msg) - - -onEnterKey : msg -> Attribute msg -onEnterKey msg = - on "keydown" (Json.andThen (ifEnterKey msg) keyCode) - - -ifEnterKey : msg -> Int -> Json.Decoder msg -ifEnterKey msg key = - case key of - 13 -> - Json.succeed msg - - _ -> - Json.fail "Another key, that isn't enter, was pressed" - - -onTap : msg -> Attribute msg -onTap msg = - on "tap" (Json.succeed msg) - - -onTapPreventDefault : msg -> Attribute msg -onTapPreventDefault msg = - preventDefaultOn "tap" (Json.succeed ( msg, True )) - - -onTapStopPropagation : msg -> Attribute msg -onTapStopPropagation msg = - stopPropagationOn "tap" (Json.succeed ( msg, True )) diff --git a/src/Library/Http/Ext.elm b/src/Library/Http/Ext.elm deleted file mode 100644 index 0cf925ec1..000000000 --- a/src/Library/Http/Ext.elm +++ /dev/null @@ -1,27 +0,0 @@ -module Http.Ext exposing (errorToString) - -import Http exposing (Error(..)) - - - --- ๐Ÿ›  - - -errorToString : Http.Error -> String -errorToString err = - -- Thanks to: https://github.com/hercules-ci/elm-hercules-extras/blob/1.0.0/src/Http/Extras.elm - case err of - Timeout -> - "Timeout exceeded" - - NetworkError -> - "Network error" - - BadStatus code -> - "Something went wrong, got status code: " ++ String.fromInt code - - BadBody text -> - "Unexpected response: " ++ text - - BadUrl url -> - "Malformed url: " ++ url diff --git a/src/Library/Icons.elm b/src/Library/Icons.elm deleted file mode 100644 index 40fd078c0..000000000 --- a/src/Library/Icons.elm +++ /dev/null @@ -1,25 +0,0 @@ -module Icons exposing (..) - -import Chunky exposing (slaby) -import Html -import Material.Icons.Types exposing (Coloring) -import VirtualDom - - - --- ๐ŸŒณ - - -type alias Icon msg = - Int -> Coloring -> VirtualDom.Node msg - - - --- ๐Ÿ”ฑ - - -wrapped : List String -> Icon msg -> Int -> Coloring -> VirtualDom.Node msg -wrapped classes icon size coloring = - coloring - |> icon size - |> slaby Html.span [] classes diff --git a/src/Library/Json/Decode/Ext.elm b/src/Library/Json/Decode/Ext.elm deleted file mode 100644 index 20a20a150..000000000 --- a/src/Library/Json/Decode/Ext.elm +++ /dev/null @@ -1,28 +0,0 @@ -module Json.Decode.Ext exposing (listIgnore, optionalField) - -import Json.Decode as Decode exposing (Decoder) -import Maybe.Extra as Maybe - - - --- ๐Ÿ”ฑ - - -{-| A list decoder that always succeeds, throwing away the failures. --} -listIgnore : Decoder a -> Decoder (List a) -listIgnore decoder = - decoder - |> Decode.maybe - |> Decode.list - |> Decode.map Maybe.values - - -{-| Provide a default value for a field that might not be there. --} -optionalField : String -> Decoder a -> a -> Decoder a -optionalField field decoder defaultValue = - decoder - |> Decode.field field - |> Decode.maybe - |> Decode.map (Maybe.withDefault defaultValue) diff --git a/src/Library/Json/Encode/Ext.elm b/src/Library/Json/Encode/Ext.elm deleted file mode 100644 index cb768bf68..000000000 --- a/src/Library/Json/Encode/Ext.elm +++ /dev/null @@ -1,10 +0,0 @@ -module Json.Encode.Ext exposing (..) - -import Json.Encode as Encode - - -encodeMaybe : Maybe a -> (a -> Encode.Value) -> Encode.Value -encodeMaybe maybe encoder = - maybe - |> Maybe.map encoder - |> Maybe.withDefault Encode.null diff --git a/src/Library/LastFm.elm b/src/Library/LastFm.elm deleted file mode 100644 index d9280ded0..000000000 --- a/src/Library/LastFm.elm +++ /dev/null @@ -1,200 +0,0 @@ -module LastFm exposing (..) - -import Common -import Http -import Json.Decode as Json -import List.Ext as List -import MD5 -import String.Ext as String -import Tracks exposing (Track) -import Tuple.Ext as Tuple -import Url exposing (Url) -import Url.Ext as Url - - - --- ๐Ÿ” - - -apiKey = - "4f0fe85b67baef8bb7d008a8754a95e5" - - -apiUrl = - "https://ws.audioscrobbler.com/2.0" - - -notSoSecret = - "0cec3ca0f58e04a5082f1131aba1e0d3" - - - --- ๐ŸŒณ - - -type alias Model = - { authenticating : Bool - , sessionKey : Maybe String - } - - -initialModel : Model -initialModel = - { authenticating = False - , sessionKey = Nothing - } - - -authenticationCommand : (Result Http.Error String -> msg) -> Url -> Cmd msg -authenticationCommand msg url = - case Url.extractQueryParam "token" url of - Just token -> - Http.get - { url = - authenticatedUrl - [ ( "method", "auth.getSession" ) - , ( "token", token ) - ] - , expect = - Json.string - |> Json.at [ "session", "key" ] - |> Http.expectJson msg - } - - Nothing -> - Cmd.none - - - --- ๐Ÿ“ฃ - - -disconnect : Model -> Model -disconnect model = - { model | sessionKey = Nothing } - - -failedToAuthenticate : Model -> Model -failedToAuthenticate model = - { model | authenticating = False } - - -gotSessionKey : String -> Model -> Model -gotSessionKey sessionKey model = - { model | sessionKey = Just sessionKey } - - - --- ๐ŸŽต - - -nowPlaying : Model -> { duration : Int, msg : msg, track : Track } -> Cmd msg -nowPlaying model { duration, msg, track } = - case model.sessionKey of - Just sessionKey -> - Http.post - { url = - apiUrl - , body = - [ ( "duration", String.fromInt duration ) - , ( "track", track.tags.title ) - , ( "trackNumber", String.fromInt track.tags.nr ) - - -- - , ( "method", "track.updateNowPlaying" ) - , ( "sk", sessionKey ) - ] - |> addAlbum track - |> addArtist track - |> authenticatedBody - , expect = - Http.expectWhatever (always msg) - } - - Nothing -> - Cmd.none - - -scrobble : Model -> { duration : Int, msg : msg, timestamp : Int, track : Track } -> Cmd msg -scrobble model { duration, msg, timestamp, track } = - case model.sessionKey of - Just sessionKey -> - Http.post - { url = - apiUrl - , body = - [ ( "duration", String.fromInt duration ) - , ( "track", track.tags.title ) - , ( "trackNumber", String.fromInt track.tags.nr ) - - -- - , ( "method", "track.scrobble" ) - , ( "sk", sessionKey ) - , ( "timestamp", String.fromInt timestamp ) - ] - |> addAlbum track - |> addArtist track - |> authenticatedBody - , expect = - Http.expectWhatever (always msg) - } - - Nothing -> - Cmd.none - - -addAlbum track list = - case track.tags.album of - Just album -> - ( "album", album ) :: list - - Nothing -> - list - - -addArtist track list = - case track.tags.artist of - Just artist -> - ( "artist", artist ) :: list - - Nothing -> - list - - - --- ๐Ÿ”ฑ - - -authenticatedBody : List ( String, String ) -> Http.Body -authenticatedBody params = - params - |> authenticatedParams - |> Common.queryString - |> String.dropLeft 1 - |> Http.stringBody "application/x-www-form-urlencoded" - - -authenticatedUrl : List ( String, String ) -> String -authenticatedUrl params = - params - |> authenticatedParams - |> Common.queryString - |> String.append apiUrl - - -authenticatedParams : List ( String, String ) -> List ( String, String ) -authenticatedParams params = - let - extendedParams = - ( "api_key", apiKey ) :: params - in - extendedParams - |> List.sortBy Tuple.first - |> List.map (Tuple.uncurry String.append) - |> String.concat - |> String.addSuffix notSoSecret - |> MD5.hex - |> Tuple.pair "api_sig" - |> List.addTo extendedParams - |> (::) ( "format", "json" ) - |> List.sortBy Tuple.first diff --git a/src/Library/Lens/Ext.elm b/src/Library/Lens/Ext.elm deleted file mode 100644 index ee39cd285..000000000 --- a/src/Library/Lens/Ext.elm +++ /dev/null @@ -1,17 +0,0 @@ -module Lens.Ext exposing (..) - -import Monocle.Lens as Lens exposing (Lens) - - -{-| Flipped version of `Lens.modify`. --} -adjust : Lens a b -> a -> (b -> b) -> a -adjust lens a fn = - Lens.modify lens fn a - - -{-| Flipped version of `lens.set`. --} -replace : Lens a b -> a -> b -> a -replace lens a b = - lens.set b a diff --git a/src/Library/List/Ext.elm b/src/Library/List/Ext.elm deleted file mode 100644 index 04b76488c..000000000 --- a/src/Library/List/Ext.elm +++ /dev/null @@ -1,75 +0,0 @@ -module List.Ext exposing (..) - -import List.Extra as List - - -{-| Flipped version of `append`. - - >>> add [2, 3] [1] - [1, 2, 3] - --} -add : List a -> List a -> List a -add a b = - List.append b a - - -{-| Flipped version of (::). - - >>> addTo [2, 3] 1 - [1, 2, 3] - --} -addTo : List a -> a -> List a -addTo list item = - item :: list - - -{-| Move an item "from" an index "to" another index. -Putting the item in front of the `to` index. - - >>> move { from = 0, to = 2, amount = 1 } [1, 2, 3] - [2, 1, 3] - - >>> move { from = 2, to = 0, amount = 1 } [1, 2, 3] - [3, 1, 2] - - >>> move { from = 2, to = 7, amount = 3 } [0, 1, 2, 3, 4, 5, 6, 7] - [0, 1, 5, 6, 2, 3, 4, 7] - - >>> move { from = 2, to = 1, amount = 3 } [0, 1, 2, 3, 4, 5, 6, 7] - [0, 2, 3, 4, 1, 5, 6, 7] - --} -move : { amount : Int, from : Int, to : Int } -> List a -> List a -move { from, to, amount } list = - [] - ++ (list |> List.take (min from to)) - ++ (list |> List.take to |> List.drop (from + amount)) - ++ (list |> List.drop from |> List.take amount) - ++ (list |> List.take from |> List.drop to) - ++ (list |> List.drop (max (from + amount) to)) - - -pickIndexes : List Int -> List a -> List a -pickIndexes indexes items = - List.foldr - (\idx acc -> - items - |> List.getAt idx - |> Maybe.map (addTo acc) - |> Maybe.withDefault acc - ) - [] - indexes - - -{-| Exclude a list from another list. - - >>> without [ 2 ] [ 1, 2, 3 ] - [ 1, 3 ] - --} -without : List a -> List a -> List a -without exclude = - List.filter (\c -> List.notMember c exclude) diff --git a/src/Library/Management.elm b/src/Library/Management.elm deleted file mode 100644 index 01efbde84..000000000 --- a/src/Library/Management.elm +++ /dev/null @@ -1,43 +0,0 @@ -module Management exposing (..) - -import Monocle.Lens exposing (Lens) - - - --- ๐Ÿ“ฃ - - -type alias Manager msg model = - model -> ( model, Cmd msg ) - - - --- ๐Ÿ”ฑ - - -{-| For working with nested models. - - organize : Manager Msg NestedModel -> Manager Msg Model - organize = - { get = .nested - , set = \nested ui -> { ui | nested = nested } - } - |> Monocle.Lens.Lens - |> Management.organize - - update : Nested.Msg -> Manager Msg Model - update msg = - case msg of - NestedMsg -> - organize handleNestedMsg - --} -organize : - Lens parent nested - -> Manager msg nested - -> Manager msg parent -organize lens manager parent = - parent - |> lens.get - |> manager - |> Tuple.mapFirst (\nested -> lens.set nested parent) diff --git a/src/Library/Maybe/Ext.elm b/src/Library/Maybe/Ext.elm deleted file mode 100644 index 298a13229..000000000 --- a/src/Library/Maybe/Ext.elm +++ /dev/null @@ -1,13 +0,0 @@ -module Maybe.Ext exposing (preferFirst, preferSecond) - -import Maybe.Extra - - -preferFirst : Maybe a -> Maybe a -> Maybe a -preferFirst = - Maybe.Extra.or - - -preferSecond : Maybe a -> Maybe a -> Maybe a -preferSecond = - Maybe.Extra.orElse diff --git a/src/Library/MediaSession.elm b/src/Library/MediaSession.elm deleted file mode 100644 index 299fe63f6..000000000 --- a/src/Library/MediaSession.elm +++ /dev/null @@ -1,8 +0,0 @@ -module MediaSession exposing (states) - - -states = - { none = "none" - , paused = "paused" - , playing = "playing" - } diff --git a/src/Library/Notifications.elm b/src/Library/Notifications.elm deleted file mode 100644 index 1d1068628..000000000 --- a/src/Library/Notifications.elm +++ /dev/null @@ -1,186 +0,0 @@ -module Notifications exposing (Action, Kind(..), Notification, Options, casual, contents, dismiss, error, errorWithCode, id, kind, options, stickyCasual, stickyError, stickySuccess, success) - -import Chunky exposing (..) -import Html exposing (Html) -import Markdown -import Murmur3 exposing (..) - - - --- ๐ŸŒณ - - -type Notification msg - = Notification Kind Int Options (Html msg) - - -type alias Action msg = - { label : String, msg : msg } - - -type alias Options = - { sticky : Bool, wasDismissed : Bool } - - -type Kind - = Casual - | Error - | Success - - - --- ๐Ÿ”ฑ - - -id : Notification msg -> Int -id (Notification _ i _ _) = - i - - -contents : Notification msg -> Html msg -contents (Notification _ _ _ c) = - c - - -kind : Notification msg -> Kind -kind (Notification k _ _ _) = - k - - -options : Notification msg -> Options -options (Notification _ _ o _) = - o - - - --- โš—๏ธ - - -dismiss : Notification msg -> Notification msg -dismiss (Notification k i o c) = - Notification k i { o | wasDismissed = True } c - - - --- ๐Ÿšจ - - -error : String -> Notification msg -error content = - Notification - Error - (hashString 0 content) - { sticky = False - , wasDismissed = False - } - (render content) - - -stickyError : String -> Notification msg -stickyError content = - Notification - Error - (hashString 0 content) - { sticky = True - , wasDismissed = False - } - (render content) - - -errorWithCode : String -> String -> List (Action msg) -> Notification msg -errorWithCode content code _ = - Notification - Error - (hashString 0 content) - { sticky = True - , wasDismissed = False - } - (Html.div - [] - [ render content - , if String.isEmpty (String.trim code) then - nothing - - else - chunk - [ "bg-black-50" - , "break-all" - , "rounded" - , "mb-0" - , "mt-3" - , "p-2" - , "text-xxs" - ] - [ slab - Html.code - [] - [ "align-middle" ] - [ Html.text code ] - ] - ] - ) - - - --- ๐Ÿ’š - - -success : String -> Notification msg -success content = - Notification - Success - (hashString 0 content) - { sticky = False - , wasDismissed = False - } - (render content) - - -stickySuccess : String -> Notification msg -stickySuccess content = - Notification - Success - (hashString 0 content) - { sticky = True - , wasDismissed = False - } - (render content) - - - --- ๐Ÿฆ‰ - - -casual : String -> Notification msg -casual content = - Notification - Casual - (hashString 0 content) - { sticky = False - , wasDismissed = False - } - (render content) - - -stickyCasual : String -> Notification msg -stickyCasual content = - Notification - Casual - (hashString 0 content) - { sticky = True - , wasDismissed = False - } - (render content) - - - --- โš—๏ธ - - -render : String -> Html msg -render content = - content - |> String.lines - |> List.map String.trimLeft - |> String.join "\n" - |> Markdown.toHtml [] diff --git a/src/Library/Playlists.elm b/src/Library/Playlists.elm deleted file mode 100644 index f398b8b67..000000000 --- a/src/Library/Playlists.elm +++ /dev/null @@ -1,41 +0,0 @@ -module Playlists exposing (..) - -import Time - - - --- ๐ŸŒณ - - -type alias Playlist = - { autoGenerated : Maybe { level : Int } - , collection : Bool - , name : String - , public : Bool - , tracks : List PlaylistTrack - } - - -type alias PlaylistTrackWithoutMetadata = - { album : Maybe String - , artist : Maybe String - , title : String - } - - -type alias PlaylistTrack = - { album : Maybe String - , artist : Maybe String - , title : String - - -- - , insertedAt : Time.Posix - } - - -type alias IdentifiedPlaylistTrack = - ( Identifiers, PlaylistTrack ) - - -type alias Identifiers = - { index : Int } diff --git a/src/Library/Playlists/Encoding.elm b/src/Library/Playlists/Encoding.elm deleted file mode 100644 index 102427f7d..000000000 --- a/src/Library/Playlists/Encoding.elm +++ /dev/null @@ -1,66 +0,0 @@ -module Playlists.Encoding exposing (decoder, encode, encodePlaylistTrack, playlistTrackDecoder) - -import Json.Decode as Decode -import Json.Decode.Ext as Decode -import Json.Encode as Encode -import Json.Encode.Ext exposing (..) -import Playlists exposing (..) -import Time.Ext as Time - - - --- ENCODE - - -encode : Playlist -> Encode.Value -encode playlist = - Encode.object - [ ( "autoGenerated" - , case playlist.autoGenerated of - Just { level } -> - Encode.object - [ ( "level", Encode.int level ) ] - - Nothing -> - Encode.null - ) - , ( "collection", Encode.bool playlist.collection ) - , ( "name", Encode.string playlist.name ) - , ( "public", Encode.bool playlist.public ) - , ( "tracks", Encode.list encodePlaylistTrack playlist.tracks ) - ] - - -encodePlaylistTrack : PlaylistTrack -> Encode.Value -encodePlaylistTrack playlistTrack = - Encode.object - [ ( "album", encodeMaybe playlistTrack.album Encode.string ) - , ( "artist", encodeMaybe playlistTrack.artist Encode.string ) - , ( "title", Encode.string playlistTrack.title ) - - -- - , ( "insertAt", Time.encode playlistTrack.insertedAt ) - ] - - - --- DECODE - - -decoder : Decode.Decoder Playlist -decoder = - Decode.map5 Playlist - (Decode.field "autoGenerated" <| Decode.maybe <| Decode.map (\l -> { level = l }) <| Decode.field "level" Decode.int) - (Decode.optionalField "collection" Decode.bool False) - (Decode.field "name" Decode.string) - (Decode.optionalField "public" Decode.bool False) - (Decode.field "tracks" <| Decode.list playlistTrackDecoder) - - -playlistTrackDecoder : Decode.Decoder PlaylistTrack -playlistTrackDecoder = - Decode.map4 PlaylistTrack - (Decode.maybe <| Decode.field "album" Decode.string) - (Decode.maybe <| Decode.field "artist" Decode.string) - (Decode.field "title" Decode.string) - (Decode.optionalField "insertedAt" Time.decoder Time.default) diff --git a/src/Library/Playlists/Matching.elm b/src/Library/Playlists/Matching.elm deleted file mode 100644 index 1e47b9f4a..000000000 --- a/src/Library/Playlists/Matching.elm +++ /dev/null @@ -1,53 +0,0 @@ -module Playlists.Matching exposing (match) - -import Playlists exposing (..) -import Tracks exposing (IdentifiedTrack) - - - --- ๐Ÿ”ฑ - - -match : Playlist -> List IdentifiedTrack -> ( List IdentifiedTrack, List IdentifiedPlaylistTrack ) -match playlist = - List.foldl - (\( i, t ) ( identifiedTracks, remainingPlaylistTracks ) -> - let - im = - { album = t.tags.album - , artist = t.tags.artist - , title = t.tags.title - } - - ( matches, remainingPlaylistTracksWithoutMatches ) = - List.foldl - (\( pi, pt ) -> - if im.title == pt.title && im.album == pt.album && im.artist == pt.artist then - Tuple.mapBoth - ((::) ( playlistTrackIdentifiers i pi, t )) - identity - - else - Tuple.mapBoth - identity - ((::) ( pi, pt )) - ) - ( [], [] ) - remainingPlaylistTracks - in - ( identifiedTracks ++ matches - , remainingPlaylistTracksWithoutMatches - ) - ) - ( [] - , List.indexedMap (\idx -> Tuple.pair { index = idx }) playlist.tracks - ) - - - --- ใŠ™๏ธ - - -playlistTrackIdentifiers : Tracks.Identifiers -> Playlists.Identifiers -> Tracks.Identifiers -playlistTrackIdentifiers i pi = - { i | indexInPlaylist = Just pi.index } diff --git a/src/Library/Queue.elm b/src/Library/Queue.elm deleted file mode 100644 index 3188597f0..000000000 --- a/src/Library/Queue.elm +++ /dev/null @@ -1,78 +0,0 @@ -module Queue exposing (EngineItem, Item, makeEngineItem, makeItem, makeTrackUrl) - -import Dict exposing (Dict) -import List.Extra as List -import Sources exposing (Source) -import Sources.Processing exposing (HttpMethod(..)) -import Sources.Services -import Time -import Tracks exposing (IdentifiedTrack, Tags, Track) - - - --- ๐ŸŒณ - - -type alias Item = - { manualEntry : Bool - , identifiedTrack : IdentifiedTrack - } - - -type alias EngineItem = - { isCached : Bool - , isPreload : Bool - , progress : Maybe Float - , sourceId : String - , trackId : String - , trackTags : Tags - , trackPath : String - , url : String - } - - - --- ๐Ÿ”ฑ - - -makeEngineItem : Bool -> Time.Posix -> List Source -> List String -> Dict String Float -> Track -> EngineItem -makeEngineItem preload timestamp sources cachedTrackIds progressTable track = - { isCached = List.member track.id cachedTrackIds - , isPreload = preload - , progress = Dict.get track.id progressTable - , sourceId = track.sourceId - , trackId = track.id - , trackPath = track.path - , trackTags = track.tags - , url = makeTrackUrl timestamp sources track - } - - -makeItem : Bool -> IdentifiedTrack -> Item -makeItem isManualEntry identifiedTrack = - { manualEntry = isManualEntry - , identifiedTrack = identifiedTrack - } - - -makeTrackUrl : Time.Posix -> List Source -> Track -> String -makeTrackUrl timestamp sources track = - sources - |> List.find (.id >> (==) track.sourceId) - |> Maybe.map (makeTrackUrl_ timestamp track) - |> Maybe.withDefault "" - - - --- ใŠ™๏ธ - - -makeTrackUrl_ : Time.Posix -> Track -> Source -> String -makeTrackUrl_ timestamp track source = - Sources.Services.makeTrackUrl - source.service - timestamp - source.id - source.data - Get - track.path diff --git a/src/Library/Return/Ext.elm b/src/Library/Return/Ext.elm deleted file mode 100644 index abccc6e07..000000000 --- a/src/Library/Return/Ext.elm +++ /dev/null @@ -1,19 +0,0 @@ -module Return.Ext exposing (..) - -import Task - - - --- ๐Ÿ”ฑ - - -communicate : Cmd msg -> model -> ( model, Cmd msg ) -communicate c m = - ( m, c ) - - -task : msg -> Cmd msg -task msg = - msg - |> Task.succeed - |> Task.perform identity diff --git a/src/Library/Settings.elm b/src/Library/Settings.elm deleted file mode 100644 index 73199a090..000000000 --- a/src/Library/Settings.elm +++ /dev/null @@ -1,63 +0,0 @@ -module Settings exposing (Settings, decoder, encode) - -import Json.Decode as Json -import Json.Decode.Pipeline exposing (optional) -import Json.Encode -import Maybe.Extra as Maybe - - - --- ๐ŸŒณ - - -type alias Settings = - { backgroundImage : Maybe String - , coverSelectionReducesPool : Bool - , hideDuplicates : Bool - , lastFm : Maybe String - , processAutomatically : Bool - , rememberProgress : Bool - } - - - --- ENCODING - - -encode : Settings -> Json.Value -encode settings = - Json.Encode.object - [ ( "backgroundImage" - , Maybe.unwrap Json.Encode.null Json.Encode.string settings.backgroundImage - ) - , ( "coverSelectionReducesPool" - , Json.Encode.bool settings.coverSelectionReducesPool - ) - , ( "hideDuplicates" - , Json.Encode.bool settings.hideDuplicates - ) - , ( "lastFm" - , Maybe.unwrap Json.Encode.null Json.Encode.string settings.lastFm - ) - , ( "processAutomatically" - , Json.Encode.bool settings.processAutomatically - ) - , ( "rememberProgress" - , Json.Encode.bool settings.rememberProgress - ) - ] - - - --- DECODING - - -decoder : Json.Decoder Settings -decoder = - Json.succeed Settings - |> optional "backgroundImage" (Json.maybe Json.string) Nothing - |> optional "coverSelectionReducesPool" Json.bool True - |> optional "hideDuplicates" Json.bool False - |> optional "lastFm" (Json.maybe Json.string) Nothing - |> optional "processAutomatically" Json.bool True - |> optional "rememberProgress" Json.bool True diff --git a/src/Library/Sources.elm b/src/Library/Sources.elm deleted file mode 100644 index 250a231cf..000000000 --- a/src/Library/Sources.elm +++ /dev/null @@ -1,120 +0,0 @@ -module Sources exposing (..) - -import Conditional exposing (..) -import Dict exposing (Dict) -import Json.Decode -import Time - - - --- ๐ŸŒณ - - -type alias Source = - { id : String - , data : SourceData - , directoryPlaylists : Bool - , enabled : Bool - , service : Service - } - - - --- PIECES - - -type alias Property = - { key : String - , label : String - , placeholder : String - , password : Bool - } - - -type alias SourceData = - Dict String String - - - --- SERVICES - - -type Service - = AmazonS3 - | AzureBlob - | AzureFile - | Dropbox - | Google - | Ipfs - | WebDav - - -serviceDictionary : Dict String Service -serviceDictionary = - Dict.fromList - [ ( "amazons3", AmazonS3 ) - , ( "amazon_s3", AmazonS3 ) - , ( "azureblob", AzureBlob ) - , ( "azure_blob", AzureBlob ) - , ( "azurefile", AzureFile ) - , ( "azure_file", AzureFile ) - , ( "dropbox", Dropbox ) - , ( "google", Google ) - , ( "ipfs", Ipfs ) - , ( "webdav", WebDav ) - , ( "web_dav", WebDav ) - ] - - -serviceDecoder : Json.Decode.Decoder Service -serviceDecoder = - Json.Decode.andThen - (\string -> - serviceDictionary - |> Dict.get string - |> Maybe.map Json.Decode.succeed - |> Maybe.withDefault (Json.Decode.fail "Invalid source kind") - ) - Json.Decode.string - - - ---- ๐Ÿ”ฑ - - -enabledSourceIds : List Source -> List String -enabledSourceIds = - List.filterMap (\s -> ifThenElse s.enabled (Just s.id) Nothing) - - -setProperId : Int -> Time.Posix -> Source -> Source -setProperId n time source = - { source | id = String.fromInt (Time.posixToMillis time) ++ String.fromInt n } - - -worksOffline : Source -> Bool -worksOffline source = - case source.service of - AmazonS3 -> - False - - AzureBlob -> - False - - AzureFile -> - False - - Dropbox -> - False - - Google -> - False - - Ipfs -> - True - - WebDav -> - source.data - |> Dict.get "url" - |> Maybe.map (\u -> String.contains "localhost" u || String.contains "127.0.0.1" u) - |> Maybe.withDefault False diff --git a/src/Library/Sources/Encoding.elm b/src/Library/Sources/Encoding.elm deleted file mode 100644 index 722e008da..000000000 --- a/src/Library/Sources/Encoding.elm +++ /dev/null @@ -1,74 +0,0 @@ -module Sources.Encoding exposing (decode, decoder, encode, encodeData, serviceDecoder) - -{-| Encoding. --} - -import Dict -import Json.Decode as Decode -import Json.Encode as Encode -import Sources exposing (..) -import Sources.Services as Services - - - --- ENCODE - - -encode : Source -> Encode.Value -encode source = - Encode.object - [ ( "id", Encode.string source.id ) - , ( "data", encodeData source.data ) - , ( "directoryPlaylists", Encode.bool source.directoryPlaylists ) - , ( "enabled", Encode.bool source.enabled ) - , ( "service", Encode.string (Services.typeToKey source.service) ) - ] - - -encodeData : SourceData -> Encode.Value -encodeData data = - data - |> Dict.toList - |> List.map (Tuple.mapSecond Encode.string) - |> Encode.object - - - --- DECODE - - -decode : Decode.Value -> Maybe Source -decode value = - value - |> Decode.decodeValue decoder - |> Result.toMaybe - - -decoder : Decode.Decoder Source -decoder = - Decode.map5 Source - (Decode.field "id" Decode.string) - (Decode.field "data" (Decode.dict Decode.string)) - (Decode.field "directoryPlaylists" Decode.bool - |> Decode.maybe - |> Decode.map (Maybe.withDefault True) - ) - (Decode.field "enabled" Decode.bool - |> Decode.maybe - |> Decode.map (Maybe.withDefault True) - ) - (Decode.field "service" serviceDecoder) - - -serviceDecoder : Decode.Decoder Service -serviceDecoder = - Decode.andThen - (\key -> - case Services.keyToType key of - Just service -> - Decode.succeed service - - Nothing -> - Decode.fail "Unrecognizable source service" - ) - Decode.string diff --git a/src/Library/Sources/Pick.elm b/src/Library/Sources/Pick.elm deleted file mode 100644 index c9d4205ba..000000000 --- a/src/Library/Sources/Pick.elm +++ /dev/null @@ -1,30 +0,0 @@ -module Sources.Pick exposing (isMusicFile, selectMusicFiles) - -import Regex - - - --- ๐Ÿ”ฑ - - -isMusicFile : String -> Bool -isMusicFile = - Regex.contains musicFileRegex - - -selectMusicFiles : List String -> List String -selectMusicFiles = - List.filter isMusicFile - - - ------------------------------------------ --- ใŠ™๏ธ ------------------------------------------ - - -musicFileRegex : Regex.Regex -musicFileRegex = - "\\.(mp3|mp4|m4a|flac|ogg|opus|wav|webm)$" - |> Regex.fromStringWith { caseInsensitive = True, multiline = False } - |> Maybe.withDefault Regex.never diff --git a/src/Library/Sources/Processing.elm b/src/Library/Sources/Processing.elm deleted file mode 100644 index 8bf842674..000000000 --- a/src/Library/Sources/Processing.elm +++ /dev/null @@ -1,97 +0,0 @@ -module Sources.Processing exposing (..) - -import Sources exposing (Source, SourceData) -import Tracks exposing (Tags, Track) - - - --- ๐ŸŒณ - - -type Status - = Processing ( Source, List Track ) (List ( Source, List Track )) - | NotProcessing - - -type alias Arguments = - { origin : String - , sources : List Source - } - - - --- MARKERS & RESPONSES - - -type Marker - = TheBeginning - | InProgress String - | TheEnd - - -type alias PrepationAnswer marker = - { sourceData : SourceData - , marker : marker - } - - -type alias TreeAnswer marker = - { filePaths : List String - , marker : marker - } - - - --- CONTEXTS - - -type alias Context = - { filePaths : List String - , origin : String - , preparationMarker : Marker - , source : Source - , treeMarker : Marker - } - - -type alias ContextForTags = - { amount : Int - , nextFilePaths : List String - , receivedFilePaths : List String - , receivedTags : List (Maybe Tags) - , sourceId : String - , urlsForTags : List TagUrls - } - - -type alias ContextForTagsSync = - { receivedFilePaths : List String - , receivedTags : List (Maybe Tags) - , trackIds : List String - , urlsForTags : List TagUrls - } - - -type alias TagUrls = - { getUrl : String - , headUrl : String - } - - - --- HTTP - - -type HttpMethod - = Get - | Head - - -httpMethod : HttpMethod -> String -httpMethod method = - case method of - Get -> - "GET" - - Head -> - "HEAD" diff --git a/src/Library/Sources/Processing/Encoding.elm b/src/Library/Sources/Processing/Encoding.elm deleted file mode 100644 index 0401f6ea1..000000000 --- a/src/Library/Sources/Processing/Encoding.elm +++ /dev/null @@ -1,19 +0,0 @@ -module Sources.Processing.Encoding exposing (argumentsDecoder) - -{-| Encoding. --} - -import Json.Decode as Decode exposing (Decoder) -import Sources.Encoding as Sources -import Sources.Processing exposing (Arguments) - - - --- ๐Ÿ”ฑ - - -argumentsDecoder : Decoder Arguments -argumentsDecoder = - Decode.map2 Arguments - (Decode.field "origin" Decode.string) - (Decode.field "sources" <| Decode.list Sources.decoder) diff --git a/src/Library/Sources/Refresh/AccessToken.elm b/src/Library/Sources/Refresh/AccessToken.elm deleted file mode 100644 index 2152a5a96..000000000 --- a/src/Library/Sources/Refresh/AccessToken.elm +++ /dev/null @@ -1,39 +0,0 @@ -module Sources.Refresh.AccessToken exposing (..) - -import Json.Decode exposing (Decoder) -import Sources exposing (Service) -import Sources.Encoding exposing (serviceDecoder) - - - --- ๐ŸŒณ - - -type alias PortArguments = - { service : Service - , sourceId : String - , accessToken : String - - -- Unix timestamp in milliseconds - , expiresAt : Int - } - - - --- ๐Ÿ›  - - -portArgumentsDecoder : Decoder PortArguments -portArgumentsDecoder = - Json.Decode.map4 - (\service sourceId accessToken expiresAt -> - { service = service - , sourceId = sourceId - , accessToken = accessToken - , expiresAt = expiresAt - } - ) - (Json.Decode.field "service" serviceDecoder) - (Json.Decode.field "sourceId" Json.Decode.string) - (Json.Decode.field "accessToken" Json.Decode.string) - (Json.Decode.field "expiresAt" Json.Decode.int) diff --git a/src/Library/Sources/Services.elm b/src/Library/Sources/Services.elm deleted file mode 100644 index a3b16fcef..000000000 --- a/src/Library/Sources/Services.elm +++ /dev/null @@ -1,328 +0,0 @@ -module Sources.Services exposing (initialData, keyToType, labels, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties, typeToKey) - -{-| Service functions used in other modules. --} - -import Http -import Sources exposing (..) -import Sources.Processing exposing (..) -import Sources.Services.AmazonS3 as AmazonS3 -import Sources.Services.AzureBlob as AzureBlob -import Sources.Services.AzureFile as AzureFile -import Sources.Services.Dropbox as Dropbox -import Sources.Services.Google as Google -import Sources.Services.Ipfs as Ipfs -import Sources.Services.WebDav as WebDav -import Time - - - --- FUNCTIONS - - -initialData : Service -> SourceData -initialData service = - case service of - AmazonS3 -> - AmazonS3.initialData - - AzureBlob -> - AzureBlob.initialData - - AzureFile -> - AzureFile.initialData - - Dropbox -> - Dropbox.initialData - - Google -> - Google.initialData - - Ipfs -> - Ipfs.initialData - - WebDav -> - WebDav.initialData - - -makeTrackUrl : Service -> Time.Posix -> String -> SourceData -> HttpMethod -> String -> String -makeTrackUrl service = - case service of - AmazonS3 -> - AmazonS3.makeTrackUrl - - AzureBlob -> - AzureBlob.makeTrackUrl - - AzureFile -> - AzureFile.makeTrackUrl - - Dropbox -> - Dropbox.makeTrackUrl - - Google -> - Google.makeTrackUrl - - Ipfs -> - Ipfs.makeTrackUrl - - WebDav -> - WebDav.makeTrackUrl - - -makeTree : - Service - -> SourceData - -> Marker - -> Time.Posix - -> (Result Http.Error String -> msg) - -> Cmd msg -makeTree service = - case service of - AmazonS3 -> - AmazonS3.makeTree - - AzureBlob -> - AzureBlob.makeTree - - AzureFile -> - AzureFile.makeTree - - Dropbox -> - Dropbox.makeTree - - Google -> - Google.makeTree - - Ipfs -> - Ipfs.makeTree - - WebDav -> - WebDav.makeTree - - -parseErrorResponse : Service -> String -> Maybe String -parseErrorResponse service = - case service of - AmazonS3 -> - AmazonS3.parseErrorResponse - - AzureBlob -> - AzureBlob.parseErrorResponse - - AzureFile -> - AzureFile.parseErrorResponse - - Dropbox -> - Dropbox.parseErrorResponse - - Google -> - Google.parseErrorResponse - - Ipfs -> - Ipfs.parseErrorResponse - - WebDav -> - WebDav.parseErrorResponse - - -parsePreparationResponse : Service -> String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse service = - case service of - AmazonS3 -> - AmazonS3.parsePreparationResponse - - AzureBlob -> - AzureBlob.parsePreparationResponse - - AzureFile -> - AzureFile.parsePreparationResponse - - Dropbox -> - Dropbox.parsePreparationResponse - - Google -> - Google.parsePreparationResponse - - Ipfs -> - Ipfs.parsePreparationResponse - - WebDav -> - WebDav.parsePreparationResponse - - -parseTreeResponse : Service -> String -> Marker -> TreeAnswer Marker -parseTreeResponse service = - case service of - AmazonS3 -> - AmazonS3.parseTreeResponse - - AzureBlob -> - AzureBlob.parseTreeResponse - - AzureFile -> - AzureFile.parseTreeResponse - - Dropbox -> - Dropbox.parseTreeResponse - - Google -> - Google.parseTreeResponse - - Ipfs -> - Ipfs.parseTreeResponse - - WebDav -> - WebDav.parseTreeResponse - - -postProcessTree : Service -> List String -> List String -postProcessTree service = - case service of - AmazonS3 -> - AmazonS3.postProcessTree - - AzureBlob -> - AzureBlob.postProcessTree - - AzureFile -> - AzureFile.postProcessTree - - Dropbox -> - Dropbox.postProcessTree - - Google -> - Google.postProcessTree - - Ipfs -> - Ipfs.postProcessTree - - WebDav -> - WebDav.postProcessTree - - -prepare : - Service - -> String - -> SourceData - -> Marker - -> (Result Http.Error String -> msg) - -> Maybe (Cmd msg) -prepare service = - case service of - AmazonS3 -> - AmazonS3.prepare - - AzureBlob -> - AzureBlob.prepare - - AzureFile -> - AzureFile.prepare - - Dropbox -> - Dropbox.prepare - - Google -> - Google.prepare - - Ipfs -> - Ipfs.prepare - - WebDav -> - WebDav.prepare - - -properties : Service -> List Property -properties service = - case service of - AmazonS3 -> - AmazonS3.properties - - AzureBlob -> - AzureBlob.properties - - AzureFile -> - AzureFile.properties - - Dropbox -> - Dropbox.properties - - Google -> - Google.properties - - Ipfs -> - Ipfs.properties - - WebDav -> - WebDav.properties - - - --- KEYS & LABELS - - -keyToType : String -> Maybe Service -keyToType str = - case str of - "AmazonS3" -> - Just AmazonS3 - - "AzureBlob" -> - Just AzureBlob - - "AzureFile" -> - Just AzureFile - - "Dropbox" -> - Just Dropbox - - "Google" -> - Just Google - - "Ipfs" -> - Just Ipfs - - "WebDav" -> - Just WebDav - - _ -> - Nothing - - -typeToKey : Service -> String -typeToKey service = - case service of - AmazonS3 -> - "AmazonS3" - - AzureBlob -> - "AzureBlob" - - AzureFile -> - "AzureFile" - - Dropbox -> - "Dropbox" - - Google -> - "Google" - - Ipfs -> - "Ipfs" - - WebDav -> - "WebDav" - - -{-| Service labels. -Maps a service key to a label. --} -labels : List ( String, String ) -labels = - [ ( typeToKey AmazonS3, "Amazon S3" ) - , ( typeToKey AzureBlob, "Azure Blob Storage" ) - , ( typeToKey AzureFile, "Azure File Storage" ) - , ( typeToKey Dropbox, "Dropbox" ) - , ( typeToKey Ipfs, "IPFS" ) - , ( typeToKey WebDav, "WebDAV" ) - ] diff --git a/src/Library/Sources/Services/AmazonS3.elm b/src/Library/Sources/Services/AmazonS3.elm deleted file mode 100644 index 34cc66bca..000000000 --- a/src/Library/Sources/Services/AmazonS3.elm +++ /dev/null @@ -1,190 +0,0 @@ -module Sources.Services.AmazonS3 exposing (defaults, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties) - -{-| Amazon S3 Service. - -Resources: - - - - --} - -import Common -import Dict -import Http -import Sources exposing (Property, SourceData) -import Sources.Pick -import Sources.Processing exposing (..) -import Sources.Services.AmazonS3.Parser as Parser -import Sources.Services.AmazonS3.Presign exposing (..) -import Sources.Services.Common exposing (cleanPath, noPrep) -import Time - - - --- PROPERTIES --- ๐Ÿ“Ÿ - - -defaults = - { bucketName = "music" - , name = "Music from Amazon S3" - , region = "eu-west-1" - } - - -{-| The list of properties we need from the user. --} -properties : List Property -properties = - [ { key = "accessKey" - , label = "Access key" - , placeholder = "Fv6EWfLfCcMo" - , password = True - } - , { key = "secretKey" - , label = "Secret key" - , placeholder = "qeNcqiMpgqC8" - , password = True - } - , { key = "bucketName" - , label = "Bucket name" - , placeholder = "music" - , password = False - } - , { key = "region" - , label = "Region" - , placeholder = defaults.region - , password = False - } - , { key = "directoryPath" - , label = "Directory (Optional)" - , placeholder = "/" - , password = False - } - , { key = "host" - , label = "Host (Optional)" - , placeholder = "http://localhost:9000" - , password = False - } - ] - - -{-| Initial data set. --} -initialData : SourceData -initialData = - Dict.fromList - [ ( "accessKey", "" ) - , ( "bucketName", defaults.bucketName ) - , ( "directoryPath", "" ) - , ( "host", "" ) - , ( "name", defaults.name ) - , ( "region", defaults.region ) - , ( "secretKey", "" ) - ] - - - --- PREPARATION - - -prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) -prepare _ _ _ _ = - Nothing - - - --- TREE - - -{-| Create a directory tree. - -List all the tracks in the bucket. -Or a specific directory in the bucket. - --} -makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg -makeTree srcData marker currentTime resultMsg = - let - directoryPath = - srcData - |> Dict.get "directoryPath" - |> Maybe.withDefault "" - |> cleanPath - - initialParams = - [ ( "list-type", "2" ) - , ( "max-keys", "500" ) - ] - - prefix = - if String.length directoryPath > 0 then - [ ( "prefix", directoryPath ) ] - - else - [] - - continuation = - case marker of - InProgress s -> - [ ( "continuation-token", s ) ] - - _ -> - [] - - params = - initialParams ++ prefix ++ continuation - - url = - presignedUrl Get (60 * 5) params currentTime srcData "/" - in - Http.get - { url = url - , expect = Http.expectStringResponse resultMsg Common.translateHttpResponse - } - - -{-| Re-export parser functions. --} -parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse = - noPrep - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse = - Parser.parseTreeResponse - - -parseErrorResponse : String -> Maybe String -parseErrorResponse = - Parser.parseErrorResponse - - - --- POST - - -{-| Post process the tree results. - -Make sure we only use music files that we can use. - --} -postProcessTree : List String -> List String -postProcessTree = - Sources.Pick.selectMusicFiles - - - --- TRACK URL - - -{-| Create a public url for a file. - -We need this to play the track. -Creates a presigned url that's valid for 48 hours - --} -makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String -makeTrackUrl currentTime _ srcData method pathToFile = - presignedUrl method 172800 [] currentTime srcData pathToFile diff --git a/src/Library/Sources/Services/AmazonS3/Parser.elm b/src/Library/Sources/Services/AmazonS3/Parser.elm deleted file mode 100644 index 17e6ef3c8..000000000 --- a/src/Library/Sources/Services/AmazonS3/Parser.elm +++ /dev/null @@ -1,66 +0,0 @@ -module Sources.Services.AmazonS3.Parser exposing (parseErrorResponse, parseTreeResponse) - -import Conditional exposing (..) -import Sources.Processing exposing (Marker(..), TreeAnswer) -import Xml.Decode exposing (..) - - - --- TREE - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse response _ = - response - |> decodeString - (map2 - (\f m -> { filePaths = f, marker = m }) - filePathsDecoder - markerDecoder - ) - |> Result.withDefault { filePaths = [], marker = TheEnd } - - -filePathsDecoder : Decoder (List String) -filePathsDecoder = - string - |> single - |> path [ "Key" ] - |> list - |> path [ "Contents" ] - - -markerDecoder : Decoder Marker -markerDecoder = - map2 - (\a b -> - Maybe.withDefault - TheEnd - (Maybe.map2 - (\isTruncated token -> - ifThenElse (isTruncated == "true") (InProgress token) TheEnd - ) - a - b - ) - ) - (maybe <| path [ "IsTruncated" ] <| single string) - (maybe <| path [ "NextContinuationToken" ] <| single string) - - - --- ERROR - - -parseErrorResponse : String -> Maybe String -parseErrorResponse response = - response - |> decodeString errorMessagesDecoder - |> Result.toMaybe - - -errorMessagesDecoder : Decoder String -errorMessagesDecoder = - string - |> single - |> path [ "Message" ] diff --git a/src/Library/Sources/Services/AmazonS3/Presign.elm b/src/Library/Sources/Services/AmazonS3/Presign.elm deleted file mode 100644 index 6510534c6..000000000 --- a/src/Library/Sources/Services/AmazonS3/Presign.elm +++ /dev/null @@ -1,208 +0,0 @@ -module Sources.Services.AmazonS3.Presign exposing (presignedUrl) - -import Binary exposing (Bits) -import Common -import Cryptography.Hmac as Hmac -import DateFormat as Date -import Dict.Ext as Dict -import Hex -import Maybe.Extra as Maybe -import Regex -import SHA -import Sources exposing (SourceData) -import Sources.Processing exposing (HttpMethod, httpMethod) -import String.Ext as String -import Time -import Url - - - --- ๐Ÿ”ฑ - - -presignedUrl : - HttpMethod - -> Int - -> List ( String, String ) - -> Time.Posix - -> SourceData - -> String - -> String -presignedUrl method lifeExpectancyInSeconds extraParams currentTime srcData pathToFile = - let - aws = - srcData - - region = - Dict.fetchUnknown "region" aws - - bucketName = - Dict.fetchUnknown "bucketName" aws - - customHost = - case Dict.fetch "host" "" aws of - "" -> - Nothing - - x -> - Just x - - host = - case customHost of - Just h -> - h - |> String.chopStart "http://" - |> String.chopStart "https://" - |> String.chopEnd "/" - - Nothing -> - case String.trim region of - "" -> - bucketName ++ ".s3.amazonaws.com" - - r -> - bucketName ++ ".s3." ++ r ++ ".amazonaws.com" - - protocol = - if String.contains "http://" (Maybe.withDefault "" customHost) then - "http://" - - else - "https://" - - -- {var} Paths - filePathPrefix = - if Maybe.isJust customHost then - bucketName ++ "/" - - else - "" - - filePath = - pathToFile - |> String.chopStart "/" - |> String.split "/" - |> List.map (Url.percentEncode >> encodeAdditionalCharacters) - |> String.join "/" - |> String.append ("/" ++ filePathPrefix) - - -- {var} Time - -- timestamp -> 20130721T201207Z - -- date -> 20130721 - timestamp = - Date.format - [ Date.yearNumber - , Date.monthFixed - , Date.dayOfMonthFixed - , Date.text "T" - , Date.hourMilitaryFixed - , Date.minuteFixed - , Date.secondFixed - , Date.text "Z" - ] - Time.utc - currentTime - - date = - Date.format - [ Date.yearNumber - , Date.monthFixed - , Date.dayOfMonthFixed - ] - Time.utc - currentTime - - -- Request - credential = - [ Dict.fetchUnknown "accessKey" aws - , date - , region - , "s3" - , "aws4_request" - ] - |> String.join "/" - - queryString = - [ ( "X-Amz-Algorithm", "AWS4-HMAC-SHA256" ) - , ( "X-Amz-Credential", credential ) - , ( "X-Amz-Date", timestamp ) - , ( "X-Amz-Expires", String.fromInt lifeExpectancyInSeconds ) - , ( "X-Amz-SignedHeaders", "host" ) - ] - |> List.append extraParams - |> List.sortBy Tuple.first - |> Common.queryString - |> String.dropLeft 1 - |> encodeAdditionalCharacters - - request = - String.join - "\n" - [ httpMethod method - , filePath - , queryString - , "host:" ++ host - , "" - , "host" - , "UNSIGNED-PAYLOAD" - ] - - -- String to sign - stringToSign = - String.join - "\n" - [ "AWS4-HMAC-SHA256" - , timestamp - , String.join "/" [ date, region, "s3", "aws4_request" ] - - -- - , request - |> Binary.fromStringAsUtf8 - |> SHA.sha256 - |> Binary.toHex - |> String.toLower - ] - - -- Signature - signature = - ("AWS4" ++ Dict.fetchUnknown "secretKey" aws) - |> Binary.fromStringAsUtf8 - |> hmacSha256 date - |> hmacSha256 region - |> hmacSha256 "s3" - |> hmacSha256 "aws4_request" - |> hmacSha256 stringToSign - |> Binary.toHex - |> String.toLower - in - String.concat - [ protocol - , host - , filePath - , "?" - , queryString - , "&X-Amz-Signature=" - , signature - ] - - - --- โš—๏ธ - - -encodeAdditionalCharacters : String -> String -encodeAdditionalCharacters query = - Regex.replace - (Maybe.withDefault Regex.never <| Regex.fromString "[!*'()]") - (\{ match } -> - match - |> String.toList - |> List.map (Char.toCode >> Hex.toString >> String.toUpper >> (++) "%") - |> String.concat - ) - query - - -hmacSha256 : String -> Bits -> Bits -hmacSha256 = - Hmac.encrypt64 SHA.sha256 diff --git a/src/Library/Sources/Services/Azure/Authorization.elm b/src/Library/Sources/Services/Azure/Authorization.elm deleted file mode 100755 index 8c5a8b0fe..000000000 --- a/src/Library/Sources/Services/Azure/Authorization.elm +++ /dev/null @@ -1,225 +0,0 @@ -module Sources.Services.Azure.Authorization exposing (Computation(..), SignatureDependencies, StorageMethod(..), makeSignature, presignedUrl) - -{-| Resources: - - - - - - --} - -import Binary -import BinaryBase64 -import Common -import Cryptography.Hmac as Hmac -import DateFormat as Date -import Dict.Ext as Dict -import SHA -import Sources exposing (SourceData) -import Sources.Processing exposing (HttpMethod) -import String.Ext as String -import Time -import Url - - - --- Types - - -type Computation - = List - | Read - - -type StorageMethod - = Blob - | File - - - --- Public functions - - -presignedUrl : - StorageMethod - -> Computation - -> HttpMethod - -> Int - -> Time.Posix - -> SourceData - -> String - -> List ( String, String ) - -> String -presignedUrl storageMethod computation _ _ currentTime srcData pathToFile params = - let - azure = - srcData - - accountName = - Dict.fetchUnknown "accountName" azure - - accountKey = - Dict.fetchUnknown "accountKey" azure - - container = - Dict.fetchUnknown "container" azure - - -- {var} Time (y-MM-ddTHH:mmZ) - expiryTime = - Date.format - [ Date.yearNumber - , Date.text "-" - , Date.monthFixed - , Date.text "-" - , Date.dayOfMonthFixed - , Date.text "T" - , Date.hourMilitaryFixed - , Date.text ":" - , Date.minuteFixed - , Date.text "Z" - ] - Time.utc - (currentTime - |> Time.posixToMillis - |> (+) 3600000 - |> Time.millisToPosix - ) - - -- {var} Other - permissions = - case computation of - List -> - "l" - - Read -> - "r" - - resourceType = - case storageMethod of - Blob -> - "blob" - - File -> - "file" - - resType = - case storageMethod of - Blob -> - "container" - - File -> - "directory" - - -- Signature - signatureStuff = - { accountKey = accountKey - , accountName = accountName - , expiryTime = expiryTime - , permissions = permissions - , protocol = "https" - , resources = "co" - , services = "bf" - , startTime = "" - , version = "2017-04-17" - } - in - String.concat - [ "https://" - , Url.percentEncode accountName - , "." - , Url.percentEncode resourceType - , ".core.windows.net/" - , Url.percentEncode container - , "/" - , Url.percentEncode (String.chopStart "/" pathToFile) - - -- Start query params - , case Common.queryString params of - "" -> - "?" - - qs -> - qs - - -- Query params for certain requests - , case computation of - List -> - "&restype=" ++ resType ++ "&comp=list" - - _ -> - "" - - -- Signature things - , "&sv=" - , Url.percentEncode signatureStuff.version - , "&ss=" - , Url.percentEncode signatureStuff.services - , "&srt=" - , Url.percentEncode signatureStuff.resources - , "&sp=" - , Url.percentEncode signatureStuff.permissions - , "&se=" - , Url.percentEncode signatureStuff.expiryTime - , "&spr=" - , Url.percentEncode signatureStuff.protocol - , "&sig=" - , Url.percentEncode (makeSignature signatureStuff) - ] - - - --- Signature - - -type alias SignatureDependencies = - { accountKey : String - , accountName : String - , expiryTime : String - , permissions : String - , protocol : String - , resources : String - , services : String - , startTime : String - , version : String - } - - -{-| Make a signature. - - >>> makeSignature { accountKey = "93K17Co74T2lDHk2rA+wmb/avIAS6u6lPnZrk2hyT+9+aov82qNhrcXSNGZCzm9mjd4d75/oxxOr6r1JVpgTLA==", accountName = "tsmatsuzsttest0001", expiryTime = "2016-07-08T04:41:20Z", permissions = "rwdlacup", protocol = "https", resources = "sco", services = "bfqt", startTime = "2016-06-29T04:41:20Z", version = "2015-04-05" } - "+XuDjuLE1Sv/FrJTLz8YjsaDukWNTKX7e8G8Ew+5aps=" - --} -makeSignature : SignatureDependencies -> String -makeSignature { accountKey, accountName, expiryTime, permissions, protocol, resources, services, startTime, version } = - let - message = - -- accountname + "\n" + - -- signedpermissions + "\n" + - -- signedservice + "\n" + - -- signedresourcetype + "\n" + - -- signedstart + "\n" + - -- signedexpiry + "\n" + - -- signedIP + "\n" + - -- signedProtocol + "\n" + - -- signedversion + "\n" - String.join "\n" - [ accountName - , permissions - , services - , resources - , startTime - , expiryTime - , "" - , protocol - , version ++ "\n" - ] - in - accountKey - |> BinaryBase64.decode - |> Result.withDefault [] - |> List.map (Binary.fromDecimal >> Binary.ensureSize 8) - |> Binary.concat - |> Hmac.encrypt64 SHA.sha256 message - |> Binary.chunksOf 8 - |> List.map Binary.toDecimal - |> BinaryBase64.encode diff --git a/src/Library/Sources/Services/Azure/BlobParser.elm b/src/Library/Sources/Services/Azure/BlobParser.elm deleted file mode 100755 index c7d6f3900..000000000 --- a/src/Library/Sources/Services/Azure/BlobParser.elm +++ /dev/null @@ -1,64 +0,0 @@ -module Sources.Services.Azure.BlobParser exposing (parseErrorResponse, parseTreeResponse) - -import Sources.Processing exposing (Marker(..), TreeAnswer) -import Xml.Decode exposing (..) - - - --- TREE - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse response _ = - response - |> decodeString - (map2 - (\f m -> { filePaths = f, marker = m }) - filePathsDecoder - markerDecoder - ) - |> Result.withDefault { filePaths = [], marker = TheEnd } - - -filePathsDecoder : Decoder (List String) -filePathsDecoder = - string - |> single - |> path [ "Name" ] - |> list - |> path [ "Blobs", "Blob" ] - - -markerDecoder : Decoder Marker -markerDecoder = - map - (\maybeNextMarker -> - case maybeNextMarker of - Just "" -> - TheEnd - - Just nextMarker -> - InProgress nextMarker - - Nothing -> - TheEnd - ) - (maybe <| path [ "NextMarker" ] <| single string) - - - --- ERROR - - -parseErrorResponse : String -> Maybe String -parseErrorResponse response = - response - |> decodeString errorMessagesDecoder - |> Result.toMaybe - - -errorMessagesDecoder : Decoder String -errorMessagesDecoder = - string - |> single - |> path [ "Message" ] diff --git a/src/Library/Sources/Services/Azure/FileMarker.elm b/src/Library/Sources/Services/Azure/FileMarker.elm deleted file mode 100755 index 5bbcc497b..000000000 --- a/src/Library/Sources/Services/Azure/FileMarker.elm +++ /dev/null @@ -1,154 +0,0 @@ -module Sources.Services.Azure.FileMarker exposing (MarkerItem(..), concat, itemToString, paramSeparator, prefixer, removeOne, separator, stringToItem, takeOne) - -{-| Custom `Marker` for the Azure File API. - -The Azure File API currently doesn't make a recursive list, -so we have to manage that ourselves. - -This custom marker is a combination of: - - - The default `marker` param, see URI parameters at - - Our custom logic to handle recursive listings - -Example: InProgress "dir=example ยถ param=defaultMarker" - --} - -import Sources.Processing exposing (Marker(..)) - - -type MarkerItem - = Directory String - | Param { directory : String, marker : String } - - -separator : String -separator = - " ษ‘ " - - -prefixer : String -prefixer = - " ฮฒ " - - -paramSeparator : String -paramSeparator = - " ษฃ " - - - --- IN - - -concat : List MarkerItem -> Marker -> Marker -concat list marker = - let - listStringified = - List.map itemToString list - - result = - case marker of - InProgress m -> - [ listStringified, String.split separator m ] - |> List.concat - |> String.join separator - - _ -> - String.join separator listStringified - in - case result of - "" -> - TheEnd - - r -> - InProgress r - - - --- OUT - - -{-| Take the first item and return it. --} -takeOne : Marker -> Maybe MarkerItem -takeOne marker = - case marker of - InProgress m -> - m - |> String.split separator - |> List.head - |> Maybe.andThen stringToItem - - _ -> - Nothing - - -{-| Remove the first item if there is one. --} -removeOne : Marker -> Marker -removeOne marker = - case marker of - InProgress m -> - let - tmp = - m - |> String.split separator - |> List.drop 1 - |> String.join separator - in - case tmp of - "" -> - TheEnd - - x -> - InProgress x - - _ -> - TheEnd - - - --- CONVERSIONS - - -itemToString : MarkerItem -> String -itemToString item = - case item of - Directory d -> - "dir" ++ prefixer ++ d - - Param { directory, marker } -> - "par" ++ prefixer ++ directory ++ paramSeparator ++ marker - - -stringToItem : String -> Maybe MarkerItem -stringToItem string = - let - exploded = - String.split prefixer string - in - case List.head exploded of - Just "dir" -> - exploded - |> List.drop 1 - |> String.join prefixer - |> Directory - |> Just - - Just "par" -> - exploded - |> List.drop 1 - |> String.join prefixer - |> String.split paramSeparator - |> (\x -> - case x of - [ dir, mar ] -> - Just (Param { directory = dir, marker = mar }) - - _ -> - Nothing - ) - - _ -> - Nothing diff --git a/src/Library/Sources/Services/Azure/FileParser.elm b/src/Library/Sources/Services/Azure/FileParser.elm deleted file mode 100755 index ed1c82036..000000000 --- a/src/Library/Sources/Services/Azure/FileParser.elm +++ /dev/null @@ -1,98 +0,0 @@ -module Sources.Services.Azure.FileParser exposing (parseErrorResponse, parseTreeResponse) - -import Sources.Processing exposing (Marker(..), TreeAnswer) -import Sources.Services.Azure.FileMarker as FileMarker exposing (MarkerItem(..)) -import Sources.Services.Common exposing (cleanPath) -import Xml.Decode exposing (..) - - - --- TREE - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse response previousMarker = - response - |> decodeString (treeDecoder previousMarker) - |> Result.withDefault { filePaths = [], marker = TheEnd } - - -treeDecoder : Marker -> Decoder (TreeAnswer Marker) -treeDecoder previousMarker = - usedDirectoryDecoder - |> map cleanPath - |> andThen - (\usedDirectory -> - map2 - (\a b -> ( usedDirectory, a, b )) - (map (List.map <| String.append usedDirectory) filePathsDecoder) - (map (List.map <| String.append usedDirectory) directoryPathsDecoder) - ) - |> andThen - (\( usedDirectory, filePaths, directoryPaths ) -> - previousMarker - |> FileMarker.removeOne - |> FileMarker.concat (List.map Directory directoryPaths) - |> markerDecoder usedDirectory - |> map (\marker -> { filePaths = filePaths, marker = marker }) - ) - - -usedDirectoryDecoder : Decoder String -usedDirectoryDecoder = - stringAttr "DirectoryPath" - - -filePathsDecoder : Decoder (List String) -filePathsDecoder = - string - |> single - |> path [ "Name" ] - |> list - |> path [ "Entries", "File" ] - - -directoryPathsDecoder : Decoder (List String) -directoryPathsDecoder = - string - |> single - |> path [ "Name" ] - |> list - |> path [ "Entries", "Directory" ] - - -markerDecoder : String -> Marker -> Decoder Marker -markerDecoder usedDirectory markerWithDirectories = - map - (\maybeNextMarker -> - case maybeNextMarker of - Just "" -> - markerWithDirectories - - Just marker -> - FileMarker.concat - [ Param { directory = usedDirectory, marker = marker } ] - markerWithDirectories - - Nothing -> - markerWithDirectories - ) - (maybe <| path [ "NextMarker" ] <| single string) - - - --- ERROR - - -parseErrorResponse : String -> Maybe String -parseErrorResponse response = - response - |> decodeString errorMessagesDecoder - |> Result.toMaybe - - -errorMessagesDecoder : Decoder String -errorMessagesDecoder = - string - |> single - |> path [ "Message" ] diff --git a/src/Library/Sources/Services/AzureBlob.elm b/src/Library/Sources/Services/AzureBlob.elm deleted file mode 100644 index d9ca88f05..000000000 --- a/src/Library/Sources/Services/AzureBlob.elm +++ /dev/null @@ -1,168 +0,0 @@ -module Sources.Services.AzureBlob exposing (defaults, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties) - -{-| Microsoft Azure Blob Service. - -Resources: - - - - --} - -import Common -import Dict -import Http -import Sources exposing (Property, SourceData) -import Sources.Pick -import Sources.Processing exposing (..) -import Sources.Services.Azure.Authorization exposing (..) -import Sources.Services.Azure.BlobParser as Parser -import Sources.Services.Common exposing (cleanPath, noPrep) -import Time - - - --- PROPERTIES --- ๐Ÿ“Ÿ - - -defaults = - { name = "Music from Azure Blob Storage" - } - - -{-| The list of properties we need from the user. - -Tuple: (property, label, placeholder, isPassword) -Will be used for the forms. - --} -properties : List Property -properties = - [ { key = "accountName" - , label = "Account name" - , placeholder = "myaccount" - , password = False - } - , { key = "accountKey" - , label = "Account key" - , placeholder = "MXFPDkaN4KBT" - , password = True - } - , { key = "container" - , label = "Container" - , placeholder = "music" - , password = False - } - , { key = "directoryPath" - , label = "Directory (aka. Prefix, Optional)" - , placeholder = "/" - , password = False - } - ] - - -{-| Initial data set. --} -initialData : SourceData -initialData = - Dict.fromList - [ ( "accountName", "" ) - , ( "accountKey", "" ) - , ( "container", "" ) - , ( "directoryPath", "" ) - , ( "name", defaults.name ) - ] - - - --- PREPARATION - - -prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) -prepare _ _ _ _ = - Nothing - - - --- TREE - - -{-| Create a directory tree. - -List all the tracks in the container. -Or a specific directory in the container. - --} -makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg -makeTree srcData marker currentTime resultMsg = - let - directoryPath = - srcData - |> Dict.get "directoryPath" - |> Maybe.withDefault "" - |> cleanPath - - baseParams = - [ ( "maxresults", "1000" ) ] - - params = - case marker of - InProgress s -> - [ ( "marker", s ) ] - - _ -> - [] - - url = - presignedUrl Blob List Get 1 currentTime srcData directoryPath (baseParams ++ params) - in - Http.get - { url = url - , expect = Http.expectStringResponse resultMsg Common.translateHttpResponse - } - - -{-| Re-export parser functions. --} -parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse = - noPrep - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse = - Parser.parseTreeResponse - - -parseErrorResponse : String -> Maybe String -parseErrorResponse = - Parser.parseErrorResponse - - - --- POST - - -{-| Post process the tree results. - -!!! Make sure we only use music files that we can use. - --} -postProcessTree : List String -> List String -postProcessTree = - Sources.Pick.selectMusicFiles - - - --- TRACK URL - - -{-| Create a public url for a file. - -We need this to play the track. -(!) Creates a presigned url that's valid for 48 hours - --} -makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String -makeTrackUrl currentTime _ srcData _ pathToFile = - presignedUrl Blob Read Get 48 currentTime srcData pathToFile [] diff --git a/src/Library/Sources/Services/AzureFile.elm b/src/Library/Sources/Services/AzureFile.elm deleted file mode 100644 index e49a68bb8..000000000 --- a/src/Library/Sources/Services/AzureFile.elm +++ /dev/null @@ -1,172 +0,0 @@ -module Sources.Services.AzureFile exposing (defaults, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties) - -{-| Microsoft Azure File Service. - -Resources: - - - - --} - -import Common -import Dict -import Http -import Sources exposing (Property, SourceData) -import Sources.Pick -import Sources.Processing exposing (..) -import Sources.Services.Azure.Authorization exposing (..) -import Sources.Services.Azure.FileMarker as FileMarker exposing (MarkerItem(..)) -import Sources.Services.Azure.FileParser as Parser -import Sources.Services.Common exposing (cleanPath, noPrep) -import Time - - - --- PROPERTIES --- ๐Ÿ“Ÿ - - -defaults = - { name = "Music from Azure File Storage" - } - - -{-| The list of properties we need from the user. - -Tuple: (property, label, placeholder, isPassword) -Will be used for the forms. - --} -properties : List Property -properties = - [ { key = "accountName" - , label = "Account name" - , placeholder = "myaccount" - , password = False - } - , { key = "accountKey" - , label = "Account key" - , placeholder = "MXFPDkaN4KBT" - , password = True - } - , { key = "container" - , label = "Share name" - , placeholder = "music" - , password = False - } - , { key = "directoryPath" - , label = "Directory (aka. Prefix, Optional)" - , placeholder = "/" - , password = False - } - ] - - -{-| Initial data set. --} -initialData : SourceData -initialData = - Dict.fromList - [ ( "accountName", "" ) - , ( "accountKey", "" ) - , ( "container", "" ) - , ( "directoryPath", "" ) - , ( "name", defaults.name ) - ] - - - --- PREPARATION - - -prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) -prepare _ _ _ _ = - Nothing - - - --- TREE - - -{-| Create a directory tree. - -List all the tracks in the container. -Or a specific directory in the container. - --} -makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg -makeTree srcData marker currentTime resultMsg = - let - directoryPathFromSrcData = - srcData - |> Dict.get "directoryPath" - |> Maybe.withDefault "" - |> cleanPath - - baseParams = - [ ( "maxresults", "1000" ) ] - - ( directoryPath, params ) = - case FileMarker.takeOne marker of - Just (Directory directory) -> - Tuple.pair directory [] - - Just (Param param) -> - Tuple.pair param.directory [ ( "marker", param.marker ) ] - - _ -> - Tuple.pair directoryPathFromSrcData [] - - url = - presignedUrl File List Get 1 currentTime srcData directoryPath (baseParams ++ params) - in - Http.get - { url = url - , expect = Http.expectStringResponse resultMsg Common.translateHttpResponse - } - - -{-| Re-export parser functions. --} -parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse = - noPrep - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse = - Parser.parseTreeResponse - - -parseErrorResponse : String -> Maybe String -parseErrorResponse = - Parser.parseErrorResponse - - - --- POST - - -{-| Post process the tree results. - -!!! Make sure we only use music files that we can use. - --} -postProcessTree : List String -> List String -postProcessTree = - Sources.Pick.selectMusicFiles - - - --- TRACK URL - - -{-| Create a public url for a file. - -We need this to play the track. -(!) Creates a presigned url that's valid for 48 hours - --} -makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String -makeTrackUrl currentTime _ srcData _ pathToFile = - presignedUrl File Read Get 48 currentTime srcData pathToFile [] diff --git a/src/Library/Sources/Services/Common.elm b/src/Library/Sources/Services/Common.elm deleted file mode 100644 index 286e3e472..000000000 --- a/src/Library/Sources/Services/Common.elm +++ /dev/null @@ -1,49 +0,0 @@ -module Sources.Services.Common exposing (cleanPath, noPrep) - -import Sources exposing (..) -import Sources.Processing exposing (..) -import String.Ext as String -import Time - - - --- PATHS - - -{-| Clean a path. - - >>> cleanPath " " - "" - - >>> cleanPath "/example" - "example/" - - >>> cleanPath "example" - "example/" - - >>> cleanPath "example/" - "example/" - --} -cleanPath : String -> String -cleanPath dirtyPath = - dirtyPath - |> String.trim - |> String.chopStart "/" - |> String.chopEnd "/" - |> (\p -> - if String.isEmpty p then - p - - else - p ++ "/" - ) - - - --- PARSING - - -noPrep : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -noPrep _ _ srcData _ = - { sourceData = srcData, marker = TheEnd } diff --git a/src/Library/Sources/Services/Dropbox.elm b/src/Library/Sources/Services/Dropbox.elm deleted file mode 100644 index 130b52d4f..000000000 --- a/src/Library/Sources/Services/Dropbox.elm +++ /dev/null @@ -1,230 +0,0 @@ -module Sources.Services.Dropbox exposing (authorizationSourceData, authorizationUrl, defaults, getProperDirectoryPath, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties) - -{-| Dropbox Service. --} - -import Base64 -import Common -import Dict -import Dict.Ext as Dict -import Http -import Json.Decode -import Json.Encode -import Sources exposing (Property, SourceData) -import Sources.Pick -import Sources.Processing exposing (..) -import Sources.Services.Common exposing (cleanPath, noPrep) -import Sources.Services.Dropbox.Parser as Parser -import Time - - - --- PROPERTIES --- ๐Ÿ“Ÿ - - -defaults = - { appKey = "kwsydtrzban41zr" - , name = "Music from Dropbox" - } - - -{-| The list of properties we need from the user. - -Tuple: (property, label, placeholder, isPassword) -Will be used for the forms. - --} -properties : List Property -properties = - [ { key = "directoryPath" - , label = "Directory (Optional)" - , placeholder = "/" - , password = False - } - , { key = "appKey" - , label = "App key" - , placeholder = defaults.appKey - , password = False - } - ] - - -{-| Initial data set. --} -initialData : SourceData -initialData = - Dict.fromList - [ ( "appKey", defaults.appKey ) - , ( "directoryPath", "" ) - , ( "name", defaults.name ) - ] - - - --- AUTHORIZATION - - -{-| Authorization url. --} -authorizationUrl : SourceData -> String -> String -authorizationUrl sourceData origin = - let - encodeData data = - data - |> Dict.toList - |> List.map (Tuple.mapSecond Json.Encode.string) - |> Json.Encode.object - - state = - sourceData - |> encodeData - |> Json.Encode.encode 0 - |> Base64.encode - in - [ ( "response_type", "token" ) - , ( "client_id", Dict.fetch "appKey" "unknown" sourceData ) - , ( "redirect_uri", origin ++ "?path=sources/new/dropbox" ) - , ( "state", state ) - ] - |> Common.queryString - |> String.append "https://www.dropbox.com/oauth2/authorize" - - -{-| Authorization source data. --} -authorizationSourceData : { codeOrToken : Maybe String, state : Maybe String } -> SourceData -authorizationSourceData args = - args.state - |> Maybe.andThen (Base64.decode >> Result.toMaybe) - |> Maybe.withDefault "{}" - |> Json.Decode.decodeString (Json.Decode.dict Json.Decode.string) - |> Result.withDefault Dict.empty - |> Dict.unionFlipped initialData - |> Dict.update "accessToken" (\_ -> args.codeOrToken) - - - --- PREPARATION - - -prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) -prepare _ _ _ _ = - Nothing - - - --- TREE - - -{-| Create a directory tree. - -List all the tracks in the bucket. -Or a specific directory in the bucket. - --} -makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg -makeTree srcData marker _ resultMsg = - let - accessToken = - Dict.fetch "accessToken" "" srcData - - body = - (case marker of - TheBeginning -> - [ ( "limit", Json.Encode.int 2000 ) - , ( "path", Json.Encode.string (getProperDirectoryPath srcData) ) - , ( "recursive", Json.Encode.bool True ) - ] - - InProgress cursor -> - [ ( "cursor", Json.Encode.string cursor ) - ] - - TheEnd -> - [] - ) - |> Json.Encode.object - |> Http.jsonBody - - url = - case marker of - TheBeginning -> - "https://api.dropboxapi.com/2/files/list_folder" - - InProgress _ -> - "https://api.dropboxapi.com/2/files/list_folder/continue" - - TheEnd -> - "" - in - Http.request - { method = "POST" - , headers = [ Http.header "Authorization" ("Bearer " ++ accessToken) ] - , url = url - , body = body - , expect = Http.expectStringResponse resultMsg Common.translateHttpResponse - , timeout = Nothing - , tracker = Nothing - } - - -getProperDirectoryPath : SourceData -> String -getProperDirectoryPath srcData = - let - path = - srcData - |> Dict.get "directoryPath" - |> Maybe.withDefault "" - |> cleanPath - in - if path == "" then - "" - - else - "/" ++ path - - -{-| Re-export parser functions. --} -parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse = - noPrep - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse = - Parser.parseTreeResponse - - -parseErrorResponse : String -> Maybe String -parseErrorResponse = - Parser.parseErrorResponse - - - --- POST - - -{-| Post process the tree results. - -!!! Make sure we only use music files that we can use. - --} -postProcessTree : List String -> List String -postProcessTree = - Sources.Pick.selectMusicFiles - - - --- TRACK URL - - -{-| Create a public url for a file. - -We need this to play the track. - --} -makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String -makeTrackUrl _ _ srcData _ pathToFile = - "dropbox://" ++ Dict.fetch "accessToken" "" srcData ++ "@" ++ pathToFile diff --git a/src/Library/Sources/Services/Dropbox/Parser.elm b/src/Library/Sources/Services/Dropbox/Parser.elm deleted file mode 100755 index 80dbb1982..000000000 --- a/src/Library/Sources/Services/Dropbox/Parser.elm +++ /dev/null @@ -1,43 +0,0 @@ -module Sources.Services.Dropbox.Parser exposing (parseErrorResponse, parseTreeResponse) - -import Json.Decode exposing (..) -import Sources.Processing exposing (Marker(..), TreeAnswer) - - - --- TREE - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse response _ = - let - hasMore = - decodeString (field "has_more" bool) response - - cursor = - decodeString (field "cursor" string) response - - paths = - decodeString - (field "entries" <| list <| field "path_display" string) - response - in - { filePaths = Result.withDefault [] paths - , marker = - if Result.withDefault False hasMore then - InProgress (Result.withDefault "" cursor) - - else - TheEnd - } - - - --- Error - - -parseErrorResponse : String -> Maybe String -parseErrorResponse response = - response - |> decodeString (field "error_summary" string) - |> Result.toMaybe diff --git a/src/Library/Sources/Services/Google.elm b/src/Library/Sources/Services/Google.elm deleted file mode 100644 index 76db9a089..000000000 --- a/src/Library/Sources/Services/Google.elm +++ /dev/null @@ -1,323 +0,0 @@ -module Sources.Services.Google exposing (authorizationSourceData, authorizationUrl, defaultClientId, defaults, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties) - -{-| Google Drive Service. --} - -import Base64 -import Common -import Conditional exposing (..) -import Dict -import Dict.Ext as Dict -import Http -import Json.Decode -import Json.Encode -import Sources exposing (Property, SourceData) -import Sources.Processing exposing (..) -import Sources.Services.Google.Marker as Marker -import Sources.Services.Google.Parser as Parser -import String.Path -import Time - - - --- PROPERTIES --- ๐Ÿ“Ÿ - - -defaults = - { clientId = defaultClientId - , clientSecret = "uHBInBeGnA38FOlpLTEyPlUv" - , folderId = "" - , name = "Music from Google Drive" - } - - -defaultClientId : String -defaultClientId = - String.concat - [ "720114869239-74amkqeila5ursobjqvo9c263u1cllhu" - , ".apps.googleusercontent.com" - ] - - -{-| The list of properties we need from the user. - -Tuple: (property, label, placeholder, isPassword) -Will be used for the forms. - --} -properties : List Property -properties = - [ { key = "folderId" - , label = "Folder Id (Optional)" - , placeholder = defaults.folderId - , password = False - } - , { key = "clientId" - , label = "Client Id (Google Console)" - , placeholder = defaults.clientId - , password = False - } - , { key = "clientSecret" - , label = "Client Secret (Google Console)" - , placeholder = defaults.clientSecret - , password = False - } - ] - - -{-| Initial data set. --} -initialData : SourceData -initialData = - Dict.fromList - [ ( "clientId", defaults.clientId ) - , ( "clientSecret", defaults.clientSecret ) - , ( "folderId", defaults.folderId ) - , ( "name", defaults.name ) - ] - - - --- AUTHORIZATION - - -{-| Authorization url. --} -authorizationUrl : SourceData -> String -> String -authorizationUrl sourceData origin = - let - encodeData data = - data - |> Dict.toList - |> List.map (Tuple.mapSecond Json.Encode.string) - |> Json.Encode.object - - state = - sourceData - |> encodeData - |> Json.Encode.encode 0 - |> Base64.encode - in - [ ( "access_type", "offline" ) - , ( "client_id", Dict.fetch "clientId" "unknown" sourceData ) - , ( "prompt", "consent" ) - , ( "redirect_uri", origin ++ "?path=sources/new/google" ) - , ( "response_type", "code" ) - , ( "scope", "https://www.googleapis.com/auth/drive.readonly" ) - , ( "state", state ) - ] - |> Common.queryString - |> String.append "https://accounts.google.com/o/oauth2/v2/auth" - - -{-| Authorization source data. --} -authorizationSourceData : { codeOrToken : Maybe String, state : Maybe String } -> SourceData -authorizationSourceData args = - args.state - |> Maybe.andThen (Base64.decode >> Result.toMaybe) - |> Maybe.withDefault "{}" - |> Json.Decode.decodeString (Json.Decode.dict Json.Decode.string) - |> Result.withDefault Dict.empty - |> Dict.unionFlipped initialData - |> Dict.update "authCode" (\_ -> args.codeOrToken) - - - --- PREPARATION - - -{-| Before processing we need to prepare the source. -In this case this means that we will refresh the `access_token`. -Or if we don't have an access token yet, get one. --} -prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) -prepare origin srcData _ resultMsg = - let - maybeCode = - Dict.get "authCode" srcData - - queryParams = - case maybeCode of - -- Exchange authorization code for access token & request token - Just _ -> - [ ( "client_id", Dict.fetch "clientId" "" srcData ) - , ( "client_secret", Dict.fetch "clientSecret" "" srcData ) - , ( "code", Dict.fetch "authCode" "" srcData ) - , ( "grant_type", "authorization_code" ) - , ( "redirect_uri", origin ++ "?path=sources/new/google" ) - ] - - -- Refresh access token - Nothing -> - [ ( "client_id", Dict.fetch "clientId" "" srcData ) - , ( "client_secret", Dict.fetch "clientSecret" "" srcData ) - , ( "refresh_token", Dict.fetch "refreshToken" "" srcData ) - , ( "grant_type", "refresh_token" ) - ] - - query = - Common.queryString queryParams - - url = - "https://www.googleapis.com/oauth2/v4/token" ++ query - in - (Just << Http.post) - { url = url - , body = Http.emptyBody - , expect = Http.expectStringResponse resultMsg Common.translateHttpResponse - } - - - --- TREE - - -{-| Create a directory tree. - -List all the tracks in the bucket. -Or a specific directory in the bucket. - --} -makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg -makeTree srcData marker _ resultMsg = - let - accessToken = - Dict.fetch "accessToken" "" srcData - - folderId = - Dict.fetch "folderId" "" srcData - - parentId = - marker - |> Marker.takeOne - |> Maybe.map Marker.itemDirectory - |> Maybe.andThen (\dir -> ifThenElse (String.isEmpty dir) Nothing <| Just dir) - |> Maybe.withDefault folderId - |> String.Path.file - - query = - case parentId of - "" -> - [ "mimeType contains 'audio/'" ] - - pid -> - [ "(mimeType contains 'audio/'" - , "or mimeType = 'application/vnd.google-apps.folder')" - , "and ('" ++ pid ++ "' in parents)" - ] - - paramsBase = - [ ( "fields" - , String.join ", " - [ "nextPageToken" - , "files/id" - , "files/mimeType" - , "files/name" - , "files/trashed" - ] - ) - , ( "includeItemsFromAllDrives", "true" ) - , ( "pageSize", "1000" ) - , ( "q", String.concat query ) - , ( "spaces", "drive" ) - , ( "supportsAllDrives", "true" ) - ] - - queryString = - (case Marker.takeOne marker of - Just (Marker.Param { token }) -> - [ ( "pageToken", token ) ] - - _ -> - [] - ) - |> List.append paramsBase - |> Common.queryString - in - Http.request - { method = "GET" - , headers = [ Http.header "Authorization" ("Bearer " ++ accessToken) ] - , url = "https://www.googleapis.com/drive/v3/files" ++ queryString - , body = Http.emptyBody - , expect = Http.expectStringResponse resultMsg Common.translateHttpResponse - , timeout = Nothing - , tracker = Nothing - } - - -{-| Re-export parser functions. --} -parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse = - Parser.parsePreparationResponse - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse = - Parser.parseTreeResponse - - -parseErrorResponse : String -> Maybe String -parseErrorResponse = - Parser.parseErrorResponse - - - --- POST - - -{-| Post process the tree results. - -!!! Make sure we only use music files that we can use. - --} -postProcessTree : List String -> List String -postProcessTree = - identity - - - --- TRACK URL - - -{-| Create a public url for a file. - -We need this to play the track. - --} -makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String -makeTrackUrl currentTime srcId srcData _ path = - let - file = - String.Path.file path - - fileId = - file - |> String.split "?" - |> List.head - |> Maybe.withDefault file - - now = - Time.posixToMillis currentTime - - expiresAt = - Dict.fetch "expiresAt" (String.fromInt now) srcData - in - String.concat - [ "google://" - , Dict.fetch "accessToken" "" srcData - , ":" - , expiresAt - , ":" - , Dict.fetch "refreshToken" "" srcData - , ":" - , Dict.fetch "clientId" "" srcData - , ":" - , Dict.fetch "clientSecret" "" srcData - , ":" - , srcId - , "@" - , fileId - ] diff --git a/src/Library/Sources/Services/Google/Marker.elm b/src/Library/Sources/Services/Google/Marker.elm deleted file mode 100644 index 79c2e2976..000000000 --- a/src/Library/Sources/Services/Google/Marker.elm +++ /dev/null @@ -1,164 +0,0 @@ -module Sources.Services.Google.Marker exposing (..) - -{-| Custom `Marker` for the Google Drive API. - -The Google Drive API currently doesn't make a recursive list, -so we have to manage that ourselves. - -This custom marker is a combination of: - - - The standard `nextPageToken` param - - Our custom logic to handle recursive listings - -Example: InProgress "dir=example ยถ param=defaultMarker" - --} - -import Sources.Processing exposing (Marker(..)) - - -type MarkerItem - = Directory String - | Param { directory : String, token : String } - - -separator : String -separator = - " ษ‘ " - - -prefixer : String -prefixer = - " ฮฒ " - - -paramSeparator : String -paramSeparator = - " ษฃ " - - - --- IN - - -concat : List MarkerItem -> Marker -> Marker -concat list marker = - let - listStringified = - List.map itemToString list - - result = - case marker of - InProgress m -> - [ listStringified, String.split separator m ] - |> List.concat - |> String.join separator - - _ -> - String.join separator listStringified - in - case result of - "" -> - TheEnd - - r -> - InProgress r - - - --- OUT - - -{-| Take the first item and return it. --} -takeOne : Marker -> Maybe MarkerItem -takeOne marker = - case marker of - InProgress m -> - m - |> String.split separator - |> List.head - |> Maybe.andThen stringToItem - - _ -> - Nothing - - -{-| Remove the first item if there is one. --} -removeOne : Marker -> Marker -removeOne marker = - case marker of - InProgress m -> - let - tmp = - m - |> String.split separator - |> List.drop 1 - |> String.join separator - in - case tmp of - "" -> - TheEnd - - x -> - InProgress x - - _ -> - TheEnd - - - --- CONVERSIONS - - -itemDirectory : MarkerItem -> String -itemDirectory item = - case item of - Directory dir -> - dir - - Param { directory } -> - directory - - -itemToString : MarkerItem -> String -itemToString item = - case item of - Directory d -> - "dir" ++ prefixer ++ d - - Param { directory, token } -> - "par" ++ prefixer ++ directory ++ paramSeparator ++ token - - -stringToItem : String -> Maybe MarkerItem -stringToItem string = - let - exploded = - String.split prefixer string - in - case List.head exploded of - Just "dir" -> - exploded - |> List.drop 1 - |> String.join prefixer - |> Directory - |> Just - - Just "par" -> - exploded - |> List.drop 1 - |> String.join prefixer - |> String.split paramSeparator - |> (\x -> - case x of - [ dir, tok ] -> - Just (Param { directory = dir, token = tok }) - - _ -> - Nothing - ) - - _ -> - Nothing diff --git a/src/Library/Sources/Services/Google/Parser.elm b/src/Library/Sources/Services/Google/Parser.elm deleted file mode 100755 index ed2ad27a1..000000000 --- a/src/Library/Sources/Services/Google/Parser.elm +++ /dev/null @@ -1,185 +0,0 @@ -module Sources.Services.Google.Parser exposing (..) - -import Dict -import Json.Decode exposing (..) -import Json.Decode.Ext exposing (..) -import Maybe.Extra -import Sources exposing (SourceData) -import Sources.Pick -import Sources.Processing exposing (Marker(..), PrepationAnswer, TreeAnswer) -import Sources.Services.Google.Marker as Marker -import String.Path -import Time - - - --- PREPARATION - - -parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse response currentTimePosix srcData _ = - let - newAccessToken = - response - |> decodeString (field "access_token" string) - |> Result.withDefault "" - - currentTime = - -- Current time in milliseconds - Time.posixToMillis currentTimePosix - - expiresAt = - -- Unix timestamp in milliseconds - response - |> decodeString (field "expires_in" int) - -- time in seconds - |> Result.withDefault 2500 - |> (\s -> currentTime + s * 1000) - - maybeRefreshToken = - response - |> decodeString (maybe <| field "refresh_token" string) - |> Result.toMaybe - |> Maybe.Extra.join - - refreshTokenUpdater dict = - case maybeRefreshToken of - Just refreshToken -> - Dict.insert "refreshToken" refreshToken dict - - Nothing -> - dict - in - srcData - |> Dict.insert "accessToken" newAccessToken - |> Dict.insert "expiresAt" (String.fromInt expiresAt) - |> refreshTokenUpdater - |> Dict.remove "authCode" - |> (\s -> { sourceData = s, marker = TheEnd }) - - - --- TREE - - -type alias Properties = - { id : String, name : String } - - -type Item - = File Properties - | Directory Properties - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse response previousMarker = - let - nextPageToken = - response - |> decodeString (maybe <| field "nextPageToken" string) - |> Result.toMaybe - |> Maybe.Extra.join - - items = - response - |> decodeString (field "files" <| listIgnore itemDecoder) - |> Result.withDefault [] - - usedDirectory = - previousMarker - |> Marker.takeOne - |> Maybe.map Marker.itemDirectory - |> Maybe.withDefault "" - - usedPath = - usedDirectory - |> String.Path.dropRight 1 - |> String.Path.addSuffix - - ( directories, files ) = - List.partition - (\item -> - case item of - Directory _ -> - True - - File _ -> - False - ) - items - in - { filePaths = - files - |> List.map itemProperties - |> List.filter (.name >> Sources.Pick.isMusicFile) - |> List.map (\{ id, name } -> usedPath ++ id ++ "?name=" ++ name) - , marker = - previousMarker - |> Marker.removeOne - |> Marker.concat - (List.map - (itemProperties - >> (\props -> props.name ++ "/" ++ props.id) - >> String.append usedPath - >> Marker.Directory - ) - directories - ) - |> (case nextPageToken of - Just token -> - { directory = usedDirectory, token = token } - |> Marker.Param - |> List.singleton - |> Marker.concat - - Nothing -> - identity - ) - } - - -itemDecoder : Decoder Item -itemDecoder = - map4 - (\id name mime _ -> - case mime of - "application/vnd.google-apps.folder" -> - Directory { id = id, name = name } - - _ -> - File { id = id, name = name } - ) - (field "id" string) - (field "name" string) - (field "mimeType" string) - (andThen - (\b -> - if b then - fail "Exclude deleted files" - - else - succeed b - ) - (field "trashed" bool) - ) - - -itemProperties : Item -> Properties -itemProperties item = - case item of - Directory props -> - props - - File props -> - props - - - --- ERROR - - -parseErrorResponse : String -> Maybe String -parseErrorResponse response = - response - |> decodeString (at [ "error", "message" ] string) - |> Result.toMaybe diff --git a/src/Library/Sources/Services/Ipfs.elm b/src/Library/Sources/Services/Ipfs.elm deleted file mode 100644 index af17a8649..000000000 --- a/src/Library/Sources/Services/Ipfs.elm +++ /dev/null @@ -1,318 +0,0 @@ -module Sources.Services.Ipfs exposing (..) - -{-| IPFS Service. - -Resources: - - - - --} - -import Common exposing (boolFromString, boolToString) -import Conditional exposing (ifThenElse) -import Dict -import Dict.Ext as Dict -import Http -import Json.Decode as Json -import Sources exposing (Property, SourceData) -import Sources.Processing exposing (..) -import Sources.Services.Ipfs.Marker as Marker -import Sources.Services.Ipfs.Parser as Parser -import String.Ext as String -import Task -import Time -import Url - - - --- PROPERTIES --- ๐Ÿ“Ÿ - - -defaults = - { gateway = "" - , local = boolToString False - , name = "Music from IPFS" - , ipns = boolToString False - } - - -defaultGateway = - "https://ipfs.io" - - -{-| The list of properties we need from the user. - -Tuple: (property, label, placeholder, isPassword) -Will be used for the forms. - --} -properties : List Property -properties = - [ { key = "directoryHash" - , label = "Directory hash / DNSLink domain" - , placeholder = "QmVLDAhCY3X9P2u" - , password = False - } - , { key = "ipns" - , label = "Resolve using IPNS" - , placeholder = defaults.ipns - , password = False - } - , { key = "gateway" - , label = "Gateway (Optional)" - , placeholder = defaultGateway - , password = False - } - , { key = "local" - , label = "Resolve IPNS locally" - , placeholder = defaults.local - , password = False - } - ] - - -{-| Initial data set. --} -initialData : SourceData -initialData = - Dict.fromList - [ ( "directoryHash", "" ) - , ( "gateway", defaults.gateway ) - , ( "ipns", defaults.ipns ) - , ( "local", defaults.local ) - , ( "name", defaults.name ) - ] - - - --- PREPARATION - - -prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) -prepare _ srcData _ toMsg = - let - domainName = - srcData - |> Dict.get "directoryHash" - |> Maybe.withDefault "" - |> String.chopStart "http://" - |> String.chopStart "https://" - |> String.chopEnd "/" - |> String.chopStart "_dnslink." - in - if isDnsLink srcData then - (Just << Http.request) - { method = "POST" - , headers = [] - , url = extractGateway srcData ++ "/api/v0/dns?arg=" ++ domainName - , body = Http.emptyBody - , expect = Http.expectString toMsg - , timeout = Nothing - , tracker = Nothing - } - - else - Nothing - - - --- TREE - - -{-| Create a directory tree. --} -makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg -makeTree srcData marker _ resultMsg = - let - gateway = - extractGateway srcData - - resolveWithIpns = - case marker of - InProgress _ -> - False - - _ -> - srcData - |> Dict.fetch "ipns" defaults.ipns - |> boolFromString - - resolveLocally = - srcData - |> Dict.fetch "local" defaults.local - |> boolFromString - |> (\b -> ifThenElse b "true" "false") - - root = - rootHash srcData - - path = - case marker of - InProgress _ -> - marker - |> Marker.takeOne - |> Maybe.map (\p -> root ++ "/" ++ p) - |> Maybe.withDefault "" - - _ -> - root - in - (if resolveWithIpns then - Http.task - { method = "POST" - , headers = [] - , url = gateway ++ "/api/v0/name/resolve?arg=" ++ encodedPath path ++ "&local=" ++ resolveLocally ++ "&encoding=json" - , body = Http.emptyBody - , resolver = Http.stringResolver ipnsResolver - , timeout = Just (60 * 15 * 1000) - } - - else - Task.succeed { ipfsPath = path } - ) - |> Task.andThen - (\{ ipfsPath } -> - Http.task - { method = "POST" - , headers = [] - , url = gateway ++ "/api/v0/ls?arg=" ++ encodedPath ipfsPath ++ "&encoding=json" - , body = Http.emptyBody - , resolver = Http.stringResolver Common.translateHttpResponse - , timeout = Just (60 * 15 * 1000) - } - ) - |> Task.attempt resultMsg - - -ipnsResolver : Http.Response String -> Result Http.Error { ipfsPath : String } -ipnsResolver response = - case response of - Http.BadUrl_ u -> - Err (Http.BadUrl u) - - Http.Timeout_ -> - Err Http.Timeout - - Http.NetworkError_ -> - Err Http.NetworkError - - Http.BadStatus_ _ body -> - Err (Http.BadBody body) - - Http.GoodStatus_ _ body -> - body - |> Json.decodeString (Json.field "Path" Json.string) - |> Result.map (\path -> { ipfsPath = String.chopStart "/ipfs/" path }) - |> Result.mapError (Json.errorToString >> Http.BadBody) - - -{-| Re-export parser functions. --} -parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse = - Parser.parseDnsLookup - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse = - Parser.parseTreeResponse - - -parseErrorResponse : String -> Maybe String -parseErrorResponse = - Parser.parseErrorResponse - - - --- POST - - -{-| Post process the tree results. - -!!! Make sure we only use music files that we can use. - --} -postProcessTree : List String -> List String -postProcessTree = - identity - - - --- TRACK URL - - -{-| Create a public url for a file. - -We need this to play the track. - --} -makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String -makeTrackUrl _ _ srcData _ path = - if not (String.contains "/" path) && not (String.contains "." path) then - -- If it still uses the old way of doing things - -- (ie. each path was a cid) - extractGateway srcData ++ "/ipfs/" ++ path - - else - -- Or the new way - extractGateway srcData ++ "/ipfs/" ++ rootHash srcData ++ "/" ++ encodedPath path - - - --- โš—๏ธ - - -encodedPath : String -> String -encodedPath path = - path - |> String.split "/" - |> List.map Url.percentEncode - |> String.join "/" - - -extractGateway : SourceData -> String -extractGateway srcData = - srcData - |> Dict.get "gateway" - |> Maybe.map String.trim - |> Maybe.andThen - (\s -> - case s of - "" -> - Nothing - - _ -> - Just s - ) - |> Maybe.map (String.chopEnd "/") - |> Maybe.withDefault defaultGateway - - -isDnsLink : SourceData -> Bool -isDnsLink srcData = - srcData - |> Dict.get "directoryHash" - |> Maybe.map pathIsDnsLink - |> Maybe.withDefault False - - -pathIsDnsLink : String -> Bool -pathIsDnsLink = - String.contains "." - - -rootHash : SourceData -> String -rootHash srcData = - srcData - |> Dict.get "directoryHash" - |> Maybe.andThen - (\path -> - if pathIsDnsLink path then - Dict.get "directoryHashFromDnsLink" srcData - - else - Just path - ) - |> Maybe.withDefault "" - |> String.chopEnd "/" diff --git a/src/Library/Sources/Services/Ipfs/Marker.elm b/src/Library/Sources/Services/Ipfs/Marker.elm deleted file mode 100755 index 1be8f727c..000000000 --- a/src/Library/Sources/Services/Ipfs/Marker.elm +++ /dev/null @@ -1,93 +0,0 @@ -module Sources.Services.Ipfs.Marker exposing (concat, removeOne, separator, takeOne) - -{-| Marker stuff for IPFS. - -The IPFS API currently doesn't have a way to return a tree. -So we have build one ourselves. - -How it works: - -1. We list the objects in a given directory -2. We make a list of the sub directories -3. The marker becomes either: - - InProgress `hashOfSubDirA/hashOfSubDirB/hashOfSubDirC` - - TheEnd -4. If the marker was of the type `InProgress`, - the next request will make a list of the objects in `hashOfSubDirA`. - And so on ... - --} - -import Sources.Processing exposing (Marker(..)) - - -separator : String -separator = - " ษ‘ " - - - --- In - - -concat : List String -> Marker -> Marker -concat list marker = - let - result = - case marker of - InProgress m -> - [ list, String.split separator m ] - |> List.concat - |> String.join separator - - _ -> - String.join separator list - in - case result of - "" -> - TheEnd - - r -> - InProgress r - - - --- Out - - -{-| Take the first item and return it. --} -takeOne : Marker -> Maybe String -takeOne marker = - case marker of - InProgress m -> - m - |> String.split separator - |> List.head - - _ -> - Nothing - - -{-| Remove the first item if there is one. --} -removeOne : Marker -> Marker -removeOne marker = - case marker of - InProgress m -> - let - tmp = - m - |> String.split separator - |> List.drop 1 - |> String.join separator - in - case tmp of - "" -> - TheEnd - - x -> - InProgress x - - _ -> - TheEnd diff --git a/src/Library/Sources/Services/Ipfs/Parser.elm b/src/Library/Sources/Services/Ipfs/Parser.elm deleted file mode 100755 index 8175f757c..000000000 --- a/src/Library/Sources/Services/Ipfs/Parser.elm +++ /dev/null @@ -1,146 +0,0 @@ -module Sources.Services.Ipfs.Parser exposing (Link, linkDecoder, parseDnsLookup, parseErrorResponse, parseTreeResponse, treeDecoder) - -import Dict -import Json.Decode exposing (..) -import Sources exposing (SourceData) -import Sources.Pick exposing (isMusicFile) -import Sources.Processing exposing (Marker(..), PrepationAnswer, TreeAnswer) -import Sources.Services.Ipfs.Marker as Marker -import String.Ext as String -import Time - - - --- PREPARATION - - -parseDnsLookup : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parseDnsLookup response _ srcData _ = - case decodeString dnsResultDecoder response of - Ok path -> - srcData - |> Dict.insert "directoryHashFromDnsLink" (String.chopStart "/ipfs/" path) - |> (\s -> { sourceData = s, marker = TheEnd }) - - Err _ -> - { sourceData = srcData, marker = TheEnd } - - -dnsResultDecoder : Decoder String -dnsResultDecoder = - oneOf - [ at [ "Path" ] string - , cloudflareDnsResultDecoder - ] - - -cloudflareDnsResultDecoder : Decoder String -cloudflareDnsResultDecoder = - string - |> at [ "Answer", "0", "data" ] - |> map - (\txt -> - txt - |> String.chopEnd "\"" - |> String.chopStart "\"" - |> String.chopStart "dnslink=/ipfs/" - ) - - - --- TREE - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse response previousMarker = - let - prefix = - case previousMarker of - TheBeginning -> - "" - - _ -> - response - |> decodeString prefixDecoder - |> Result.map - (String.chopStart "/ipfs/" - >> String.split "/" - >> List.drop 1 - >> String.join "/" - ) - |> Result.map - (\s -> - if String.isEmpty s then - "" - - else - s ++ "/" - ) - |> Result.withDefault "" - - links = - case decodeString treeDecoder response of - Ok l -> - l - - Err _ -> - [] - - dirs = - links - |> List.filter (.typ >> (==) 1) - |> List.map (\l -> prefix ++ l.name) - - files = - links - |> List.filter (.typ >> (==) 2) - |> List.filter (.name >> isMusicFile) - |> List.map (\l -> prefix ++ l.name) - in - { filePaths = - files - , marker = - previousMarker - |> Marker.removeOne - |> Marker.concat dirs - } - - -prefixDecoder : Decoder String -prefixDecoder = - field "Objects" <| index 0 <| field "Hash" <| string - - -treeDecoder : Decoder (List Link) -treeDecoder = - field "Objects" <| index 0 <| field "Links" <| list linkDecoder - - - --- LINKS - - -type alias Link = - { hash : String - , name : String - , typ : Int - } - - -linkDecoder : Decoder Link -linkDecoder = - map3 Link - (field "Hash" string) - (field "Name" string) - (field "Type" int) - - - --- ERRORS - - -parseErrorResponse : String -> Maybe String -parseErrorResponse response = - response - |> decodeString (field "Message" string) - |> Result.toMaybe diff --git a/src/Library/Sources/Services/WebDav.elm b/src/Library/Sources/Services/WebDav.elm deleted file mode 100644 index 3d1f6dfe4..000000000 --- a/src/Library/Sources/Services/WebDav.elm +++ /dev/null @@ -1,214 +0,0 @@ -module Sources.Services.WebDav exposing (defaults, initialData, makeTrackUrl, makeTree, parseErrorResponse, parsePreparationResponse, parseTreeResponse, postProcessTree, prepare, properties) - -{-| IPFS Service. - -Resources: - - - - --} - -import Base64 -import Common -import Dict -import Dict.Ext as Dict -import Http -import Sources exposing (Property, SourceData) -import Sources.Pick exposing (selectMusicFiles) -import Sources.Processing exposing (..) -import Sources.Services.Common exposing (noPrep) -import Sources.Services.WebDav.Marker as Marker -import Sources.Services.WebDav.Parser as Parser -import String.Ext as String -import Time -import Url - - - --- PROPERTIES --- ๐Ÿ“Ÿ - - -defaults = - { name = "Music from WebDAV" - } - - -{-| The list of properties we need from the user. - -Tuple: (property, label, placeholder, isPassword) -Will be used for the forms. - --} -properties : List Property -properties = - [ { key = "url" - , label = "Host URL" - , placeholder = "https://demo.nextcloud.com" - , password = False - } - , { key = "directoryPath" - , label = "Directory (Optional)" - , placeholder = "/icidasset/remote.php/webdav/" - , password = False - } - , { key = "username" - , label = "Username (Optional)" - , placeholder = "" - , password = False - } - , { key = "password" - , label = "Password (Optional)" - , placeholder = "" - , password = True - } - ] - - -{-| Initial data set. --} -initialData : SourceData -initialData = - Dict.fromList - [ ( "directoryPath", "" ) - , ( "url", "" ) - , ( "username", "" ) - , ( "password", "" ) - , ( "name", defaults.name ) - ] - - - --- PREPARATION - - -prepare : String -> SourceData -> Marker -> (Result Http.Error String -> msg) -> Maybe (Cmd msg) -prepare _ _ _ _ = - Nothing - - - --- TREE - - -{-| Create a directory tree. --} -makeTree : SourceData -> Marker -> Time.Posix -> (Result Http.Error String -> msg) -> Cmd msg -makeTree srcData marker _ resultMsg = - let - directory = - case marker of - InProgress _ -> - marker - |> Marker.takeOne - |> Maybe.withDefault "" - - _ -> - srcData - |> Dict.get "directoryPath" - |> Maybe.withDefault "" - - username = - Dict.fetch "username" "" srcData - - password = - Dict.fetch "password" "" srcData - - auth = - "Basic " ++ Base64.encode (username ++ ":" ++ password) - in - Http.request - { method = "PROPFIND" - , headers = [ Http.header "Authorization" auth, Http.header "Depth" "1" ] - , url = url { addAuth = False } srcData directory - , body = Http.emptyBody - , expect = Http.expectStringResponse resultMsg Common.translateHttpResponse - , timeout = Nothing - , tracker = Nothing - } - - -{-| Re-export parser functions. --} -parsePreparationResponse : String -> Time.Posix -> SourceData -> Marker -> PrepationAnswer Marker -parsePreparationResponse = - noPrep - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse = - Parser.parseTreeResponse - - -parseErrorResponse : String -> Maybe String -parseErrorResponse = - Parser.parseErrorResponse - - - --- POST - - -{-| Post process the tree results. - -!!! Make sure we only use music files that we can use. - --} -postProcessTree : List String -> List String -postProcessTree = - selectMusicFiles - - - --- TRACK URL - - -{-| Create a public url for a file. - -We need this to play the track. - --} -makeTrackUrl : Time.Posix -> String -> SourceData -> HttpMethod -> String -> String -makeTrackUrl _ _ srcData _ filePath = - url { addAuth = True } srcData filePath - - - --- COMMON - - -url : { addAuth : Bool } -> SourceData -> String -> String -url { addAuth } srcData path = - let - host = - String.chopEnd "/" (Dict.fetch "url" "" srcData) - - username = - Dict.fetch "username" "" srcData - - password = - Dict.fetch "password" "" srcData - - authPrefix = - case ( username, password ) of - ( "", "" ) -> - "" - - ( u, p ) -> - u ++ ":" ++ p - - authBit = - if addAuth && String.length authPrefix > 0 then - "?basic_auth=" ++ Url.percentEncode (Base64.encode authPrefix) - - else - "" - - encodedPath = - path - |> String.chopStart "/" - |> String.split "/" - |> List.map Url.percentEncode - |> String.join "/" - in - host ++ "/" ++ encodedPath ++ authBit diff --git a/src/Library/Sources/Services/WebDav/Marker.elm b/src/Library/Sources/Services/WebDav/Marker.elm deleted file mode 100644 index 105c36903..000000000 --- a/src/Library/Sources/Services/WebDav/Marker.elm +++ /dev/null @@ -1,32 +0,0 @@ -module Sources.Services.WebDav.Marker exposing (concat, removeOne, takeOne) - -{-| Custom `Marker`. - -WebDAV doesn't make a recursive list, so we have to manage that ourselves. -The spec seems to have `Depth: Infinity` which should allow that. -But a lot of clients don't implement it. -This basically re-exports `Sources.Services.IPFS.Marker`. - --} - -import Sources.Services.Ipfs.Marker as IpfsMarker - - - --- IN - - -concat = - IpfsMarker.concat - - - --- OUT - - -takeOne = - IpfsMarker.takeOne - - -removeOne = - IpfsMarker.removeOne diff --git a/src/Library/Sources/Services/WebDav/Parser.elm b/src/Library/Sources/Services/WebDav/Parser.elm deleted file mode 100644 index 8e2d359f9..000000000 --- a/src/Library/Sources/Services/WebDav/Parser.elm +++ /dev/null @@ -1,135 +0,0 @@ -module Sources.Services.WebDav.Parser exposing (..) - -import Maybe.Extra as Maybe -import Sources.Pick exposing (isMusicFile) -import Sources.Processing exposing (Marker, TreeAnswer) -import Sources.Services.Ipfs.Marker as Marker -import String.Ext as String -import Url -import Xml.Decode exposing (..) -import XmlParser - - - --- TREE - - -parseTreeResponse : String -> Marker -> TreeAnswer Marker -parseTreeResponse response previousMarker = - let - currentDir = - Maybe.withDefault "//" (Marker.takeOne previousMarker) - - parseResult = - XmlParser.parse response - - namespace = - parseResult - |> Result.map - (\xml -> - case xml.root of - XmlParser.Element nodeName _ _ -> - nodeName - |> String.split ":" - |> List.head - - _ -> - Nothing - ) - |> Result.withDefault Nothing - |> (\maybe -> - case maybe of - Just n -> - n ++ ":" - - Nothing -> - if String.contains " decodeString (treeDecoder namespace) - |> Result.withDefault [] - |> List.map Url.percentDecode - |> Maybe.values - |> List.filter ((/=) currentDir) - - ( dirs, files ) = - List.partition (String.endsWith "/") entries - in - { filePaths = - List.map (String.chopStart "/") files - , marker = - previousMarker - |> Marker.removeOne - |> Marker.concat dirs - } - - -treeDecoder : String -> Decoder (List String) -treeDecoder namespace = - path - [ namespace ++ "response" ] - (leakyList <| treeItemDecoder namespace) - - -treeItemDecoder : String -> Decoder String -treeItemDecoder namespace = - let - withNamespace = - String.append namespace - in - string - |> single - |> path [ withNamespace "href" ] - |> andThen - (\href -> - oneOf - [ -- Audio - -------- - string - |> single - |> path [ withNamespace "propstat", withNamespace "prop", withNamespace "getcontenttype" ] - |> andThen (mustBeAudio href) - |> map (\_ -> href) - - -- Directory - ------------ - , string - |> single - |> path [ withNamespace "propstat", withNamespace "prop", withNamespace "resourcetype", withNamespace "collection" ] - |> andThen - (\_ -> - if String.endsWith "/@eaDir/" href then - fail "Ignore Synology metadata" - - else - succeed href - ) - ] - ) - - -mustBeAudio : String -> String -> Decoder String -mustBeAudio href contentType = - if isMusicFile href then - succeed contentType - - else if String.startsWith "audio/" contentType then - succeed contentType - - else - fail "Ignore this, not an audio file" - - - --- ERROR - - -parseErrorResponse : String -> Maybe String -parseErrorResponse = - Just diff --git a/src/Library/String/Ext.elm b/src/Library/String/Ext.elm deleted file mode 100644 index f16b45a73..000000000 --- a/src/Library/String/Ext.elm +++ /dev/null @@ -1,46 +0,0 @@ -module String.Ext exposing (..) - -{-| Flipped version of `append`. --} - --- ๐Ÿ”ฑ - - -{-| Flipped version of `append`. --} -addSuffix : String -> String -> String -addSuffix a b = - String.append b a - - -{-| Chop something from the end of a string until it's not there anymore. --} -chopEnd : String -> String -> String -chopEnd needle str = - if String.endsWith needle str then - str - |> String.dropRight (String.length needle) - |> chopEnd needle - - else - str - - -{-| Chop something from the beginning of a string until it's not there anymore. --} -chopStart : String -> String -> String -chopStart needle str = - if String.startsWith needle str then - str - |> String.dropLeft (String.length needle) - |> chopStart needle - - else - str - - -{-| Join a list of Strings with a space in between. --} -joinWithSpace : List String -> String -joinWithSpace = - String.join " " diff --git a/src/Library/String/Path.elm b/src/Library/String/Path.elm deleted file mode 100644 index becbe6d9b..000000000 --- a/src/Library/String/Path.elm +++ /dev/null @@ -1,48 +0,0 @@ -module String.Path exposing (..) - -import List.Extra as List - - - --- โ›ฉ - - -sep : String -sep = - "/" - - - --- ๐Ÿ”ฑ - - -addSuffix : String -> String -addSuffix path = - case path of - "" -> - "" - - p -> - p ++ sep - - -{-| Drop an amount of path segments from the right side. - - >>> dropRight 5 "a/b/c/d/e/f" - "a" - --} -dropRight : Int -> String -> String -dropRight int path = - path - |> String.split sep - |> (\l -> List.take (List.length l - int) l) - |> String.join sep - - -file : String -> String -file path = - path - |> String.split sep - |> List.last - |> Maybe.withDefault path diff --git a/src/Library/Syncing.elm b/src/Library/Syncing.elm deleted file mode 100644 index 19622e3fc..000000000 --- a/src/Library/Syncing.elm +++ /dev/null @@ -1,198 +0,0 @@ -module Syncing exposing (LocalConfig, RemoteConfig, task) - -import Json.Decode as Decode -import Json.Encode as Json -import Maybe.Extra as Maybe -import Task exposing (Task) -import Task.Extra as Task -import Time -import Time.Ext as Time -import User.Layer as User exposing (..) - - - --- ๐ŸŒณ - - -type alias LocalConfig = - { localData : HypaethralData - , saveLocal : HypaethralBit -> Decode.Value -> Task String () - } - - -type alias RemoteConfig = - { retrieve : HypaethralBit -> Task String (Maybe Decode.Value) - , save : HypaethralBit -> Decode.Value -> Task String () - } - - - --- ๐Ÿ›  - - -{-| Syncs all hypaethral data. - -Returns `Nothing` if the local data is preferred. - -๐Ÿ๏ธ LOCAL -๐Ÿ›ฐ๏ธ REMOTE - -1. Try to pull remote `modified.json` timestamp - a. If newer, continue (#2) - b. If same, do nothing - c. If older, or not present, prefer local data ๐Ÿ๏ธ (stop & push) -2. Try to download all remote data - a. If any remote data, continue (#3) - b. If none, prefer local data ๐Ÿ๏ธ (stop & push) -3. Decode remote data and compare timestamps - a. If newer, use remote data ๐Ÿ›ฐ๏ธ - b. If same, do nothing - c. If older, prefer local data ๐Ÿ๏ธ (stop & push) - d. If no timestamps, if local data, prefer local ๐Ÿ๏ธ (stop & push), otherwise remote ๐Ÿ›ฐ๏ธ - --} -task : - Task String a - -> LocalConfig - -> RemoteConfig - -> Task String (Maybe HypaethralData) -task initialTask localConfig remoteConfig = - initialTask - |> Task.andThen - (\_ -> - remoteConfig.retrieve ModifiedAt - ) - |> Task.andThen - (\maybeModifiedAt -> - let - maybeRemoteModifiedAt = - Maybe.andThen - (Decode.decodeValue Time.decoder >> Result.toMaybe) - maybeModifiedAt - in - case ( maybeRemoteModifiedAt, localConfig.localData.modifiedAt ) of - ( Just remoteModifiedAt, Just localModifiedAt ) -> - if Time.posixToMillis remoteModifiedAt == Time.posixToMillis localModifiedAt then - -- ๐Ÿ๏ธ - Task.succeed Nothing - - else if Time.posixToMillis remoteModifiedAt > Time.posixToMillis localModifiedAt then - -- ๐Ÿ›ฐ๏ธ - fetchRemote localConfig remoteConfig - - else - -- ๐Ÿ๏ธ โ†’ ๐Ÿ›ฐ๏ธ - pushLocalToRemote localConfig remoteConfig { return = Nothing } - - ( Just _, Nothing ) -> - -- ๐Ÿ›ฐ๏ธ - fetchRemote localConfig remoteConfig - - ( Nothing, _ ) -> - -- ๐Ÿ›ฐ๏ธ - fetchRemote localConfig remoteConfig - ) - - -fetchRemote : - LocalConfig - -> RemoteConfig - -> Task String (Maybe HypaethralData) -fetchRemote localConfig remoteConfig = - let - { localData, saveLocal } = - localConfig - - { retrieve } = - remoteConfig - - saveLocally data = - data - |> User.saveHypaethralData saveLocal - |> Task.map (\_ -> Just data) - - noLocalData = - List.isEmpty localData.sources - && List.isEmpty localData.favourites - && List.isEmpty localData.playlists - in - retrieve - |> User.retrieveHypaethralData - |> Task.andThen - (\list -> - let - remoteHasExistingData = - List.any (Tuple.second >> Maybe.isJust) list - in - if remoteHasExistingData then - -- ๐Ÿ›ฐ๏ธ - Task.succeed list - - else - -- ๐Ÿ๏ธ โ†’ ๐Ÿ›ฐ๏ธ - pushLocalToRemote localConfig remoteConfig { return = list } - ) - |> Task.andThen - (\list -> - -- Decode remote - list - |> List.map (\( a, b ) -> ( hypaethralBitKey a, Maybe.withDefault Json.null b )) - |> Json.object - |> User.decodeHypaethralData - |> Task.fromResult - |> Task.mapError Decode.errorToString - ) - |> Task.andThen - (\remoteData -> - -- Compare modifiedAt timestamps - case ( remoteData.modifiedAt, localData.modifiedAt ) of - ( Just remoteModifiedAt, Just localModifiedAt ) -> - if Time.posixToMillis remoteModifiedAt == Time.posixToMillis localModifiedAt then - -- ๐Ÿ๏ธ - Task.succeed Nothing - - else if Time.posixToMillis remoteModifiedAt > Time.posixToMillis localModifiedAt then - -- ๐Ÿ›ฐ๏ธ - saveLocally remoteData - - else - -- ๐Ÿ๏ธ โ†’ ๐Ÿ›ฐ๏ธ - pushLocalToRemote localConfig remoteConfig { return = Nothing } - - ( Just _, Nothing ) -> - -- ๐Ÿ›ฐ๏ธ - saveLocally remoteData - - ( Nothing, Just _ ) -> - -- ๐Ÿ๏ธ โ†’ ๐Ÿ›ฐ๏ธ - pushLocalToRemote localConfig remoteConfig { return = Nothing } - - _ -> - if noLocalData then - -- ๐Ÿ›ฐ๏ธ - saveLocally remoteData - - else - -- ๐Ÿ๏ธ - Task.succeed Nothing - ) - - - --- ใŠ™๏ธ - - -pushLocalToRemote : LocalConfig -> RemoteConfig -> { return : a } -> Task String a -pushLocalToRemote localConfig remoteConfig { return } = - localConfig.localData - |> User.encodedHypaethralDataList - |> (case localConfig.localData.modifiedAt of - Just localModifiedAt -> - (::) ( ModifiedAt, Time.encode localModifiedAt ) - - Nothing -> - identity - ) - |> List.map (\( bit, data ) -> remoteConfig.save bit data) - |> Task.sequence - |> Task.map (\_ -> return) diff --git a/src/Library/Syncing/Services/Dropbox/Token.elm b/src/Library/Syncing/Services/Dropbox/Token.elm deleted file mode 100644 index 21b07d6bf..000000000 --- a/src/Library/Syncing/Services/Dropbox/Token.elm +++ /dev/null @@ -1,18 +0,0 @@ -module Syncing.Services.Dropbox.Token exposing (..) - -import Time - - -isExpired : { currentTime : Time.Posix, expiresAt : Int } -> Bool -isExpired { currentTime, expiresAt } = - let - currentTimeInSeconds = - Time.posixToMillis currentTime // 1000 - - currentTimeWithOffset = - -- We add 60 seconds here because we only get the current time every minute, - -- so there's always the chance the "current time" is 1-60 seconds behind. - currentTimeInSeconds + 60 - in - -- If the access token is expired - currentTimeWithOffset >= expiresAt diff --git a/src/Library/Task/Extra.elm b/src/Library/Task/Extra.elm deleted file mode 100644 index a18e786f8..000000000 --- a/src/Library/Task/Extra.elm +++ /dev/null @@ -1,28 +0,0 @@ -module Task.Extra exposing (do, doDelayed, fromResult) - -import Process -import Task - - - --- ๐Ÿ”ฑ - - -do : msg -> Cmd msg -do msg = - Task.perform identity (Task.succeed msg) - - -doDelayed : Float -> msg -> Cmd msg -doDelayed delay msg = - Task.perform (always msg) (Process.sleep delay) - - -fromResult : Result error value -> Task.Task error value -fromResult result = - case result of - Ok v -> - Task.succeed v - - Err e -> - Task.fail e diff --git a/src/Library/TaskPort/Extra.elm b/src/Library/TaskPort/Extra.elm deleted file mode 100644 index 8a1792f52..000000000 --- a/src/Library/TaskPort/Extra.elm +++ /dev/null @@ -1,18 +0,0 @@ -module TaskPort.Extra exposing (..) - -import TaskPort - - -errorToStringCustom : TaskPort.Error -> String -errorToStringCustom err = - case err of - TaskPort.JSError jsErr -> - case jsErr of - TaskPort.ErrorObject _ errRecord -> - errRecord.message - - TaskPort.ErrorValue _ -> - TaskPort.errorToString err - - TaskPort.InteropError _ -> - TaskPort.errorToString err diff --git a/src/Library/Theme.elm b/src/Library/Theme.elm deleted file mode 100644 index ff61848ba..000000000 --- a/src/Library/Theme.elm +++ /dev/null @@ -1,28 +0,0 @@ -module Theme exposing (..) - -import Html exposing (Html) -import Json.Decode as Decode -import Json.Encode as Encode -import Material.Icons.Types exposing (Icon) - - -type alias Id = - { id : String } - - -type alias Theme msg model = - { id : String - , title : String - , icon : Icon msg - , view : model -> Html msg - } - - -idDecoder : Decode.Decoder Id -idDecoder = - Decode.map (\s -> { id = s }) Decode.string - - -encodeId : Id -> Encode.Value -encodeId { id } = - Encode.string id diff --git a/src/Library/Time/Ext.elm b/src/Library/Time/Ext.elm deleted file mode 100644 index b91d31aed..000000000 --- a/src/Library/Time/Ext.elm +++ /dev/null @@ -1,104 +0,0 @@ -module Time.Ext exposing (decoder, default, encode, monthName, monthNumber) - -import Json.Decode as Decode exposing (Decoder) -import Json.Encode as Json -import Time exposing (Month(..)) - - - --- ๐Ÿ”ฑ - - -decoder : Decoder Time.Posix -decoder = - Decode.map Time.millisToPosix Decode.int - - -default : Time.Posix -default = - Time.millisToPosix 0 - - -encode : Time.Posix -> Json.Value -encode time = - Json.int (Time.posixToMillis time) - - -monthName : Time.Month -> String -monthName month = - case month of - Jan -> - "January" - - Feb -> - "February" - - Mar -> - "March" - - Apr -> - "April" - - May -> - "May" - - Jun -> - "June" - - Jul -> - "July" - - Aug -> - "August" - - Sep -> - "September" - - Oct -> - "October" - - Nov -> - "November" - - Dec -> - "December" - - -monthNumber : Time.Month -> Int -monthNumber month = - case month of - Jan -> - 1 - - Feb -> - 2 - - Mar -> - 3 - - Apr -> - 4 - - May -> - 5 - - Jun -> - 6 - - Jul -> - 7 - - Aug -> - 8 - - Sep -> - 9 - - Oct -> - 10 - - Nov -> - 11 - - Dec -> - 12 diff --git a/src/Library/Tracks.elm b/src/Library/Tracks.elm deleted file mode 100644 index 372fba7d5..000000000 --- a/src/Library/Tracks.elm +++ /dev/null @@ -1,469 +0,0 @@ -module Tracks exposing (..) - -import Base64 -import List.Extra as List -import Maybe.Extra as Maybe -import Playlists exposing (Playlist, PlaylistTrackWithoutMetadata) -import String.Ext as String -import Time -import Time.Ext as Time - - - --- ๐ŸŒณ - - -type alias Track = - { id : String - , insertedAt : Time.Posix - , path : String - , sourceId : String - , tags : Tags - } - - - --- PIECES - - -type alias Tags = - { disc : Int - , nr : Int - - -- Main - , album : Maybe String - , artist : Maybe String - , title : String - - -- Extra - , genre : Maybe String - , picture : Maybe String - , year : Maybe Int - } - - - --- DERIVATIVES & SUPPLEMENTS - - -type alias Cover = - { group : String - , identifiedTrackCover : IdentifiedTrack - , key : String - , sameAlbum : Bool - , sameArtist : Bool - , trackIds : List String - , tracks : List IdentifiedTrack - , variousArtists : Bool - } - - -type alias Favourite = - { artist : Maybe String - , title : String - } - - -type alias IdentifiedTrack = - ( Identifiers, Track ) - - -type alias Identifiers = - { isFavourite : Bool - , isMissing : Bool - - -- - , filename : String - , group : Maybe { name : String, firstInGroup : Bool } - , indexInList : Int - , indexInPlaylist : Maybe Int - , parentDirectory : String - } - - - --- COLLECTIONS - - -type alias Collection = - { untouched : List Track - - -- `Track`s with `Identifiers` - , identified : List IdentifiedTrack - - -- Sorted, grouped and filtered by playlist (if not auto-generated) - , arranged : List IdentifiedTrack - - -- Filtered by search results, favourites, etc. - , harvested : List IdentifiedTrack - - -- Contexts - ----------- - , scrollContext : String - } - - -type alias CollectionDependencies = - { cached : List String - , cachedOnly : Bool - , enabledSourceIds : List String - , favourites : List Favourite - , favouritesOnly : Bool - , grouping : Maybe Grouping - , hideDuplicates : Bool - , selectedPlaylist : Maybe Playlist - , searchResults : Maybe (List String) - , sortBy : SortBy - , sortDirection : SortDirection - } - - -type alias Parcel = - ( CollectionDependencies, Collection ) - - -type alias CoverCollection = - { arranged : List Cover - , harvested : List Cover - } - - - --- GROUPING & SORTING - - -type Grouping - = AddedOn - | Directory - | FirstAlphaCharacter - | TrackYear - - -type SortBy - = Artist - | Album - | PlaylistIndex - | Title - - -type SortDirection - = Asc - | Desc - - - --- VIEW - - -type Scene - = Covers - | List - - - --- ๐Ÿ”ฑ - - -emptyTrack : Track -emptyTrack = - { id = "" - , insertedAt = Time.default - , path = "" - , sourceId = "" - , tags = emptyTags - } - - -emptyTags : Tags -emptyTags = - { disc = 1 - , nr = 0 - , album = Nothing - , artist = Nothing - , title = "Empty" - , genre = Nothing - , picture = Nothing - , year = Nothing - } - - -emptyIdentifiedTrack : IdentifiedTrack -emptyIdentifiedTrack = - ( emptyIdentifiers - , emptyTrack - ) - - -emptyIdentifiers : Identifiers -emptyIdentifiers = - { isFavourite = False - , isMissing = False - - -- - , filename = "" - , group = Nothing - , indexInList = 0 - , indexInPlaylist = Nothing - , parentDirectory = "" - } - - -emptyCollection : Collection -emptyCollection = - { untouched = [] - , identified = [] - , arranged = [] - , harvested = [] - - -- Contexts - ----------- - , scrollContext = "" - } - - -{-| If a track doesn't fit into a group, where does it go? --} -fallbackCoverGroup : String -fallbackCoverGroup = - "MISSING_TRACK_INFO" - - -{-| This value is used as a fallback in the UI if the album is missing. --} -fallbackAlbum : String -fallbackAlbum = - "" - - -{-| This value is used as a fallback in the UI if the artist is missing. --} -fallbackArtist : String -fallbackArtist = - "" - - - --- MORE STUFF - - -coverGroup : SortBy -> IdentifiedTrack -> String -coverGroup sort ( identifiers, { tags } as track ) = - if identifiers.isMissing then - "MISSING_TRACKS" - - else - case sort of - Artist -> - Maybe.unwrap fallbackCoverGroup (String.trim >> String.toLower) tags.artist - - Album -> - -- There is the possibility of albums with the same name, - -- such as "Greatests Hits". - -- To make sure we treat those as different albums, - -- we prefix the album by its parent directory. - case tags.album of - Just album -> - (identifiers.parentDirectory ++ album) - |> String.trim - |> String.toLower - - Nothing -> - fallbackCoverGroup - - PlaylistIndex -> - "" - - Title -> - tags.title - - -coverKey : Bool -> Track -> String -coverKey isVariousArtists { tags } = - if isVariousArtists then - Maybe.withDefault "?" tags.album - - else - Maybe.withDefault "?" tags.artist ++ " --- " ++ Maybe.withDefault "?" tags.album - - -isNowPlaying : IdentifiedTrack -> IdentifiedTrack -> Bool -isNowPlaying ( a, b ) ( x, y ) = - a.indexInPlaylist == x.indexInPlaylist && b.id == y.id - - -makeTrack : String -> ( String, Tags ) -> Track -makeTrack sourceId ( path, tags ) = - { id = - (sourceId ++ "//" ++ path) - |> Base64.encode - |> String.chopEnd "=" - , insertedAt = Time.default - , path = path - , sourceId = sourceId - , tags = tags - } - - -matchesAutoGeneratedPlaylist : Playlist -> Track -> Bool -matchesAutoGeneratedPlaylist playlist track = - case playlist.autoGenerated of - Just { level } -> - track.path - |> String.split "/" - |> List.drop (max 0 (level - 1)) - |> List.head - |> (==) (Just playlist.name) - - Nothing -> - False - - -missingId : String -missingId = - "" - - -pathParts : Track -> { filename : String, parentDirectory : String } -pathParts { path } = - let - s = - String.split "/" path - - l = - List.length s - in - case List.drop (max 0 <| l - 2) s of - [ p, f ] -> - { filename = f, parentDirectory = p } - - [ f ] -> - { filename = f, parentDirectory = "" } - - _ -> - { filename = "", parentDirectory = "" } - - -{-| Given a collection of tracks, pick out the tracks by id in order. -Note that track ids in the ids list may occur multiple times. --} -pick : List String -> List Track -> List Track -pick ids collection = - collection - |> List.foldr - (\track -> - List.map - (\picking -> - case picking of - PickId id -> - if id == track.id then - PickTrack track - - else - PickId id - - p -> - p - ) - ) - (List.map PickId ids) - |> List.foldr - (\picking acc -> - case picking of - PickId _ -> - acc - - PickTrack track -> - track :: acc - ) - [] - - -removeByPaths : { sourceId : String, paths : List String } -> List Track -> { kept : List Track, removed : List Track } -removeByPaths { sourceId, paths } tracks = - tracks - |> List.foldr - (\t ( kept, removed, remainingPathsToRemove ) -> - if t.sourceId == sourceId && List.member t.path remainingPathsToRemove then - ( kept, t :: removed, List.remove t.path remainingPathsToRemove ) - - else - ( t :: kept, removed, remainingPathsToRemove ) - ) - ( [], [], paths ) - |> (\( k, r, _ ) -> - { kept = k, removed = r } - ) - - -removeBySourceId : String -> List Track -> { kept : List Track, removed : List Track } -removeBySourceId removedSourceId tracks = - tracks - |> List.foldr - (\t ( kept, removed ) -> - if t.sourceId == removedSourceId then - ( kept, t :: removed ) - - else - ( t :: kept, removed ) - ) - ( [], [] ) - |> (\( k, r ) -> - { kept = k, removed = r } - ) - - -removeFromPlaylist : List IdentifiedTrack -> Playlist -> Playlist -removeFromPlaylist tracks playlist = - playlist.tracks - |> List.indexedFoldr - (\idx t ( acc, remaining ) -> - case List.partition ((==) (Just idx)) remaining of - ( _ :: _, rem ) -> - ( acc, rem ) - - ( _, rem ) -> - ( t :: acc, rem ) - ) - ( [] - , List.map (Tuple.first >> .indexInPlaylist) tracks - ) - |> (\( t, _ ) -> { playlist | tracks = t }) - - -shouldNoteProgress : { duration : Float } -> Bool -shouldNoteProgress { duration } = - duration >= 30 * 60 - - -shouldRenderGroup : Identifiers -> Bool -shouldRenderGroup identifiers = - identifiers.group - |> Maybe.map (.firstInGroup >> (==) True) - |> Maybe.withDefault False - - -playlistTrackFromTrack : Track -> PlaylistTrackWithoutMetadata -playlistTrackFromTrack track = - { album = track.tags.album - , artist = track.tags.artist - , title = track.tags.title - } - - -sortByIndexInPlaylist : List IdentifiedTrack -> List IdentifiedTrack -sortByIndexInPlaylist = - List.sortBy (\( i, t ) -> Maybe.withDefault (t.tags.disc * 1000 + t.tags.nr) i.indexInPlaylist) - - -toPlaylistTracks : List IdentifiedTrack -> List PlaylistTrackWithoutMetadata -toPlaylistTracks = - List.map (Tuple.second >> playlistTrackFromTrack) - - - --- ใŠ™๏ธ - - -type Pick - = PickId String - | PickTrack Track diff --git a/src/Library/Tracks/Collection.elm b/src/Library/Tracks/Collection.elm deleted file mode 100644 index 33b91f17c..000000000 --- a/src/Library/Tracks/Collection.elm +++ /dev/null @@ -1,92 +0,0 @@ -module Tracks.Collection exposing (add, arrange, harvest, identifiedTracksChanged, identify, map, replace, tracksChanged) - -import Tracks exposing (IdentifiedTrack, Parcel, Track, emptyCollection) -import Tracks.Collection.Internal as Internal - - - --- ๐Ÿ”ฑ - - -identify : Parcel -> Parcel -identify = - Internal.identify >> Internal.arrange >> Internal.harvest - - -arrange : Parcel -> Parcel -arrange = - Internal.arrange >> Internal.harvest - - -harvest : Parcel -> Parcel -harvest = - Internal.harvest - - -map : (List IdentifiedTrack -> List IdentifiedTrack) -> Parcel -> Parcel -map fn ( model, collection ) = - ( model - , { collection - | identified = fn collection.identified - , arranged = fn collection.arranged - , harvested = fn collection.harvested - } - ) - - - --- โš—๏ธ - - -add : List Track -> Parcel -> Parcel -add tracks ( deps, { untouched } ) = - identify - ( deps - , { emptyCollection | untouched = untouched ++ tracks } - ) - - -replace : List Track -> Parcel -> Parcel -replace tracks ( deps, { untouched } ) = - identify - ( deps - , { emptyCollection | untouched = tracks } - ) - - - --- โš—๏ธ - - -tracksChanged : List Track -> List Track -> Bool -tracksChanged listA listB = - case ( listA, listB ) of - ( [], [] ) -> - False - - ( a :: restA, b :: restB ) -> - if a.id /= b.id then - True - - else - tracksChanged restA restB - - _ -> - True - - -identifiedTracksChanged : List IdentifiedTrack -> List IdentifiedTrack -> Bool -identifiedTracksChanged listA listB = - case ( listA, listB ) of - ( [], [] ) -> - False - - ( ( _, a ) :: restA, ( _, b ) :: restB ) -> - if a.id /= b.id then - True - - else - identifiedTracksChanged restA restB - - _ -> - True diff --git a/src/Library/Tracks/Collection/Internal.elm b/src/Library/Tracks/Collection/Internal.elm deleted file mode 100644 index 16d5b048c..000000000 --- a/src/Library/Tracks/Collection/Internal.elm +++ /dev/null @@ -1,29 +0,0 @@ -module Tracks.Collection.Internal exposing - ( arrange - , harvest - , identify - ) - -import Tracks exposing (Parcel) -import Tracks.Collection.Internal.Arrange as Internal -import Tracks.Collection.Internal.Harvest as Internal -import Tracks.Collection.Internal.Identify as Internal - - - --- ๐Ÿ”ฑ - - -identify : Parcel -> Parcel -identify = - Internal.identify - - -arrange : Parcel -> Parcel -arrange = - Internal.arrange - - -harvest : Parcel -> Parcel -harvest = - Internal.harvest diff --git a/src/Library/Tracks/Collection/Internal/Arrange.elm b/src/Library/Tracks/Collection/Internal/Arrange.elm deleted file mode 100644 index 71a7c7b04..000000000 --- a/src/Library/Tracks/Collection/Internal/Arrange.elm +++ /dev/null @@ -1,305 +0,0 @@ -module Tracks.Collection.Internal.Arrange exposing (arrange) - -import Conditional exposing (ifThenElse) -import Dict exposing (Dict) -import List.Extra as List -import Maybe.Extra as Maybe -import Playlists exposing (..) -import Playlists.Matching -import String.Ext as String -import Time -import Time.Ext as Time -import Tracks exposing (..) -import Tracks.Sorting as Sorting - - - --- ๐Ÿฏ - - -arrange : Parcel -> Parcel -arrange ( deps, collection ) = - case deps.selectedPlaylist of - Just playlist -> - if playlist.collection then - case playlist.autoGenerated of - Just _ -> - arrangeByGroup ( deps, collection ) - - Nothing -> - arrangeByCollection ( deps, collection ) playlist - - else - arrangeByPlaylist ( deps, collection ) playlist - - Nothing -> - arrangeByGroup ( deps, collection ) - - - --- GROUPING - - -arrangeByGroup : Parcel -> Parcel -arrangeByGroup ( deps, collection ) = - case deps.grouping of - Just AddedOn -> - ( deps, groupByInsertedAt deps collection ) - - Just Directory -> - ( deps, groupByDirectory deps collection ) - - Just FirstAlphaCharacter -> - ( deps, groupByFirstAlphaCharacter deps collection ) - - Just TrackYear -> - ( deps, groupByYear deps collection ) - - Nothing -> - collection.identified - |> Sorting.sort deps.sortBy deps.sortDirection - |> (\x -> { collection | arranged = x }) - |> (\x -> ( deps, x )) - - -addToList : a -> Maybe (List a) -> Maybe (List a) -addToList item maybeList = - case maybeList of - Just list -> - Just (item :: list) - - Nothing -> - Just [ item ] - - -groupBy : { reversed : Bool } -> (IdentifiedTrack -> Dict a (List IdentifiedTrack) -> Dict a (List IdentifiedTrack)) -> CollectionDependencies -> Collection -> Collection -groupBy { reversed } folder deps collection = - collection.identified - |> List.foldl folder Dict.empty - |> Dict.values - |> ifThenElse reversed List.reverse identity - |> List.concatMap (Sorting.sort deps.sortBy deps.sortDirection) - |> (\arranged -> { collection | arranged = arranged }) - - - --- GROUPING โ–‘โ–‘ ADDED ON - - -groupByInsertedAt : CollectionDependencies -> Collection -> Collection -groupByInsertedAt = - groupBy { reversed = True } groupByInsertedAtFolder - - -groupByInsertedAtFolder : IdentifiedTrack -> Dict Int (List IdentifiedTrack) -> Dict Int (List IdentifiedTrack) -groupByInsertedAtFolder ( i, t ) = - let - ( year, month ) = - ( Time.toYear Time.utc t.insertedAt - , Time.toMonth Time.utc t.insertedAt - ) - - group = - { name = insertedAtGroupName year month - , firstInGroup = False - } - - item = - ( { i | group = Just group } - , t - ) - in - Dict.update - (year * 1000 + Time.monthNumber month) - (addToList item) - - -insertedAtGroupName : Int -> Time.Month -> String -insertedAtGroupName year month = - if year == 1970 then - "MANY MOONS AGO" - - else - Time.monthName month ++ " " ++ String.fromInt year - - - --- GROUPING โ–‘โ–‘ DIRECTORY - - -groupByDirectory : CollectionDependencies -> Collection -> Collection -groupByDirectory deps = - groupBy { reversed = False } (groupByDirectoryFolder deps) deps - - -groupByDirectoryFolder : CollectionDependencies -> IdentifiedTrack -> Dict String (List IdentifiedTrack) -> Dict String (List IdentifiedTrack) -groupByDirectoryFolder deps ( i, t ) = - let - directory = - t.path - |> String.chopStart "/" - |> String.split "/" - |> (case Maybe.andThen .autoGenerated deps.selectedPlaylist of - Just { level } -> - List.drop (max 0 (level - 1) + 1) - - Nothing -> - identity - ) - |> List.init - |> Maybe.map (String.join " / ") - |> Maybe.withDefault t.path - - group = - { name = directory - , firstInGroup = False - } - - item = - ( { i | group = Just group } - , t - ) - in - Dict.update - directory - (addToList item) - - - --- GROUPING โ–‘โ–‘ FIRST LETTER - - -groupByFirstAlphaCharacter : CollectionDependencies -> Collection -> Collection -groupByFirstAlphaCharacter deps = - groupBy { reversed = False } (groupByFirstAlphaCharacterFolder deps) deps - - -groupByFirstAlphaCharacterFolder : CollectionDependencies -> IdentifiedTrack -> Dict String (List IdentifiedTrack) -> Dict String (List IdentifiedTrack) -groupByFirstAlphaCharacterFolder deps ( i, t ) = - let - tag = - case deps.sortBy of - Artist -> - Maybe.withDefault fallbackArtist t.tags.artist - - Album -> - Maybe.withDefault fallbackAlbum t.tags.album - - PlaylistIndex -> - "" - - Title -> - t.tags.title - - group = - { name = - tag - |> String.toList - |> List.head - |> Maybe.andThen - (\char -> - if Char.isAlpha char then - Just (String.fromList [ Char.toUpper char ]) - - else - Nothing - ) - |> Maybe.withDefault "#" - , firstInGroup = False - } - - item = - ( { i | group = Just group } - , t - ) - in - Dict.update - group.name - (addToList item) - - - --- GROUPING โ–‘โ–‘ YEAR - - -groupByYear : CollectionDependencies -> Collection -> Collection -groupByYear = - groupBy { reversed = True } groupByYearFolder - - -groupByYearFolder : IdentifiedTrack -> Dict Int (List IdentifiedTrack) -> Dict Int (List IdentifiedTrack) -groupByYearFolder ( i, t ) = - let - group = - { name = Maybe.unwrap "0000 - Unknown" String.fromInt t.tags.year - , firstInGroup = False - } - - item = - ( { i | group = Just group } - , t - ) - in - Dict.update - (Maybe.withDefault 0 t.tags.year) - (addToList item) - - - --- PLAYLISTS - - -arrangeByCollection : Parcel -> Playlist -> Parcel -arrangeByCollection ( deps, collection ) playlist = - collection.identified - |> Playlists.Matching.match playlist - |> dealWithMissingPlaylistTracks - |> Sorting.sort deps.sortBy deps.sortDirection - |> (\x -> { collection | arranged = x }) - |> (\x -> ( deps, x )) - - -arrangeByPlaylist : Parcel -> Playlist -> Parcel -arrangeByPlaylist ( deps, collection ) playlist = - collection.identified - |> Playlists.Matching.match playlist - |> dealWithMissingPlaylistTracks - |> Sorting.sort PlaylistIndex Asc - |> (\x -> { collection | arranged = x }) - |> (\x -> ( deps, x )) - - -dealWithMissingPlaylistTracks : ( List IdentifiedTrack, List IdentifiedPlaylistTrack ) -> List IdentifiedTrack -dealWithMissingPlaylistTracks ( identifiedTracks, remainingPlaylistTracks ) = - identifiedTracks ++ List.map makeMissingPlaylistTrack remainingPlaylistTracks - - -makeMissingPlaylistTrack : IdentifiedPlaylistTrack -> IdentifiedTrack -makeMissingPlaylistTrack ( identifiers, playlistTrack ) = - let - tags = - { disc = 1 - , nr = 0 - , artist = playlistTrack.artist - , title = playlistTrack.title - , album = playlistTrack.album - , genre = Nothing - , picture = Nothing - , year = Nothing - } - in - Tuple.pair - { filename = "" - , group = Nothing - , indexInList = 0 - , indexInPlaylist = Just identifiers.index - , isFavourite = False - , isMissing = True - , parentDirectory = "" - } - { tags = tags - , id = missingId - , insertedAt = Time.default - , path = missingId - , sourceId = missingId - } diff --git a/src/Library/Tracks/Collection/Internal/Harvest.elm b/src/Library/Tracks/Collection/Internal/Harvest.elm deleted file mode 100644 index dbd45918a..000000000 --- a/src/Library/Tracks/Collection/Internal/Harvest.elm +++ /dev/null @@ -1,148 +0,0 @@ -module Tracks.Collection.Internal.Harvest exposing (harvest) - -import Dict -import List.Extra as List -import Maybe.Extra as Maybe -import Tracks exposing (..) - - - --- ๐Ÿฏ - - -harvest : Parcel -> Parcel -harvest ( deps, collection ) = - let - harvested = - case deps.searchResults of - Just [] -> - [] - - Just trackIds -> - collection.arranged - |> List.foldl harvester ( [], trackIds ) - |> Tuple.first - - Nothing -> - collection.arranged - - filters = - [ -- Favourites / Missing - ----------------------- - if deps.favouritesOnly then - Tuple.first >> .isFavourite >> (==) True - - else if Maybe.map .autoGenerated deps.selectedPlaylist == Just Nothing then - always True - - else - Tuple.first >> .isMissing >> (==) False - - -- Playlist - ----------- - , case deps.selectedPlaylist of - Just playlist -> - case playlist.autoGenerated of - Just _ -> - \( _, t ) -> - Tracks.matchesAutoGeneratedPlaylist playlist t - - Nothing -> - \( i, _ ) -> - Maybe.isJust i.indexInPlaylist - - Nothing -> - always True - - -- Cached - --------- - , if deps.cachedOnly then - \( _, t ) -> List.member t.id deps.cached - - else - always True - ] - - theFilter x = - List.foldl - (\filter bool -> - if True == bool then - filter x - - else - bool - ) - True - filters - in - harvested - |> List.foldl - (\( i, t ) ( dict, ( idx, prevIdentifiers ), acc ) -> - let - -- Identifier used to distinguish duplicates - s = - case t.tags.artist of - Just artist -> - String.toLower (artist ++ t.tags.title) - - Nothing -> - String.toLower t.tags.title - in - if theFilter ( i, t ) == False then - ( dict, ( idx, prevIdentifiers ), acc ) - - else if deps.hideDuplicates && Dict.member s dict then - ( dict, ( idx, prevIdentifiers ), acc ) - - else - let - prevGroup = - Maybe.unwrap - "" - .name - prevIdentifiers.group - - newIdentifiers = - { i - | group = - Maybe.map - (\g -> { g | firstInGroup = prevGroup /= g.name }) - i.group - , indexInList = idx - } - in - ( if deps.hideDuplicates then - Dict.insert s () dict - - else - dict - -- - , ( idx + 1, newIdentifiers ) - , ( newIdentifiers, t ) :: acc - ) - ) - ( Dict.empty, ( 0, Tracks.emptyIdentifiers ), [] ) - |> (\( _, _, c ) -> c) - |> (\h -> { collection | harvested = List.reverse h }) - |> (\c -> ( deps, c )) - - - --- ๐Ÿ›  - - -harvester : - IdentifiedTrack - -> ( List IdentifiedTrack, List String ) - -> ( List IdentifiedTrack, List String ) -harvester ( i, t ) ( acc, trackIds ) = - case List.findIndex ((==) t.id) trackIds of - Just idx -> - ( acc ++ [ ( i, t ) ] - , List.removeAt idx trackIds - ) - - Nothing -> - ( acc - , trackIds - ) diff --git a/src/Library/Tracks/Collection/Internal/Identify.elm b/src/Library/Tracks/Collection/Internal/Identify.elm deleted file mode 100644 index 42c5568c5..000000000 --- a/src/Library/Tracks/Collection/Internal/Identify.elm +++ /dev/null @@ -1,178 +0,0 @@ -module Tracks.Collection.Internal.Identify exposing (identify) - -import Dict -import List.Extra as List -import Time.Ext as Time -import Tracks exposing (..) - - - --- ๐Ÿ”ฑ - - -identify : Parcel -> Parcel -identify ( deps, collection ) = - let - ( favouritesDictionary, simplifiedFavourites ) = - List.foldr - (\fav ( dict, acc ) -> - let - simpl = - case fav.artist of - Just artist -> - String.toLower artist ++ String.toLower fav.title - - Nothing -> - String.toLower fav.title - in - ( Dict.insert simpl fav dict - , simpl :: acc - ) - ) - ( Dict.empty, [] ) - deps.favourites - - ( identifiedUnsorted, missingFavouritesSimplified ) = - List.foldl - (identifyTrack - deps.enabledSourceIds - simplifiedFavourites - ) - ( [], simplifiedFavourites ) - collection.untouched - - missingFavourites = - List.foldr - (\simpl acc -> - case Dict.get simpl favouritesDictionary of - Just fav -> - fav :: acc - - Nothing -> - acc - ) - [] - missingFavouritesSimplified - in - identifiedUnsorted - |> List.append (List.map makeMissingFavouriteTrack missingFavourites) - |> (\x -> { collection | identified = x }) - |> (\x -> ( deps, x )) - - - --- IDENTIFY - - -identifyTrack : - List String - -> List String - -> Track - -> ( List IdentifiedTrack, List String ) - -> ( List IdentifiedTrack, List String ) -identifyTrack enabledSourceIds favourites track = - if List.member track.sourceId enabledSourceIds then - partTwo favourites track - - else - identity - - -partTwo : - List String - -> Track - -> ( List IdentifiedTrack, List String ) - -> ( List IdentifiedTrack, List String ) -partTwo favourites track ( acc, remainingFavourites ) = - let - isFavourite_ = - isFavourite track - - isFav = - List.any isFavourite_ favourites - - { filename, parentDirectory } = - pathParts track - - identifiedTrack = - ( { isFavourite = isFav - , isMissing = False - - -- - , filename = filename - , group = Nothing - , indexInList = 0 - , indexInPlaylist = Nothing - , parentDirectory = parentDirectory - } - , track - ) - in - if isFav then - -- - -- A favourite - -- - ( identifiedTrack :: acc - , remainingFavourites - |> List.findIndex isFavourite_ - |> Maybe.map (\idx -> List.removeAt idx remainingFavourites) - |> Maybe.withDefault remainingFavourites - ) - - else - -- - -- Not a favourite - -- - ( identifiedTrack :: acc - , remainingFavourites - ) - - - --- FAVOURITES - - -isFavourite : Track -> String -> Bool -isFavourite track = - -- This needs to match the `simplifiedFavourites` format from above - (==) - (case track.tags.artist of - Just artist -> - String.toLower artist ++ String.toLower track.tags.title - - Nothing -> - String.toLower track.tags.title - ) - - -makeMissingFavouriteTrack : Favourite -> IdentifiedTrack -makeMissingFavouriteTrack fav = - let - tags = - { disc = 1 - , nr = 0 - , artist = fav.artist - , title = fav.title - , album = Nothing - , genre = Nothing - , picture = Nothing - , year = Nothing - } - in - ( { isFavourite = True - , isMissing = True - - -- - , filename = "" - , group = Nothing - , indexInList = 0 - , indexInPlaylist = Nothing - , parentDirectory = "" - } - , { tags = tags - , id = missingId - , insertedAt = Time.default - , path = missingId - , sourceId = missingId - } - ) diff --git a/src/Library/Tracks/Encoding.elm b/src/Library/Tracks/Encoding.elm deleted file mode 100644 index 00fd32cc7..000000000 --- a/src/Library/Tracks/Encoding.elm +++ /dev/null @@ -1,227 +0,0 @@ -module Tracks.Encoding exposing (..) - -import Json.Decode as Decode -import Json.Decode.Pipeline exposing (optional, required) -import Json.Encode as Encode -import Json.Encode.Ext exposing (..) -import Time.Ext as Time -import Tracks exposing (..) - - - --- ENCODE - - -encodeFavourite : Favourite -> Encode.Value -encodeFavourite fav = - Encode.object - [ ( "artist", encodeMaybe fav.artist Encode.string ) - , ( "title", Encode.string fav.title ) - ] - - -encodeGrouping : Grouping -> Encode.Value -encodeGrouping v = - case v of - AddedOn -> - Encode.string "ADDED_ON" - - Directory -> - Encode.string "DIRECTORY" - - FirstAlphaCharacter -> - Encode.string "FIRST_ALPHA_CHARACTER" - - TrackYear -> - Encode.string "TRACK_YEAR" - - -encodeScene : Scene -> Encode.Value -encodeScene scene = - case scene of - Covers -> - Encode.string "COVERS" - - List -> - Encode.string "LIST" - - -encodeSortBy : SortBy -> Encode.Value -encodeSortBy v = - case v of - Artist -> - Encode.string "ARTIST" - - Album -> - Encode.string "ALBUM" - - PlaylistIndex -> - Encode.string "PLAYLIST_INDEX" - - Title -> - Encode.string "TITLE" - - -encodeSortDirection : SortDirection -> Encode.Value -encodeSortDirection v = - case v of - Asc -> - Encode.string "ASC" - - Desc -> - Encode.string "DESC" - - -encodeTrack : Track -> Encode.Value -encodeTrack track = - Encode.object - [ ( "id", Encode.string track.id ) - , ( "insertedAt", Time.encode track.insertedAt ) - , ( "path", Encode.string track.path ) - , ( "sourceId", Encode.string track.sourceId ) - , ( "tags", encodeTags track.tags ) - ] - - -encodeTags : Tags -> Encode.Value -encodeTags tags = - Encode.object - [ ( "disc", Encode.int tags.disc ) - , ( "nr", Encode.int tags.nr ) - - -- - , ( "album", encodeMaybe tags.album Encode.string ) - , ( "artist", encodeMaybe tags.artist Encode.string ) - , ( "title", Encode.string tags.title ) - - -- - , ( "genre", encodeMaybe tags.genre Encode.string ) - , ( "picture", encodeMaybe tags.picture Encode.string ) - , ( "year", encodeMaybe tags.year Encode.int ) - ] - - - --- DECODE - - -decodeFavourite : Decode.Value -> Maybe Favourite -decodeFavourite = - Decode.decodeValue favouriteDecoder - >> Result.toMaybe - - -decodeTrack : Decode.Value -> Maybe Track -decodeTrack = - Decode.decodeValue trackDecoder - >> Result.toMaybe - - -favouriteDecoder : Decode.Decoder Favourite -favouriteDecoder = - Decode.map2 Favourite - (Decode.maybe <| Decode.field "artist" Decode.string) - (Decode.field "title" Decode.string) - - -groupingDecoder : Decode.Decoder Grouping -groupingDecoder = - Decode.andThen - (\string -> - case string of - "ADDED_ON" -> - Decode.succeed AddedOn - - "DIRECTORY" -> - Decode.succeed Directory - - "FIRST_ALPHA_CHARACTER" -> - Decode.succeed FirstAlphaCharacter - - "TRACK_YEAR" -> - Decode.succeed TrackYear - - _ -> - Decode.fail "Invalid Grouping" - ) - Decode.string - - -sceneDecoder : Decode.Decoder Scene -sceneDecoder = - Decode.andThen - (\string -> - case string of - "COVERS" -> - Decode.succeed Covers - - "LIST" -> - Decode.succeed List - - _ -> - Decode.fail "Invalid Scene" - ) - Decode.string - - -sortByDecoder : Decode.Decoder SortBy -sortByDecoder = - Decode.andThen - (\string -> - case string of - "ARTIST" -> - Decode.succeed Artist - - "ALBUM" -> - Decode.succeed Album - - "PLAYLIST_INDEX" -> - Decode.succeed PlaylistIndex - - "TITLE" -> - Decode.succeed Title - - _ -> - Decode.fail "Invalid SortBy" - ) - Decode.string - - -sortDirectionDecoder : Decode.Decoder SortDirection -sortDirectionDecoder = - Decode.andThen - (\string -> - case string of - "ASC" -> - Decode.succeed Asc - - "DESC" -> - Decode.succeed Desc - - _ -> - Decode.fail "Invalid SortDirection" - ) - Decode.string - - -tagsDecoder : Decode.Decoder Tags -tagsDecoder = - Decode.map8 Tags - (Decode.field "disc" Decode.int) - (Decode.field "nr" Decode.int) - (Decode.maybe <| Decode.field "album" Decode.string) - (Decode.maybe <| Decode.field "artist" Decode.string) - (Decode.field "title" Decode.string) - (Decode.maybe <| Decode.field "genre" Decode.string) - (Decode.maybe <| Decode.field "picture" Decode.string) - (Decode.maybe <| Decode.field "year" Decode.int) - - -trackDecoder : Decode.Decoder Track -trackDecoder = - Decode.succeed Track - |> required "id" Decode.string - |> optional "insertedAt" Time.decoder Time.default - |> required "path" Decode.string - |> required "sourceId" Decode.string - |> required "tags" tagsDecoder diff --git a/src/Library/Tracks/Favourites.elm b/src/Library/Tracks/Favourites.elm deleted file mode 100644 index c762b7ddf..000000000 --- a/src/Library/Tracks/Favourites.elm +++ /dev/null @@ -1,183 +0,0 @@ -module Tracks.Favourites exposing (completeFavouritesList, completeTracksList, match, removeFromFavouritesList, removeFromTracksList, toggleInFavouritesList, toggleInTracksList) - -import List.Extra as List -import Maybe.Extra as Maybe -import Tracks exposing (Favourite, IdentifiedTrack, Track, fallbackArtist) - - - --- ๐Ÿ”ฑ - - -completeFavouritesList : List IdentifiedTrack -> List Favourite -> List Favourite -completeFavouritesList tracks favourites = - List.append - favourites - (List.filterMap - (\( i, t ) -> - if not i.isFavourite then - Just - { artist = t.tags.artist - , title = t.tags.title - } - - else - Nothing - ) - tracks - ) - - -completeTracksList : List IdentifiedTrack -> List IdentifiedTrack -> List IdentifiedTrack -completeTracksList tracksToMakeFavourite tracks = - let - favs = - List.filterMap - (\( i, t ) -> - if not i.isFavourite then - Just ( lowercaseArtist t, lowercaseTitle t ) - - else - Nothing - ) - tracksToMakeFavourite - in - List.map - (\( i, t ) -> - let - ( la, lt ) = - ( lowercaseArtist t, lowercaseTitle t ) - in - List.foldr - (\( lartist, ltitle ) ( ai, at ) -> - if la == lartist && lt == ltitle && not ai.isFavourite then - ( { ai | isFavourite = True }, at ) - - else - ( ai, at ) - ) - ( i, t ) - favs - ) - tracks - - -match : Favourite -> Favourite -> Bool -match a b = - let - ( aa, at ) = - ( Maybe.unwrap "" String.toLower a.artist - , String.toLower a.title - ) - - ( ba, bt ) = - ( Maybe.unwrap "" String.toLower b.artist - , String.toLower b.title - ) - in - aa == ba && at == bt - - -removeFromFavouritesList : List IdentifiedTrack -> List Favourite -> List Favourite -removeFromFavouritesList tracks favourites = - List.foldr - (\( i, t ) acc -> - if i.isFavourite then - List.filterNot - (match { artist = t.tags.artist, title = t.tags.title }) - acc - - else - acc - ) - favourites - tracks - - -removeFromTracksList : List IdentifiedTrack -> List IdentifiedTrack -> List IdentifiedTrack -removeFromTracksList tracksToRemoveFromFavs tracks = - let - unfavs = - List.filterMap - (\( i, t ) -> - if i.isFavourite then - Just ( lowercaseArtist t, lowercaseTitle t ) - - else - Nothing - ) - tracksToRemoveFromFavs - in - List.map - (\( i, t ) -> - let - ( la, lt ) = - ( lowercaseArtist t, lowercaseTitle t ) - in - List.foldr - (\( lartist, ltitle ) ( ai, at ) -> - if la == lartist && lt == ltitle && ai.isFavourite then - ( { ai | isFavourite = False }, at ) - - else - ( ai, at ) - ) - ( i, t ) - unfavs - ) - tracks - - -toggleInTracksList : Track -> List IdentifiedTrack -> List IdentifiedTrack -toggleInTracksList track = - let - lartist = - lowercaseArtist track - - ltitle = - lowercaseTitle track - in - List.map - (\( i, t ) -> - if lowercaseArtist t == lartist && lowercaseTitle t == ltitle then - ( { i | isFavourite = not i.isFavourite }, t ) - - else - ( i, t ) - ) - - -toggleInFavouritesList : IdentifiedTrack -> List Favourite -> List Favourite -toggleInFavouritesList ( i, t ) favourites = - let - favourite = - { artist = t.tags.artist - , title = t.tags.title - } - in - if i.isFavourite then - -- Remove from list - List.filterNot - (match favourite) - favourites - - else - -- Add to list - List.append - favourites - [ favourite ] - - - --- โš—๏ธ - - -lowercaseArtist : Track -> String -lowercaseArtist = - -- NOTE: Not entirely sure this fallback is correct - .tags >> .artist >> Maybe.unwrap fallbackArtist String.toLower - - -lowercaseTitle : Track -> String -lowercaseTitle = - .tags >> .title >> String.toLower diff --git a/src/Library/Tracks/Sorting.elm b/src/Library/Tracks/Sorting.elm deleted file mode 100644 index dd3c80e41..000000000 --- a/src/Library/Tracks/Sorting.elm +++ /dev/null @@ -1,158 +0,0 @@ -module Tracks.Sorting exposing (sort) - -import Maybe.Extra as Maybe -import Tracks exposing (..) - - - --- ๐Ÿ”ฑ - - -sort : SortBy -> SortDirection -> List IdentifiedTrack -> List IdentifiedTrack -sort property direction list = - let - sortFn = - case property of - Album -> - sortByAlbum - - Artist -> - sortByArtist - - PlaylistIndex -> - sortByPlaylistIndex - - Title -> - sortByTitle - - dirFn = - if direction == Desc then - List.reverse - - else - identity - in - list - |> List.sortWith sortFn - |> dirFn - - - --- BY - - -sortByAlbum : IdentifiedTrack -> IdentifiedTrack -> Order -sortByAlbum ( x, a ) ( y, b ) = - EQ - |> andThenCompareBools isMissing x y - |> andThenCompare album a b - |> andThenCompare parentDir x y - |> andThenCompare disc a b - |> andThenCompare nr a b - |> andThenCompare artist a b - |> andThenCompare title a b - - -sortByArtist : IdentifiedTrack -> IdentifiedTrack -> Order -sortByArtist ( x, a ) ( y, b ) = - EQ - |> andThenCompareBools isMissing x y - |> andThenCompare artist a b - |> andThenCompare album a b - |> andThenCompare parentDir x y - |> andThenCompare disc a b - |> andThenCompare nr a b - |> andThenCompare title a b - - -sortByTitle : IdentifiedTrack -> IdentifiedTrack -> Order -sortByTitle ( _, a ) ( _, b ) = - EQ - |> andThenCompare title a b - |> andThenCompare artist a b - |> andThenCompare album a b - - -sortByPlaylistIndex : IdentifiedTrack -> IdentifiedTrack -> Order -sortByPlaylistIndex ( a, _ ) ( b, _ ) = - andThenCompare (.indexInPlaylist >> Maybe.withDefault 0) a b EQ - - - --- TAGS - - -album : Track -> String -album = - .tags >> .album >> Maybe.unwrap fallbackAlbum low - - -artist : Track -> String -artist = - .tags >> .artist >> Maybe.unwrap fallbackArtist low - - -title : Track -> String -title = - .tags >> .title >> low - - -disc : Track -> Int -disc = - .tags >> .disc - - -nr : Track -> Int -nr = - .tags >> .nr - - -isMissing : Identifiers -> Bool -isMissing = - .isMissing - - -parentDir : Identifiers -> String -parentDir = - .parentDirectory >> low - - - --- COMMON - - -andThenCompare : (ctx -> comparable) -> ctx -> ctx -> Order -> Order -andThenCompare fn a b order = - if order == EQ then - compare (fn a) (fn b) - - else - order - - -andThenCompareBools : (ctx -> Bool) -> ctx -> ctx -> Order -> Order -andThenCompareBools fn a b order = - if order == EQ then - let - af = - fn a - - bf = - fn b - in - if af == bf then - EQ - - else if af == False then - GT - - else - LT - - else - order - - -low : String -> String -low = - String.trim >> String.toLower diff --git a/src/Library/Tuple/Ext.elm b/src/Library/Tuple/Ext.elm deleted file mode 100644 index f52a67774..000000000 --- a/src/Library/Tuple/Ext.elm +++ /dev/null @@ -1,8 +0,0 @@ -module Tuple.Ext exposing (uncurry) - --- ๐Ÿ”ฑ - - -uncurry : (a -> b -> c) -> ( a, b ) -> c -uncurry fn ( a, b ) = - fn a b diff --git a/src/Library/Url/Ext.elm b/src/Library/Url/Ext.elm deleted file mode 100644 index 48501651e..000000000 --- a/src/Library/Url/Ext.elm +++ /dev/null @@ -1,46 +0,0 @@ -module Url.Ext exposing (action, extractQueryParam, queryDictionary) - -import Dict exposing (Dict) -import Maybe.Extra as Maybe -import Url exposing (Url) -import Url.Parser as Url -import Url.Parser.Query as Query - - - --- ๐Ÿ”ฑ - - -action : Url -> List String -action url = - url - |> extractQueryParam "action" - |> Maybe.map (String.split "/") - |> Maybe.withDefault [] - - -extractQueryParam : String -> Url -> Maybe String -extractQueryParam key url = - { url | path = "" } - |> Url.parse (Url.query (Query.string key)) - |> Maybe.join - - -queryDictionary : Url -> Dict String String -queryDictionary url = - url.query - |> Maybe.map (String.split "&") - |> Maybe.withDefault [] - |> List.filterMap - (\s -> - case String.split "=" s of - [ k, v ] -> - Just ( k, v ) - - k :: v -> - Just ( k, String.join "=" v ) - - _ -> - Nothing - ) - |> Dict.fromList diff --git a/src/Library/User/Layer.elm b/src/Library/User/Layer.elm deleted file mode 100644 index f3aee3b8f..000000000 --- a/src/Library/User/Layer.elm +++ /dev/null @@ -1,527 +0,0 @@ -module User.Layer exposing (..) - -{-| User Layer. - -This concerns data that relates to the app, -controlled by the user and stored by the user. - -**Enclosed** data is data like, the enable-shuffle setting, -equalizer settings, or the currently-active-search term. -Which is stored in the browser. - -**Hypaethral** data is data like, the user's favourites, -processed tracks, or the user's sources. -Which is stored in the location chosen by the user. - --} - -import Dict exposing (Dict) -import Enum exposing (Enum) -import Equalizer -import Json.Decode as Json -import Json.Decode.Ext as Json -import Json.Decode.Pipeline exposing (optional) -import Json.Encode -import List.Extra as List -import Maybe.Extra as Maybe -import Playlists -import Playlists.Encoding as Playlists -import Settings -import Sources -import Sources.Encoding as Sources -import Task exposing (Task) -import Theme -import Time -import Time.Ext as Time -import Tracks -import Tracks.Encoding as Tracks - - - --- ๐ŸŒณ - - -type Method - = Dropbox { accessToken : String, expiresAt : Int, refreshToken : String } - | Ipfs { apiOrigin : String } - | RemoteStorage { userAddress : String, token : String } - - -dropboxMethod : Method -dropboxMethod = - Dropbox { accessToken = "", expiresAt = 0, refreshToken = "" } - - -ipfsMethod : Method -ipfsMethod = - Ipfs { apiOrigin = "https://ipfs.io" } - - -remoteStorageMethod : Method -remoteStorageMethod = - RemoteStorage { userAddress = "", token = "" } - - - --- ๐ŸŒณ โ–‘โ–‘ ENCLOSED - - -type alias EnclosedData = - { cachedTracks : List String - , equalizerSettings : Equalizer.Settings - , grouping : Maybe Tracks.Grouping - , onlyShowCachedTracks : Bool - , onlyShowFavourites : Bool - , repeat : Bool - , scene : Tracks.Scene - , searchTerm : Maybe String - , selectedPlaylist : Maybe String - , shuffle : Bool - , sortBy : Tracks.SortBy - , sortDirection : Tracks.SortDirection - , theme : Maybe Theme.Id - } - - - --- ๐ŸŒณ โ–‘โ–‘ HYPAETHRAL - - -type HypaethralBit - = Favourites - | Playlists - | Progress - | Settings - | Sources - | Tracks - -- - | ModifiedAt - - -type HypaethralBaggage - = BaggageClaimed - -- - | PlaylistsBaggage PlaylistsBaggageAttributes - - -type alias HypaethralData = - { favourites : List Tracks.Favourite - , playlists : List Playlists.Playlist - , progress : Dict String Float - , settings : Maybe Settings.Settings - , sources : List Sources.Source - , tracks : List Tracks.Track - - -- - , modifiedAt : Maybe Time.Posix - } - - -type alias PlaylistsBaggageAttributes = - { publicPlaylistsRead : List Json.Value - , publicPlaylistsTodo : List String - , privatePlaylistsRead : List Json.Value - , privatePlaylistsTodo : List String - } - - - --- ๐Ÿ”ฑ โ–‘โ–‘ METHOD - - -decodeMethod : Json.Value -> Maybe Method -decodeMethod = - Json.decodeValue (Json.map methodFromString Json.string) >> Result.toMaybe >> Maybe.join - - -encodeMethod : Method -> Json.Value -encodeMethod = - methodToString >> Json.Encode.string - - -methodName : Method -> String -methodName method = - case method of - Dropbox _ -> - "Dropbox" - - Ipfs _ -> - "IPFS (using MFS)" - - RemoteStorage _ -> - "Remote Storage" - - -methodFromString : String -> Maybe Method -methodFromString string = - case String.split methodSeparator string of - [ "DROPBOX", a, e, r ] -> - Just - (Dropbox - { accessToken = a - , expiresAt = Maybe.withDefault 0 (String.toInt e) - , refreshToken = r - } - ) - - [ "IPFS", a ] -> - Just (Ipfs { apiOrigin = a }) - - [ "REMOTE_STORAGE", u, t ] -> - Just (RemoteStorage { userAddress = u, token = t }) - - _ -> - Nothing - - -methodToString : Method -> String -methodToString method = - case method of - Dropbox { accessToken, expiresAt, refreshToken } -> - String.join - methodSeparator - [ "DROPBOX" - , accessToken - , String.fromInt expiresAt - , refreshToken - ] - - Ipfs { apiOrigin } -> - String.join - methodSeparator - [ "IPFS" - , apiOrigin - ] - - RemoteStorage { userAddress, token } -> - String.join - methodSeparator - [ "REMOTE_STORAGE" - , userAddress - , token - ] - - -methodSeparator : String -methodSeparator = - "___" - - -methodSupportsPublicData : Method -> Bool -methodSupportsPublicData method = - case method of - Dropbox _ -> - False - - Ipfs _ -> - False - - RemoteStorage _ -> - False - - - --- ๐Ÿ”ฑ โ–‘โ–‘ ENCLOSED - - -decodeEnclosedData : Json.Value -> Result Json.Error EnclosedData -decodeEnclosedData = - Json.decodeValue enclosedDataDecoder - - -enclosedDataDecoder : Json.Decoder EnclosedData -enclosedDataDecoder = - Json.succeed EnclosedData - |> optional "cachedTracks" (Json.list Json.string) [] - |> optional "equalizerSettings" Equalizer.settingsDecoder Equalizer.defaultSettings - |> optional "grouping" (Json.maybe Tracks.groupingDecoder) Nothing - |> optional "onlyShowCachedTracks" Json.bool False - |> optional "onlyShowFavourites" Json.bool False - |> optional "repeat" Json.bool False - |> optional "scene" Tracks.sceneDecoder Tracks.Covers - |> optional "searchTerm" (Json.maybe Json.string) Nothing - |> optional "selectedPlaylist" (Json.maybe Json.string) Nothing - |> optional "shuffle" Json.bool False - |> optional "sortBy" Tracks.sortByDecoder Tracks.Album - |> optional "sortDirection" Tracks.sortDirectionDecoder Tracks.Asc - |> optional "theme" (Json.maybe Theme.idDecoder) Nothing - - -encodeEnclosedData : EnclosedData -> Json.Value -encodeEnclosedData { cachedTracks, equalizerSettings, grouping, onlyShowCachedTracks, onlyShowFavourites, repeat, scene, searchTerm, selectedPlaylist, shuffle, sortBy, sortDirection, theme } = - Json.Encode.object - [ ( "cachedTracks", Json.Encode.list Json.Encode.string cachedTracks ) - , ( "equalizerSettings", Equalizer.encodeSettings equalizerSettings ) - , ( "grouping", Maybe.unwrap Json.Encode.null Tracks.encodeGrouping grouping ) - , ( "onlyShowCachedTracks", Json.Encode.bool onlyShowCachedTracks ) - , ( "onlyShowFavourites", Json.Encode.bool onlyShowFavourites ) - , ( "repeat", Json.Encode.bool repeat ) - , ( "scene", Tracks.encodeScene scene ) - , ( "searchTerm", Maybe.unwrap Json.Encode.null Json.Encode.string searchTerm ) - , ( "selectedPlaylist", Maybe.unwrap Json.Encode.null Json.Encode.string selectedPlaylist ) - , ( "shuffle", Json.Encode.bool shuffle ) - , ( "sortBy", Tracks.encodeSortBy sortBy ) - , ( "sortDirection", Tracks.encodeSortDirection sortDirection ) - , ( "theme", Maybe.unwrap Json.Encode.null Theme.encodeId theme ) - ] - - - --- ๐Ÿ”ฑ โ–‘โ–‘ HYPAETHRAL - - -allHypaethralBits : List HypaethralBit -allHypaethralBits = - [ Favourites - , Playlists - , Progress - , Settings - , Sources - , Tracks - ] - - -decodeHypaethralData : Json.Value -> Result Json.Error HypaethralData -decodeHypaethralData = - Json.decodeValue hypaethralDataDecoder - - -emptyHypaethralData : HypaethralData -emptyHypaethralData = - { favourites = [] - , playlists = [] - , progress = Dict.empty - , settings = Nothing - , sources = [] - , tracks = [] - - -- - , modifiedAt = Nothing - } - - -encodeHypaethralBit : HypaethralBit -> HypaethralData -> Json.Value -encodeHypaethralBit bit { favourites, playlists, progress, settings, sources, tracks, modifiedAt } = - case bit of - ModifiedAt -> - Maybe.unwrap Json.Encode.null Time.encode modifiedAt - - _ -> - Json.Encode.object - [ ( "data" - , case bit of - Favourites -> - Json.Encode.list Tracks.encodeFavourite favourites - - ModifiedAt -> - Maybe.unwrap Json.Encode.null Time.encode modifiedAt - - Playlists -> - Json.Encode.list Playlists.encode playlists - - Progress -> - Json.Encode.dict identity Json.Encode.float progress - - Settings -> - Maybe.unwrap Json.Encode.null Settings.encode settings - - Sources -> - Json.Encode.list Sources.encode sources - - Tracks -> - Json.Encode.list Tracks.encodeTrack tracks - ) - , ( "modifiedAt" - , Maybe.unwrap Json.Encode.null Time.encode modifiedAt - ) - ] - - -encodeHypaethralData : HypaethralData -> Json.Value -encodeHypaethralData data = - data - |> encodedHypaethralDataList - |> List.map (Tuple.mapFirst hypaethralBitKey) - |> Json.Encode.object - - -encodedHypaethralDataList : HypaethralData -> List ( HypaethralBit, Json.Value ) -encodedHypaethralDataList data = - List.map - (\bit -> ( bit, encodeHypaethralBit bit data )) - allHypaethralBits - - -hypaethralBit : Enum HypaethralBit -hypaethralBit = - allHypaethralBits - |> List.map (\bit -> ( hypaethralBitKey bit, bit )) - |> Enum.create - - -hypaethralBitFileName : HypaethralBit -> String -hypaethralBitFileName bit = - hypaethralBitKey bit ++ ".json" - - -hypaethralBitKey : HypaethralBit -> String -hypaethralBitKey bit = - case bit of - Favourites -> - "favourites" - - ModifiedAt -> - "modified" - - Playlists -> - "playlists" - - Progress -> - "progress" - - Settings -> - "settings" - - Sources -> - "sources" - - Tracks -> - "tracks" - - -hypaethralDataDecoder : Json.Decoder HypaethralData -hypaethralDataDecoder = - let - optionalWithPossiblyData key dec def a = - optional - (hypaethralBitKey key) - (Json.oneOf [ modifiedAtDecoder dec, noModifiedAt dec ]) - { data = def, modifiedAt = Nothing } - a - in - (\fav pla pro set sor tra mod -> - { favourites = fav.data - , playlists = pla.data - , progress = pro.data - , settings = set.data - , sources = sor.data - , tracks = tra.data - - -- - , modifiedAt = - case mod of - Just m -> - Just (Time.millisToPosix m) - - Nothing -> - [ fav.modifiedAt - , pla.modifiedAt - , pro.modifiedAt - , set.modifiedAt - , sor.modifiedAt - , tra.modifiedAt - ] - |> List.filterMap (Maybe.map Time.posixToMillis) - |> List.sort - |> List.last - |> Maybe.map Time.millisToPosix - } - ) - |> Json.succeed - |> optionalWithPossiblyData Favourites (Json.listIgnore Tracks.favouriteDecoder) [] - |> optionalWithPossiblyData Playlists (Json.listIgnore Playlists.decoder) [] - |> optionalWithPossiblyData Progress (Json.dict Json.float) Dict.empty - |> optionalWithPossiblyData Settings (Json.maybe Settings.decoder) Nothing - |> optionalWithPossiblyData Sources (Json.listIgnore Sources.decoder) [] - |> optionalWithPossiblyData Tracks (Json.listIgnore Tracks.trackDecoder) [] - |> optional (hypaethralBitKey ModifiedAt) (Json.maybe Json.int) Nothing - - - --- merge : HypaethralData -> HypaethralData -> HypaethralData --- merge a b = --- { favourites = List.unique (a.favourites ++ b.favourites) --- , playlists = List.unique (a.playlists ++ b.playlists) --- , progress = List.unique (a.progress ++ b.progress) --- , settings = List.unique (a.settings ++ b.settings) --- , sources = List.unique (a.sources ++ b.sources) --- , tracks = List.unique (a.tracks ++ b.tracks) --- -- --- , modifiedAt = --- case ( a.modifiedAt, b.modifiedAt ) of --- ( Just am, Just bm ) -> --- if Time.posixToMillis am > Time.posixToMillis bm then --- Just am --- else --- Just bm --- ( Just am, Nothing ) -> --- Just am --- ( Nothing, Just bm ) -> --- Just bm --- ( Nothing, Nothing ) -> --- Nothing --- } - - -modifiedAtDecoder : Json.Decoder a -> Json.Decoder { data : a, modifiedAt : Maybe Time.Posix } -modifiedAtDecoder decoder = - Json.map2 - (\d m -> { data = d, modifiedAt = m }) - (Json.field "data" decoder) - (Json.maybe <| Json.field "modifiedAt" Time.decoder) - - -noModifiedAt : Json.Decoder a -> Json.Decoder { data : a, modifiedAt : Maybe Time.Posix } -noModifiedAt = - Json.map - (\data -> - { data = data - , modifiedAt = Nothing - } - ) - - -putHypaethralJsonBitsTogether : List ( HypaethralBit, Json.Value, HypaethralBaggage ) -> Json.Value -putHypaethralJsonBitsTogether bits = - bits - |> List.map (\( a, b, _ ) -> ( hypaethralBitKey a, b )) - |> Json.Encode.object - - -retrieveHypaethralData : (HypaethralBit -> Task x (Maybe Json.Value)) -> Task x (List ( HypaethralBit, Maybe Json.Encode.Value )) -retrieveHypaethralData retrievalFn = - hypaethralBit.list - |> List.map - (\( _, bit ) -> - bit - |> retrievalFn - |> Task.map (\value -> ( bit, value )) - ) - |> Task.sequence - - -saveHypaethralData : (HypaethralBit -> Json.Value -> Task x ()) -> HypaethralData -> Task x () -saveHypaethralData saveFn data = - hypaethralBit.list - |> List.map - (\( _, bit ) -> - data - |> encodeHypaethralBit bit - |> saveFn bit - ) - |> Task.sequence - |> Task.map (always ()) - - - --- ๐Ÿ”ฑ โ–‘โ–‘ BAGGAGE - - -mapPlaylistsBaggage : (PlaylistsBaggageAttributes -> PlaylistsBaggageAttributes) -> HypaethralBaggage -> HypaethralBaggage -mapPlaylistsBaggage fn baggage = - case baggage of - PlaylistsBaggage p -> - PlaylistsBaggage (fn p) - - b -> - b diff --git a/src/Library/User/Layer/Methods/Dropbox.elm b/src/Library/User/Layer/Methods/Dropbox.elm deleted file mode 100644 index 272421695..000000000 --- a/src/Library/User/Layer/Methods/Dropbox.elm +++ /dev/null @@ -1,114 +0,0 @@ -module User.Layer.Methods.Dropbox exposing (..) - -import Common -import Http -import Http.Ext as Http -import Http.Extras as Http -import Json.Decode as Json -import Task exposing (Task) -import Url exposing (Url) - - - --- ๐ŸŒณ - - -type alias Tokens = - { accessToken : String - , expiresIn : Int -- Time in seconds the access token expires in - , refreshToken : Maybe String - } - - - --- ๐Ÿ” - - -clientId : String -clientId = - "te0c9pbeii8f8bw" - - -clientSecret : String -clientSecret = - "kxmlfdsw8k9e0ot" - - -redirectUri : Url -> String -redirectUri url = - Common.urlOrigin url ++ "?action=authenticate/dropbox" - - - --- ENCODING - - -tokensDecoder : Json.Decoder Tokens -tokensDecoder = - Json.map3 - (\a e r -> - { accessToken = a - , expiresIn = e - , refreshToken = r - } - ) - (Json.field "access_token" Json.string) - (Json.field "expires_in" Json.int) - (Json.string - |> Json.field "refresh_token" - |> Json.maybe - ) - - - --- ๐Ÿ›  - - -exchangeAuthCode : (Result Http.Error Tokens -> msg) -> Url -> String -> Cmd msg -exchangeAuthCode msg url code = - [ ( "client_id", clientId ) - , ( "client_secret", clientSecret ) - , ( "code", code ) - , ( "grant_type", "authorization_code" ) - , ( "redirect_uri", redirectUri url ) - ] - |> Common.queryString - |> String.append "https://api.dropboxapi.com/oauth2/token" - |> (\u -> - { url = u - , body = Http.emptyBody - , expect = Http.expectJson msg tokensDecoder - } - ) - |> Http.post - - -refreshAccessToken : String -> Task String Tokens -refreshAccessToken refreshToken = - [ ( "client_id", clientId ) - , ( "client_secret", clientSecret ) - , ( "refresh_token", refreshToken ) - , ( "grant_type", "refresh_token" ) - ] - |> Common.queryString - |> String.append "https://api.dropboxapi.com/oauth2/token" - |> (\u -> - { method = "POST" - , headers = [] - , url = u - , body = Http.emptyBody - , resolver = - Http.stringResolver - (\resp -> - resp - |> Http.responseToString - |> Result.mapError Http.errorToString - |> Result.andThen - (Json.decodeString tokensDecoder - >> Result.mapError Json.errorToString - ) - ) - , timeout = Nothing - } - ) - |> Http.task diff --git a/src/Library/User/Layer/Methods/RemoteStorage.elm b/src/Library/User/Layer/Methods/RemoteStorage.elm deleted file mode 100644 index 62f9f9d1d..000000000 --- a/src/Library/User/Layer/Methods/RemoteStorage.elm +++ /dev/null @@ -1,113 +0,0 @@ -module User.Layer.Methods.RemoteStorage exposing (Attributes, oauthAddress, parseUserAddress, userAddressError, webfingerAddress, webfingerDecoder, webfingerError, webfingerRequest) - -import Base64 -import Http -import Json.Decode as Decode exposing (Decoder) -import Url -import UrlBase64 - - - --- ๐ŸŒณ - - -type alias Attributes = - { host : String - , username : String - } - - -userAddressError = - "Please provide a valid RemoteStorage address, the format is **user@server**" - - -webfingerError = - "**Failed to connect** to the given RemoteStorage server, maybe a typo?" - - - --- ๐Ÿ”ฑ - - -parseUserAddress : String -> Maybe Attributes -parseUserAddress str = - case String.split "@" str of - [ u, h ] -> - Just { host = h, username = u } - - _ -> - Nothing - - -oauthAddress : { oauthOrigin : String, origin : String } -> Attributes -> String -oauthAddress { oauthOrigin, origin } { host, username } = - let - hostWithoutProtocol = - host - |> String.split "://" - |> List.drop 1 - |> List.head - |> Maybe.withDefault host - - ua = - (username ++ "@" ++ hostWithoutProtocol) - |> UrlBase64.encode (Base64.encode >> Ok) - |> Result.withDefault "BASE64_ENCODING_FAILED" - in - String.concat - [ oauthOrigin - , "?redirect_uri=" ++ Url.percentEncode (origin ++ "?action=authenticate/remotestorage/" ++ ua) - , "&client_id=" ++ Url.percentEncode origin - , "&scope=" ++ Url.percentEncode "diffuse:rw" - , "&response_type=token" - ] - - -webfingerAddress : Url.Protocol -> Attributes -> String -webfingerAddress originProtocol { host, username } = - let - fallbackProtocol = - case originProtocol of - Url.Http -> - "http" - - Url.Https -> - "https" - - protocol = - if String.contains "://" host then - host - |> String.split "://" - |> List.head - |> Maybe.withDefault fallbackProtocol - - else - fallbackProtocol - - hostWithoutProtocol = - host - |> String.split "://" - |> List.drop 1 - |> List.head - |> Maybe.withDefault host - in - protocol ++ "://" ++ hostWithoutProtocol ++ "/.well-known/webfinger?resource=acct:" ++ Url.percentEncode (username ++ "@" ++ hostWithoutProtocol) - - -webfingerDecoder : Decoder String -webfingerDecoder = - Decode.at - [ "links" - , "0" - , "properties" - , "http://tools.ietf.org/html/rfc6749#section-4.2" - ] - Decode.string - - -webfingerRequest : (Attributes -> Result Http.Error String -> msg) -> Url.Protocol -> Attributes -> Cmd msg -webfingerRequest toMsg originProtocol rs = - Http.get - { url = webfingerAddress originProtocol rs - , expect = Http.expectJson (toMsg rs) webfingerDecoder - } diff --git a/src/README.md b/src/README.md deleted file mode 100644 index 17b2300ef..000000000 --- a/src/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Elm code - -Elm directories: -- src/Core -- src/Library - -`UI` is the Elm application that'll be executed on the main thread (ie. the UI thread) and `Brain` is the Elm application that'll live inside a web worker. `UI` will be the main application and `Brain` does the heavy lifting. The code shared between these two applications lives in `Library`. The library also contains the more "generic" code that's not necessarily tied to one or the other. Additionally you have `Themes` which is a layer on top of the UI code. - - - -## Emoji Comments - -``` -โ›ฉ Gates (Flags & Ports) -๐ŸŒณ Types & Initial State -๐Ÿ“ฃ Updates -๐Ÿ“ฐ Subscriptions -๐Ÿ—บ Views - -๐Ÿง  Brain -๐Ÿ› ๏ธ Functions -๐Ÿ–ผ Styles -ใŠ™๏ธ Secret -``` diff --git a/src/Static/About/CORS.md b/src/Static/About/CORS.md deleted file mode 100644 index 5c72c453a..000000000 --- a/src/Static/About/CORS.md +++ /dev/null @@ -1,121 +0,0 @@ -> A music player that connects to your cloud & distributed storage - -[Return to the application](../../)
-[About](../) - - - -
- -### CORS - -There's only one thing you need to do yourself so that the service you chose will work with the application, and that's setting up [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (Cross-Origin Resource Sharing). Here are the instructions you'll need for each service: - -
- -#### Amazon S3 - -You can find the CORS configuration editor under the "Permissions" tab, on the S3 AWS Console. - -```json -[ - { - "AllowedHeaders": [ - "Range", - "X-Playback-Session-Id" - ], - "AllowedMethods": [ - "GET", - "HEAD" - ], - "AllowedOrigins": [ - "https://diffuse.sh", - "http://127.0.0.1:44999" - ], - "ExposeHeaders": [ - "Accept-Ranges", - "Content-Length", - "Content-Range" - ], - "MaxAgeSeconds": 31536000 - } -] -``` - -
- -#### Dropbox - -_Not necessary._ - -
- -#### IPFS - -Add the domain of the app, with the protocol, to the __list of allowed origins__. - -```shell -ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["https://diffuse.sh", "http://diffuse.sh.ipns.localhost:8080", "http://127.0.0.1:44999"]' -``` - -You can also make this change in the Web UI, you'll find it under "Settings โ†’ IPFS Config". - -```javascript -{ - "API": { - "HTTPHeaders": { - "Access-Control-Allow-Origin": [ - ... // Default IPFS values - - "https://diffuse.sh", // ๐ŸŽต Default - "http://diffuse.sh.ipns.localhost:8080", // IPNS through IPFS Companion - "http://127.0.0.1:44999" // Electron app - ] - } - } -} -``` - -
- -#### Microsoft Azure Storage - -You can find the CORS configuration under the "Settings -> CORS". -Then fill in the following in the input boxes (left to right): - -``` -ALLOWED ORIGINS * -ALLOWED METHODS GET, HEAD -ALLOWED HEADERS Range, X-Playback-Session-Id -EXPOSED HEADERS Accept-Ranges, Content-Length, Content-Range -MAX AGE 0 -``` - -
- -#### WebDAV - -__Depends on your WebDAV server.__ -Example setup for Henrique Dias's [WebDAV server](https://github.com/hacdias/webdav): - -```yaml -cors: - enabled: true - credentials: true - - allowed_headers: - - Authorization - - Depth - - X-Playback-Session-Id - allowed_methods: - - GET - - HEAD - - PROPFIND - allowed_hosts: - - https://diffuse.sh - - http://127.0.0.1:44999 - exposed_headers: - - Accept-Ranges - - Content-Length - - Content-Range -``` diff --git a/src/Static/About/Dev.md b/src/Static/About/Dev.md deleted file mode 100644 index c3e002d6c..000000000 --- a/src/Static/About/Dev.md +++ /dev/null @@ -1,111 +0,0 @@ -> A music player that connects to your cloud & distributed storage - -[Return to the application](../../)
-[About](../) - - - -## Adding Sources Using Query Parameter - -```js -JSON = encodeURIComponent(JSON.stringify({ - // Object contents depends on type of source, see below. - kind: "ipfs", - data: { - name: "Music from IPFS", - - // Source type specific - directoryHash: "Qm..." - } -})) - -"https://diffuse.sh?source=JSON" -``` - -You can add multiple "source" query parameters, if you want to add multiple sources. - -### Amazon S3 - -```yaml -kind: - amazon_s3 - -data: - # Required - accessKey - bucketName - name - region - secretKey - - # Optional - directoryPath - host -``` - -### Azure - -```yaml -kind: - "azure_file" # or "azure_blob" - -data: - # Required - accountName - accountKey - container - name - - # Optional - directoryPath -``` - -### Dropbox - -```yaml -kind: - dropbox - -data: - # Required - accessToken - appKey - name - - # Optional - directoryPath -``` - -### IPFS - -```yaml -kind: - ipfs - -data: - # Required - directoryHash - name - - # Optional - gateway - ipns โ† boolean, `t` of `f` - local โ† boolean, `t` of `f` -``` - -### WebDAV - -```yaml -kind: - webdav - -data: - # Required - name - url - - # Optional - directoryPath - password - username -``` diff --git a/src/Static/About/Index.md b/src/Static/About/Index.md deleted file mode 100644 index f8f15b5a9..000000000 --- a/src/Static/About/Index.md +++ /dev/null @@ -1,141 +0,0 @@ -> A music player that connects to your cloud & distributed storage - -[Return to the application](../)
-[CORS instructions](cors/)
-[Developers](dev/)
-[Privacy policy](../privacy.txt) - - - -## What makes it different? - -Diffuse is a decentralized music player consisting out of two main parts. One part is the music and the other is your data (eg. playlists), both of which are in locations of your choice. Meaning that there's no central server for Diffuse, all of the processing happens on your device and all the data is in your control. You can use the [web version](https://diffuse.sh), the [native version](https://github.com/icidasset/diffuse/releases) or host it yourself by downloading the pre-built packages from [Github](https://github.com/icidasset/diffuse). - - -### Music layer - -This layer connects to the services on which your music is stored, no data is written to these services. You can combine all of the following: - -- [Amazon S3](https://aws.amazon.com/s3/) -- [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/) -- [Azure File Storage](https://azure.microsoft.com/en-us/services/storage/files/) -- [Dropbox](https://dropbox.com/) -- [IPFS](https://ipfs.io/) (supports DNSLink & IPNS) -- [WebDAV](https://en.wikipedia.org/wiki/WebDAV) - - -### User layer - -This (optional) layer will use a single service on which to store your data externally. Your data being your settings, favourites, playlists, etc. You can choose between these services: - -- [Dropbox](https://www.dropbox.com/) -- [IPFS](https://ipfs.io/) (using MFS) -- [RemoteStorage](https://remotestorage.io/) - - - -
- -## How does it work? - -Diffuse locates all the music files on the given services, extracts the metadata and then stores it via the previously-explained user layer. - - -### Supported File Formats - -- MP3 -- MP4/M4A -- FLAC -- OGG -- WAV -- WEBM - -*Note, support may vary depending on your browser.* - - - -
- -## UI - -There are a few "hidden" features: - -- **Tracks have a context menu** which can be opened by either right clicking, - or holding it (ie. a long tap). Use the ALT key whilst right clicking - on a track to show the alternative track-context menu with more specialized options. -- **You can reorder items** in the queue or a playlist with drag-and-drop. - Select the item you want to move by tapping on it, then tap and hold to move it around. -- You can select multiple tracks using the SHIFT key and then add that selection - to the queue or a playlist using the context menu. -- Click on the now-playing bit to scroll to that track. -- Double tap on a EQ setting to reset it to its default value. - -### Playlists - -To add something to a playlist, and create that playlist if it doesn't exist yet, you open the context menu of a track. To move tracks around in a playlist, first select the track, then drag it. - -### Search - -```shell -# Show me every track where the title, artist or album contains the term 'Parkway' and the term 'Drive'. -Parkway Drive - -# Show me every track of which the artist's name starts with 'park'. -artist:park - -# Show me every track from Parkway Drive of which the album starts with "Deep Blue". -artist:Parkway Drive album:Deep Blue - -# Show me every track from Parkway Drive but not their "Atlas" album. -artist:Parkway Drive - album:Atlas -``` - -### Keyboard - -The app should be usable with only the keyboard, there are various keyboard shortcuts: - -```js -CTRL + K or CMD + K // Show command palette - -CTRL + L // Select playlist using autocompletion -CTRL + N // Scroll to currently-playing track -CTRL + P // Play / Pause -CTRL + R // Toggle Repeat -CTRL + S // Toggle Shuffle - -CTRL + [ or ] // Previous / Next -CTRL + { or } // Seek forwards / Seek backwards - -Alternatively you can use the media-control keys, -if your browser supports it. - -ESC // Close overlay, close context-menu, deselect album cover, etc. - -CTRL + 1 // Tracks -CTRL + 2 // Playlists -CTRL + 3 // Queue -CTRL + 4 // EQ - -CTRL + 8 // Sources -CTRL + 9 // Settings -``` - - - -
- -## Q&A - -### I used version one, where's my data? - -There's a small link, or button if you will, on the "Settings โ†’ Import / Export" -page that will allow you to import data from version 1 of the app. Note that this -will need to reflect the authentication/storage method you chose in version 1. - - - -
- -## ๐Ÿช - -_Part of the App Ring_ diff --git a/src/Static/About/Layout.gren b/src/Static/About/Layout.gren deleted file mode 100644 index 8c2f1e48a..000000000 --- a/src/Static/About/Layout.gren +++ /dev/null @@ -1,135 +0,0 @@ -module About.Layout exposing (..) - -import Transmutable.Html as Html exposing ( Html ) -import Transmutable.Html.Attributes as A - - -layout : - { pathToRoot : String - } - -> Array (Html msg) - -> Array (Html msg) -layout { pathToRoot } contents = - [ Html.doctype - , Html.html - [ A.lang "en" - ] - [ Html.head - [] - [ Html.meta - [ A.charset "utf8" - ] - , Html.title - [ Html.text "About | Diffuse" - ] - , Html.meta - [ A.name "description" - , A.content "A music player that connects to your cloud/distributed storage, in the form of a static, serverless, web application." - ] - - , -- Viewport - Html.meta - [ A.name "viewport" - , A.content "width=device-width, initial-scale=1" - ] - - , -- Favicons & Mobile - Html.link - [ A.rel "apple-touch-icon" - , A.href (pathToRoot ++ "apple-touch-icon.png") - , A.attribute "sizes" "180x180" - ] - , Html.link - [ A.rel "icon" - , A.href (pathToRoot ++ "favicon-32x32.png") - , A.attribute "sizes" "32x32" - ] - , Html.link - [ A.rel "icon" - , A.type_ "image/png" - , A.href (pathToRoot ++ "favicon-16x16.png") - , A.attribute "sizes" "16x16" - ] - , Html.link - [ A.rel "manifest" - , A.href (pathToRoot ++ "site.webmanifest") - ] - , Html.link - [ A.rel "mask-icon" - , A.href (pathToRoot ++ "safari-pinned-tab.svg") - , A.attribute "color" "#8a90a9" - ] - , Html.meta - [ A.name "msapplication-TileColor" - , A.content "#8a90a9" - ] - , Html.meta - [ A.name "theme-color" - , A.content "#8a90a9" - ] - - , -- Styles - Html.meta - [ A.name "color-scheme" - , A.content "dark light" - ] - , Html.link - [ A.rel "stylesheet" - , A.href (pathToRoot ++ "about.css") - ] - ] - - , -- - Html.body - [ A.class "font-body text-base01 dark:bg-darkest-hour my-16 bg-white px-4 dark:text-gray-600" - ] - [ Html.main_ - [ A.class "mx-auto max-w-2xl" - ] - [ Html.a - [ A.class "logo inline-block" - , A.href pathToRoot - ] - [ Html.img - [ A.class "block dark:hidden" - , A.src (pathToRoot ++ "images/diffuse-dark.svg") - ] - [] - , Html.img - [ A.class "hidden dark:block" - , A.src (pathToRoot ++ "images/diffuse-light.svg") - ] - [] - , Html.h1 - [] - [ Html.text "Diffuse" - ] - ] - - , -- - Html.article [] contents - ] - - , -- - Html.node - "script" - [ A.src "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/highlight.min.js" - ] - [] - , Html.node - "script" - [] - [ Html.text "hljs.initHighlightingOnLoad();" - ] - , Html.node - "script" - [] - [ Html.text "if (\"serviceWorker\" in navigator) {\n" - , Html.text " navigator.serviceWorker.register(\"" - , Html.text pathToRoot - , Html.text "service-worker.js\", { type: \"module\" });\n" - , Html.text "}" - ] - ] - ] - ] diff --git a/src/Static/Fonts/Hack/bold-subset.woff2 b/src/Static/Fonts/Hack/bold-subset.woff2 deleted file mode 100644 index 93d425efd..000000000 Binary files a/src/Static/Fonts/Hack/bold-subset.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Hack/bold.woff2 b/src/Static/Fonts/Hack/bold.woff2 deleted file mode 100644 index 1155477e9..000000000 Binary files a/src/Static/Fonts/Hack/bold.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Hack/regular-subset.woff2 b/src/Static/Fonts/Hack/regular-subset.woff2 deleted file mode 100644 index 1e3abb96e..000000000 Binary files a/src/Static/Fonts/Hack/regular-subset.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Hack/regular.woff2 b/src/Static/Fonts/Hack/regular.woff2 deleted file mode 100644 index 524465cf5..000000000 Binary files a/src/Static/Fonts/Hack/regular.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Montserrat/Old/bold.woff2 b/src/Static/Fonts/Montserrat/Old/bold.woff2 deleted file mode 100644 index d9940cd11..000000000 Binary files a/src/Static/Fonts/Montserrat/Old/bold.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Montserrat/Old/extrabold.woff2 b/src/Static/Fonts/Montserrat/Old/extrabold.woff2 deleted file mode 100644 index d5022db1b..000000000 Binary files a/src/Static/Fonts/Montserrat/Old/extrabold.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Montserrat/Old/light.woff2 b/src/Static/Fonts/Montserrat/Old/light.woff2 deleted file mode 100644 index 31b855fe1..000000000 Binary files a/src/Static/Fonts/Montserrat/Old/light.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Montserrat/Old/medium.woff2 b/src/Static/Fonts/Montserrat/Old/medium.woff2 deleted file mode 100644 index 50b35aa92..000000000 Binary files a/src/Static/Fonts/Montserrat/Old/medium.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Montserrat/Old/regular.woff2 b/src/Static/Fonts/Montserrat/Old/regular.woff2 deleted file mode 100644 index 72d13c60b..000000000 Binary files a/src/Static/Fonts/Montserrat/Old/regular.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Montserrat/Old/semibold.woff2 b/src/Static/Fonts/Montserrat/Old/semibold.woff2 deleted file mode 100644 index c9d70ea59..000000000 Binary files a/src/Static/Fonts/Montserrat/Old/semibold.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Montserrat/Variable/variable.woff2 b/src/Static/Fonts/Montserrat/Variable/variable.woff2 deleted file mode 100644 index 2a938cbd6..000000000 Binary files a/src/Static/Fonts/Montserrat/Variable/variable.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Playfair Display/bold.woff2 b/src/Static/Fonts/Playfair Display/bold.woff2 deleted file mode 100644 index 38ae598dd..000000000 Binary files a/src/Static/Fonts/Playfair Display/bold.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/black.woff2 b/src/Static/Fonts/Source Sans Pro/Old/black.woff2 deleted file mode 100755 index a5618c0f6..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/black.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/bold-italic.woff2 b/src/Static/Fonts/Source Sans Pro/Old/bold-italic.woff2 deleted file mode 100755 index 9b659fef5..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/bold-italic.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/bold.woff2 b/src/Static/Fonts/Source Sans Pro/Old/bold.woff2 deleted file mode 100755 index edc11b3ae..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/bold.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/italic.woff2 b/src/Static/Fonts/Source Sans Pro/Old/italic.woff2 deleted file mode 100755 index f384ac680..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/italic.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/light-italic.woff2 b/src/Static/Fonts/Source Sans Pro/Old/light-italic.woff2 deleted file mode 100755 index 8b8e01fc4..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/light-italic.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/light.woff2 b/src/Static/Fonts/Source Sans Pro/Old/light.woff2 deleted file mode 100755 index b53ed655e..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/light.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/regular.woff2 b/src/Static/Fonts/Source Sans Pro/Old/regular.woff2 deleted file mode 100755 index 51a632966..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/regular.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/semibold-italic.woff2 b/src/Static/Fonts/Source Sans Pro/Old/semibold-italic.woff2 deleted file mode 100755 index caa153ed4..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/semibold-italic.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Old/semibold.woff2 b/src/Static/Fonts/Source Sans Pro/Old/semibold.woff2 deleted file mode 100755 index ee29125c6..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Old/semibold.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Variable/italic.woff2 b/src/Static/Fonts/Source Sans Pro/Variable/italic.woff2 deleted file mode 100644 index 13bcc865e..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Variable/italic.woff2 and /dev/null differ diff --git a/src/Static/Fonts/Source Sans Pro/Variable/roman.woff2 b/src/Static/Fonts/Source Sans Pro/Variable/roman.woff2 deleted file mode 100644 index b60988fe2..000000000 Binary files a/src/Static/Fonts/Source Sans Pro/Variable/roman.woff2 and /dev/null differ diff --git a/src/Static/Hosting/CORS b/src/Static/Hosting/CORS deleted file mode 100644 index 72e8ffc0d..000000000 --- a/src/Static/Hosting/CORS +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/src/Static/Hosting/_headers b/src/Static/Hosting/_headers deleted file mode 100644 index f7e3db34b..000000000 --- a/src/Static/Hosting/_headers +++ /dev/null @@ -1,2 +0,0 @@ -/manifest.json - Access-Control-Allow-Origin: * diff --git a/src/Static/Hosting/_redirects b/src/Static/Hosting/_redirects deleted file mode 100644 index 4353d4fcb..000000000 --- a/src/Static/Hosting/_redirects +++ /dev/null @@ -1 +0,0 @@ -/* /index.html 301 diff --git a/src/Static/Hosting/privacy.txt b/src/Static/Hosting/privacy.txt deleted file mode 100644 index 71e9526be..000000000 --- a/src/Static/Hosting/privacy.txt +++ /dev/null @@ -1,20 +0,0 @@ -Privacy Policy --------------- - -Your privacy is important to us. It is Diffuse's policy to respect your privacy regarding any information we may collect from you across our website, https://diffuse.sh, and other sites we own and operate. - -We only ask for personal information when we truly need it to provide a service to you. We collect it by fair and lawful means, with your knowledge and consent. We also let you know why weโ€™re collecting it and how it will be used. - -We only retain collected information for as long as necessary to provide you with your requested service. What data we store, weโ€™ll protect within commercially acceptable means to prevent loss and theft, as well as unauthorised access, disclosure, copying, use or modification. - -We donโ€™t share any personally identifying information publicly or with third-parties, except when required to by law. - -Our website may link to external sites that are not operated by us. Please be aware that we have no control over the content and practices of these sites, and cannot accept responsibility or liability for their respective privacy policies. - -You are free to refuse our request for your personal information, with the understanding that we may be unable to provide you with some of your desired services. - -Your continued use of our website will be regarded as acceptance of our practices around privacy and personal information. If you have any questions about how we handle user data and personal information, feel free to contact us. - -This policy is effective as of 11 March 2018. - -Generated by GetTerms.io diff --git a/src/Static/Html/301.html b/src/Static/Html/301.html deleted file mode 100644 index 8f1511c90..000000000 --- a/src/Static/Html/301.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/Static/Html/Application.html b/src/Static/Html/Application.html deleted file mode 100644 index ead34426c..000000000 --- a/src/Static/Html/Application.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - Diffuse - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - - - - diff --git a/src/Static/Images/ep_naturalblack_pattern.jpg b/src/Static/Images/ep_naturalblack_pattern.jpg deleted file mode 100644 index b06aa8ff4..000000000 Binary files a/src/Static/Images/ep_naturalblack_pattern.jpg and /dev/null differ diff --git a/src/Static/Images/ep_naturalblack_pattern.png b/src/Static/Images/ep_naturalblack_pattern.png deleted file mode 100644 index 323409bd7..000000000 Binary files a/src/Static/Images/ep_naturalblack_pattern.png and /dev/null differ diff --git a/src/Static/Images/icon.png b/src/Static/Images/icon.png deleted file mode 100644 index c8a86c342..000000000 Binary files a/src/Static/Images/icon.png and /dev/null differ diff --git a/src/Static/Images/ocean.jpg b/src/Static/Images/ocean.jpg deleted file mode 100644 index 747a64b64..000000000 Binary files a/src/Static/Images/ocean.jpg and /dev/null differ diff --git a/src/Static/Images/zwartevilt.png b/src/Static/Images/zwartevilt.png deleted file mode 100644 index dc1ebab48..000000000 Binary files a/src/Static/Images/zwartevilt.png and /dev/null differ diff --git a/src/Static/Manifests/manifest.json b/src/Static/Manifests/manifest.json deleted file mode 100644 index 9246ea266..000000000 --- a/src/Static/Manifests/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Diffuse", - "short_name": "Diffuse", - "description": "A music player that connects to your cloud/distributed storage", - "version": "3.5.0", - "author": "Steven Vandevelde ", - "icons": [ - { - "src": "images/icon.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "images/icon.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "start_url": ".", - "theme_color": "#8a90a9", - "background_color": "#02070e", - "display": "standalone", - "screenshots": [ - { - "src": "https://icidasset-public.s3.amazonaws.com/diffuse-v3.jpg", - "type": "image/jpg", - "sizes": "2840x1602" - }, - { - "src": "https://icidasset-public.s3.amazonaws.com/diffuse-v3-art.jpg", - "type": "image/jpg", - "sizes": "2840x1602" - } - ] -} diff --git a/src/_components/diffuse/logo.vto b/src/_components/diffuse/logo.vto new file mode 100644 index 000000000..15b15353f --- /dev/null +++ b/src/_components/diffuse/logo.vto @@ -0,0 +1,6 @@ + + Diffuse + + diff --git a/src/_components/element.vto b/src/_components/element.vto new file mode 100644 index 000000000..43ad66508 --- /dev/null +++ b/src/_components/element.vto @@ -0,0 +1,9 @@ +
+

{{title}}

+ +

+ {{content}} +

+ + {{ await comp.list({ items }) }} +
diff --git a/src/_components/list.vto b/src/_components/list.vto new file mode 100644 index 000000000..1340844a8 --- /dev/null +++ b/src/_components/list.vto @@ -0,0 +1,15 @@ +
    + {{ for item of items }} +
  • + {{ if item.todo }} + [todo] + {{item.title}} + {{ else }} + {{item.title}} + {{ /if }} + {{ if item.desc }} +
    {{item.desc |> md(true)}}
    + {{ /if }} +
  • + {{ /for }} +
diff --git a/src/_includes/layouts/diffuse.vto b/src/_includes/layouts/diffuse.vto new file mode 100644 index 000000000..f9207af41 --- /dev/null +++ b/src/_includes/layouts/diffuse.vto @@ -0,0 +1,44 @@ +--- +title: "Diffuse" + +base: "./" +scripts: [] +styles: [] +--- + + + + + + + + + {{title}} + + + + + + + + + + + + + + + {{ for url of styles }} + + {{ /for }} + + + {{ for url of scripts }} + + {{ /for }} + + + {{ content }} + + diff --git a/src/common/constituents/default.js b/src/common/constituents/default.js new file mode 100644 index 000000000..173a1e5e0 --- /dev/null +++ b/src/common/constituents/default.js @@ -0,0 +1,88 @@ +import Queue from "@components/engine/queue/element.js"; +import InputOrchestrator from "@components/orchestrator/input/element.js"; +import OutputOrchestrator from "@components/orchestrator/output/element.js"; +import QueueTracksOrchestrator from "@components/orchestrator/queue-tracks/element.js"; +import SearchProcessor from "@components/processor/search/element.js"; +import SearchTracksOrchestrator from "@components/orchestrator/search-tracks/element.js"; + +import { effect } from "../signal.js"; +import QueueAudioOrchestrator from "@components/orchestrator/queue-audio/element.js"; + +export const GROUP = "constituents"; + +/** + * Default config for constituents. + */ +export function config() { + // Queue + const queue = new Queue(); + queue.setAttribute("group", GROUP); + + document.body.append(queue); + + // Input + const input = new InputOrchestrator(); + input.setAttribute("id", "input"); + + document.body.append(input); + + // Output + const output = new OutputOrchestrator(); + output.setAttribute("id", "output"); + + document.body.append(output); + + // Processors + const search = new SearchProcessor(); + search.setAttribute("group", GROUP); + + document.body.append(search); + + // Orchestrators + const oqt = new QueueTracksOrchestrator(); + oqt.setAttribute("group", GROUP); + oqt.setAttribute("input-selector", "#input"); + oqt.setAttribute("output-selector", "#output"); + oqt.setAttribute("queue-engine-selector", queue.localName); + + const ost = new SearchTracksOrchestrator(); + ost.setAttribute("group", GROUP); + ost.setAttribute("input-selector", "#input"); + ost.setAttribute("output-selector", "#output"); + ost.setAttribute("search-processor-selector", search.localName); + + document.body.append(oqt, ost); + + // Signals & effects + effect(() => { + const trigger = queue.now(); + const _other_trigger = queue.poolHash(); + + oqt.isLeader().then((isLeader) => { + if (!isLeader) return; + queue.fill({ amount: 10, shuffled: true }); + if (!trigger) queue.shift(); + }); + }); + + // Return elements + return { + GROUP, + + configurator: { + input, + output, + }, + engine: { + queue, + }, + orchestrator: { + input, + output, + queueTracks: oqt, + }, + processor: { + search, + }, + }; +} diff --git a/src/common/constituents/default/config.js b/src/common/constituents/default/config.js new file mode 100644 index 000000000..958794f28 --- /dev/null +++ b/src/common/constituents/default/config.js @@ -0,0 +1,3 @@ +import { config } from "../default.js"; + +config(); diff --git a/src/common/element.d.ts b/src/common/element.d.ts new file mode 100644 index 000000000..52f40d9e2 --- /dev/null +++ b/src/common/element.d.ts @@ -0,0 +1,22 @@ +import type { Tunnel } from "./worker.d.ts"; + +export type BroadcastingStatus = + | { leader: true; initialLeader: boolean } + | { leader: false }; + +export type HtmlTagFunction = ( + strings: string[] | ArrayLike, + ...values: unknown[] +) => string; + +export type RenderArg = { + html: HtmlTagFunction; + state: State; +}; + +export type WorkerOpts = { + forceNew?: boolean | { + self?: boolean; + dependencies?: Record; + }; +}; diff --git a/src/common/element.js b/src/common/element.js new file mode 100644 index 000000000..7ec054bd5 --- /dev/null +++ b/src/common/element.js @@ -0,0 +1,533 @@ +import QS from "query-string"; +import { decodeMessage, encodeMessage, RPCChannel } from "@kunkun/kkrpc"; +import { html, render } from "lit-html"; + +import { effect, signal } from "@common/signal.js"; +import { rpc, workerLink, workerProxy, workerTunnel } from "./worker.js"; +import { BrowserPostMessageIo } from "./worker/rpc.js"; + +/** + * @import {BroadcastingStatus, WorkerOpts} from "./element.d.ts" + * @import {ProxiedActions, Tunnel} from "./worker.d.ts"; + * @import {Signal} from "./signal.d.ts" + */ + +export { nothing } from "lit-html"; +export const DEFAULT_GROUP = "default"; + +/** + * Base for custom elements, provides some utility functionality + * around rendering and managing signals. + */ +export class DiffuseElement extends HTMLElement { + $connected = signal(false) + + #connected = Promise.withResolvers(); + #disposables = /** @type {Array<() => void>} */ ([]); + + /** */ + constructor() { + super(); + + this.worker = this.worker.bind(this); + this.workerLink = this.workerLink.bind(this); + } + + /** + * @param {string} _name + * @param {string} oldValue + * @param {string} newValue + */ + attributeChangedCallback(_name, oldValue, newValue) { + if (oldValue !== newValue) this.#render(); + } + + /** + * Effect helper that automatically is disposes + * when this element is removed from the DOM. + * + * @param {() => void} fn + */ + effect(fn) { + this.#disposables.push(effect(fn)); + } + + /** */ + forceRender() { + return this.#render(); + } + + /** */ + get group() { + return this.getAttribute("group") ?? DEFAULT_GROUP; + } + + /** */ + get label() { + return this.getAttribute("label") ?? this.id ?? this.localName; + } + + /** */ + get nameWithGroup() { + return `${this.constructor.prototype.constructor.NAME}/${this.group}`; + } + + /** */ + root() { + return (this.shadowRoot ?? this); + } + + /** */ + whenConnected() { + return this.#connected.promise; + } + + /** + * Avoid replacing the whole subtree, + * morph the existing DOM into the new given tree. + */ + #render() { + if (!("render" in this && typeof this.render === "function")) return; + + const tmp = this.render({ + html: html, + state: "state" in this ? this.state : undefined, + }); + + render(tmp, this.root()); + } + + // LIFECYCLE + + connectedCallback() { + this.$connected.value = true + this.#connected.resolve(null); + + if (!("render" in this && typeof this.render === "function")) return; + + this.effect(() => { + if (!("render" in this && typeof this.render === "function")) return; + this.#render(); + }); + } + + disconnectedCallback() { + this.$connected.value = false + this.#teardown(); + } + + #teardown() { + this.#disposables.forEach((fn) => fn()); + } + + // WORKERS + + /** @type {undefined | Worker | SharedWorker} */ + #worker; + + createWorker() { + const NAME = this.constructor.prototype.constructor.NAME; + const WORKER_URL = this.constructor.prototype.constructor.WORKER_URL; + + if (!NAME) throw new Error("Missing `NAME` static property"); + if (!WORKER_URL) throw new Error("Missing `WORKER_URL` static property"); + + // Query + const query = QS.stringify( + "workerQuery" in this && typeof this.workerQuery === "function" + ? this.workerQuery() + : {}, + ); + + // Setup worker + const name = this.nameWithGroup; + const url = import.meta.resolve("./" + WORKER_URL) + `?${query}`; + + let worker; + + if (this.hasAttribute("group")) { + worker = new SharedWorker(url, { name, type: "module" }); + } else { + worker = new Worker(url, { name, type: "module" }); + } + + return worker; + } + + /** */ + dependencies() { + return Object.fromEntries( + Array.from(this.children).flatMap((element) => { + if ("nameWithGroup" in element === false) { + return []; + } + + const d = /** @type {DiffuseElement} */ (element); + return [[d.localName, d]]; + }), + ); + } + + worker() { + this.#worker ??= this.createWorker(); + return this.#worker; + } + + workerLink() { + const worker = this.worker(); + return workerLink(worker); + } + + /** + * @template {Record any>} Actions + * @param {WorkerOpts} [opts] + * @returns {ProxiedActions} + */ + workerProxy(opts) { + return workerProxy( + () => this.workerTunnel(opts).port, + ); + } + + /** + * @param {WorkerOpts} [opts] + */ + workerTunnel({ forceNew } = {}) { + // Creates a MessagePort that is connected to the worker. + // All the dependencies are added automatically. + const worker = forceNew === true || + (typeof forceNew === "object" && forceNew.self === true) + ? this.createWorker() + : this.worker(); + const deps = this.dependencies(); + + let toWorker; + + if (Object.keys(deps).length) { + toWorker = + /** + * @param {any} msg + */ + async (msg) => { + /** @type {Array<[string, Tunnel]>} */ + const ports = Object.entries(deps).map( + /** @param {[string, DiffuseElement]} _ */ + ([k, v]) => { + const n = typeof forceNew === "object" + ? forceNew.dependencies?.[k] ?? false + : false; + return [k, v.workerTunnel({ forceNew: n })]; + }, + ); + + const decoded = await decodeMessage(msg); + const data = { + data: Array.isArray(decoded.args) ? decoded.args[0] : decoded.args, + ports: Object.fromEntries(ports.map(([k, v]) => { + return [k, v.port]; + })), + }; + + const encoded = encodeMessage( + { + ...decoded, + args: Array.isArray(decoded.args) + ? [data, ...decoded.args.slice(1)] + : decoded.args, + }, + {}, + true, + ports.map(([_k, v]) => v.port), + ); + + this.#disposables.push(() => { + ports.forEach(([_k, v]) => v.disconnect()); + }); + + return { + data: encoded, + transfer: ports.map(([_k, v]) => v.port), + }; + }; + } + + const tunnel = workerTunnel(worker, { toWorker }); + return tunnel; + } +} + +/** + * Broadcastable version of the base class. + * + * Share the state of an element across multiple tabs + * of the same origin and have one instance be the leader. + */ +export class BroadcastableDiffuseElement extends DiffuseElement { + broadcasted = false; + + /** @type {{ assumeLeadership?: boolean }} */ + #broadcastingOptions = {}; + + #broadcastingStatus; + broadcastingStatus; + + /** @type {PromiseWithResolvers} */ + #lock = Promise.withResolvers(); + + /** @type {PromiseWithResolvers} */ + #status = Promise.withResolvers(); + + constructor() { + super(); + + this.broadcast = this.broadcast.bind(this); + + /** @type {Signal>} */ + this.#broadcastingStatus = signal(this.#status.promise, { eager: true }); + this.broadcastingStatus = this.#broadcastingStatus.get; + } + + /** + * @template {Record any }>} ActionsWithStrategy + * @template {{ [K in keyof ActionsWithStrategy]: ActionsWithStrategy[K]["fn"] }} Actions + * @param {string} channelName + * @param {ActionsWithStrategy} actionsWithStrategy + * @param {{ assumeLeadership?: boolean }} [options] + */ + broadcast(channelName, actionsWithStrategy, options) { + if (this.broadcasted) return; + if (options) this.#broadcastingOptions = options; + + const channel = new BroadcastChannel(channelName); + const msg = new MessageChannel(); + + /** + * @typedef {{ [K in keyof ActionsWithStrategy]: ActionsWithStrategy[K]["fn"] }} A + */ + + this.broadcasted = true; + this.channelName = channelName; + + const _rpc = rpc( + msg.port2, + Object.fromEntries( + Object.entries(actionsWithStrategy).map(([k, v]) => { + return [k, v.fn.bind(this)]; + }), + ), + ); + + channel.addEventListener( + "message", + async (event) => { + if (event.data?.includes('"method":"leader:')) { + const status = await this.#status.promise; + if (status.leader) { + const json = event.data.replace('"method":"leader:', '"method":"'); + msg.port1.postMessage(json); + } + } else { + msg.port1.postMessage(event.data); + } + }, + ); + + msg.port1.addEventListener( + "message", + (event) => channel.postMessage(event.data), + ); + + msg.port1.start(); + msg.port2.start(); + + async function anyoneWaiting() { + const state = await navigator.locks.query(); + return !!state.pending?.length; + } + + const io = new BrowserPostMessageIo(() => msg.port2); + + /** @type {undefined | RPCChannel<{}, ProxiedActions>} */ + const proxyChannel = new RPCChannel(io, { enableTransfer: true }); + + /** @type {ProxiedActions} */ + const proxy = proxyChannel.getAPI(); + + /** @type {any} */ + const actions = {}; + + Object.entries(actionsWithStrategy).forEach( + ([action, { fn, strategy }]) => { + const ogFn = fn.bind(this); + let wrapFn = ogFn; + + switch (strategy) { + case "leaderOnly": + /** @param {Parameters} args */ + wrapFn = async (...args) => { + const status = await this.#status.promise; + return status.leader + ? ogFn(...args) + : proxyChannel.callMethod(`leader:${action}`, args); + }; + break; + + case "replicate": + /** @param {Parameters} args */ + wrapFn = async (...args) => { + anyoneWaiting().then((bool) => { + if (bool) proxy[action](...args); + }); + return ogFn(...args); + }; + break; + } + + actions[action] = wrapFn; + }, + ); + + return /** @type {ProxiedActions} */ (actions); + } + + async isLeader() { + if (this.broadcasted) { + const status = await this.broadcastingStatus(); + return status.leader; + } else { + return true; + } + } + + // LIFECYCLE + + /** + * @override + */ + connectedCallback() { + super.connectedCallback(); + + if (!this.broadcasted) return; + + // Grab a lock if it isn't acquired yet and if needed, + // and hold it until `this.lock.promise` resolves. + const assumeLeadership = this.#broadcastingOptions?.assumeLeadership; + + if (assumeLeadership === undefined || assumeLeadership === true) { + navigator.locks.request( + `${this.channelName}/lock`, + assumeLeadership === true ? { steal: true } : { ifAvailable: true }, + (lock) => { + this.#status.resolve( + lock ? { leader: true, initialLeader: true } : { leader: false }, + ); + if (lock) return this.#lock.promise; + }, + ); + } else { + this.#status.resolve( + { leader: false }, + ); + } + + // When the lock status is initially determined, log its status. + // Additionally, wait for lock if needed. + this.#status.promise.then((status) => { + if (status.leader) { + console.log(`๐Ÿง™ Elected leader for: ${this.channelName}`); + } else { + console.log(`๐Ÿ”ฎ Watching leader: ${this.channelName}`); + } + + // Wait for leadership + if (status.leader === false) { + navigator.locks.request( + `${this.channelName}/lock`, + () => { + this.#status = Promise.withResolvers(); + this.#status.resolve({ leader: true, initialLeader: false }); + + this.#broadcastingStatus.value = this.#status.promise; + + return this.#lock.promise; + }, + ); + } + }); + } + + /** + * @override + */ + disconnectedCallback() { + super.disconnectedCallback(); + this.#lock.resolve(); + } +} + +/** + * Component DOM selector. + * + * Basically `document.querySelector` but returns the element + * with the correct type based on the element module given. + * + * ``` + * import * as QueryEngine from "@components/engine/query/element.js" + * + * const instance = component(QueryEngine) + * ``` + * + * @template {abstract new (...args: any[]) => any} C + * @param {{ CLASS: C; NAME: string }} elementModule + * @param {string} [id] Optional id to select + */ +export function component(elementModule, id) { + const el = document.querySelector( + id ? `${elementModule.NAME}#${id}` : elementModule.NAME, + ); + if (!el) { + throw new Error(`Element for selector '${elementModule.NAME}' not found.`); + } + return /** @type {InstanceType} */ (el); +} + +/** + * @template {HTMLElement} T + * @param {DiffuseElement} parent + * @param {string} attribute + * @returns {T} + */ +export function query(parent, attribute) { + const selector = parent.getAttribute(attribute); + + if (!selector) { + throw new Error(`Missing required '${attribute}' attribute`); + } + + /** @type {T | null} */ + const element = document.querySelector(selector); + if (!element) throw new Error(`Missing required '${selector}' element`); + + return element; +} + +/** + * @param {Record} workers + */ +export function terminateWorkers(workers) { + Object.values(workers).forEach((worker) => { + if (worker instanceof Worker) worker.terminate(); + }); +} + +/** + * @template {Record} T + * @param {T} elements + */ +export async function whenElementsDefined(elements) { + await Promise.all( + Object.values(elements).map((element) => + customElements.whenDefined(element.localName) + ), + ); +} diff --git a/src/common/index.js b/src/common/index.js new file mode 100644 index 000000000..d97cf302f --- /dev/null +++ b/src/common/index.js @@ -0,0 +1,98 @@ +import { base64url } from "iso-base/rfc4648"; +import { xxh32r } from "xxh32/dist/raw.js"; + +/** + * @import {Track} from "@definitions/types.d.ts" + */ + +/** + * @template T + * @param {Array} array + * @returns Array + */ +export function arrayShuffle(array) { + if (array.length === 0) { + return []; + } + + array = [...array]; + + for (let index = array.length - 1; index > 0; index--) { + const randArr = crypto.getRandomValues(new Uint32Array(1)); + const randVal = randArr[0] / 2 ** 32; + const newIndex = Math.floor(randVal * (index + 1)); + [array[index], array[newIndex]] = [array[newIndex], array[index]]; + } + + return array; +} + +/** + * @param {string | undefined | null} value + */ +export function boolAttr(value) { + return value === ""; +} + +/** + * @param {any} object + */ +export function hash(object) { + return xxh32r(jsonEncode(object)).toString(); +} + +/** + * @param {Track[]} tracks + * @param {Record} initial + * @returns {Record} + */ +export function groupTracksPerScheme( + tracks, + initial = {}, +) { + /** @type {Record} */ + const acc = initial; + + tracks.forEach((track) => { + const scheme = track.uri.substring(0, track.uri.indexOf(":")); + acc[scheme] ??= []; + acc[scheme].push(track); + }); + + return acc; +} + +/** + * @param {unknown} test + */ +export function isPrimitive(test) { + return test !== Object(test); +} + +/** + * @template T + * @param {any} a + * @returns {T} + */ +export function jsonDecode(a) { + return JSON.parse(new TextDecoder().decode(a)); +} + +/** + * @template T + * @param {T} a + * @returns Uint8Array + */ +export function jsonEncode(a) { + return new TextEncoder().encode(JSON.stringify(a)); +} + +/** + * @param {Track} track + * @returns {Promise} + */ +export async function trackArtworkCacheId(track) { + return await crypto.subtle + .digest("SHA-256", new TextEncoder().encode(track.uri)) + .then((a) => base64url.encode(new Uint8Array(a))); +} diff --git a/src/common/signal.d.ts b/src/common/signal.d.ts new file mode 100644 index 000000000..1929f7e76 --- /dev/null +++ b/src/common/signal.d.ts @@ -0,0 +1,10 @@ +export type SignalReader = () => T; +export type SignalWriter = (t: T) => void; + +export type Signal = { + get: SignalReader; + set: SignalWriter; + + get value(): T; + set value(t: T); +}; diff --git a/src/common/signal.js b/src/common/signal.js new file mode 100644 index 000000000..46704a272 --- /dev/null +++ b/src/common/signal.js @@ -0,0 +1,84 @@ +import deepDiff from "@fry69/deep-diff"; +import { + endBatch, + setActiveSub, + signal as alienSignal, + startBatch, +} from "alien-signals"; + +export * from "alien-signals"; + +/** + * @import {Signal, SignalReader, SignalWriter} from "./signal.d.ts" + */ + +/** + * @param {function(): void} fn + */ +export const batch = (fn) => { + startBatch(); + try { + fn(); + } finally { + endBatch(); + } +}; + +/** + * @template T + * @param {T} initialValue + * @param {{ eager?: boolean }} [options] + * @returns {Signal} + */ +export function signal(initialValue, options) { + const s = alienSignal(initialValue); + if (options?.eager === true) { + return _signal({ + get: () => s(), + set: (v) => s(v), + }); + } + + return _signal({ + get: () => s(), + set: (b) => { + const a = untracked(() => s()); + const diff = deepDiff(a, b); + if (diff) s(b); + }, + }); +} + +/** + * @template T + * @param {function(): T} fn + * @returns {T} + */ +export const untracked = (fn) => { + const sub = setActiveSub(void 0); + try { + return fn(); + } finally { + setActiveSub(sub); + } +}; + +/** + * @template T + * @param {{ get: SignalReader; set: SignalWriter }} _ + * @returns {Signal} + */ +function _signal({ get, set }) { + return { + get, + set, + + get value() { + return get(); + }, + + set value(v) { + set(v); + }, + }; +} diff --git a/src/common/worker.d.ts b/src/common/worker.d.ts new file mode 100644 index 000000000..b44f8fd71 --- /dev/null +++ b/src/common/worker.d.ts @@ -0,0 +1,60 @@ +export type Announcement = MRpcBaseMsg & { type: "announcement"; args: T }; +export type IncompleteArray = ["Missing required items", T]; + +export type ActionsWithTunnel< + Actions extends Record any>, +> = { + [A in keyof Actions]: WithTunnel; +}; + +export type Dependencies = { + [K in T]: Worker | SharedWorker; +}; + +/** + * Comes from the `@mys/m-rpc` library, + * but it is not exported. Used to identify + * messages sent via `postMessage`. + */ +export type MRpcBaseMsg = { ns: string; name: string; key: number }; + +/** */ +export type ProxiedActions< + Actions extends Record any>, +> = { + [A in keyof Actions]: ProxiedAction; +}; + +export type ProxiedAction< + Action extends (...args: any[]) => any, + PromisedReturn = + (ReturnType extends Promise ? ReturnType + : Promise>), +> = (...args: Parameters) => PromisedReturn; + +/** */ +export interface MessengerRealm { + postMessage: MessagePort["postMessage"]; + addEventListener: MessagePort["addEventListener"]; + removeEventListener: MessagePort["removeEventListener"]; +} + +/** */ +export type Tunnel = { + disconnect: () => void; + port: MessagePort; +}; + +/** */ +export type WithTunnel< + Fn extends (...args: any[]) => any, + PromisedReturn = (ReturnType extends Promise ? ReturnType + : Promise>), +> = ( + _: { data: Parameters[0]; ports: Record }, + ...args: Rest> +) => PromisedReturn; + +// ๐Ÿ›‘ + +type Rest = T extends [any, ...(infer R)[]] ? R : never; diff --git a/src/common/worker.js b/src/common/worker.js new file mode 100644 index 000000000..cee47e7df --- /dev/null +++ b/src/common/worker.js @@ -0,0 +1,240 @@ +import { RPCChannel } from "@kunkun/kkrpc"; +import { getTransferables } from "@okikio/transferables"; +import { debounceMicrotask } from "@vicary/debounce-microtask"; +import { xxh32 } from "xxh32"; + +import { BrowserPostMessageIo } from "./worker/rpc.js"; + +export { getTransferables } from "@okikio/transferables"; +export { transfer } from "@kunkun/kkrpc"; + +/** + * @import {Announcement, Dependencies, MessengerRealm, ProxiedActions, Tunnel} from "./worker.d.ts" + */ + +//////////////////////////////////////////// +// MISC +//////////////////////////////////////////// + +/** + * Manage incoming connections for a shared worker. + * If a regular worker is used instead, it'll just execute the callback immediately. + * + * @template {MessagePort | Worker | MessengerRealm} T + * @param {(context: MessagePort | T, firstConnection: boolean, connectionId: string) => void} callback + * @param {T} [context] Uses `globalThis` by default. + */ +export function ostiary( + callback, + context = /** @type {T} */ (/** @type {unknown} */ (globalThis)), +) { + if (/** @type {any} */ (context).onmessage === null) { + return callback(context, true, crypto.randomUUID()); + } + + const c = /** @type {any} */ (context); + c.__id ??= crypto.randomUUID(); + + context.addEventListener( + "connect", + /** + * @param {any} event + */ + (event) => { + /** @type {MessagePort} */ + const port = event.ports[0]; + port.start(); + + // Initiate setup + callback(port, !(c.__initiated ?? false), c.__id); + c.__initiated = true; + }, + ); +} + +/** + * @param {Worker | SharedWorker} worker + */ +export function workerLink(worker) { + if (worker instanceof SharedWorker) { + worker.port.start(); + return worker.port; + } else { + return worker; + } +} + +/** + * @template {Record any>} Actions + * @param {() => MessagePort | Worker} workerLinkCreator + * @returns {ProxiedActions} + */ +export function workerProxy(workerLinkCreator) { + /** @type {ProxiedActions | undefined} */ + let int_api; + + /** @returns {ProxiedActions} */ + function ensureAPI() { + if (!int_api) { + const io = new BrowserPostMessageIo(workerLinkCreator); + + /** @type {undefined | RPCChannel<{}, ProxiedActions>} */ + const rpc = new RPCChannel(io, { enableTransfer: true }); + + int_api = rpc.getAPI(); + } + + return int_api; + } + + // Create proxy that creates RPC API when needed + const proxy = new Proxy(() => {}, { + get: (_target, prop) => { + /** @param {Parameters} args */ + return (...args) => { + const api = ensureAPI(); + return api[prop.toString()](...args); + }; + }, + }); + + return /** @type {ProxiedActions} */ (/** @type {any} */ (proxy)); +} + +/** + * @param {MessagePort | Worker | SharedWorker} workerOrLink + * @param {{ fromWorker?: (message: any) => Promise<{ data: any, transfer?: Transferable[] }>; toWorker?: (message: any) => Promise<{ data: any, transfer?: Transferable[] }> }} [hooks] + * @returns {Tunnel} + */ +export function workerTunnel(workerOrLink, hooks = {}) { + const link = workerOrLink instanceof SharedWorker + ? workerLink(workerOrLink) + : workerOrLink; + const channel = new MessageChannel(); + + channel.port1.addEventListener("message", async (event) => { + // Send to worker + const { data, transfer } = await hooks?.toWorker?.(event.data) ?? + { data: event.data }; + link.postMessage(data, { transfer }); + }); + + /** + * @param {Event} event + */ + const workerListener = async (event) => { + // Receive from worker + const msgEvent = /** @type {MessageEvent} */ (event); + const { data, transfer } = await hooks?.fromWorker?.(msgEvent.data) ?? + { data: msgEvent.data }; + channel.port1.postMessage(data, { transfer }); + }; + + link.addEventListener("message", workerListener); + + channel.port1.start(); + channel.port2.start(); + + return { + disconnect: () => { + link.removeEventListener("message", workerListener); + channel.port1.close(); + channel.port2.close(); + }, + port: channel.port2, + }; +} + +//////////////////////////////////////////// +// RAW +//////////////////////////////////////////// + +/** + * @template T + * @param {string} name + * @param {T} args + * @param {MessagePort | Worker | MessengerRealm} [context] Uses `globalThis` by default. + */ +export function announce( + name, + args, + context, +) { + const a = announcement(name, args); + const transferables = getTransferables(a); + (context ?? globalThis).postMessage(a, { transfer: transferables }); +} + +/** + * @template T + * @param {string} name + * @param {(args: T) => void} fn + * @param {MessagePort | Worker | MessengerRealm} [context] + */ +export function listen( + name, + fn, + context = /** @type {MessengerRealm} */ (globalThis), +) { + const c = /** @type {any} */ (context); + + if (!c.__incoming) { + context.addEventListener("message", incomingAnnouncementsHandler(context)); + c.__incoming = {}; + } + + c.__incoming[name] = debounceMicrotask(fn, { updateArguments: true }); +} + +//////////////////////////////////////////// +// RPC +//////////////////////////////////////////// + +/** + * @template {Record any>} Actions + * @param {MessagePort | Worker | MessengerRealm} context + * @param {Actions} actions + */ +export function rpc(context, actions) { + const io = new BrowserPostMessageIo(() => context); + + /** @type {undefined | RPCChannel} */ + return new RPCChannel(io, { enableTransfer: true, expose: actions }); +} + +//////////////////////////////////////////// +// โ›”๏ธ +//////////////////////////////////////////// + +const ANNOUNCEMENT = "announcement"; + +/** + * @template T + * @param {string} name + * @param {T} args + * @returns {Announcement} + */ +function announcement(name, args) { + return { + ns: ANNOUNCEMENT, + name, + key: xxh32(crypto.randomUUID()), + + type: ANNOUNCEMENT, + args, + }; +} + +/** + * @param {MessagePort | Worker | MessengerRealm} context + */ +function incomingAnnouncementsHandler(context) { + /** @param {any} event */ + return (event) => { + const { ns, type } = event.data; + if (ns !== ANNOUNCEMENT || type !== ANNOUNCEMENT) return; + const announcement = /** @type {Announcement} */ (event.data); + const c = /** @type {any} */ (context); + c.__incoming[announcement.name]?.(announcement.args); + }; +} diff --git a/src/common/worker/rpc.js b/src/common/worker/rpc.js new file mode 100644 index 000000000..19f6f0936 --- /dev/null +++ b/src/common/worker/rpc.js @@ -0,0 +1,142 @@ +/** + * @import { IoCapabilities, IoInterface, IoMessage, WireEnvelope } from "@kunkun/kkrpc" + * + * @import { MessengerRealm } from "../worker.d.ts" + */ + +const DESTROY_SIGNAL = "__DESTROY__"; + +/** + * @implements {IoInterface} + */ +export class BrowserPostMessageIo { + name = "browser-postmessage-io"; + + /** @type {Array} */ + #messageQueue = []; + + /** @type {((value: string | IoMessage | null) => void) | null} */ + #resolveRead = null; + + /** */ + #realm; + + /** @type {IoCapabilities} */ + capabilities = { + structuredClone: true, + transfer: true, + }; + + /** + * @param {() => MessengerRealm} realmCreator + */ + constructor(realmCreator) { + /** @type {undefined | MessengerRealm} */ + const realm = realmCreator(); + realm.addEventListener("message", this.#handleMessage.bind(this)); + + this.#realm = () => { + return realm; + }; + } + + /** + * @param {MessageEvent} event + */ + #handleMessage(event) { + const raw = event.data; + const message = this.#normalizeIncoming(raw); + + // Handle destroy signal + if (message === DESTROY_SIGNAL) { + this.destroy(); + return; + } + + if (this.#resolveRead) { + this.#resolveRead(message); + this.#resolveRead = null; + } else { + this.#messageQueue.push(message); + } + } + + /** + * @param {any} message + * @returns {string | IoMessage} + */ + #normalizeIncoming(message) { + if (typeof message === "string") { + return message; + } + + if (message && typeof message === "object" && message.version === 2) { + const envelope = /** @type {WireEnvelope} */ (message); + return { + data: envelope, + transfers: (/** @type {unknown[] | undefined} */ (envelope + .__transferredValues)) ?? [], + }; + } + + return /** @type {string} */ (message); + } + + /** @returns {Promise} */ + read() { + // If there are queued messages, return the first one + if (this.#messageQueue.length > 0) { + return Promise.resolve(this.#messageQueue.shift() ?? null); + } + + // Otherwise, wait for the next message + return new Promise((resolve) => { + this.#resolveRead = resolve; + }); + } + + /** + * @param {string | IoMessage} message + */ + write(message) { + if (typeof message === "string") { + this.#realm().postMessage(message); + return Promise.resolve(); + } + + if (message.transfers && message.transfers.length > 0) { + const msg = { ...message }; + + if (typeof msg.data === "object" && msg.data.payload.args) { + if (msg.data.payload.args[0] instanceof HTMLElement) { + msg.data.payload.args[0] = undefined; + } + } + + this.#realm().postMessage( + message.data, + /** @type {Transferable[]} */ (message.transfers), + ); + } else { + this.#realm().postMessage(message.data); + } + + return Promise.resolve(); + } + + destroy() { + const realm = this.#realm(); + + realm.postMessage(DESTROY_SIGNAL); + + if ( + "terminate" in realm && typeof realm.terminate === "function" + ) { + realm.terminate(); + } + } + + signalDestroy() { + this.#realm().postMessage(DESTROY_SIGNAL); + } +} diff --git a/src/components/configurator/input/element.js b/src/components/configurator/input/element.js new file mode 100644 index 000000000..e9f2e3e00 --- /dev/null +++ b/src/components/configurator/input/element.js @@ -0,0 +1,64 @@ +import { DiffuseElement, whenElementsDefined } from "@common/element.js"; + +/** + * @import {ProxiedActions, Tunnel} from "@common/worker.d.ts" + * @import {InputActions, InputElement} from "@components/input/types.d.ts" + */ + +/** + * @typedef {{ element: InputElement, tunnel: Tunnel, worker: Worker | SharedWorker }} Input + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {ProxiedActions} + */ +class InputConfigurator extends DiffuseElement { + static NAME = "diffuse/configurator/input"; + static WORKER_URL = "components/configurator/input/worker.js"; + + constructor() { + super(); + + /** @type {ProxiedActions} */ + const proxy = this.workerProxy(); + + this.consult = proxy.consult; + this.contextualize = proxy.contextualize; + this.groupConsult = proxy.groupConsult; + this.list = proxy.list; + this.resolve = proxy.resolve; + } + + // WORKERS + + /** + * @override + */ + dependencies() { + return this.inputs(); + } + + inputs() { + return Object.fromEntries( + Array.from(this.children).map((element) => { + const input = /** @type {InputElement} */ (element); + return [input.SCHEME, input]; + }), + ); + } +} + +export default InputConfigurator; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = InputConfigurator; +export const NAME = "dc-input"; + +customElements.define(NAME, CLASS); diff --git a/src/components/configurator/input/worker.js b/src/components/configurator/input/worker.js new file mode 100644 index 000000000..9bfadbbd2 --- /dev/null +++ b/src/components/configurator/input/worker.js @@ -0,0 +1,166 @@ +import * as URI from "uri-js"; + +import { groupTracksPerScheme } from "@common/index.js"; +import { ostiary, rpc, workerProxy } from "@common/worker.js"; + +/** + * @import {Track} from "@definitions/types.d.ts"; + * @import {GroupConsult, InputActions} from "@components/input/types.d.ts" + * @import {ActionsWithTunnel, ProxiedActions} from "@common/worker.d.ts" + */ + +//////////////////////////////////////////// +// INPUT ACTIONS +//////////////////////////////////////////// + +/** + * @type {ActionsWithTunnel['consult']} + */ +export async function consult({ data, ports }) { + const fileUriOrScheme = data; + const scheme = fileUriOrScheme.includes(":") + ? URI.parse(fileUriOrScheme).scheme || fileUriOrScheme + : fileUriOrScheme; + + const input = grabInput(scheme, ports); + + if (!input) { + return { supported: false, reason: "Unsupported scheme" }; + } + + return await input.consult(fileUriOrScheme); +} + +/** + * @type {ActionsWithTunnel['contextualize']} + */ +export async function contextualize({ data, ports }) { + const tracks = data; + const groups = groupTracks(tracks, ports); + const promises = Object.entries(groups).map( + async ([scheme, tracksGroup]) => { + const input = grabInput(scheme, ports); + if (!input || tracksGroup.length === 0) return; + return await input.contextualize(tracksGroup); + }, + ); + + await Promise.all(promises); +} + +/** + * @type {ActionsWithTunnel['groupConsult']} + */ +export async function groupConsult({ data, ports }) { + const tracks = data; + const groups = groupTracksPerScheme(tracks); + + /** @type {GroupConsult[]} */ + const consultations = await Promise.all( + Object.keys(groups).map(async (scheme) => { + const input = grabInput(scheme, ports); + + if (!input) { + return { + [scheme]: { + available: false, + reason: "Unsupported scheme", + scheme, + tracks: groups[scheme] ?? [], + }, + }; + } + + return await input.groupConsult(groups[scheme] ?? {}); + }), + ); + + return consultations.reduce((acc, c) => { + return { ...acc, ...c }; + }, {}); +} + +/** + * @type {ActionsWithTunnel['list']} + */ +export async function list({ data, ports }) { + const groups = await groupConsult({ data, ports }); + + const promises = Object.values(groups).map( + async ({ available, scheme, tracks }) => { + if (!available) return tracks; + + const input = grabInput(scheme, ports); + if (!input) return tracks; + return await input.list(tracks); + }, + ); + + const nested = await Promise.all(promises); + const tracks = nested.flat(1); + + return tracks; +} + +/** + * @type {ActionsWithTunnel['resolve']} + */ +export async function resolve({ data, ports }) { + const uri = data.uri; + const scheme = uri.split(":", 1)[0]; + const input = grabInput(scheme, ports); + if (!input) return undefined; + + const result = await input.resolve(data); + return result; +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + rpc(context, { + consult, + contextualize, + groupConsult, + list, + resolve, + }); +}); + +//////////////////////////////////////////// +// ๐Ÿ› ๏ธ +//////////////////////////////////////////// + +/** + * @param {string} scheme + * @param {Record} ports + * @returns {ProxiedActions | null} + */ +function grabInput(scheme, ports) { + const port = ports[scheme]; + if (!port) return null; + + return workerProxy(() => { + port.start(); + return port; + }); +} + +/** + * @param {Track[]} tracks + * @param {Record} ports + */ +function groupTracks(tracks, ports) { + const grouped = groupTracksPerScheme( + tracks, + Object.fromEntries( + Object.keys(ports).map((k) => { + return [k, []]; + }), + ), + ); + + return grouped; +} diff --git a/src/components/configurator/output/element.js b/src/components/configurator/output/element.js new file mode 100644 index 000000000..6e81ba6ec --- /dev/null +++ b/src/components/configurator/output/element.js @@ -0,0 +1,161 @@ +import { DiffuseElement } from "@common/element.js"; +import { computed, signal } from "@common/signal.js"; + +/** + * @import {Track} from "@definitions/types.d.ts" + * @import {OutputManager, OutputElement} from "@components/output/types.d.ts" + */ + +/** + * @typedef {OutputElement} Output + */ + +const STORAGE_PREFIX = "diffuse/configurator/output"; + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {OutputManager} + */ +class OutputConfigurator extends DiffuseElement { + static NAME = "diffuse/configurator/output"; + + constructor() { + super(); + + /** @type {OutputManager} */ + const manager = { + tracks: { + collection: computed(() => { + const out = this.#selectedOutput.value; + if (out) return out.tracks.collection(); + return this.#memory.tracks.value; + }), + reload: () => { + const out = this.#selectedOutput.value; + if (out) return out.tracks.reload(); + return Promise.resolve(); + }, + save: async (newTracks) => { + const out = this.#selectedOutput.value; + if (out) return await out.tracks.save(newTracks); + this.#memory.tracks.value = newTracks; + }, + state: computed(() => { + const out = this.#selectedOutput.value; + if (out) return out.tracks.state(); + return out === undefined ? "loading" : "loaded"; + }), + }, + }; + + // Assign manager properties to class + this.tracks = manager.tracks; + } + + // SIGNALS + + #memory = { + tracks: signal(/** @type {Track[]} */ ([])), + }; + + #selectedOutput = signal( + /** @type {Output | null | undefined} */ (undefined), + ); + + // LIFECYCLE + + /** + * @override + */ + async connectedCallback() { + super.connectedCallback(); + this.#selectedOutput.value = await this.#findSelectedOutput(); + } + + // MISC + + async #findSelectedOutput() { + const id = localStorage.getItem(`${STORAGE_PREFIX}/selected/id`) ?? + this.getAttribute("default"); + const el = id ? this.root().querySelector(`#${id}`) : null; + + if (!el) return null; + + await customElements.whenDefined(el.localName); + + if ( + "nameWithGroup" in el === false || + "tracks" in el === false + ) { + return null; + } + + return /** @type {Output} */ (/** @type {unknown} */ (el)); + } + + /** + * @override + */ + dependencies() { + return Object.fromEntries( + Array.from(this.children).flatMap((element) => { + if (element.hasAttribute("id") === false) { + console.warn( + "Missing `id` for output configurator child element with `localName` '" + + element.localName + "'", + ); + return []; + } + + const d = /** @type {DiffuseElement} */ (element); + return [[d.id, d]]; + }), + ); + } + + // ADDITIONAL ACTIONS + + async deselect() { + localStorage.removeItem(`${STORAGE_PREFIX}/selected/id`); + this.#selectedOutput.value = await this.#findSelectedOutput(); + } + + async options() { + const deps = this.dependencies(); + const entries = Object.entries(deps); + + await Promise.all( + entries.map(([_k, v]) => customElements.whenDefined(v.localName)), + ); + + return entries.map(([k, v]) => { + return { + id: k, + label: v.label, + element: v, + }; + }); + } + + /** + * @param {string} id + */ + async select(id) { + localStorage.setItem(`${STORAGE_PREFIX}/selected/id`, id); + this.#selectedOutput.value = await this.#findSelectedOutput(); + } +} + +export default OutputConfigurator; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = OutputConfigurator; +export const NAME = "dc-output"; + +customElements.define(NAME, CLASS); diff --git a/src/components/engine/audio/element.js b/src/components/engine/audio/element.js new file mode 100644 index 000000000..7113b2755 --- /dev/null +++ b/src/components/engine/audio/element.js @@ -0,0 +1,655 @@ +import { keyed } from "lit-html/directives/keyed.js"; + +import { BroadcastableDiffuseElement, nothing } from "@common/element.js"; +import { computed, signal, untracked } from "@common/signal.js"; + +/** + * @import {Actions, Audio, AudioState, AudioStateReadOnly, LoadingState} from "./types.d.ts" + * @import {RenderArg} from "@common/element.d.ts" + * @import {SignalReader} from "@common/signal.d.ts" + */ + +//////////////////////////////////////////// +// CONSTANTS +//////////////////////////////////////////// +const SILENT_MP3 = + "data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjM2LjEwMAAAAAAAAAAAAAAA//OEAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAEAAABIADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV6urq6urq6urq6urq6urq6urq6urq6urq6v////////////////////////////////8AAAAATGF2YzU2LjQxAAAAAAAAAAAAAAAAJAAAAAAAAAAAASDs90hvAAAAAAAAAAAAAAAAAAAA//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAMAAAGkAAAAAAAAA0gAAAAARTMu//MUZAYAAAGkAAAAAAAAA0gAAAAAOTku//MUZAkAAAGkAAAAAAAAA0gAAAAANVVV"; + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {Actions} + */ +class AudioEngine extends BroadcastableDiffuseElement { + static NAME = "diffuse/engine/audio"; + + constructor() { + super(); + + this.isPlaying = this.isPlaying.bind(this); + this.state = this.state.bind(this); + } + + // SIGNALS + + #items = signal(/** @type {Audio[]} */ ([])); + #volume = signal(0.5); + + // STATE + + items = this.#items.get; + volume = this.#volume.get; + + // LIFECYCLE + + /** + * @override + */ + connectedCallback() { + // Setup broadcasting if part of group + if (this.hasAttribute("group")) { + const actions = this.broadcast( + this.nameWithGroup, + { + adjustVolume: { strategy: "replicate", fn: this.adjustVolume }, + pause: { strategy: "leaderOnly", fn: this.pause }, + play: { strategy: "leaderOnly", fn: this.play }, + seek: { strategy: "leaderOnly", fn: this.seek }, + supply: { strategy: "replicate", fn: this.supply }, + }, + ); + + if (actions) { + this.adjustVolume = actions.adjustVolume; + this.pause = actions.pause; + this.play = actions.play; + this.seek = actions.seek; + this.supply = actions.supply; + } + } + + // Super + super.connectedCallback(); + + // Get volume from previous session if possible + const VOLUME_KEY = + `${this.constructor.prototype.constructor.NAME}/${this.group}/volume`; + const volume = localStorage.getItem(VOLUME_KEY); + + if (volume != undefined) { + this.#volume.set(parseFloat(volume)); + } + + // Manage playback across tabs if needed + if (this.broadcasted) { + this.effect(async () => { + const status = await this.broadcastingStatus(); + untracked(() => { + if (!(status.leader && status.initialLeader === false)) return; + + console.log("๐Ÿง™ Leadership acquired"); + this.items().forEach((item) => { + const el = this.#itemElement(item.id); + if (!el) return; + + el.removeAttribute("initial-progress"); + + if (!el.audio) return; + + const progress = el.$state.progress.value; + const canPlay = () => { + this.seek({ + audioId: item.id, + percentage: progress, + }); + + if (el.$state.isPlaying.value) this.play({ audioId: item.id }); + }; + + el.audio.addEventListener("canplay", canPlay, { once: true }); + + if (el.audio.readyState === 0) el.audio.load(); + else canPlay(); + }); + }); + }); + } + + // Monitor volume signal + this.effect(() => { + Array.from(this.querySelectorAll("de-audio-item")).forEach( + (node) => { + const item = /** @type {AudioEngineItem} */ (node); + if (item.hasAttribute("preload")) return; + const audio = item.querySelector("audio"); + if (audio) audio.volume = this.#volume.value; + }, + ); + + localStorage.setItem(VOLUME_KEY, this.#volume.value.toString()); + }); + } + + // ACTIONS + + /** + * @type {Actions["adjustVolume"]} + */ + adjustVolume(args) { + if (args.audioId) { + this.#withAudioNode(args.audioId, (audio) => { + audio.volume = args.volume; + }); + } else { + this.#volume.value = args.volume; + } + } + + /** + * @type {Actions["pause"]} + */ + pause({ audioId }) { + this.#withAudioNode(audioId, (audio) => audio.pause()); + } + + /** + * @type {Actions["play"]} + */ + play({ audioId, volume }) { + this.#withAudioNode(audioId, (audio, item) => { + audio.volume = volume ?? this.volume(); + audio.muted = false; + + // TODO: Might need this for `data-initial-progress` + // Does seem to cause trouble when broadcasting + // (open multiple sessions and play the next audio) + // if (audio.readyState === 0) audio.load(); + if (!audio.isConnected) return; + + const promise = audio.play() || Promise.resolve(); + item.$state.isPlaying.set(true); + + promise.catch((e) => { + if (!audio.isConnected) { + return; /* The node was removed from the DOM, we can ignore this error */ + } + const err = + "Couldn't play audio automatically. Please resume playback manually."; + console.error(err, e); + item.$state.isPlaying.set(false); + }); + }); + } + + /** + * @type {Actions["reload"]} + */ + reload(args) { + this.#withAudioNode(args.audioId, (audio, item) => { + if (audio.readyState === 0 || audio.error?.code === 2) { + audio.load(); + + if (args.progress !== undefined) { + item.setAttribute( + "initial-progress", + JSON.stringify(args.progress), + ); + } + + if (args.play) { + this.play({ audioId: args.audioId, volume: audio.volume }); + } + } + }); + } + + /** + * @type {Actions["seek"]} + */ + seek({ audioId, percentage }) { + this.#withAudioNode(audioId, (audio) => { + if (!isNaN(audio.duration)) { + audio.currentTime = audio.duration * percentage; + } + }); + } + + /** + * @type {Actions["supply"]} + */ + supply(args) { + const existingSet = new Set(this.#items.value.map((a) => a.id)); + const newSet = new Set(args.audio.map((a) => a.id)); + + if (newSet.difference(existingSet).size !== 0) { + this.#items.value = args.audio; + } + + if (args.play) this.play(args.play); + } + + // RENDER + + /** + * @param {RenderArg} _ + */ + render({ html }) { + const ids = this.items().map((i) => i.id); + + this.querySelectorAll("de-audio-item").forEach((element) => { + if (ids.includes(element.id)) return; + + const source = element.querySelector("source"); + if (source) source.src = SILENT_MP3; + }); + + const group = this.group; + const nodes = this.items().map((audio) => { + const ip = audio.progress === undefined + ? "0" + : JSON.stringify(audio.progress); + + return keyed( + audio.id, + html` + + + + `, + ); + }); + + return html` +
+ ${nodes} +
+ `; + } + + // ๐Ÿ› ๏ธ + + /** + * Convenience signal to track if something is, or was, playing. + */ + _isPlaying() { + return computed(() => { + const item = this.items()?.[0]; + if (!item) return false; + + const state = this.state(item.id); + if (!state) return false; + + return state.isPlaying() || state.hasEnded() || state.progress() === 1; + }); + } + + /** + * Get the state of a single audio item. + * + * @param {string} audioId + * @returns {SignalReader} + */ + _state(audioId) { + return computed(() => { + const _trigger = this.#items.value; + + const s = this.#itemElement(audioId)?.state; + return s ? { ...s } : undefined; + }); + } + + /** + * Convenience signal to track if something is, or was, playing. + */ + isPlaying() { + return this._isPlaying()(); + } + + /** + * Get the state of a single audio item. + * + * @param {string} audioId + * @returns {AudioStateReadOnly | undefined} + */ + state(audioId) { + return this._state(audioId)(); + } + + /** + * @param {string} audioId + */ + #itemElement(audioId) { + const node = this.querySelector( + `de-audio-item[id="${audioId}"]:not([preload])`, + ); + + if (node) { + const item = /** @type {AudioEngineItem} */ (node); + return item; + } + } + + /** + * @param {string} audioId + * @param {(audio: HTMLAudioElement, item: AudioEngineItem) => void} fn + */ + #withAudioNode(audioId, fn) { + const item = this.#itemElement(audioId); + if (item) fn(item.audio, item); + } +} + +export default AudioEngine; + +//////////////////////////////////////////// +// ITEM ELEMENT +//////////////////////////////////////////// + +class AudioEngineItem extends BroadcastableDiffuseElement { + static NAME = "diffuse/engine/audio/item"; + + constructor() { + super(); + + const ip = this.getAttribute("initial-progress"); + + /** + * @type {AudioState} + */ + this.$state = { + duration: signal(0), + hasEnded: signal(false), + isPlaying: signal(false), + isPreload: signal(this.hasAttribute("preload")), + loadingState: signal(/** @type {LoadingState} */ ("loading")), + progress: signal(ip ? parseFloat(ip) : 0), + }; + } + + // LIFECYCLE + + /** + * @override + */ + async connectedCallback() { + const audio = this.audio; + + audio.addEventListener("canplay", this.canplayEvent); + audio.addEventListener("durationchange", this.durationchangeEvent); + audio.addEventListener("ended", this.endedEvent); + audio.addEventListener("error", this.errorEvent); + audio.addEventListener("pause", this.pauseEvent); + audio.addEventListener("play", this.playEvent); + audio.addEventListener("suspend", this.suspendEvent); + audio.addEventListener("timeupdate", this.timeupdateEvent); + audio.addEventListener("waiting", this.waitingEvent); + + // Setup broadcasting if part of group + if (this.hasAttribute("group")) { + const actions = this.broadcast( + this.nameWithGroup, + { + getDuration: { strategy: "leaderOnly", fn: this.$state.duration.get }, + getHasEnded: { strategy: "leaderOnly", fn: this.$state.hasEnded.get }, + getIsPlaying: { + strategy: "leaderOnly", + fn: this.$state.isPlaying.get, + }, + getIsPreload: { + strategy: "leaderOnly", + fn: this.$state.isPreload.get, + }, + getLoadingState: { + strategy: "leaderOnly", + fn: this.$state.loadingState.get, + }, + getProgress: { strategy: "leaderOnly", fn: this.$state.progress.get }, + + // SET + setDuration: { strategy: "replicate", fn: this.$state.duration.set }, + setHasEnded: { strategy: "replicate", fn: this.$state.hasEnded.set }, + setIsPlaying: { + strategy: "replicate", + fn: this.$state.isPlaying.set, + }, + setIsPreload: { + strategy: "replicate", + fn: this.$state.isPreload.set, + }, + setLoadingState: { + strategy: "replicate", + fn: this.$state.loadingState.set, + }, + setProgress: { strategy: "replicate", fn: this.$state.progress.set }, + }, + { + // Sync leadership with engine's broadcasting channel + assumeLeadership: (await this.engine?.broadcastingStatus())?.leader, + }, + ); + + if (actions) { + this.$state.duration.set = actions.setDuration; + this.$state.hasEnded.set = actions.setHasEnded; + this.$state.isPlaying.set = actions.setIsPlaying; + this.$state.isPreload.set = actions.setIsPreload; + this.$state.loadingState.set = actions.setLoadingState; + this.$state.progress.set = actions.setProgress; + + untracked(async () => { + this.$state.duration.value = await actions.getDuration(); + this.$state.hasEnded.value = await actions.getHasEnded(); + this.$state.isPlaying.value = await actions.getIsPlaying(); + this.$state.isPreload.value = await actions.getIsPreload(); + this.$state.loadingState.value = await actions.getLoadingState(); + this.$state.progress.value = await actions.getProgress(); + }); + } + } + + // Super + super.connectedCallback(); + } + + // STATE + + /** + * @type {AudioStateReadOnly} + */ + get state() { + return { + id: this.id, + mimeType: (this.getAttribute("mime-type") ?? undefined), + url: (this.getAttribute("url") ?? ""), + + duration: this.$state.duration.get, + hasEnded: this.$state.hasEnded.get, + isPlaying: this.$state.isPlaying.get, + isPreload: this.$state.isPreload.get, + loadingState: this.$state.loadingState.get, + progress: this.$state.progress.get, + }; + } + + // RELATED ELEMENTS + + get audio() { + const el = this.querySelector("audio"); + if (el) return /** @type {HTMLAudioElement} */ (el); + else throw new Error("Cannot find child audio element"); + } + + get engine() { + const el = this.closest("de-audio"); + if (el) return /** @type {AudioEngine} */ (el); + else return null; + } + + // EVENTS + + /** + * @param {Event} event + */ + canplayEvent(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + const item = engineItem(audio); + + if ( + item?.hasAttribute("initial-progress") && + audio.duration && + !isNaN(audio.duration) + ) { + const progress = JSON.parse( + item.getAttribute("initial-progress") ?? "0", + ); + audio.currentTime = audio.duration * progress; + item.removeAttribute("initial-progress"); + } + + finishedLoading(event); + } + + /** + * @param {Event} event + */ + durationchangeEvent(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + + if (!isNaN(audio.duration)) { + engineItem(audio)?.$state.duration.set(audio.duration); + } + } + + /** + * @param {Event} event + */ + endedEvent(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + audio.currentTime = 0; + + engineItem(audio)?.$state.hasEnded.set(true); + } + + /** + * @param {Event} event + */ + errorEvent(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + const code = audio.error?.code || 0; + + engineItem(audio)?.$state.loadingState.set({ error: { code } }); + } + + /** + * @param {Event} event + */ + pauseEvent(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + const item = engineItem(audio); + + item?.$state.isPlaying.set(false); + } + + /** + * @param {Event} event + */ + playEvent(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + + const item = engineItem(audio); + item?.$state.hasEnded.set(false); + item?.$state.isPlaying.set(true); + + // In case audio was preloaded: + if (audio.readyState === 4) finishedLoading(event); + } + + /** + * @param {Event} event + */ + suspendEvent(event) { + finishedLoading(event); + } + + /** + * @param {Event} event + */ + timeupdateEvent(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + if (isNaN(audio.duration) || audio.duration === 0) return; + + const progress = audio.currentTime / audio.duration; + if (progress === 0) return; + + engineItem(audio)?.$state.progress.set(progress); + } + + /** + * @param {Event} event + */ + waitingEvent(event) { + initiateLoading(event); + } +} + +export { AudioEngineItem }; + +//////////////////////////////////////////// +// ๐Ÿ› ๏ธ +//////////////////////////////////////////// + +/** + * @param {HTMLAudioElement} audio + */ +function engineItem(audio) { + const c = audio.closest("de-audio-item"); + if (c) return /** @type {AudioEngineItem} */ (c); + else return null; +} + +/** + * @param {Event} event + */ +function finishedLoading(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + engineItem(audio)?.$state.loadingState.set("loaded"); +} + +/** + * @param {Event} event + */ +function initiateLoading(event) { + const audio = /** @type {HTMLAudioElement} */ (event.target); + if (audio.readyState < 4) { + engineItem(audio)?.$state.loadingState.set("loading"); + } +} + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = AudioEngine; +export const NAME = "de-audio"; +export const NAME_ITEM = "de-audio-item"; + +customElements.define(NAME, AudioEngine); +customElements.define(NAME_ITEM, AudioEngineItem); diff --git a/src/components/engine/audio/types.d.ts b/src/components/engine/audio/types.d.ts new file mode 100644 index 000000000..1b01eaee2 --- /dev/null +++ b/src/components/engine/audio/types.d.ts @@ -0,0 +1,57 @@ +import type { Signal, SignalReader } from "@common/signal.d.ts"; + +export type Actions = { + adjustVolume: (_: { audioId?: string; volume: number }) => void; + pause: (_: { audioId: string }) => void; + play: (_: { audioId: string; volume?: number }) => void; + reload: (_: { audioId: string; play: boolean; progress?: number }) => void; + seek: (_: { audioId: string; percentage: number }) => void; + supply: ( + _: { audio: Audio[]; play?: { audioId: string; volume?: number } }, + ) => void; +}; + +export type Audio = { + id: string; + isPreload: boolean; + mimeType?: string; + // NOTE: Initial progress + progress?: number; + url: string; +}; + +export type AudioState = { + duration: Signal; + hasEnded: Signal; + isPlaying: Signal; + isPreload: Signal; + loadingState: Signal; + progress: Signal; +}; + +export type AudioStateReadOnly = { + id: string; + url: string; + mimeType: string | undefined; + + duration: SignalReader; + hasEnded: SignalReader; + isPlaying: SignalReader; + isPreload: SignalReader; + loadingState: SignalReader; + progress: SignalReader; +}; + +export type LoadingState = + | "initialisation" + | "loading" + | "loaded" + | { + error: { code: number }; + }; + +export type State = { + isPlaying: SignalReader; + items: SignalReader; + volume: SignalReader; +}; diff --git a/src/components/engine/queue/element.js b/src/components/engine/queue/element.js new file mode 100644 index 000000000..3e550dff1 --- /dev/null +++ b/src/components/engine/queue/element.js @@ -0,0 +1,83 @@ +import { DiffuseElement } from "@common/element.js"; +import { signal } from "@common/signal.js"; +import { listen } from "@common/worker.js"; +import { hash } from "@common/index.js"; + +/** + * @import {ProxiedActions} from "@common/worker.d.ts"; + * @import {Actions, Item, State} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {ProxiedActions} + */ +class QueueEngine extends DiffuseElement { + static NAME = "diffuse/engine/queue"; + static WORKER_URL = "components/engine/queue/worker.js"; + + constructor() { + super(); + + /** @type {ProxiedActions} */ + this.proxy = this.workerProxy(); + + this.add = this.proxy.add; + this.fill = this.proxy.fill; + this.pool = this.proxy.pool; + this.shift = this.proxy.shift; + this.unshift = this.proxy.unshift; + } + + // SIGNALS + + #future = signal(/** @type {Array} */ ([])); + #now = signal(/** @type {Item | null} */ (null)); + #past = signal(/** @type {Array} */ ([])); + #poolHash = signal(hash([])); + + // STATE + + future = this.#future.get; + now = this.#now.get; + past = this.#past.get; + poolHash = this.#poolHash.get; + + // LIFECYCLE + + /** + * @override + */ + connectedCallback() { + super.connectedCallback(); + + // Sync data with worker + const link = this.workerLink(); + + // Listen for remote data changes + listen("future", this.#future.set, link); + listen("now", this.#now.set, link); + listen("past", this.#past.set, link); + listen("poolHash", this.#poolHash.set, link); + + // Fetch current data state + this.proxy.future().then(this.#future.set); + this.proxy.now().then(this.#now.set); + this.proxy.past().then(this.#past.set); + this.proxy.poolHash().then(this.#poolHash.set); + } +} + +export default QueueEngine; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = QueueEngine; +export const NAME = "de-queue"; + +customElements.define(NAME, QueueEngine); diff --git a/src/components/engine/queue/types.d.ts b/src/components/engine/queue/types.d.ts new file mode 100644 index 000000000..887413412 --- /dev/null +++ b/src/components/engine/queue/types.d.ts @@ -0,0 +1,28 @@ +import type { Track } from "@definitions/types.d.ts"; +import type { SignalReader } from "@common/signal.d.ts"; + +export type Actions = { + add: (args: { inFront?: boolean; tracks: Track[] }) => void; + fill: ( + args: { + /** Always keep adding, even if the amount of non-manual items in the queue are passed the given `amount` */ + augment?: boolean; + amount: number; + shuffled: boolean; + }, + ) => void; + pool: (tracks: Track[]) => void; + shift: () => void; + unshift: () => void; +}; + +export type Item = + & Track + & { manualEntry?: boolean }; + +export type State = { + future: SignalReader; + now: SignalReader; + past: SignalReader; + poolHash: SignalReader; +}; diff --git a/src/components/engine/queue/worker.js b/src/components/engine/queue/worker.js new file mode 100644 index 000000000..866c3c7b3 --- /dev/null +++ b/src/components/engine/queue/worker.js @@ -0,0 +1,235 @@ +import { announce, ostiary, rpc } from "@common/worker.js"; +import { effect, signal } from "@common/signal.js"; +import { arrayShuffle, hash } from "@common/index.js"; + +/** + * @import {Actions, Item} from "./types.d.ts" + * @import {Track} from "@definitions/types.d.ts" + */ + +//////////////////////////////////////////// +// STATE +//////////////////////////////////////////// + +export const $lake = signal(/** @type {Track[]} */ ([])); + +// Communicated state +export const $future = signal(/** @type {Item[]} */ ([])); +export const $now = signal(/** @type {Item | null} */ (null)); +export const $past = signal(/** @type {Item[]} */ ([])); +export const $poolHash = signal(hash([])); + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {Actions['add']} + */ +export function add({ inFront, tracks }) { + const items = tracks.map((track) => { + return { ...track, manualEntry: true }; + }); + + $future.value = inFront + ? [...items, ...$future.value] + : [...$future.value, ...items]; +} + +/** + * @type {Actions['fill']} + */ +export function fill({ augment, amount, shuffled }) { + $future.value = fillQueue( + shuffled, + amount + + (augment + ? $future.value.filter((i) => i.manualEntry === false).length + : 0), + $future.value, + ); +} + +/** + * @type {Actions['pool']} + */ +export function pool(tracks) { + $lake.value = tracks; + $poolHash.value = hash(tracks); +} + +/** + * @type {Actions['shift']} + */ +export function shift() { + return _shift(); +} + +/** + * @type {Actions['unshift']} + */ +export function unshift() { + const p = $past.value; + if (p.length === 0) return; + + const n = $now.value; + const [last] = p.splice(p.length - 1, 1); + + $now.value = last ?? null; + if (n) $future.value = [n, ...$future.value]; +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context, _firstConnection, _connectionId) => { + // Setup RPC + + rpc(context, { + add, + fill, + pool, + shift, + unshift, + + // State + future: $future.get, + now: $now.get, + past: $past.get, + poolHash: $poolHash.get, + }); + + // Effects + + // Communicate state + effect(() => announce("future", $future.value, context)); + effect(() => announce("now", $now.value, context)); + effect(() => announce("past", $past.value, context)); + effect(() => announce("poolHash", $poolHash.value, context)); + + // When the pool changes, + // make sure all future queue items still exist. + effect(() => { + const existing = new Set($lake.value.map((t) => t.id)); + + $future.value = $future.value.filter((i) => { + return existing.has(i.id); + }); + }); +}); + +//////////////////////////////////////////// +// โ›”๏ธ +//////////////////////////////////////////// + +/** + * Add non-manual items to the queue. + * + * @param {boolean} shuffled + * @param {number | undefined | null} fillAmount + * @param {Item[]} future + * @returns {Item[]} + */ +function fillQueue(shuffled, fillAmount, future) { + if (!fillAmount) return future; + + // Count + let autoFutureCount = 0; + let manualFutureCount = 0; + + future.forEach((item) => { + if (item.manualEntry) manualFutureCount++; + else autoFutureCount++; + }); + + // Fill + if (shuffled) { + if (autoFutureCount >= fillAmount) return future; + return fillShuffle(fillAmount, future, autoFutureCount); + } else { + return fillSequentially(fillAmount, future); + } +} + +/** + * @param {number} fillAmount + * @param {Item[]} future + * @returns {Item[]} + */ +export function fillSequentially(fillAmount, future) { + const onlyManual = future.filter((i) => i.manualEntry); + const lastManual = onlyManual.slice(-1)[0]; + const startIndex = lastManual + ? $lake.value.findIndex((t) => t.id === lastManual.id) + 1 + : $now.value + ? $lake.value.findIndex((t) => t.id === $now.value?.id) + 1 + : 0; + + const maxIndex = $lake.value.length - 1; + let currIndex = startIndex; + + /** @type {Item[]} */ + const autoItems = []; + + for (let i = 0; i < fillAmount; i++) { + if (currIndex > maxIndex) currIndex = 0; + const item = $lake.value[currIndex]; + if (item) { + autoItems.push({ + ...item, + manualEntry: false, + }); + } + currIndex++; + } + + return [...onlyManual, ...autoItems]; +} + +/** + * @param {number} fillAmount + * @param {Item[]} future + * @param {number} autoFutureCount + * @returns {Item[]} + */ +export function fillShuffle(fillAmount, future, autoFutureCount) { + // Determine pool of available queue items + /** @type {Item[]} */ + const pool = []; + + const pastSet = new Set($past.value.map((i) => i.id)); + let reducedPool = pool; + + $lake.value.forEach((track) => { + if (pastSet.delete(track.id) === false) { + pool.push({ + ...track, + manualEntry: false, + }); + } + }); + + if (reducedPool.length === 0) { + reducedPool = $lake.value; + } + + const poolSelection = arrayShuffle(reducedPool).slice( + 0, + Math.max(0, fillAmount - autoFutureCount), + ); + + return [...future, ...poolSelection]; +} + +/** + * @param {Item[]} [future] + */ +export function _shift(future) { + const n = $now.value; + const f = future ?? $future.value; + + $now.value = f[0] ?? null; + if (n) $past.value = [...$past.value, n]; + $future.value = f.slice(1); +} diff --git a/src/components/input/common.js b/src/components/input/common.js new file mode 100644 index 000000000..ddc95fe06 --- /dev/null +++ b/src/components/input/common.js @@ -0,0 +1,18 @@ +import { base64url } from "iso-base/rfc4648"; + +/** + * @param {string} scheme + * @param {string} groupId + */ +export async function groupKeyHash(scheme, groupId) { + const rawBytes = new TextEncoder().encode(`${scheme}://${groupId}`); + const hashedBytes = await crypto.subtle.digest("SHA-256", rawBytes); + return base64url.encode(new Uint8Array(hashedBytes)); +} + +/** + * @param {string} filename + */ +export function isAudioFile(filename) { + return filename.match(/\.(flac|m4a|mp3|mp4|ogg|opus|wav|webm)$/); +} diff --git a/src/components/input/opensubsonic/common.js b/src/components/input/opensubsonic/common.js new file mode 100644 index 000000000..4890e29ed --- /dev/null +++ b/src/components/input/opensubsonic/common.js @@ -0,0 +1,200 @@ +import { SubsonicAPI } from "subsonic-api"; +import * as IDB from "idb-keyval"; +import * as URI from "uri-js"; +import QS from "query-string"; + +import { IDB_SERVERS, SCHEME } from "./constants.js"; + +/** + * @import {Child} from "subsonic-api" + * @import {Track} from "@definitions/types.d.ts"; + * @import {Server} from "./types.d.ts"; + */ + +/** + * @param {Child["type"]} type + * @returns {Track["kind"]} + */ +export function autoTypeToTrackKind(type) { + switch (type?.toLowerCase()) { + case "audiobook": + return "audiobook"; + + case "music": + return "music"; + + case "podcast": + return "podcast"; + + default: + return "miscellaneous"; + } +} + +/** + * @param {Server} server + * @param {{ songId: string; path?: string }} [args] + */ +export function buildURI(server, args) { + return URI.serialize({ + scheme: SCHEME, + userinfo: server.apiKey + ? URI.escapeComponent(server.apiKey) + : `${URI.escapeComponent(server.username || "")}:${ + URI.escapeComponent(server.password || "") + }`, + host: server.host.replace(/^https?:\/\//, ""), + path: args?.path, + query: QS.stringify({ + songId: args?.songId, + tls: server.tls ? "t" : "f", + }), + }); +} + +/** + * @param {Server} server + */ +export async function consultServer(server) { + const client = createClient(server); + const resp = await client.ping().catch(() => undefined); + + return resp?.status?.toLowerCase() === "ok"; +} + +/** + * @param {Server} server + */ +export function createClient(server) { + return new SubsonicAPI({ + url: `http${server.tls ? "s" : ""}://${server.host}`, + auth: server.apiKey ? { apiKey: URI.unescapeComponent(server.apiKey) } : { + username: URI.unescapeComponent(server.username || ""), + password: URI.unescapeComponent(server.password || ""), + }, + }); +} + +/** + * @param {Track[]} tracks + */ +export function groupTracksByServer(tracks) { + /** @type {Record} */ + const acc = {}; + + tracks.forEach((track) => { + const parsed = parseURI(track.uri); + if (!parsed) return; + + const id = serverId(parsed.server); + + if (acc[id]) { + acc[id].tracks.push(track); + } else { + acc[id] = { server: parsed.server, tracks: [track] }; + } + }); + + return acc; +} + +/** + * @returns {Promise>} + */ +export async function loadServers() { + const i = await IDB.get(IDB_SERVERS); + return i ? i : {}; +} + +/** + * Parse an opensubsonic URI. + * + * ``` + * opensubsonic://username:password@server-host:port/path?tls=f + * ``` + * + * @param {string} uriString + * @returns {{ path: string | undefined; server: Server; songId: string | undefined } | undefined} + */ +export function parseURI(uriString) { + const uri = URI.parse(uriString); + if (uri.scheme !== SCHEME) return undefined; + if (!uri.host) return undefined; + + let apiKey = undefined; + let username = undefined; + let password = undefined; + + if (uri.userinfo?.includes(":")) { + // Username + Password + const [u, p] = uri.userinfo.split(":"); + username = u; + password = p; + if (!username || !password) return undefined; + } else { + // API key + apiKey = uri.userinfo; + if (!apiKey) return undefined; + } + + const qs = QS.parse(uri.query || ""); + + const server = { + apiKey, + host: uri.port ? `${uri.host}:${uri.port}` : uri.host, + password, + tls: qs.tls === "f" ? false : true, + username, + }; + + const path = uri.path; + const songId = typeof qs.songId === "string" ? qs.songId : undefined; + + return { path, server, songId }; +} + +/** + * @param {Record} items + */ +export async function saveServers(items) { + await IDB.set(IDB_SERVERS, items); +} + +/** + * @param {Track[]} tracks + */ +export function serversFromTracks(tracks) { + /** @type {Record} */ + const acc = {}; + + tracks.forEach((track) => { + const parsed = parseURI(track.uri); + if (!parsed) return; + + const id = serverId(parsed.server); + if (acc[id]) return; + + acc[id] = parsed.server; + }); + + return acc; +} + +/** + * @param {Server} server + */ +export function serverId(server) { + const parts = { + host: server.host, + query: `tls=${server.tls ? "t" : "f"}`, + }; + + const uri = server.apiKey + ? URI.serialize({ ...parts, userinfo: server.apiKey }) + : URI.serialize({ + ...parts, + userinfo: `${server.username}:${server.password}`, + }); + + return btoa(uri); +} diff --git a/src/components/input/opensubsonic/constants.js b/src/components/input/opensubsonic/constants.js new file mode 100644 index 000000000..981a35df4 --- /dev/null +++ b/src/components/input/opensubsonic/constants.js @@ -0,0 +1,3 @@ +export const IDB_PREFIX = "@components/input/opensubsonic"; +export const IDB_SERVERS = `${IDB_PREFIX}/servers`; +export const SCHEME = "opensubsonic"; diff --git a/src/components/input/opensubsonic/element.js b/src/components/input/opensubsonic/element.js new file mode 100644 index 000000000..f5459a6db --- /dev/null +++ b/src/components/input/opensubsonic/element.js @@ -0,0 +1,46 @@ +import { DiffuseElement } from "@common/element.js"; +import { SCHEME } from "./constants.js"; + +/** + * @import {InputActions, InputSchemeProvider} from "@components/input/types.d.ts" + * @import {ProxiedActions} from "@common/worker.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {ProxiedActions} + * @implements {InputSchemeProvider} + */ +class OpensubsonicInput extends DiffuseElement { + static NAME = "diffuse/input/opensubsonic"; + static WORKER_URL = "components/input/opensubsonic/worker.js"; + + SCHEME = SCHEME; + + constructor() { + super(); + + /** @type {ProxiedActions} */ + const p = this.workerProxy(); + + this.consult = p.consult; + this.contextualize = p.contextualize; + this.groupConsult = p.groupConsult; + this.list = p.list; + this.resolve = p.resolve; + } +} + +export default OpensubsonicInput; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = OpensubsonicInput; +export const NAME = "di-opensubsonic"; + +customElements.define(NAME, OpensubsonicInput); diff --git a/src/components/input/opensubsonic/types.d.ts b/src/components/input/opensubsonic/types.d.ts new file mode 100644 index 000000000..457b2639d --- /dev/null +++ b/src/components/input/opensubsonic/types.d.ts @@ -0,0 +1,8 @@ +// https://opensubsonic.netlify.app/docs/api-reference/ +export type Server = { + apiKey?: string; + host: string; + password?: string; + tls: boolean; + username?: string; +}; diff --git a/src/components/input/opensubsonic/worker.js b/src/components/input/opensubsonic/worker.js new file mode 100644 index 000000000..a61fa4f28 --- /dev/null +++ b/src/components/input/opensubsonic/worker.js @@ -0,0 +1,298 @@ +import * as URI from "uri-js"; + +import { effect, signal } from "@common/signal.js"; +import { announce, ostiary, rpc } from "@common/worker.js"; + +import { SCHEME } from "./constants.js"; +import { + autoTypeToTrackKind, + buildURI, + consultServer, + createClient, + groupTracksByServer, + loadServers, + parseURI, + saveServers, + serverId, + serversFromTracks, +} from "./common.js"; +import { groupKeyHash } from "../common.js"; + +/** + * @import {Child, SubsonicAPI} from "subsonic-api" + * @import {Track} from "@definitions/types.d.ts"; + * @import {ConsultGrouping, InputActions as Actions} from "@components/input/types.d.ts"; + * @import {Server} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// STATE +//////////////////////////////////////////// + +const $servers = signal(/** @type {Record} */ ({})); + +effect(() => { + saveServers($servers.value); +}); + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {Actions['consult']} + */ +export async function consult(fileUriOrScheme) { + if (!fileUriOrScheme.includes(":")) { + return { supported: true, consult: "undetermined" }; + } + + const parsed = parseURI(fileUriOrScheme); + if (!parsed) return { supported: true, consult: "undetermined" }; + + const consult = await consultServer(parsed.server); + return { supported: true, consult }; +} + +/** + * @type {Actions['contextualize']} + */ +export async function contextualize(tracks) { + const servers = serversFromTracks(tracks); + $servers.value = servers; +} + +/** + * @type {Actions['groupConsult']} + */ +export async function groupConsult(tracks) { + const groups = groupTracksByServer(tracks); + + const promises = Object.entries(groups).map( + async ([serverId, { server, tracks }]) => { + const available = await consultServer(server); + + /** @type {ConsultGrouping} */ + const grouping = available + ? { available, scheme: SCHEME, tracks } + : { available, reason: "Server ping failed", scheme: SCHEME, tracks }; + + return { + key: await groupKeyHash(SCHEME, serverId), + grouping, + }; + }, + ); + + const entries = (await Promise.all(promises)).map(( + entry, + ) => [entry.key, entry.grouping]); + + return Object.fromEntries(entries); +} + +/** + * @type {Actions['list']} + */ +export async function list(cachedTracks = []) { + /** @type {Record>} */ + const cache = {}; + + /** @type {Record} */ + const servers = {}; + + cachedTracks.forEach((t) => { + const parsed = parseURI(t.uri); + if (!parsed || parsed.path === undefined) return; + + const sid = serverId(parsed.server); + servers[sid] = parsed.server; + + cache[sid] ??= {}; + cache[sid][URI.unescapeComponent(parsed.path)] = t; + }); + + /** + * @param {SubsonicAPI} client + * @returns {Promise} + */ + async function search(client, offset = 0) { + const result = await client.search3({ + query: "", + artistCount: 0, + albumCount: 0, + songCount: 1000, + songOffset: offset, + }); + + const songs = result.searchResult3.song || []; + + if (songs.length === 1000) { + const moreSongs = await search(client, offset + 1000); + return [...songs, ...moreSongs]; + } + + return songs; + } + + const promises = Object.values(servers).map(async (server) => { + const client = createClient(server); + const sid = serverId(server); + const list = await search(client, 0); + + let tracks = list + .filter((song) => !song.isVideo) + .map((song) => { + const path = song.path + ? song.path.startsWith("/") ? song.path : `/${song.path}` + : undefined; + + const fromCache = path ? cache[sid]?.[path] : undefined; + if (fromCache) return fromCache; + + /** @type {Track} */ + const track = { + $type: "sh.diffuse.output.tracks", + id: crypto.randomUUID(), + kind: autoTypeToTrackKind(song.type), + uri: buildURI(server, { songId: song.id, path }), + + stats: { + albumGain: undefined, + bitrate: song.bitRate ? song.bitRate * 1000 : undefined, + bitsPerSample: undefined, + codec: undefined, + container: undefined, + duration: song.duration, + lossless: undefined, + numberOfChannels: undefined, + sampleRate: undefined, + trackGain: undefined, + }, + tags: { + album: song.album, + albumartist: song.albumArtists?.[0]?.name, + albumartists: song.albumArtists?.map((a) => a.name), + albumartistsort: song.albumArtists?.[0]?.sortName, + albumsort: undefined, + arranger: undefined, + artist: song.artist ?? song.displayArtist, + artists: undefined, + artistsort: undefined, + asin: undefined, + averageLevel: undefined, + barcode: undefined, + bpm: song.bpm, + catalognumbers: undefined, + compilation: undefined, + composers: song.displayComposer + ? [song.displayComposer] + : undefined, + composersort: undefined, + conductors: undefined, + date: undefined, + disc: { + no: song.discNumber || 1, + }, + djmixers: undefined, + engineers: undefined, + gapless: undefined, + genres: song.genres, + isrc: undefined, + labels: undefined, + lyricists: undefined, + media: undefined, + mixers: undefined, + moods: song.moods, + originaldate: undefined, + originalyear: undefined, + peakLevel: undefined, + producers: undefined, + publishers: undefined, + releasecountry: undefined, + releasedate: undefined, + releasestatus: undefined, + releasetypes: undefined, + remixers: undefined, + technicians: undefined, + title: song.title ?? "Unknown", + titlesort: undefined, + track: { + no: song.track ?? 1, + of: song.size, + }, + work: undefined, + writers: undefined, + year: song.year, + }, + }; + + return track; + }); + + // If a server didn't have any tracks, + // keep a placeholder track so the server gets + // picked up whenever it is re-contextualized. + if (!tracks.length) { + tracks = [{ + $type: "sh.diffuse.output.tracks", + id: crypto.randomUUID(), + kind: "placeholder", + uri: buildURI(server), + }]; + } + + return tracks; + }); + + const tracks = (await Promise.all(promises)).flat(1); + return tracks; +} + +/** + * @type {Actions['resolve']} + */ +export async function resolve({ uri }) { + const parsed = parseURI(uri); + if (!parsed) return undefined; + + const client = createClient(parsed.server); + const songId = parsed.songId; + if (!songId) return undefined; + + // TODO: + // const expiresInSeconds = 60 * 60 * 24 * 7; // 7 days + // const expiresAtSeconds = Math.round(Date.now() / 1000) + expiresInSeconds; + + const url = await client + .download({ + id: songId, + format: "raw", + }) + .then((a) => a.url); + + return { expiresAt: Infinity, url }; +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + // Setup RPC + + rpc(context, { + consult, + contextualize, + groupConsult, + list, + resolve, + + // State + servers: $servers.get, + }); + + // Communicate state + + effect(() => announce("servers", $servers.value, context)); +}); diff --git a/src/components/input/s3/common.js b/src/components/input/s3/common.js new file mode 100644 index 000000000..04205a51c --- /dev/null +++ b/src/components/input/s3/common.js @@ -0,0 +1,165 @@ +import { S3Client } from "@bradenmacdonald/s3-lite-client"; +import * as IDB from "idb-keyval"; +import * as URI from "uri-js"; +import QS from "query-string"; + +import { ENCODINGS, IDB_BUCKETS, SCHEME } from "./constants.js"; + +/** + * @import { Track } from "@definitions/types.d.ts"; + * @import { Bucket } from "./types.d.ts"; + */ + +//////////////////////////////////////////// +// ๐Ÿ› ๏ธ +//////////////////////////////////////////// + +/** + * @param {Track[]} tracks + */ +export function bucketsFromTracks(tracks) { + /** @type {Record} */ + const acc = {}; + + tracks.forEach((track) => { + const parsed = parseURI(track.uri); + if (!parsed) return; + + const id = bucketId(parsed.bucket); + if (acc[id]) return; + + acc[id] = parsed.bucket; + }); + + return acc; +} + +/** + * @param {Bucket} bucket + */ +export function bucketId(bucket) { + return `${bucket.accessKey}:${bucket.secretKey}@${bucket.host}`; +} + +/** + * @param {Bucket} bucket + * @param {string} [path] + */ +export function buildURI(bucket, path) { + return URI.serialize({ + scheme: SCHEME, + userinfo: `${bucket.accessKey}:${bucket.secretKey}`, + host: bucket.host.replace(/^https?:\/\//, ""), + path: path, + query: QS.stringify({ + bucketName: bucket.bucketName, + bucketPath: bucket.path, + region: bucket.region, + }), + }); +} + +/** + * @param {Bucket} bucket + */ +export async function consultBucket(bucket) { + const client = createClient(bucket); + return await client.bucketExists(bucket.bucketName); +} + +/** + * @param {Bucket} bucket + */ +export function createClient(bucket) { + return new S3Client({ + bucket: bucket.bucketName, + endPoint: `http${ + bucket.host.startsWith("localhost") ? "" : "s" + }://${bucket.host}`, + region: bucket.region, + pathStyle: false, + accessKey: bucket.accessKey, + secretKey: bucket.secretKey, + }); +} + +/** + * @param {string} a + */ +export function encodeAwsUriComponent(a) { + return encodeURIComponent(a).replace( + /(\+|!|"|#|\$|&|'|\(|\)|\*|\+|,|:|;|=|\?|@)/gim, + (match) => /** @type {any} */ (ENCODINGS)[match] ?? match, + ); +} + +/** + * @param {Track[]} tracks + */ +export function groupTracksByBucket(tracks) { + /** @type {Record} */ + const acc = {}; + + tracks.forEach((track) => { + const parsed = parseURI(track.uri); + if (!parsed) return acc; + + const id = bucketId(parsed.bucket); + + if (acc[id]) { + acc[id].tracks.push(track); + } else { + acc[id] = { bucket: parsed.bucket, tracks: [track] }; + } + }); + + return acc; +} + +/** + * @returns {Promise>} + */ +export async function loadBuckets() { + const i = await IDB.get(IDB_BUCKETS); + return i ? i : {}; +} + +/** + * @param {string} uriString + * @returns {{ bucket: Bucket; path: string } | undefined} + */ +export function parseURI(uriString) { + const uri = URI.parse(uriString); + if (uri.scheme !== SCHEME) return undefined; + if (!uri.host) return undefined; + + const [accessKey, secretKey] = uri.userinfo?.split(":") ?? []; + if (!accessKey || !secretKey) return undefined; + + const qs = QS.parse(uri.query || ""); + + const bucket = { + accessKey, + bucketName: typeof qs.bucketName === "string" ? qs.bucketName : "", + host: uri.host, + path: qs.bucketPath === "string" ? qs.bucketPath : "/", + region: typeof qs.region === "string" ? qs.region : "", + secretKey, + }; + + const path = + (bucket.path.replace(/\/$/, "") + URI.unescapeComponent(uri.path || "")) + .replace( + /^\//, + "", + ); + + return { bucket, path }; +} + +/** + * @param {Record} items + */ +export async function saveBuckets(items) { + await IDB.set(IDB_BUCKETS, items); +} diff --git a/src/components/input/s3/constants.js b/src/components/input/s3/constants.js new file mode 100644 index 000000000..3eecda9de --- /dev/null +++ b/src/components/input/s3/constants.js @@ -0,0 +1,22 @@ +export const IDB_PREFIX = "@components/input/s3"; +export const IDB_BUCKETS = `${IDB_PREFIX}/buckets`; +export const SCHEME = "s3"; + +export const ENCODINGS = { + "\+": "%2B", + "\!": "%21", + '"': "%22", + "\#": "%23", + "\$": "%24", + "\&": "%26", + "'": "%27", + "\(": "%28", + "\)": "%29", + "\*": "%2A", + "\,": "%2C", + "\:": "%3A", + "\;": "%3B", + "\=": "%3D", + "\?": "%3F", + "\@": "%40", +}; diff --git a/src/components/input/s3/element.js b/src/components/input/s3/element.js new file mode 100644 index 000000000..55be593d3 --- /dev/null +++ b/src/components/input/s3/element.js @@ -0,0 +1,48 @@ +import { DiffuseElement } from "@common/element.js"; +import { SCHEME } from "./constants.js"; + +/** + * @import {InputActions, InputSchemeProvider} from "@components/input/types.d.ts" + * @import {ProxiedActions} from "@common/worker.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {ProxiedActions} + * @implements {InputSchemeProvider} + */ +class S3Input extends DiffuseElement { + static NAME = "diffuse/input/s3"; + static WORKER_URL = "components/input/s3/worker.js"; + + SCHEME = SCHEME; + + constructor() { + super(); + + /** @type {ProxiedActions Promise }>} */ + const p = this.workerProxy(); + + this.consult = p.consult; + this.contextualize = p.contextualize; + this.groupConsult = p.groupConsult; + this.list = p.list; + this.resolve = p.resolve; + + this.demo = p.demo; + } +} + +export default S3Input; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = S3Input; +export const NAME = "di-s3"; + +customElements.define(NAME, CLASS); diff --git a/src/components/input/s3/types.d.ts b/src/components/input/s3/types.d.ts new file mode 100644 index 000000000..6c0a658f6 --- /dev/null +++ b/src/components/input/s3/types.d.ts @@ -0,0 +1,8 @@ +export type Bucket = { + accessKey: string; + bucketName: string; + host: string; + path: string; + region: string; + secretKey: string; +}; diff --git a/src/components/input/s3/worker.js b/src/components/input/s3/worker.js new file mode 100644 index 000000000..53edc70fd --- /dev/null +++ b/src/components/input/s3/worker.js @@ -0,0 +1,242 @@ +import { groupKeyHash, isAudioFile } from "@components/input/common.js"; +import { + bucketId, + bucketsFromTracks, + buildURI, + consultBucket, + createClient, + groupTracksByBucket, + loadBuckets, + parseURI, +} from "./common.js"; +import { SCHEME } from "./constants.js"; +import { announce, ostiary, rpc } from "@common/worker.js"; +import { effect, signal } from "@common/signal.js"; + +import { saveBuckets } from "./common.js"; + +/** + * @import { InputActions as Actions, ConsultGrouping } from "@components/input/types.d.ts"; + * @import { Track } from "@definitions/types.d.ts" + * @import { Bucket } from "./types.d.ts" + */ + +//////////////////////////////////////////// +// STATE +//////////////////////////////////////////// + +const $buckets = signal(/** @type {Record} */ ({})); + +effect(() => { + saveBuckets($buckets.value); +}); + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {Actions['consult']} + */ +export async function consult(fileUriOrScheme) { + if (!fileUriOrScheme.includes(":")) { + return { supported: true, consult: "undetermined" }; + } + + const parsed = parseURI(fileUriOrScheme); + if (!parsed) return { supported: true, consult: "undetermined" }; + + const consult = await consultBucket(parsed.bucket); + return { supported: true, consult }; +} + +/** + * @type {Actions['contextualize']} + */ +export async function contextualize(tracks) { + const buckets = bucketsFromTracks(tracks); + $buckets.value = buckets; +} + +/** + * @type {Actions['groupConsult']} + */ +export async function groupConsult(tracks) { + const groups = groupTracksByBucket(tracks); + + const promises = Object.entries(groups).map( + async ([bucketId, { bucket, tracks }]) => { + const available = await consultBucket(bucket); + + /** @type {ConsultGrouping} */ + const grouping = available + ? { available, scheme: SCHEME, tracks } + : { available, reason: "Bucket unavailable", scheme: SCHEME, tracks }; + + return { + key: await groupKeyHash(SCHEME, bucketId), + grouping, + }; + }, + ); + + const entries = (await Promise.all(promises)).map(( + entry, + ) => [entry.key, entry.grouping]); + + return Object.fromEntries(entries); +} + +/** + * @type {Actions['list']} + */ +export async function list(cachedTracks = []) { + /** @type {Record>} */ + const cache = {}; + + /** @type {Record} */ + const buckets = {}; + + cachedTracks.forEach((t) => { + const parsed = parseURI(t.uri); + if (!parsed) return; + + const bid = bucketId(parsed.bucket); + buckets[bid] = parsed.bucket; + + if (cache[bid]) { + cache[bid][parsed.path] = t; + } else { + cache[bid] = { [parsed.path]: t }; + } + }); + + const promises = Object.values(buckets).map(async (bucket) => { + const client = createClient(bucket); + const bid = bucketId(bucket); + + const list = await Array.fromAsync( + client.listObjects({ + prefix: bucket.path.replace(/^\//, ""), + }), + ); + + let tracks = list + .filter((l) => isAudioFile(l.key)) + .map((l) => { + const cachedTrack = cache[bid]?.[l.key]; + + const id = cachedTrack?.id || crypto.randomUUID(); + const stats = cachedTrack?.stats; + const tags = cachedTrack?.tags; + + /** @type {Track} */ + const track = { + $type: "sh.diffuse.output.tracks", + id, + stats, + tags, + uri: buildURI(bucket, l.key), + }; + + return track; + }); + + // If a bucket didn't have any tracks, + // keep a placeholder track so the bucket gets + // picked up whenever it is re-contextualized. + if (!tracks.length) { + tracks = [{ + $type: "sh.diffuse.output.tracks", + id: crypto.randomUUID(), + kind: "placeholder", + uri: buildURI(bucket), + }]; + } + + return tracks; + }); + + const tracks = (await Promise.all(promises)).flat(1); + return tracks; +} + +/** + * @type {Actions['resolve']} + */ +export async function resolve( + { method, uri }, +) { + const parsed = parseURI(uri); + if (!parsed) return undefined; + + const expiresInSeconds = 60 * 60 * 24 * 7; // 7 days + const expiresAtSeconds = Math.round(Date.now() / 1000) + expiresInSeconds; + + const client = createClient(parsed.bucket); + const url = await client.getPresignedUrl( + /** @type {any} */ (method?.toUpperCase() ?? "GET"), + parsed.path, + ); + + return { expiresAt: expiresAtSeconds, url }; +} + +//////////////////////////////////////////// +// ADDITIONAL ACTIONS +//////////////////////////////////////////// + +export function demo() { + // Credentials are read-only, no worries. + + /** @type {Bucket} */ + const bucket = { + accessKey: atob("QUtJQTZPUTNFVk1BWFZDRFFINkI="), + bucketName: "ongaku-ryoho-demo", + host: "s3.amazonaws.com", + path: "/", + region: "us-east-1", + secretKey: atob("Z0hPQkdHRzU1aXc0a0RDbjdjWlRJYTVTUDRZWnpERkRzQnFCYWI4Mg=="), + }; + + const uri = buildURI(bucket); + + /** @type {Track} */ + const track = { + $type: "sh.diffuse.output.tracks", + id: crypto.randomUUID(), + kind: "placeholder", + uri, + }; + + return { + bucket, + track, + }; +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + // Setup RPC + + rpc(context, { + consult, + contextualize, + groupConsult, + list, + resolve, + + // Additional actions + demo, + + // State + buckets: $buckets.get, + }); + + // Communicate state + + effect(() => announce("buckets", $buckets.value, context)); +}); diff --git a/src/components/input/types.d.ts b/src/components/input/types.d.ts new file mode 100644 index 000000000..c15b86acf --- /dev/null +++ b/src/components/input/types.d.ts @@ -0,0 +1,42 @@ +import type { ProxiedActions } from "@common/worker.d.ts"; + +import type { Track } from "@definitions/types.d.ts"; +import type { DiffuseElement } from "@common/element.js"; + +/** + * Consultation. + * + * `consult` can be "undetermined" if only a scheme was given + * instead of a full URI. + */ +export type Consult = + | { supported: false; reason: string } + | { supported: true; consult: "undetermined" | boolean }; + +export type ConsultGrouping = + | { available: false; reason: string; scheme: string; tracks: Track[] } + | { available: true; scheme: string; tracks: Track[] }; + +export type GroupConsult = Record; + +export type InputActions = { + consult(fileUriOrScheme: string): Promise; + contextualize(tracks: Track[]): Promise; + groupConsult(tracks: Track[]): Promise; + list(cachedTracks: Track[]): Promise; + resolve( + { method, uri }: { method?: string; uri: string }, + ): Promise; +}; + +export type InputElement = + & DiffuseElement + & InputSchemeProvider + & ProxiedActions; + +export type InputSchemeProvider = { SCHEME: string }; + +export type ResolvedUri = undefined | { + stream: ReadableStream; + expiresAt: number; +} | { url: string; expiresAt: number }; diff --git a/src/components/orchestrator/input/element.js b/src/components/orchestrator/input/element.js new file mode 100644 index 000000000..bca9fde1c --- /dev/null +++ b/src/components/orchestrator/input/element.js @@ -0,0 +1,79 @@ +import { DiffuseElement } from "@common/element.js"; + +import "@components/configurator/input/element.js"; +import "@components/input/opensubsonic/element.js"; +import "@components/input/s3/element.js"; + +/** + * @import {RenderArg} from "@common/element.d.ts" + * @import {InputActions, InputElement} from "@components/input/types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +class InputOrchestrator extends DiffuseElement { + static NAME = "diffuse/orchestrator/input"; + static WORKER_URL = "components/configurator/input/worker.js"; + + /** + * @returns {InputElement} + */ + get input() { + /** @type {InputElement | null} */ + const input = this.querySelector("dc-input"); + + if (!input) throw new Error("Input orchestrator did not render yet."); + return input; + } + + // PROXY INPUT ACTIONS + + consult = /** @type {InputActions["consult"]} */ (...args) => + this.input.consult(...args); + + contextualize = /** @type {InputActions["contextualize"]} */ (...args) => + this.input.contextualize(...args); + + groupConsult = /** @type {InputActions["groupConsult"]} */ (...args) => + this.input.groupConsult(...args); + + list = /** @type {InputActions["list"]} */ (...args) => + this.input.list(...args); + + resolve = /** @type {InputActions["resolve"]} */ (...args) => + this.input.resolve(...args); + + // PROXY OTHER FUNCTIONS + + /** @override */ + dependencies() { + return this.input.dependencies(); + } + + // RENDER + + /** + * @param {RenderArg} _ + */ + render({ html }) { + return html` + + + + + `; + } +} + +export default InputOrchestrator; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = InputOrchestrator; +export const NAME = "do-input"; + +customElements.define(NAME, CLASS); diff --git a/src/components/orchestrator/output/element.js b/src/components/orchestrator/output/element.js new file mode 100644 index 000000000..60ffc8c75 --- /dev/null +++ b/src/components/orchestrator/output/element.js @@ -0,0 +1,74 @@ +import { DiffuseElement } from "@common/element.js"; + +import "@components/configurator/output/element.js"; +import "@components/output/polymorphic/indexed-db/element.js"; +import "@components/transformer/output/refiner/default/element.js"; +import "@components/transformer/output/string/json/element.js"; + +/** + * @import {RenderArg} from "@common/element.d.ts" + * @import {Track} from "@definitions/types.d.ts" + * @import {OutputElement} from "@components/output/types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +class OutputOrchestrator extends DiffuseElement { + static NAME = "diffuse/orchestrator/output"; + + /** + * @returns {OutputElement} + */ + get output() { + /** @type {OutputElement | null} */ + const output = this.querySelector("#do-output__output"); + + if (!output) throw new Error("Output orchestrator did not render yet."); + return output; + } + + // PROXY OUTPUT ACTIONS + + get tracks() { + return this.output.tracks; + } + + // RENDER + + /** + * @param {RenderArg} _ + */ + render({ html }) { + return html` + + + + + + + + `; + } +} + +export default OutputOrchestrator; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = OutputOrchestrator; +export const NAME = "do-output"; + +customElements.define(NAME, CLASS); diff --git a/src/components/orchestrator/process-tracks/element.js b/src/components/orchestrator/process-tracks/element.js new file mode 100644 index 000000000..65b61f77b --- /dev/null +++ b/src/components/orchestrator/process-tracks/element.js @@ -0,0 +1,136 @@ +import { BroadcastableDiffuseElement, query } from "@common/element.js"; +import { signal, untracked } from "@common/signal.js"; + +/** + * @import {Track} from "@definitions/types.d.ts" + * @import {ProxiedActions} from "@common/worker.d.ts" + * @import {InputElement} from "@components/input/types.d.ts" + * @import {OutputElement} from "@components/output/types.d.ts" + * + * @import {Actions} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * Processes inputs into tracks whenever + * the already existing tracks are loaded + * from the assigned output element. + */ +class ProcessTracksOrchestrator extends BroadcastableDiffuseElement { + static NAME = "diffuse/orchestrator/process-tracks"; + static WORKER_URL = "components/orchestrator/process-tracks/worker.js"; + + /** @type {ProxiedActions} */ + #proxy; + + constructor() { + super(); + this.#proxy = this.workerProxy({ + forceNew: { + dependencies: { input: true }, + }, + }); + } + + // SIGNALS + + #isProcessing = signal(false); + + // STATE + + isProcessing = this.#isProcessing.get; + + // LIFECYCLE + + /** + * @override + */ + async connectedCallback() { + // Broadcast if needed + if (this.hasAttribute("group")) { + this.broadcast(this.nameWithGroup, {}); + } + + // Super + super.connectedCallback(); + + /** @type {InputElement} */ + const input = query(this, "input-selector"); + + /** @type {OutputElement} */ + const output = query(this, "output-selector"); + + /** @type {import("@components/processor/metadata/element.js").CLASS} */ + const metadataProcessor = query(this, "metadata-processor-selector"); + + // Assign to self + this.input = input; + this.output = output; + this.metadataProcessor = metadataProcessor; + + // Wait until defined + await customElements.whenDefined(output.localName); + + // Process whenever tracks are initially loaded + if (this.hasAttribute("process-when-ready")) { + this.effect(() => { + const state = output.tracks.state(); + if (state !== "loaded") return; + + untracked(() => this.process()); + }); + } + } + + // WORKERS + + /** + * @override + */ + dependencies() { + if (!this.input) throw new Error("Input element not defined yet"); + if (!this.metadataProcessor) { + throw new Error("Metadata processor element not defined yet"); + } + + return { + input: this.input, + metadataProcessor: this.metadataProcessor, + }; + } + + // ACTIONS + + async process() { + if (!this.output) return; + if (!(await this.isLeader())) return; + + // Start + this.#isProcessing.value = true; + console.log("๐Ÿชต Processing initiated"); + + const cachedTracks = this.output.tracks.collection(); + const result = await this.#proxy.process(cachedTracks); + + // Save if collection changed + if (result) await this.output.tracks.save(result); + + // Fin + console.log("๐Ÿชต Processing completed"); + this.#isProcessing.value = false; + } +} + +export default ProcessTracksOrchestrator; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = ProcessTracksOrchestrator; +export const NAME = "do-process-tracks"; + +customElements.define(NAME, ProcessTracksOrchestrator); diff --git a/src/components/orchestrator/process-tracks/types.d.ts b/src/components/orchestrator/process-tracks/types.d.ts new file mode 100644 index 000000000..50b956b83 --- /dev/null +++ b/src/components/orchestrator/process-tracks/types.d.ts @@ -0,0 +1,5 @@ +import type { Track } from "@definitions/types.d.ts"; + +export type Actions = { + process: (tracks: Track[]) => Promise; +}; diff --git a/src/components/orchestrator/process-tracks/worker.js b/src/components/orchestrator/process-tracks/worker.js new file mode 100644 index 000000000..655367c5c --- /dev/null +++ b/src/components/orchestrator/process-tracks/worker.js @@ -0,0 +1,90 @@ +import deepDiff from "@fry69/deep-diff"; + +import { ostiary, rpc, workerProxy } from "@common/worker.js"; + +/** + * @import {Track} from "@definitions/types.d.ts" + * @import {ActionsWithTunnel, ProxiedActions} from "@common/worker.d.ts" + * @import {InputActions} from "@components/input/types.d.ts" + * @import {Actions as MetadataProcessorActions} from "@components/processor/metadata/types.d.ts" + * @import {Actions} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {ActionsWithTunnel["process"]} + */ +export async function process({ data, ports }) { + const cachedTracks = data; + + /** @type {ProxiedActions} */ + const input = workerProxy(() => ports.input); + + /** @type {ProxiedActions} */ + const metadataProcessor = workerProxy(() => ports.metadataProcessor); + + ports.input.start(); + ports.metadataProcessor.start(); + + // Contextualize + await input.contextualize(cachedTracks); + + // List + const tracks = await input.list(cachedTracks); + + // Fetch metadata if needed + const tracksWithMetadata = await tracks.reduce( + /** + * @param {Promise} promise + * @param {Track} track + */ + async (promise, track) => { + const acc = await promise; + if (track.tags && track.stats) return [...acc, track]; + + const resGet = await input.resolve({ + method: "GET", + uri: track.uri, + }); + + if (!resGet) return [...acc, track]; + + const resHead = "stream" in resGet ? undefined : await input.resolve({ + method: "HEAD", + uri: track.uri, + }); + + const { stats, tags } = await metadataProcessor.supply({ + stream: "stream" in resGet ? resGet.stream : undefined, + urls: "url" in resGet + ? { + get: resGet.url, + head: resHead && "url" in resHead ? resHead.url : resGet.url, + } + : undefined, + }); + + return [...acc, { ...track, stats, tags }]; + }, + Promise.resolve([]), + ); + + // Changed? + const diff = deepDiff.diff(tracksWithMetadata, cachedTracks); + const changed = !!diff; + + // Save if changed + if (changed) return tracksWithMetadata; + return null; +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + rpc(context, { process }); +}); diff --git a/src/components/orchestrator/queue-audio/element.js b/src/components/orchestrator/queue-audio/element.js new file mode 100644 index 000000000..f8fe9bdf5 --- /dev/null +++ b/src/components/orchestrator/queue-audio/element.js @@ -0,0 +1,113 @@ +import { BroadcastableDiffuseElement, query } from "@common/element.js"; +import { untracked } from "@common/signal.js"; + +/** + * @import {InputElement} from "@components/input/types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * When the active queue item changes, + * coordinate the audio engine accordingly. + * + * Vice versa, when the audio ends, + * shift the queue if needed. + */ +class QueueAudioOrchestrator extends BroadcastableDiffuseElement { + /** + * @override + */ + async connectedCallback() { + // Broadcast if needed + if (this.hasAttribute("group")) { + this.broadcast(this.nameWithGroup, {}); + } + + // Super + super.connectedCallback(); + + /** @type {InputElement} */ + this.input = query(this, "input-selector"); + + /** @type {import("@components/engine/audio/element.js").CLASS} */ + this.audio = query(this, "audio-engine-selector"); + + /** @type {import("@components/engine/queue/element.js").CLASS} */ + this.queue = query(this, "queue-engine-selector"); + + // Wait until defined + await customElements.whenDefined(this.audio.localName); + await customElements.whenDefined(this.input.localName); + await customElements.whenDefined(this.queue.localName); + + // Effects + this.effect(() => this.monitorActiveQueueItem()); + this.effect(() => this.monitorAudioEnd()); + } + + // ๐Ÿ› ๏ธ + + async monitorActiveQueueItem() { + if (!this.audio) return; + if (!this.input) return; + if (!this.queue) return; + + const activeTrack = this.queue.now(); + if ((await this.isLeader()) === false) return; + + const isPlaying = untracked(this.audio.isPlaying); + + // Resolve URIs + const resolvedUri = activeTrack + ? await this.input.resolve({ method: "GET", uri: activeTrack.uri }) + : undefined; + + if (resolvedUri && "stream" in resolvedUri) { + throw new Error("Streams are not supported yet."); + } + + const url = resolvedUri?.url; + + // Check if we still need to render + if (this.queue.now?.()?.id !== activeTrack?.id) return; + + // Play new active queue item + // TODO: Take URL expiration timestamp into account + // TODO: Preload next queue item + this.audio.supply({ + audio: activeTrack && url + ? [{ + id: activeTrack.id, + isPreload: false, + url, + }] + // TODO: Keep preloads + : [], + play: activeTrack && isPlaying ? { audioId: activeTrack.id } : undefined, + }); + } + + async monitorAudioEnd() { + if (!this.audio) return; + if (!this.queue) return; + + const now = this.queue.now(); + const aud = now ? this.audio.state(now.id) : undefined; + + if (aud?.hasEnded() && (await this.isLeader())) await this.queue.shift(); + } +} + +export default QueueAudioOrchestrator; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = QueueAudioOrchestrator; +export const NAME = "do-queue-audio"; + +customElements.define(NAME, QueueAudioOrchestrator); diff --git a/src/components/orchestrator/queue-tracks/element.js b/src/components/orchestrator/queue-tracks/element.js new file mode 100644 index 000000000..b9d40f43a --- /dev/null +++ b/src/components/orchestrator/queue-tracks/element.js @@ -0,0 +1,111 @@ +import { BroadcastableDiffuseElement, query } from "@common/element.js"; +import { untracked } from "@common/signal.js"; + +/** + * @import {Track} from "@definitions/types.d.ts" + * @import {ProxiedActions} from "@common/worker.d.ts" + * @import {InputElement} from "@components/input/types.d.ts" + * @import {OutputElement} from "@components/output/types.d.ts" + * + * @import {Actions} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * Update the queue pool whenever + * tracks have been loaded, + * or the tracks collection changes. + */ +class QueueTracksOrchestrator extends BroadcastableDiffuseElement { + static NAME = "diffuse/orchestrator/queue-tracks"; + static WORKER_URL = "components/orchestrator/queue-tracks/worker.js"; + + /** @type {ProxiedActions} */ + #proxy; + + constructor() { + super(); + this.#proxy = this.workerProxy({ + forceNew: { + dependencies: { + input: true, + }, + }, + }); + } + + // LIFECYCLE + + /** + * @override + */ + async connectedCallback() { + // Broadcast if needed + if (this.hasAttribute("group")) { + this.broadcast(this.nameWithGroup, {}); + } + + // Super + super.connectedCallback(); + + /** @type {InputElement} */ + const input = query(this, "input-selector"); + + /** @type {OutputElement} */ + const output = query(this, "output-selector"); + + /** @type {import("@components/engine/queue/element.js").CLASS} */ + const queue = query(this, "queue-engine-selector"); + + // Assign to self + this.input = input; + this.output = output; + this.queue = queue; + + // When defined + await customElements.whenDefined(this.input.localName); + await customElements.whenDefined(this.output.localName); + await customElements.whenDefined(this.queue.localName); + + // Watch tracks collection + this.effect(() => { + const tracks = output.tracks.collection(); + + this.isLeader().then((isLeader) => { + if (!isLeader) return; + untracked(() => this.#proxy.poolAvailable(tracks)); + }); + }); + + // ๐ŸŒธ + } + + // WORKERS + + /** + * @override + */ + dependencies() { + if (!this.input) throw new Error("Input element not defined yet"); + if (!this.queue) throw new Error("Queue element not defined yet"); + + return { + input: this.input, + queue: this.queue, + }; + } +} + +export default QueueTracksOrchestrator; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = QueueTracksOrchestrator; +export const NAME = "do-queue-tracks"; + +customElements.define(NAME, QueueTracksOrchestrator); diff --git a/src/components/orchestrator/queue-tracks/types.d.ts b/src/components/orchestrator/queue-tracks/types.d.ts new file mode 100644 index 000000000..95a112b84 --- /dev/null +++ b/src/components/orchestrator/queue-tracks/types.d.ts @@ -0,0 +1,5 @@ +import type { Track } from "@definitions/types.d.ts"; + +export type Actions = { + poolAvailable(tracks: Track[]): Promise; +}; diff --git a/src/components/orchestrator/queue-tracks/worker.js b/src/components/orchestrator/queue-tracks/worker.js new file mode 100644 index 000000000..c1d54cafe --- /dev/null +++ b/src/components/orchestrator/queue-tracks/worker.js @@ -0,0 +1,51 @@ +import { ostiary, rpc, workerProxy } from "@common/worker.js"; + +/** + * @import {Track} from "@definitions/types.d.ts" + * @import {ActionsWithTunnel, ProxiedActions} from "@common/worker.d.ts" + * @import {InputActions} from "@components/input/types.d.ts" + * @import {Actions as QueueEngineActions} from "@components/engine/queue/types.d.ts" + * @import {Actions} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {ActionsWithTunnel["poolAvailable"]} + */ +export async function poolAvailable({ data, ports }) { + const cachedTracks = data.filter((t) => t.kind !== "placeholder"); + + /** @type {ProxiedActions} */ + const input = workerProxy(() => ports.input); + + /** @type {ProxiedActions} */ + const queue = workerProxy(() => ports.queue); + + ports.input.start(); + ports.queue.start(); + + // Consult input + const groups = await input.groupConsult(cachedTracks); + + /** @type {Track[]} */ + let availableTracks = []; + + Object.values(groups).forEach((value) => { + if (value.available === false) return; + availableTracks = availableTracks.concat(value.tracks); + }, []); + + // Set pool + await queue.pool(availableTracks); +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + rpc(context, { poolAvailable }); +}); diff --git a/src/components/orchestrator/search-tracks/element.js b/src/components/orchestrator/search-tracks/element.js new file mode 100644 index 000000000..f936869cf --- /dev/null +++ b/src/components/orchestrator/search-tracks/element.js @@ -0,0 +1,103 @@ +import { BroadcastableDiffuseElement, query } from "@common/element.js"; + +/** + * @import {Track} from "@definitions/types.d.ts" + * @import {ProxiedActions} from "@common/worker.d.ts" + * @import {InputElement} from "@components/input/types.d.ts" + * @import {OutputElement} from "@components/output/types.d.ts" + * + * @import {Actions} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * Fill the search supply automatically with + * tracks whenever they have been loaded, + * or the tracks collection changes. + */ +class SearchTracksOrchestrator extends BroadcastableDiffuseElement { + static NAME = "diffuse/orchestrator/search-tracks"; + static WORKER_URL = "components/orchestrator/search-tracks/worker.js"; + + /** @type {ProxiedActions} */ + #proxy; + + constructor() { + super(); + this.#proxy = this.workerProxy({ + forceNew: { + dependencies: { + input: true, + }, + }, + }); + } + + // LIFECYCLE + + /** + * @override + */ + async connectedCallback() { + // Broadcast if needed + if (this.hasAttribute("group")) { + this.broadcast(this.nameWithGroup, {}); + } + + // Super + super.connectedCallback(); + + /** @type {InputElement} */ + const input = query(this, "input-selector"); + + /** @type {OutputElement} */ + const output = query(this, "output-selector"); + + /** @type {import("@components/processor/search/element.js").CLASS} */ + const search = query(this, "search-processor-selector"); + + // Assign to self + this.input = input; + this.output = output; + this.search = search; + + // When defined + await customElements.whenDefined(this.output.localName); + + // Watch tracks collection + this.effect(async () => { + const collection = output.tracks.collection(); + if ((await this.isLeader()) === false) return; + this.#proxy.supplyAvailable(collection); + }); + } + + // WORKERS + + /** + * @override + */ + dependencies() { + if (!this.input) throw new Error("Input element not defined yet"); + if (!this.search) throw new Error("Search element not defined yet"); + + return { + input: this.input, + search: this.search, + }; + } +} + +export default SearchTracksOrchestrator; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = SearchTracksOrchestrator; +export const NAME = "do-search-tracks"; + +customElements.define(NAME, SearchTracksOrchestrator); diff --git a/src/components/orchestrator/search-tracks/types.d.ts b/src/components/orchestrator/search-tracks/types.d.ts new file mode 100644 index 000000000..c9cc6bc16 --- /dev/null +++ b/src/components/orchestrator/search-tracks/types.d.ts @@ -0,0 +1,5 @@ +import type { Track } from "@definitions/types.d.ts"; + +export type Actions = { + supplyAvailable(tracks: Track[]): Promise; +}; diff --git a/src/components/orchestrator/search-tracks/worker.js b/src/components/orchestrator/search-tracks/worker.js new file mode 100644 index 000000000..7fdd9dd88 --- /dev/null +++ b/src/components/orchestrator/search-tracks/worker.js @@ -0,0 +1,51 @@ +import { ostiary, rpc, workerProxy } from "@common/worker.js"; + +/** + * @import {Track} from "@definitions/types.d.ts" + * @import {ActionsWithTunnel, ProxiedActions} from "@common/worker.d.ts" + * @import {InputActions} from "@components/input/types.d.ts" + * @import {Actions as SearchProcessorActions} from "@components/processor/search/types.d.ts" + * @import {Actions} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {ActionsWithTunnel["supplyAvailable"]} + */ +export async function supplyAvailable({ data, ports }) { + const cachedTracks = data.filter((t) => t.kind !== "placeholder"); + + /** @type {ProxiedActions} */ + const input = workerProxy(() => ports.input); + + /** @type {ProxiedActions} */ + const search = workerProxy(() => ports.search); + + ports.input.start(); + ports.search.start(); + + // Consult input + const groups = await input.groupConsult(cachedTracks); + + /** @type {Track[]} */ + let availableTracks = []; + + Object.values(groups).forEach((value) => { + if (value.available === false) return; + availableTracks = availableTracks.concat(value.tracks); + }, []); + + // Set pool + await search.supply({ tracks: availableTracks }); +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + rpc(context, { supplyAvailable }); +}); diff --git a/src/components/output/common.js b/src/components/output/common.js new file mode 100644 index 000000000..b522d0ff8 --- /dev/null +++ b/src/components/output/common.js @@ -0,0 +1,35 @@ +import { effect, signal } from "@common/signal.js"; + +/** + * @import {OutputManager, OutputManagerProperties} from "./types.d.ts" + */ + +/** + * @template Tracks + * @param {OutputManagerProperties} _ + * @returns {OutputManager} + */ +export function outputManager({ init, tracks }) { + const t = signal(/** @type {Tracks} */ (tracks.empty())); + const ts = signal(/** @type {"loading" | "loaded"} */ ("loading")); + + async function loadTracks() { + if (init && (await init()) === false) return; + t.value = await tracks.get(); + ts.value = "loaded"; + } + + effect(loadTracks); + + return { + tracks: { + collection: t.get, + reload: loadTracks, + save: async (newTracks) => { + t.value = newTracks; + await tracks.put(newTracks); + }, + state: ts.get, + }, + }; +} diff --git a/src/components/output/polymorphic/indexed-db/constants.js b/src/components/output/polymorphic/indexed-db/constants.js new file mode 100644 index 000000000..f40032dab --- /dev/null +++ b/src/components/output/polymorphic/indexed-db/constants.js @@ -0,0 +1 @@ +export const IDB_PREFIX = "diffuse/output/polymorphic/indexed-db"; diff --git a/src/components/output/polymorphic/indexed-db/element.js b/src/components/output/polymorphic/indexed-db/element.js new file mode 100644 index 000000000..650727e86 --- /dev/null +++ b/src/components/output/polymorphic/indexed-db/element.js @@ -0,0 +1,60 @@ +import { DiffuseElement } from "@common/element.js"; +import { outputManager } from "../../common.js"; + +/** + * @import {ProxiedActions} from "@common/worker.d.ts" + * @import {OutputManager, OutputWorkerActions} from "../../types.d.ts" + * @import {SupportedDataTypes} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {OutputManager} + */ +class IndexedDBOutput extends DiffuseElement { + static NAME = "diffuse/output/polymorphic/indexed-db"; + static WORKER_URL = "components/output/polymorphic/indexed-db/worker.js"; + + constructor() { + super(); + + /** @type {ProxiedActions>} */ + const p = this.workerProxy(); + + /** @type {OutputManager} */ + const manager = outputManager({ + init: this.whenConnected.bind(this), + tracks: { + empty: () => undefined, + get: () => p.get({ name: this.#cat("tracks") }), + put: (data) => p.put({ name: this.#cat("tracks"), data }), + }, + }); + + this.tracks = manager.tracks; + } + + // ๐Ÿ› ๏ธ + + /** @param {string} name */ + #cat(name) { + const namespace = this.hasAttribute("namespace") + ? this.getAttribute("namespace") + "/" + : ""; + return `${namespace}${name}`; + } +} + +export default IndexedDBOutput; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = IndexedDBOutput; +export const NAME = "dop-indexed-db"; + +customElements.define(NAME, IndexedDBOutput); diff --git a/src/components/output/polymorphic/indexed-db/types.d.ts b/src/components/output/polymorphic/indexed-db/types.d.ts new file mode 100644 index 000000000..0c35ba7e5 --- /dev/null +++ b/src/components/output/polymorphic/indexed-db/types.d.ts @@ -0,0 +1 @@ +export type SupportedDataTypes = any; diff --git a/src/components/output/polymorphic/indexed-db/worker.js b/src/components/output/polymorphic/indexed-db/worker.js new file mode 100644 index 000000000..2c2b03865 --- /dev/null +++ b/src/components/output/polymorphic/indexed-db/worker.js @@ -0,0 +1,37 @@ +import * as IDB from "idb-keyval"; + +import { IDB_PREFIX } from "./constants.js"; +import { ostiary, rpc } from "@common/worker.js"; + +/** + * @import {OutputWorkerActions} from "@components/output/types.d.ts"; + * @import {SupportedDataTypes} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {OutputWorkerActions["get"]} + */ +export async function get({ name }) { + return await IDB.get(`${IDB_PREFIX}/${name}`); +} + +/** + * @type {OutputWorkerActions["put"]} + */ +export async function put({ data, name }) { + return await IDB.set(`${IDB_PREFIX}/${name}`, data); +} +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + rpc(context, { + get, + put, + }); +}); diff --git a/src/components/output/types.d.ts b/src/components/output/types.d.ts new file mode 100644 index 000000000..5f7bf2795 --- /dev/null +++ b/src/components/output/types.d.ts @@ -0,0 +1,27 @@ +import type { SignalReader } from "@common/signal.d.ts"; +import type { DiffuseElement } from "@common/element.js"; + +export type OutputElement = DiffuseElement & OutputManager; + +export type OutputManager = { + tracks: { + collection: SignalReader; + reload: () => Promise; + save: (tracks: Tracks) => Promise; + state: SignalReader<"loading" | "loaded">; + }; +}; + +export type OutputManagerProperties = { + init?: () => Promise; + tracks: { + empty(): Tracks; + get(): Promise; + put(tracks: Tracks): Promise; + }; +}; + +export type OutputWorkerActions = { + get(args: { name: string }): Promise; + put(args: { data: DataType; name: string }): Promise; +}; diff --git a/src/components/processor/artwork/constants.js b/src/components/processor/artwork/constants.js new file mode 100644 index 000000000..1bda0811d --- /dev/null +++ b/src/components/processor/artwork/constants.js @@ -0,0 +1,2 @@ +export const IDB_PREFIX = "@components/processor/artwork"; +export const IDB_ARTWORK_PREFIX = `${IDB_PREFIX}/cache`; diff --git a/src/components/processor/artwork/element.js b/src/components/processor/artwork/element.js new file mode 100644 index 000000000..06780fc15 --- /dev/null +++ b/src/components/processor/artwork/element.js @@ -0,0 +1,39 @@ +import { DiffuseElement } from "@common/element.js"; + +/** + * @import {ProxiedActions} from "@common/worker.d.ts" + * @import {Actions} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {ProxiedActions} + */ +class ArtworkProcessor extends DiffuseElement { + static NAME = "diffuse/processor/artwork"; + static WORKER_URL = "components/processor/artwork/worker.js"; + + constructor() { + super(); + + /** @type {ProxiedActions} */ + const p = this.workerProxy(); + + this.artwork = p.artwork; + this.supply = p.supply; + } +} + +export default ArtworkProcessor; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = ArtworkProcessor; +export const NAME = "dp-artwork"; + +customElements.define(NAME, ArtworkProcessor); diff --git a/src/components/processor/artwork/types.d.ts b/src/components/processor/artwork/types.d.ts new file mode 100644 index 000000000..5cecdcdee --- /dev/null +++ b/src/components/processor/artwork/types.d.ts @@ -0,0 +1,26 @@ +import type { TrackTags } from "@definitions/types.d.ts"; + +export type Actions = { + artwork(request: ArtworkRequest): Promise; + supply(items: ArtworkRequest[]): void; +}; + +export type Artwork = { + bytes: Uint8Array; + mime: string; +}; + +export type ArtworkRequest = { + cacheId: string; + mimeType?: string; + stream?: ReadableStream; + tags?: Tags; + urls?: Urls; + variousArtists?: boolean; +}; + +// export type State = { +// artwork: Record; +// }; + +export type Urls = { get: string; head: string }; diff --git a/src/components/processor/artwork/worker.js b/src/components/processor/artwork/worker.js new file mode 100644 index 000000000..29eff824a --- /dev/null +++ b/src/components/processor/artwork/worker.js @@ -0,0 +1,270 @@ +import * as IDB from "idb-keyval"; + +import { IDB_ARTWORK_PREFIX } from "./constants.js"; +import { musicMetadataTags } from "../metadata/common.js"; +import { ostiary, rpc } from "@common/worker.js"; + +/** + * @import {IPicture} from "music-metadata" + * @import {Actions, Artwork, ArtworkRequest} from "./types.d.ts" + * @import {Extraction} from "../metadata/types.d.ts" + */ + +/** + * @type {ArtworkRequest[]} + */ +let queue = []; + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {Actions['artwork']} + */ +export async function artwork(request) { + const art = await processRequest(request); + return art; +} + +/** + * @type {Actions['supply']} + */ +export function supply(items) { + const exe = !queue[0]; + queue = [...queue, ...items]; + if (exe) shiftQueue(); +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + rpc(context, { + artwork, + supply, + }); +}); + +//////////////////////////////////////////// +// ๐Ÿ› ๏ธ +//////////////////////////////////////////// + +/** + * @param {string} str + */ +function escapeLucene(str) { + return [].map + .call(str, (char) => { + if ( + char === "+" || + char === "-" || + char === "&" || + char === "|" || + char === "!" || + char === "(" || + char === ")" || + char === "{" || + char === "}" || + char === "[" || + char === "]" || + char === "^" || + char === '"' || + char === "~" || + char === "*" || + char === "?" || + char === ":" || + char === "\\" || + char === "/" + ) { + return "\\" + char; + } else return char; + }) + .join(""); +} + +/** + * @param {ArtworkRequest} req + * @returns {Promise} + */ +async function lastFm(req) { + if (!navigator.onLine) return []; + + const query = req.tags?.artist; + + return await fetch( + `https://ws.audioscrobbler.com/2.0/?method=album.search&album=${query}&api_key=4f0fe85b67baef8bb7d008a8754a95e5&format=json`, + ) + .then((r) => r.json()) + .then((r) => lastFmCover(r.results.albummatches.album)) + .catch((err) => { + console.error(err); + return []; + }); +} + +/** + * @param {any[]} remainingMatches + * @returns {Promise} + */ +async function lastFmCover(remainingMatches) { + const album = remainingMatches[0]; + const url = album ? album.image[album.image.length - 1]["#text"] : null; + + return url && url !== "" + ? await fetch(url) + .then((r) => r.blob()) + .then(async (b) => [ + { + bytes: await b.arrayBuffer().then((buf) => new Uint8Array(buf)), + mime: b.type, + }, + ]) + .catch((err) => { + console.error(err); + return lastFmCover(remainingMatches.slice(1)); + }) + : album + ? lastFmCover(remainingMatches.slice(1)) + : []; +} + +/** + * @param {ArtworkRequest} req + * @returns {Promise} + */ +async function musicBrainz(req) { + const artist = req.tags?.artist; + const album = req.tags?.album; + + if (!navigator.onLine) return []; + if (!album && !artist) return []; + + const query = `release:"${escapeLucene(album || "")}"` + + (req.variousArtists + ? `` + : ` AND artistname:"${escapeLucene(artist || "")}"`); + const encodedQuery = encodeURIComponent(query); + + return await fetch( + `https://musicbrainz.org/ws/2/release/?query=${encodedQuery}&fmt=json`, + ) + .then((r) => r.json()) + .then((r) => { + if (r.releases.length === 0 && !req.variousArtists) { + return musicBrainz({ ...req, variousArtists: true }); + } else { + return musicBrainzCover(r.releases, req); + } + }) + .catch((err) => { + console.error(err); + return []; + }); +} + +/** + * @param {any[]} remainingReleases + * @param {ArtworkRequest} req + * @returns {Promise} + */ +async function musicBrainzCover(remainingReleases, req) { + const release = remainingReleases[0]; + if (!release) return []; + + const credit = release?.["artist-credit"]?.[0]?.name; + if ( + req.variousArtists && credit !== "Various Artists" && + credit !== req.tags?.artist + ) return []; + + return await fetch( + `https://coverartarchive.org/release/${release.id}/front-1200`, + ) + .then((r) => r.blob()) + .then(async (b) => { + if (b.type.startsWith("image/")) { + return [{ + bytes: await b.arrayBuffer().then((buf) => new Uint8Array(buf)), + mime: b.type, + }]; + } else { + return musicBrainzCover(remainingReleases.slice(1), req); + } + }) + .catch((err) => { + console.error(err); + return musicBrainzCover(remainingReleases.slice(1), req); + }); +} + +/** + * @param {ArtworkRequest} req + * @returns {Promise} + */ +async function processRequest(req) { + // Check if already processed + // TODO: Retry if none was found? + const cache = await IDB.get(`${IDB_ARTWORK_PREFIX}/${req.cacheId}`); + if (cache && Array.isArray(cache) && cache.length) return cache; + + // Request override + if (req.tags?.artist?.toUpperCase() === "VA") { + req.variousArtists = true; + } + + // ๐Ÿš€ + + /** @type {Artwork[]} */ + let art = []; + + // Get metadata + possible artwork from file metadata + const meta = await musicMetadataTags({ ...req, includeArtwork: true }).catch( + /** @param {Error} err */ (err) => { + console.error("music-metadata error", err); + /** @type {Extraction} */ + const extraction = {}; + return extraction; + }, + ); + + if (!req.tags && meta.tags) req.tags = meta.tags; + + // Add artwork from metadata + const fromMeta = meta.artwork?.map( + /** + * @param {IPicture} a + */ + (a) => { + return { bytes: a.data, mime: a.format }; + }, + ) || []; + + art.push(...fromMeta); + + // If no artwork, try finding it on other sources + if (art.length === 0) { + const fromMusicBrainz = await musicBrainz(req); + art.push(...fromMusicBrainz); + } + + if (art.length === 0) { + const fromLastFm = await lastFm(req); + art.push(...fromLastFm); + } + + // Save artwork to IDB + await IDB.set(`${IDB_ARTWORK_PREFIX}/${req.cacheId}`, art); + + // Fin + return art; +} + +async function shiftQueue() { + const next = queue.shift(); + if (!next) return; + + await processRequest(next); + await shiftQueue(); +} diff --git a/src/components/processor/metadata/common.js b/src/components/processor/metadata/common.js new file mode 100644 index 000000000..31f25ed6b --- /dev/null +++ b/src/components/processor/metadata/common.js @@ -0,0 +1,143 @@ +import { parseBlob, parseFromTokenizer, parseWebStream } from "music-metadata"; +import * as URI from "uri-js"; +import { HttpClient } from "@tokenizer/http"; +import { tokenizer as rangeTokenizer } from "@tokenizer/range"; + +/** + * @import { TrackStats, TrackTags } from "@definitions/types.d.ts"; + * @import { Extraction, Urls } from "./types.d.ts"; + */ + +// ๐Ÿ› ๏ธ + +/** + * @param {{ includeArtwork?: boolean; mimeType?: string; stream?: ReadableStream; urls?: Urls; }} _ + * @returns {Promise} + */ +export async function musicMetadataTags({ + includeArtwork, + mimeType, + stream, + urls, +}) { + const uri = urls ? URI.parse(urls.get) : undefined; + const pathParts = uri?.path?.split("/"); + const filename = pathParts?.[pathParts.length - 1]; + + let meta; + + if (urls?.get.startsWith("blob:")) { + const blob = await fetch(urls.get).then((r) => r.blob()); + meta = await parseBlob(blob, { skipCovers: !includeArtwork }); + } else if (urls) { + const httpClient = new HttpClient(urls.head, { + resolveUrl: false, + }); + httpClient.resolvedUrl = urls.get; + const getHeadInfo = httpClient.getHeadInfo; + + // FUCKAROUND: Not sure of the downsides of this + httpClient.getHeadInfo = async () => { + const info = await getHeadInfo.call(httpClient); + return { ...info, acceptPartialRequests: true }; + }; + + /** @type {any} */ + const tokenizer = await rangeTokenizer(httpClient); + meta = await parseFromTokenizer(tokenizer, { skipCovers: !includeArtwork }); + } else if (stream) { + meta = await parseWebStream(stream, { mimeType }, { + skipCovers: !includeArtwork, + }); + } else { + throw new Error("Missing args, need either some urls or a stream."); + } + + /** @type {TrackStats} */ + const stats = { + albumGain: meta.format.albumGain, + bitrate: meta.format.bitrate, + bitsPerSample: meta.format.bitsPerSample, + codec: meta.format.codec, + container: meta.format.container, + duration: meta.format.duration, + lossless: meta.format.lossless, + numberOfChannels: meta.format.numberOfChannels, + sampleRate: meta.format.sampleRate, + trackGain: meta.format.trackGain, + }; + + /** @type {TrackTags} */ + const tags = { + album: meta.common.album, + albumartist: meta.common.albumartist, + albumartists: Array.isArray(meta.common.albumartist) + ? meta.common.albumartist + : (meta.common.albumartist ? [meta.common.albumartist] : undefined), + albumartistsort: meta.common.albumartistsort, + albumsort: meta.common.albumsort, + arranger: meta.common.arranger, + artist: meta.common.artist, + artists: meta.common.artists ?? + (meta.common.artist ? [meta.common.artist] : []), + artistsort: meta.common.artistsort, + asin: meta.common.asin, + averageLevel: meta.common.averageLevel, + barcode: meta.common.barcode, + bpm: meta.common.bpm, + catalognumbers: meta.common.catalognumber, + compilation: meta.common.compilation, + composers: meta.common.composer, + composersort: meta.common.composersort, + conductors: meta.common.conductor, + date: meta.common.date, + disc: { + no: meta.common.disk.no || 1, + of: meta.common.disk.of ?? undefined, + }, + djmixers: meta.common.djmixer, + engineers: meta.common.engineer, + gapless: meta.common.gapless, + genres: Array.isArray(meta.common.genre) + ? meta.common.genre + : meta.common.genre + ? [meta.common.genre] + : undefined, + isrc: meta.common.isrc, + labels: meta.common.label, + lyricists: meta.common.lyricist, + media: meta.common.media, + mixers: meta.common.mixer, + moods: Array.isArray(meta.common.mood) + ? meta.common.mood + : meta.common.mood + ? [meta.common.mood] + : undefined, + originaldate: meta.common.originaldate, + originalyear: meta.common.originalyear, + peakLevel: meta.common.peakLevel, + producers: meta.common.producer, + publishers: meta.common.publisher, + releasecountry: meta.common.releasecountry, + releasedate: meta.common.releasedate, + releasestatus: meta.common.releasestatus, + releasetypes: meta.common.releasetype, + remixers: meta.common.remixer, + technicians: meta.common.technician, + title: meta.common.title || filename || urls?.head || "Unknown", + titlesort: meta.common.titlesort, + track: { + no: meta.common.track.no || 1, + of: meta.common.track.of ?? undefined, + }, + work: meta.common.work, + writers: meta.common.writer, + year: meta.common.year, + }; + + return { + artwork: includeArtwork ? meta.common.picture : undefined, + stats, + tags, + }; +} diff --git a/src/components/processor/metadata/element.js b/src/components/processor/metadata/element.js new file mode 100644 index 000000000..cb0cc2223 --- /dev/null +++ b/src/components/processor/metadata/element.js @@ -0,0 +1,39 @@ +import { DiffuseElement } from "@common/element.js"; + +/** + * @import {ProxiedActions} from "@common/worker.d.ts" + * @import {Actions} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {ProxiedActions} + */ +class MetadataProcessor extends DiffuseElement { + static NAME = "diffuse/processor/metadata"; + static WORKER_URL = "components/processor/metadata/worker.js"; + + constructor() { + super(); + + /** @type {ProxiedActions} */ + const p = this.workerProxy(); + + // Worker proxy + this.supply = p.supply; + } +} + +export default MetadataProcessor; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = MetadataProcessor; +export const NAME = "dp-metadata"; + +customElements.define(NAME, MetadataProcessor); diff --git a/src/components/processor/metadata/types.d.ts b/src/components/processor/metadata/types.d.ts new file mode 100644 index 000000000..3dca18d8f --- /dev/null +++ b/src/components/processor/metadata/types.d.ts @@ -0,0 +1,21 @@ +import type { IPicture } from "music-metadata"; +import type { TrackStats, TrackTags } from "@definitions/types.d.ts"; + +export type Actions = { + supply: ( + args: { + includeArtwork?: boolean; + mimeType?: string; + stream?: ReadableStream; + urls?: Urls; + }, + ) => Promise; +}; + +export type Extraction = { + artwork?: IPicture[]; + stats?: TrackStats; + tags?: TrackTags; +}; + +export type Urls = { get: string; head: string }; diff --git a/src/components/processor/metadata/worker.js b/src/components/processor/metadata/worker.js new file mode 100644 index 000000000..15ceb8639 --- /dev/null +++ b/src/components/processor/metadata/worker.js @@ -0,0 +1,38 @@ +import { ostiary, rpc } from "@common/worker.js"; +import { musicMetadataTags } from "./common.js"; + +/** + * @import { Actions, Extraction } from "./types.d.ts"; + */ + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {Actions['supply']} + */ +export async function supply(args) { + // Construct records + // TODO: Use other metadata lib as fallback: https://github.com/buzz/mediainfo.js + return await musicMetadataTags(args).catch( + /** + * @param {Error} err + * @returns {Extraction} + */ + (err) => { + console.warn("Metadata processor error:", err, args); + return {}; + }, + ); +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + rpc(context, { + supply, + }); +}); diff --git a/src/components/processor/search/constants.js b/src/components/processor/search/constants.js new file mode 100644 index 000000000..b9110a7f3 --- /dev/null +++ b/src/components/processor/search/constants.js @@ -0,0 +1,28 @@ +/** + * Maps directly on the `Track` definition + * (ie. `definitions/output/tracks.json`) + */ +export const SCHEMA = { + id: /** @type {const} */ ("string"), + kind: /** @type {const} */ ("string"), + tags: { + album: /** @type {const} */ ("string"), + artist: /** @type {const} */ ("string"), + genre: /** @type {const} */ ("string"), + title: /** @type {const} */ ("string"), + year: /** @type {const} */ ("number"), + + disc: { + no: /** @type {const} */ ("number"), + }, + track: { + no: /** @type {const} */ ("number"), + }, + }, + + // TODO: + // isFavorite: "boolean" as const, + // inPlaylists: [ ... ], + + embeddings: /** @type {const} */ ("vector[512]"), +}; diff --git a/src/components/processor/search/element.js b/src/components/processor/search/element.js new file mode 100644 index 000000000..237d545ba --- /dev/null +++ b/src/components/processor/search/element.js @@ -0,0 +1,67 @@ +import { DiffuseElement } from "@common/element.js"; +import { signal } from "@common/signal.js"; +import { listen } from "@common/worker.js"; + +/** + * @import {ProxiedActions} from "@common/worker.d.ts"; + * @import {Actions, State} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +/** + * @implements {ProxiedActions} + */ +class SearchProcessor extends DiffuseElement { + static NAME = "diffuse/processor/search"; + static WORKER_URL = "components/processor/search/worker.js"; + + constructor() { + super(); + + /** @type {ProxiedActions} */ + this.proxy = this.workerProxy(); + + this.search = this.proxy.search; + this.supply = this.proxy.supply; + } + + // SIGNALS + + #cacheId = signal(/** @type {string | undefined} */ (undefined)); + + // STATE + + cacheId = this.#cacheId.get; + + // LIFECYCLE + + /** + * @override + */ + connectedCallback() { + super.connectedCallback(); + + // Sync data with worker + const link = this.workerLink(); + + // Listen for remote data changes + listen("cacheId", this.#cacheId.set, link); + + // Fetch current data state + this.proxy.cacheId().then(this.#cacheId.set); + } +} + +export default SearchProcessor; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = SearchProcessor; +export const NAME = "dp-search"; + +customElements.define(NAME, SearchProcessor); diff --git a/src/components/processor/search/types.d.ts b/src/components/processor/search/types.d.ts new file mode 100644 index 000000000..97a981b40 --- /dev/null +++ b/src/components/processor/search/types.d.ts @@ -0,0 +1,19 @@ +import type { Orama, SearchParams } from "@orama/orama"; + +import type { SignalReader } from "@common/signal.d.ts"; +import type { Track } from "@definitions/types.d.ts"; +import type { SCHEMA } from "./constants.js"; + +export type Actions = { + /** + * https://docs.orama.com/docs/orama-js/search + */ + search(params: SearchParams): Promise; + supply(args: { tracks: Track[] }): Promise; +}; + +export type Schema = Orama; + +export type State = { + cacheId: SignalReader; +}; diff --git a/src/components/processor/search/worker.js b/src/components/processor/search/worker.js new file mode 100644 index 000000000..506bf5a08 --- /dev/null +++ b/src/components/processor/search/worker.js @@ -0,0 +1,173 @@ +import * as Orama from "@orama/orama"; +import { xxh32 } from "xxh32"; +// import { pluginQPS } from "@orama/plugin-qps"; + +import { SCHEMA } from "./constants.js"; +import { announce, ostiary, rpc } from "@common/worker.js"; +import { effect, signal } from "@common/signal.js"; + +/** + * @import {SearchParams} from "@orama/orama"; + * + * @import {Track} from "@definitions/types.d.ts" + * @import {Actions, Schema} from "./types.d.ts" + */ + +//////////////////////////////////////////// +// STATE +//////////////////////////////////////////// + +export const $inserted = signal(/** @type {Set} */ (new Set())); + +// Communicated state +export const $cacheId = signal(/** @type {string | undefined} */ (undefined)); + +//////////////////////////////////////////// +// DATABASE +//////////////////////////////////////////// + +// TODO: +// * pluginEmbeddings +// * pluginQPS + +/** + * @type {Orama.OramaPlugin[]} + */ +const PLUGINS = []; + +const db = Orama.create({ + schema: SCHEMA, + plugins: PLUGINS, + // components: { + // TODO: + // https://docs.orama.com/open-source/usage/insert#remote-document-storing + // documentStore: { ... } + // }, +}); + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +/** + * @type {Actions['search']} + */ +export async function search(params) { + return await _search( + "term" in params && typeof params.term === "string" + ? { ...params, term: params.term.trim() } + : params, + [], + ); +} + +/** + * @type {Actions['supply']} + */ +export async function supply({ tracks }) { + // TODO: Generate a hash based on the track itself, + // so we can detect changes to tags or other data. + + /** @type {string[]} */ + const ids = []; + + /** @type {Record} */ + const tracksMap = {}; + + tracks.forEach((track) => { + ids.push(track.id); + tracksMap[track.id] = track; + }); + + const currentSet = $inserted.value; + const newSet = new Set(ids); + + const removedIds = currentSet.difference(newSet); + const newIds = newSet.difference(currentSet); + const newTracks = Array.from(newIds).map((id) => tracksMap[id]); + + await Orama.removeMultiple(db, Array.from(removedIds)); + await Orama.insertMultiple(db, newTracks); + + $inserted.value = newSet; + $cacheId.value = ids.length === 0 + ? undefined + : xxh32(ids.sort().join("")).toString(); +} + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +ostiary((context) => { + rpc(context, { + search, + supply, + + // State + cacheId: $cacheId.get, + }); + + // Effects + + // Communicate state + effect(() => announce("cacheId", $cacheId.value, context)); +}); + +//////////////////////////////////////////// +// โ›”๏ธ +//////////////////////////////////////////// + +/** + * @param {SearchParams} params + * @param {Track[]} tracks + */ +async function _search(params, tracks) { + const results = await Orama.search(db, { + // @ts-ignore: No clue what the correct type is for this one + sortBy, + ...params, + // mode: "hybrid", + limit: 10000, + offset: tracks.length, + }); + + const allTracks = tracks.concat( + results.hits.map(( + hit, + ) => /** @type {Track} */ (/** @type {unknown} */ (hit.document))), + ); + + if (allTracks.length < results.count) { + return await _search(params, allTracks); + } else { + return allTracks; + } +} + +/** + * @type {Orama.CustomSorterFunction>} + */ +function sortBy(a, b) { + const artist = (a[2].tags?.artist ?? "").localeCompare( + b[2].tags?.artist ?? "", + ); + if (artist != 0) return artist; + + const album = (a[2].tags?.album ?? "").localeCompare( + b[2].tags?.album ?? "", + ); + if (album != 0) return album; + + const discNo = (a[2].tags?.disc?.no ?? 0) - (b[2].tags?.disc?.no ?? 0); + if (discNo != 0) return discNo; + + const trackNo = (a[2].tags?.track?.no ?? 0) - (b[2].tags?.track?.no ?? 0); + if (trackNo != 0) return trackNo; + + const title = (a[2].tags?.title ?? "").localeCompare( + b[2].tags?.title ?? "", + ); + if (title != 0) return title; + return 0; +} diff --git a/src/components/transformer/output/base.js b/src/components/transformer/output/base.js new file mode 100644 index 000000000..5716e96bc --- /dev/null +++ b/src/components/transformer/output/base.js @@ -0,0 +1,65 @@ +import { DiffuseElement, query } from "@common/element.js"; +import { computed, signal } from "@common/signal.js"; + +/** + * @import { OutputElement, OutputManager } from "../../output/types.d.ts" + */ + +/** + * @template T + */ +export class OutputTransformer extends DiffuseElement { + // SIGNALS + + #output = signal(/** @type {OutputElement | undefined} */ (undefined)); + #outputWhenDefined = Promise.withResolvers(); + + output = { + whenDefined: this.#outputWhenDefined.promise, + signal: this.#output.get, + }; + + // LIFECYCLE + + /** + * @override + */ + connectedCallback() { + super.connectedCallback(); + + /** @type {OutputElement} */ + const output = query(this, "output-selector"); + + // When defined + customElements.whenDefined(output.localName).then(() => { + this.#output.value = output; + this.#outputWhenDefined.resolve(null); + }); + } + + // MANAGER + + base() { + /** @type {OutputManager} */ + const m = { + tracks: { + collection: computed(() => { + return this.output.signal()?.tracks?.collection(); + }), + reload: () => { + return this.output.signal()?.tracks?.reload() ?? Promise.resolve(); + }, + save: async (newTracks) => { + if (newTracks === undefined) return; + await this.output.whenDefined; + await this.output.signal()?.tracks.save(newTracks); + }, + state: computed(() => { + return this.output.signal()?.tracks.state() ?? "loading" + }), + }, + }; + + return m; + } +} diff --git a/src/components/transformer/output/refiner/default/element.js b/src/components/transformer/output/refiner/default/element.js new file mode 100644 index 000000000..716dc9416 --- /dev/null +++ b/src/components/transformer/output/refiner/default/element.js @@ -0,0 +1,46 @@ +import { computed } from "@common/signal.js"; +import { OutputTransformer } from "../../base.js"; + +/** + * @import { OutputManager } from "../../../../output/types.d.ts" + * @import { Track } from "@definitions/types.d.ts" + */ + +/** + * @extends {OutputTransformer} + */ +class DefaultOutputRefinerTransformer extends OutputTransformer { + constructor() { + super(); + + const base = this.base(); + + /** @type {OutputManager} */ + const manager = { + tracks: { + ...base.tracks, + collection: computed(() => { + return base.tracks.collection() ?? []; + }), + save: async (newTracks) => { + const filtered = newTracks.filter((t) => !t.ephemeral); + await base.tracks.save(filtered); + }, + }, + }; + + // Assign manager properties to class + this.tracks = manager.tracks; + } +} + +export default DefaultOutputRefinerTransformer; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = DefaultOutputRefinerTransformer; +export const NAME = "dtor-default"; + +customElements.define(NAME, CLASS); diff --git a/src/components/transformer/output/string/json/element.js b/src/components/transformer/output/string/json/element.js new file mode 100644 index 000000000..2d5d32f88 --- /dev/null +++ b/src/components/transformer/output/string/json/element.js @@ -0,0 +1,57 @@ +import { computed } from "@common/signal.js"; +import { OutputTransformer } from "../../base.js"; + +/** + * @import { OutputManager } from "../../../../output/types.d.ts" + * @import { Track } from "@definitions/types.d.ts" + */ + +/** + * @extends {OutputTransformer} + */ +class JsonStringOutputTransformer extends OutputTransformer { + constructor() { + super(); + + const base = this.base(); + + /** @type {OutputManager} */ + const manager = { + tracks: { + ...base.tracks, + collection: computed(() => { + let json = base.tracks.collection(); + if (typeof json !== "string") json = "[]" + + // Try parsing JSON + try { + return JSON.parse(json); + } catch (err) { + console.error( + "components/transformer/output/string/json: Failed to parse JSON", + ); + return []; + } + }), + save: async (newTracks) => { + const json = JSON.stringify(newTracks); + await base.tracks.save(json); + }, + }, + }; + + // Assign manager properties to class + this.tracks = manager.tracks; + } +} + +export default JsonStringOutputTransformer; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = JsonStringOutputTransformer; +export const NAME = "dtos-json"; + +customElements.define(NAME, CLASS); diff --git a/src/definitions/index.ts b/src/definitions/index.ts new file mode 100644 index 000000000..483e60a36 --- /dev/null +++ b/src/definitions/index.ts @@ -0,0 +1 @@ +export * as ShDiffuseOutputTracks from "./types/sh/diffuse/output/tracks.ts"; diff --git a/src/definitions/output/tracks.json b/src/definitions/output/tracks.json new file mode 100644 index 000000000..53a43f333 --- /dev/null +++ b/src/definitions/output/tracks.json @@ -0,0 +1,116 @@ +{ + "lexicon": 1, + "id": "sh.diffuse.output.tracks", + "defs": { + "main": { + "type": "record", + "record": { + "type": "object", + "required": ["id", "uri"], + "properties": { + "id": { "type": "string" }, + "createdAt": { "type": "string", "format": "datetime" }, + "ephemeral": { "type": "boolean" }, + "kind": { + "type": "string", + "enum": ["audiobook", "miscellaneous", "music", "placeholder", "podcast"] + }, + "stats": { + "type": "ref", + "ref": "#stats" + }, + "tags": { + "type": "ref", + "ref": "#tags" + }, + "updatedAt": { "type": "string", "format": "datetime" }, + "uri": { + "type": "string", + "description": "This is a 'semi-permanent' URI. Tracks are typically cached so you can't, for example, use an URL that expires in several hours." + } + } + } + }, + "count": { + "type": "object", + "required": ["no"], + "properties": { + "no": { "type": "integer" }, + "of": { "type": "integer" } + } + }, + "stats": { + "type": "object", + "properties": { + "albumGain": { "type": "integer", "description": "Album gain in dB" }, + "bitrate": { "type": "integer", "description": "Bits per second" }, + "bitsPerSample": { "type": "integer", "description": "Bit depth" }, + "codec": { "type": "string", "description": "Compression algorithm" }, + "container": { "type": "string", "description": "Encoding format" }, + "duration": { "type": "integer", "description": "Duration in seconds" }, + "lossless": { "type": "boolean", "description": "Is track lossless" }, + "numberOfChannels": { "type": "integer", "description": "Number of audio channels" }, + "sampleRate": { "type": "integer", "description": "Samples per second" }, + "trackGain": { "type": "integer", "description": "Track gain in dB" } + } + }, + "tags": { + "type": "object", + "properties": { + "album": { "type": "string" }, + "albumartist": { "type": "string" }, + "albumartists": { "type": "array", "items": { "type": "string" } }, + "albumartistsort": { "type": "string" }, + "albumsort": { "type": "string" }, + "arranger": { "type": "array", "items": { "type": "string" } }, + "artist": { "type": "string" }, + "artists": { "type": "array", "items": { "type": "string" } }, + "artistsort": { "type": "string" }, + "asin": { "type": "string" }, + "averageLevel": { "type": "integer" }, + "barcode": { "type": "string" }, + "bpm": { "type": "integer" }, + "catalognumbers": { "type": "array", "items": { "type": "string" } }, + "compilation": { "type": "boolean" }, + "composers": { "type": "array", "items": { "type": "string" } }, + "composersort": { "type": "string" }, + "conductors": { "type": "array", "items": { "type": "string" } }, + "date": { "type": "string" }, + "disc": { + "type": "ref", + "ref": "#count" + }, + "djmixers": { "type": "array", "items": { "type": "string" } }, + "engineers": { "type": "array", "items": { "type": "string" } }, + "gapless": { "type": "boolean" }, + "genres": { "type": "array", "items": { "type": "string" } }, + "isrc": { "type": "array", "items": { "type": "string" } }, + "labels": { "type": "array", "items": { "type": "string" } }, + "lyricists": { "type": "array", "items": { "type": "string" } }, + "media": { "type": "string" }, + "mixers": { "type": "array", "items": { "type": "string" } }, + "moods": { "type": "array", "items": { "type": "string" } }, + "originaldate": { "type": "string" }, + "originalyear": { "type": "integer" }, + "peakLevel": { "type": "integer" }, + "producers": { "type": "array", "items": { "type": "string" } }, + "publishers": { "type": "array", "items": { "type": "string" } }, + "releasecountry": { "type": "string" }, + "releasedate": { "type": "string" }, + "releasestatus": { "type": "string" }, + "releasetypes": { "type": "array", "items": { "type": "string" } }, + "remixers": { "type": "array", "items": { "type": "string" } }, + "technicians": { "type": "array", "items": { "type": "string" } }, + "title": { "type": "string" }, + "titlesort": { "type": "string" }, + "track": { + "type": "ref", + "ref": "#count" + }, + "work": { "type": "string" }, + "writers": { "type": "array", "items": { "type": "string" } }, + "year": { "type": "integer" } + } + } + } +} diff --git a/src/definitions/types.d.ts b/src/definitions/types.d.ts new file mode 100644 index 000000000..076b7762a --- /dev/null +++ b/src/definitions/types.d.ts @@ -0,0 +1,5 @@ +export type { + Main as Track, + Stats as TrackStats, + Tags as TrackTags, +} from "./types/sh/diffuse/output/tracks.ts"; diff --git a/src/Static/Favicons/android-chrome-192x192.png b/src/favicons/android-chrome-192x192.png similarity index 100% rename from src/Static/Favicons/android-chrome-192x192.png rename to src/favicons/android-chrome-192x192.png diff --git a/src/Static/Favicons/android-chrome-512x512.png b/src/favicons/android-chrome-512x512.png similarity index 100% rename from src/Static/Favicons/android-chrome-512x512.png rename to src/favicons/android-chrome-512x512.png diff --git a/src/Static/Favicons/apple-touch-icon.png b/src/favicons/apple-touch-icon.png similarity index 100% rename from src/Static/Favicons/apple-touch-icon.png rename to src/favicons/apple-touch-icon.png diff --git a/src/Static/Favicons/browserconfig.xml b/src/favicons/browserconfig.xml similarity index 76% rename from src/Static/Favicons/browserconfig.xml rename to src/favicons/browserconfig.xml index a05bb7132..6a93f2c23 100644 --- a/src/Static/Favicons/browserconfig.xml +++ b/src/favicons/browserconfig.xml @@ -2,7 +2,7 @@ - + #8a90a9 diff --git a/src/Static/Favicons/favicon-16x16.png b/src/favicons/favicon-16x16.png similarity index 100% rename from src/Static/Favicons/favicon-16x16.png rename to src/favicons/favicon-16x16.png diff --git a/src/Static/Favicons/favicon-32x32.png b/src/favicons/favicon-32x32.png similarity index 100% rename from src/Static/Favicons/favicon-32x32.png rename to src/favicons/favicon-32x32.png diff --git a/src/Static/Favicons/favicon.ico b/src/favicons/favicon.ico similarity index 100% rename from src/Static/Favicons/favicon.ico rename to src/favicons/favicon.ico diff --git a/src/Static/Favicons/mstile-150x150.png b/src/favicons/mstile-150x150.png similarity index 100% rename from src/Static/Favicons/mstile-150x150.png rename to src/favicons/mstile-150x150.png diff --git a/src/Static/Favicons/safari-pinned-tab.svg b/src/favicons/safari-pinned-tab.svg similarity index 100% rename from src/Static/Favicons/safari-pinned-tab.svg rename to src/favicons/safari-pinned-tab.svg diff --git a/src/fonts/InterVariable-Italic.woff2 b/src/fonts/InterVariable-Italic.woff2 new file mode 100644 index 000000000..b3530f3f5 Binary files /dev/null and b/src/fonts/InterVariable-Italic.woff2 differ diff --git a/src/fonts/InterVariable.woff2 b/src/fonts/InterVariable.woff2 new file mode 100644 index 000000000..5a8d3e72a Binary files /dev/null and b/src/fonts/InterVariable.woff2 differ diff --git a/src/Static/Images/Background/1.jpg b/src/images/background/1.jpg similarity index 100% rename from src/Static/Images/Background/1.jpg rename to src/images/background/1.jpg diff --git a/src/Static/Images/Background/10.jpg b/src/images/background/10.jpg similarity index 100% rename from src/Static/Images/Background/10.jpg rename to src/images/background/10.jpg diff --git a/src/Static/Images/Background/11.jpg b/src/images/background/11.jpg similarity index 100% rename from src/Static/Images/Background/11.jpg rename to src/images/background/11.jpg diff --git a/src/Static/Images/Background/12.jpg b/src/images/background/12.jpg similarity index 100% rename from src/Static/Images/Background/12.jpg rename to src/images/background/12.jpg diff --git a/src/Static/Images/Background/13.jpg b/src/images/background/13.jpg similarity index 100% rename from src/Static/Images/Background/13.jpg rename to src/images/background/13.jpg diff --git a/src/Static/Images/Background/14.jpg b/src/images/background/14.jpg similarity index 100% rename from src/Static/Images/Background/14.jpg rename to src/images/background/14.jpg diff --git a/src/Static/Images/Background/15.jpg b/src/images/background/15.jpg similarity index 100% rename from src/Static/Images/Background/15.jpg rename to src/images/background/15.jpg diff --git a/src/Static/Images/Background/16.jpg b/src/images/background/16.jpg similarity index 100% rename from src/Static/Images/Background/16.jpg rename to src/images/background/16.jpg diff --git a/src/Static/Images/Background/17.jpg b/src/images/background/17.jpg similarity index 100% rename from src/Static/Images/Background/17.jpg rename to src/images/background/17.jpg diff --git a/src/Static/Images/Background/18.jpg b/src/images/background/18.jpg similarity index 100% rename from src/Static/Images/Background/18.jpg rename to src/images/background/18.jpg diff --git a/src/Static/Images/Background/19.jpg b/src/images/background/19.jpg similarity index 100% rename from src/Static/Images/Background/19.jpg rename to src/images/background/19.jpg diff --git a/src/Static/Images/Background/2.jpg b/src/images/background/2.jpg similarity index 100% rename from src/Static/Images/Background/2.jpg rename to src/images/background/2.jpg diff --git a/src/Static/Images/Background/20.jpg b/src/images/background/20.jpg similarity index 100% rename from src/Static/Images/Background/20.jpg rename to src/images/background/20.jpg diff --git a/src/Static/Images/Background/21.jpg b/src/images/background/21.jpg similarity index 100% rename from src/Static/Images/Background/21.jpg rename to src/images/background/21.jpg diff --git a/src/Static/Images/Background/22.jpg b/src/images/background/22.jpg similarity index 100% rename from src/Static/Images/Background/22.jpg rename to src/images/background/22.jpg diff --git a/src/Static/Images/Background/23.jpg b/src/images/background/23.jpg similarity index 100% rename from src/Static/Images/Background/23.jpg rename to src/images/background/23.jpg diff --git a/src/Static/Images/Background/24.jpg b/src/images/background/24.jpg similarity index 100% rename from src/Static/Images/Background/24.jpg rename to src/images/background/24.jpg diff --git a/src/Static/Images/Background/25.jpg b/src/images/background/25.jpg similarity index 100% rename from src/Static/Images/Background/25.jpg rename to src/images/background/25.jpg diff --git a/src/Static/Images/Background/26.jpg b/src/images/background/26.jpg similarity index 100% rename from src/Static/Images/Background/26.jpg rename to src/images/background/26.jpg diff --git a/src/Static/Images/Background/27.jpg b/src/images/background/27.jpg similarity index 100% rename from src/Static/Images/Background/27.jpg rename to src/images/background/27.jpg diff --git a/src/Static/Images/Background/28.jpg b/src/images/background/28.jpg similarity index 100% rename from src/Static/Images/Background/28.jpg rename to src/images/background/28.jpg diff --git a/src/Static/Images/Background/29.jpg b/src/images/background/29.jpg similarity index 100% rename from src/Static/Images/Background/29.jpg rename to src/images/background/29.jpg diff --git a/src/Static/Images/Background/3.jpg b/src/images/background/3.jpg similarity index 100% rename from src/Static/Images/Background/3.jpg rename to src/images/background/3.jpg diff --git a/src/Static/Images/Background/30.jpg b/src/images/background/30.jpg similarity index 100% rename from src/Static/Images/Background/30.jpg rename to src/images/background/30.jpg diff --git a/src/Static/Images/Background/4.jpg b/src/images/background/4.jpg similarity index 100% rename from src/Static/Images/Background/4.jpg rename to src/images/background/4.jpg diff --git a/src/Static/Images/Background/5.jpg b/src/images/background/5.jpg similarity index 100% rename from src/Static/Images/Background/5.jpg rename to src/images/background/5.jpg diff --git a/src/Static/Images/Background/6.jpg b/src/images/background/6.jpg similarity index 100% rename from src/Static/Images/Background/6.jpg rename to src/images/background/6.jpg diff --git a/src/Static/Images/Background/7.jpg b/src/images/background/7.jpg similarity index 100% rename from src/Static/Images/Background/7.jpg rename to src/images/background/7.jpg diff --git a/src/Static/Images/Background/8.jpg b/src/images/background/8.jpg similarity index 100% rename from src/Static/Images/Background/8.jpg rename to src/images/background/8.jpg diff --git a/src/Static/Images/Background/9.jpg b/src/images/background/9.jpg similarity index 100% rename from src/Static/Images/Background/9.jpg rename to src/images/background/9.jpg diff --git a/src/Static/Images/Background/Thumbnails/1.jpg b/src/images/background/thumbnails/1.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/1.jpg rename to src/images/background/thumbnails/1.jpg diff --git a/src/Static/Images/Background/Thumbnails/10.jpg b/src/images/background/thumbnails/10.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/10.jpg rename to src/images/background/thumbnails/10.jpg diff --git a/src/Static/Images/Background/Thumbnails/11.jpg b/src/images/background/thumbnails/11.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/11.jpg rename to src/images/background/thumbnails/11.jpg diff --git a/src/Static/Images/Background/Thumbnails/12.jpg b/src/images/background/thumbnails/12.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/12.jpg rename to src/images/background/thumbnails/12.jpg diff --git a/src/Static/Images/Background/Thumbnails/13.jpg b/src/images/background/thumbnails/13.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/13.jpg rename to src/images/background/thumbnails/13.jpg diff --git a/src/Static/Images/Background/Thumbnails/14.jpg b/src/images/background/thumbnails/14.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/14.jpg rename to src/images/background/thumbnails/14.jpg diff --git a/src/Static/Images/Background/Thumbnails/15.jpg b/src/images/background/thumbnails/15.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/15.jpg rename to src/images/background/thumbnails/15.jpg diff --git a/src/Static/Images/Background/Thumbnails/16.jpg b/src/images/background/thumbnails/16.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/16.jpg rename to src/images/background/thumbnails/16.jpg diff --git a/src/Static/Images/Background/Thumbnails/17.jpg b/src/images/background/thumbnails/17.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/17.jpg rename to src/images/background/thumbnails/17.jpg diff --git a/src/Static/Images/Background/Thumbnails/18.jpg b/src/images/background/thumbnails/18.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/18.jpg rename to src/images/background/thumbnails/18.jpg diff --git a/src/Static/Images/Background/Thumbnails/19.jpg b/src/images/background/thumbnails/19.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/19.jpg rename to src/images/background/thumbnails/19.jpg diff --git a/src/Static/Images/Background/Thumbnails/2.jpg b/src/images/background/thumbnails/2.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/2.jpg rename to src/images/background/thumbnails/2.jpg diff --git a/src/Static/Images/Background/Thumbnails/20.jpg b/src/images/background/thumbnails/20.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/20.jpg rename to src/images/background/thumbnails/20.jpg diff --git a/src/Static/Images/Background/Thumbnails/21.jpg b/src/images/background/thumbnails/21.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/21.jpg rename to src/images/background/thumbnails/21.jpg diff --git a/src/Static/Images/Background/Thumbnails/22.jpg b/src/images/background/thumbnails/22.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/22.jpg rename to src/images/background/thumbnails/22.jpg diff --git a/src/Static/Images/Background/Thumbnails/23.jpg b/src/images/background/thumbnails/23.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/23.jpg rename to src/images/background/thumbnails/23.jpg diff --git a/src/Static/Images/Background/Thumbnails/24.jpg b/src/images/background/thumbnails/24.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/24.jpg rename to src/images/background/thumbnails/24.jpg diff --git a/src/Static/Images/Background/Thumbnails/25.jpg b/src/images/background/thumbnails/25.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/25.jpg rename to src/images/background/thumbnails/25.jpg diff --git a/src/Static/Images/Background/Thumbnails/26.jpg b/src/images/background/thumbnails/26.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/26.jpg rename to src/images/background/thumbnails/26.jpg diff --git a/src/Static/Images/Background/Thumbnails/27.jpg b/src/images/background/thumbnails/27.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/27.jpg rename to src/images/background/thumbnails/27.jpg diff --git a/src/Static/Images/Background/Thumbnails/28.jpg b/src/images/background/thumbnails/28.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/28.jpg rename to src/images/background/thumbnails/28.jpg diff --git a/src/Static/Images/Background/Thumbnails/29.jpg b/src/images/background/thumbnails/29.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/29.jpg rename to src/images/background/thumbnails/29.jpg diff --git a/src/Static/Images/Background/Thumbnails/3.jpg b/src/images/background/thumbnails/3.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/3.jpg rename to src/images/background/thumbnails/3.jpg diff --git a/src/Static/Images/Background/Thumbnails/30.jpg b/src/images/background/thumbnails/30.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/30.jpg rename to src/images/background/thumbnails/30.jpg diff --git a/src/Static/Images/Background/Thumbnails/4.jpg b/src/images/background/thumbnails/4.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/4.jpg rename to src/images/background/thumbnails/4.jpg diff --git a/src/Static/Images/Background/Thumbnails/5.jpg b/src/images/background/thumbnails/5.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/5.jpg rename to src/images/background/thumbnails/5.jpg diff --git a/src/Static/Images/Background/Thumbnails/6.jpg b/src/images/background/thumbnails/6.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/6.jpg rename to src/images/background/thumbnails/6.jpg diff --git a/src/Static/Images/Background/Thumbnails/7.jpg b/src/images/background/thumbnails/7.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/7.jpg rename to src/images/background/thumbnails/7.jpg diff --git a/src/Static/Images/Background/Thumbnails/8.jpg b/src/images/background/thumbnails/8.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/8.jpg rename to src/images/background/thumbnails/8.jpg diff --git a/src/Static/Images/Background/Thumbnails/9.jpg b/src/images/background/thumbnails/9.jpg similarity index 100% rename from src/Static/Images/Background/Thumbnails/9.jpg rename to src/images/background/thumbnails/9.jpg diff --git a/src/images/diffuse-current.svg b/src/images/diffuse-current.svg new file mode 100644 index 000000000..d388861a4 --- /dev/null +++ b/src/images/diffuse-current.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Static/Images/diffuse-dark.svg b/src/images/diffuse-dark.svg similarity index 100% rename from src/Static/Images/diffuse-dark.svg rename to src/images/diffuse-dark.svg diff --git a/src/Static/Images/diffuse-grey.svg b/src/images/diffuse-grey.svg similarity index 100% rename from src/Static/Images/diffuse-grey.svg rename to src/images/diffuse-grey.svg diff --git a/src/Static/Images/diffuse-light.svg b/src/images/diffuse-light.svg similarity index 100% rename from src/Static/Images/diffuse-light.svg rename to src/images/diffuse-light.svg diff --git a/src/Static/Images/diffuse__icon-dark.svg b/src/images/diffuse__icon-dark.svg similarity index 100% rename from src/Static/Images/diffuse__icon-dark.svg rename to src/images/diffuse__icon-dark.svg diff --git a/src/Static/Images/diffuse__icon-grey.svg b/src/images/diffuse__icon-grey.svg similarity index 100% rename from src/Static/Images/diffuse__icon-grey.svg rename to src/images/diffuse__icon-grey.svg diff --git a/src/Static/Images/diffuse__icon-light.svg b/src/images/diffuse__icon-light.svg similarity index 100% rename from src/Static/Images/diffuse__icon-light.svg rename to src/images/diffuse__icon-light.svg diff --git a/src/Static/Images/icon-square-ws.png b/src/images/icon-square-ws.png similarity index 100% rename from src/Static/Images/icon-square-ws.png rename to src/images/icon-square-ws.png diff --git a/src/Static/Images/icon-square.png b/src/images/icon-square.png similarity index 100% rename from src/Static/Images/icon-square.png rename to src/images/icon-square.png diff --git a/src-tauri/icons/icon.png b/src/images/icon.png similarity index 100% rename from src-tauri/icons/icon.png rename to src/images/icon.png diff --git a/src/images/icons/windows_98/catalog-1.png b/src/images/icons/windows_98/catalog-1.png new file mode 100644 index 000000000..6f8560fc3 Binary files /dev/null and b/src/images/icons/windows_98/catalog-1.png differ diff --git a/src/images/icons/windows_98/cd_audio_cd_a-0.png b/src/images/icons/windows_98/cd_audio_cd_a-0.png new file mode 100644 index 000000000..7a500dc64 Binary files /dev/null and b/src/images/icons/windows_98/cd_audio_cd_a-0.png differ diff --git a/src/images/icons/windows_98/cd_audio_cd_a-2.png b/src/images/icons/windows_98/cd_audio_cd_a-2.png new file mode 100644 index 000000000..f7cce7b93 Binary files /dev/null and b/src/images/icons/windows_98/cd_audio_cd_a-2.png differ diff --git a/src/images/icons/windows_98/cd_audio_cd_a-3.png b/src/images/icons/windows_98/cd_audio_cd_a-3.png new file mode 100644 index 000000000..68bb414d3 Binary files /dev/null and b/src/images/icons/windows_98/cd_audio_cd_a-3.png differ diff --git a/src/images/icons/windows_98/cd_audio_cd_a-4.png b/src/images/icons/windows_98/cd_audio_cd_a-4.png new file mode 100644 index 000000000..fc07a9dea Binary files /dev/null and b/src/images/icons/windows_98/cd_audio_cd_a-4.png differ diff --git a/src/images/icons/windows_98/cd_drive-0.png b/src/images/icons/windows_98/cd_drive-0.png new file mode 100644 index 000000000..d359272f3 Binary files /dev/null and b/src/images/icons/windows_98/cd_drive-0.png differ diff --git a/src/images/icons/windows_98/cd_drive_purple-3.png b/src/images/icons/windows_98/cd_drive_purple-3.png new file mode 100644 index 000000000..3dcb824da Binary files /dev/null and b/src/images/icons/windows_98/cd_drive_purple-3.png differ diff --git a/src/images/icons/windows_98/channels-2.png b/src/images/icons/windows_98/channels-2.png new file mode 100644 index 000000000..0bfbd03c3 Binary files /dev/null and b/src/images/icons/windows_98/channels-2.png differ diff --git a/src/images/icons/windows_98/check-0.png b/src/images/icons/windows_98/check-0.png new file mode 100644 index 000000000..b184ecc07 Binary files /dev/null and b/src/images/icons/windows_98/check-0.png differ diff --git a/src/images/icons/windows_98/computer_sound-0.png b/src/images/icons/windows_98/computer_sound-0.png new file mode 100644 index 000000000..867a1490e Binary files /dev/null and b/src/images/icons/windows_98/computer_sound-0.png differ diff --git a/src/images/icons/windows_98/computer_user_pencil-0.png b/src/images/icons/windows_98/computer_user_pencil-0.png new file mode 100644 index 000000000..4f084977c Binary files /dev/null and b/src/images/icons/windows_98/computer_user_pencil-0.png differ diff --git a/src/images/icons/windows_98/connected_world-0.png b/src/images/icons/windows_98/connected_world-0.png new file mode 100644 index 000000000..ba373b907 Binary files /dev/null and b/src/images/icons/windows_98/connected_world-0.png differ diff --git a/src/images/icons/windows_98/directory_admin_tools-3.png b/src/images/icons/windows_98/directory_admin_tools-3.png new file mode 100644 index 000000000..3e7b64d23 Binary files /dev/null and b/src/images/icons/windows_98/directory_admin_tools-3.png differ diff --git a/src/images/icons/windows_98/directory_admin_tools-5.png b/src/images/icons/windows_98/directory_admin_tools-5.png new file mode 100644 index 000000000..c47e2a716 Binary files /dev/null and b/src/images/icons/windows_98/directory_admin_tools-5.png differ diff --git a/src/images/icons/windows_98/directory_channels-2.png b/src/images/icons/windows_98/directory_channels-2.png new file mode 100644 index 000000000..641505039 Binary files /dev/null and b/src/images/icons/windows_98/directory_channels-2.png differ diff --git a/src/images/icons/windows_98/directory_channels-3.png b/src/images/icons/windows_98/directory_channels-3.png new file mode 100644 index 000000000..87c293545 Binary files /dev/null and b/src/images/icons/windows_98/directory_channels-3.png differ diff --git a/src/images/icons/windows_98/directory_closed-3.png b/src/images/icons/windows_98/directory_closed-3.png new file mode 100644 index 000000000..b1765646f Binary files /dev/null and b/src/images/icons/windows_98/directory_closed-3.png differ diff --git a/src/images/icons/windows_98/directory_closed-4.png b/src/images/icons/windows_98/directory_closed-4.png new file mode 100644 index 000000000..136be6a49 Binary files /dev/null and b/src/images/icons/windows_98/directory_closed-4.png differ diff --git a/src/images/icons/windows_98/directory_control_panel-2.png b/src/images/icons/windows_98/directory_control_panel-2.png new file mode 100644 index 000000000..d64eeedbf Binary files /dev/null and b/src/images/icons/windows_98/directory_control_panel-2.png differ diff --git a/src/images/icons/windows_98/directory_control_panel-3.png b/src/images/icons/windows_98/directory_control_panel-3.png new file mode 100644 index 000000000..70d666886 Binary files /dev/null and b/src/images/icons/windows_98/directory_control_panel-3.png differ diff --git a/src/images/icons/windows_98/directory_explorer-4.png b/src/images/icons/windows_98/directory_explorer-4.png new file mode 100644 index 000000000..bfaf2ae01 Binary files /dev/null and b/src/images/icons/windows_98/directory_explorer-4.png differ diff --git a/src/images/icons/windows_98/directory_explorer-5.png b/src/images/icons/windows_98/directory_explorer-5.png new file mode 100644 index 000000000..88db8da8c Binary files /dev/null and b/src/images/icons/windows_98/directory_explorer-5.png differ diff --git a/src/images/icons/windows_98/directory_favorites-2.png b/src/images/icons/windows_98/directory_favorites-2.png new file mode 100644 index 000000000..113bc816a Binary files /dev/null and b/src/images/icons/windows_98/directory_favorites-2.png differ diff --git a/src/images/icons/windows_98/directory_favorites-4.png b/src/images/icons/windows_98/directory_favorites-4.png new file mode 100644 index 000000000..e52d84751 Binary files /dev/null and b/src/images/icons/windows_98/directory_favorites-4.png differ diff --git a/src/images/icons/windows_98/directory_net_web-3.png b/src/images/icons/windows_98/directory_net_web-3.png new file mode 100644 index 000000000..9a03f23ed Binary files /dev/null and b/src/images/icons/windows_98/directory_net_web-3.png differ diff --git a/src/images/icons/windows_98/directory_net_web-4.png b/src/images/icons/windows_98/directory_net_web-4.png new file mode 100644 index 000000000..3de2426f8 Binary files /dev/null and b/src/images/icons/windows_98/directory_net_web-4.png differ diff --git a/src/images/icons/windows_98/directory_network_conn-3.png b/src/images/icons/windows_98/directory_network_conn-3.png new file mode 100644 index 000000000..da7a22b97 Binary files /dev/null and b/src/images/icons/windows_98/directory_network_conn-3.png differ diff --git a/src/images/icons/windows_98/directory_network_conn-5.png b/src/images/icons/windows_98/directory_network_conn-5.png new file mode 100644 index 000000000..a13e184a8 Binary files /dev/null and b/src/images/icons/windows_98/directory_network_conn-5.png differ diff --git a/src/images/icons/windows_98/directory_open_file_mydocs_2k-3.png b/src/images/icons/windows_98/directory_open_file_mydocs_2k-3.png new file mode 100644 index 000000000..a2007df3e Binary files /dev/null and b/src/images/icons/windows_98/directory_open_file_mydocs_2k-3.png differ diff --git a/src/images/icons/windows_98/directory_open_file_mydocs_2k-4.png b/src/images/icons/windows_98/directory_open_file_mydocs_2k-4.png new file mode 100644 index 000000000..890b172cf Binary files /dev/null and b/src/images/icons/windows_98/directory_open_file_mydocs_2k-4.png differ diff --git a/src/images/icons/windows_98/gears-0.png b/src/images/icons/windows_98/gears-0.png new file mode 100644 index 000000000..6600aa987 Binary files /dev/null and b/src/images/icons/windows_98/gears-0.png differ diff --git a/src/images/icons/windows_98/globe_map-0.png b/src/images/icons/windows_98/globe_map-0.png new file mode 100644 index 000000000..975ff7b03 Binary files /dev/null and b/src/images/icons/windows_98/globe_map-0.png differ diff --git a/src/images/icons/windows_98/help_book_big-0.png b/src/images/icons/windows_98/help_book_big-0.png new file mode 100644 index 000000000..a5ab72c86 Binary files /dev/null and b/src/images/icons/windows_98/help_book_big-0.png differ diff --git a/src/images/icons/windows_98/installer-3.png b/src/images/icons/windows_98/installer-3.png new file mode 100644 index 000000000..ff9f9c88b Binary files /dev/null and b/src/images/icons/windows_98/installer-3.png differ diff --git a/src/images/icons/windows_98/installer_generic_old-0.png b/src/images/icons/windows_98/installer_generic_old-0.png new file mode 100644 index 000000000..1dce572a3 Binary files /dev/null and b/src/images/icons/windows_98/installer_generic_old-0.png differ diff --git a/src/images/icons/windows_98/loudspeaker_wave-0.png b/src/images/icons/windows_98/loudspeaker_wave-0.png new file mode 100644 index 000000000..d32866bfc Binary files /dev/null and b/src/images/icons/windows_98/loudspeaker_wave-0.png differ diff --git a/src/images/icons/windows_98/magnifying_glass-0.png b/src/images/icons/windows_98/magnifying_glass-0.png new file mode 100644 index 000000000..d1136ae66 Binary files /dev/null and b/src/images/icons/windows_98/magnifying_glass-0.png differ diff --git a/src/images/icons/windows_98/magnifying_glass_4-1.png b/src/images/icons/windows_98/magnifying_glass_4-1.png new file mode 100644 index 000000000..ef6d5c073 Binary files /dev/null and b/src/images/icons/windows_98/magnifying_glass_4-1.png differ diff --git a/src/images/icons/windows_98/media_player-0.png b/src/images/icons/windows_98/media_player-0.png new file mode 100644 index 000000000..b93121577 Binary files /dev/null and b/src/images/icons/windows_98/media_player-0.png differ diff --git a/src/images/icons/windows_98/media_player_stream_no.png b/src/images/icons/windows_98/media_player_stream_no.png new file mode 100644 index 000000000..76ba98228 Binary files /dev/null and b/src/images/icons/windows_98/media_player_stream_no.png differ diff --git a/src/images/icons/windows_98/ms_dos-1.png b/src/images/icons/windows_98/ms_dos-1.png new file mode 100644 index 000000000..61251a776 Binary files /dev/null and b/src/images/icons/windows_98/ms_dos-1.png differ diff --git a/src/images/icons/windows_98/msg_error-0.png b/src/images/icons/windows_98/msg_error-0.png new file mode 100644 index 000000000..6e84307f9 Binary files /dev/null and b/src/images/icons/windows_98/msg_error-0.png differ diff --git a/src/images/icons/windows_98/msg_information-0.png b/src/images/icons/windows_98/msg_information-0.png new file mode 100644 index 000000000..12a334014 Binary files /dev/null and b/src/images/icons/windows_98/msg_information-0.png differ diff --git a/src/images/icons/windows_98/msg_question-0.png b/src/images/icons/windows_98/msg_question-0.png new file mode 100644 index 000000000..8edb01f7b Binary files /dev/null and b/src/images/icons/windows_98/msg_question-0.png differ diff --git a/src/images/icons/windows_98/msg_warning-0.png b/src/images/icons/windows_98/msg_warning-0.png new file mode 100644 index 000000000..309941ee6 Binary files /dev/null and b/src/images/icons/windows_98/msg_warning-0.png differ diff --git a/src/images/icons/windows_98/multimedia-4.png b/src/images/icons/windows_98/multimedia-4.png new file mode 100644 index 000000000..6aed20c18 Binary files /dev/null and b/src/images/icons/windows_98/multimedia-4.png differ diff --git a/src/images/icons/windows_98/network_drive_world-0.png b/src/images/icons/windows_98/network_drive_world-0.png new file mode 100644 index 000000000..5b64a83e3 Binary files /dev/null and b/src/images/icons/windows_98/network_drive_world-0.png differ diff --git a/src/images/icons/windows_98/no-1.png b/src/images/icons/windows_98/no-1.png new file mode 100644 index 000000000..fb8127e61 Binary files /dev/null and b/src/images/icons/windows_98/no-1.png differ diff --git a/src/images/icons/windows_98/restrict-1.png b/src/images/icons/windows_98/restrict-1.png new file mode 100644 index 000000000..c7b1c0ce6 Binary files /dev/null and b/src/images/icons/windows_98/restrict-1.png differ diff --git a/src/images/icons/windows_98/search_computer-0.png b/src/images/icons/windows_98/search_computer-0.png new file mode 100644 index 000000000..72606e3a5 Binary files /dev/null and b/src/images/icons/windows_98/search_computer-0.png differ diff --git a/src/images/icons/windows_98/search_server-1.png b/src/images/icons/windows_98/search_server-1.png new file mode 100644 index 000000000..f5d58fe41 Binary files /dev/null and b/src/images/icons/windows_98/search_server-1.png differ diff --git a/src/images/icons/windows_98/search_web-0.png b/src/images/icons/windows_98/search_web-0.png new file mode 100644 index 000000000..2abe114a5 Binary files /dev/null and b/src/images/icons/windows_98/search_web-0.png differ diff --git a/src/images/icons/windows_98/settings_gear-0.png b/src/images/icons/windows_98/settings_gear-0.png new file mode 100644 index 000000000..03f89996d Binary files /dev/null and b/src/images/icons/windows_98/settings_gear-0.png differ diff --git a/src/images/icons/windows_98/settings_gear-2.png b/src/images/icons/windows_98/settings_gear-2.png new file mode 100644 index 000000000..70e5a7d4e Binary files /dev/null and b/src/images/icons/windows_98/settings_gear-2.png differ diff --git a/src/images/icons/windows_98/tip.png b/src/images/icons/windows_98/tip.png new file mode 100644 index 000000000..64355833f Binary files /dev/null and b/src/images/icons/windows_98/tip.png differ diff --git a/src/images/icons/windows_98/utopia_smiley.png b/src/images/icons/windows_98/utopia_smiley.png new file mode 100644 index 000000000..9f82dd287 Binary files /dev/null and b/src/images/icons/windows_98/utopia_smiley.png differ diff --git a/src/images/icons/windows_98/winamp2-32x32.png b/src/images/icons/windows_98/winamp2-32x32.png new file mode 100644 index 000000000..a096bf136 Binary files /dev/null and b/src/images/icons/windows_98/winamp2-32x32.png differ diff --git a/src/images/icons/windows_98/windows-0.png b/src/images/icons/windows_98/windows-0.png new file mode 100644 index 000000000..572511dd5 Binary files /dev/null and b/src/images/icons/windows_98/windows-0.png differ diff --git a/src/images/icons/windows_98/world-2.png b/src/images/icons/windows_98/world-2.png new file mode 100644 index 000000000..18a8b5d8a Binary files /dev/null and b/src/images/icons/windows_98/world-2.png differ diff --git a/src/images/icons/windows_98/world-4.png b/src/images/icons/windows_98/world-4.png new file mode 100644 index 000000000..4fd822cd5 Binary files /dev/null and b/src/images/icons/windows_98/world-4.png differ diff --git a/src/images/icons/windows_98/world_network_directories-3.png b/src/images/icons/windows_98/world_network_directories-3.png new file mode 100644 index 000000000..7941a4a99 Binary files /dev/null and b/src/images/icons/windows_98/world_network_directories-3.png differ diff --git a/src/index.vto b/src/index.vto new file mode 100644 index 000000000..1c1e5b907 --- /dev/null +++ b/src/index.vto @@ -0,0 +1,423 @@ +--- +layout: layouts/diffuse.vto + +styles: + - styles/base.css + - styles/diffuse/page.css + - styles/vendor/phosphor/fill/style.css + +# THEMES + +themes: + - url: "themes/blur/" + title: "Blur" + todo: true + desc: > + **A DJ theme with an Apple-inspired playback view.** Features two audio players instead of the usual one. + - title: "Loader" + todo: true + desc: > + **A theme that loads other themes!** Gives you the option to save a theme to your user data output and load it from there. + - url: "themes/webamp/" + title: "Webamp" + desc: > + **Winamp 2 + Windows 98**. Uses Webamp as the audio player connected to various Diffuse elements. Also features a desktop-like Windows 98 environment in which you can open "programs" that control the used Diffuse elements. + +# CONSTITUENTS + +constituents: + - url: "themes/blur/artwork-controller/" + title: "Blur / Artwork controller" + desc: > + Audio playback controller with artwork display. + - url: "themes/webamp/browser/" + title: "Webamp / Browser" + desc: > + Collection browser + search in a retro, win98, look. + +# ELEMENTS + +configurators: + - url: "components/configurator/input/element.js" + title: "Input" + desc: "Allows for multiple inputs to be used at once." + - url: "components/configurator/output/element.js" + title: "Output" + desc: "Enables the user to configure a specific output. If no default output is set, it creates a temporary session by storing everything in memory." + - url: "components/configurator/scrobbles/element.js" + title: "Scrobbles" + desc: "Configure multiple scrobblers (music trackers)." + todo: true + +engines: + - url: "components/engine/audio/element.js" + title: "Audio" + desc: "Plays audio through audio elements." + - url: "components/engine/queue/element.js" + title: "Queue" + desc: "A simple queue for tracks." + +input: + - url: "components/input/opensubsonic/element.js" + title: "Opensubsonic" + desc: "Add any (open)subsonic server." + - url: "components/input/s3/element.js" + title: "S3" + desc: "AWS S3 and services that provide the same surface API such as Cloudflare R2." + +orchestrators: + - url: "components/orchestrator/input/element.js" + title: "Input" + desc: "**A default input configuration.** Contains all the inputs provided here." + - url: "components/orchestrator/output/element.js" + title: "Output" + desc: "**A default output configuration.** Contains all the outputs provided here along with the relevant transformers." + - url: "components/orchestrator/process-tracks/element.js" + title: "Process inputs into tracks" + desc: "Whenever the cached tracks are initially loaded through the passed output element it will contextualize and then list tracks by using the passed input element. Afterwards it loops over all tracks and checks if metadata needs to be fetched. If anything has changed, it'll pass the results to the output element." + - url: "components/orchestrator/queue-audio/element.js" + title: "Queue โญค Audio" + desc: "Connects the given queue engine to the given audio engine." + - url: "components/orchestrator/queue-tracks/element.js" + title: "Queue โญค Tracks" + desc: "Sets the given queue element pool whenever the tracks signal from the given output changes." + - title: "Repeat & Shuffle" + desc: "An opinionated way to setup repeat & shuffle." + todo: true + - url: "components/orchestrator/search-tracks/element.js" + title: "Search โญค Tracks" + desc: "Supplies tracks to the given search processor whenever the tracks collection changes." + +output: + - url: "components/output/polymorphic/indexed-db/element.js" + title: "Polymorphic / IndexedDB" + desc: "Stores output into the local indexedDB. Supports any type of data that indexedDB supports." + +processors: + - url: "components/processor/artwork/element.js" + title: "Artwork" + desc: "Fetches cover art for a given set of tracks, stored locally in indexedDB. Checks the audio metadata first, then MusicBrainz and uses Last.fm as the fallback." + - url: "components/processor/metadata/element.js" + title: "Metadata" + desc: "Fetch audio metadata for a given set of tracks, adding to the `Track` object." + - url: "components/processor/search/element.js" + title: "Search" + desc: "Provides a way to search through a collection of tracks, powered by orama.js" + +supplements: + - title: "Last.fm scrobbler" + todo: true + - title: "ListenBrainz scrobbler" + todo: true + - title: "Rocksky scrobbler" + todo: true + - title: "Teal.fm scrobbler" + todo: true + +transformers: + - title: "Output / Bytes / Cambria lenses" + desc: "Uses the Cambria library to seamlessly translate between data schemas so that no data migration is needed." + todo: true + - title: "Output / Bytes / JSON" + desc: "Raw data schema output โ‡„ JSON Uint8Array." + todo: true + - title: "Output / Refiner / Default" + desc: "The task of a refiner transformer is to remove the output state that is not meant to be saved to storage. For example, ephemeral tracks; this transformer will keep them in memory, but they will not be present in the output. **Ideally this is part of every theme, but you may swap it out with another transformer that might provide better defaults.**" + url: "components/transformer/output/refiner/default/element.js" + - title: "Output / String / JSON" + desc: "Raw data schema output โ‡„ JSON UTF8 string." + url: "components/transformer/output/string/json/element.js" + +# DEFINITIONS + +definitions: + - title: "Output / Favourites" + desc: "Indicate a user's favourite audio. Not a property of a track because tracks are associated with a specific source. Favourites may match with multiple tracks. Specified using the audio's title and artist." + todo: true + - title: "Output / Playlist" + desc: "Just like favourites, does not refer to specific tracks. Unlike favourites, must also specify the album. Can also be considered a collection which is basically an unordered playlist." + todo: true + - title: "Output / Progress" + desc: "Used to track progress of (long) audio playback." + todo: true + - title: "Output / Tracks" + desc: "Represents audio that can be played, or a placeholder for a source of tracks. Contains a URI that will resolve to the audio. This object may be cached if convenient." + url: "definitions/output/tracks.json" + +--- + +
+

+ {{ await comp.diffuse.logo() }} +

+

+ Construct your audio player. +

+

+ Diffuse is a collection of components and software that make it possible to listen to audio from various sources on your devices and the web, and to create the ideal digital + audio listening experience for you. +

+

+ + HEAVILY EXPERIMENTAL +

+

+ Built by tokono.ma +

+
+
+
+ +
+

Usage

+ +

+ The easiest way to start is by exploring the software. If you prefer a traditional pre­packaged web application approach, you can check out themes. +

+ +

+ Alternatively, there's constituents which allows you to use any component from any theme interchange­ably, each in a separate browser tab. Each tab talks to each other, so you can for example browse audio in one tab and play it in another. +

+ +

+ If you're a programmer, you can compose the elements listed below to make your own theme. Easily build software by connecting various elements. +

+ +

+ + + I'd like there to be a way to compose your own theme without having to write code. Hopefully sometime in the future. + +

+
+ + +
+

Demo

+ +

Diffuse is not your typical streaming service, you have to add sources of audio. This button here adds a few sample audio files.

+ +

+ +

+ +

+ Next, select any theme from below to play the audio. Or any of the other options suggested in the usage section. +

+ +

+ + + The items are added to the default "IndexedDB" (local-only) output, so make sure that output is configured. + +

+
+
+
+ +
+

Themes

+ +

+ Themes are element compositions and provide a traditional browser web application way of + using them. In other words, pretty much the whole thing, besides your data, lives inside a single browser tab. +

+ +

+ + + Each theme is unique, not just a skin; each one might have a totally different feature set. + +

+ + {{ await comp.list({ items: themes }) }} +
+ + +
+

Constituents

+ +

+ Constituents are various interface components each loaded in their own web page. Every used component is configured so that it operates in broadcast mode, making all the pages communicate with each other. +

+ +

+ + + It also allows non-browser web environments, such as WebViews, to display UI components separately. For example, to enable moveable web views on the desktop. + +

+ + {{ await comp.list({ items: constituents }) }} +
+
+ + +
+

Elements

+ +

+ The (web) components of the system. These custom elements are then recombined into an entire music player experience, or whatever you want to build. +

+ +
+ {{ await comp.element({ + title: "Configurators", + items: configurators, + content: ` + Elements that serve as an intermediate in order to make a particular kind of element configurable. In other words, these allow for an element to be swapped out with another that takes the same set of the actions and data output. + ` + }) }} + + {{ await comp.element({ + title: "Engines", + items: engines, + content: ` + Elements with each a singular purpose and don't have any UI. There are specialised UI and orchestrator elements that control these. + ` + }) }} + + {{ await comp.element({ + title: "Input", + items: input, + content: ` + Inputs are sources of audio tracks. Each track is an entry in the list of possible items to play. These can be files or streams, static or dynamic. + ` + }) }} + + {{ await comp.element({ + title: "Orchestrators", + items: orchestrators, + content: ` + These too are element compositions. However, unlike themes, these are purely logical. Mostly exist in order to construct sensible defaults to use across themes and other compositions. + ` + }) }} + + {{ await comp.element({ + title: "Output", + items: output, + content: ` + Output is application-derived data such as playlists. These elements can receive such data and keep it around. These are categorised by the type of data they ingest, or many types in the case of polymorphic. Optionally use transformers to convert output into the expected format. + ` + }) }} + + {{ await comp.element({ + title: "Processors", + items: processors, + content: ` + These elements work with the output generated by the input elements to add more data to them, or process them in some other way. + ` + }) }} + + {{ await comp.element({ + title: "Supplements", + items: supplements, + content: ` + Additional elements, such as scrobblers. + ` + }) }} + + {{ await comp.element({ + title: "Transformers", + items: transformers, + content: ` + Transform data from one format or schema into another. See schema section below for more information. Just as configurators, these are intermediates and require to have the same set of actions as the element it targets. + ` + }) }} +
+
+ + +
+

Definitions

+ +

All of the elements here are built with these data definitions in mind. That said, you can mix elements that use different definitions; you just have to put a transformer between them in order to translate between them, if needed.

+ + {{ await comp.list({ items: definitions }) }} +
+ + +
+

Code your own

+ +

+ + TODO: + Explain how to connect the various elements. + +

+
+
+ + + + + + + + + + + + + diff --git a/src/styles/animations.css b/src/styles/animations.css new file mode 100644 index 000000000..c6379963b --- /dev/null +++ b/src/styles/animations.css @@ -0,0 +1,54 @@ +:root { + --animate-spin: spin 1s linear infinite; + --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + --animate-bounce: bounce 1s infinite; +} + +.animate-spin { + animation: var(--animate-spin); +} + +.animate-ping { + animation: var(--animate-ping); +} + +.animate-pulse { + animation: var(--animate-pulse); +} + +.animate-bounce { + animation: var(--animate-bounce); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@keyframes ping { + 75%, + 100% { + transform: scale(2); + opacity: 0; + } +} + +@keyframes pulse { + 50% { + opacity: 0.5; + } +} + +@keyframes bounce { + 0%, + 100% { + transform: translateY(-25%); + animation-timing-function: cubic-bezier(0.8, 0, 1, 1); + } + 50% { + transform: none; + animation-timing-function: cubic-bezier(0, 0, 0.2, 1); + } +} diff --git a/src/styles/base.css b/src/styles/base.css new file mode 100644 index 000000000..8bf516c8f --- /dev/null +++ b/src/styles/base.css @@ -0,0 +1,7 @@ +@import "./reset.css"; +@import "./variables.css"; +@import "./fonts.css"; +@import "./animations.css"; + +@import "./diffuse/colors.css"; +@import "./diffuse/fonts.css"; diff --git a/src/styles/diffuse/colors.css b/src/styles/diffuse/colors.css new file mode 100644 index 000000000..58cde8b42 --- /dev/null +++ b/src/styles/diffuse/colors.css @@ -0,0 +1,31 @@ +:root { + /* Colors */ + --color-1: oklch(4.1308% 0.25306 109.22); + --color-2: oklch(98.369% 0.01834 67.664); + --color-3: oklch(26.787% 0.00168 186.65); + + /* Orange/Red */ + /*--accent: oklch(86.947% 0.25527 28.789);*/ + /*--accent: hsl(51, 100%, 50%);*/ + /*--accent: #9e86b8;*/ + + /* Green */ + /*--accent: hsl(120, 73.4%, 74.9%);*/ + --accent: hsl(82, 39%, 30.2%); + /*--accent: hsl(80, 60.5%, 34.7%);*/ + + /* Blue */ + /*--accent: hsl(203, 92%, 75.5%);*/ + + --bg-color: var(--color-2); + --text-color: var(--color-1); +} + +@media (prefers-color-scheme: dark) { + :root { + --bg-color: var(--color-3); + --text-color: var(--color-2); + + --accent: #9e86b8; + } +} diff --git a/src/styles/diffuse/fonts.css b/src/styles/diffuse/fonts.css new file mode 100644 index 000000000..e1b9d1738 --- /dev/null +++ b/src/styles/diffuse/fonts.css @@ -0,0 +1,13 @@ +:root { + font-family: "Inter", sans-serif; + font-size: var(--fs-base); +} + +@supports (font-variation-settings: normal) { + :root { + font-family: "InterVariable", sans-serif; + font-feature-settings: + /* "zero" 2, */ "ss03" 2; + font-optical-sizing: auto; + } +} diff --git a/src/styles/diffuse/page.css b/src/styles/diffuse/page.css new file mode 100644 index 000000000..1a628a307 --- /dev/null +++ b/src/styles/diffuse/page.css @@ -0,0 +1,139 @@ +body { + background-color: var(--bg-color); + color: var(--text-color); +} + +header, +main { + margin: var(--space-md) var(--space-lg); +} + +a { + color: inherit; + text-underline-offset: 6px; +} + +button { + background: var(--accent); + border: 0; + border-radius: var(--radius-md); + color: var(--bg-color); + cursor: pointer; + font-family: inherit; + font-weight: 500; + line-height: var(--leading-tight); + padding: var(--space-2xs) var(--space-xs); + transition-duration: 500ms; + transition-property: opacity; + + &[disabled] { + opacity: 0.5; + } + + & > span { + align-items: center; + display: inline-flex; + gap: var(--space-3xs); + padding-top: 1px; + } +} + +h1 { + margin: var(--space-lg) 0 var(--space-xl); + padding-top: var(--space-2xs); +} + +h1 svg { + fill: oklch(from var(--bg-color) calc(l - 0.5) c h); + opacity: 0.2; + width: 4.25em; + + @media (prefers-color-scheme: dark) { + & { + fill: var(--text-color); + opacity: 0.25; + } + } +} + +h2 { + /* color: oklch(from var(--bg-color) calc(l - 0.25) c h); */ + color: var(--accent); + + font-size: var(--fs-xl); + font-weight: 900; + letter-spacing: -0.0125em; + line-height: 1; + margin: var(--space-2xl) 0 var(--space-md); + text-transform: uppercase; +} + +h3 { + font-size: var(--fs-md); + font-weight: 800; + line-height: 1; + margin: var(--space-xl) 0 var(--space-sm); + text-transform: uppercase; +} + +h2 + h3 { + margin-top: var(--space-md); +} + +ul, +ol { + padding-left: var(--space-md); +} + +p, +ul, +ol { + margin: var(--space-sm) 0; + max-width: var(--container-sm); +} + +small { + font-size: var(--fs-xs); +} + +.columns { + display: flex; + flex-wrap: wrap; + gap: 0 var(--space-3xl); +} + +.construct { + color: oklch(from currentColor l c h / 0.3); + font-size: var(--fs-3xl); + font-weight: 900; + letter-spacing: -0.0125em; + line-height: 0.775em; + line-height: 0.9cap; + margin-bottom: var(--space-md); + max-width: var(--container-xl); + text-transform: uppercase; + + @media (prefers-color-scheme: dark) { + & { + color: oklch(from var(--color-2) l c h / 0.75); + } + } +} + +.element { + min-width: min(var(--container-xs), 100%); + width: 32.5%; +} + +.list-description { + font-size: var(--fs-xs); + margin-bottom: var(--space-xs); + margin-top: var(--space-2xs); + opacity: 0.6; +} + +.todo { + font-size: var(--fs-sm); + font-weight: 600; + opacity: 0.4; +} diff --git a/src/styles/fonts.css b/src/styles/fonts.css new file mode 100644 index 000000000..ade90e099 --- /dev/null +++ b/src/styles/fonts.css @@ -0,0 +1,15 @@ +@font-face { + font-display: swap; + font-family: InterVariable; + font-style: normal; + font-weight: 100 900; + src: url("../fonts/InterVariable.woff2") format("woff2"); +} + +@font-face { + font-display: swap; + font-family: InterVariable; + font-style: italic; + font-weight: 100 900; + src: url("../fonts/InterVariable-Italic.woff2") format("woff2"); +} diff --git a/src/styles/reset.css b/src/styles/reset.css new file mode 100644 index 000000000..b2a838f8e --- /dev/null +++ b/src/styles/reset.css @@ -0,0 +1,119 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +* { + margin: 0; +} + +html, +:host { + font-family: ui-sans-serif, system-ui, sans-serif; + font-synthesis: none; + line-height: 1.5; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; +} + +body { + line-height: inherit; +} + +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +input, +button, +textarea, +select { + font: inherit; +} + +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} + +p { + text-wrap: pretty; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + text-wrap: balance; +} + +#root, +#__next { + isolation: isolate; +} + +small { + font-size: 80%; +} + +progress { + vertical-align: baseline; +} + +/* + 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) + 2. Set the default placeholder color to a semi-transparent version of the current text color. +*/ + +::placeholder { + opacity: 1; /* 1 */ + color: color-mix(in oklab, currentColor 50%, transparent); /* 2 */ +} + +/* + Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* + Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* + Correct the inability to style the border radius in iOS Safari. +*/ + +button, +input:where([type="button"], [type="reset"], [type="submit"]), +::file-selector-button { + appearance: button; +} + +/* + Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} diff --git a/src/styles/variables.css b/src/styles/variables.css new file mode 100644 index 000000000..73693dd62 --- /dev/null +++ b/src/styles/variables.css @@ -0,0 +1,82 @@ +:root { + /* Font scales */ + --fs-3xs: clamp(0.41rem, -0.06vi + 0.42rem, 0.38rem); + --fs-2xs: clamp(0.51rem, -0.02vi + 0.52rem, 0.5rem); + --fs-xs: clamp(0.64rem, 0.05vi + 0.63rem, 0.67rem); + --fs-sm: clamp(0.8rem, 0.17vi + 0.76rem, 0.89rem); + --fs-base: clamp(1rem, 0.34vi + 0.91rem, 1.19rem); + --fs-md: clamp(1.25rem, 0.61vi + 1.1rem, 1.58rem); + --fs-lg: clamp(1.56rem, 1vi + 1.31rem, 2.11rem); + --fs-xl: clamp(1.95rem, 1.56vi + 1.56rem, 2.81rem); + --fs-2xl: clamp(2.44rem, 2.38vi + 1.85rem, 3.75rem); + --fs-3xl: clamp(3.05rem, 3.54vi + 2.17rem, 5rem); + + /* Space scales */ + --space-3xs: clamp(0.25rem, 0.2216rem + 0.1136vw, 0.3125rem); + --space-2xs: clamp(0.5rem, 0.4432rem + 0.2273vw, 0.625rem); + --space-xs: clamp(0.75rem, 0.6932rem + 0.2273vw, 0.875rem); + --space-sm: clamp(1rem, 0.9148rem + 0.3409vw, 1.1875rem); + --space-md: clamp(1.5rem, 1.358rem + 0.5682vw, 1.8125rem); + --space-lg: clamp(2rem, 1.8295rem + 0.6818vw, 2.375rem); + --space-xl: clamp(3rem, 2.7443rem + 1.0227vw, 3.5625rem); + --space-2xl: clamp(4rem, 3.6591rem + 1.3636vw, 4.75rem); + --space-3xl: clamp(6rem, 5.4886rem + 2.0455vw, 7.125rem); + + /* Border-radius */ + --radius-xs: 0.125rem; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --radius-xl: 0.75rem; + --radius-2xl: 1rem; + --radius-3xl: 1.5rem; + --radius-4xl: 2rem; + + /* Containers */ + --container-3xs: 16rem; + --container-2xs: 18rem; + --container-xs: 20rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-2xl: 42rem; + --container-3xl: 48rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-6xl: 72rem; + --container-7xl: 80rem; + + /* Letter-spacing */ + --tracking-tighter: -0.05em; + --tracking-tight: -0.025em; + --tracking-normal: 0em; + --tracking-wide: 0.025em; + --tracking-wider: 0.05em; + --tracking-widest: 0.1em; + + /* Line-height */ + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; + + /* Shadows */ + --box-shadow-2xs: 0 1px rgb(0 0 0 / 0.05); + --box-shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --box-shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --box-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --box-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --box-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --box-shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); + + --text-shadow-2xs: 0px 1px 0px rgb(0 0 0 / 0.15); + --text-shadow-xs: 0px 1px 1px rgb(0 0 0 / 0.2); + --text-shadow-sm: + 0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075); + --text-shadow-md: + 0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1); + --text-shadow-lg: + 0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1); +} diff --git a/src/themes/blur/artwork-controller/element.css b/src/themes/blur/artwork-controller/element.css new file mode 100644 index 000000000..eb8f3e377 --- /dev/null +++ b/src/themes/blur/artwork-controller/element.css @@ -0,0 +1,302 @@ +:host { + --transition-durition: 750ms; +} + +main { + background: var(--color-3); + color: white; + display: flex; + flex-direction: column; + font-size: var(--fs-sm); + height: 100dvh; + overflow: hidden; + position: relative; + transition: + background-color var(--transition-durition), + color var(--transition-durition); +} + +/* Artwork */ + +.artwork { + app-region: drag; + flex: 1; + position: relative; + user-select: none; +} + +.artwork img { + height: 100%; + left: 0; + object-fit: cover; + opacity: 0; + position: absolute; + top: 0; + transition-duration: var(--transition-durition); + transition-property: opacity; + width: 100%; + z-index: 0; +} + +.artwork label { + background: oklch(0 0 0); + border-radius: var(--radius-sm); + box-shadow: var(--box-shadow-lg); + font-size: var(--fs-2xs); + font-weight: 600; + left: var(--space-xs); + letter-spacing: var(--tracking-wide); + line-height: 1; + padding: 8px 6px; + position: absolute; + text-box: trim-both cap alphabetic; + text-transform: uppercase; + top: var(--space-xs); + transition: + background-color var(--transition-durition), + color var(--transition-durition); + z-index: 10; +} + +/* Progress bars */ + +progress { + appearance: none; + border: 0; + display: block; + height: 4px; + width: 100%; +} + +progress, +progress::-webkit-progress-bar { + background-color: color-mix(in oklch, currentColor 40%, transparent); + overflow: hidden; + border-radius: 4px; +} + +progress[value]::-webkit-progress-value { + border-radius: 4px; + background-color: color-mix(in oklch, currentColor 90%, transparent); +} + +progress[value]::-moz-progress-bar { + border-radius: 4px; + background-color: color-mix(in oklch, currentColor 50%, transparent); +} + +/* Controller */ + +.controller { + flex-shrink: 0; + opacity: 1 !important; + padding: 0 var(--space-md) var(--space-md); + position: relative; +} + +.controller__background { + inset: 0; + opacity: 0.5; + position: absolute; + transition: background-color var(--transition-durition); + z-index: 1; +} + +.controller__inner { + position: relative; + transition-duration: var(--transition-durition); + transition-property: color; + z-index: 10; +} + +.controller__inner.controller__inner--light-mode { + color: rgba(0, 0, 0, 0.6); +} + +/* Now playing */ + +cite { + display: block; + font-style: normal; + text-shadow: var(--text-shadow-sm); + + & > span, + & > strong { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.controller__inner--light-mode cite { + text-shadow: none; +} + +/* Progress */ + +.progress { + cursor: pointer; + margin: var(--space-xs) 0; + padding-top: var(--space-2xs); +} + +.timestamps { + display: flex; + font-size: var(--fs-2xs); + font-weight: 500; + justify-content: space-between; + margin-top: var(--space-3xs); + opacity: 0.4; + text-shadow: var(--text-shadow-xs); +} + +.controller__inner--light-mode .timestamps { + text-shadow: none; +} + +/* Controls */ + +.controller menu { + align-items: center; + display: flex; + font-size: var(--fs-lg); + gap: var(--space-lg); + justify-content: center; + margin: var(--space-md) 0; + padding: 0; + text-align: center; +} + +.controller .menu__loader { + line-height: 0; + transform-origin: center; +} + +.controller li { + cursor: pointer; + line-height: 0; + list-style: none; + transition-duration: var(--transition-durition); + transition-property: opacity; +} + +.controller .ph-pause, +.controller .ph-play, +.controller .menu__loader { + font-size: var(--fs-xl); +} + +/* Volume */ + +footer { + align-items: center; + display: flex; + font-size: var(--fs-xs); + gap: var(--space-2xs); + justify-content: space-between; +} + +footer .progress-bar { + cursor: pointer; + flex: 1; + padding: var(--space-2xs) 0; +} + +footer i { + cursor: pointer; +} + +/* Gradient blur */ + +.gradient-blur { + bottom: 0; + height: 150%; + left: 0; + pointer-events: none; + position: absolute; + right: 0; + z-index: 2; +} + +.gradient-blur > div { + position: absolute; + inset: 0; +} + +.gradient-blur > div:nth-of-type(1) { + backdrop-filter: blur(1px); + mask: linear-gradient( + to bottom, + rgba(0, 0, 0, 0) 0%, + rgba(0, 0, 0, 1) 4.166666665%, + rgba(0, 0, 0, 1) 8.333333332%, + rgba(0, 0, 0, 0) 12.499999999% + ); + z-index: 1; +} + +.gradient-blur > div:nth-of-type(2) { + backdrop-filter: blur(2px); + mask: linear-gradient( + to bottom, + rgba(0, 0, 0, 0) 4.166666665%, + rgba(0, 0, 0, 1) 8.333333332%, + rgba(0, 0, 0, 1) 12.499999999%, + rgba(0, 0, 0, 0) 16.666666666% + ); + z-index: 2; +} + +.gradient-blur > div:nth-of-type(3) { + backdrop-filter: blur(4px); + mask: linear-gradient( + to bottom, + rgba(0, 0, 0, 0) 8.333333332%, + rgba(0, 0, 0, 1) 12.499999999%, + rgba(0, 0, 0, 1) 16.666666666%, + rgba(0, 0, 0, 0) 20.833333333% + ); + z-index: 3; +} + +.gradient-blur > div:nth-of-type(4) { + backdrop-filter: blur(8px); + mask: linear-gradient( + to bottom, + rgba(0, 0, 0, 0) 12.499999999%, + rgba(0, 0, 0, 1) 16.666666666%, + rgba(0, 0, 0, 1) 20.833333333%, + rgba(0, 0, 0, 0) 25% + ); + z-index: 4; +} + +.gradient-blur > div:nth-of-type(5) { + backdrop-filter: blur(16px); + mask: linear-gradient( + to bottom, + rgba(0, 0, 0, 0) 16.666666666%, + rgba(0, 0, 0, 1) 20.833333333%, + rgba(0, 0, 0, 1) 25%, + rgba(0, 0, 0, 0) 100% + ); + z-index: 5; +} + +.gradient-blur > div:nth-of-type(6) { + backdrop-filter: blur(32px); + mask: linear-gradient( + to bottom, + rgba(0, 0, 0, 0) 20.833333333%, + rgba(0, 0, 0, 1) 25%, + rgba(0, 0, 0, 1) 100% + ); + z-index: 6; +} + +.gradient-blur > div:nth-of-type(7) { + backdrop-filter: blur(64px); + mask: linear-gradient(to bottom, rgba(0, 0, 0, 0) 25%, rgba(0, 0, 0, 1) 100%); + z-index: 7; +} diff --git a/src/themes/blur/artwork-controller/element.js b/src/themes/blur/artwork-controller/element.js new file mode 100644 index 000000000..c8e260b6e --- /dev/null +++ b/src/themes/blur/artwork-controller/element.js @@ -0,0 +1,534 @@ +import { FastAverageColor } from "fast-average-color"; +import { Temporal } from "@js-temporal/polyfill"; +import { cache } from "lit-html/directives/cache.js"; +import { debounce } from "throttle-debounce"; +import { xxh32r } from "xxh32/dist/raw.js"; + +import { + DEFAULT_GROUP, + DiffuseElement, + query, + whenElementsDefined, +} from "@common/element.js"; + +import { trackArtworkCacheId } from "@common/index.js"; +import { computed, signal, untracked } from "@common/signal.js"; + +/** + * @import {RenderArg} from "@common/element.d.ts" + * + * @import {InputElement} from "@components/input/types.d.ts" + * @import {Artwork} from "@components/processor/artwork/types.d.ts" + * @import AudioEngine from "@components/engine/audio/element.js" + * @import QueueEngine from "@components/engine/queue/element.js" + * @import ArtworkProcessor from "@components/processor/artwork/element.js" + */ + +class ArtworkController extends DiffuseElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + + // Bind event handlers to self + this.artworkLoaded = this.artworkLoaded.bind(this); + this.fullVolume = this.fullVolume.bind(this); + this.mute = this.mute.bind(this); + this.next = this.next.bind(this); + this.playPause = this.playPause.bind(this); + this.previous = this.previous.bind(this); + this.seek = this.seek.bind(this); + this.setVolume = this.setVolume.bind(this); + } + + // VARIABLES + + /** @type {number | undefined} */ + #isLoadingTimeout = undefined; + + // SIGNALS + + #artwork = signal( + /** @type {{ current: (Artwork & { hash: string; index: number; loaded: boolean; url: string }) | null; previous: (Artwork & { hash: string; index: number; loaded: boolean; url: string }) | null }} */ ({ + current: null, + previous: null, + }), + { eager: true }, + ); + + #artworkColor = signal(/** @type {string | undefined} */ (undefined)); + #artworkLightMode = signal(false); + #duration = signal("0:00"); + #isLoading = signal(true); + #time = signal("0:00"); + + // SIGNALS - DEPENDENCIES + + $artwork = signal(/** @type {ArtworkProcessor | undefined} */ (undefined)); + $audio = signal(/** @type {AudioEngine | undefined} */ (undefined)); + $input = signal(/** @type {InputElement | undefined} */ (undefined)); + $queue = signal(/** @type {QueueEngine | undefined} */ (undefined)); + + // SIGNALS - COMPUTED + + #audio = computed(() => { + const curr = this.$queue.value?.now(); + return curr ? this.$audio.value?.state(curr.id) : undefined; + }); + + #isPlaying = computed(() => { + return this.$audio.value?.isPlaying(); + }); + + // LIFECYCLE + + /** + * @override + */ + connectedCallback() { + super.connectedCallback(); + + /** @type {ArtworkProcessor} */ + const artwork = query(this, "artwork-processor-selector"); + + /** @type {AudioEngine} */ + const audio = query(this, "audio-engine-selector"); + + /** @type {InputElement} */ + const input = query(this, "input-selector"); + + /** @type {QueueEngine} */ + const queue = query(this, "queue-engine-selector"); + + this.$artwork.value = artwork; + this.$audio.value = audio; + this.$input.value = input; + this.$queue.value = queue; + + whenElementsDefined({ audio, artwork, input, queue }).then(() => { + // Changed artwork based on active queue item. + const debouncedChangeArtwork = debounce( + 1000, + this.#setArtwork.bind(this), + ); + + this.effect(() => { + const _trigger = queue.now(); + debouncedChangeArtwork(); + }); + + this.effect(() => this.#formatTimestamps()); + this.effect(() => this.#lightOrDark()); + + this.effect(() => { + const now = !!queue.now(); + const bool = now && this.#audio()?.loadingState() !== "loaded"; + + if (this.#isLoadingTimeout) { + clearTimeout(this.#isLoadingTimeout); + } + + if (bool) { + this.#isLoadingTimeout = setTimeout( + () => this.#isLoading.value = true, + 2000, + ); + } else { + this.#isLoading.set(false); + } + }); + }); + } + + //////////////////////////////////////////// + // โœจ EFFECTS + // ๐Ÿ–ผ๏ธ Artwork + //////////////////////////////////////////// + + #lightOrDark() { + const controller = this.root().querySelector(".controller__inner"); + if (!controller) return; + + if (this.#artworkLightMode.value) { + controller.classList.add("controller__inner--light-mode"); + } else controller.classList.remove("controller__inner--light-mode"); + } + + /** */ + async #setArtwork() { + const track = this.$queue.value?.now(); + const currArtwork = untracked(this.#artwork.get); + + if (!track) { + if (currArtwork.current) { + this.#artwork.value = { current: null, previous: currArtwork.current }; + } + return; + } + + const cacheId = await trackArtworkCacheId(track); + + const resGet = await this.$input.value?.resolve({ + method: "GET", + uri: track.uri, + }); + + const resHead = await this.$input.value?.resolve({ + method: "HEAD", + uri: track.uri, + }); + + if (!resGet) return; + + const request = "stream" in resGet + ? { + cacheId, + stream: resGet.stream, + tags: track.tags, + } + : { + cacheId, + tags: track.tags, + urls: { + get: resGet.url, + head: resHead && "url" in resHead ? resHead.url : resGet.url, + }, + }; + + if (this.$queue.value?.now()?.id !== track.id) { + return; + } + + const allArt = await this.$artwork.value?.artwork(request) ?? []; + + const currTrack = this.$queue.value?.now(); + const currCacheId = currTrack + ? await trackArtworkCacheId(currTrack) + : undefined; + + if (cacheId === currCacheId) { + const art = allArt[0]; + const blob = new Blob( + [/** @type {ArrayBuffer} */ (art.bytes.buffer)], + { type: art.mime }, + ); + + const url = URL.createObjectURL(blob); + + this.#artwork.set({ + previous: currArtwork.current + ? { ...currArtwork.current, loaded: false } + : null, + current: art + ? { + ...art, + hash: xxh32r(art.bytes).toString(), + index: (currArtwork.current?.index ?? 0) + 1, + loaded: false, + url, + } + : null, + }); + + if (!art) { + this.#artworkColor.value = undefined; + this.#artworkLightMode.value = false; + } + } + } + + //////////////////////////////////////////// + // โœจ EFFECTS + // โŒš๏ธ Time + //////////////////////////////////////////// + #formatTimestamps() { + const curr = this.$queue.value?.now?.() ?? undefined; + const audio = this.#audio(); + const prog = audio?.progress() ?? 0; + const dur = curr?.stats?.duration ?? audio?.duration(); + + if (audio && dur != undefined && !isNaN(dur)) { + const p = Temporal.Duration.from({ + milliseconds: Math.round(dur * prog * 1000), + }).round({ + largestUnit: "hours", + }); + + const d = Temporal.Duration.from({ milliseconds: Math.round(dur * 1000) }) + .round({ + largestUnit: "hours", + }); + + this.#time.value = this.#formatTime(p); + this.#duration.value = this.#formatTime(d); + } else { + this.#time.value = "0:00"; + this.#duration.value = "0:00"; + } + } + + /** + * @param {Temporal.Duration} duration + */ + #formatTime(duration) { + return `${duration.hours > 0 ? duration.hours.toFixed(0) + ":" : ""}${ + duration.hours > 0 + ? (duration.minutes > 9 + ? duration.minutes.toFixed(0) + : "0" + duration.minutes.toFixed(0)) + : duration.minutes.toFixed(0) + }:${ + duration.seconds > 9 + ? duration.seconds.toFixed(0) + : "0" + duration.seconds.toFixed(0) + }`; + } + + // EVENTS + + /** + * @param {Event} event + */ + artworkLoaded(event) { + if (!(event.target instanceof HTMLImageElement)) return; + + const hash = event.target.getAttribute("data-hash"); + if (!hash) return; + + if (hash !== this.#artwork.value.current?.hash) return; + if (this.#artwork.value.current?.loaded) return; + + const fac = new FastAverageColor(); + const color = fac.getColor(event.target); + const rgb = color.value; + const o = Math.round( + (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000, + ); + + this.#artworkColor.value = color.rgba; + this.#artworkLightMode.value = o > 165; + this.#artwork.value = { + previous: this.#artwork.value.previous, + current: { ...this.#artwork.value.current, loaded: true }, + }; + } + + fullVolume() { + this.$audio.value?.adjustVolume({ volume: 1 }); + } + + mute() { + this.$audio.value?.adjustVolume({ volume: 0 }); + } + + next() { + this.$queue.value?.shift(); + } + + playPause() { + const audioId = this.$queue.value?.now()?.id; + + if (this.#isPlaying() && audioId) { + this.$audio.value?.pause({ audioId }); + } else if (audioId) { + this.$audio.value?.play({ audioId }); + } + } + + previous() { + this.$queue.value?.unshift(); + } + + /** + * @param {MouseEvent} event + */ + seek(event) { + const target = event.target + ? /** @type {HTMLProgressElement} */ (event.target) + : null; + const percentage = target ? event.offsetX / target.clientWidth : 0; + const audioId = this.$queue.value?.now()?.id; + + if (audioId) this.$audio.value?.seek({ audioId, percentage }); + } + + /** + * @param {MouseEvent} event + */ + setVolume(event) { + const target = event.target + ? /** @type {HTMLProgressElement} */ (event.target) + : null; + + const percentage = target ? event.offsetX / target.clientWidth : 0; + this.$audio.value?.adjustVolume({ volume: percentage }); + } + + // RENDER + + /** + * @param {RenderArg} _ + */ + render({ html }) { + const activeQueueItem = this.$queue.value?.now(); + + // Artwork + const artworkArr = [ + this.#artwork.value.previous, + this.#artwork.value.current, + ].sort((a, b) => { + if (!a || !b) return 0; + return a.index % 2 ? 1 : -1; + }); + + const artwork = artworkArr.map((art) => { + if (art === null) { + return null; + } + + return cache(html` + + `); + }); + + return html` + + + + +
+
+ + + ${artwork} +
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ + + + ${activeQueueItem?.tags?.title || + "Diffuse"} + + ${activeQueueItem?.tags?.artist ?? + (activeQueueItem ? `Waiting on queue ...` : ``)} + + + + + +
+ +
+ + +
+
+ + + + + +
  • + +
  • + + + + + +
  • + +
  • + + +
  • + +
  • + + +
  • + +
  • +
    + + + +
    + +
    + +
    + +
    +
    +
    +
    + `; + } +} + +export default ArtworkController; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = ArtworkController; +export const NAME = "db-artwork-controller"; + +customElements.define(NAME, CLASS); diff --git a/src/themes/blur/artwork-controller/index.vto b/src/themes/blur/artwork-controller/index.vto new file mode 100644 index 000000000..0514008db --- /dev/null +++ b/src/themes/blur/artwork-controller/index.vto @@ -0,0 +1,46 @@ +--- +layout: layouts/diffuse.vto +base: ../../../ + +styles: + - styles/vendor/phosphor/fill/style.css + - styles/base.css +--- + + + + + + + + + + + + diff --git a/src/themes/blur/index.css b/src/themes/blur/index.css new file mode 100644 index 000000000..6ac7fa78a --- /dev/null +++ b/src/themes/blur/index.css @@ -0,0 +1,45 @@ +body { + background-color: oklch(from var(--bg-color) calc(l - 0.025) c h); + color: var(--text-color); + display: flex; + flex-direction: column; + overflow: hidden; + height: 100dvh; +} + +/*********************************** + * Main + ***********************************/ +main { + display: grid; + gap: var(--space-2xs); + grid-template-columns: repeat(3, 1fr); + grid-template-rows: auto; + height: 100dvh; + overflow: hidden; + padding: var(--space-md) var(--space-md); + + /* TODO: & iframe { + border-radius: 6px; + }*/ +} + +/*********************************** + * Elements + ***********************************/ + +iframe[src*="/artwork-controller/"] { + grid-column: 3; + height: 100%; + justify-self: end; + max-width: var(--container-3xs); + width: 100%; +} + +iframe[src*="/browser/"] { + grid-column: 1 / span 2; + grid-row: 1 / span 2; + height: 100%; + justify-self: end; + width: 100%; +} diff --git a/src/themes/blur/index.js b/src/themes/blur/index.js new file mode 100644 index 000000000..16ec18662 --- /dev/null +++ b/src/themes/blur/index.js @@ -0,0 +1,59 @@ +import "@components/input/opensubsonic/element.js"; +import "@components/processor/metadata/element.js"; + +import * as Audio from "@components/engine/audio/element.js"; +import * as Output from "@components/orchestrator/output/element.js"; +import * as Queue from "@components/engine/queue/element.js"; + +import { component } from "@common/element.js"; +import { effect } from "@common/signal.js"; + +const audio = component(Audio); +const output = component(Output); +const queue = component(Queue); + +globalThis.audio = audio; +globalThis.output = output; +globalThis.queue = queue; + +// ๐Ÿš€ + +isLeader().then((bool) => { + if (!bool) return; + + // Only load these orchestrators if leader + import("@components/orchestrator/process-tracks/element.js"); + import("@components/orchestrator/queue-audio/element.js"); + import("@components/orchestrator/queue-tracks/element.js"); +}); + +// EFFECTS + +effect(() => { + console.log("Active queue item:", queue.now()); +}); + +effect(() => { + console.log("Queue pool hash:", queue.poolHash()); +}); + +/** + * Make sure there's always some random tracks in the queue. + */ +effect(() => { + const trigger = queue.now(); + const _other_trigger = queue.poolHash(); + + isLeader().then((bool) => { + if (bool) { + queue.fill({ amount: 10, shuffled: true }); + if (!trigger) queue.shift(); + } + }); +}); + +// ๐Ÿ› ๏ธ + +async function isLeader() { + return await audio.isLeader(); +} diff --git a/src/themes/blur/index.vto b/src/themes/blur/index.vto new file mode 100644 index 000000000..11920ac41 --- /dev/null +++ b/src/themes/blur/index.vto @@ -0,0 +1,45 @@ +--- +layout: layouts/diffuse.vto +base: ../../ + +styles: + - ../../styles/base.css + - index.css + +scripts: + - index.js +--- + + +
    +
    + + + + + + + + + + + + + + + + + + diff --git a/src/themes/blur/variables.css b/src/themes/blur/variables.css new file mode 100644 index 000000000..89795a72c --- /dev/null +++ b/src/themes/blur/variables.css @@ -0,0 +1,26 @@ +:root { + /* Colors */ + /* https://farbvelo.elastiq.ch/?s=eyJzIjoiZTBjNjIyMTdiNTcxZSIsImEiOjYsImNnIjo0LCJoZyI6dHJ1ZSwiaGIiOmZhbHNlLCJobyI6ZmFsc2UsImhjIjpmYWxzZSwiaHQiOmZhbHNlLCJiIjpmYWxzZSwicCI6MC4xNzUsIm1kIjo2MCwiY20iOiJsYWIiLCJmIjoiTGVnYWN5IiwiYyI6ImhzbHV2Iiwic2MiOmZhbHNlLCJidyI6dHJ1ZSwiYWgiOmZhbHNlLCJpdSI6IiIsImxtIjp0cnVlLCJzbSI6ZmFsc2UsImN2IjoiaGV4IiwicW0iOiJhcnQtcGFsZXR0ZSIsIm5sIjoiYmVzdE9mIn0= */ + --moonscape: #7f6c71; + --grandmaโ€™s-pink-tiles: #e1bac0; + --cinderella: #f8d1c6; + --young-apricot: #f8d7b6; + --cereal-flake: #f0d8ad; + --oatmeal: #cdc5b9; + + /* https://farbvelo.elastiq.ch/?s=eyJzIjoiZmZjY2JkZDg2ZjEzYiIsImEiOjYsImNnIjo0LCJoZyI6dHJ1ZSwiaGIiOmZhbHNlLCJobyI6ZmFsc2UsImhjIjpmYWxzZSwiaHQiOmZhbHNlLCJiIjpmYWxzZSwicCI6MC4xNzgzMDcwODQxNjMzNDY2LCJtZCI6NjAsImNtIjoibGFiIiwiZiI6IkxlZ2FjeSIsImMiOiJoc2x1diIsInNjIjpmYWxzZSwiYnciOnRydWUsImFoIjpmYWxzZSwiaXUiOiIiLCJsbSI6dHJ1ZSwic20iOmZhbHNlLCJjdiI6ImhzbCIsInFtIjoiYXJ0LXBhbGV0dGUiLCJubCI6ImJlc3RPZiJ9 */ + --made-in-the-shade: #67717c; + --misty-mountains: #b8cce0; + --lucid-dreams: #c7e6f4; + --icy-breeze: #c2eff1; + --crushed-ice: #bdf5ed; + --water-leaf: #b7efe7; + + /* https://farbvelo.elastiq.ch/?s=eyJzIjoiODJiN2FjMjU1ODRiOCIsImEiOjYsImNnIjo0LCJoZyI6dHJ1ZSwiaGIiOmZhbHNlLCJobyI6ZmFsc2UsImhjIjpmYWxzZSwiaHQiOmZhbHNlLCJiIjpmYWxzZSwicCI6MC4yMTkxOTgyMDcxNzEzMTQ3LCJtZCI6NjAsImNtIjoibGFiIiwiZiI6IkxlZ2FjeSIsImMiOiJoc2x1diIsInNjIjpmYWxzZSwiYnciOnRydWUsImFoIjpmYWxzZSwiaXUiOiIiLCJsbSI6dHJ1ZSwic20iOmZhbHNlLCJjdiI6ImhleCIsInFtIjoiYXJ0LXBhbGV0dGUiLCJubCI6ImJlc3RPZiJ9 */ + --wizards-brew: #9d8bb3; + --innocent-snowdrop: #cec0fa; + --foggy-plateau: #d5d2fb; + --puffy-cloud: #dce3fb; + --diamond-white: #e1f4fb; + --delicate-cloud: #d9dbe4; +} diff --git a/src/themes/webamp/98-vars.css b/src/themes/webamp/98-vars.css new file mode 100644 index 000000000..c0b4e0e3c --- /dev/null +++ b/src/themes/webamp/98-vars.css @@ -0,0 +1,24 @@ +:host { + /* Color */ + --text-color: #222222; + --surface: #c0c0c0; + --button-highlight: #ffffff; + --button-face: #dfdfdf; + --button-shadow: #808080; + --window-frame: #0a0a0a; + --dialog-blue: #000080; + --dialog-blue-light: #1084d0; + --dialog-gray: #808080; + --dialog-gray-light: #b5b5b5; + --link-blue: #0000ff; + + /* Spacing */ + --element-spacing: 8px; + --grouped-button-spacing: 4px; + --grouped-element-spacing: 6px; + --radio-width: 12px; + --checkbox-width: 13px; + --radio-label-spacing: 6px; + --range-track-height: 4px; + --range-spacing: 10px; +} diff --git a/src/themes/webamp/browser/element.js b/src/themes/webamp/browser/element.js new file mode 100644 index 000000000..a94e67a21 --- /dev/null +++ b/src/themes/webamp/browser/element.js @@ -0,0 +1,243 @@ +import { DiffuseElement, query, whenElementsDefined } from "@common/element.js"; +import { signal } from "@common/signal.js"; + +/** + * @import {RenderArg} from "@common/element.d.ts" + * @import {Track} from "@definitions/types.d.ts" + * @import {InputElement} from "@components/input/types.d.ts" + * @import {OutputElement} from "@components/output/types.d.ts" + */ + +class Browser extends DiffuseElement { + constructor() { + super(); + + this.attachShadow({ mode: "open" }); + this.performSearch = this.performSearch.bind(this); + } + + // SIGNALS + + #searchResults = signal(/** @type {Track[]} */ ([])); + + $input = signal(/** @type {InputElement | undefined} */ (undefined)); + $output = signal( + /** @type {OutputElement | undefined} */ (undefined), + ); + $queue = signal( + /** @type {import("@components/engine/queue/element.js").CLASS | undefined} */ (undefined), + ); + $search = signal( + /** @type {import("@components/processor/search/element.js").CLASS | undefined} */ (undefined), + ); + + // LIFECYCLE + + /** + * @override + */ + connectedCallback() { + super.connectedCallback(); + + /** @type {InputElement} */ + const input = query(this, "input-selector"); + + /** @type {OutputElement} */ + const output = query(this, "output-selector"); + + /** @type {import("@components/engine/queue/element.js").CLASS} */ + const queue = query(this, "queue-engine-selector"); + + /** @type {import("@components/processor/search/element.js").CLASS} */ + const search = query(this, "search-processor-selector"); + + this.$input.value = input; + this.$output.value = output; + this.$queue.value = queue; + this.$search.value = search; + + // Wait for the above dependencies to be defined, then render again. + whenElementsDefined({ input, output, search }).then(() => { + this.effect(() => { + const _cacheId = search.cacheId(); + this.performSearch(); + }); + + this.effect(() => { + this.forceRender(); + }); + }); + + // Effects + this.effect(() => { + const _results = this.#searchResults.value; + this.root().querySelector(".sunken-panel")?.scrollTo(0, 0); + }); + } + + // EVENTS + + /** + * @param {MouseEvent} event + */ + highlightTableEntry(event) { + if (event.target instanceof HTMLElement === false) return; + + const tr = event.target.tagName === "TR" + ? event.target + : event.target.closest("tr"); + if (!tr) return; + + tr.parentElement?.querySelector("tr.highlighted")?.classList.remove( + "highlighted", + ); + + tr.classList.add("highlighted"); + } + + /** + * @param {Track} track + */ + playTrack(track) { + this.$queue.value?.add({ + inFront: true, + tracks: [track], + }); + + this.$queue.value?.shift(); + } + + async performSearch() { + /** @type {HTMLInputElement | null} */ + const input = this.root().querySelector("#search-input"); + const term = input?.value?.trim(); + + this.#searchResults.value = await this.$search.value?.search({ + term: term, + }) ?? []; + } + + // RENDER + + /** + * @param {RenderArg} _ + */ + render({ html }) { + const isLoading = this.$output.value?.tracks?.state() !== "loaded" || + (this.$output.value?.tracks?.collection()?.length && + this.$search.value?.cacheId() === undefined); + const tracks = this.#searchResults.value; + + return html` + + + + + + + + + +
    + + + + + + + + + + ${isLoading + ? html` + + + + + + ` + : tracks.map((track) => { + return html` + + + + + + `; + })} + +
    TitleArtistAlbum
    Loading ...
    ${track.tags?.title}${track.tags?.artist}${track.tags?.album}
    +
    + `; + } +} + +export default Browser; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = Browser; +export const NAME = "dtw-browser"; + +customElements.define(NAME, CLASS); diff --git a/src/themes/webamp/browser/index.vto b/src/themes/webamp/browser/index.vto new file mode 100644 index 000000000..08b9895c4 --- /dev/null +++ b/src/themes/webamp/browser/index.vto @@ -0,0 +1,47 @@ +--- +layout: layouts/diffuse.vto +base: ../../../ + +styles: + - styles/vendor/98.css + - themes/webamp/fonts.css + - themes/webamp/constituent.css +--- + + + +
    +
    +
    + +
    +
    + Browse collection +
    +
    + +
    +
    +
    + +
    +
    + + + + diff --git a/src/themes/webamp/configurators/output.js b/src/themes/webamp/configurators/output.js new file mode 100644 index 000000000..642415e9f --- /dev/null +++ b/src/themes/webamp/configurators/output.js @@ -0,0 +1,46 @@ +import { DiffuseElement, query, whenElementsDefined } from "@common/element.js"; +import { signal } from "@common/signal.js"; + +/** + * @import {RenderArg} from "@common/element.d.ts" + * @import {Track} from "@definitions/types.d.ts" + * @import {InputElement} from "@components/input/types.d.ts" + * @import {OutputElement} from "@components/output/types.d.ts" + */ + +class OutputConfig extends DiffuseElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + } + + // RENDER + + /** + * @param {RenderArg} _ + */ + render({ html }) { + return html` + + +
    +

    Where do you want to keep your data?

    +
    + + +
    +
    + `; + } +} + +export default OutputConfig; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = OutputConfig; +export const NAME = "dtw-output-config"; + +customElements.define(NAME, CLASS); diff --git a/src/themes/webamp/constituent.css b/src/themes/webamp/constituent.css new file mode 100644 index 000000000..c23668def --- /dev/null +++ b/src/themes/webamp/constituent.css @@ -0,0 +1,15 @@ +body { + margin: 0; +} + +.window { + box-sizing: border-box; + display: flex; + flex-direction: column; + height: 100dvh; +} + +.window-body { + flex: 1; + overflow: hidden; +} diff --git a/src/themes/webamp/fonts.css b/src/themes/webamp/fonts.css new file mode 100644 index 000000000..ea2078fc1 --- /dev/null +++ b/src/themes/webamp/fonts.css @@ -0,0 +1,13 @@ +@font-face { + font-family: "Pixelated MS Sans Serif"; + src: url("../../fonts/ms_sans_serif.woff2") format("woff2"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "Pixelated MS Sans Serif"; + src: url("../../fonts/ms_sans_serif_bold.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} diff --git a/src/themes/webamp/index.css b/src/themes/webamp/index.css new file mode 100644 index 000000000..b6346d7f6 --- /dev/null +++ b/src/themes/webamp/index.css @@ -0,0 +1,66 @@ +/*********************************** + * ๐ŸชŸ + ***********************************/ + +body { + background: #3a6ea5; + color: white; + font-family: "Pixelated MS Sans Serif", sans-serif; + font-size: 12px; + margin: 12px; + overflow: hidden; +} + +#webamp { + isolation: isolate; +} + +main > section { + inset: 0; + position: absolute; +} + +/*********************************** + * Desktop + ***********************************/ +.desktop { + align-items: start; + display: flex; + flex-wrap: wrap; + gap: 12px; + inset: 12px; +} + +.desktop__item { + align-items: center; + background: transparent; + border: 0; + cursor: pointer; + color: inherit; + display: inline-flex; + flex-direction: column; + font-family: inherit; + text-decoration: none; + user-select: none; + + &:visited, + &:active { + color: inherit; + text-decoration: none; + } + + & > label { + cursor: inherit; + margin-top: 4px; + } + + &:focus { + border: 0; + outline: 0; + + & label { + outline: 1px dotted white; + outline-offset: 2px; + } + } +} diff --git a/src/themes/webamp/index.js b/src/themes/webamp/index.js new file mode 100644 index 000000000..db88fba66 --- /dev/null +++ b/src/themes/webamp/index.js @@ -0,0 +1,179 @@ +import "@components/input/opensubsonic/element.js"; +import "@components/input/s3/element.js"; +import "@components/orchestrator/input/element.js"; +import "@components/orchestrator/output/element.js"; +import "@components/orchestrator/process-tracks/element.js"; +import "@components/orchestrator/queue-tracks/element.js"; +import "@components/orchestrator/search-tracks/element.js"; +import "@components/processor/metadata/element.js"; + +import * as Input from "@components/configurator/input/element.js"; +import * as Queue from "@components/engine/queue/element.js"; +import * as Search from "@components/processor/search/element.js"; + +import { component } from "@common/element.js"; +import { effect, signal, untracked } from "@common/signal.js"; + +import "./browser/element.js"; +import "./configurators/output.js"; +import "./window/element.js"; + +import WindowManager from "./window-manager/element.js"; +import WebampElement from "./webamp/element.js"; + +/** + * @import {OutputElement} from "@components/output/types.d.ts" + * @import {Track} from "@definitions/types.d.ts" + */ + +const input = component(Input); +const queue = component(Queue); +const search = component(Search); + +/** @type {OutputElement | null} */ +const output = document.querySelector("#output"); +if (!output) throw new Error("Missing output element"); + +globalThis.queue = queue; +globalThis.output = output; + +//////////////////////////////////////////// +// ๐Ÿ“ก +//////////////////////////////////////////// + +const $playlist = signal(/** @type {Array} */ ([]), { + eager: true, +}); + +//////////////////////////////////////////// +// โšก๏ธ +//////////////////////////////////////////// + +const ampElement = document.querySelector("dtw-webamp"); +if (ampElement instanceof WebampElement === false) { + throw new Error("Missing webamp element"); +} + +const amp = ampElement.amp; + +// Override track loader +const loadFromUrl = amp.media.loadFromUrl.bind(amp.media); + +/** + * @param {string} uri + * @param {boolean} autoPlay + */ +async function loadOverride(uri, autoPlay) { + const resp = await input.resolve({ method: "GET", uri }); + if (!resp) throw new Error("Failed to resolve URI"); + if (resp && "stream" in resp) { + throw new Error("Webamp does not support playing streams."); + } + + return await loadFromUrl(resp.url, autoPlay); +} + +amp.media.loadFromUrl = loadOverride.bind(amp.media); + +//////////////////////////////////////////// +// ๐Ÿ“ก +//////////////////////////////////////////// + +/** + * Whenever the queue changes update the playlist. + */ +effect(() => { + const now = untracked(queue.now); + const past = untracked(queue.past); + const future = queue.future(); + + const playlist = [ + ...past, + ...(now ? [now] : []), + ...future, + ]; + + const lengthLastPlaylist = untracked($playlist.get).length; + const tracksToAdd = playlist.slice(lengthLastPlaylist); + + $playlist.value = playlist; + + tracksToAdd.forEach((t) => ampElement.addTrack(t)); + + if (lengthLastPlaylist === 0 && playlist.length) { + amp.store.dispatch({ type: "BUFFER_TRACK", id: 0 }); + } +}); + +/** + * Keep note of when search is ready. + */ +const tracksPromise = Promise.withResolvers(); + +effect(() => { + const state = output.tracks.state(); + if (state !== "loaded") return; + + const cacheId = search.cacheId(); + if (cacheId === undefined) return; + + tracksPromise.resolve("loaded"); +}); + +//////////////////////////////////////////// +// DESKTOP +//////////////////////////////////////////// + +// Open associated window when click desktop items +document.body.querySelectorAll(".desktop__item").forEach((element) => { + if (element instanceof HTMLElement) { + element.addEventListener("dblclick", () => { + const f = element.querySelector("label")?.getAttribute("for"); + if (f) return windowManager()?.toggleWindow(f); + }); + } +}); + +// Add batch +document.body.querySelector("#desktop-batch")?.addEventListener( + "dblclick", + () => { + tracksPromise.promise.then(() => { + addBatch(); + }); + }, +); + +// Toggle Winamp if click that desktop item +let winampIsShown = true; + +document.body.querySelector("#desktop-winamp")?.addEventListener( + "dblclick", + () => { + if (winampIsShown) { + amp.close(); + } else { + amp.reopen(); + winampIsShown = true; + } + }, +); + +amp.onClose(() => winampIsShown = false); + +//////////////////////////////////////////// +// ๐Ÿ› ๏ธ +//////////////////////////////////////////// + +async function addBatch() { + await queue.fill({ augment: true, amount: 50, shuffled: true }); + + // Automatically insert track if there isn't any + if (!queue.now()) await queue.shift(); +} + +function windowManager() { + const w = document.body.querySelector("dtw-window-manager"); + if (w instanceof WindowManager) return w; + return null; +} diff --git a/src/themes/webamp/index.vto b/src/themes/webamp/index.vto new file mode 100644 index 000000000..8cf36e1ac --- /dev/null +++ b/src/themes/webamp/index.vto @@ -0,0 +1,184 @@ +--- +layout: layouts/diffuse.vto +base: ../../ + +styles: + - themes/webamp/fonts.css + - themes/webamp/index.css +--- + + +
    + +
    + + + + + + + Manage audio inputs +

    ๐Ÿ‘€

    +
    + + + + + Manage user data + + + + + + + + Browse collection + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + diff --git a/src/themes/webamp/webamp/element.js b/src/themes/webamp/webamp/element.js new file mode 100644 index 000000000..76c64187a --- /dev/null +++ b/src/themes/webamp/webamp/element.js @@ -0,0 +1,112 @@ +import Webamp from "webamp/lazy"; + +/** + * @import {Track} from "@definitions/types.d.ts" + */ +class WebampElement extends HTMLElement { + constructor() { + super(); + + // โšก + + /** @type {import("webamp/lazy").default} */ + this.amp = new /** @type {any} */ (Webamp)({ + enableMediaSession: true, + initialTracks: [], + zIndex: 99, + + /** */ + handleLoadListEvent: async () => { + // TODO + return [ + /* Array of Tracks */ + ]; + }, + + /** + * @param {any} tracks + */ + handleSaveListEvent: (tracks) => { + // TODO + }, + }); + } + + // LIFECYCLE + + connectedCallback() { + this.attachShadow({ mode: "open" }); + + // Custom webamp rendering + this.renderWebamp(); + } + + // ACTIONS + + /** + * @param {Track} track + * @param {number} [idx] + */ + addTrack(track, idx) { + idx = idx ?? (this.amp.getPlaylistTracks().length); + + this.amp.store.dispatch( + /** + * @param {any} dispatch + */ + (dispatch) => { + dispatch({ + type: "ADD_TRACK_FROM_URL", + url: track.uri, + duration: track.stats?.duration, + defaultName: undefined, + id: idx, + atIndex: idx, + }); + + dispatch({ + type: "SET_MEDIA_DURATION", + duration: track.stats?.duration, + id: idx, + }); + + dispatch({ + type: "SET_MEDIA_TAGS", + artist: track.tags?.artist, + title: track.tags?.title, + album: track.tags?.album, + sampleRate: track.stats?.sampleRate ?? 44000, + bitrate: track.stats?.bitrate ?? 192000, + numberOfChannels: 2, // TODO + id: idx, + }); + }, + ); + } + + // RENDER + + async renderWebamp() { + // Ideally this would render in the shadow root, + // but sadly it does not. + + const ampNode = document.createElement("main"); + ampNode.style = + "height: 100vh; left: 0; position: absolute; top: 0; width: 100vw; z-index: -1000;"; + + this.shadowRoot?.appendChild(ampNode); + + return await this.amp.renderWhenReady(ampNode); + } +} + +export default WebampElement; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = WebampElement; +export const NAME = "dtw-webamp"; + +customElements.define(NAME, WebampElement); diff --git a/src/themes/webamp/window-manager/element.js b/src/themes/webamp/window-manager/element.js new file mode 100644 index 000000000..fc153d636 --- /dev/null +++ b/src/themes/webamp/window-manager/element.js @@ -0,0 +1,197 @@ +import { DiffuseElement } from "@common/element.js"; +import { signal } from "@common/signal.js"; +import { debounceMicrotask } from "@vicary/debounce-microtask"; + +import WindowElement from "../window/element.js" + +/** + * @import {RenderArg} from "@common/element.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +class WindowManager extends DiffuseElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + + this.focusOnWindow = this.focusOnWindow.bind(this) + this.windowMoveStart = this.windowMoveStart.bind(this) + } + + // SIGNALS + + $activeWindow = signal(/** @type {string | null} */ (null)); + #lastZindex = 1000; + + // LIFECYCLE + + /** + * @override + */ + async connectedCallback() { + super.connectedCallback(); + + // Events + this.root().addEventListener("mousedown", this.focusOnWindow); + this.root().addEventListener("dtw-window-start-move", this.windowMoveStart); + + // Webamp stuff + document.body.addEventListener( + "mousedown", + this.bringWebampToFront.bind(this), + ); + + // React to active window changing + this.effect(() => { + const activeId = this.$activeWindow.value; + this.setWindowStatuses(activeId); + }); + } + + /** + * @override + */ + disconnectedCallback() { + super.disconnectedCallback(); + + this.root().removeEventListener("mousedown", this.focusOnWindow); + this.root().removeEventListener("dtw-window-start-move", this.windowMoveStart); + + document.body.removeEventListener( + "mousedown", + this.bringWebampToFront.bind(this), + ); + } + + /** + * @param {MouseEvent} event + */ + bringWebampToFront(event) { + if (event.target instanceof HTMLElement) { + const webamp = event.target?.closest("#webamp"); + if (webamp instanceof HTMLElement) { + this.#lastZindex++; + webamp.style.zIndex = this.#lastZindex.toString(); + } + } + } + + /** + * @param {Event} event + */ + focusOnWindow(event) { + if (event.target instanceof HTMLElement) { + const win = event.target?.closest("dtw-window"); + if (win instanceof HTMLElement === false) return; + if (win.id) this.$activeWindow.value = win.id; + + this.#lastZindex++; + win.style.zIndex = this.#lastZindex.toString(); + } + } + + /** + * @param {string | null} activeId + */ + async setWindowStatuses(activeId) { + await customElements.whenDefined("dtw-window"); + this.activateWindow(activeId) + } + + /** + * @param {any} ogEvent + */ + windowMoveStart(ogEvent) { + /** + * @param {Event} event + */ + const moveFn = debounceMicrotask((event) => { + if (event instanceof MouseEvent) { + const x = event.x - ogEvent.detail.xElement; + const y = event.y - ogEvent.detail.yElement; + const target = ogEvent.detail.element; + + if (target) { + target.style.left = `${x}px`; + target.style.top = `${y}px`; + } + } + }, { + updateArguments: true, + }); + + const stopMove = () => { + document.removeEventListener("mousemove", moveFn); + document.removeEventListener("mouseup", stopMove); + document.removeEventListener("mouseleave", stopMove); + }; + + document.addEventListener("mousemove", moveFn); + document.addEventListener("mouseup", stopMove); + document.addEventListener("mouseleave", stopMove); + } + + // ACTIONS + + /** + * @param {string | null} activeId + */ + activateWindow(activeId) { + this.querySelectorAll("dtw-window").forEach(w => { + if (w instanceof WindowElement === false) return + + if (activeId && w.id === activeId) { + w.activate(); + } else { + w.deactivate(); + } + }) + } + + /** + * @param {string} id + */ + toggleWindow(id) { + const w = this.querySelector(`dtw-window#${id}`) + if (w instanceof WindowElement === false) return + + w.toggleAttribute("open") + + if (w.hasAttribute("open")) { + this.activateWindow(id) + this.#lastZindex++; + w.style.zIndex = this.#lastZindex.toString(); + } + } + + // RENDER + + /** + * @param {RenderArg} _ + */ + render({ html }) { + return html` + + + + `; + } +} + +export default WindowManager; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = WindowManager; +export const NAME = "dtw-window-manager"; + +customElements.define(NAME, WindowManager); diff --git a/src/themes/webamp/window/element.js b/src/themes/webamp/window/element.js new file mode 100644 index 000000000..baf315817 --- /dev/null +++ b/src/themes/webamp/window/element.js @@ -0,0 +1,126 @@ +import { DiffuseElement } from "@common/element.js"; + +/** + * @import {RenderArg} from "@common/element.d.ts" + */ + +//////////////////////////////////////////// +// ELEMENT +//////////////////////////////////////////// + +class WindowElement extends DiffuseElement { + static observedAttributes = ["open"]; + + constructor() { + super(); + + this.id = this.id?.length ? this.id : crypto.randomUUID(); + this.attachShadow({ mode: "open" }); + } + + // ACTIONS + + activate() { + this.shadowRoot?.querySelector(".title-bar")?.classList.remove("inactive"); + } + + deactivate() { + this.shadowRoot?.querySelector(".title-bar")?.classList.add("inactive"); + } + + // RENDER + + /** + * @param {RenderArg} _ + */ + render({ html }) { + return html` + + + + + +
    +
    +
    + +
    +
    + +
    +
    + + + +
    +
    +
    + +
    +
    +
    + `; + } + + // EVENTS + + /** + * @param {MouseEvent} mouse + */ + titleBarMouseDown(mouse) { + const event = new CustomEvent("dtw-window-start-move", { + bubbles: true, + composed: true, + detail: { + element: this, + x: mouse.x, + xElement: mouse.layerX, + y: mouse.y, + yElement: mouse.layerY, + }, + }); + + this.dispatchEvent(event); + } +} + +export default WindowElement; + +//////////////////////////////////////////// +// REGISTER +//////////////////////////////////////////// + +export const CLASS = WindowElement; +export const NAME = "dtw-window"; + +customElements.define(NAME, WindowElement); diff --git a/system/Build/Build.gren b/system/Build/Build.gren deleted file mode 100644 index c2399f6a3..000000000 --- a/system/Build/Build.gren +++ /dev/null @@ -1,223 +0,0 @@ -module Build exposing ( main ) - -import About.Layout -import Bytes exposing ( Bytes ) -import Bytes.Decode -import Bytes.Encode -import Dict -import Json.Encode -import Markdown -import Shikensu -import Shikensu.Bundle as Bundle -import Shikensu.Contrib as Shikensu -import Shikensu.Definition as Definition -import Shikensu.Focus exposing ( Focus(..) ) -import Shikensu.Path as Path exposing (..) -import Shikensu.Path.Encapsulated as Path.Encapsulated -import Task -import Transmutable.Html - - - --- | (โ€ข โ—กโ€ข)| (โแดฅโส‹) - - -main = - Shikensu.programs - [ -- Copy static files to dist - copy (staticDir "Favicons") - , copy (staticDir "Hosting") - , copyInto "fonts" (staticDir "Fonts") - , copyInto "images" (staticDir "Images") - - , -- Copy more static files with some alterations - copyWithAlterations - { focus = staticDir "Html" - , alt = Shikensu.rename (filePath "Application.html") (filePath "index.html") - } - , copyWithAlterations - { focus = staticDir "Manifests" - , alt = Shikensu.rename (filePath "manifest.json") (filePath "site.webmanifest") - } - - , -- Render about pages - about - - , -- Make a file tree so the service worker knows what to cache - tree - ] - - - --- FOCUSES & PATHS - - -dist = - Relative - (Path.directory - [ "dist" - ] - ) - - -filePath path = - path - |> Path.fromPosix - |> Path.Encapsulated.toFile - |> Maybe.withDefault (Path.file (Array.singleton path)) - - -staticDir dirName = - Relative - (Path.directory - [ "src" - , "Static" - , dirName - ] - ) - - - --- PROGRAMS - - -about = - { focus = staticDir "About" - , sequence = read >> Task.map aboutAlts >> write - } - - -copy focus = - { focus = focus - , sequence = read >> write - } - - -copyInto dirName focus = - { focus = focus - , sequence = read >> Task.map (prefixDirname dirName) >> write - } - - -copyWithAlterations { focus, alt } = - { focus = focus - , sequence = read >> Task.map alt >> write - } - - -tree = - { focus = dist - , sequence = - Task.map - (\bundle -> - bundle.compendium - |> Array.map - (\def -> - def - |> Definition.relativePath - |> Path.toPosix - { absolute = False - } - ) - |> Array.filter (String.contains "images/Background/" >> (==) False) - |> Json.Encode.array Json.Encode.string - |> Json.Encode.encode 0 - |> stringToBytes - |> (\content -> - { baseName = "tree" - , content = Just content - , directoryPath = Path.directory [] - , extensionName = Just "json" - , metadata = Dict.empty - } - ) - |> (\def -> - { bundle - | compendium = - [ def - ] - } - ) - ) - >> write - } - - - --- ALTERATIONS - - -aboutAlts bundle = - bundle - |> Shikensu.withExtension "md" - |> lowerCasePath - |> prefixDirname "about" - |> Shikensu.renameExtension "md" "html" - |> Shikensu.permalink "index" - |> Shikensu.renderContent - (\def -> - def.content - |> Maybe.andThen bytesToString - |> Maybe.withDefault "" - |> Markdown.parse - { frontmatter = Nothing - } - |> (\{ blocks } -> Array.map Markdown.toHtml blocks) - |> About.Layout.layout - { pathToRoot = - def.directoryPath - |> Path.unwrap - |> Array.map (\_ -> "..") - |> String.join "/" - |> (\a -> a ++ "/") - } - |> Transmutable.Html.arrayToString - |> stringToBytes - |> Just - ) - - -lowerCasePath = - (\def -> - def - |> Definition.relativePath - |> Path.map (Array.map String.toLower) - |> (\path -> Definition.fork path def) - ) - |> Array.map - |> Bundle.mapCompendium - - -prefixDirname dirName = - (\def -> { def | directoryPath = Path.map (Array.pushFirst dirName) def.directoryPath }) - |> Array.map - |> Bundle.mapCompendium - - - --- TASKS - - -read = - Task.andThen Shikensu.read - - -write = - Task.andThen (Shikensu.write dist) - - - --- ๐Ÿ› ๏ธ - - -bytesToString : Bytes -> Maybe String -bytesToString bytes = - bytes - |> Bytes.width - |> Bytes.Decode.string - |> (\decoder -> Bytes.Decode.decode decoder bytes) - - -stringToBytes : String -> Bytes -stringToBytes = - Bytes.Encode.string >> Bytes.Encode.encode diff --git a/system/Css/PostCSS.js b/system/Css/PostCSS.js deleted file mode 100644 index dd2e1ded5..000000000 --- a/system/Css/PostCSS.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - plugins: { - // NOTE: Order matters here - "postcss-import": {}, - "postcss-custom-properties": {}, - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/system/Css/Tailwind.js b/system/Css/Tailwind.js deleted file mode 100644 index d8d0307be..000000000 --- a/system/Css/Tailwind.js +++ /dev/null @@ -1,262 +0,0 @@ -const tailwindColors = require("tailwindcss/colors") - - -// Colors -// ------ - -const colors = { - base00: "rgb(45, 45, 45)", - base01: "rgb(63, 63, 63)", - base02: "rgb(79, 79, 79)", - base03: "rgb(119, 119, 119)", - base04: "rgb(140, 140, 140)", - base05: "rgb(163, 163, 163)", - base06: "rgb(186, 186, 186)", - base07: "rgb(232, 232, 232)", - base08: "rgb(239, 97, 85)", - base09: "rgb(249, 155, 21)", - base0a: "rgb(254, 196, 24)", - base0b: "rgb(72, 182, 133)", - base0c: "rgb(91, 196, 191)", - base0d: "rgb(6, 182, 239)", - base0e: "rgb(129, 91, 164)", - base0f: "rgb(233, 107, 168)" -} - - - -// Config -// ------ - -module.exports = { - - theme: { - - // Animations - // ---------- - - animations: { - - "fadeIn": { - from: { opacity: "0" }, - to: { opacity: "1" }, - } - - }, - - animationDelay: { - "50ms": "50ms" - }, - - - // Fonts - // ----- - - fontFamily: { - body: '"Source Sans Pro", sans-serif', - display: '"Montserrat", Futura, "Trebuchet MS", Arial, sans-serif', - mono: 'Hack, Consolas, Menlo, Monaco, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", "Courier New", Courier, monospace' - }, - - - // Colors - // ------ - - colors: { - ...colors, - - black: tailwindColors.black, - current: tailwindColors.current, - inherit: tailwindColors.inherit, - transparent: tailwindColors.transparent, - white: tailwindColors.white, - - gray: tailwindColors.gray, - neutral: tailwindColors.neutral, - - "accent-btn": "hsl(219, 20.2%, 38.8%)", - "accent-dark": "hsl(304.3, 9.6%, 71.4%)", - "accent-light": "hsl(228.4, 15.3%, 60.2%)", - "background": "rgb(2, 7, 14)", - "base01-15": "rgba(63, 63, 63, 0.15)", - "base01-55": "rgba(63, 63, 63, 0.55)", - "black-05": "rgba(0, 0, 0, 0.05)", - "black-35": "rgba(0, 0, 0, 0.35)", - "black-50": "rgba(0, 0, 0, 0.5)", - "current-color": "currentColor", - "white-025": "rgba(255, 255, 255, 0.025)", - "white-20": "rgba(255, 255, 255, 0.2)", - "white-60": "rgba(255, 255, 255, 0.6)", - "white-90": "rgba(255, 255, 255, 0.9)", - - // Darkest hour - - "darkest-hour": "hsl(0, 0%, 14%)", - "near-darkest-hour": "hsl(0, 0%, 15%)", - - // Shades of gray - - gray: { - "100": "hsl(0, 0%, 98.8%)", - "200": "hsl(0, 0%, 97.3%)", - "300": "hsl(0, 0%, 93.3%)", - "400": "hsl(0, 0%, 88.2%)", - "500": "hsl(0, 0%, 86.3%)", - "600": "hsl(0, 0%, 77.6%)" - } - }, - - - // Extensions - // ---------- - - extend: { - - boxShadow: { - "md-darker": "0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.36)" - }, - - fontSize: { - "0": 0, - "almost-sm": "0.78125rem", - "nearly-sm": "0.8125rem", - "xxs": "0.6875rem", - }, - - letterSpacing: { - "tad-closer": "-0.015em", - "tad-further": "0.015em", - }, - - lineHeight: { - "0": 0 - }, - - maxWidth : { - "insulation": "107.5vh", - "screen": "100vw" - }, - - minWidth: { - "3xl": "48rem", - "tiny": "13.125rem" - }, - - screens: { - "dark": { "raw": "(prefers-color-scheme: dark)" } - }, - - spacing: { - "1/2": "50%", - "1/3": "33.333333%", - "2/3": "66.666667%", - "1/4": "25%", - "2/4": "50%", - "3/4": "75%", - "1/5": "20%", - "2/5": "40%", - "3/5": "60%", - "4/5": "80%", - "1/6": "16.666667%", - "2/6": "33.333333%", - "3/6": "50%", - "4/6": "66.666667%", - "5/6": "83.333333%", - "1/7": "14.28571429%", - "1/8": "12.5%", - "1/10": "10%", - "1/12": "8.333333%", - "1/16": "6.25%", - "1/20": "5%", - "2/12": "16.666667%", - "3/12": "25%", - "4/12": "33.333333%", - "5/12": "41.666667%", - "6/12": "50%", - "7/12": "58.333333%", - "8/12": "66.666667%", - "9/12": "75%", - "10/12": "83.333333%", - "11/12": "91.666667%", - "-full": "-100%", - "full": "100%", - } - - }, - - - // Inset - // ----- - - inset: { - "0": 0, - "1/2": "50%", - "full": "100%", - "-px": "-1px" - }, - - - // Opacity - // ------- - - opacity: { - "0": "0", - "025": ".025", - "05": ".05", - "10": ".1", - "20": ".2", - "25": ".25", - "30": ".3", - "40": ".4", - "50": ".5", - "60": ".6", - "70": ".7", - "75": ".75", - "80": ".8", - "90": ".9", - "95": ".95", - "100": "1" - }, - - }, - - - - // PLUGINS - - - plugins: [ - - require("tailwindcss-animations"), - require("tailwindcss-interaction-variants"), - - // Add variant for `:focus:not(:active)` - function({ addVariant, e }) { - addVariant("inactive-focus", ({ modifySelectors, separator }) => { - modifySelectors(({ className }) => { - return `.${e(`fixate${separator}${className}`)}:focus:not(:active)` - }) - }) - } - - ], - - - - // VARIANTS - - - variants: { - - backgroundColor: [ "focus", "hover", "inactive-focus", "responsive" ], - borderColor: [ "first", "focus", "hover", "inactive-focus", "last", "responsive" ], - borderWidth: [ "first", "last" ], - cursor: [ "first", "last" ], - margin: [ "first", "last", "responsive" ], - opacity: [ "focus", "hocus", "hover", "responsive" ], - padding: [ "first", "last", "responsive" ], - textColor: [ "focus", "focus-within", "hover", "inactive-focus", "responsive" ], - - } - -} diff --git a/system/Js/esbuild.mjs b/system/Js/esbuild.mjs deleted file mode 100644 index d23d94c93..000000000 --- a/system/Js/esbuild.mjs +++ /dev/null @@ -1,47 +0,0 @@ -import esbuild from "esbuild" -import { wasmLoader } from "esbuild-plugin-wasm" - -import parseArgv from "tiny-parse-argv" - -const args = parseArgv(process.argv.slice(2), { - string: ["alias", "define", "inject"], -}) - -esbuild.build({ - alias: {...obj("alias") }, - bundle: true, - define: obj("define"), - entryPoints: [args._[0]], - format: "esm", - inject: arr("inject"), - minify: args.minify || false, - outdir: args.outdir || undefined, - outfile: args.outfile || undefined, - plugins: [wasmLoader()], - splitting: args.splitting || false, - target: "esnext", -}) - -function arr(name) { - return Object.entries(args) - .filter(([k, v]) => { - if (!k.includes(":")) return false - return k.split(":")[0] == name - }) - .map(([k, v]) => { - return k.split(":").slice(1).join(":") + v - }) -} - -function obj(name) { - const entries = Object.entries(args) - .filter(([k, v]) => { - if (!k.includes(":")) return false - return k.split(":")[0] == name - }) - .map(([k, v]) => { - return [k.split(":").slice(1).join(":"), v.toString()] - }) - - return Object.fromEntries(entries) -} diff --git a/system/Js/node-shims.js b/system/Js/node-shims.js deleted file mode 100644 index 41a0baf05..000000000 --- a/system/Js/node-shims.js +++ /dev/null @@ -1,10 +0,0 @@ -import BufferPolyfill from "buffer/" - -export let Buffer = BufferPolyfill.Buffer -export let global = globalThis -export let process = { env: { NODE_DEBUG: false } } -export let localStorage = globalThis.localStorage || { - getItem: () => null, - setItem: () => null, - removeItem: () => null -} diff --git a/system/Review/ReviewConfig.elm b/system/Review/ReviewConfig.elm deleted file mode 100644 index abfb06d72..000000000 --- a/system/Review/ReviewConfig.elm +++ /dev/null @@ -1,45 +0,0 @@ -module ReviewConfig exposing (config) - -import NoDebug.Log -import NoDebug.TodoOrToString -import NoDeprecated -import NoDuplicatePorts -import NoExposingEverything -import NoImportingEverything -import NoMissingSubscriptionsCall -import NoMissingTypeAnnotation -import NoMissingTypeAnnotationInLetIn -import NoMissingTypeExpose -import NoPrematureLetComputation -import NoRecursiveUpdate -import NoUnoptimizedRecursion -import NoUnsafeDivision -import NoUnsafePorts -import NoUnused.CustomTypeConstructors -import NoUnused.Dependencies -import NoUnused.Exports -import NoUnused.Modules -import NoUnused.Variables -import NoUnusedPorts -import NoUselessSubscriptions -import Review.Rule exposing (Rule) - - -config : List Rule -config = - [ NoDebug.Log.rule - , NoDeprecated.rule NoDeprecated.defaults - , NoMissingSubscriptionsCall.rule - -- , NoPrematureLetComputation.rule - -- , NoRecursiveUpdate.rule - -- , NoUnoptimizedRecursion.rule (NoUnoptimizedRecursion.optOutWithComment "IGNORE TCO") - - -- , NoUnsafeDivision.rule - -- , NoUselessSubscriptions.rule - -- - -- Unused - --------- - , NoUnused.Dependencies.rule - , NoUnused.Modules.rule - , NoUnused.Variables.rule - ] diff --git a/system/Review/elm.json b/system/Review/elm.json deleted file mode 100644 index 96702066b..000000000 --- a/system/Review/elm.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "type": "application", - "source-directories": [ - "." - ], - "elm-version": "0.19.1", - "dependencies": { - "direct": { - "elm/core": "1.0.5", - "elm/json": "1.1.3", - "jfmengels/elm-review": "2.13.1", - "jfmengels/elm-review-common": "1.3.2", - "jfmengels/elm-review-debug": "1.0.8", - "jfmengels/elm-review-performance": "1.0.2", - "jfmengels/elm-review-the-elm-architecture": "1.0.3", - "jfmengels/elm-review-unused": "1.1.29", - "sparksp/elm-review-ports": "1.3.1", - "vkfisher/elm-review-no-unsafe-division": "1.0.3" - }, - "indirect": { - "elm/bytes": "1.0.8", - "elm/html": "1.0.0", - "elm/parser": "1.1.0", - "elm/project-metadata-utils": "1.0.2", - "elm/random": "1.0.0", - "elm/time": "1.0.0", - "elm/virtual-dom": "1.0.3", - "elm-community/list-extra": "8.7.0", - "elm-community/maybe-extra": "5.3.0", - "elm-explorations/test": "2.1.0", - "miniBill/elm-unicode": "1.0.3", - "rtfeldman/elm-hex": "1.0.0", - "stil4m/elm-syntax": "7.2.9", - "stil4m/structured-writer": "1.0.3" - } - }, - "test-dependencies": { - "direct": {}, - "indirect": {} - } -}