diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f053231ec..94adaaf77 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -43,13 +43,15 @@ jobs: DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_APPLICATION_CERTIFICATE_BASE64 }} steps: - name: Set the timezone to New Zealand - uses: szenius/set-timezone@v2.0 - with: - timezoneWindows: 'New Zealand Standard Time' - timezoneLinux: 'Pacific/Auckland' - timezoneMacos: 'Pacific/Auckland' + shell: bash + run: | + case "${RUNNER_OS}" in + Windows) MSYS_NO_PATHCONV=1 tzutil /s "New Zealand Standard Time" ;; + Linux) sudo timedatectl set-timezone Pacific/Auckland ;; + macOS) sudo systemsetup -settimezone Pacific/Auckland ;; + esac - name: Check out OpenCOR - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Retrieve the EV certificate (Windows only) if: ${{ runner.os == 'Windows' }} shell: bash @@ -95,13 +97,16 @@ jobs: if: ${{ github.repository == 'opencor/webapp' }} name: Web app runs-on: ubuntu-22.04 + concurrency: + group: web-app-deployment-app + cancel-in-progress: false + # Note: our deployments must not overlap since they would otherwise interfere with one another, e.g. when uploading + # index.html or when pruning our old assets (see below). steps: - name: Set the timezone to New Zealand - uses: szenius/set-timezone@v2.0 - with: - timezoneLinux: 'Pacific/Auckland' + run: sudo timedatectl set-timezone Pacific/Auckland - name: Check out OpenCOR - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install bun uses: oven-sh/setup-bun@v2 - name: OpenCOR dependencies @@ -113,7 +118,13 @@ jobs: VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }} VITE_FIREBASE_MEASUREMENT_ID: ${{ secrets.VITE_FIREBASE_MEASUREMENT_ID }} run: bun build:web + - name: Archive OpenCOR's Web app + id: archive + run: bun archive:web + # Note: we archive our Web app before deploying it since the deployment adds a .deploy-running file to + # src/renderer/dist, which would otherwise end up in our archive. - name: Deploy to https://opencor.ws/app/ + id: deploy uses: milanmk/actions-file-deployer@master with: remote-protocol: sftp @@ -123,28 +134,48 @@ jobs: local-path: ./src/renderer/dist remote-path: /home/www/opencor.ws/app sync: full - # Note: we upload version.json last so that OpenCOR's Web app only reports a new version once all the other - # files have been uploaded. - ftp-mirror-options: --exclude-glob=assets/version.json - ftp-post-sync-commands: put -O /home/www/opencor.ws/app/assets ./src/renderer/dist/assets/version.json - - name: Archive OpenCOR's Web app - run: bun archive:web + ftp-options: set cmd:fail-exit yes; set xfer:use-temp-file yes + # Note #1: lftp carries on after a command has failed, so we ask it to exit instead. Otherwise, if some of our + # assets couldn't be uploaded, index.html and version.json would still get uploaded (see below), and + # index.html would reference assets that are not available. + # Note #2: lftp uploads a file in place by default, and it uploads all our files on every deployment (since + # they are all newer than their remote counterpart), including assets that are still referenced by + # the live index.html. So, a browser could get a partially uploaded asset and, since our hashed + # assets are cached for good (see public/.htaccess), keep using it. So, we ask lftp to upload each + # file to a temporary file (named .in.), which it then renames to its final name. + ftp-mirror-options: --exclude-glob=index.html --exclude-glob=assets/version.json + ftp-post-sync-commands: put -O /home/www/opencor.ws/app ./src/renderer/dist/index.html; put -O /home/www/opencor.ws/app/assets ./src/renderer/dist/assets/version.json + # Note: our assets have a hash in their file names, so we upload index.html (which references them) once they + # have all been uploaded, so that it never references assets that are not available yet. Then, we upload + # version.json last so that OpenCOR's Web app only reports a new version once all the other files have + # been uploaded. - name: Release OpenCOR's Web app - uses: softprops/action-gh-release@v2 + if: ${{ !cancelled() && steps.archive.outcome == 'success' }} + uses: softprops/action-gh-release@v3 with: files: dist/OpenCOR-*-WebApp.tar.gz + # Note: we release our Web app even if it couldn't be deployed, so that a deployment failure (e.g., a transient + # SFTP error) doesn't also deprive our release of its Web app archive. + - name: Prune old assets from https://opencor.ws/app/ + if: ${{ !cancelled() && steps.deploy.outcome == 'success' }} + env: + REMOTE_HOST: ${{ secrets.REMOTE_HOST }} + REMOTE_USER: ${{ secrets.REMOTE_USER }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + run: bun prune:web /home/www/opencor.ws/app + # Note: our assets have a hash in their file names, so old assets would otherwise accumulate on the server. They + # are only deleted after a grace period since some clients may still be using a previous index.html (see + # src/renderer/scripts/prune.web.ts). npm: if: ${{ github.repository == 'opencor/webapp' }} runs-on: ubuntu-22.04 steps: - name: Set the timezone to New Zealand - uses: szenius/set-timezone@v2.0 - with: - timezoneLinux: 'Pacific/Auckland' + run: sudo timedatectl set-timezone Pacific/Auckland - name: Check out OpenCOR - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: 'lts/*' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/cddev.yml b/.github/workflows/cddev.yml index 140556155..22bce5b49 100644 --- a/.github/workflows/cddev.yml +++ b/.github/workflows/cddev.yml @@ -10,13 +10,16 @@ jobs: if: ${{ github.repository == 'opencor/webapp' }} name: Web app runs-on: ubuntu-22.04 + concurrency: + group: web-app-deployment-appdev + cancel-in-progress: false + # Note: our deployments must not overlap since they would otherwise interfere with one another, e.g. when uploading + # index.html or when pruning our old assets (see below). steps: - name: Set the timezone to New Zealand - uses: szenius/set-timezone@v2.0 - with: - timezoneLinux: 'Pacific/Auckland' + run: sudo timedatectl set-timezone Pacific/Auckland - name: Check out OpenCOR - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install bun uses: oven-sh/setup-bun@v2 - name: OpenCOR dependencies @@ -38,7 +41,27 @@ jobs: local-path: ./src/renderer/dist remote-path: /home/www/opencor.ws/appdev sync: full - # Note: we upload version.json last so that OpenCOR's Web app only reports a new version once all the other - # files have been uploaded. - ftp-mirror-options: --exclude-glob=assets/version.json - ftp-post-sync-commands: put -O /home/www/opencor.ws/appdev/assets ./src/renderer/dist/assets/version.json + ftp-options: set cmd:fail-exit yes; set xfer:use-temp-file yes + # Note #1: lftp carries on after a command has failed, so we ask it to exit instead. Otherwise, if some of our + # assets couldn't be uploaded, index.html and version.json would still get uploaded (see below), and + # index.html would reference assets that are not available. + # Note #2: lftp uploads a file in place by default, and it uploads all our files on every deployment (since + # they are all newer than their remote counterpart), including assets that are still referenced by + # the live index.html. So, a browser could get a partially uploaded asset and, since our hashed + # assets are cached for good (see public/.htaccess), keep using it. So, we ask lftp to upload each + # file to a temporary file (named .in.), which it then renames to its final name. + ftp-mirror-options: --exclude-glob=index.html --exclude-glob=assets/version.json + ftp-post-sync-commands: put -O /home/www/opencor.ws/appdev ./src/renderer/dist/index.html; put -O /home/www/opencor.ws/appdev/assets ./src/renderer/dist/assets/version.json + # Note: our assets have a hash in their file names, so we upload index.html (which references them) once they + # have all been uploaded, so that it never references assets that are not available yet. Then, we upload + # version.json last so that OpenCOR's Web app only reports a new version once all the other files have + # been uploaded. + - name: Prune old assets from https://opencor.ws/appdev/ + env: + REMOTE_HOST: ${{ secrets.REMOTE_HOST }} + REMOTE_USER: ${{ secrets.REMOTE_USER }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + run: bun prune:web /home/www/opencor.ws/appdev + # Note: our assets have a hash in their file names, so old assets would otherwise accumulate on the server. They + # are only deleted after a grace period since some clients may still be using a previous index.html (see + # src/renderer/scripts/prune.web.ts). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ecb12183..79249440a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,13 +33,15 @@ jobs: os: ubuntu-22.04 steps: - name: Set the timezone to New Zealand - uses: szenius/set-timezone@v2.0 - with: - timezoneWindows: 'New Zealand Standard Time' - timezoneLinux: 'Pacific/Auckland' - timezoneMacos: 'Pacific/Auckland' + shell: bash + run: | + case "${RUNNER_OS}" in + Windows) MSYS_NO_PATHCONV=1 tzutil /s "New Zealand Standard Time" ;; + Linux) sudo timedatectl set-timezone Pacific/Auckland ;; + macOS) sudo systemsetup -settimezone Pacific/Auckland ;; + esac - name: Check out OpenCOR - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install CMake and Ninja uses: lukka/get-cmake@latest - name: Install bun diff --git a/.gitignore b/.gitignore index 977303d43..8f6a96bc3 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,4 @@ out src/renderer/components.d.ts # Generated by Vite -src/renderer/public +src/renderer/public/libopencor diff --git a/CMakeLists.txt b/CMakeLists.txt index 540620f27..f841208fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,12 @@ add_library(${CMAKE_PROJECT_NAME} SHARED ${CMAKE_JS_SRC} ${CPP_FILES}) +# Convert any C++ exception (and not just Napi::Error) into a JavaScript exception rather than have it terminate the +# renderer process. + +target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE + NODE_ADDON_API_CPP_EXCEPTIONS_ALL) + # Don't have a "lib" prefix and use a ".node" suffix. set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES diff --git a/bun.lock b/bun.lock index 45e44f06d..d24094b45 100644 --- a/bun.lock +++ b/bun.lock @@ -5,37 +5,35 @@ "": { "name": "@opencor/opencor", "dependencies": { - "@napi-rs/keyring": "^2.1.0", - "@opencor/libopencor-types": "^1.20261007.0", - "@primeuix/themes": "2.0.3", - "@primevue/auto-import-resolver": "4.2.5", - "@vueuse/core": "12.8.2", - "primeicons": "7.0.0", - "primevue": "4.2.5", - "vue": "3.4.21", + "@napi-rs/keyring": "^2.1.1", "xxhash-wasm": "^1.1.0", }, "devDependencies": { "@biomejs/biome": "^2.5.15", "@electron-toolkit/tsconfig": "^2.0.0", "@electron-toolkit/utils": "^4.0.0", + "@opencor/libopencor-types": "^1.20261016.0", + "@primeuix/themes": "2.0.3", + "@primevue/auto-import-resolver": "4.2.5", "@tailwindcss/postcss": "^4.3.3", "@tailwindcss/vite": "^4.3.3", - "@types/node": "^26.6.4", - "@types/plotly.js": "^3.0.14", - "@vitejs/plugin-vue": "^6.0.9", + "@types/node": "^26.6.5", + "@types/plotly.js": "^3.0.15", + "@vitejs/plugin-vue": "^6.0.10", "@vue/tsconfig": "^0.9.1", + "@vueuse/core": "12.8.2", "@wasm-fmt/clang-format": "^23.1.1", "autoprefixer": "^10.6.1", "cmake-js": "^8.0.0", - "electron": "^44.6.0", + "electron": "^44.7.2", "electron-builder": "^26.15.3", "electron-conf": "^1.3.0", "electron-updater": "^6.8.9", - "electron-vite": "^5.0.0", - "esbuild": "^0.28.2", + "electron-vite": "^6.0.0", "node-addon-api": "^8.9.2", - "postcss": "^8.5.29", + "postcss": "^8.5.30", + "primeicons": "7.0.0", + "primevue": "4.2.5", "rollup-plugin-visualizer": "^7.1.1", "stylelint": "^17.16.0", "stylelint-config-standard": "^40.0.0", @@ -45,8 +43,9 @@ "typescript": "^7.0.2", "typescript-6": "npm:typescript@^6.0.3", "unplugin-vue-components": "^32.1.0", - "vite": "7.3.6", - "vue-tsc": "^3.3.12", + "vite": "^8.3.4", + "vue": "3.4.21", + "vue-tsc": "^3.3.13", }, "peerDependencies": { "vue": "3.4.21", @@ -80,7 +79,7 @@ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], - "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], + "@babel/helpers": ["@babel/helpers@7.29.10", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.8" } }, "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg=="], "@babel/parser": ["@babel/parser@7.29.9", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA=="], @@ -88,7 +87,7 @@ "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], - "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], + "@babel/traverse": ["@babel/traverse@7.29.10", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.9", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ=="], "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], @@ -150,58 +149,6 @@ "@electron/windows-sign": ["@electron/windows-sign@1.2.2", "", { "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" } }, "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], - "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -222,33 +169,31 @@ "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="], - "@napi-rs/keyring": ["@napi-rs/keyring@2.1.0", "", { "optionalDependencies": { "@napi-rs/keyring-darwin-arm64": "2.1.0", "@napi-rs/keyring-darwin-x64": "2.1.0", "@napi-rs/keyring-freebsd-x64": "2.1.0", "@napi-rs/keyring-linux-arm-gnueabihf": "2.1.0", "@napi-rs/keyring-linux-arm64-gnu": "2.1.0", "@napi-rs/keyring-linux-arm64-musl": "2.1.0", "@napi-rs/keyring-linux-riscv64-gnu": "2.1.0", "@napi-rs/keyring-linux-x64-gnu": "2.1.0", "@napi-rs/keyring-linux-x64-musl": "2.1.0", "@napi-rs/keyring-win32-arm64-msvc": "2.1.0", "@napi-rs/keyring-win32-ia32-msvc": "2.1.0", "@napi-rs/keyring-win32-x64-msvc": "2.1.0" } }, "sha512-km9J3fomkGSLpImpelq0cMvLBrJ5eVlTzuWdG3Iy0laP6gU90MmHYjqhUhG32aYq0/0++r2TLuAA9xlnh4XU1g=="], - - "@napi-rs/keyring-darwin-arm64": ["@napi-rs/keyring-darwin-arm64@2.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-j6ATTOhmPW+N5EKwi8Yzn6P4Bz/uqnxzyYvqOL73/dgzDSYG5pS5eprqpVmOLse68WUTf79ZMqz97HnfvirJ9g=="], + "@napi-rs/keyring": ["@napi-rs/keyring@2.1.1", "", { "optionalDependencies": { "@napi-rs/keyring-darwin-arm64": "2.1.1", "@napi-rs/keyring-darwin-x64": "2.1.1", "@napi-rs/keyring-freebsd-x64": "2.1.1", "@napi-rs/keyring-linux-arm-gnueabihf": "2.1.1", "@napi-rs/keyring-linux-arm64-gnu": "2.1.1", "@napi-rs/keyring-linux-arm64-musl": "2.1.1", "@napi-rs/keyring-linux-riscv64-gnu": "2.1.1", "@napi-rs/keyring-linux-x64-gnu": "2.1.1", "@napi-rs/keyring-linux-x64-musl": "2.1.1", "@napi-rs/keyring-win32-arm64-msvc": "2.1.1", "@napi-rs/keyring-win32-ia32-msvc": "2.1.1", "@napi-rs/keyring-win32-x64-msvc": "2.1.1" } }, "sha512-7HUq8FHgW/O4s8ESvkxngTC9tOR/IyktdR0ral2VD0C8HDZk86FwPAH4qZyerKwS1DhJ6AhoebIsxcyw+Lbt1Q=="], - "@napi-rs/keyring-darwin-x64": ["@napi-rs/keyring-darwin-x64@2.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-54Q803nguiOaQzBG2h4uIkacI0eLGAlzzdpxPNi6IZtXbjqbPI4AQ86GrQy25tw3czBqRPBoGu0lHP2U+/f5iA=="], + "@napi-rs/keyring-darwin-arm64": ["@napi-rs/keyring-darwin-arm64@2.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-H6uxAztj6GSDwnh/1JDHCtAqWvBTr+0+FGwOOw+rY568z7vFOqrJTTboePFuAnJSO7kNvq+yuyXJ9B5Ueco4yA=="], - "@napi-rs/keyring-freebsd-x64": ["@napi-rs/keyring-freebsd-x64@2.1.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BL7ngJAYQBrUp1PTF8enOXOIVfxN51RbwVOEQ+8eAjWypF/5Ipqs8oQoMqkuc0wAbk+vatnNVjdKCIFa/HPglg=="], + "@napi-rs/keyring-darwin-x64": ["@napi-rs/keyring-darwin-x64@2.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-RcHBNnOIzqliNU8Z0jc71bPs/PRn4IIO02XE6DV3HxVFAHo4CooNXDySCL+jbHhObLlSy9yynRn+wq5k+IAmRw=="], - "@napi-rs/keyring-linux-arm-gnueabihf": ["@napi-rs/keyring-linux-arm-gnueabihf@2.1.0", "", { "os": "linux", "cpu": "arm" }, "sha512-JYgbCDk+giss7lKla8NTXhM/fLj+Hrfgr3xwKQ8jKn8qMD6vXLwYcfK8HJ9VoAcjIeYEUpCL6dRszJC5M4zFNg=="], + "@napi-rs/keyring-freebsd-x64": ["@napi-rs/keyring-freebsd-x64@2.1.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IrM8IKBZKQzETI1BlvSpT6lRzC9eTIQ5TgUwcbQSiQTuXTEXAAHHvBYkoHicN2b2uqgAfO3dwN382CGRw0Ownw=="], - "@napi-rs/keyring-linux-arm64-gnu": ["@napi-rs/keyring-linux-arm64-gnu@2.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-+PMONFQ+2GkBXG7vRG2spgR73bNCMeVpNopHoSmhj2E+5Gu6zJooRBjoaIZKj6hCyrOU8pXAhoZ22EEFQ/4YeQ=="], + "@napi-rs/keyring-linux-arm-gnueabihf": ["@napi-rs/keyring-linux-arm-gnueabihf@2.1.1", "", { "os": "linux", "cpu": "arm" }, "sha512-KMld1c9TRucQYV/qasA5jS/VZWCpxMOeHflrciOAsfhiVQ8VfA77grlhGD0kiWtz5e+2M6kYLH9iFmQhPROkGw=="], - "@napi-rs/keyring-linux-arm64-musl": ["@napi-rs/keyring-linux-arm64-musl@2.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-dKjiXKTHQjAS57hL0HVzLpW9XYNGFpfYkr/51FzIIvbZR08PAnzIDYKwShoNYGvjQP6+rDFf1a5Utl0aWwcFrA=="], + "@napi-rs/keyring-linux-arm64-gnu": ["@napi-rs/keyring-linux-arm64-gnu@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-AcMk+ZDP8H20Ml5vaTuSh/Ibh5vU4lrAavS2tjoJ7EJJBLDYssONINWrvVgy43SfzntgQVDBcU5GlMI+z5IMFA=="], - "@napi-rs/keyring-linux-riscv64-gnu": ["@napi-rs/keyring-linux-riscv64-gnu@2.1.0", "", { "os": "linux", "cpu": "none" }, "sha512-tqhb0ofhtEJ2+ZfmlKTDHbmIoNSz+SSTPWRgMfSVCgI4JVkqDdfcmIOcThZfRffJ3QI9C3Tng+rZL24acmD+GA=="], + "@napi-rs/keyring-linux-arm64-musl": ["@napi-rs/keyring-linux-arm64-musl@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-wUHecqT9SMUaQCsJO6vtFEsMcfskspXf6+J0AtKsnK8/GnAAb4hf2FHtwp2ZUOecOk1kTW3LPhU5LwTMJ1LLeA=="], - "@napi-rs/keyring-linux-x64-gnu": ["@napi-rs/keyring-linux-x64-gnu@2.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-7ZA0ssxfpuGXV8S5Ct1ZJoEL3sKg/8u7mIL4SK9/PwKBhxIq7K+FVcjnggNsuFnEhHTX+HYl9hHDrlX0odZnHg=="], + "@napi-rs/keyring-linux-riscv64-gnu": ["@napi-rs/keyring-linux-riscv64-gnu@2.1.1", "", { "os": "linux", "cpu": "none" }, "sha512-hrfvuY0zxrpJOa4k/LzWkrB/R/nXnniz/I2V39X5pWgxKJ1iZBQI03/wT8OI7BwSv9czSh5LLeX/o5D5ywRHBg=="], - "@napi-rs/keyring-linux-x64-musl": ["@napi-rs/keyring-linux-x64-musl@2.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-dI74Fzl03CrN2TEP7YVBtUBFgyRTRPyK1LMcIgAUfgW3o6f5SD1a8EsfT82+brpFOltjOhacIlFiYOkYtSI5Fg=="], + "@napi-rs/keyring-linux-x64-gnu": ["@napi-rs/keyring-linux-x64-gnu@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-NHfMyYyu6laqRBzgfsB0zN12l+z5gz6xQqmLDT8ijevMMOc9ICcVo2GUJCPSX5WvRBgzui91D4R4QG0tahMurw=="], - "@napi-rs/keyring-win32-arm64-msvc": ["@napi-rs/keyring-win32-arm64-msvc@2.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-SltrXXkEe9a+Jv8ConDkZW2Mdr0srpWurS7oYkYP1Mp07DNBpFyqawRnyo4p7ZvQU3e+R6irj/Fq0epCCvVUXA=="], + "@napi-rs/keyring-linux-x64-musl": ["@napi-rs/keyring-linux-x64-musl@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5E2pO2zqOv+HJr2YorAUYmqItv8ts2oa+BccxZ07/I7x3XvxqwZGtRbkp4wM8psv7phhIfgy+0kZ/ItBsGiDhg=="], - "@napi-rs/keyring-win32-ia32-msvc": ["@napi-rs/keyring-win32-ia32-msvc@2.1.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-l5wJQhqXkAfQ4FGxoRh0wMpL9VxVmfiYI5TTeOGNupuVsQvbHmysh8gfDQBBWVhyMENHJS2OqOSYIuqVjumAtA=="], + "@napi-rs/keyring-win32-arm64-msvc": ["@napi-rs/keyring-win32-arm64-msvc@2.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-gn+Xg3oZmyC/pMIU0HTCyrIsncn2/u47H3JRQ8WKfPgS0KDCytcf2kuCCSShtNETSE5V8Zc18ymgIW5UrOQ5OA=="], - "@napi-rs/keyring-win32-x64-msvc": ["@napi-rs/keyring-win32-x64-msvc@2.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-3zYwO7dhJiUNyv2te81ujXm2vGxHwgx+z2eBm0dTYe3aWpvKQVzpfxCp/7cViJs8duVYE04wVOgXiRhRCMEq2w=="], + "@napi-rs/keyring-win32-ia32-msvc": ["@napi-rs/keyring-win32-ia32-msvc@2.1.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DeMQnalb+ueYjskBBUjwv0b9xuN9vhs/vi2H4q/crERfDWgmqu77rGZOW4Bi9kDc7Za3t29yeZctEBZzjAtXuA=="], - "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + "@napi-rs/keyring-win32-x64-msvc": ["@napi-rs/keyring-win32-x64-msvc@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-DjRshMlsMdEyZTNpqfQYj60kDrCZLkl4y80U3w5WJQIj7ypToj02+A/hfJw/g68wzbSZ6uXhklyITQ1DABZAUg=="], "@noble/hashes": ["@noble/hashes@2.4.0", "", {}, "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA=="], @@ -258,9 +203,11 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@opencor/libopencor-types": ["@opencor/libopencor-types@1.20261007.0", "", {}, "sha512-6jYhgY6d6tqRqmRF0++64RCI3Xg6PiwhVA4vy0NUyrwWQ3lpLlu+b7Kc+kdwbcNCk7ZOX74ZEOZpEYr6g6Enkw=="], + "@opencor/libopencor-types": ["@opencor/libopencor-types@1.20261016.0", "", {}, "sha512-E4rJ92XsusJyXC75F2zXdMArauPsPid0g8wwH6JBfywaEnjIhn3JgG3WezrNb7tkMoq2tchZ8L7Vy6AnCzbPjg=="], + + "@oxc-project/types": ["@oxc-project/types@0.154.0", "", {}, "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA=="], - "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.10.0", "", { "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw=="], + "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.11.0", "", { "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-8xh4avo53/vXO6UEv9yaoJ83hY581geaLTygf61NPIXP5AeIZCPjJhpTaYU23IZkG+NpLN/C9FYzWp7hphIT9A=="], "@peculiar/json-schema": ["@peculiar/json-schema@1.1.12", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w=="], @@ -282,57 +229,37 @@ "@primevue/metadata": ["@primevue/metadata@4.2.5", "", {}, "sha512-dABQ8BecGg6NRnrLx11MvmCQCFn2inUEmao8t17/Tc2wxwoXY315TaakDa5ebOdYnTG91lh+NX7SBpTH+MYIyQ=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.64.0", "", { "os": "android", "cpu": "arm" }, "sha512-FLLpX941CD/7aBvX3GqLGBM9OvY5PGXTryPJkG5r5/OuBpZpV5k45Uo3RxAvqd35tsyc3hH1bhtz921IKzoIzg=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.64.0", "", { "os": "android", "cpu": "arm64" }, "sha512-iI32XM/jnDYJsvx5ai/za/7gWO9XoSeFFtU06/ZCaRgyilfJOjaopD3nOpk2WrTCGOyeDt4hleUiGOAnd5tHNg=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.64.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dRBZlbt2sEtF8Mn4MODThze9fIPdDoOhVSlXMAKy5dQbOYK9pOOoPY4Fat3OTtr0+8zbJo5TTZwlsfxY/kDNJg=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.64.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Y3whrJOMucfkhgNkvcM93M6j8C53iEAMmPMYD99C6G1kUy4ELigLWk9ar2/4aCBSRgsql0zdlOemLgW2m5GZgQ=="], + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.14", "", { "os": "android", "cpu": "arm" }, "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.64.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-S6S1GEzPjFF6IONYJX3AKt/799+8RgBgCcwRnUo+Ep1IvBxUbw5M51iU0TDdK2NWWVnSvCQ8yi6Em0fGASMOng=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.14", "", { "os": "android", "cpu": "arm64" }, "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.64.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Ed1DPoFENkXj4IGzSB12SesV40iP6LxCCrbTIkSGIQBpOOxpBM+0kj62lmaeIHfMWTDoddqTOtqc7s8LYfn+Pw=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-J/6xhutJPtFyIkmHdf4olOdHHKH5kOJNumojdWn+Elv5ICEYTVOa362Vw27TxsK0BLzb1le3EDlO3UbvFK1lnQ=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-G4L/Nnx59d2LvDoV/P4S5lZ7oVLT6hodOHMacqjiq2LL2gts86KbkEBTYL53+fBt0odhf7XPIbIATGoYiJctxg=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zF16pvJDEUksGYRbfnXne7pdklAyj2XDmJeYKgP93oq0cUyX9Y8O7lUo1R+IY1GagP/YBvCEDCe7eEcAKpw//g=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.14", "", { "os": "linux", "cpu": "arm" }, "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-nIggWn8n+w12+R5lsV+PYYoUY8vuObxPAQqJI7WES+9v0qspYuAHoZMfYM0yLxlipasT5BY9ht5W8t5c/rSCkg=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-WLOTiHPzszP0dxIMsHpFeDSOSB5I49Mj8K4+yKE3H1X/D0JRG5XZ5jrctJLMjnUc/LUzaKiwq6EHWNJ8pup8PA=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-35RK9aRhh7g7Pmeu5gQ01aCvrhXzj9LoBE6xtUcEH1ns+nyTaSzxSpADmBQINu4QiTDGvmQYwo1j5hlg1rRXFw=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.14", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.64.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RqTfnpoTjuWfPAmqK5p2edt2AMCDJWm6oTn7sKfmyx+6tRga0ip05SJ977o6udTcIiDeF/CJMUfkaN4gtWt6Aw=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.14", "", { "os": "linux", "cpu": "s390x" }, "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.64.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RQjZPigjxFvFZDijG6rjlSXxhRaTf9zLYVpGK+lVcga64oMqP1GbgVqCb46eHTTceSIgp89Ja0y5aPe3GFBYHg=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.14", "", { "os": "linux", "cpu": "x64" }, "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-tjL58mgQcTRnDRpeky+Sth1MKQeYi2Ct6r3Luq9E5aTSjnM/i/JbozqZkKqp9thM5+oDSZxo+05f9kdQedfTGA=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.14", "", { "os": "linux", "cpu": "x64" }, "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-ictoRyVaoZ8C67myHef+8hhCa6IplXJJ/xUCkVCxziV4zxMZh+jvhkhGsPBLqj1S72fnqD0QH5zL4lcaa4X7DQ=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.14", "", { "os": "none", "cpu": "arm64" }, "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.64.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-8aVvZ2hfr9RLFzBQkQNmYeHLDja8fLeQAmLt757zdKN/B+8L809KlLJKctQjS22wZotptV8G6o7fVPuAs8S1tQ=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2dEF8GAcDKwshUfydD+GhosppNyBzhVUHkdFF3CXd3FpOkzj/RZzF5aoEtzklujklT5qFVRU2GK/cFeLaTNKVg=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.14", "", { "os": "win32", "cpu": "x64" }, "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3XHTvoo2hKkh0MiOfS7SkhHjMaaWF7P4F3drocZZaho/Z8w7h/jpnuBHm91BOQLfvrKHs1yR+zir4E8RI3XzTQ=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.64.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HKtkzodL4a3KFWDXDoO61cRynAWTR3LnAE0oGtfG9lcNFpNuVYUFOByxDgNqEvic9rvaQbAj+SFwTEWDptRaIw=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.64.0", "", { "os": "none", "cpu": "arm64" }, "sha512-JBsxqiY16uyyCujtoC3s7zL+SrYyNElptD7K4V+KUoJ2V8/XG3hPWpheK+SuPwNh1cikJvIZmIdsrYXvD6PLJw=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.64.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-S5Qrijh37qhnUYQ0ay8WN3LuWeKjow7wTUyCOPMZtnXiBXYXdsZETI6ZLfYnQAKovltckPLBFdNeD61P2rJc7g=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.64.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zbZ6iMmLFEsCtTdc2/y1F/cDFVNNCPF7Ae8PCT/wdPE//hGwYp6r+MyCLMFX+kOeIe09Pz6ASfnnfpSTN1FJQQ=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.64.0", "", { "os": "win32", "cpu": "x64" }, "sha512-L6Hw69oNpuYaOr1zCG30v0/DqKYCSRwodPGwIcVZ/6f8SOzq9rxtL/xNVI5MO4wjli+Ko0ErK9uHJL3mNPt8wQ=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.64.0", "", { "os": "win32", "cpu": "x64" }, "sha512-nl+CTWIgOGszQmUz/kanfgrfFZ4qiwxKIfT/ryl3N3Ewk5tqizmYtNXiqb7Bu/fDtNgu1kyxdFiAUJNur3iysg=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], @@ -376,8 +303,6 @@ "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], - "@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="], "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], @@ -386,9 +311,9 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@26.6.4", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg=="], + "@types/node": ["@types/node@26.6.5", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg=="], - "@types/plotly.js": ["@types/plotly.js@3.0.14", "", {}, "sha512-3JZH20UBXQ8rZFPeV40fKinx5Le0T2cuz6kM8LEYkkAuDrljzezlwvZuXnXQhyBost7YwgKxEDg7SVTIBbA6eg=="], + "@types/plotly.js": ["@types/plotly.js@3.0.15", "", {}, "sha512-ftBxk2fB7BxxeMpT0IVEb/3HKpYTXO5nCdR5mGEz6gHpLd6GGRFxvDVoG1gpD/gBAgoN36KIyRflxCnOfWlskQ=="], "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], @@ -434,7 +359,7 @@ "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], - "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.9", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA=="], + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.10", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25 || ^3.6.0-rc.10" } }, "sha512-b80aUQh3n+qEzzZm5oQp3rT2QImeCx58K6eFl/RkJ3d65KW0kTuv5lNsLRVAjbW9YRUpo/wzmvzMgV34bTAItA=="], "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], @@ -450,9 +375,9 @@ "@vue/compiler-ssr": ["@vue/compiler-ssr@3.4.21", "", { "dependencies": { "@vue/compiler-dom": "3.4.21", "@vue/shared": "3.4.21" } }, "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q=="], - "@vue/language-core": ["@vue/language-core@3.3.12", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-k7uIKR0n4CBidk233y7cac9osnDQ5WRidTl6nsrNvpZ3nf8cx9iJxRVsRzVsoPRHbowT/iKgGMTS9RGov3jeIA=="], + "@vue/language-core": ["@vue/language-core@3.3.13", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-axA0Xemn5LuUKuKWob0/kVq7xQ2mJJM6VqA9Exvt35nM9yRC31goWVve/G/Zb2ZCLsXC8FHYjjfXlnshknSCew=="], - "@vue/reactivity": ["@vue/reactivity@3.5.43", "", { "dependencies": { "@vue/shared": "3.5.43" } }, "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA=="], + "@vue/reactivity": ["@vue/reactivity@3.5.44", "", { "dependencies": { "@vue/shared": "3.5.44" } }, "sha512-u+QJdKeBBEADkPdevsL+6E12QlYVfBD8Zcw5qjDQKtamI6l14KyqazJMobhCKelhWN5zpyKUiEDoTyHE93cqWw=="], "@vue/runtime-core": ["@vue/runtime-core@3.4.21", "", { "dependencies": { "@vue/reactivity": "3.4.21", "@vue/shared": "3.4.21" } }, "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA=="], @@ -512,7 +437,7 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.28", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA=="], "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="], @@ -534,7 +459,7 @@ "bytestreamjs": ["bytestreamjs@2.0.1", "", {}, "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ=="], - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "cac": ["cac@7.1.0", "", {}, "sha512-LMDU/U2yA4IPIY8KT0NsNPWEYZvAMkbgyspxJ6btJaYV+WMDOHAZCmB0lsTObiHaRS1V/ztKAl7+TSkTqXyeoA=="], "cacheable": ["cacheable@2.5.0", "", { "dependencies": { "@cacheable/memory": "^2.2.0", "@cacheable/utils": "^2.5.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.10.1" } }, "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g=="], @@ -546,7 +471,7 @@ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "caniuse-lite": ["caniuse-lite@1.0.30001814", "", {}, "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001816", "", {}, "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -636,7 +561,7 @@ "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], - "electron": ["electron@44.6.0", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-qjzKZ82yTsAoo0Q/gqdy4PEMQkzYpukZqIduaHWZYHapFhK9wgWwU4YId60xHmwTCraqF4AfVCm05o90dZJKMQ=="], + "electron": ["electron@44.7.2", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-u3Pbz0Hi1sSSwBMz2v8LiagsfUG4QURPUe/qphM+g4THKP8o4BYdnuboyhlLO8HIC/KfV5rvltyacbP4jEq9yw=="], "electron-builder": ["electron-builder@26.15.3", "", { "dependencies": { "app-builder-lib": "26.15.3", "builder-util": "26.15.3", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.15.3", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "./cli.js", "install-app-deps": "./install-app-deps.js" } }, "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA=="], @@ -646,11 +571,11 @@ "electron-publish": ["electron-publish@26.15.3", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", "builder-util": "26.15.3", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q=="], - "electron-to-chromium": ["electron-to-chromium@1.5.447", "", {}, "sha512-vhYiRwTPq7tvwplP6fOFVRJxfZifavlr6tK8FD1EO5RxRuvZ9qdQM0lr1mvNBeDe1s2XrfL3n5BBwvigXJ7KuA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.456", "", {}, "sha512-8hboqI6xesozteFb8aa7OGknvxUtTeRJLz0FoePysUEh/oWbddykIGdgpEJRbPRg/nIMfBf2DFos+H2MtvJl2A=="], "electron-updater": ["electron-updater@6.8.9", "", { "dependencies": { "builder-util-runtime": "9.7.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig=="], - "electron-vite": ["electron-vite@5.0.0", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-arrow-functions": "^7.27.1", "cac": "^6.7.14", "esbuild": "^0.25.11", "magic-string": "^0.30.19", "picocolors": "^1.1.1" }, "peerDependencies": { "@swc/core": "^1.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@swc/core"], "bin": { "electron-vite": "bin/electron-vite.js" } }, "sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ=="], + "electron-vite": ["electron-vite@6.0.0", "", { "dependencies": { "@babel/core": "^7.29.7", "@babel/plugin-transform-arrow-functions": "^7.29.7", "cac": "^7.0.0", "magic-string": "^1.4.2", "picocolors": "^1.1.1", "rolldown": "~1.2.10" }, "peerDependencies": { "@swc/core": "^1.0.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@swc/core"], "bin": { "electron-vite": "bin/electron-vite.js" } }, "sha512-zafg2uizgll7jdx0BNDkkzO/SowsUBsiHqYN3v9Ieq7XPDHreRGZ4G9IRY/+RslgBktpLOjLM4jlP+IVpV0BGw=="], "electron-winstaller": ["electron-winstaller@5.4.0", "", { "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg=="], @@ -678,8 +603,6 @@ "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], - "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -850,29 +773,29 @@ "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], @@ -890,7 +813,7 @@ "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "magic-string": ["magic-string@1.4.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.6.0" } }, "sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ=="], "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], @@ -932,7 +855,7 @@ "nanoid": ["nanoid@3.3.20", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="], - "node-abi": ["node-abi@4.36.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-zrvE1Qy5ElbZVqmJFSiNfwzNZzUCepasgp/4dw0Z0fsn40LJkAk9FA9oMh2SKMeNFRRe/3IfeOKvWwWr8u4SmQ=="], + "node-abi": ["node-abi@4.37.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-O9XXMXLeRHQtvh43NCah3y9N1K69tecOmu8Y5p2ay4iXvtpbQr0VaroJF9EmRVp3vkdoj3qn4IFfX+ZYtC94fw=="], "node-addon-api": ["node-addon-api@8.9.2", "", {}, "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg=="], @@ -944,7 +867,7 @@ "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], - "node-releases": ["node-releases@2.0.57", "", {}, "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw=="], + "node-releases": ["node-releases@2.0.59", "", {}, "sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA=="], "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], @@ -988,7 +911,7 @@ "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], - "postcss": ["postcss@8.5.29", "", { "dependencies": { "nanoid": "^3.3.19", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ=="], + "postcss": ["postcss@8.5.30", "", { "dependencies": { "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA=="], "postcss-safe-parser": ["postcss-safe-parser@7.1.0", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg=="], @@ -1056,7 +979,7 @@ "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], - "rollup": ["rollup@4.64.0", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.64.0", "@rollup/rollup-android-arm64": "4.64.0", "@rollup/rollup-darwin-arm64": "4.64.0", "@rollup/rollup-darwin-x64": "4.64.0", "@rollup/rollup-freebsd-arm64": "4.64.0", "@rollup/rollup-freebsd-x64": "4.64.0", "@rollup/rollup-linux-arm-gnueabihf": "4.64.0", "@rollup/rollup-linux-arm-musleabihf": "4.64.0", "@rollup/rollup-linux-arm64-gnu": "4.64.0", "@rollup/rollup-linux-arm64-musl": "4.64.0", "@rollup/rollup-linux-loong64-gnu": "4.64.0", "@rollup/rollup-linux-loong64-musl": "4.64.0", "@rollup/rollup-linux-ppc64-gnu": "4.64.0", "@rollup/rollup-linux-ppc64-musl": "4.64.0", "@rollup/rollup-linux-riscv64-gnu": "4.64.0", "@rollup/rollup-linux-riscv64-musl": "4.64.0", "@rollup/rollup-linux-s390x-gnu": "4.64.0", "@rollup/rollup-linux-x64-gnu": "4.64.0", "@rollup/rollup-linux-x64-musl": "4.64.0", "@rollup/rollup-openbsd-x64": "4.64.0", "@rollup/rollup-openharmony-arm64": "4.64.0", "@rollup/rollup-win32-arm64-msvc": "4.64.0", "@rollup/rollup-win32-ia32-msvc": "4.64.0", "@rollup/rollup-win32-x64-gnu": "4.64.0", "@rollup/rollup-win32-x64-msvc": "4.64.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-gjA/kFeDfffII8COoPsvLPvE16hWDTEc1+0QKQNKUkuV8iJjqsC4xXs8/FzuL0v9EV39kuxTbAgcFoTRyy2TGQ=="], + "rolldown": ["rolldown@1.2.14", "", { "dependencies": { "@oxc-project/types": "=0.154.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.14", "@rolldown/binding-android-arm64": "1.2.14", "@rolldown/binding-darwin-arm64": "1.2.14", "@rolldown/binding-darwin-x64": "1.2.14", "@rolldown/binding-freebsd-x64": "1.2.14", "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", "@rolldown/binding-linux-arm64-gnu": "1.2.14", "@rolldown/binding-linux-arm64-musl": "1.2.14", "@rolldown/binding-linux-ppc64-gnu": "1.2.14", "@rolldown/binding-linux-s390x-gnu": "1.2.14", "@rolldown/binding-linux-x64-gnu": "1.2.14", "@rolldown/binding-linux-x64-musl": "1.2.14", "@rolldown/binding-openharmony-arm64": "1.2.14", "@rolldown/binding-win32-arm64-msvc": "1.2.14", "@rolldown/binding-win32-x64-msvc": "1.2.14" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw=="], "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.1.1", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.5", "source-map": "^0.8.0", "yargs": "^18.1.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-ThaGiHTU8XW02OkK80TrTHATraJmM9OAduU4otal+7gyXLpYEtmGBLfx5kW+EHvvLwn03YGW2NnwKUIqsYlJAA=="], @@ -1174,7 +1097,7 @@ "unzipper": ["unzipper@0.12.5", "", { "dependencies": { "bluebird": "~3.7.2", "duplexer2": "~0.1.4", "fs-extra": "11.3.1", "graceful-fs": "^4.2.2", "node-int64": "^0.4.0" } }, "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A=="], - "update-browserslist-db": ["update-browserslist-db@1.3.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ=="], + "update-browserslist-db": ["update-browserslist-db@1.3.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A=="], "url-join": ["url-join@4.0.1", "", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="], @@ -1182,13 +1105,13 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="], + "vite": ["vite@8.3.4", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.29", "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ=="], "vscode-uri": ["vscode-uri@3.2.0", "", {}, "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg=="], "vue": ["vue@3.4.21", "", { "dependencies": { "@vue/compiler-dom": "3.4.21", "@vue/compiler-sfc": "3.4.21", "@vue/runtime-dom": "3.4.21", "@vue/server-renderer": "3.4.21", "@vue/shared": "3.4.21" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA=="], - "vue-tsc": ["vue-tsc@3.3.12", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.12" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-kbpaz18O9jPVkXVHD1BEjpUXM6WhXSu+62L1sGGr6Wbzd8GbvkBQWjoj3KZ/C0aq2MZllce7UdtWzwCIYsBMGA=="], + "vue-tsc": ["vue-tsc@3.3.13", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.13" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-jE4/bXdOMSHVxEGgQCqNllJy+9N3Gx6tfSAsg0yhFK/MAN6eXqISihupnDEKSCt12VVnjN2gI3AqLYcqoh5x/A=="], "webcrypto-core": ["webcrypto-core@1.9.2", "", { "dependencies": { "@peculiar/asn1-schema": "^2.7.0", "@peculiar/json-schema": "^1.1.12", "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q=="], @@ -1242,6 +1165,10 @@ "@primevue/core/@primeuix/styled": ["@primeuix/styled@0.3.2", "", { "dependencies": { "@primeuix/utils": "^0.3.2" } }, "sha512-ColZes0+/WKqH4ob2x8DyNYf1NENpe5ZguOvx5yCLxaP8EIMVhLjWLO/3umJiDnQU4XXMLkn2mMHHw+fhTX/mw=="], + "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "@tailwindcss/node/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], @@ -1254,17 +1181,19 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.43", "", { "dependencies": { "@vue/compiler-core": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ=="], + "@vue/compiler-sfc/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.44", "", { "dependencies": { "@vue/compiler-core": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg=="], - "@vue/language-core/@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + "@vue/language-core/@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], - "@vue/reactivity/@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + "@vue/reactivity/@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], "@vue/runtime-core/@vue/reactivity": ["@vue/reactivity@3.4.21", "", { "dependencies": { "@vue/shared": "3.4.21" } }, "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw=="], - "@vueuse/core/vue": ["vue@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/compiler-sfc": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/server-renderer": "3.5.43", "@vue/shared": "3.5.43" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng=="], + "@vueuse/core/vue": ["vue@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/compiler-sfc": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/server-renderer": "3.5.44", "@vue/shared": "3.5.44" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ=="], - "@vueuse/shared/vue": ["vue@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/compiler-sfc": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/server-renderer": "3.5.43", "@vue/shared": "3.5.43" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng=="], + "@vueuse/shared/vue": ["vue@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/compiler-sfc": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/server-renderer": "3.5.44", "@vue/shared": "3.5.44" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ=="], "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], @@ -1292,7 +1221,7 @@ "dmg-builder/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - "electron/@types/node": ["@types/node@24.19.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ=="], + "electron/@types/node": ["@types/node@24.19.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ=="], "electron-builder/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], @@ -1300,8 +1229,6 @@ "electron-updater/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - "electron-vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], @@ -1346,6 +1273,8 @@ "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], + "unplugin-vue-components/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "unzipper/fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="], "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1360,93 +1289,63 @@ "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.1.7", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g=="], - "@vue/language-core/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], - "@vueuse/core/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.43", "", { "dependencies": { "@vue/compiler-core": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ=="], + "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], - "@vueuse/core/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.43", "@vue/compiler-dom": "3.5.43", "@vue/compiler-ssr": "3.5.43", "@vue/shared": "3.5.43", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.28", "source-map-js": "^1.2.1" } }, "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ=="], + "@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], - "@vueuse/core/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/runtime-core": "3.5.43", "@vue/shared": "3.5.43", "csstype": "^3.2.3" } }, "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw=="], + "@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], - "@vueuse/core/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.43", "", { "dependencies": { "@vue/compiler-ssr": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ=="], + "@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], - "@vueuse/core/vue/@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], - "@vueuse/shared/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.43", "", { "dependencies": { "@vue/compiler-core": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ=="], + "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], - "@vueuse/shared/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.43", "@vue/compiler-dom": "3.5.43", "@vue/compiler-ssr": "3.5.43", "@vue/shared": "3.5.43", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.28", "source-map-js": "^1.2.1" } }, "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ=="], + "@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], - "@vueuse/shared/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/runtime-core": "3.5.43", "@vue/shared": "3.5.43", "csstype": "^3.2.3" } }, "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw=="], + "@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], - "@vueuse/shared/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.43", "", { "dependencies": { "@vue/compiler-ssr": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ=="], + "@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], - "@vueuse/shared/vue/@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + "@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - "app-builder-lib/@electron/get/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + "@vueuse/core/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.44", "", { "dependencies": { "@vue/compiler-core": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg=="], - "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@vueuse/core/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/compiler-core": "3.5.44", "@vue/compiler-dom": "3.5.44", "@vue/compiler-ssr": "3.5.44", "@vue/shared": "3.5.44", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.29", "source-map-js": "^1.2.2" } }, "sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw=="], - "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "@vueuse/core/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/runtime-core": "3.5.44", "@vue/shared": "3.5.44", "csstype": "^3.2.3" } }, "sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg=="], - "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], + "@vueuse/core/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.44", "", { "dependencies": { "@vue/compiler-ssr": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ=="], - "electron-vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + "@vueuse/core/vue/@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], - "electron-vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + "@vueuse/shared/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.44", "", { "dependencies": { "@vue/compiler-core": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg=="], - "electron-vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + "@vueuse/shared/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/compiler-core": "3.5.44", "@vue/compiler-dom": "3.5.44", "@vue/compiler-ssr": "3.5.44", "@vue/shared": "3.5.44", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.29", "source-map-js": "^1.2.2" } }, "sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw=="], - "electron-vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + "@vueuse/shared/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/runtime-core": "3.5.44", "@vue/shared": "3.5.44", "csstype": "^3.2.3" } }, "sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg=="], - "electron-vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + "@vueuse/shared/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.44", "", { "dependencies": { "@vue/compiler-ssr": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ=="], - "electron-vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + "@vueuse/shared/vue/@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], - "electron-vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], - - "electron-vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], - - "electron-vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], - - "electron-vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], - - "electron-vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], - - "electron-vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], - - "electron-vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], - - "electron-vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], - - "electron-vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], - - "electron-vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], - - "electron-vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], - - "electron-vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], - - "electron-vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], - - "electron-vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + "app-builder-lib/@electron/get/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - "electron-vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - "electron-vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "electron-vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], - "electron-vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "electron-vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "electron-vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.21", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw=="], "electron-winstaller/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], @@ -1478,25 +1377,29 @@ "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], + + "@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], + + "@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], - "@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@vueuse/core/vue/@vue/compiler-sfc/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + "@vueuse/core/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ=="], - "@vueuse/core/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw=="], + "@vueuse/core/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], - "@vueuse/core/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + "@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], - "@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + "@vueuse/shared/vue/@vue/compiler-sfc/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "@vueuse/shared/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw=="], + "@vueuse/shared/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ=="], - "@vueuse/shared/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + "@vueuse/shared/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], "app-builder-lib/@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], diff --git a/electron-builder.yml b/electron-builder.yml index 6c340559d..21973ef90 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -1,5 +1,14 @@ appId: ws.opencor.app productName: OpenCOR +# Only package what is needed at runtime, i.e. our built main, preload, and renderer files (out/), the files used by our +# splash screen window and for our application icon (src/main/assets/ and our logo), and package.json. +# Note: our production dependencies get added automatically. +files: + - out/** + - src/main/assets/** + - "!src/main/assets/README.md" + - src/renderer/src/assets/logo.svg + - package.json publish: provider: github channel: latest-${arch} diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 2d3305b17..30dc0d83d 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -9,12 +9,16 @@ import * as fs from 'node:fs'; import path from 'node:path'; import vitePlugin from 'unplugin-vue-components/vite'; -import { libopencorVersion } from './src/renderer/scripts/libopencor.version'; +import { downloadLibopencorJsIfNeeded } from './src/renderer/scripts/download.libopencor.js.ts'; +import { libopencorVersion, opencorVersion } from './src/renderer/scripts/libopencor.version.ts'; +import { stripPrimeIconsFontFacePlugin } from './src/renderer/scripts/primeicons.plugin.ts'; + +const rendererPublicDir = path.join(import.meta.dirname, 'src', 'renderer', 'public'); export default electronVite.defineConfig({ main: { build: { - rollupOptions: { + rolldownOptions: { output: { format: 'cjs' } @@ -27,28 +31,36 @@ export default electronVite.defineConfig({ preload: {}, renderer: { build: { - target: 'esnext' + target: 'esnext', + copyPublicDir: false + // Note: our renderer's public folder only contains libOpenCOR's glue (see downloadLibopencorJsIfNeeded()) and our + // Web app's .htaccess file, which are only needed by our Web app (OpenCOR uses our C++ API, see + // initialiseLocApi(), and isn't served by Apache), so there is no need to ship them. }, define: { - __LIBOPENCOR_WASM_VERSION__: JSON.stringify(libopencorVersion) + __LIBOPENCOR_VERSION__: JSON.stringify(libopencorVersion), + __OPENCOR_VERSION__: JSON.stringify(opencorVersion) }, envDir: path.join(import.meta.dirname, 'src/renderer'), plugins: [ - // Note: this must be in sync with src/renderer/vite.config.ts. + // Note: this must be in sync with src/renderer/vite.config.ts, except for the download-libopencor-js plugin. { - // Plugin to strip unneeded PrimeIcons files. + // Plugin to download libOpenCOR's glue, so that our renderer can also be used as our Web app (e.g., at + // http://localhost:5173/) when running in development mode. Indeed, our Web app's Content Security Policy only + // allows scripts from 'self', so libOpenCOR's glue must be served from our public folder (see + // importLibOpenCORGlue()). - name: 'strip-unneeded-primeicons-files', - generateBundle(_options, bundle) { - for (const fileName of Object.keys(bundle)) { - if (fileName.includes('assets/primeicons') && /\.(eot|svg|ttf|woff2?)$/.test(fileName)) { - delete bundle[fileName]; - } - } + name: 'download-libopencor-js', + apply: 'serve', + async configureServer() { + await downloadLibopencorJsIfNeeded( + path.join(rendererPublicDir, 'libopencor', 'downloads', 'wasm', libopencorVersion) + ); } }, tailwindcssPlugin(), + stripPrimeIconsFontFacePlugin(), vuePlugin({ script: { fs: { @@ -62,6 +74,7 @@ export default electronVite.defineConfig({ resolvers: [primeVueAutoImportResolver.PrimeVueResolver()] }) ], + publicDir: rendererPublicDir, server: { fs: { allow: [path.join(import.meta.dirname, '..')] diff --git a/package.json b/package.json index 435ec073c..30cdfa840 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "url": "git+https://github.com/opencor/webapp.git" }, "type": "module", - "version": "1.20261007.1", + "version": "1.20261016.0", "engines": { "bun": ">=1.2.0" }, @@ -40,6 +40,7 @@ "format:check": "bunx --bun biome format --max-diagnostics=none && clang-format --dry-run -Werror src/renderer/src/libopencor/src/*", "lint": "bunx --bun biome lint --fix --error-on-warnings --max-diagnostics=none && bunx stylelint '**/*.css' --fix", "postinstall": "bun src/renderer/scripts/ensure.electron.ts", + "prune:web": "bun src/renderer/scripts/prune.web.ts", "release": "bun src/renderer/scripts/libopencor.ts && electron-builder", "release:local": "bun src/renderer/scripts/libopencor.ts && CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --config.mac.notarize=false --publish=never", "start": "bun src/renderer/scripts/libopencor.ts && electron-vite preview", @@ -56,37 +57,35 @@ "vue": "3.4.21" }, "dependencies": { - "@napi-rs/keyring": "^2.1.0", - "@opencor/libopencor-types": "^1.20261007.0", - "@primeuix/themes": "2.0.3", - "@primevue/auto-import-resolver": "4.2.5", - "@vueuse/core": "12.8.2", - "primeicons": "7.0.0", - "primevue": "4.2.5", - "vue": "3.4.21", + "@napi-rs/keyring": "^2.1.1", "xxhash-wasm": "^1.1.0" }, "devDependencies": { "@biomejs/biome": "^2.5.15", "@electron-toolkit/tsconfig": "^2.0.0", "@electron-toolkit/utils": "^4.0.0", + "@opencor/libopencor-types": "^1.20261016.0", + "@primeuix/themes": "2.0.3", + "@primevue/auto-import-resolver": "4.2.5", "@tailwindcss/postcss": "^4.3.3", "@tailwindcss/vite": "^4.3.3", - "@types/node": "^26.6.4", - "@types/plotly.js": "^3.0.14", - "@vitejs/plugin-vue": "^6.0.9", + "@types/node": "^26.6.5", + "@types/plotly.js": "^3.0.15", + "@vitejs/plugin-vue": "^6.0.10", "@vue/tsconfig": "^0.9.1", + "@vueuse/core": "12.8.2", "@wasm-fmt/clang-format": "^23.1.1", "autoprefixer": "^10.6.1", "cmake-js": "^8.0.0", - "electron": "^44.6.0", + "electron": "^44.7.2", "electron-builder": "^26.15.3", "electron-conf": "^1.3.0", "electron-updater": "^6.8.9", - "electron-vite": "^5.0.0", - "esbuild": "^0.28.2", + "electron-vite": "^6.0.0", "node-addon-api": "^8.9.2", - "postcss": "^8.5.29", + "postcss": "^8.5.30", + "primeicons": "7.0.0", + "primevue": "4.2.5", "rollup-plugin-visualizer": "^7.1.1", "stylelint": "^17.16.0", "stylelint-config-standard": "^40.0.0", @@ -96,7 +95,8 @@ "typescript": "^7.0.2", "typescript-6": "npm:typescript@^6.0.3", "unplugin-vue-components": "^32.1.0", - "vite": "7.3.6", - "vue-tsc": "^3.3.12" + "vite": "^8.3.4", + "vue": "3.4.21", + "vue-tsc": "^3.3.13" } } diff --git a/src/extern/corsProxy.js b/src/extern/corsProxy.js index dcbf48116..5100601c4 100644 --- a/src/extern/corsProxy.js +++ b/src/extern/corsProxy.js @@ -1,33 +1,434 @@ // A simple CORS proxy handler for Cloudflare Workers (see https://workers.cloudflare.com/). +// Note: the proxy accepts any target URL, but it only returns content that is a CellML file, a SED-ML file, an OMEX file, +// or a CSV file (see isCsvFile()), and that is not too large (see MAX_SIZE). Such content is returned as an +// opaque, sandboxed download, so that a browser never renders or executes it. const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST', + 'Access-Control-Allow-Methods': 'GET', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Allow-Credentials': 'false' }; -const extractTargetUrl = (url) => url.searchParams.get('url'); +const CONTENT_HEADERS = { + ...CORS_HEADERS, + 'Content-Type': 'application/octet-stream', + 'Content-Security-Policy': "sandbox; default-src 'none'", + 'X-Content-Type-Options': 'nosniff' +}; + +const SNIFF_SIZE = 64 * 1024; // 64 KB. +const MAX_XML_PROLOG_SIZE = 1024 * 1024; // 1 MB. +// Note: the prolog of an XML file (i.e. its XML declaration, processing instructions, comments, and document type +// declaration) may be longer than SNIFF_SIZE (e.g., because of a long licence comment), in which case we read up +// to MAX_XML_PROLOG_SIZE of it to find its root element (see forwardRequest()). +const MAX_SIZE = 32 * 1024 * 1024; // 32 MB. +// Note: an OMEX file, as well as some content which size we don't know in advance, needs to be fully read (see +// forwardRequest()), which means holding two copies of it (i.e. the chunks we read and their concatenation, see +// readChunks()), so MAX_SIZE must be small enough for at least two copies of it to fit in the memory available to +// a Cloudflare Worker (i.e. 128 MB). + +const ZIP_LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50; +const ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014b50; +const ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50; +const ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06064b50; +const ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE = 0x07064b50; + +const extractTargetUrl = (url) => { + return url.searchParams.get('url'); +}; const isValidTargetUrl = (targetUrl) => { try { - const parsedUrl = new URL(targetUrl); - const allowedHosts = ['cellml.org', 'figshare.com', 'opencor.ws', 'physiomeproject.org']; - const allowedExtensions = ['.cellml', '.sedml', '.omex', '.csv']; + return ['http:', 'https:'].includes(new URL(targetUrl).protocol); + } catch { + return false; + } +}; + +const concatenate = (chunks, length) => { + const bytes = new Uint8Array(length); + let offset = 0; + + for (const chunk of chunks) { + bytes.set(chunk, offset); + + offset += chunk.length; + } + + return bytes; +}; + +const readChunks = async (reader, maxSize, initialChunks = []) => { + // Read chunks until (roughly) the given number of bytes, including those of the given initial chunks, has been + // reached and let the caller know whether the end of the stream was reached. + // Note: we don't concatenate the chunks, so that the caller can decide whether (and when) to do it, i.e. without + // having to hold more than two copies of the data at any given time (see MAX_SIZE). + + const chunks = [...initialChunks]; + let length = chunks.reduce((total, chunk) => total + chunk.length, 0); + let done = false; + + while (length < maxSize) { + const result = await reader.read(); + + if (result.done) { + done = true; + + break; + } + + chunks.push(result.value); + + length += result.value.length; + } + + return { chunks, length, done }; +}; + +const readBytes = async (reader, maxSize) => { + // Read up to (roughly) the given number of bytes and let the caller know whether the end of the stream was reached. + + const { chunks, length, done } = await readChunks(reader, maxSize); + + return { bytes: concatenate(chunks, length), done }; +}; + +const readRest = async (reader, head) => { + // Read the rest of some content, the start of which we have already read, and return all of it, unless it is too + // large, in which case return null. + // Note: we read (up to) one more byte than allowed, so that we know whether the content is too large, in which case we + // reject it without concatenating its chunks. Otherwise, we concatenate the start of the content and the rest of + // it in one go. + + const all = await readChunks(reader, MAX_SIZE + 1, [head]); + + if (all.length > MAX_SIZE) { + await reader.cancel(); + + return null; + } + + return concatenate(all.chunks, all.length); +}; + +const replayStream = (bytes, reader) => { + // Return a stream that first outputs the given bytes (i.e. those we already read to check the content) and then the + // rest of the given reader's data, unless there is too much data, in which case the stream errors. + + let size = bytes.length; + + return new ReadableStream({ + start(controller) { + controller.enqueue(bytes); + }, + async pull(controller) { + const { done, value } = await reader.read(); + + if (done) { + controller.close(); + + return; + } + + size += value.length; + + if (size > MAX_SIZE) { + await reader.cancel(); + + controller.error(new Error('The target content is too large.')); + + return; + } + + controller.enqueue(value); + }, + cancel(reason) { + return reader.cancel(reason); + } + }); +}; + +const isZipFile = (bytes) => { + // Check that the given bytes are a ZIP file by looking for the local file header signature at the start of the file. + + return ( + bytes.length >= 4 && + new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0, true) === + ZIP_LOCAL_FILE_HEADER_SIGNATURE + ); +}; + +const isOmexFile = (bytes) => { + // An OMEX file is a ZIP file with a manifest.xml file at its root, so look for it in the ZIP file's central + // directory. + + // Find the end of central directory record, which is at the end of the ZIP file, but may be followed by a comment of + // up to 65,535 bytes. + + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + let endOfCentralDirectoryOffset = -1; + + for (let offset = bytes.length - 22; offset >= Math.max(0, bytes.length - 22 - 0xffff); --offset) { + if (view.getUint32(offset, true) === ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE) { + endOfCentralDirectoryOffset = offset; + + break; + } + } + + if (endOfCentralDirectoryOffset === -1) { + return false; + } + + // Use the ZIP64 end of central directory record, if needed. + + let nbOfEntries = view.getUint16(endOfCentralDirectoryOffset + 10, true); + let offset = view.getUint32(endOfCentralDirectoryOffset + 16, true); + + if ( + (nbOfEntries === 0xffff || offset === 0xffffffff) && + endOfCentralDirectoryOffset >= 20 && + view.getUint32(endOfCentralDirectoryOffset - 20, true) === ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE + ) { + const zip64EndOfCentralDirectoryOffset = Number(view.getBigUint64(endOfCentralDirectoryOffset - 12, true)); if ( - !allowedHosts.some((host) => parsedUrl.hostname.endsWith(host)) && - !allowedExtensions.some((extension) => parsedUrl.pathname.toLowerCase().endsWith(extension)) + zip64EndOfCentralDirectoryOffset + 56 > bytes.length || + view.getUint32(zip64EndOfCentralDirectoryOffset, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE ) { return false; } - return ['http:', 'https:'].includes(parsedUrl.protocol); + nbOfEntries = Number(view.getBigUint64(zip64EndOfCentralDirectoryOffset + 32, true)); + offset = Number(view.getBigUint64(zip64EndOfCentralDirectoryOffset + 48, true)); + } + + // Look for a central directory entry with the name "manifest.xml". + + const textDecoder = new TextDecoder(); + + for (let i = 0; i < nbOfEntries; ++i) { + if (offset + 46 > bytes.length || view.getUint32(offset, true) !== ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE) { + return false; + } + + const fileNameLength = view.getUint16(offset + 28, true); + const extraFieldLength = view.getUint16(offset + 30, true); + const fileCommentLength = view.getUint16(offset + 32, true); + + if (offset + 46 + fileNameLength > bytes.length) { + return false; + } + + if (textDecoder.decode(bytes.subarray(offset + 46, offset + 46 + fileNameLength)) === 'manifest.xml') { + return true; + } + + offset += 46 + fileNameLength + extraFieldLength + fileCommentLength; + } + + return false; +}; + +const xmlRootElement = (text) => { + // Return the local name and namespace of the root element of the given XML text, skipping its prolog (i.e. its XML + // declaration, processing instructions, comments, and document type declaration), or null if there is no such root + // element. If the given XML text ends before the start tag of its root element does, i.e. if it is only the start of + // some XML text, then let the caller know that it is incomplete. + + const INCOMPLETE = { incomplete: true }; + + const skipPast = (string, marker, from = 0) => { + const index = string.indexOf(marker, from); + + return index === -1 ? null : string.slice(index + marker.length); + }; + + let rest = text; + + for (;;) { + rest = rest.trimStart(); + + if (rest.startsWith(''); + } else if (rest.startsWith(''); + } else if (rest.startsWith(''); + + if (internalSubsetStart !== -1 && (declarationEnd === -1 || internalSubsetStart < declarationEnd)) { + const internalSubsetEnd = rest.indexOf(']', internalSubsetStart); + + rest = internalSubsetEnd === -1 ? null : skipPast(rest, '>', internalSubsetEnd); + } else { + rest = skipPast(rest, '>'); + } + } else { + break; + } + + if (rest === null) { + return INCOMPLETE; + } + } + + const match = /^<(?:([\w.-]+):)?([\w.-]+)((?:\s+[^\s=/>]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*\/?>/.exec(rest); + + if (!match) { + // Note: our XML text is incomplete if it is empty (e.g., it only has some whitespace so far) or if it ends within a + // start tag (e.g., that of our root element or of something that is part of our prolog, like "')) ? INCOMPLETE : null; + } + + const [, prefix, name, attributes] = match; + const namespaceAttribute = prefix ? `xmlns:${prefix}` : 'xmlns'; + + for (const [, attribute, doubleQuotedValue, singleQuotedValue] of attributes.matchAll( + /([^\s=]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g + )) { + if (attribute === namespaceAttribute) { + return { name, namespace: doubleQuotedValue ?? singleQuotedValue }; + } + } + + return { name, namespace: null }; +}; + +const xmlEncoding = (bytes) => { + // Return the encoding of the given XML bytes, as detected from their first bytes (see Appendix F of the XML + // specification, https://www.w3.org/TR/xml/#sec-guessing), i.e. using a byte order mark or, failing that, the way the + // start of an XML declaration (" { + return new TextDecoder(xmlEncoding(bytes)).decode(bytes); +}; + +const isIncompleteXmlProlog = (bytes) => { + return xmlRootElement(xmlText(bytes))?.incomplete === true; +}; + +const isCellmlOrSedmlFile = (bytes) => { + const rootElement = xmlRootElement(xmlText(bytes)); + + if (!rootElement?.namespace) { + return false; + } + + return ( + (rootElement.name === 'model' && rootElement.namespace.startsWith('http://www.cellml.org/cellml/')) || + (rootElement.name === 'sedML' && rootElement.namespace.startsWith('http://sed-ml.org/')) + ); +}; + +const hasCsvExtension = (url) => { + try { + return new URL(url).pathname.toLowerCase().endsWith('.csv'); } catch { return false; } }; +const isCsvFile = (bytes, complete, csvExtension) => { + // Check that the given bytes are a CSV file, i.e. text (without control characters other than tabs and line breaks). + // If the URL we ended up at (i.e. after following redirects) has a .csv extension, then that is all we check since + // the app can tell the user why the CSV file cannot be used, if needed (see parseExternalCsvData()). Otherwise, the + // content is all we have to go by, so we also check that the text looks like a CSV file as expected by the app, i.e. + // lines are separated by line breaks and values by commas, there are at least two lines (i.e. a header and some + // data), and every line has the same number of values, which must be at least two (i.e. a VOI column and a data + // column). Like the app, we decode the text as UTF-8 (replacing invalid byte sequences, like Response.text() does), + // trim lines, and skip blank lines. + // Note: if we only have the start of the CSV file, then we ignore its last (and likely incomplete) line. + + const text = new TextDecoder().decode(bytes, { stream: !complete }); + + // biome-ignore lint/suspicious/noControlCharactersInRegex: we are looking for control characters. + if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(text)) { + return false; + } + + if (csvExtension) { + return true; + } + + const lines = text.split(/\r?\n/); + + if (!complete) { + lines.pop(); + } + + let nbOfLines = 0; + let nbOfValuesPerLine = -1; + + for (const line of lines) { + const trimmedLine = line.trim(); + + if (!trimmedLine) { + continue; + } + + const nbOfValues = trimmedLine.split(',').length; + + if (nbOfValues < 2 || (nbOfValuesPerLine !== -1 && nbOfValues !== nbOfValuesPerLine)) { + return false; + } + + nbOfValuesPerLine = nbOfValues; + + ++nbOfLines; + } + + return nbOfLines >= 2; +}; + +const errorResponse = (message, status) => { + // Note: our error responses must also have our CORS headers, so that a browser lets the app know why we failed. + + return new Response(message, { + status, + headers: CONTENT_HEADERS + }); +}; + +const errorMessage = (error) => { + return (error instanceof Error ? error.message : String(error)).replace(/\.+$/, ''); +}; + +const rejectedResponse = (reason) => { + return errorResponse(`Target content is not allowed (${reason}).`, 403); +}; + +const hasKnownSize = (response) => { + // Check whether the size of the given response's content is known in advance, i.e. whether it has a content length + // and its content is not compressed. + // Note: if the content is compressed, then fetch() decompresses it for us, but the content length is that of the + // compressed content. + + const contentEncoding = response.headers.get('content-encoding'); + + return ( + /^\d+$/.test(response.headers.get('content-length') ?? '') && + (!contentEncoding || contentEncoding.toLowerCase() === 'identity') + ); +}; + const forwardRequest = async (targetUrl, request) => { const forwardHeaders = new Headers(request.headers); @@ -35,9 +436,11 @@ const forwardRequest = async (targetUrl, request) => { 'authorization', 'cookie', 'connection', + 'if-range', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', + 'range', 'te', 'trailers', 'transfer-encoding', @@ -47,20 +450,97 @@ const forwardRequest = async (targetUrl, request) => { } const response = await fetch(targetUrl, { - method: request.method, - headers: forwardHeaders, - body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : null + method: 'GET', + headers: forwardHeaders }); - const newHeaders = new Headers(response.headers); + // Note: we don't return the body of an error response since it is likely to be an HTML page. + + if (!response.ok || !response.body) { + return response.ok + ? rejectedResponse('no content') + : new Response(null, { + status: response.status, + headers: CONTENT_HEADERS + }); + } + + // Reject content that we know is too large. + // Note: the content length may not be known or, if the content is compressed, it may be smaller than the actual size + // of the content, so we also check the size of the content as we read it. + + if (Number(response.headers.get('content-length')) > MAX_SIZE) { + await response.body.cancel(); + + return rejectedResponse('too large'); + } + + // A CellML, SED-ML, or CSV file only needs its start to be checked, after which it can be streamed, but only if we + // know that it isn't too large (see above). Otherwise, we need to fully read it first, so that we can reject it if it + // is too large. Indeed, once we have started streaming it, all we could do is abort it, which the app would see as a + // network error rather than as a rejection, so it wouldn't try to fetch the target URL directly instead (see + // fetchThroughCorsProxy() in src/renderer/src/common/common.ts). + // Note: when streaming, we still check the size of the content as we read it, in case the content length is wrong + // (see replayStream()). + + const reader = response.body.getReader(); + let { bytes: head, done } = await readBytes(reader, SNIFF_SIZE); + + // Read more of the content if it is an XML file which prolog is longer than what we have read so far (see + // MAX_XML_PROLOG_SIZE). + + while (!done && head.length < MAX_XML_PROLOG_SIZE && isIncompleteXmlProlog(head)) { + const more = await readChunks(reader, Math.min(2 * head.length, MAX_XML_PROLOG_SIZE), [head]); + + head = concatenate(more.chunks, more.length); + done = more.done; + } + + // Note: the URL of the response is the one we ended up at, i.e. after following redirects (e.g., a figshare download + // URL redirects to the actual file). We only check its extension, not that of the target URL, since the latter + // could otherwise be used to proxy any text (e.g., https://example.com/file.csv redirecting to any Web page). + + if (isCellmlOrSedmlFile(head) || isCsvFile(head, done, hasCsvExtension(response.url))) { + if (done || hasKnownSize(response)) { + return new Response(done ? head : replayStream(head, reader), { + status: response.status, + headers: CONTENT_HEADERS + }); + } + + const bytes = await readRest(reader, head); + + if (!bytes) { + return rejectedResponse('too large'); + } + + return new Response(bytes, { + status: response.status, + headers: CONTENT_HEADERS + }); + } + + // An OMEX file needs to be fully read since we need its central directory, which is at the end of the file. + + if (!isZipFile(head)) { + await reader.cancel(); + + return rejectedResponse('not a CellML, SED-ML, OMEX, or CSV file'); + } + + const bytes = done ? head : await readRest(reader, head); + + if (!bytes) { + return rejectedResponse('too large'); + } - for (const [name, value] of Object.entries(CORS_HEADERS)) { - newHeaders.set(name, value); + if (!isOmexFile(bytes)) { + return rejectedResponse('OMEX file without a manifest.xml file'); } - return new Response(response.body, { + return new Response(bytes, { status: response.status, - headers: newHeaders + headers: CONTENT_HEADERS }); }; @@ -79,22 +559,27 @@ export default { const targetUrl = extractTargetUrl(url); if (!targetUrl) { - return new Response('Missing "url" query parameter.', { status: 400 }); + return errorResponse('Missing "url" query parameter.', 400); } if (!isValidTargetUrl(targetUrl)) { - return new Response('Target URL is not allowed.', { status: 403 }); + return errorResponse('Target URL is not allowed.', 403); } - if (!['GET', 'POST'].includes(request.method)) { - return new Response('Method not allowed.', { status: 405 }); + if (request.method !== 'GET') { + return errorResponse('Method not allowed.', 405); } - return await forwardRequest(targetUrl, request); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); + // Note: we may not be able to fetch the target URL (e.g., it is only reachable from the user's network), in which + // case the app may still be able to fetch it directly, so we let it know using an HTTP 502 error. - return new Response(`An error occurred: ${message}`, { status: 500 }); + try { + return await forwardRequest(targetUrl, request); + } catch (error) { + return errorResponse(`Target URL could not be fetched (${errorMessage(error)}).`, 502); + } + } catch (error) { + return errorResponse(`An error occurred (${errorMessage(error)}).`, 500); } } }; diff --git a/src/main/ApplicationWindow.ts b/src/main/ApplicationWindow.ts index ba4860da7..378cab22d 100644 --- a/src/main/ApplicationWindow.ts +++ b/src/main/ApplicationWindow.ts @@ -2,7 +2,11 @@ import electron from 'electron'; import path from 'node:path'; export class ApplicationWindow extends electron.BrowserWindow { - constructor(options: electron.BrowserWindowConstructorOptions) { + // Note: a window that doesn't need to communicate with our main process (e.g., our splash screen window) doesn't need + // our preload script, which is heavy (it loads the native libOpenCOR module, among other things), in which case + // it also uses Electron's default (i.e. secure) Web preferences. + + constructor(options: electron.BrowserWindowConstructorOptions, usePreload = true) { // Add some common options and call our parent constructor. // Note: we use backgroundColor to minimise the flickering that may occur when first showing a window. This means // that the colours used here should be the same as the ones used by --p-content-background in PrimeVue, i.e. @@ -11,10 +15,13 @@ export class ApplicationWindow extends electron.BrowserWindow { options.backgroundColor = electron.nativeTheme.shouldUseDarkColors ? '#18181b' : '#ffffff'; options.show = false; options.useContentSize = true; - options.webPreferences = { - preload: path.join(import.meta.dirname, '../preload/index.mjs'), - sandbox: false - }; + + if (usePreload) { + options.webPreferences = { + preload: path.join(import.meta.dirname, '../preload/index.mjs'), + sandbox: false + }; + } super(options); @@ -24,4 +31,15 @@ export class ApplicationWindow extends electron.BrowserWindow { this.show(); }); } + + // Send a message to our renderer. + // Note: we may have been closed (and therefore destroyed) while some asynchronous operation was in progress (e.g., + // checking for updates or showing an open dialog), in which case sending a message would throw an "Object has + // been destroyed" error, so we don't send it. + + send(channel: string, ...args: unknown[]): void { + if (!this.isDestroyed() && !this.webContents.isDestroyed()) { + this.webContents.send(channel, ...args); + } + } } diff --git a/src/main/MainMenu.ts b/src/main/MainMenu.ts index eae769780..e3c757034 100644 --- a/src/main/MainMenu.ts +++ b/src/main/MainMenu.ts @@ -1,17 +1,23 @@ import electron from 'electron'; import { formatError } from '../renderer/src/common/common'; -import { isMacOs, isPackaged } from '../renderer/src/common/electron'; +import { isMacOs } from '../renderer/src/common/electron'; import { mainWindow } from './index'; -import { clearRecentFiles } from './MainWindow'; +import { canCheckForUpdates, clearRecentFiles } from './MainWindow'; let enabledMenu: electron.Menu | null = null; let disabledMenu: electron.Menu | null = null; let recentFilePaths: string[] = []; let hasFiles = false; +let menuEnabled = false; +// Note: our main menu is disabled until our renderer enables it (see MainWindow's constructor). export const enableDisableMainMenu = (enable: boolean): void => { + // Keep track of whether our menu is enabled (see updateReopenMenu()). + + menuEnabled = enable; + // Build our menu, if needed. if (enable && enabledMenu) { @@ -25,17 +31,17 @@ export const enableDisableMainMenu = (enable: boolean): void => { label: 'Settings...', accelerator: 'CmdOrCtrl+,', click: () => { - mainWindow?.webContents.send('settings'); + mainWindow?.send('settings'); } }; let checkForUpdatesMenuItem: electron.MenuItemConstructorOptions | null = null; - if (isPackaged()) { + if (canCheckForUpdates()) { checkForUpdatesMenuItem = { label: 'Check for Updates...', click: () => { - mainWindow?.webContents.send('check-for-updates'); + mainWindow?.send('check-for-updates'); } }; } @@ -43,7 +49,7 @@ export const enableDisableMainMenu = (enable: boolean): void => { const aboutOpencorMenuItem: electron.MenuItemConstructorOptions = { label: 'About OpenCOR', click: () => { - mainWindow?.webContents.send('about'); + mainWindow?.send('about'); } }; @@ -98,7 +104,7 @@ export const enableDisableMainMenu = (enable: boolean): void => { label: 'Open Remote...', accelerator: 'CmdOrCtrl+Shift+O', click: () => { - mainWindow?.webContents.send('open-remote'); + mainWindow?.send('open-remote'); } }); fileSubMenu.push({ @@ -107,7 +113,7 @@ export const enableDisableMainMenu = (enable: boolean): void => { { label: 'Lorenz', click: () => { - mainWindow?.webContents.send('open-sample-lorenz'); + mainWindow?.send('open-sample-lorenz'); } } ] @@ -119,7 +125,7 @@ export const enableDisableMainMenu = (enable: boolean): void => { label: 'Most Recent', accelerator: 'CmdOrCtrl+Shift+T', click: () => { - mainWindow?.webContents.send('open', recentFilePaths[0]); + mainWindow?.send('open', recentFilePaths[0]); }, enabled: recentFilePaths.length > 0 }); @@ -131,7 +137,7 @@ export const enableDisableMainMenu = (enable: boolean): void => { fileReopenSubMenu.push({ label: filePath, click: () => { - mainWindow?.webContents.send('open', filePath); + mainWindow?.send('open', filePath); } }); }); @@ -158,7 +164,7 @@ export const enableDisableMainMenu = (enable: boolean): void => { label: 'Close', accelerator: 'CmdOrCtrl+W', click: () => { - mainWindow?.webContents.send('close'); + mainWindow?.send('close'); }, enabled: hasFiles }); @@ -166,7 +172,7 @@ export const enableDisableMainMenu = (enable: boolean): void => { id: 'fileCloseAll', label: 'Close All', click: () => { - mainWindow?.webContents.send('close-all'); + mainWindow?.send('close-all'); }, enabled: hasFiles }); @@ -222,7 +228,7 @@ export const enableDisableMainMenu = (enable: boolean): void => { toolsSubMenu.push({ label: 'Reset All...', click: () => { - mainWindow?.webContents.send('reset-all'); + mainWindow?.send('reset-all'); } }); @@ -293,9 +299,12 @@ export const enableDisableMainMenu = (enable: boolean): void => { }; export const enableDisableFileCloseAndCloseAllMenuItems = (enable: boolean): void => { - if (enabledMenu) { - hasFiles = enable; + // Note: we keep track of whether we have files even if we don't currently have an enabled menu (e.g., because it got + // invalidated while our menu was disabled, see updateReopenMenu()), so that it gets built accordingly. + + hasFiles = enable; + if (enabledMenu) { const fileCloseMenu = enabledMenu.getMenuItemById('fileClose'); const fileCloseAllMenu = enabledMenu.getMenuItemById('fileCloseAll'); @@ -307,8 +316,14 @@ export const enableDisableFileCloseAndCloseAllMenuItems = (enable: boolean): voi }; export const updateReopenMenu = (filePaths: string[]): void => { + // Invalidate our enabled menu so that it gets rebuilt with our new Reopen menu, but only rebuild (and set) it now if + // our menu is enabled. Indeed, our menu may be disabled (e.g., because a dialog is open in our renderer), in which + // case it must remain disabled and it will get rebuilt when it gets enabled again. + enabledMenu = null; recentFilePaths = filePaths; - enableDisableMainMenu(true); + if (menuEnabled) { + enableDisableMainMenu(true); + } }; diff --git a/src/main/MainWindow.ts b/src/main/MainWindow.ts index 7615d11bd..c850c0358 100644 --- a/src/main/MainWindow.ts +++ b/src/main/MainWindow.ts @@ -1,63 +1,150 @@ import electron from 'electron'; import { autoUpdater, type ProgressInfo, type UpdateCheckResult } from 'electron-updater'; +import fs from 'node:fs'; import path from 'node:path'; -import { formatError, isDataUrlOmexFileName, type ISettings, isUrl } from '../renderer/src/common/common'; -import { FULL_URI_SCHEME, LONG_DELAY, SHORT_DELAY } from '../renderer/src/common/constants'; -import { isLinux, isMacOs, isPackaged, isWindows } from '../renderer/src/common/electron'; +import { formatError, isDataUrlFileName, type ISettings, isUrl } from '../renderer/src/common/common'; +import { FULL_URI_SCHEME, LONG_DELAY } from '../renderer/src/common/constants'; +import { appImagePath, isMacOs, isPackaged } from '../renderer/src/common/electron'; /* TODO: enable once our GitHub integration is fully ready. import { deleteGitHubAccessToken } from '../renderer/src/common/gitHubIntegration'; */ import icon from './assets/icon.png?asset'; import { ApplicationWindow } from './ApplicationWindow'; -import { electronConf, type IElectronConfState } from './index'; -import { enableDisableMainMenu, updateReopenMenu } from './MainMenu'; +import { electronConf, type IElectronConfState, takeElectronConfBackupFileName } from './index'; +import { enableDisableFileCloseAndCloseAllMenuItems, enableDisableMainMenu, updateReopenMenu } from './MainMenu'; import type { SplashScreenWindow } from './SplashScreenWindow'; autoUpdater.autoDownload = false; autoUpdater.logger = null; -export const checkForUpdates = (atStartup: boolean): void => { - // Check for updates, if requested and if OpenCOR is packaged. +// Determine whether we can check for updates. +// Note: we can only check for updates if OpenCOR is packaged and has some update information, i.e. an app-update.yml +// file next to its app.asar file, which electron-builder only generates when building a release (e.g., not when +// building an unpacked version of OpenCOR using electron-builder --dir). + +let _canCheckForUpdates: boolean | null = null; + +export const canCheckForUpdates = (): boolean => { + _canCheckForUpdates ??= isPackaged() && fs.existsSync(path.join(process.resourcesPath, 'app-update.yml')); + + return _canCheckForUpdates; +}; - if (isPackaged() && electronConf.get('settings.general.checkForUpdatesAtStartup')) { +// Keep track of the state of an update, i.e. whether it is being downloaded, has been downloaded, or is being installed. +// Note: our renderer process may be reloaded while an update is being downloaded or installed (e.g., after it crashed), +// in which case we must not check for updates (e.g., at startup) since an issue with checking for updates could +// get forwarded as an issue with installing the update (see below) and the update could be offered (and +// downloaded) again. Instead, we let our renderer know about the update (see resendUpdateState()). + +type UpdateState = 'idle' | 'downloading' | 'downloaded' | 'installing'; + +let updateState: UpdateState = 'idle'; +let updateDownloadPercent = 0; + +export const checkForUpdates = (atStartup: boolean): void => { + // Check for updates, if we can and if requested (i.e. at startup, if the user wants us to, or by the user), and if we + // are not already dealing with an update. + + if ( + canCheckForUpdates() && + (!atStartup || electronConf.get('settings.general.checkForUpdatesAtStartup')) && + updateState === 'idle' + ) { autoUpdater .checkForUpdates() .then((result: UpdateCheckResult | null) => { const updateAvailable = result?.isUpdateAvailable ?? false; if (updateAvailable) { - MainWindow.instance?.webContents.send('update-available', result?.updateInfo.version); + MainWindow.instance?.send('update-available', result?.updateInfo.version); } else if (!atStartup) { - MainWindow.instance?.webContents.send('update-not-available'); + MainWindow.instance?.send('update-not-available'); } }) .catch((error: unknown) => { - MainWindow.instance?.webContents.send('update-check-error', formatError(error)); + MainWindow.instance?.send('update-check-error', formatError(error)); }); } }; autoUpdater.on('download-progress', (info: ProgressInfo) => { - MainWindow.instance?.webContents.send('update-download-progress', info.percent); + if (updateState === 'downloading') { + updateDownloadPercent = info.percent; + + MainWindow.instance?.send('update-download-progress', info.percent); + } +}); + +// Forward any issue with installing an update. +// Note: on macOS, electron-updater hands a downloaded update over to Squirrel.Mac, which reports any issue (e.g., the +// update couldn't be staged or its signature is invalid) only through an error event, i.e. after downloadUpdate() +// has resolved, so our renderer would otherwise wait forever for the update to be installed. Other issues are +// reported through the promises returned by checkForUpdates() and downloadUpdate() (as well as through an error +// event), so we only forward error events once an update has been downloaded, to avoid reporting an issue twice. +// Once forwarded, the installation of the update has failed, so we are done with it (and any subsequent error +// event, e.g., from checking for updates again, must not be forwarded as an issue with installing an update). + +autoUpdater.on('error', (error: Error) => { + if (updateState === 'downloaded' || updateState === 'installing') { + updateState = 'idle'; + + MainWindow.instance?.send('update-install-error', formatError(error)); + } }); export const downloadAndInstallUpdate = (): void => { + updateState = 'downloading'; + updateDownloadPercent = 0; + autoUpdater .downloadUpdate() .then(() => { - MainWindow.instance?.webContents.send('update-downloaded'); + updateState = 'downloaded'; + updateDownloadPercent = 100; + + MainWindow.instance?.send('update-downloaded'); }) .catch((error: unknown) => { - MainWindow.instance?.webContents.send('update-download-error', formatError(error)); + updateState = 'idle'; + + MainWindow.instance?.send('update-download-error', formatError(error)); }); }; export const installUpdateAndRestart = (): void => { + // Install the update and restart OpenCOR. + // Note: our renderer may ask us to install the update more than once (see resendUpdateState()), but electron-updater + // must only be asked once (e.g., on macOS, it would otherwise install the update once Squirrel.Mac has fetched it + // as many times as it was asked to). + + if (updateState !== 'downloaded') { + return; + } + + updateState = 'installing'; + autoUpdater.quitAndInstall(true, true); }; +const resendUpdateState = (): void => { + // Let our renderer know about the update that is being downloaded or installed, if any. + // Note: this is needed when our renderer has been reloaded (e.g., after it crashed), so that it can show the progress + // of the update (and block its UI) and ask us to install the update once it has been downloaded (in case our + // previous renderer didn't get a chance to do so). + + if (updateState === 'idle') { + return; + } + + MainWindow.instance?.send('update-download-progress', updateDownloadPercent); + + if (updateState === 'downloaded') { + MainWindow.instance?.send('update-downloaded'); + } +}; + export const loadSettings = (): ISettings => { return electronConf.get('settings'); }; @@ -75,18 +162,39 @@ export const resetAll = (): void => { deleteGitHubAccessToken(); */ - electron.app.relaunch(); + // Note: when running as an AppImage, process.execPath (which Electron relaunches by default) is within the AppImage's + // temporary mount point, which disappears once OpenCOR exits, so we relaunch the AppImage itself instead (see + // appImagePath() and the creation of our desktop file in index.ts). In that case, we must also pass our + // original arguments (e.g., --no-sandbox, which may be needed for OpenCOR to start at all) since, unlike by + // default, Electron doesn't relaunch us with them when we specify the path to relaunch. + + const appImage = appImagePath(); + + electron.app.relaunch(appImage ? { execPath: appImage, args: process.argv.slice(1) } : undefined); electron.app.quit(); }; +// Our recent files, as well as the files that are opened and the file that is selected, are identified by their +// canonical path, as determined by libOpenCOR (and given to us by our renderer), so that a file is only listed once +// even if it was opened using different paths (e.g., "/tmp/model.cellml" and "/private/tmp/model.cellml" on macOS). + let recentFilePaths: string[] = []; -const removeRecentFilePath = (filePath: string): void => { +const removeRecentFilePath = (filePath: string): boolean => { + // Remove the given file path from our recent file paths and return whether it was removed (in which case our Reopen + // menu needs to be updated). + + let res = false; + for (let i = recentFilePaths.length - 1; i >= 0; --i) { if (recentFilePaths[i] === filePath) { recentFilePaths.splice(i, 1); + + res = true; } } + + return res; }; export const clearRecentFiles = (): void => { @@ -96,9 +204,9 @@ export const clearRecentFiles = (): void => { }; export const fileClosed = (filePath: string): void => { - // Make sure that the file is not a COMBINE archive that was opened using a data URL. + // Make sure that the file was not opened using a data URL (see isDataUrlFileName()). - if (isDataUrlOmexFileName(filePath)) { + if (isDataUrlFileName(filePath)) { return; } @@ -113,22 +221,62 @@ export const fileClosed = (filePath: string): void => { updateReopenMenu(recentFilePaths); }; -export const fileIssue = (filePath: string): void => { - removeRecentFilePath(filePath); +export const recentFilePathCanonicalised = (filePath: string, canonicalFilePath: string): void => { + // Replace the given (non-canonical) file path with its canonical version in our recent file paths (see + // MainWindow.rendererReady()), unless it is no longer there (e.g., if our Reopen menu got cleared in the meantime), + // making sure that the canonical file path is only listed once. + + const index = recentFilePaths.indexOf(filePath); + + if (filePath === canonicalFilePath || index === -1) { + return; + } + + recentFilePaths[index] = canonicalFilePath; + + for (let i = recentFilePaths.length - 1; i >= 0; --i) { + if (i !== index && recentFilePaths[i] === canonicalFilePath) { + recentFilePaths.splice(i, 1); + } + } updateReopenMenu(recentFilePaths); }; -export const fileOpened = (filePath: string): void => { - removeRecentFilePath(filePath); +// Note: for fileIssue() and fileOpened(), filePath is the path that was used to (try to) open the file, which may be +// the path that we asked our renderer to open (see reopenFilePathsAndSelectFilePath()). - updateReopenMenu(recentFilePaths); +const removeRecentFilePaths = (filePath: string, canonicalFilePath: string): void => { + // Remove both the canonical path of the file and the path that was used to (try to) open it from our recent file + // paths. + // Note: our recent file paths are normally canonical, but they may also include some paths that we couldn't + // canonicalise (see onRenderProcessGone()), in which case the canonical path of such a file is only known once + // our renderer has canonicalised it (see recentFilePathCanonicalised()) or (tried to) open it. - // A file has been opened, but it may have been opened while reopening files during OpenCOR startup, in which case we - // need to reopen the next file, hence our call to reopenFilePathsAndSelectFilePath(), which will do nothing if there - // are no more files to reopen. + const filePathRemoved = filePath !== canonicalFilePath && removeRecentFilePath(filePath); + const canonicalFilePathRemoved = removeRecentFilePath(canonicalFilePath); - MainWindow.instance?.reopenFilePathsAndSelectFilePath(); + if (filePathRemoved || canonicalFilePathRemoved) { + updateReopenMenu(recentFilePaths); + } +}; + +export const fileIssue = (filePath: string, canonicalFilePath: string): void => { + removeRecentFilePaths(filePath, canonicalFilePath); + + // A file couldn't be opened, possibly while reopening files during OpenCOR startup, in which case we need to make sure + // that it doesn't get selected and reopen the next file. + + MainWindow.instance?.fileOpenedOrNot(filePath, false); +}; + +export const fileOpened = (filePath: string, canonicalFilePath: string): void => { + removeRecentFilePaths(filePath, canonicalFilePath); + + // A file has been opened, possibly while reopening files during OpenCOR startup, in which case we need to reopen the + // next file. + + MainWindow.instance?.fileOpenedOrNot(filePath, true); }; let openedFilePaths: string[] = []; @@ -147,15 +295,121 @@ export const fileSelected = (filePath: string): void => { selectedFilePath = filePath; }; +// Retrieve the files that are currently open and the file that is currently selected, so that they can be reopened and +// selected (e.g., the next time OpenCOR is started or after our renderer has crashed). +// Note: a file that was opened using a data URL only exists in memory (under a made-up name, e.g., "OMEX #1", see +// isDataUrlFileName()), so it cannot be reopened. Hence, we don't reopen such a file and, if it is the selected +// file, we select the first file that is to be reopened instead. + +interface IFilesToReopen { + opened: string[]; + selected: string; +} + +const currentFilesToReopen = (): IFilesToReopen => { + const opened = openedFilePaths.filter((filePath) => !isDataUrlFileName(filePath)); + const selected = selectedFilePath && !isDataUrlFileName(selectedFilePath) ? selectedFilePath : (opened[0] ?? ''); + + return { opened, selected }; +}; + +// Determine whether the given argument is an OpenCOR action (i.e. an opencor:// link). + +const isAction = (argument: string | undefined): boolean => { + return argument?.startsWith(FULL_URI_SCHEME) ?? false; +}; + +// Normalise the given command line (i.e. the process.argv of an instance of OpenCOR), so that it only contains the +// files to open and the OpenCOR actions to handle, with files given as absolute paths. +// Note: this is used both for our own command line and for the command line of another instance of OpenCOR that was +// started while we were running (see the second-instance event in src/main/index.ts), in which case relative +// paths must be resolved against the working directory of that other instance. + +export const normaliseCommandLine = (commandLine: string[], workingDirectory: string): string[] => { + const res = [...commandLine]; + + // The command line can either be a classical command line or an OpenCOR action (i.e. an opencor:// link). In the + // former case, we need to remove the path to OpenCOR and, if we are not packaged, the path to our app, while, in the + // latter case, nothing should be removed. + + if (!isAction(res[0])) { + res.shift(); + + if (!isPackaged() && res.length) { + res.shift(); + } + } + + // Ignore any switch (e.g., --updated when auto-updating OpenCOR on Windows and macOS, --no-sandbox on Linux, or any + // switch added by Electron or Chromium) and make sure that local files are given as absolute paths. + + return res + .filter((argument) => !argument.startsWith('--')) + .map((argument) => + isAction(argument) || isUrl(argument) || path.isAbsolute(argument) + ? argument + : path.resolve(workingDirectory, argument) + ); +}; + +// Retrieve the message to let the user know that our configuration file couldn't be loaded, and that our default +// configuration is therefore used instead, if needed (see createElectronConf()). +// Note: the backup of our configuration file is only retrieved once (see takeElectronConfBackupFileName()), so the user +// is only told about it once, whichever way they are told about it. + +const electronConfResetMessage = (): { message: string; detail: string } | null => { + const backupFileName = takeElectronConfBackupFileName(); + + if (!backupFileName) { + return null; + } + + return { + message: 'OpenCOR could not read its configuration file, so it has been reset.', + detail: `The position and size of the window, the files that were open, the recent files, and the settings have been reset to their defaults.\n\nThe configuration file that could not be read has been kept as:\n${backupFileName}` + }; +}; + +// Report a fatal error (e.g., OpenCOR couldn't be started) and quit. +// Note: our splash screen window is always on top, so we must close it first. Otherwise, it would hide our error dialog +// and remain shown forever. + +export const reportFatalErrorAndQuit = (message: string, splashScreenWindow?: SplashScreenWindow | null): void => { + console.error(`OpenCOR: ${message}`); + + if (splashScreenWindow && !splashScreenWindow.isDestroyed()) { + splashScreenWindow.close(); + } + + // Let the user know if our configuration file had to be reset since we are about to quit, i.e. they wouldn't be told + // about it otherwise. + + const confResetMessage = electronConfResetMessage(); + + electron.dialog.showErrorBox( + 'OpenCOR', + confResetMessage ? `${message}\n\n${confResetMessage.message} ${confResetMessage.detail}` : message + ); + + electron.app.quit(); +}; + export class MainWindow extends ApplicationWindow { // Properties. static instance: MainWindow | null = null; - private _splashScreenWindowClosed = false; - + private _splashScreenWindow: SplashScreenWindow | null = null; + private _rendererReady = false; + private _closing = false; + private _rendererGone = false; + private _rendererGoneCount = 0; + private _pendingArguments: string[] = []; + private _recentFilePathsToCanonicalise: string[] = []; + private _filesToReopen: IFilesToReopen | null = null; private _openedFilePaths: string[] = []; private _openedFilePathIndex = 0; + private _reopeningFilePath: string | null = null; private _selectedFilePath = ''; // Constructor. @@ -179,12 +433,30 @@ export class MainWindow extends ApplicationWindow { MainWindow.instance = this; + this.on('closed', () => { + if (MainWindow.instance === this) { + MainWindow.instance = null; + } + }); + // Set our dock icon (macOS only). if (!isPackaged() && isMacOs()) { electron.app.dock?.setIcon(icon); } + // Close our splash screen window with a short delay once we are visible. + // Note: we must do this before restoring our state since maximising a window may also show it (on Windows and + // Linux), in which case we would otherwise miss the show event and never close our splash screen window. + + this._splashScreenWindow = splashScreenWindow; + + this.once('show', () => { + setTimeout(() => { + this.closeSplashScreenWindow(); + }, LONG_DELAY); + }); + // Restore our state, if needed. if (state.isMaximized) { @@ -193,66 +465,15 @@ export class MainWindow extends ApplicationWindow { this.setFullScreen(true); } - // Ask for the splash screen window to be closed with a short delay once we are visible and handle our command line - // (also with a short delay if needed). - - this.once('show', () => { - let handleCommandLineDelay = SHORT_DELAY; - - if (!this._splashScreenWindowClosed) { - this._splashScreenWindowClosed = true; - - handleCommandLineDelay = LONG_DELAY; - - setTimeout(() => { - splashScreenWindow.close(); - }, LONG_DELAY); - } - - setTimeout(() => { - // Retrieve the recently opened files and our Reopen menu. - // Note: for some reasons, recentFilePAths may, in some cases, end up having only one null entry, so just in - // case we filter out all null entries. - - recentFilePaths = (electronConf.get('app.files.recent') as string[]).filter( - (filePath: string | null) => filePath - ); - - updateReopenMenu(recentFilePaths); - - // Reopen previously opened files, if any, and select the previously selected file. - - this._openedFilePaths = electronConf.get('app.files.opened'); - this._openedFilePathIndex = 0; - this._selectedFilePath = electronConf.get('app.files.selected'); - - this.reopenFilePathsAndSelectFilePath(); - - // The command line can either be a classical command line or an OpenCOR action (i.e. an opencor:// link). In - // the former case, we need to remove one or two arguments while, in the latter case, nothing should be removed. - - if (!this.isAction(commandLine[0])) { - // The first argument is not an action, so remove the first argument and then the second argument, but only - // if we are not packaged. - - commandLine.shift(); + // Retrieve the recently opened files and our Reopen menu. - if (!isPackaged() && commandLine.length) { - commandLine.shift(); - } - } + recentFilePaths = electronConf.get('app.files.recent'); - // When auto updating OpenCOR, we may end up with an extra argument that we need to ignore. + updateReopenMenu(recentFilePaths); - if ((isWindows() || isMacOs()) && commandLine[0] === '--updated') { - commandLine.shift(); - } else if (isLinux() && commandLine[0] === '--no-sandbox') { - commandLine.shift(); - } + // Handle our command line, which will effectively be done once our renderer is ready (see rendererReady()). - this.handleArguments(commandLine); - }, handleCommandLineDelay); - }); + this.handleArguments(normaliseCommandLine(commandLine, process.cwd())); // Keep track of our settings unless we are resetting all. @@ -287,23 +508,27 @@ export class MainWindow extends ApplicationWindow { electronConf.set('app.files.recent', recentFilePaths); // Opened files and selected file. - // Note: make sure that no data URL OMEX file is to be reopened or selected. + // Note: if our renderer is not ready, then either it crashed and we are being closed before it could be + // reloaded (or instead of being reloaded), in which case we save the files that were open (and selected) + // at the time of the crash, unless the user chose not to reopen them (see onRenderProcessGone()), or it + // never was ready (e.g., OpenCOR couldn't be loaded), in which case it never told us about the files that + // are open, so we keep the files that were open when OpenCOR was last closed. - const actualOpenedFilePaths = openedFilePaths.filter((filePath) => !isDataUrlOmexFileName(filePath)); - let actualSelectedFilePath = selectedFilePath; + const filesToReopen = this._rendererReady ? this.filesToReopen() : this._filesToReopen; - if (selectedFilePath && isDataUrlOmexFileName(selectedFilePath)) { - actualSelectedFilePath = actualOpenedFilePaths[0] || null; + if (filesToReopen) { + electronConf.set('app.files.opened', filesToReopen.opened); + electronConf.set('app.files.selected', filesToReopen.selected); } - - electronConf.set('app.files.opened', actualOpenedFilePaths); - electronConf.set('app.files.selected', actualSelectedFilePath); } }); - // Enable our main menu. + // Disable our main menu. + // Note: our renderer enables/disables our main menu depending on whether its UI is enabled (see compUiEnabled in + // src/renderer/src/components/OpenCOR.vue), so we keep it disabled until then. Otherwise, it could be used + // before our renderer is able to handle it (e.g., while it is being loaded or initialised). - enableDisableMainMenu(true); + enableDisableMainMenu(false); // Make sure that our menu bar is always visible. @@ -363,77 +588,464 @@ export class MainWindow extends ApplicationWindow { }; }); + // Prevent our renderer from navigating away from OpenCOR (e.g., if a file is dropped outside of the area where we + // handle drag and drop, in which case Chromium would replace OpenCOR with that file). Not only would the user lose + // their work, but the page we would navigate to would also have access to our preload API. + // Note #1: our renderer never needs to navigate to another origin (links are opened through our window open handler + // above), so we only allow navigations within our renderer's origin (e.g., a reload). + // Note #2: will-redirect is also emitted for subframes (unlike will-navigate), which may legitimately be redirected + // to another origin (e.g., the iframe used by Firebase for authentication), so we only check the + // navigations of our main frame. + + const rendererOrigin = new URL(rendererUrl).origin; + const preventNavigationAway = ( + event: electron.Event + ): void => { + if (!event.isMainFrame) { + return; + } + + let origin: string | null = null; + + try { + origin = new URL(event.url).origin; + } catch { + // The URL is invalid, so we block the navigation. + } + + if (origin !== rendererOrigin) { + console.warn(`OpenCOR: blocked attempt to navigate to ${event.url}.`); + + event.preventDefault(); + } + }; + + this.webContents.on('will-navigate', preventNavigationAway); + this.webContents.on('will-redirect', preventNavigationAway); + + // Handle our renderer process crashing (or being killed). + + this.webContents.on('render-process-gone', (_event, details) => { + this.onRenderProcessGone(details); + }); + // Load the renderer URL. + // Note #1: a navigation that gets interrupted by another navigation (e.g., our renderer being reloaded after it + // crashed) results in an ERR_ABORTED error while one that gets interrupted by us being closed (e.g., + // because OpenCOR is being quit) or destroyed results in an ERR_FAILED error. Neither is fatal, but since + // Electron rejects the navigation after we have been asked to close but before we have been destroyed, we + // need to keep track of whether we are being closed. + // Note #2: a navigation that gets interrupted by our renderer crashing (e.g., because of the native libOpenCOR + // module) also results in an ERR_FAILED error. This is not fatal either since onRenderProcessGone() lets + // the user reload OpenCOR (or quit it). However, Electron rejects the navigation before it emits + // render-process-gone and even before our Web contents is flagged as crashed, so we cannot tell straight + // away whether our renderer has crashed. So, rather than reporting the error straight away, we give our + // renderer some time to be reported as gone (in practice, it is a matter of milliseconds). + // Note #3: any other error means that OpenCOR cannot be used, so we report it and quit. + + const RENDERER_GONE_GRACE_PERIOD = 1000; + + this.once('close', () => { + this._closing = true; + }); + + electron.app.once('before-quit', () => { + this._closing = true; + }); + + // Note: on Windows, before-quit doesn't get emitted when the session ends (i.e. when shutting down or restarting + // the system, or when logging off), and our renderer may get killed before we get closed, so we also rely on + // session-end. We don't rely on query-session-end though since the session may not end after all (e.g., if + // another application prevents it from ending), in which case we would ignore our renderer crashing (see + // onRenderProcessGone()) for the rest of our lifetime. + + this.once('session-end', () => { + this._closing = true; + }); + + const loadingWasInterrupted = (error: unknown): boolean => { + if ( + (error as { code?: string }).code === 'ERR_ABORTED' || + this._closing || + this.isDestroyed() || + this.webContents.isDestroyed() || + this._rendererGone || + this.webContents.isCrashed() + ) { + console.warn(`OpenCOR: loading of URL (${rendererUrl}) was interrupted:`, formatError(error)); + + return true; + } + + return false; + }; this.loadURL(rendererUrl).catch((error: unknown) => { - console.error(`OpenCOR: failed to load URL (${rendererUrl}):`, formatError(error)); + if (loadingWasInterrupted(error)) { + return; + } + + setTimeout(() => { + if (loadingWasInterrupted(error)) { + return; + } + + reportFatalErrorAndQuit(`OpenCOR could not be loaded (${formatError(error)}).`, this._splashScreenWindow); + }, RENDERER_GONE_GRACE_PERIOD); + }); + } + + // Close our splash screen window, if it is still shown. + + closeSplashScreenWindow(): void { + if (this._splashScreenWindow && !this._splashScreenWindow.isDestroyed()) { + this._splashScreenWindow.close(); + } + + this._splashScreenWindow = null; + + this.reportElectronConfReset(); + } + + // Let the user know that our configuration file couldn't be loaded, and that our default configuration is therefore + // used instead, if needed (see createElectronConf()). + // Note #1: we wait for our splash screen window to be closed, so that our message box isn't hidden by it (it is + // always on top). + // Note #2: we don't wait for our renderer to be ready since it may never be (e.g., if its initialisation fails), in + // which case the user would never be told about it. If our renderer crashes or OpenCOR cannot be loaded, + // then the user is told about it as part of the error that gets reported (see onRenderProcessGone() and + // reportFatalErrorAndQuit()). + + private reportElectronConfReset(): void { + if ((this._splashScreenWindow && !this._splashScreenWindow.isDestroyed()) || this._closing || this.isDestroyed()) { + return; + } + + const confResetMessage = electronConfResetMessage(); + + if (!confResetMessage) { + return; + } + + electron.dialog + .showMessageBox(this, { + type: 'warning', + title: 'OpenCOR', + ...confResetMessage, + buttons: ['OK'] + }) + .catch((error: unknown) => { + console.warn('OpenCOR: failed to report that the configuration file has been reset:', formatError(error)); + }); + } + + // Our renderer process is gone (e.g., it crashed because of the native libOpenCOR module), so let the user know and + // either reload our renderer (and reopen the files that were open, or not) or quit. + // Note: our renderer may crash because of one of the files that are open (e.g., a model that crashes libOpenCOR when + // it gets simulated, which happens as soon as it is opened if it has a UI JSON). Reopening that file would then + // crash our renderer again, and again each time it is reloaded. We cannot reliably tell which file (if any) + // caused our renderer to crash, so we let the user choose between reloading our renderer with or without the + // files that were open, the latter allowing them to break such a cycle. + + onRenderProcessGone(details: electron.RenderProcessGoneDetails): void { + // Ignore our renderer being gone if we are being closed (e.g., because OpenCOR is being quit), whatever the reason. + // Indeed, our renderer will normally exit cleanly in that case, but it may also be killed (e.g., when logging off or + // shutting down the system) or crash while being torn down. Either way, there is nothing to recover since we are + // going away, so we must not offer to reload OpenCOR. + // Note: our renderer may also exit cleanly on its own (e.g., if the native libOpenCOR module calls exit(0)), in + // which case it is gone just as if it had crashed, so we handle it as such. + + if (this._closing || this.isDestroyed() || this.webContents.isDestroyed()) { + return; + } + + // Let our loading of the renderer URL know that our renderer is gone, so that it doesn't report it as a fatal error + // (see the constructor). + // Note: this must be done before showing our message box below since it blocks. + + this._rendererGone = true; + + ++this._rendererGoneCount; + + console.error(`OpenCOR: the renderer process is gone (${details.reason}, exit code ${details.exitCode}).`); + + // Let the user know if our configuration file had to be reset, as part of our message box below, so that they get + // told about it even if they choose to quit OpenCOR. + // Note: this must be done before closing our splash screen window since it would otherwise be reported in a message + // box of its own (see reportElectronConfReset()). + + const confResetMessage = electronConfResetMessage(); + + // Make sure that our splash screen window doesn't hide our message box and that we are visible. + + this.closeSplashScreenWindow(); + + if (!this.isVisible()) { + this.show(); + } + + const RELOAD = 0; + const RELOAD_WITHOUT_FILES = 1; + const QUIT = 2; + + const choice = electron.dialog.showMessageBoxSync(this, { + type: 'error', + title: 'OpenCOR', + message: 'OpenCOR has encountered a problem and needs to be reloaded.', + detail: `Reason: ${details.reason} (exit code ${details.exitCode}).\n\nIf the problem keeps happening, reload OpenCOR without reopening the files that were open. Those files won't be reopened either if you quit OpenCOR, but they will be available from File|Reopen.${confResetMessage ? `\n\n${confResetMessage.message} ${confResetMessage.detail}` : ''}`, + buttons: ['Reload', 'Reload Without Files', 'Quit'], + defaultId: RELOAD, + cancelId: QUIT }); + + // Discard the files that we would have reopened if the user wants to reload our renderer without them or to quit + // OpenCOR, or keep track of them so that they can be reopened once our renderer is ready again. + // Note #1: if the user wants to quit OpenCOR, then the files that we would have reopened would otherwise be saved + // and therefore reopened the next time OpenCOR is started, crashing our renderer again if one of them + // caused the crash. + // Note #2: if our renderer was ready, then we keep track of the files that were open (as well as those that we had + // yet to reopen, see filesToReopen()) and of the selected file, so that they can be reopened and selected + // once our renderer is ready again. If it wasn't ready, then it never told us about the files that are + // open, so we keep the files that we were going to reopen (i.e. those from a previous crash, if any, or + // those that were open when OpenCOR was last closed, see rendererReady()), as well as the arguments that + // we were asked to handle (e.g., our command line), which were never handled. Either way, the files to + // reopen are what gets saved should OpenCOR be quit before our renderer is ready again, so the next time + // OpenCOR is started, it won't reopen the files that might have caused the crash either. + + if (choice === RELOAD_WITHOUT_FILES || choice === QUIT) { + this.discardFilesToReopen(); + } else if (this._rendererReady) { + this._filesToReopen = this.filesToReopen(); + } + + // Forget about the files that our (crashed) renderer told us were open (and selected). + // Note #1: our reloaded renderer will tell us about the files that are open, but only once it has opened some, so + // we would otherwise save the files that were open at the time of the crash (see currentFilesToReopen()) + // should OpenCOR be quit before any file gets opened, i.e. reopen the files that might have caused the + // crash the next time OpenCOR is started. + // Note #2: similarly, our reloaded renderer has no files to start with, so our File|Close and File|Close All menu + // items must be disabled (they will get enabled again once a file gets (re)opened). + + filesOpened([]); + + enableDisableFileCloseAndCloseAllMenuItems(false); + + this._rendererReady = false; + this._openedFilePaths = []; + this._openedFilePathIndex = 0; + this._reopeningFilePath = null; + this._selectedFilePath = ''; + + // Quit OpenCOR or reload our renderer, making sure that our main menu is disabled until our reloaded renderer + // enables it (see the constructor) and that we reopen our files once our renderer is ready again (see + // rendererReady()). + // Note: we only quit OpenCOR now since our files to reopen get saved when we are closed (see the constructor), and + // we want the ones that we have just discarded to be saved, not the ones that our (crashed) renderer told us + // were open. + + enableDisableMainMenu(false); + + if (choice === QUIT) { + electron.app.quit(); + + return; + } + + this.webContents.reload(); + } + + // Discard the files that we would have reopened and the arguments that we were asked to handle since one of them + // might have caused our renderer to crash (see onRenderProcessGone()). + + private discardFilesToReopen(): void { + // Add the files that we would have reopened (as well as the files that we were asked to open, e.g., through our + // command line) to our recent files, so that the user can still reopen them, one by one, if they want to. + // Note #1: we add them in reverse order and the selected file last, so that the selected file ends up being the + // most recent one, followed by the other files in their original order (see fileClosed()). + // Note #2: unlike the files that we would have reopened, the files that we were asked to open were never given to + // our renderer, so their path may not be canonical (only libOpenCOR, i.e. our renderer, can canonicalise + // a path). So, we ask our reloaded renderer to canonicalise them once it is ready (see rendererReady()). + // We still add them straight away though, so that they don't get lost should OpenCOR be quit before + // then (or instead of reloading our renderer). + + const filesNotToReopen = this._rendererReady + ? this.filesToReopen() + : (this._filesToReopen ?? { + opened: electronConf.get('app.files.opened'), + selected: electronConf.get('app.files.selected') + }); + const filesNotToOpen = this._pendingArguments.filter((argument) => !isAction(argument)); + + for (const filePath of [...filesNotToReopen.opened, ...filesNotToOpen].reverse()) { + fileClosed(filePath); + } + + this._recentFilePathsToCanonicalise.push(...filesNotToOpen); + + if (filesNotToReopen.selected) { + fileClosed(filesNotToReopen.selected); + } + + // Discard the files that we were going to reopen and the arguments that we were asked to handle, so that neither our + // reloaded renderer nor OpenCOR, the next time it is started, reopens the files that might have caused the crash + // or handles the arguments that our (crashed) renderer never handled. + + this._filesToReopen = { opened: [], selected: '' }; + this._pendingArguments = []; + } + + // Our renderer is ready, so reopen previously opened files, if any, select the previously selected file, and handle + // any arguments that we were asked to handle until now (e.g., our command line). + // Note: our renderer may get reloaded (e.g., during development), in which case it will let us know again that it is + // ready, but there is nothing more for us to do then. + + rendererReady(): void { + if (this._rendererReady) { + return; + } + + this._rendererReady = true; + + // If our renderer has been reloaded following a crash, then we reopen the files that were open at the time rather + // than those that were open when OpenCOR was last closed. + + const filesToReopen = this._filesToReopen ?? { + opened: electronConf.get('app.files.opened'), + selected: electronConf.get('app.files.selected') + }; + + this._filesToReopen = null; + + this._openedFilePaths = filesToReopen.opened; + this._openedFilePathIndex = 0; + this._selectedFilePath = filesToReopen.selected; + + this.reopenFilePathsAndSelectFilePath(); + + this.handleArguments(this._pendingArguments.splice(0)); + + // Ask our renderer to canonicalise the recent file paths that we couldn't get from it (see onRenderProcessGone()). + + if (this._recentFilePathsToCanonicalise.length) { + this.send('canonicalise-recent-file-paths', this._recentFilePathsToCanonicalise.splice(0)); + } + + // Let our renderer know about the update that is being downloaded or installed, if any. + + resendUpdateState(); + } + + // Retrieve the files to reopen and the file to select (e.g., because we are being closed or because our renderer has + // crashed). + // Note: if we are still reopening files (see reopenFilePathsAndSelectFilePath()), then our renderer has only told us + // about the files that it has opened so far, so we must also account for the file that we are reopening and for + // those that we have yet to reopen. Similarly, our renderer selects each file that it opens, so the file that + // it has told us is selected is not necessarily the one that we have yet to select. + + private filesToReopen(): IFilesToReopen { + const res = currentFilesToReopen(); + + if (this._reopeningFilePath === null) { + return res; + } + + const opened = new Set(res.opened); + + for (const filePath of [this._reopeningFilePath, ...this._openedFilePaths.slice(this._openedFilePathIndex)]) { + if (filePath && !isDataUrlFileName(filePath)) { + opened.add(filePath); + } + } + + const openedFilePaths = [...opened]; + + return { + opened: openedFilePaths, + selected: + this._selectedFilePath && opened.has(this._selectedFilePath) + ? this._selectedFilePath + : res.selected || (openedFilePaths[0] ?? '') + }; + } + + // A file has been opened or not (e.g., because it couldn't be retrieved). + // Note: we only reopen the next file if the given file is the one that we are reopening. Indeed, other files may be + // opened while we are reopening files (e.g., files passed on the command line or files opened by the user) + // and they must not result in us reopening the next file before the file that we are reopening has been opened + // (or not) since we would otherwise end up reopening several files at once and selecting the previously + // selected file before it has been reopened. + + fileOpenedOrNot(filePath: string, opened: boolean): void { + // Make sure that we don't select a file that couldn't be (re)opened. + + if (!opened && filePath === this._selectedFilePath) { + this._selectedFilePath = ''; + } + + // Reopen the next file, if any, if the given file is the one that we are reopening. + + if (filePath === this._reopeningFilePath) { + this._reopeningFilePath = null; + + this.reopenFilePathsAndSelectFilePath(); + } } // Reopen previously opened files, if any, and select the previously selected file. // Note: we reopen one file at a time since a file may be a remote file which means that it may take some time to - // reopen. So, we need to wait for the file to be reopened before reopening the next one. + // reopen. So, we only reopen the next file once our renderer has told us that the previous file has been + // opened (or couldn't be opened), see fileOpenedOrNot(). reopenFilePathsAndSelectFilePath(): void { - if (this._openedFilePathIndex < this._openedFilePaths.length) { + // Reopen the next file, if any. + + while (this._openedFilePathIndex < this._openedFilePaths.length) { const filePath = this._openedFilePaths[this._openedFilePathIndex]; + ++this._openedFilePathIndex; + if (filePath) { - this.webContents.send('open', filePath); - } + this._reopeningFilePath = filePath; - ++this._openedFilePathIndex; + this.send('open', filePath); - if (this._openedFilePathIndex < this._openedFilePaths.length) { return; } - - this._openedFilePaths = []; - this._openedFilePathIndex = 0; } + this._openedFilePaths = []; + this._openedFilePathIndex = 0; + + // All the files have been reopened (or couldn't be reopened), so we can now select the previously selected file. + if (this._selectedFilePath) { - this.webContents.send('select', this._selectedFilePath); + this.send('select', this._selectedFilePath); this._selectedFilePath = ''; } } - // Handle our command line arguments. - - isAction(argument: string | undefined): boolean { - if (!argument) { - return false; - } - - return argument.startsWith(FULL_URI_SCHEME); - } + // Handle the given (normalised) command line arguments (see normaliseCommandLine()). handleArguments(commandLine: string[]): void { if (!commandLine.length) { return; } - for (let argument of commandLine) { - if (this.isAction(argument)) { - this.webContents.send('action', argument.slice(FULL_URI_SCHEME.length)); + // Wait for our renderer to be ready, if needed. - continue; - } + if (!this._rendererReady) { + this._pendingArguments.push(...commandLine); - if (argument === '--allow-file-access-from-files' || argument === '--enable-avfoundation') { - continue; - } - - // The argument is not an action (and not --allow-file-access-from-files or --enable-avfoundation either), so it - // must be a file to open. But, first, check whether the argument is a relative path and, if so, convert it to - // an absolute path. + return; + } - if (!path.isAbsolute(argument)) { - argument = path.resolve(argument); + for (const argument of commandLine) { + if (isAction(argument)) { + this.send('action', argument.slice(FULL_URI_SCHEME.length)); + } else { + this.send('open', argument); } - - this.webContents.send('open', argument); } } @@ -442,29 +1054,40 @@ export class MainWindow extends ApplicationWindow { enableDisableUi(enable: boolean): void { enableDisableMainMenu(enable); - this.webContents.send('enable-disable-ui', enable); + this.send('enable-disable-ui', enable); } // Handle our File|Open menu. + // Note: our renderer may get reloaded while our open dialog is shown (e.g., if it crashed and the user chose to + // reload it), in which case we must wait for it to be ready before asking it to open the selected files (see + // handleArguments()). Similarly, we must not enable our UI in that case since our reloaded renderer will enable + // it once it is ready (see onRenderProcessGone()). Otherwise, we must enable our UI, even if our renderer isn't + // ready, i.e. if its initialisation failed (in which case it never tells us that it is ready), or our UI would + // remain disabled. open(): void { this.enableDisableUi(false); + const rendererGoneCount = this._rendererGoneCount; + const enableUiIfRendererNotGone = (): void => { + if (this._rendererGoneCount === rendererGoneCount) { + this.enableDisableUi(true); + } + }; + electron.dialog .showOpenDialog({ properties: ['openFile', 'multiSelections'] }) .then(({ filePaths }) => { - for (const filePath of filePaths) { - this.webContents.send('open', filePath); - } + this.handleArguments(filePaths); - this.enableDisableUi(true); + enableUiIfRendererNotGone(); }) .catch((error: unknown) => { console.warn('OpenCOR: failed to open file(s):', formatError(error)); - this.enableDisableUi(true); + enableUiIfRendererNotGone(); }); } } diff --git a/src/main/SplashScreenWindow.ts b/src/main/SplashScreenWindow.ts index 468972a59..00b6e28ab 100644 --- a/src/main/SplashScreenWindow.ts +++ b/src/main/SplashScreenWindow.ts @@ -14,31 +14,34 @@ export class SplashScreenWindow extends ApplicationWindow { const height = 351 + 42; const state: IElectronConfState = electronConf.get('app.state'); - super({ - x: state.x + ((state.width - width) >> 1), - y: state.y + ((state.height - height) >> 1), - width: width, - height: height, - minWidth: width, - minHeight: height, - maxWidth: width, - maxHeight: height, - frame: false, - show: false, - alwaysOnTop: true - }); - - this.loadFile('./src/main/assets/splashscreen.html').catch((error: unknown) => { - console.warn('OpenCOR: failed to load splash screen:', formatError(error)); - }); - - // Initialise our Web contents. - - this.on('ready-to-show', () => { - this.webContents.send('init-splash-screen-window', { + super( + { + x: state.x + ((state.width - width) >> 1), + y: state.y + ((state.height - height) >> 1), + width: width, + height: height, + minWidth: width, + minHeight: height, + maxWidth: width, + maxHeight: height, + frame: false, + show: false, + alwaysOnTop: true + }, + false + ); + + // Load our splash screen, passing it our copyright and version. + // Note: we pass them using a query string rather than IPC since our splash screen window doesn't use our preload + // script (see ApplicationWindow). + + this.loadFile('./src/main/assets/splashscreen.html', { + query: { copyright: COPYRIGHT, version: electron.app.getVersion() - }); + } + }).catch((error: unknown) => { + console.warn('OpenCOR: failed to load the splash screen:', formatError(error)); }); } } diff --git a/src/main/assets/splashscreen.js b/src/main/assets/splashscreen.js index b7f0291fe..b54e928c3 100644 --- a/src/main/assets/splashscreen.js +++ b/src/main/assets/splashscreen.js @@ -1,4 +1,4 @@ -window.electronApi.onInitSplashScreenWindow((info) => { - document.getElementById('copyright').innerText = info.copyright; - document.getElementById('version').innerText = info.version; -}); +const searchParams = new URLSearchParams(window.location.search); + +document.getElementById('copyright').innerText = searchParams.get('copyright') ?? ''; +document.getElementById('version').innerText = searchParams.get('version') ?? ''; diff --git a/src/main/index.ts b/src/main/index.ts index 979aa07e4..99a2ea929 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -7,9 +7,9 @@ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; -import { formatError, type ISettings } from '../renderer/src/common/common'; +import { formatError, type ISettings, isObject } from '../renderer/src/common/common'; import { SHORT_DELAY, URI_SCHEME } from '../renderer/src/common/constants'; -import { isLinux, isPackaged, isWindows } from '../renderer/src/common/electron'; +import { appImagePath, isLinux, isPackaged, isWindows } from '../renderer/src/common/electron'; /* TODO: enable once our GitHub integration is fully ready. import { clearGitHubCache, @@ -32,6 +32,9 @@ import { installUpdateAndRestart, loadSettings, MainWindow, + normaliseCommandLine, + recentFilePathCanonicalised, + reportFatalErrorAndQuit, resetAll, saveSettings } from './MainWindow'; @@ -55,6 +58,7 @@ interface IElectronConf { recent: string[]; selected: string; }; + rendererServerPort: number; state: IElectronConfState; }; settings: ISettings; @@ -62,9 +66,114 @@ interface IElectronConf { export let electronConf: ElectronConf; +// A helper function to sanitise a (loaded) configuration value against its default value, i.e. to use the default +// value for whatever is missing or of the wrong type. +// Note #1: electron-conf only merges our default configuration at the top level, so a configuration file that was saved +// by an older version of OpenCOR or edited by hand may lack some (nested) values or have values of the wrong +// type (e.g., a string rather than an array of file paths), which would otherwise prevent OpenCOR from +// starting. +// Note #2: our only arrays are arrays of file paths, so we only keep their strings. +// Note #3: we keep the values for which we don't have a default value (e.g., values saved by a newer version of +// OpenCOR). + +const sanitisedConfValue = (value: unknown, defaultValue: unknown): unknown => { + if (Array.isArray(defaultValue)) { + return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [...defaultValue]; + } + + if (isObject(defaultValue)) { + const res: Record = isObject(value) ? { ...value } : {}; + + for (const [key, keyDefaultValue] of Object.entries(defaultValue)) { + res[key] = sanitisedConfValue(res[key], keyDefaultValue); + } + + return res; + } + + return typeof value === typeof defaultValue ? value : defaultValue; +}; + +// The backup of our configuration file, if it couldn't be loaded (see createElectronConf()). + +let electronConfBackupFileName: string | null = null; + +// Retrieve the backup of our configuration file, if any, so that the user can be told about it (see +// electronConfResetMessage() in MainWindow.ts). +// Note: we only return it once, so that the user is only told about it once. + +export const takeElectronConfBackupFileName = (): string | null => { + const res = electronConfBackupFileName; + + electronConfBackupFileName = null; + + return res; +}; + +// A helper function to create our Electron store. +// Note #1: electron-conf throws if our configuration file cannot be parsed (e.g., if it is empty or truncated because +// it couldn't be fully written, or if it was edited by hand), in which case OpenCOR would never be able to +// start again. So, we back it up (so that it can be inspected or recovered), use our default configuration +// instead, and let the user know about it once OpenCOR is shown (or as part of the error that gets reported, +// if any, see MainWindow.reportElectronConfReset()). If that fails too, then there is nothing more that we can +// do, so we let the error through. +// Note #2: electron-conf also throws if our configuration file cannot be read (e.g., because it is temporarily locked +// by an antivirus or a file synchronisation service on Windows, or because of its permissions), in which case +// our configuration file is most likely fine, so we must not reset it. Instead, we let the error through (so +// that OpenCOR reports it and quits), leaving our configuration file as is. + +const createElectronConf = (defaults: IElectronConf): ElectronConf => { + const options = { + dir: electron.app.getPath('userData'), + name: 'config', + defaults + }; + let res: ElectronConf; + + try { + res = new ElectronConf(options); + } catch (error: unknown) { + const fileName = path.join(options.dir, `${options.name}.json`); + + if (!(error instanceof SyntaxError) || !fs.existsSync(fileName)) { + throw error; + } + + const backupFileName = `${fileName}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`; + + fs.renameSync(fileName, backupFileName); + + console.error( + `OpenCOR: the configuration file (${fileName}) could not be loaded (${formatError(error)}), so it has been backed up (as ${backupFileName}) and the default configuration is used instead.` + ); + + res = new ElectronConf(options); + + electronConfBackupFileName = backupFileName; + } + + // Sanitise our configuration and save it, if needed. + + const store = res.store; + const sanitisedStore = sanitisedConfValue(store, defaults) as IElectronConf; + + if (JSON.stringify(sanitisedStore) !== JSON.stringify(store)) { + res.store = sanitisedStore; + } + + return res; +}; + // Allow only one instance of OpenCOR. +// Note #1: we pass our command line and working directory to the instance that is already running (see the +// second-instance event below). +// Note #2: app.quit() doesn't stop the rest of this module from being executed, so we must make sure that we don't do +// anything else if we are not the primary instance (e.g., register our URI scheme or create our splash screen +// window). + +const isPrimaryInstance = electron.app.requestSingleInstanceLock({ argv: process.argv, cwd: process.cwd() }); -if (!electron.app.requestSingleInstanceLock()) { +if (!isPrimaryInstance) { electron.app.quit(); } @@ -72,7 +181,16 @@ if (!electron.app.requestSingleInstanceLock()) { export let mainWindow: MainWindow | null = null; -electron.app.on('second-instance', (_event, argv) => { +// Note: another instance of OpenCOR may be started, or a file or an opencor:// link may be opened, before our main +// window has been created (i.e. while our splash screen is shown), in which case we keep track of the arguments +// so that they can be handled once our main window has been created. + +const pendingArguments: string[][] = []; + +// A helper function to handle the given (normalised) command line arguments, i.e. let our main window handle them (and +// bring it to the front) or, if it hasn't been created yet, keep track of them so that they can be handled once it has. + +const handleArguments = (commandLine: string[]): void => { if (mainWindow) { if (mainWindow.isMinimized()) { mainWindow.restore(); @@ -80,15 +198,50 @@ electron.app.on('second-instance', (_event, argv) => { mainWindow.focus(); - argv.shift(); // Remove the first argument, which is the path to OpenCOR. - - mainWindow.handleArguments(argv); + mainWindow.handleArguments(commandLine); + } else { + pendingArguments.push(commandLine); } +}; + +electron.app.on('second-instance', (_event, argv, workingDirectory, additionalData) => { + // Use the command line and working directory that the other instance passed to us, if available, since, according to + // Electron's documentation, argv may have been reordered and have additional switches. + + const otherInstanceData = additionalData as { argv?: string[]; cwd?: string } | undefined; + + handleArguments(normaliseCommandLine(otherInstanceData?.argv ?? argv, otherInstanceData?.cwd ?? workingDirectory)); +}); + +// Handle a file being opened with OpenCOR on macOS (e.g., by dropping it on OpenCOR's Dock icon or by using +// `open -a OpenCOR `). +// Note #1: on macOS, the file is not passed on the command line, not even when OpenCOR gets started by opening it, in +// which case this event is emitted before OpenCOR is ready, hence we register our handler straight away. +// Note #2: we must prevent the default behaviour to let macOS know that we handle the file. + +electron.app.on('open-file', (event, filePath) => { + event.preventDefault(); + + handleArguments([filePath]); +}); + +// Handle the clicking of an opencor:// link on macOS. +// Note #1: on macOS, the link is not passed on the command line, not even when OpenCOR gets started by clicking it, in +// which case this event is emitted before OpenCOR is ready, hence we register our handler straight away. Also, +// several links may be clicked in a row, hence we handle each of them (see handleArguments()). +// Note #2: we must prevent the default behaviour to let macOS know that we handle the link. + +electron.app.on('open-url', (event, url) => { + event.preventDefault(); + + handleArguments([url]); }); // Register our URI scheme. -electron.app.setAsDefaultProtocolClient(URI_SCHEME, isWindows() ? process.execPath : undefined); +if (isPrimaryInstance) { + electron.app.setAsDefaultProtocolClient(URI_SCHEME, isWindows() ? process.execPath : undefined); +} // Set up Linux desktop integration by creating a desktop file and making our application icon available. // Note: this is not needed on Windows and macOS since they automatically pick up the necessary information from @@ -112,11 +265,24 @@ const setupLinuxDesktopIntegration = async (): Promise => { await fs.promises.mkdir(localApplicationsFolder, { recursive: true }); + // Note #1: when running as an AppImage, process.execPath is the path of OpenCOR within the AppImage's temporary + // mount point (e.g., /tmp/.mount_OpenCOxxxxxx/OpenCOR), which disappears once OpenCOR exits, so we must + // use the path of the AppImage itself (see appImagePath()). + // Note #2: the path of OpenCOR may contain spaces or other special characters, so we quote it as required by the + // Desktop Entry specification (see + // https://specifications.freedesktop.org/desktop-entry-spec/latest/exec-variables.html), i.e. we escape + // '"', '`', '$', and '\' with a backslash, double that backslash (since the general escape rule for string + // values is applied before the quoting rule), and double `%` (since it introduces a field code). + + const executablePath = appImagePath() ?? process.execPath; + const quotedExecutablePath = `"${executablePath + .replace(/["`$\\]/g, (character) => (character === '\\' ? '\\\\\\\\' : `\\\\${character}`)) + .replace(/%/g, '%%')}"`; const desktopFilePath = path.join(localApplicationsFolder, `${URI_SCHEME}.desktop`); const desktopFileContents = `[Desktop Entry] Type=Application Name=OpenCOR -Exec=${process.execPath} %u +Exec=${quotedExecutablePath} %u Icon=${iconTargetPath} Terminal=false MimeType=x-scheme-handler/${URI_SCHEME}`; @@ -153,21 +319,17 @@ MimeType=x-scheme-handler/${URI_SCHEME}`; } }; -// Handle the clicking of an opencor:// link. - -let triggeringUrl: string | null = null; - -electron.app.on('open-url', (_event, url) => { - triggeringUrl = url; - - mainWindow?.handleArguments([url]); -}); - // The app is ready, so finalise its initialisation. electron.app .whenReady() .then(() => { + // Nothing to do if we are not the primary instance (see above). + + if (!isPrimaryInstance) { + return; + } + // Set up Linux desktop integration. if (isLinux()) { @@ -187,36 +349,81 @@ electron.app const workAreaSize = electron.screen.getPrimaryDisplay().workAreaSize; const horizontalSpace = Math.round(workAreaSize.width / 13); const verticalSpace = Math.round(workAreaSize.height / 13); + const defaultState: IElectronConfState = { + x: horizontalSpace, + y: verticalSpace, + width: workAreaSize.width - 2 * horizontalSpace, + height: workAreaSize.height - 2 * verticalSpace, + isMaximized: false, + isFullScreen: false + }; - electronConf = new ElectronConf({ - defaults: { - app: { - files: { - opened: [], - recent: [], - selected: '' - }, - state: { - x: horizontalSpace, - y: verticalSpace, - width: workAreaSize.width - 2 * horizontalSpace, - height: workAreaSize.height - 2 * verticalSpace, - isMaximized: false, - isFullScreen: false - } + electronConf = createElectronConf({ + app: { + files: { + opened: [], + recent: [], + selected: '' }, - settings: { - general: { - checkForUpdatesAtStartup: true - } + rendererServerPort: 0, + state: defaultState + }, + settings: { + general: { + checkForUpdatesAtStartup: true } } }); + // Make sure that our saved window state is (still) visible on one of our displays. Indeed, a display may have been + // disconnected (or its resolution changed) since OpenCOR was last closed, in which case our main window (and our + // splash screen window, which is centred on it) would be shown off-screen. So, if our saved window state is not + // sufficiently visible on any of our displays, we reset its position and size (but keep whether it was maximised or + // in full-screen mode). + + const MIN_VISIBLE_SIZE = 100; + const state: IElectronConfState = electronConf.get('app.state'); + const stateIsVisible = electron.screen.getAllDisplays().some(({ workArea }) => { + const visibleWidth = Math.min(state.x + state.width, workArea.x + workArea.width) - Math.max(state.x, workArea.x); + const visibleHeight = + Math.min(state.y + state.height, workArea.y + workArea.height) - Math.max(state.y, workArea.y); + + return visibleWidth >= MIN_VISIBLE_SIZE && visibleHeight >= MIN_VISIBLE_SIZE; + }); + + if (!stateIsVisible) { + electronConf.set('app.state', { + ...defaultState, + isMaximized: state.isMaximized, + isFullScreen: state.isFullScreen + }); + } + // Create our splash window. const splashScreenWindow = new SplashScreenWindow(); + // Start our renderer server straight away (rather than once our splash screen window has been shown), so that it is + // ready by the time we create our main window. + // Note: any error is handled when creating our main window, but we need to handle it here too, so that it isn't + // reported as an unhandled rejection in the meantime. + + // Note: we want our renderer server to always use the same port (if possible), so that the origin of our renderer + // remains the same from one launch to another. This means that our renderer's HTTP cache can be used across + // launches and that our renderer's origin-scoped storage doesn't get orphaned (and accumulate) on each + // launch. So, we ask our renderer server to try the port it used last time and keep track of the port it ends + // up using. + + const rendererUrl = process.env.ELECTRON_RENDERER_URL + ? Promise.resolve(process.env.ELECTRON_RENDERER_URL) + : startRendererServer(electronConf.get('app.rendererServerPort')).then((url: string) => { + electronConf.set('app.rendererServerPort', Number(new URL(url).port)); + + return url; + }); + + rendererUrl.catch(() => {}); + // Set our app user model id for Windows. electronToolkitUtils.electronApp.setAppUserModelId('ws.opencor.app'); @@ -264,11 +471,11 @@ electron.app electron.ipcMain.handle('file-closed', (_event, filePath: string) => { fileClosed(filePath); }); - electron.ipcMain.handle('file-issue', (_event, filePath: string) => { - fileIssue(filePath); + electron.ipcMain.handle('file-issue', (_event, filePath: string, canonicalFilePath: string) => { + fileIssue(filePath, canonicalFilePath); }); - electron.ipcMain.handle('file-opened', (_event, filePath: string) => { - fileOpened(filePath); + electron.ipcMain.handle('file-opened', (_event, filePath: string, canonicalFilePath: string) => { + fileOpened(filePath, canonicalFilePath); }); electron.ipcMain.handle('file-selected', (_event, filePath: string) => { fileSelected(filePath); @@ -287,6 +494,15 @@ electron.app electron.ipcMain.handle('load-settings', (): ISettings => { return loadSettings(); }); + electron.ipcMain.handle( + 'recent-file-path-canonicalised', + (_event, filePath: string, canonicalFilePath: string) => { + recentFilePathCanonicalised(filePath, canonicalFilePath); + } + ); + electron.ipcMain.handle('renderer-ready', () => { + MainWindow.instance?.rendererReady(); + }); electron.ipcMain.handle('reset-all', resetAll); /* TODO: enable once our GitHub integration is fully ready. electron.ipcMain.handle('save-github-access-token', async (_event, token: string): Promise => { @@ -297,19 +513,36 @@ electron.app saveSettings(settings); }); - // Create our main window and pass to it our command line arguments or, if we got started via a URI scheme, the - // triggering URL. + // Create our main window and pass to it our command line arguments. + // Note: if that fails (e.g., our renderer server couldn't be started), then OpenCOR cannot be used, so we + // report the error and quit (rather than leave our splash screen window shown forever). - mainWindow = new MainWindow( - triggeringUrl ? [triggeringUrl] : process.argv, - splashScreenWindow, - process.env.ELECTRON_RENDERER_URL ?? (await startRendererServer()) - ); + try { + mainWindow = new MainWindow(process.argv, splashScreenWindow, await rendererUrl); + + // Forget about our main window once it has been closed (and therefore destroyed), so that we don't try to + // use it (e.g., from our main menu or when another instance of OpenCOR is started). + + mainWindow.on('closed', () => { + mainWindow = null; + }); + } catch (error: unknown) { + reportFatalErrorAndQuit(`OpenCOR could not be started (${formatError(error)}).`, splashScreenWindow); + + return; + } + + // Handle the arguments of any instance of OpenCOR that was started, or any file or opencor:// link that was + // opened while our splash screen was being shown. + + for (const argv of pendingArguments.splice(0)) { + mainWindow.handleArguments(argv); + } }, SHORT_DELAY); }); }) .catch((error: unknown) => { - console.error('OpenCOR: failed to create the main window:', formatError(error)); + reportFatalErrorAndQuit(`OpenCOR could not be started (${formatError(error)}).`); }); // Ensure that the renderer server is stopped when quitting. diff --git a/src/preload/index.ts b/src/preload/index.ts index a0e574ff4..fcd6832f0 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,46 +1,44 @@ import electron from 'electron'; -import { exec as _exec } from 'node:child_process'; import { promises as fs } from 'node:fs'; -import { promisify } from 'node:util'; import loc from '../../dist/libOpenCOR/Release/libOpenCOR.node'; import type { ISettings } from '../renderer/src/common/common'; -import type { ISplashScreenInfo } from '../renderer/src/common/electronApi'; -const exec = promisify(_exec); +// A helper function to listen to a channel from the main process. +// Note: it returns a function to stop listening to that channel so that a listener can be removed (e.g., when the +// component that registered it gets unmounted). Otherwise, a listener would remain registered (and keep its +// callback, and therefore its component, alive) for the lifetime of the renderer process. + +const onIpc = (channel: string, callback: (...args: T) => void): (() => void) => { + const listener = (_event: electron.IpcRendererEvent, ...args: T): void => { + callback(...args); + }; + + electron.ipcRenderer.on(channel, listener); + + return () => { + electron.ipcRenderer.removeListener(channel, listener); + }; +}; let _operatingSystem: string | null = null; const defaultOperatingSystem = `${process.platform} (${process.arch === 'x64' ? 'Intel' : 'ARM'})`; const retrieveOperatingSystem = async (): Promise => { - const safeExec = async (cmd: string): Promise => { - try { - const { stdout } = await exec(cmd); - - return stdout?.toString().trim() || null; - } catch { - return null; - } - }; + // Note: we don't spawn any process (e.g., sw_vers on macOS or wmic on Windows, the latter not being available on + // recent versions of Windows 11) to retrieve the operating system since it would slow down our startup. + // Instead, we use Electron's process.getSystemVersion() on macOS and Windows, and read /etc/os-release on + // Linux. let operatingSystem: string = ''; if (process.platform === 'win32') { - const res = await safeExec('wmic os get Caption'); - - if (res) { - const lines = res - .split(/\r?\n/) - .map((s) => s.trim()) - .filter(Boolean); + // Note: Windows 11 still reports itself as Windows 10.0, but with a build number of 22000 or above. - if (lines[1]) { - operatingSystem = lines[1].replace(/^Microsoft\s+/, ''); - } - } + const [major, , build] = process.getSystemVersion().split('.').map(Number); - operatingSystem = operatingSystem || 'Windows'; + operatingSystem = major === 10 && (build ?? 0) >= 22000 ? 'Windows 11' : major === 10 ? 'Windows 10' : 'Windows'; } else if (process.platform === 'linux') { let res: string | null = null; @@ -54,22 +52,9 @@ const retrieveOperatingSystem = async (): Promise => { operatingSystem = (match[1] || match[2] || '').replace(/"/g, '').trim(); } - if (!operatingSystem) { - res = await safeExec('lsb_release -ds'); - - operatingSystem = res?.replace(/^"|"$/g, '') || ''; - } - operatingSystem = operatingSystem || 'Linux'; } else if (process.platform === 'darwin') { - const nameRes = await safeExec('sw_vers -productName'); - const versionRes = await safeExec('sw_vers -productVersion'); - - if (nameRes || versionRes) { - operatingSystem = `${nameRes || 'macOS'} ${versionRes || ''}`.trim(); - } - - operatingSystem = operatingSystem || 'macOS'; + operatingSystem = `macOS ${process.getSystemVersion()}`.trim(); } return operatingSystem ? `${operatingSystem} (${process.arch === 'x64' ? 'Intel' : 'ARM'})` : defaultOperatingSystem; @@ -97,13 +82,6 @@ electron.contextBridge.exposeInMainWorld('electronApi', { return _operatingSystem || defaultOperatingSystem; }, - // Splash screen window. - - onInitSplashScreenWindow: (callback: (info: ISplashScreenInfo) => void) => - electron.ipcRenderer.on('init-splash-screen-window', (_event, info: ISplashScreenInfo) => { - callback(info); - }), - // Renderer process asking the main process to do something for it. checkForUpdates: (atStartup: boolean) => electron.ipcRenderer.invoke('check-for-updates', atStartup), @@ -114,14 +92,19 @@ electron.contextBridge.exposeInMainWorld('electronApi', { enableDisableFileCloseAndCloseAllMenuItems: (enable: boolean) => electron.ipcRenderer.invoke('enable-disable-file-close-and-close-all-menu-items', enable), fileClosed: (filePath: string) => electron.ipcRenderer.invoke('file-closed', filePath), - fileIssue: (filePath: string) => electron.ipcRenderer.invoke('file-issue', filePath), - fileOpened: (filePath: string) => electron.ipcRenderer.invoke('file-opened', filePath), + fileIssue: (filePath: string, canonicalFilePath: string) => + electron.ipcRenderer.invoke('file-issue', filePath, canonicalFilePath), + fileOpened: (filePath: string, canonicalFilePath: string) => + electron.ipcRenderer.invoke('file-opened', filePath, canonicalFilePath), filePath: (file: File) => electron.webUtils.getPathForFile(file), fileSelected: (filePath: string) => electron.ipcRenderer.invoke('file-selected', filePath), filesOpened: (filePaths: string[]) => electron.ipcRenderer.invoke('files-opened', filePaths), installUpdateAndRestart: () => electron.ipcRenderer.invoke('install-update-and-restart'), loadGitHubAccessToken: (): Promise => electron.ipcRenderer.invoke('load-github-access-token'), loadSettings: (): Promise => electron.ipcRenderer.invoke('load-settings'), + recentFilePathCanonicalised: (filePath: string, canonicalFilePath: string) => + electron.ipcRenderer.invoke('recent-file-path-canonicalised', filePath, canonicalFilePath), + rendererReady: () => electron.ipcRenderer.invoke('renderer-ready'), resetAll: () => electron.ipcRenderer.invoke('reset-all'), saveGitHubAccessToken: (token: string): Promise => electron.ipcRenderer.invoke('save-github-access-token', token), @@ -129,78 +112,27 @@ electron.contextBridge.exposeInMainWorld('electronApi', { // Renderer process listening to the main process. - onAbout: (callback: () => void) => - electron.ipcRenderer.on('about', () => { - callback(); - }), - onAction: (callback: (action: string) => void) => - electron.ipcRenderer.on('action', (_event, action: string) => { - callback(action); - }), - onCheckForUpdates: (callback: () => void) => - electron.ipcRenderer.on('check-for-updates', () => { - callback(); - }), - onEnableDisableUi: (callback: (enable: boolean) => void) => - electron.ipcRenderer.on('enable-disable-ui', (_event, enable: boolean) => { - callback(enable); - }), - onOpen: (callback: (filePath: string) => void) => - electron.ipcRenderer.on('open', (_event, filePath: string) => { - callback(filePath); - }), - onOpenRemote: (callback: () => void) => - electron.ipcRenderer.on('open-remote', () => { - callback(); - }), - onOpenSampleLorenz: (callback: () => void) => - electron.ipcRenderer.on('open-sample-lorenz', () => { - callback(); - }), - onClose: (callback: () => void) => - electron.ipcRenderer.on('close', () => { - callback(); - }), - onCloseAll: (callback: () => void) => - electron.ipcRenderer.on('close-all', () => { - callback(); - }), - onResetAll: (callback: () => void) => - electron.ipcRenderer.on('reset-all', () => { - callback(); - }), - onSelect: (callback: (filePath: string) => void) => - electron.ipcRenderer.on('select', (_event, filePath: string) => { - callback(filePath); - }), - onSettings: (callback: () => void) => - electron.ipcRenderer.on('settings', () => { - callback(); - }), - onUpdateAvailable: (callback: (version: string) => void) => - electron.ipcRenderer.on('update-available', (_event, version: string) => { - callback(version); - }), - onUpdateCheckError: (callback: (issue: string) => void) => - electron.ipcRenderer.on('update-check-error', (_event, issue: string) => { - callback(issue); - }), - onUpdateDownloaded: (callback: () => void) => - electron.ipcRenderer.on('update-downloaded', () => { - callback(); - }), - onUpdateDownloadError: (callback: (issue: string) => void) => - electron.ipcRenderer.on('update-download-error', (_event, issue: string) => { - callback(issue); - }), - onUpdateDownloadProgress: (callback: (percent: number) => void) => - electron.ipcRenderer.on('update-download-progress', (_event, percent: number) => { - callback(percent); - }), - onUpdateNotAvailable: (callback: () => void) => - electron.ipcRenderer.on('update-not-available', () => { - callback(); - }) + onAbout: (callback: () => void) => onIpc('about', callback), + onAction: (callback: (action: string) => void) => onIpc('action', callback), + onCanonicaliseRecentFilePaths: (callback: (filePaths: string[]) => void) => + onIpc('canonicalise-recent-file-paths', callback), + onCheckForUpdates: (callback: () => void) => onIpc('check-for-updates', callback), + onEnableDisableUi: (callback: (enable: boolean) => void) => onIpc('enable-disable-ui', callback), + onOpen: (callback: (filePath: string) => void) => onIpc('open', callback), + onOpenRemote: (callback: () => void) => onIpc('open-remote', callback), + onOpenSampleLorenz: (callback: () => void) => onIpc('open-sample-lorenz', callback), + onClose: (callback: () => void) => onIpc('close', callback), + onCloseAll: (callback: () => void) => onIpc('close-all', callback), + onResetAll: (callback: () => void) => onIpc('reset-all', callback), + onSelect: (callback: (filePath: string) => void) => onIpc('select', callback), + onSettings: (callback: () => void) => onIpc('settings', callback), + onUpdateAvailable: (callback: (version: string) => void) => onIpc('update-available', callback), + onUpdateCheckError: (callback: (issue: string) => void) => onIpc('update-check-error', callback), + onUpdateDownloaded: (callback: () => void) => onIpc('update-downloaded', callback), + onUpdateDownloadError: (callback: (issue: string) => void) => onIpc('update-download-error', callback), + onUpdateInstallError: (callback: (issue: string) => void) => onIpc('update-install-error', callback), + onUpdateDownloadProgress: (callback: (percent: number) => void) => onIpc('update-download-progress', callback), + onUpdateNotAvailable: (callback: () => void) => onIpc('update-not-available', callback) }); // Give our renderer process access to the native node module for libOpenCOR. @@ -213,6 +145,8 @@ electron.contextBridge.exposeInMainWorld('locApi', { // FileManager API. + fileManagerCanonicalPath: (path: string) => loc.fileManagerCanonicalPath(path), + fileManagerHasFile: (path: string) => loc.fileManagerHasFile(path), fileManagerUnmanage: (path: string) => loc.fileManagerUnmanage(path), // File API. @@ -263,6 +197,7 @@ electron.contextBridge.exposeInMainWorld('locApi', { // SolverCvode API. // TODO: this is only temporary until we have full support for our different solvers. + solverCvodeExists: (documentId: number, index: number) => loc.solverCvodeExists(documentId, index), solverCvodeMaximumStep: (documentId: number, index: number) => loc.solverCvodeMaximumStep(documentId, index), solverCvodeSetMaximumStep: (documentId: number, index: number, value: number) => loc.solverCvodeSetMaximumStep(documentId, index, value), @@ -284,43 +219,54 @@ electron.contextBridge.exposeInMainWorld('locApi', { sedInstanceTaskVoiName: (instanceId: number, index: number) => loc.sedInstanceTaskVoiName(instanceId, index), sedInstanceTaskVoiUnit: (instanceId: number, index: number) => loc.sedInstanceTaskVoiUnit(instanceId, index), - sedInstanceTaskVoi: (instanceId: number, index: number) => loc.sedInstanceTaskVoi(instanceId, index), + sedInstanceTaskVoi: (instanceId: number, index: number, start?: number, end?: number) => + loc.sedInstanceTaskVoi(instanceId, index, start, end), sedInstanceTaskStateCount: (instanceId: number, index: number) => loc.sedInstanceTaskStateCount(instanceId, index), sedInstanceTaskStateName: (instanceId: number, index: number, stateIndex: number) => loc.sedInstanceTaskStateName(instanceId, index, stateIndex), sedInstanceTaskStateUnit: (instanceId: number, index: number, stateIndex: number) => loc.sedInstanceTaskStateUnit(instanceId, index, stateIndex), - sedInstanceTaskState: (instanceId: number, index: number, stateIndex: number) => - loc.sedInstanceTaskState(instanceId, index, stateIndex), + sedInstanceTaskState: (instanceId: number, index: number, stateIndex: number, start?: number, end?: number) => + loc.sedInstanceTaskState(instanceId, index, stateIndex, start, end), sedInstanceTaskRateCount: (instanceId: number, index: number) => loc.sedInstanceTaskRateCount(instanceId, index), sedInstanceTaskRateName: (instanceId: number, index: number, rateIndex: number) => loc.sedInstanceTaskRateName(instanceId, index, rateIndex), sedInstanceTaskRateUnit: (instanceId: number, index: number, rateIndex: number) => loc.sedInstanceTaskRateUnit(instanceId, index, rateIndex), - sedInstanceTaskRate: (instanceId: number, index: number, rateIndex: number) => - loc.sedInstanceTaskRate(instanceId, index, rateIndex), + sedInstanceTaskRate: (instanceId: number, index: number, rateIndex: number, start?: number, end?: number) => + loc.sedInstanceTaskRate(instanceId, index, rateIndex, start, end), sedInstanceTaskConstantCount: (instanceId: number, index: number) => loc.sedInstanceTaskConstantCount(instanceId, index), sedInstanceTaskConstantName: (instanceId: number, index: number, constantIndex: number) => loc.sedInstanceTaskConstantName(instanceId, index, constantIndex), sedInstanceTaskConstantUnit: (instanceId: number, index: number, constantIndex: number) => loc.sedInstanceTaskConstantUnit(instanceId, index, constantIndex), - sedInstanceTaskConstant: (instanceId: number, index: number, constantIndex: number) => - loc.sedInstanceTaskConstant(instanceId, index, constantIndex), + sedInstanceTaskConstant: (instanceId: number, index: number, constantIndex: number, start?: number, end?: number) => + loc.sedInstanceTaskConstant(instanceId, index, constantIndex, start, end), sedInstanceTaskComputedConstantCount: (instanceId: number, index: number) => loc.sedInstanceTaskComputedConstantCount(instanceId, index), sedInstanceTaskComputedConstantName: (instanceId: number, index: number, computedConstantIndex: number) => loc.sedInstanceTaskComputedConstantName(instanceId, index, computedConstantIndex), sedInstanceTaskComputedConstantUnit: (instanceId: number, index: number, computedConstantIndex: number) => loc.sedInstanceTaskComputedConstantUnit(instanceId, index, computedConstantIndex), - sedInstanceTaskComputedConstant: (instanceId: number, index: number, computedConstantIndex: number) => - loc.sedInstanceTaskComputedConstant(instanceId, index, computedConstantIndex), + sedInstanceTaskComputedConstant: ( + instanceId: number, + index: number, + computedConstantIndex: number, + start?: number, + end?: number + ) => loc.sedInstanceTaskComputedConstant(instanceId, index, computedConstantIndex, start, end), sedInstanceTaskAlgebraicVariableCount: (instanceId: number, index: number) => loc.sedInstanceTaskAlgebraicVariableCount(instanceId, index), sedInstanceTaskAlgebraicVariableName: (instanceId: number, index: number, algebraicVariableIndex: number) => loc.sedInstanceTaskAlgebraicVariableName(instanceId, index, algebraicVariableIndex), sedInstanceTaskAlgebraicVariableUnit: (instanceId: number, index: number, algebraicVariableIndex: number) => loc.sedInstanceTaskAlgebraicVariableUnit(instanceId, index, algebraicVariableIndex), - sedInstanceTaskAlgebraicVariable: (instanceId: number, index: number, algebraicVariableIndex: number) => - loc.sedInstanceTaskAlgebraicVariable(instanceId, index, algebraicVariableIndex) + sedInstanceTaskAlgebraicVariable: ( + instanceId: number, + index: number, + algebraicVariableIndex: number, + start?: number, + end?: number + ) => loc.sedInstanceTaskAlgebraicVariable(instanceId, index, algebraicVariableIndex, start, end) }); diff --git a/src/renderer/README.md b/src/renderer/README.md index b9b72a211..a0e668891 100644 --- a/src/renderer/README.md +++ b/src/renderer/README.md @@ -84,6 +84,21 @@ Header set Cross-Origin-Resource-Policy "cross-origin" In addition, you will need to update the `locateFile` callback in `src/renderer/src/common/initialisation.ts` to point to your own URL. +### Embedding the `@opencor/opencor` package + +The `@opencor/opencor` package does **not** ship `libopencor.js`. At runtime, OpenCOR first tries to import it from your application, using the following URL (relative to your page's base URL): + +``` +libopencor/downloads/wasm//libopencor.js +``` + +where `` is the value of the `libopencorVersion` field in the package's `package.json` file. If your application doesn't serve it, then OpenCOR fetches it from `https://opencor.ws/libopencor/downloads/wasm//libopencor.js` and imports it from a [blob URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static), so that its worker is same-origin. If your application uses a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP), then this requires: + +- `connect-src` to allow `https://opencor.ws` (which is needed for the threaded WASM file anyway); and +- `script-src` and `worker-src` to allow `blob:`. + +If you would rather not allow `blob:` (or would rather not fetch `libopencor.js` from `https://opencor.ws`), then you can self-host `libopencor.js` by downloading it from `https://opencor.ws/libopencor/downloads/wasm//libopencor.js` and serving it, as a JavaScript file, from the above URL. Make sure to do this again whenever you update the `@opencor/opencor` package, since `` will then change. + ## Usage OpenCOR accepts the following props: diff --git a/src/renderer/bun.lock b/src/renderer/bun.lock index b45f9997c..792491811 100644 --- a/src/renderer/bun.lock +++ b/src/renderer/bun.lock @@ -5,8 +5,8 @@ "": { "name": "@opencor/opencor", "dependencies": { - "@napi-rs/keyring": "^2.1.0", - "@opencor/libopencor-types": "^1.20261007.0", + "@napi-rs/keyring": "^2.1.1", + "@opencor/libopencor-types": "^1.20261016.0", "@primeuix/themes": "2.0.3", "@primevue/auto-import-resolver": "4.2.5", "@vueuse/core": "12.8.2", @@ -19,13 +19,13 @@ "@biomejs/biome": "^2.5.15", "@tailwindcss/postcss": "^4.3.3", "@tailwindcss/vite": "^4.3.3", - "@types/node": "^26.6.4", - "@types/plotly.js": "^3.0.14", - "@vitejs/plugin-vue": "^6.0.9", + "@types/bun": "^1.4.3", + "@types/node": "^26.6.5", + "@types/plotly.js": "^3.0.15", + "@vitejs/plugin-vue": "^6.0.10", "@vue/tsconfig": "^0.9.1", "autoprefixer": "^10.6.1", - "esbuild": "^0.28.2", - "postcss": "^8.5.29", + "postcss": "^8.5.30", "rollup-plugin-visualizer": "^7.1.1", "stylelint": "^17.16.0", "stylelint-config-standard": "^40.0.0", @@ -34,8 +34,8 @@ "typescript": "^7.0.2", "typescript-6": "npm:typescript@^6.0.3", "unplugin-vue-components": "^32.1.0", - "vite": "7.3.6", - "vue-tsc": "^3.3.12", + "vite": "^8.3.4", + "vue-tsc": "^3.3.13", }, "peerDependencies": { "vue": "3.4.21", @@ -91,58 +91,6 @@ "@csstools/selector-specificity": ["@csstools/selector-specificity@6.0.1", "", { "peerDependencies": { "postcss-selector-parser": "^7.1.1" } }, "sha512-MBuqFIIvqk4S593MGAskTkPpEUUzZNvGzh8mMxEwl3oHwjQlXmqHEDxNbN3Y0Tsh+xjmem4GZjeg7I0qkwBivg=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="], - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -157,33 +105,31 @@ "@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="], - "@napi-rs/keyring": ["@napi-rs/keyring@2.1.0", "", { "optionalDependencies": { "@napi-rs/keyring-darwin-arm64": "2.1.0", "@napi-rs/keyring-darwin-x64": "2.1.0", "@napi-rs/keyring-freebsd-x64": "2.1.0", "@napi-rs/keyring-linux-arm-gnueabihf": "2.1.0", "@napi-rs/keyring-linux-arm64-gnu": "2.1.0", "@napi-rs/keyring-linux-arm64-musl": "2.1.0", "@napi-rs/keyring-linux-riscv64-gnu": "2.1.0", "@napi-rs/keyring-linux-x64-gnu": "2.1.0", "@napi-rs/keyring-linux-x64-musl": "2.1.0", "@napi-rs/keyring-win32-arm64-msvc": "2.1.0", "@napi-rs/keyring-win32-ia32-msvc": "2.1.0", "@napi-rs/keyring-win32-x64-msvc": "2.1.0" } }, "sha512-km9J3fomkGSLpImpelq0cMvLBrJ5eVlTzuWdG3Iy0laP6gU90MmHYjqhUhG32aYq0/0++r2TLuAA9xlnh4XU1g=="], - - "@napi-rs/keyring-darwin-arm64": ["@napi-rs/keyring-darwin-arm64@2.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-j6ATTOhmPW+N5EKwi8Yzn6P4Bz/uqnxzyYvqOL73/dgzDSYG5pS5eprqpVmOLse68WUTf79ZMqz97HnfvirJ9g=="], + "@napi-rs/keyring": ["@napi-rs/keyring@2.1.1", "", { "optionalDependencies": { "@napi-rs/keyring-darwin-arm64": "2.1.1", "@napi-rs/keyring-darwin-x64": "2.1.1", "@napi-rs/keyring-freebsd-x64": "2.1.1", "@napi-rs/keyring-linux-arm-gnueabihf": "2.1.1", "@napi-rs/keyring-linux-arm64-gnu": "2.1.1", "@napi-rs/keyring-linux-arm64-musl": "2.1.1", "@napi-rs/keyring-linux-riscv64-gnu": "2.1.1", "@napi-rs/keyring-linux-x64-gnu": "2.1.1", "@napi-rs/keyring-linux-x64-musl": "2.1.1", "@napi-rs/keyring-win32-arm64-msvc": "2.1.1", "@napi-rs/keyring-win32-ia32-msvc": "2.1.1", "@napi-rs/keyring-win32-x64-msvc": "2.1.1" } }, "sha512-7HUq8FHgW/O4s8ESvkxngTC9tOR/IyktdR0ral2VD0C8HDZk86FwPAH4qZyerKwS1DhJ6AhoebIsxcyw+Lbt1Q=="], - "@napi-rs/keyring-darwin-x64": ["@napi-rs/keyring-darwin-x64@2.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-54Q803nguiOaQzBG2h4uIkacI0eLGAlzzdpxPNi6IZtXbjqbPI4AQ86GrQy25tw3czBqRPBoGu0lHP2U+/f5iA=="], + "@napi-rs/keyring-darwin-arm64": ["@napi-rs/keyring-darwin-arm64@2.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-H6uxAztj6GSDwnh/1JDHCtAqWvBTr+0+FGwOOw+rY568z7vFOqrJTTboePFuAnJSO7kNvq+yuyXJ9B5Ueco4yA=="], - "@napi-rs/keyring-freebsd-x64": ["@napi-rs/keyring-freebsd-x64@2.1.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BL7ngJAYQBrUp1PTF8enOXOIVfxN51RbwVOEQ+8eAjWypF/5Ipqs8oQoMqkuc0wAbk+vatnNVjdKCIFa/HPglg=="], + "@napi-rs/keyring-darwin-x64": ["@napi-rs/keyring-darwin-x64@2.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-RcHBNnOIzqliNU8Z0jc71bPs/PRn4IIO02XE6DV3HxVFAHo4CooNXDySCL+jbHhObLlSy9yynRn+wq5k+IAmRw=="], - "@napi-rs/keyring-linux-arm-gnueabihf": ["@napi-rs/keyring-linux-arm-gnueabihf@2.1.0", "", { "os": "linux", "cpu": "arm" }, "sha512-JYgbCDk+giss7lKla8NTXhM/fLj+Hrfgr3xwKQ8jKn8qMD6vXLwYcfK8HJ9VoAcjIeYEUpCL6dRszJC5M4zFNg=="], + "@napi-rs/keyring-freebsd-x64": ["@napi-rs/keyring-freebsd-x64@2.1.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IrM8IKBZKQzETI1BlvSpT6lRzC9eTIQ5TgUwcbQSiQTuXTEXAAHHvBYkoHicN2b2uqgAfO3dwN382CGRw0Ownw=="], - "@napi-rs/keyring-linux-arm64-gnu": ["@napi-rs/keyring-linux-arm64-gnu@2.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-+PMONFQ+2GkBXG7vRG2spgR73bNCMeVpNopHoSmhj2E+5Gu6zJooRBjoaIZKj6hCyrOU8pXAhoZ22EEFQ/4YeQ=="], + "@napi-rs/keyring-linux-arm-gnueabihf": ["@napi-rs/keyring-linux-arm-gnueabihf@2.1.1", "", { "os": "linux", "cpu": "arm" }, "sha512-KMld1c9TRucQYV/qasA5jS/VZWCpxMOeHflrciOAsfhiVQ8VfA77grlhGD0kiWtz5e+2M6kYLH9iFmQhPROkGw=="], - "@napi-rs/keyring-linux-arm64-musl": ["@napi-rs/keyring-linux-arm64-musl@2.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-dKjiXKTHQjAS57hL0HVzLpW9XYNGFpfYkr/51FzIIvbZR08PAnzIDYKwShoNYGvjQP6+rDFf1a5Utl0aWwcFrA=="], + "@napi-rs/keyring-linux-arm64-gnu": ["@napi-rs/keyring-linux-arm64-gnu@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-AcMk+ZDP8H20Ml5vaTuSh/Ibh5vU4lrAavS2tjoJ7EJJBLDYssONINWrvVgy43SfzntgQVDBcU5GlMI+z5IMFA=="], - "@napi-rs/keyring-linux-riscv64-gnu": ["@napi-rs/keyring-linux-riscv64-gnu@2.1.0", "", { "os": "linux", "cpu": "none" }, "sha512-tqhb0ofhtEJ2+ZfmlKTDHbmIoNSz+SSTPWRgMfSVCgI4JVkqDdfcmIOcThZfRffJ3QI9C3Tng+rZL24acmD+GA=="], + "@napi-rs/keyring-linux-arm64-musl": ["@napi-rs/keyring-linux-arm64-musl@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-wUHecqT9SMUaQCsJO6vtFEsMcfskspXf6+J0AtKsnK8/GnAAb4hf2FHtwp2ZUOecOk1kTW3LPhU5LwTMJ1LLeA=="], - "@napi-rs/keyring-linux-x64-gnu": ["@napi-rs/keyring-linux-x64-gnu@2.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-7ZA0ssxfpuGXV8S5Ct1ZJoEL3sKg/8u7mIL4SK9/PwKBhxIq7K+FVcjnggNsuFnEhHTX+HYl9hHDrlX0odZnHg=="], + "@napi-rs/keyring-linux-riscv64-gnu": ["@napi-rs/keyring-linux-riscv64-gnu@2.1.1", "", { "os": "linux", "cpu": "none" }, "sha512-hrfvuY0zxrpJOa4k/LzWkrB/R/nXnniz/I2V39X5pWgxKJ1iZBQI03/wT8OI7BwSv9czSh5LLeX/o5D5ywRHBg=="], - "@napi-rs/keyring-linux-x64-musl": ["@napi-rs/keyring-linux-x64-musl@2.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-dI74Fzl03CrN2TEP7YVBtUBFgyRTRPyK1LMcIgAUfgW3o6f5SD1a8EsfT82+brpFOltjOhacIlFiYOkYtSI5Fg=="], + "@napi-rs/keyring-linux-x64-gnu": ["@napi-rs/keyring-linux-x64-gnu@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-NHfMyYyu6laqRBzgfsB0zN12l+z5gz6xQqmLDT8ijevMMOc9ICcVo2GUJCPSX5WvRBgzui91D4R4QG0tahMurw=="], - "@napi-rs/keyring-win32-arm64-msvc": ["@napi-rs/keyring-win32-arm64-msvc@2.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-SltrXXkEe9a+Jv8ConDkZW2Mdr0srpWurS7oYkYP1Mp07DNBpFyqawRnyo4p7ZvQU3e+R6irj/Fq0epCCvVUXA=="], + "@napi-rs/keyring-linux-x64-musl": ["@napi-rs/keyring-linux-x64-musl@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5E2pO2zqOv+HJr2YorAUYmqItv8ts2oa+BccxZ07/I7x3XvxqwZGtRbkp4wM8psv7phhIfgy+0kZ/ItBsGiDhg=="], - "@napi-rs/keyring-win32-ia32-msvc": ["@napi-rs/keyring-win32-ia32-msvc@2.1.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-l5wJQhqXkAfQ4FGxoRh0wMpL9VxVmfiYI5TTeOGNupuVsQvbHmysh8gfDQBBWVhyMENHJS2OqOSYIuqVjumAtA=="], + "@napi-rs/keyring-win32-arm64-msvc": ["@napi-rs/keyring-win32-arm64-msvc@2.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-gn+Xg3oZmyC/pMIU0HTCyrIsncn2/u47H3JRQ8WKfPgS0KDCytcf2kuCCSShtNETSE5V8Zc18ymgIW5UrOQ5OA=="], - "@napi-rs/keyring-win32-x64-msvc": ["@napi-rs/keyring-win32-x64-msvc@2.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-3zYwO7dhJiUNyv2te81ujXm2vGxHwgx+z2eBm0dTYe3aWpvKQVzpfxCp/7cViJs8duVYE04wVOgXiRhRCMEq2w=="], + "@napi-rs/keyring-win32-ia32-msvc": ["@napi-rs/keyring-win32-ia32-msvc@2.1.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DeMQnalb+ueYjskBBUjwv0b9xuN9vhs/vi2H4q/crERfDWgmqu77rGZOW4Bi9kDc7Za3t29yeZctEBZzjAtXuA=="], - "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + "@napi-rs/keyring-win32-x64-msvc": ["@napi-rs/keyring-win32-x64-msvc@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-DjRshMlsMdEyZTNpqfQYj60kDrCZLkl4y80U3w5WJQIj7ypToj02+A/hfJw/g68wzbSZ6uXhklyITQ1DABZAUg=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -191,7 +137,9 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@opencor/libopencor-types": ["@opencor/libopencor-types@1.20261007.0", "", {}, "sha512-6jYhgY6d6tqRqmRF0++64RCI3Xg6PiwhVA4vy0NUyrwWQ3lpLlu+b7Kc+kdwbcNCk7ZOX74ZEOZpEYr6g6Enkw=="], + "@opencor/libopencor-types": ["@opencor/libopencor-types@1.20261016.0", "", {}, "sha512-E4rJ92XsusJyXC75F2zXdMArauPsPid0g8wwH6JBfywaEnjIhn3JgG3WezrNb7tkMoq2tchZ8L7Vy6AnCzbPjg=="], + + "@oxc-project/types": ["@oxc-project/types@0.154.0", "", {}, "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA=="], "@primeuix/styled": ["@primeuix/styled@0.7.4", "", { "dependencies": { "@primeuix/utils": "^0.6.1" } }, "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ=="], @@ -207,57 +155,37 @@ "@primevue/metadata": ["@primevue/metadata@4.2.5", "", {}, "sha512-dABQ8BecGg6NRnrLx11MvmCQCFn2inUEmao8t17/Tc2wxwoXY315TaakDa5ebOdYnTG91lh+NX7SBpTH+MYIyQ=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.64.0", "", { "os": "android", "cpu": "arm" }, "sha512-FLLpX941CD/7aBvX3GqLGBM9OvY5PGXTryPJkG5r5/OuBpZpV5k45Uo3RxAvqd35tsyc3hH1bhtz921IKzoIzg=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.64.0", "", { "os": "android", "cpu": "arm64" }, "sha512-iI32XM/jnDYJsvx5ai/za/7gWO9XoSeFFtU06/ZCaRgyilfJOjaopD3nOpk2WrTCGOyeDt4hleUiGOAnd5tHNg=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.64.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-dRBZlbt2sEtF8Mn4MODThze9fIPdDoOhVSlXMAKy5dQbOYK9pOOoPY4Fat3OTtr0+8zbJo5TTZwlsfxY/kDNJg=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.64.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Y3whrJOMucfkhgNkvcM93M6j8C53iEAMmPMYD99C6G1kUy4ELigLWk9ar2/4aCBSRgsql0zdlOemLgW2m5GZgQ=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.64.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-S6S1GEzPjFF6IONYJX3AKt/799+8RgBgCcwRnUo+Ep1IvBxUbw5M51iU0TDdK2NWWVnSvCQ8yi6Em0fGASMOng=="], + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.14", "", { "os": "android", "cpu": "arm" }, "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.64.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Ed1DPoFENkXj4IGzSB12SesV40iP6LxCCrbTIkSGIQBpOOxpBM+0kj62lmaeIHfMWTDoddqTOtqc7s8LYfn+Pw=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.14", "", { "os": "android", "cpu": "arm64" }, "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-J/6xhutJPtFyIkmHdf4olOdHHKH5kOJNumojdWn+Elv5ICEYTVOa362Vw27TxsK0BLzb1le3EDlO3UbvFK1lnQ=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-G4L/Nnx59d2LvDoV/P4S5lZ7oVLT6hodOHMacqjiq2LL2gts86KbkEBTYL53+fBt0odhf7XPIbIATGoYiJctxg=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zF16pvJDEUksGYRbfnXne7pdklAyj2XDmJeYKgP93oq0cUyX9Y8O7lUo1R+IY1GagP/YBvCEDCe7eEcAKpw//g=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-nIggWn8n+w12+R5lsV+PYYoUY8vuObxPAQqJI7WES+9v0qspYuAHoZMfYM0yLxlipasT5BY9ht5W8t5c/rSCkg=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.14", "", { "os": "linux", "cpu": "arm" }, "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-WLOTiHPzszP0dxIMsHpFeDSOSB5I49Mj8K4+yKE3H1X/D0JRG5XZ5jrctJLMjnUc/LUzaKiwq6EHWNJ8pup8PA=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-35RK9aRhh7g7Pmeu5gQ01aCvrhXzj9LoBE6xtUcEH1ns+nyTaSzxSpADmBQINu4QiTDGvmQYwo1j5hlg1rRXFw=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.64.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RqTfnpoTjuWfPAmqK5p2edt2AMCDJWm6oTn7sKfmyx+6tRga0ip05SJ977o6udTcIiDeF/CJMUfkaN4gtWt6Aw=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.14", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.64.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RQjZPigjxFvFZDijG6rjlSXxhRaTf9zLYVpGK+lVcga64oMqP1GbgVqCb46eHTTceSIgp89Ja0y5aPe3GFBYHg=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.14", "", { "os": "linux", "cpu": "s390x" }, "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-tjL58mgQcTRnDRpeky+Sth1MKQeYi2Ct6r3Luq9E5aTSjnM/i/JbozqZkKqp9thM5+oDSZxo+05f9kdQedfTGA=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.14", "", { "os": "linux", "cpu": "x64" }, "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-ictoRyVaoZ8C67myHef+8hhCa6IplXJJ/xUCkVCxziV4zxMZh+jvhkhGsPBLqj1S72fnqD0QH5zL4lcaa4X7DQ=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.14", "", { "os": "linux", "cpu": "x64" }, "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.64.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-8aVvZ2hfr9RLFzBQkQNmYeHLDja8fLeQAmLt757zdKN/B+8L809KlLJKctQjS22wZotptV8G6o7fVPuAs8S1tQ=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.14", "", { "os": "none", "cpu": "arm64" }, "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2dEF8GAcDKwshUfydD+GhosppNyBzhVUHkdFF3CXd3FpOkzj/RZzF5aoEtzklujklT5qFVRU2GK/cFeLaTNKVg=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3XHTvoo2hKkh0MiOfS7SkhHjMaaWF7P4F3drocZZaho/Z8w7h/jpnuBHm91BOQLfvrKHs1yR+zir4E8RI3XzTQ=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.14", "", { "os": "win32", "cpu": "x64" }, "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.64.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HKtkzodL4a3KFWDXDoO61cRynAWTR3LnAE0oGtfG9lcNFpNuVYUFOByxDgNqEvic9rvaQbAj+SFwTEWDptRaIw=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.64.0", "", { "os": "none", "cpu": "arm64" }, "sha512-JBsxqiY16uyyCujtoC3s7zL+SrYyNElptD7K4V+KUoJ2V8/XG3hPWpheK+SuPwNh1cikJvIZmIdsrYXvD6PLJw=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.64.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-S5Qrijh37qhnUYQ0ay8WN3LuWeKjow7wTUyCOPMZtnXiBXYXdsZETI6ZLfYnQAKovltckPLBFdNeD61P2rJc7g=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.64.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zbZ6iMmLFEsCtTdc2/y1F/cDFVNNCPF7Ae8PCT/wdPE//hGwYp6r+MyCLMFX+kOeIe09Pz6ASfnnfpSTN1FJQQ=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.64.0", "", { "os": "win32", "cpu": "x64" }, "sha512-L6Hw69oNpuYaOr1zCG30v0/DqKYCSRwodPGwIcVZ/6f8SOzq9rxtL/xNVI5MO4wjli+Ko0ErK9uHJL3mNPt8wQ=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.64.0", "", { "os": "win32", "cpu": "x64" }, "sha512-nl+CTWIgOGszQmUz/kanfgrfFZ4qiwxKIfT/ryl3N3Ewk5tqizmYtNXiqb7Bu/fDtNgu1kyxdFiAUJNur3iysg=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], @@ -293,11 +221,11 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="], - "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + "@types/bun": ["@types/bun@1.4.3", "", { "dependencies": { "bun-types": "1.4.3" } }, "sha512-N67Pqy8JXbAY/j0tNraS3V5mubGvc6rrrNDmDCtZLlFzccbUJPDb0tvgUBC5q4m8ybkP93Jcn1QB+RYQ6PvBhw=="], - "@types/node": ["@types/node@26.6.4", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg=="], + "@types/node": ["@types/node@26.6.5", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg=="], - "@types/plotly.js": ["@types/plotly.js@3.0.14", "", {}, "sha512-3JZH20UBXQ8rZFPeV40fKinx5Le0T2cuz6kM8LEYkkAuDrljzezlwvZuXnXQhyBost7YwgKxEDg7SVTIBbA6eg=="], + "@types/plotly.js": ["@types/plotly.js@3.0.15", "", {}, "sha512-ftBxk2fB7BxxeMpT0IVEb/3HKpYTXO5nCdR5mGEz6gHpLd6GGRFxvDVoG1gpD/gBAgoN36KIyRflxCnOfWlskQ=="], "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], @@ -341,7 +269,7 @@ "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], - "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.9", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA=="], + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.10", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25 || ^3.6.0-rc.10" } }, "sha512-b80aUQh3n+qEzzZm5oQp3rT2QImeCx58K6eFl/RkJ3d65KW0kTuv5lNsLRVAjbW9YRUpo/wzmvzMgV34bTAItA=="], "@volar/language-core": ["@volar/language-core@2.4.28", "", { "dependencies": { "@volar/source-map": "2.4.28" } }, "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ=="], @@ -357,9 +285,9 @@ "@vue/compiler-ssr": ["@vue/compiler-ssr@3.4.21", "", { "dependencies": { "@vue/compiler-dom": "3.4.21", "@vue/shared": "3.4.21" } }, "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q=="], - "@vue/language-core": ["@vue/language-core@3.3.12", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-k7uIKR0n4CBidk233y7cac9osnDQ5WRidTl6nsrNvpZ3nf8cx9iJxRVsRzVsoPRHbowT/iKgGMTS9RGov3jeIA=="], + "@vue/language-core": ["@vue/language-core@3.3.13", "", { "dependencies": { "@volar/language-core": "2.4.28", "@vue/compiler-dom": "^3.5.0", "@vue/shared": "^3.5.0", "alien-signals": "^3.2.1", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "picomatch": "^4.0.4" } }, "sha512-axA0Xemn5LuUKuKWob0/kVq7xQ2mJJM6VqA9Exvt35nM9yRC31goWVve/G/Zb2ZCLsXC8FHYjjfXlnshknSCew=="], - "@vue/reactivity": ["@vue/reactivity@3.5.43", "", { "dependencies": { "@vue/shared": "3.5.43" } }, "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA=="], + "@vue/reactivity": ["@vue/reactivity@3.5.44", "", { "dependencies": { "@vue/shared": "3.5.44" } }, "sha512-u+QJdKeBBEADkPdevsL+6E12QlYVfBD8Zcw5qjDQKtamI6l14KyqazJMobhCKelhWN5zpyKUiEDoTyHE93cqWw=="], "@vue/runtime-core": ["@vue/runtime-core@3.4.21", "", { "dependencies": { "@vue/reactivity": "3.4.21", "@vue/shared": "3.4.21" } }, "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA=="], @@ -393,19 +321,21 @@ "autoprefixer": ["autoprefixer@10.6.1", "", { "dependencies": { "browserslist": "^4.28.9", "caniuse-lite": "^1.0.30001810", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.11.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.28", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "browserslist": ["browserslist@4.29.3", "", { "dependencies": { "baseline-browser-mapping": "^2.11.26", "caniuse-lite": "^1.0.30001813", "electron-to-chromium": "^1.5.439", "node-releases": "^2.0.57", "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA=="], + "bun-types": ["bun-types@1.4.3", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-tinRJNGOmUN+tEAsn2y2e942n9JHRHR42iUYzDH75NmhJqVBokGSQkkfSFdNWwsANIdrK6ACH9t01M452YyTwg=="], + "bundle-name": ["bundle-name@4.1.1", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA=="], "cacheable": ["cacheable@2.5.0", "", { "dependencies": { "@cacheable/memory": "^2.2.0", "@cacheable/utils": "^2.5.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.10.1" } }, "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "caniuse-lite": ["caniuse-lite@1.0.30001814", "", {}, "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001816", "", {}, "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw=="], "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -439,7 +369,7 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "electron-to-chromium": ["electron-to-chromium@1.5.447", "", {}, "sha512-vhYiRwTPq7tvwplP6fOFVRJxfZifavlr6tK8FD1EO5RxRuvZ9qdQM0lr1mvNBeDe1s2XrfL3n5BBwvigXJ7KuA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.456", "", {}, "sha512-8hboqI6xesozteFb8aa7OGknvxUtTeRJLz0FoePysUEh/oWbddykIGdgpEJRbPRg/nIMfBf2DFos+H2MtvJl2A=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -451,8 +381,6 @@ "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - "esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], @@ -551,29 +479,29 @@ "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], @@ -601,7 +529,7 @@ "nanoid": ["nanoid@3.3.20", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="], - "node-releases": ["node-releases@2.0.57", "", {}, "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw=="], + "node-releases": ["node-releases@2.0.59", "", {}, "sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA=="], "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], @@ -623,7 +551,7 @@ "pkg-types": ["pkg-types@2.3.3", "", { "dependencies": { "confbox": "^0.3.1", "exsolve": "^1.1.1", "pathe": "^2.0.3" } }, "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w=="], - "postcss": ["postcss@8.5.29", "", { "dependencies": { "nanoid": "^3.3.19", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ=="], + "postcss": ["postcss@8.5.30", "", { "dependencies": { "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA=="], "postcss-safe-parser": ["postcss-safe-parser@7.1.0", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg=="], @@ -651,7 +579,7 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rollup": ["rollup@4.64.0", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.64.0", "@rollup/rollup-android-arm64": "4.64.0", "@rollup/rollup-darwin-arm64": "4.64.0", "@rollup/rollup-darwin-x64": "4.64.0", "@rollup/rollup-freebsd-arm64": "4.64.0", "@rollup/rollup-freebsd-x64": "4.64.0", "@rollup/rollup-linux-arm-gnueabihf": "4.64.0", "@rollup/rollup-linux-arm-musleabihf": "4.64.0", "@rollup/rollup-linux-arm64-gnu": "4.64.0", "@rollup/rollup-linux-arm64-musl": "4.64.0", "@rollup/rollup-linux-loong64-gnu": "4.64.0", "@rollup/rollup-linux-loong64-musl": "4.64.0", "@rollup/rollup-linux-ppc64-gnu": "4.64.0", "@rollup/rollup-linux-ppc64-musl": "4.64.0", "@rollup/rollup-linux-riscv64-gnu": "4.64.0", "@rollup/rollup-linux-riscv64-musl": "4.64.0", "@rollup/rollup-linux-s390x-gnu": "4.64.0", "@rollup/rollup-linux-x64-gnu": "4.64.0", "@rollup/rollup-linux-x64-musl": "4.64.0", "@rollup/rollup-openbsd-x64": "4.64.0", "@rollup/rollup-openharmony-arm64": "4.64.0", "@rollup/rollup-win32-arm64-msvc": "4.64.0", "@rollup/rollup-win32-ia32-msvc": "4.64.0", "@rollup/rollup-win32-x64-gnu": "4.64.0", "@rollup/rollup-win32-x64-msvc": "4.64.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-gjA/kFeDfffII8COoPsvLPvE16hWDTEc1+0QKQNKUkuV8iJjqsC4xXs8/FzuL0v9EV39kuxTbAgcFoTRyy2TGQ=="], + "rolldown": ["rolldown@1.2.14", "", { "dependencies": { "@oxc-project/types": "=0.154.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.14", "@rolldown/binding-android-arm64": "1.2.14", "@rolldown/binding-darwin-arm64": "1.2.14", "@rolldown/binding-darwin-x64": "1.2.14", "@rolldown/binding-freebsd-x64": "1.2.14", "@rolldown/binding-linux-arm-gnueabihf": "1.2.14", "@rolldown/binding-linux-arm64-gnu": "1.2.14", "@rolldown/binding-linux-arm64-musl": "1.2.14", "@rolldown/binding-linux-ppc64-gnu": "1.2.14", "@rolldown/binding-linux-s390x-gnu": "1.2.14", "@rolldown/binding-linux-x64-gnu": "1.2.14", "@rolldown/binding-linux-x64-musl": "1.2.14", "@rolldown/binding-openharmony-arm64": "1.2.14", "@rolldown/binding-win32-arm64-msvc": "1.2.14", "@rolldown/binding-win32-x64-msvc": "1.2.14" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw=="], "rollup-plugin-visualizer": ["rollup-plugin-visualizer@7.1.1", "", { "dependencies": { "open": "^11.0.0", "picomatch": "^4.0.5", "source-map": "^0.8.0", "yargs": "^18.1.0" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-ThaGiHTU8XW02OkK80TrTHATraJmM9OAduU4otal+7gyXLpYEtmGBLfx5kW+EHvvLwn03YGW2NnwKUIqsYlJAA=="], @@ -713,17 +641,17 @@ "unplugin-vue-components": ["unplugin-vue-components@32.1.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.2.0", "magic-string": "^0.30.21", "mlly": "^1.8.2", "obug": "^2.1.1", "picomatch": "^4.0.4", "tinyglobby": "^0.2.16", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg=="], - "update-browserslist-db": ["update-browserslist-db@1.3.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ=="], + "update-browserslist-db": ["update-browserslist-db@1.3.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="], + "vite": ["vite@8.3.4", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.29", "rolldown": "~1.2.12", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ=="], "vscode-uri": ["vscode-uri@3.2.0", "", {}, "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg=="], "vue": ["vue@3.4.21", "", { "dependencies": { "@vue/compiler-dom": "3.4.21", "@vue/compiler-sfc": "3.4.21", "@vue/runtime-dom": "3.4.21", "@vue/server-renderer": "3.4.21", "@vue/shared": "3.4.21" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA=="], - "vue-tsc": ["vue-tsc@3.3.12", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.12" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-kbpaz18O9jPVkXVHD1BEjpUXM6WhXSu+62L1sGGr6Wbzd8GbvkBQWjoj3KZ/C0aq2MZllce7UdtWzwCIYsBMGA=="], + "vue-tsc": ["vue-tsc@3.3.13", "", { "dependencies": { "@volar/typescript": "2.4.28", "@vue/language-core": "3.3.13" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" } }, "sha512-jE4/bXdOMSHVxEGgQCqNllJy+9N3Gx6tfSAsg0yhFK/MAN6eXqISihupnDEKSCt12VVnjN2gI3AqLYcqoh5x/A=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -747,6 +675,8 @@ "@primevue/core/@primeuix/styled": ["@primeuix/styled@0.3.2", "", { "dependencies": { "@primeuix/utils": "^0.3.2" } }, "sha512-ColZes0+/WKqH4ob2x8DyNYf1NENpe5ZguOvx5yCLxaP8EIMVhLjWLO/3umJiDnQU4XXMLkn2mMHHw+fhTX/mw=="], + "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], @@ -759,17 +689,17 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.43", "", { "dependencies": { "@vue/compiler-core": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ=="], + "@vue/language-core/@vue/compiler-dom": ["@vue/compiler-dom@3.5.44", "", { "dependencies": { "@vue/compiler-core": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg=="], - "@vue/language-core/@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + "@vue/language-core/@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], - "@vue/reactivity/@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + "@vue/reactivity/@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], "@vue/runtime-core/@vue/reactivity": ["@vue/reactivity@3.4.21", "", { "dependencies": { "@vue/shared": "3.4.21" } }, "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw=="], - "@vueuse/core/vue": ["vue@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/compiler-sfc": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/server-renderer": "3.5.43", "@vue/shared": "3.5.43" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng=="], + "@vueuse/core/vue": ["vue@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/compiler-sfc": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/server-renderer": "3.5.44", "@vue/shared": "3.5.44" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ=="], - "@vueuse/shared/vue": ["vue@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/compiler-sfc": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/server-renderer": "3.5.43", "@vue/shared": "3.5.43" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng=="], + "@vueuse/shared/vue": ["vue@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/compiler-sfc": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/server-renderer": "3.5.44", "@vue/shared": "3.5.44" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ=="], "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], @@ -789,27 +719,49 @@ "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "@vue/language-core/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "@vue/language-core/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "@vueuse/core/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.43", "", { "dependencies": { "@vue/compiler-core": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ=="], + "@vueuse/core/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.44", "", { "dependencies": { "@vue/compiler-core": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg=="], - "@vueuse/core/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.43", "@vue/compiler-dom": "3.5.43", "@vue/compiler-ssr": "3.5.43", "@vue/shared": "3.5.43", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.28", "source-map-js": "^1.2.1" } }, "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ=="], + "@vueuse/core/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/compiler-core": "3.5.44", "@vue/compiler-dom": "3.5.44", "@vue/compiler-ssr": "3.5.44", "@vue/shared": "3.5.44", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.29", "source-map-js": "^1.2.2" } }, "sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw=="], - "@vueuse/core/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/runtime-core": "3.5.43", "@vue/shared": "3.5.43", "csstype": "^3.2.3" } }, "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw=="], + "@vueuse/core/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/runtime-core": "3.5.44", "@vue/shared": "3.5.44", "csstype": "^3.2.3" } }, "sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg=="], - "@vueuse/core/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.43", "", { "dependencies": { "@vue/compiler-ssr": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ=="], + "@vueuse/core/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.44", "", { "dependencies": { "@vue/compiler-ssr": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ=="], - "@vueuse/core/vue/@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + "@vueuse/core/vue/@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], - "@vueuse/shared/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.43", "", { "dependencies": { "@vue/compiler-core": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ=="], + "@vueuse/shared/vue/@vue/compiler-dom": ["@vue/compiler-dom@3.5.44", "", { "dependencies": { "@vue/compiler-core": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg=="], - "@vueuse/shared/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.43", "@vue/compiler-dom": "3.5.43", "@vue/compiler-ssr": "3.5.43", "@vue/shared": "3.5.43", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.28", "source-map-js": "^1.2.1" } }, "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ=="], + "@vueuse/shared/vue/@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/compiler-core": "3.5.44", "@vue/compiler-dom": "3.5.44", "@vue/compiler-ssr": "3.5.44", "@vue/shared": "3.5.44", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.29", "source-map-js": "^1.2.2" } }, "sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw=="], - "@vueuse/shared/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/runtime-core": "3.5.43", "@vue/shared": "3.5.43", "csstype": "^3.2.3" } }, "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw=="], + "@vueuse/shared/vue/@vue/runtime-dom": ["@vue/runtime-dom@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/runtime-core": "3.5.44", "@vue/shared": "3.5.44", "csstype": "^3.2.3" } }, "sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg=="], - "@vueuse/shared/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.43", "", { "dependencies": { "@vue/compiler-ssr": "3.5.43", "@vue/runtime-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ=="], + "@vueuse/shared/vue/@vue/server-renderer": ["@vue/server-renderer@3.5.44", "", { "dependencies": { "@vue/compiler-ssr": "3.5.44", "@vue/runtime-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ=="], - "@vueuse/shared/vue/@vue/shared": ["@vue/shared@3.5.43", "", {}, "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q=="], + "@vueuse/shared/vue/@vue/shared": ["@vue/shared@3.5.44", "", {}, "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg=="], "cliui/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -821,25 +773,25 @@ "@vue/language-core/@vue/compiler-dom/@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + "@vueuse/core/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], - "@vueuse/core/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw=="], + "@vueuse/core/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ=="], - "@vueuse/core/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + "@vueuse/core/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], - "@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@vueuse/shared/vue/@vue/compiler-dom/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.43", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.43", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg=="], + "@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-core": ["@vue/compiler-core@3.5.44", "", { "dependencies": { "@babel/parser": "^7.29.9", "@vue/shared": "3.5.44", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.2" } }, "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ=="], - "@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + "@vueuse/shared/vue/@vue/compiler-sfc/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], - "@vueuse/shared/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.43", "", { "dependencies": { "@vue/reactivity": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw=="], + "@vueuse/shared/vue/@vue/runtime-dom/@vue/runtime-core": ["@vue/runtime-core@3.5.44", "", { "dependencies": { "@vue/reactivity": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ=="], - "@vueuse/shared/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], + "@vueuse/shared/vue/@vue/server-renderer/@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.44", "", { "dependencies": { "@vue/compiler-dom": "3.5.44", "@vue/shared": "3.5.44" } }, "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA=="], "@vueuse/core/vue/@vue/compiler-dom/@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], diff --git a/src/renderer/index.html b/src/renderer/index.html index e72ae4684..2148083ad 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -7,7 +7,7 @@ + content="default-src 'self'; connect-src *; script-src 'unsafe-eval'; script-src-elem 'self' https://apis.google.com https://cdn.jsdelivr.net https://fastly.jsdelivr.net https://gcore.jsdelivr.net https://cors-proxy.opencor.workers.dev; worker-src 'self'; font-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self' https://opencorapp.firebaseapp.com; img-src 'self' blob: data:" />