diff --git a/.forgejo/workflows/deploy-dev.yml b/.forgejo/workflows/deploy-dev.yml index 26053ff..36c5efe 100644 --- a/.forgejo/workflows/deploy-dev.yml +++ b/.forgejo/workflows/deploy-dev.yml @@ -2,22 +2,35 @@ on: push: branches: - dev + paths-ignore: + - flake.nix jobs: - deploy: + build-web: runs-on: docker - steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - - name: Setup Bun + - name: Install Packages + run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm + + - name: Install Nix + uses: https://github.com/cachix/install-nix-action@v30 + + - name: Install Bun uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install --frozen-lockfile - - name: Build + - name: Copy licenses + run: bun run copy-licenses + + - name: Build packages + run: bun run build:packages + + - name: Build web run: bun run build:web - name: Install rsync @@ -25,3 +38,238 @@ jobs: - name: Deploy run: rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-dev/ + + build-mobile: + runs-on: docker + steps: + - name: Check out repo + uses: https://data.forgejo.org/actions/checkout@v4 + + - name: Install Packages + run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm + + - name: Install Nix + uses: https://github.com/cachix/install-nix-action@v30 + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Copy licenses + run: bun run copy-licenses + + - name: Build packages + run: bun run build:packages + + - name: Setup Android Keystore + env: + KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} + run: | + bun -e "require('fs').writeFileSync('keystore.jks', Buffer.from(process.env.KEYSTORE_BASE64.replace(/\s+/g, ''), 'base64'))" + bun -e "const content = process.env.KEYSTORE_PROPERTIES.replace(/\\n/g, '\n').replace(/\r/g, '').split('\n').map(l => l.trim()).filter(l => l).join('\n'); require('fs').writeFileSync('keystore.properties', content)" + + - name: Build mobile + run: bun run build:mobile + + - name: Upload mobile artifact + uses: https://data.forgejo.org/actions/upload-artifact@v3 + with: + name: mobile-apk + path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk + + build-desktop: + runs-on: docker + strategy: + matrix: + target: [linux] + steps: + - name: Check out repo + uses: https://data.forgejo.org/actions/checkout@v4 + + - name: Install Packages + run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm xz-utils + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Copy licenses + run: bun run copy-licenses + + - name: Build packages + run: bun run build:packages + + - name: Set Electron dev version + run: | + VERSION="$(node -p "require('./package.json').version")" + SHORT_SHA="$(git rev-parse --short HEAD)" + DEV_VERSION="$VERSION-dev-$SHORT_SHA" + export DEV_VERSION + node -e ' + const fs = require("fs"); + const path = "apps/electron/package.json"; + const pkg = JSON.parse(fs.readFileSync(path, "utf8")); + pkg.version = process.env.DEV_VERSION; + fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); + ' + + - name: Build Electron desktop + run: bun run build:desktop + + - name: Upload desktop artifacts + uses: https://data.forgejo.org/actions/upload-artifact@v3 + with: + name: electron-desktop-${{ matrix.target }} + path: apps/electron/release/ + + release: + runs-on: docker + needs: [build-web, build-mobile, build-desktop] + steps: + - name: Check out repo + uses: https://data.forgejo.org/actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Packages + run: apt-get update && apt-get install -y sudo curl jq + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Download mobile artifact + uses: https://data.forgejo.org/actions/download-artifact@v3 + with: + name: mobile-apk + path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/ + + - name: Download desktop artifacts + uses: https://data.forgejo.org/actions/download-artifact@v3 + with: + name: electron-desktop-linux + path: apps/electron/release/ + + - name: Read version and hash + id: version + run: | + VERSION="$(node -p "require('./package.json').version")" + SHORT_SHA="$(git rev-parse --short HEAD)" + echo "version=$VERSION" >> "$FORGEJO_OUTPUT" + echo "short_sha=$SHORT_SHA" >> "$FORGEJO_OUTPUT" + echo "tag=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT" + echo "title=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT" + + - name: Copy releases + env: + TENSAMIN_RELEASE_VERSION: ${{ steps.version.outputs.tag }} + TENSAMIN_RELEASE_TAG: ${{ steps.version.outputs.tag }} + run: | + ASSET_BASE_URL="${{ forgejo.api_url }}" + ASSET_BASE_URL="${ASSET_BASE_URL%/api/v1}/${{ forgejo.repository }}/releases/download/${{ steps.version.outputs.tag }}" + FORGEJO_RELEASE_ASSET_BASE_URL="$ASSET_BASE_URL" bun --bun run copy-releases + + - name: Create pre-release and upload files + env: + TOKEN: ${{ forgejo.token }} + API: ${{ forgejo.api_url }} + REPO: ${{ forgejo.repository }} + SHA: ${{ forgejo.sha }} + TAG: ${{ steps.version.outputs.tag }} + TITLE: ${{ steps.version.outputs.title }} + run: | + set -eu + + test -d releases + find releases -type f | grep -q . + + LATEST_PROD_TAG="$(git for-each-ref refs/tags --sort=-creatordate --format='%(refname:short)' | awk '!/-/' | head -n 1 || true)" + + if [ -n "$LATEST_PROD_TAG" ]; then + RAW_LOG="$(git log "$LATEST_PROD_TAG"..HEAD --pretty=format:'- %s')" + else + RAW_LOG="$(git log --pretty=format:'- %s')" + fi + + export RAW_LOG LATEST_PROD_TAG API REPO TAG + BODY="$(node -e ' + const raw = process.env.RAW_LOG; + const lines = raw.split("\n"); + const commits = []; + for (const line of lines) { + const msg = line.replace(/^- /, ""); + const parts = msg.split(/(?=\([^)]+\):)/).filter(Boolean).map(s => s.trim()).filter(s => s); + for (const part of parts) { + commits.push(part); + } + } + const priority = { "(feat):": 1, "(fix):": 2, "(qol):": 3 }; + commits.sort((a, b) => { + const tagA = a.match(/^(\([^)]+\):)/)?.[1] || ""; + const tagB = b.match(/^(\([^)]+\):)/)?.[1] || ""; + return (priority[tagA] || 99) - (priority[tagB] || 99); + }); + const log = commits.map(c => "- " + c).join("\n"); + const latestTag = process.env.LATEST_PROD_TAG; + if (latestTag) { + const serverUrl = process.env.API.replace(/\/api\/v1.*/, ""); + const compareUrl = serverUrl + "/" + process.env.REPO + "/compare/" + latestTag + "..." + process.env.TAG; + console.log(log + "\n\n[View changes](" + compareUrl + ")"); + } else { + console.log(log); + } + ')" + + HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG") + + if [ "$HTTP_STATUS" = "200" ]; then + echo "Release $TAG already exists." + RELEASE_ID="$(jq -r .id release_out.json)" + else + echo "Creating new pre-release for $TAG" + RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg tag "$TAG" \ + --arg name "$TITLE" \ + --arg body "$BODY" \ + --arg target "$SHA" \ + '{ + tag_name: $tag, + name: $name, + body: $body, + target_commitish: $target, + draft: false, + prerelease: true + }')")" + RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" + fi + + ASSET_BASE_URL="${API%/api/v1}/$REPO/releases/download/$TAG" + export ASSET_BASE_URL + node -e ' + const fs = require("fs"); + const path = "releases/electron-release-metadata.json"; + const metadata = JSON.parse(fs.readFileSync(path, "utf8")); + metadata.version = process.env.TAG; + metadata.tag = process.env.TAG; + for (const artifact of metadata.artifacts || []) { + artifact.url = `${process.env.ASSET_BASE_URL}/${encodeURIComponent(artifact.name)}`; + } + fs.writeFileSync(path, `${JSON.stringify(metadata, null, 2)}\n`); + ' + + find releases -type f -print0 | while IFS= read -r -d '' file; do + name="$(basename "$file")" + curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \ + -H "Authorization: token $TOKEN" \ + -F "attachment=@$file" + done diff --git a/.forgejo/workflows/deploy-prod.yml b/.forgejo/workflows/deploy-prod.yml index 1df5b60..bc086d6 100644 --- a/.forgejo/workflows/deploy-prod.yml +++ b/.forgejo/workflows/deploy-prod.yml @@ -2,17 +2,18 @@ on: push: branches: - main + paths-ignore: + - flake.nix jobs: - deploy: + build-web: runs-on: docker - steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - name: Install Packages - run: apt-get update && apt-get install -y sudo curl jq + run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm - name: Install Nix uses: https://github.com/cachix/install-nix-action@v30 @@ -23,16 +24,14 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - - name: Setup Android Keystore - env: - KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} - run: | - bun -e "require('fs').writeFileSync('keystore.jks', Buffer.from(process.env.KEYSTORE_BASE64.replace(/\s+/g, ''), 'base64'))" - bun -e "const content = process.env.KEYSTORE_PROPERTIES.replace(/\\\\n/g, '\n').replace(/\\r/g, '').split('\n').map(l => l.trim()).filter(l => l).join('\n'); require('fs').writeFileSync('keystore.properties', content)" + - name: Copy licenses + run: bun run copy-licenses - - name: Build - run: bun run build:apps + - name: Build packages + run: bun run build:packages + + - name: Build web + run: bun run build:web - name: Install rsync run: apt-get update && apt-get install -y rsync @@ -40,12 +39,135 @@ jobs: - name: Deploy run: rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-prod/ + build-mobile: + runs-on: docker + steps: + - name: Check out repo + uses: https://data.forgejo.org/actions/checkout@v4 + + - name: Install Packages + run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm + + - name: Install Nix + uses: https://github.com/cachix/install-nix-action@v30 + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Copy licenses + run: bun run copy-licenses + + - name: Build packages + run: bun run build:packages + + - name: Setup Android Keystore + env: + KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} + run: | + bun -e "require('fs').writeFileSync('keystore.jks', Buffer.from(process.env.KEYSTORE_BASE64.replace(/\s+/g, ''), 'base64'))" + bun -e "const content = process.env.KEYSTORE_PROPERTIES.replace(/\\n/g, '\n').replace(/\r/g, '').split('\n').map(l => l.trim()).filter(l => l).join('\n'); require('fs').writeFileSync('keystore.properties', content)" + + - name: Build mobile + run: bun run build:mobile + + - name: Upload mobile artifact + uses: https://data.forgejo.org/actions/upload-artifact@v3 + with: + name: mobile-apk + path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk + + build-desktop: + runs-on: docker + strategy: + matrix: + target: [linux] + steps: + - name: Check out repo + uses: https://data.forgejo.org/actions/checkout@v4 + + - name: Install Packages + run: apt-get update && apt-get install -y sudo curl jq fakeroot dpkg rpm xz-utils + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Copy licenses + run: bun run copy-licenses + + - name: Build packages + run: bun run build:packages + + - name: Set Electron prod version + run: | + VERSION="$(node -p "require('./package.json').version")" + export VERSION + node -e ' + const fs = require("fs"); + const path = "apps/electron/package.json"; + const pkg = JSON.parse(fs.readFileSync(path, "utf8")); + pkg.version = process.env.VERSION; + fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); + ' + + - name: Build Electron desktop + run: bun run build:desktop + + - name: Upload desktop artifacts + uses: https://data.forgejo.org/actions/upload-artifact@v3 + with: + name: electron-desktop-${{ matrix.target }} + path: apps/electron/release/ + + release: + runs-on: docker + needs: [build-web, build-mobile, build-desktop] + steps: + - name: Check out repo + uses: https://data.forgejo.org/actions/checkout@v4 + + - name: Install Packages + run: apt-get update && apt-get install -y sudo curl jq + + - name: Install Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Download mobile artifact + uses: https://data.forgejo.org/actions/download-artifact@v3 + with: + name: mobile-apk + path: apps/tauri/src-tauri/gen/android/app/build/outputs/apk/universal/release/ + + - name: Download desktop artifacts + uses: https://data.forgejo.org/actions/download-artifact@v3 + with: + name: electron-desktop-linux + path: apps/electron/release/ + - name: Read version id: version run: | VERSION="$(node -p "require('./package.json').version")" echo "version=$VERSION" >> "$FORGEJO_OUTPUT" - echo "tag=v$VERSION" >> "$FORGEJO_OUTPUT" + echo "tag=$VERSION" >> "$FORGEJO_OUTPUT" + + - name: Copy releases + env: + TENSAMIN_RELEASE_VERSION: ${{ steps.version.outputs.tag }} + TENSAMIN_RELEASE_TAG: ${{ steps.version.outputs.tag }} + run: | + ASSET_BASE_URL="${{ forgejo.api_url }}" + ASSET_BASE_URL="${ASSET_BASE_URL%/api/v1}/${{ forgejo.repository }}/releases/download/${{ steps.version.outputs.tag }}" + FORGEJO_RELEASE_ASSET_BASE_URL="$ASSET_BASE_URL" bun --bun run copy-releases - name: Create release and upload files env: @@ -54,42 +176,88 @@ jobs: REPO: ${{ forgejo.repository }} SHA: ${{ forgejo.sha }} TAG: ${{ steps.version.outputs.tag }} - VERSION: ${{ steps.version.outputs.version }} run: | set -eu test -d releases find releases -type f | grep -q . + COMMIT_MSG="$(git log -1 --pretty=%B | sed 's/$/ /')" + HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG") if [ "$HTTP_STATUS" = "200" ]; then echo "Release $TAG already exists." - RELEASE_ID="$(jq -r .id release_out.json)" - else - echo "Creating new release for $TAG" - RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg tag "$TAG" \ - --arg name "$TAG" \ - --arg body "Release $VERSION" \ - --arg target "$SHA" \ - '{ - tag_name: $tag, - name: $name, - body: $body, - target_commitish: $target, - draft: false, - prerelease: false - }')")" - RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" + exit 0 fi + echo "Creating new release for $TAG" + RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg tag "$TAG" \ + --arg name "$TAG" \ + --arg body "$COMMIT_MSG" \ + --arg target "$SHA" \ + '{ + tag_name: $tag, + name: $name, + body: $body, + target_commitish: $target, + draft: false, + prerelease: false + }')")" + RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" + + ASSET_BASE_URL="${API%/api/v1}/$REPO/releases/download/$TAG" + export ASSET_BASE_URL + node -e ' + const fs = require("fs"); + const path = "releases/electron-release-metadata.json"; + const metadata = JSON.parse(fs.readFileSync(path, "utf8")); + metadata.version = process.env.TAG; + metadata.tag = process.env.TAG; + for (const artifact of metadata.artifacts || []) { + artifact.url = `${process.env.ASSET_BASE_URL}/${encodeURIComponent(artifact.name)}`; + } + fs.writeFileSync(path, `${JSON.stringify(metadata, null, 2)}\n`); + ' + find releases -type f -print0 | while IFS= read -r -d '' file; do name="$(basename "$file")" curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \ -H "Authorization: token $TOKEN" \ -F "attachment=@$file" done + + - name: Update root flake release hash + env: + TAG: ${{ steps.version.outputs.tag }} + run: | + set -eu + + APPIMAGE="$(find releases -maxdepth 1 -type f -name 'Tensamin-*-linux-x86_64.AppImage' -print -quit)" + test -n "$APPIMAGE" + + HASH="$(node -e 'const fs = require("fs"); const crypto = require("crypto"); const file = process.argv[1]; console.log("sha256-" + crypto.createHash("sha256").update(fs.readFileSync(file)).digest("base64"));' "$APPIMAGE")" + export HASH + + node -e ' + const fs = require("fs"); + const version = process.env.TAG; + const hash = process.env.HASH; + let content = fs.readFileSync("flake.nix", "utf8"); + content = content.replace(/version = "[^"]+";/, `version = "${version}";`); + content = content.replace(/hash = "sha256-[^"]+";/, `hash = "${hash}";`); + fs.writeFileSync("flake.nix", content); + ' + + if git diff --quiet -- flake.nix; then + echo "flake.nix already has the current release hash." + exit 0 + fi + + git add flake.nix + git -c user.name="forgejo-actions" -c user.email="forgejo-actions@localhost" commit -m "(qol): update release flake hash" + git push diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5bb82f5..34ff437 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,7 +2,6 @@ "recommendations": [ "tauri-apps.tauri-vscode", "rust-lang.rust-analyzer", - "bradlc.vscode-tailwindcss", - "antfu.vite" + "bradlc.vscode-tailwindcss" ] } diff --git a/apps/electron/.gitignore b/apps/electron/.gitignore new file mode 100644 index 0000000..158494b --- /dev/null +++ b/apps/electron/.gitignore @@ -0,0 +1,2 @@ +dist +release \ No newline at end of file diff --git a/apps/electron/flake.lock b/apps/electron/flake.lock new file mode 100644 index 0000000..88748a4 --- /dev/null +++ b/apps/electron/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1779508470, + "narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "29916453413845e54a65b8a1cf996842300cd299", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/apps/electron/flake.nix b/apps/electron/flake.nix new file mode 100644 index 0000000..5bb9cd5 --- /dev/null +++ b/apps/electron/flake.nix @@ -0,0 +1,88 @@ +{ + description = "Electron Development Environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = {nixpkgs, flake-utils, ...}: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs {inherit system; config.allowUnfree = true;}; + electronRuntimeLibs = with pkgs; [ + alsa-lib + at-spi2-atk + at-spi2-core + atk + cairo + cups + dbus + expat + fontconfig + freetype + gdk-pixbuf + glib + gtk3 + libdrm + libgbm + libglvnd + libnotify + libpulseaudio + libuuid + libxkbcommon + mesa + nspr + nss + pango + pipewire + systemd + wayland + # xorg + libX11 + libXScrnSaver + libXcomposite + libXcursor + libXdamage + libXext + libXfixes + libXi + libXrandr + libXtst + libxcb + ]; + in { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + nodejs_22 + corepack_22 + bun + electron + pkg-config + python3 + gcc + gnumake + git + jq + patchelf + dpkg + rpm + fpm + ] ++ electronRuntimeLibs; + + shellHook = '' + export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath electronRuntimeLibs}:$LD_LIBRARY_PATH" + export ELECTRON_ENABLE_LOGGING=1 + export ELECTRON_OZONE_PLATFORM_HINT="''${ELECTRON_OZONE_PLATFORM_HINT:-auto}" + export NPM_CONFIG_TARGET_ARCH="''${NPM_CONFIG_TARGET_ARCH:-x64}" + export npm_config_build_from_source=true + export USE_SYSTEM_FPM=true + + alias electron-install='cd ../.. && bun install' + alias electron-build-web='cd ../.. && bun run build:web' + alias electron-dev='bun run dev' + alias electron-package='bun run package:linux' + alias electron-validate='bun run validate' + ''; + }; + }); +} diff --git a/apps/electron/package.json b/apps/electron/package.json new file mode 100644 index 0000000..590d97c --- /dev/null +++ b/apps/electron/package.json @@ -0,0 +1,77 @@ +{ + "name": "@tensamin/electron", + "private": true, + "version": "0.0.3", + "description": "Tensamin desktop client", + "author": "methanium", + "homepage": "https://git.methanium.net/tensamin/client", + "type": "module", + "main": "dist/main/main.js", + "scripts": { + "clean": "rm -rf dist release", + "build:web": "cd ../.. && bun run build:web", + "build": "tsc -p tsconfig.json && esbuild src/preload/preload.ts --bundle --platform=node --format=cjs --external:electron --outfile=dist/preload/preload.cjs", + "dev:raw": "cd ../.. && bun run build:web && cd apps/electron && bun run build && electron . --verbose", + "dev": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop --command bun run dev:raw; else bun run dev:raw; fi", + "start:raw": "bun run build && electron .", + "start": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop --command bun run start:raw; else bun run start:raw; fi", + "package:raw": "cd ../.. && bun run build:web && cd apps/electron && bun run build && electron-builder --publish never", + "package": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop --command bun run package:raw; else bun run package:raw; fi", + "package:linux:raw": "cd ../.. && bun run build:web && cd apps/electron && bun run build && electron-builder --linux --publish never", + "package:linux": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop --command bun run package:linux:raw; else bun run package:linux:raw; fi", + "package:windows:raw": "cd ../.. && bun run build:web && cd apps/electron && bun run build && electron-builder --win --publish never", + "package:windows": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop --command bun run package:windows:raw; else bun run package:windows:raw; fi", + "checksum": "bun scripts/generate-release-metadata.ts", + "generate-signing-key": "bun scripts/generate-signing-key.ts", + "validate:raw": "bun run build && bun run package:linux:raw", + "validate": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop --command bun run validate:raw; else bun run validate:raw; fi" + }, + "dependencies": { + "electron-updater": "^6.6.2" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "electron": "^39.2.7", + "electron-builder": "^26.0.12", + "esbuild": "^0.25.11", + "typescript": "~6.0.3" + }, + "build": { + "appId": "net.tensamin.client", + "productName": "Tensamin", + "executableName": "tensamin", + "artifactName": "Tensamin-${version}-${os}-${arch}.${ext}", + "directories": { + "output": "release" + }, + "files": [ + "dist/**/*", + "package.json" + ], + "extraResources": [ + { + "from": "../web/dist", + "to": "web" + } + ], + "linux": { + "target": ["AppImage", "deb", "rpm"], + "executableName": "tensamin", + "category": "Network", + "maintainer": "Methanium", + "desktop": { + "entry": { + "Name": "Tensamin", + "StartupWMClass": "Tensamin" + } + } + }, + "win": { + "target": ["nsis", "portable"] + }, + "mac": { + "target": ["dmg"] + }, + "publish": null + } +} diff --git a/apps/electron/scripts/generate-release-metadata.ts b/apps/electron/scripts/generate-release-metadata.ts new file mode 100644 index 0000000..36a6281 --- /dev/null +++ b/apps/electron/scripts/generate-release-metadata.ts @@ -0,0 +1,62 @@ +import { createHash } from "node:crypto"; +import { createReadStream, existsSync, readdirSync, statSync, writeFileSync } from "node:fs"; +import { basename, join } from "node:path"; +import rootPackage from "../../../package.json" with { type: "json" }; + +const releaseDir = join(import.meta.dir, "..", "release"); +const outDir = join(import.meta.dir, "..", "..", "..", "releases"); + +function sha256(filePath: string) { + const hash = createHash("sha256"); + const stream = createReadStream(filePath); + + return new Promise((resolve, reject) => { + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("error", reject); + stream.on("end", () => resolve(hash.digest("hex"))); + }); +} + +function platformFor(file: string) { + if (/win|nsis|portable|\.exe$/i.test(file)) return "windows"; + if (/mac|darwin|\.dmg$/i.test(file)) return "macos"; + return "linux"; +} + +function archFor(file: string) { + if (/arm64|aarch64/i.test(file)) return "arm64"; + return "x64"; +} + +if (!existsSync(releaseDir)) { + throw new Error(`Missing Electron release directory: ${releaseDir}`); +} + +const files = readdirSync(releaseDir) + .filter((file) => !file.endsWith(".blockmap") && !file.endsWith(".yml")) + .map((file) => join(releaseDir, file)) + .filter((file) => statSync(file).isFile()); + +const artifacts = await Promise.all( + files.map(async (filePath) => ({ + name: basename(filePath), + platform: platformFor(filePath), + arch: archFor(filePath), + url: `__FORGEJO_RELEASE_ASSET_URL__/${encodeURIComponent(basename(filePath))}`, + sha256: await sha256(filePath), + size: statSync(filePath).size, + })), +); + +const metadata = { + version: rootPackage.version, + tag: rootPackage.version, + publishedAt: new Date().toISOString(), + artifacts, +}; + +writeFileSync(join(outDir, "electron-release-metadata.json"), `${JSON.stringify(metadata, null, 2)}\n`); +writeFileSync( + join(outDir, "SHA256SUMS"), + `${artifacts.map((artifact) => `${artifact.sha256} ${artifact.name}`).join("\n")}\n`, +); diff --git a/apps/electron/scripts/generate-signing-key.ts b/apps/electron/scripts/generate-signing-key.ts new file mode 100644 index 0000000..6ad35ef --- /dev/null +++ b/apps/electron/scripts/generate-signing-key.ts @@ -0,0 +1,11 @@ +import { generateKeyPairSync } from "node:crypto"; + +const { privateKey, publicKey } = generateKeyPairSync("ed25519", { + privateKeyEncoding: { type: "pkcs8", format: "pem" }, + publicKeyEncoding: { type: "spki", format: "pem" }, +}); + +console.log("TENSAMIN_UPDATE_PRIVATE_KEY_PEM="); +console.log(privateKey.trim()); +console.log("\nTENSAMIN_UPDATE_PUBLIC_KEY_PEM="); +console.log(publicKey.trim()); diff --git a/apps/electron/src/main/main.ts b/apps/electron/src/main/main.ts new file mode 100644 index 0000000..a028573 --- /dev/null +++ b/apps/electron/src/main/main.ts @@ -0,0 +1,274 @@ +import { execFile } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { dirname, join, resolve } from "node:path"; +import { app, BrowserWindow, desktopCapturer, ipcMain, session, shell } from "electron"; +import { checkForUpdates } from "./updates.js"; +import { ipcChannels, type DesktopScreenShareCapabilities } from "../shared/ipc.js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const verbose = process.argv.includes("--verbose"); +let mainWindow: BrowserWindow | null = null; +let selectedScreenShareSourceId: string | null = null; + +if (verbose) { + app.commandLine.appendSwitch("enable-logging", "stderr"); + app.commandLine.appendSwitch("v", "1"); + app.commandLine.appendSwitch("log-level", "0"); +} + +if (process.platform === "linux" && process.env.XDG_SESSION_TYPE === "wayland" && !process.env.TENSAMIN_ENABLE_VULKAN) { + app.commandLine.appendSwitch("disable-features", "Vulkan"); +} + +function verboseLog(...args: unknown[]) { + if (verbose) { + console.log("[tensamin:electron]", ...args); + } +} + +function getRendererIndex() { + if (!app.isPackaged) { + return resolve(__dirname, "../../../web/dist/index.html"); + } + + return join(process.resourcesPath, "web", "index.html"); +} + +function getPlatform(): DesktopScreenShareCapabilities["platform"] { + if (process.platform === "linux") return "linux"; + if (process.platform === "darwin") return "macos"; + if (process.platform === "win32") return "windows"; + return "other"; +} + +function getScreenShareCapabilities(): DesktopScreenShareCapabilities { + const platform = getPlatform(); + + return { + runtime: "electron", + platform, + showAudioOutputSelector: platform === "linux", + showAudioSwitch: platform === "windows" || platform === "macos", + hasReliableSystemAudio: platform === "windows", + }; +} + +function execJson(command: string, args: string[]) { + verboseLog("exec", command, args.join(" ")); + + return new Promise((resolvePromise, reject) => { + execFile(command, args, { timeout: 3000 }, (error, stdout, stderr) => { + if (error) { + reject(new Error(stderr.trim() || error.message)); + return; + } + + resolvePromise(JSON.parse(stdout)); + }); + }); +} + +async function listAudioOutputs() { + verboseLog("listAudioOutputs", { platform: process.platform }); + + if (process.platform !== "linux") return []; + + const sinks = await execJson("pactl", ["--format=json", "list", "sinks"]); + if (!Array.isArray(sinks)) return []; + + return sinks + .map((sink) => { + if (!sink || typeof sink !== "object") return null; + const record = sink as Record; + const id = record.index == null ? undefined : String(record.index); + const name = typeof record.description === "string" ? record.description : id; + if (!id || !name) return null; + return { id, name, isDefault: false }; + }) + .filter(Boolean); +} + +async function listScreenShareSources() { + verboseLog("listScreenShareSources"); + + const sources = await desktopCapturer.getSources({ + types: ["screen", "window"], + thumbnailSize: { width: 320, height: 180 }, + fetchWindowIcons: true, + }); + + return sources.map((source) => ({ + id: source.id, + kind: source.id.startsWith("screen:") ? "screen" : "window", + name: source.name, + subtitle: source.id, + thumbnail: source.thumbnail.isEmpty() ? null : source.thumbnail.toDataURL(), + })); +} + +function registerDisplayMediaHandler() { + session.defaultSession.setDisplayMediaRequestHandler(async (_request, callback) => { + verboseLog("display media request", { selectedScreenShareSourceId }); + + const sources = await desktopCapturer.getSources({ + types: ["screen", "window"], + thumbnailSize: { width: 0, height: 0 }, + }); + + const selected = sources.find((source) => source.id === selectedScreenShareSourceId); + selectedScreenShareSourceId = null; + const video = selected ?? sources[0]; + + if (!video) { + callback({}); + return; + } + + if (process.platform === "win32") { + callback({ video, audio: "loopback" }); + return; + } + + callback({ video }); + }); +} + +function registerIpc() { + verboseLog("registering ipc handlers"); + + ipcMain.handle(ipcChannels.listScreenShareSources, listScreenShareSources); + ipcMain.handle(ipcChannels.listScreenShareAudioOutputs, listAudioOutputs); + ipcMain.handle(ipcChannels.getScreenShareCapabilities, getScreenShareCapabilities); + ipcMain.handle(ipcChannels.selectScreenShareSource, (_event, sourceId: unknown) => { + if (typeof sourceId !== "string" || sourceId.length === 0 || sourceId.length > 256) { + throw new Error("Invalid screen share source id."); + } + + selectedScreenShareSourceId = sourceId; + verboseLog("selected screen share source", sourceId); + return true; + }); + ipcMain.handle(ipcChannels.getVersion, () => app.getVersion()); + ipcMain.handle(ipcChannels.checkForUpdates, checkForUpdates); + ipcMain.handle(ipcChannels.minimizeWindow, () => { + verboseLog("window:minimize"); + mainWindow?.minimize(); + }); + ipcMain.handle(ipcChannels.maximizeWindow, () => { + verboseLog("window:maximize"); + if (!mainWindow) return; + + if (mainWindow.isMaximized()) { + mainWindow.unmaximize(); + return; + } + + mainWindow.maximize(); + }); + ipcMain.handle(ipcChannels.closeWindow, () => { + verboseLog("window:close"); + mainWindow?.close(); + }); +} + +async function createWindow() { + const rendererIndex = getRendererIndex(); + verboseLog("creating main window", { + appVersion: app.getVersion(), + electronVersion: process.versions.electron, + chromeVersion: process.versions.chrome, + nodeVersion: process.versions.node, + platform: process.platform, + arch: process.arch, + isPackaged: app.isPackaged, + rendererIndex, + argv: process.argv, + }); + + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 900, + minHeight: 600, + title: "Tensamin", + frame: false, + autoHideMenuBar: true, + webPreferences: { + preload: join(__dirname, "../preload/preload.cjs"), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + webSecurity: true, + }, + }); + mainWindow.setMenuBarVisibility(false); + + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + verboseLog("blocked window open", url); + void shell.openExternal(url); + return { action: "deny" }; + }); + + if (verbose) { + mainWindow.webContents.on("console-message", (_event, level, message, line, sourceId) => { + const target = level >= 2 ? console.error : console.log; + target("[tensamin:renderer]", message, { level, line, sourceId }); + }); + + mainWindow.webContents.on("did-fail-load", (_event, errorCode, errorDescription, validatedURL) => { + console.error("[tensamin:electron] renderer failed to load", { + errorCode, + errorDescription, + validatedURL, + }); + }); + + mainWindow.webContents.on("did-finish-load", () => { + verboseLog("renderer finished loading", mainWindow?.webContents.getURL()); + }); + + mainWindow.webContents.on("render-process-gone", (_event, details) => { + console.error("[tensamin:electron] renderer process gone", details); + }); + + mainWindow.on("unresponsive", () => { + console.error("[tensamin:electron] main window became unresponsive"); + }); + } + + await mainWindow.loadFile(rendererIndex); +} + +app.on("window-all-closed", () => { + verboseLog("window-all-closed"); + if (process.platform !== "darwin") app.quit(); +}); + +app.on("activate", () => { + verboseLog("activate"); + if (BrowserWindow.getAllWindows().length === 0) void createWindow(); +}); + +if (verbose) { + process.on("uncaughtException", (error) => { + console.error("[tensamin:electron] uncaught exception", error); + }); + + process.on("unhandledRejection", (reason) => { + console.error("[tensamin:electron] unhandled rejection", reason); + }); +} + +async function start() { + verboseLog("waiting for app readiness"); + await app.whenReady(); + verboseLog("app ready"); + registerIpc(); + registerDisplayMediaHandler(); + await createWindow(); +} + +void start().catch((error) => { + console.error("[tensamin:electron] failed to start", error); + app.exit(1); +}); diff --git a/apps/electron/src/main/updates.ts b/apps/electron/src/main/updates.ts new file mode 100644 index 0000000..7386118 --- /dev/null +++ b/apps/electron/src/main/updates.ts @@ -0,0 +1,152 @@ +import { createHash } from "node:crypto"; +import { createReadStream } from "node:fs"; +import { mkdir, rm, writeFile } from "node:fs/promises"; +import { basename, join } from "node:path"; +import { app, net } from "electron"; +import type { ReleaseArtifact, ReleaseMetadata, UpdateCheckResult } from "../shared/ipc.js"; + +const metadataUrl = process.env.TENSAMIN_UPDATE_METADATA_URL; + +function compareSemver(left: string, right: string) { + const leftParts = left.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0); + const rightParts = right.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0); + const length = Math.max(leftParts.length, rightParts.length); + + for (let index = 0; index < length; index += 1) { + const diff = (leftParts[index] ?? 0) - (rightParts[index] ?? 0); + if (diff !== 0) return diff; + } + + return 0; +} + +function isDevVersion(version: string) { + return /-dev[.-]/.test(version); +} + +function platformName() { + if (process.platform === "win32") return "windows"; + if (process.platform === "darwin") return "macos"; + if (process.platform === "linux") return "linux"; + return process.platform; +} + +function archName() { + if (process.arch === "x64") return "x64"; + if (process.arch === "arm64") return "arm64"; + return process.arch; +} + +function requestText(url: string): Promise { + return new Promise((resolve, reject) => { + const request = net.request(url); + request.on("response", (response) => { + if (response.statusCode < 200 || response.statusCode >= 300) { + reject(new Error(`Update request failed with HTTP ${response.statusCode}.`)); + return; + } + + const chunks: Buffer[] = []; + response.on("data", (chunk) => chunks.push(Buffer.from(chunk))); + response.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); + }); + request.on("error", reject); + request.end(); + }); +} + +function requestBuffer(url: string): Promise { + return new Promise((resolve, reject) => { + const request = net.request(url); + request.on("response", (response) => { + if (response.statusCode < 200 || response.statusCode >= 300) { + reject(new Error(`Download failed with HTTP ${response.statusCode}.`)); + return; + } + + const chunks: Buffer[] = []; + response.on("data", (chunk) => chunks.push(Buffer.from(chunk))); + response.on("end", () => resolve(Buffer.concat(chunks))); + }); + request.on("error", reject); + request.end(); + }); +} + +async function sha256File(filePath: string) { + const hash = createHash("sha256"); + + await new Promise((resolve, reject) => { + const stream = createReadStream(filePath); + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("error", reject); + stream.on("end", resolve); + }); + + return hash.digest("hex"); +} + +function selectArtifact(metadata: ReleaseMetadata): ReleaseArtifact | undefined { + const platform = platformName(); + const arch = archName(); + + return metadata.artifacts.find( + (artifact) => artifact.platform === platform && artifact.arch === arch, + ); +} + +export async function checkForUpdates(): Promise { + const currentVersion = app.getVersion(); + + if (!metadataUrl) { + return { available: false, currentVersion, latestVersion: currentVersion }; + } + + const metadata = JSON.parse(await requestText(metadataUrl)) as ReleaseMetadata; + const artifact = selectArtifact(metadata); + + if (isDevVersion(metadata.version) !== isDevVersion(currentVersion)) { + return { available: false, currentVersion, latestVersion: metadata.version }; + } + + if (isDevVersion(currentVersion)) { + if (!artifact || metadata.version === currentVersion) { + return { available: false, currentVersion, latestVersion: metadata.version }; + } + + return { + available: true, + currentVersion, + latestVersion: metadata.version, + artifact, + }; + } + + if (!artifact || compareSemver(metadata.version, currentVersion) <= 0) { + return { available: false, currentVersion, latestVersion: metadata.version }; + } + + return { + available: true, + currentVersion, + latestVersion: metadata.version, + artifact, + }; +} + +export async function downloadVerifiedArtifact(artifact: ReleaseArtifact) { + const updatesDir = join(app.getPath("userData"), "updates"); + await rm(updatesDir, { recursive: true, force: true }); + await mkdir(updatesDir, { recursive: true }); + + const destination = join(updatesDir, basename(artifact.name)); + await writeFile(destination, await requestBuffer(artifact.url), { mode: 0o600 }); + + const actualHash = await sha256File(destination); + if (actualHash !== artifact.sha256) { + await rm(destination, { force: true }); + throw new Error("Downloaded update failed checksum verification."); + } + + return destination; +} diff --git a/apps/electron/src/preload/preload.ts b/apps/electron/src/preload/preload.ts new file mode 100644 index 0000000..ce65ca7 --- /dev/null +++ b/apps/electron/src/preload/preload.ts @@ -0,0 +1,43 @@ +import { contextBridge, ipcRenderer } from "electron"; +import { ipcChannels, type DesktopScreenShareSource } from "../shared/ipc.js"; + +function windowAction(channel: string) { + return () => ipcRenderer.invoke(channel); +} + +const desktopApi = { + media: { + listScreenShareSources: () => + ipcRenderer.invoke(ipcChannels.listScreenShareSources), + listScreenShareAudioOutputs: () => + ipcRenderer.invoke(ipcChannels.listScreenShareAudioOutputs), + getScreenShareCapabilities: () => + ipcRenderer.invoke(ipcChannels.getScreenShareCapabilities), + selectScreenShareSource: (sourceId: DesktopScreenShareSource["id"]) => { + if (typeof sourceId !== "string" || sourceId.length === 0) { + return Promise.reject(new Error("Invalid screen share source id.")); + } + + return ipcRenderer.invoke(ipcChannels.selectScreenShareSource, sourceId); + }, + }, + app: { + getVersion: () => ipcRenderer.invoke(ipcChannels.getVersion), + }, + updates: { + checkForUpdates: () => ipcRenderer.invoke(ipcChannels.checkForUpdates), + }, + window: { + minimize: () => windowAction(ipcChannels.minimizeWindow), + maximize: () => windowAction(ipcChannels.maximizeWindow), + close: () => windowAction(ipcChannels.closeWindow), + }, +}; + +contextBridge.exposeInMainWorld("tensaminDesktop", desktopApi); +contextBridge.exposeInMainWorld( + "tensaminShowWindowControls", + process.env.TENSAMIN_HIDE_CONTROLS == null, +); + +export type TensaminDesktopApi = typeof desktopApi; diff --git a/apps/electron/src/shared/ipc.ts b/apps/electron/src/shared/ipc.ts new file mode 100644 index 0000000..d1754f9 --- /dev/null +++ b/apps/electron/src/shared/ipc.ts @@ -0,0 +1,58 @@ +export type DesktopScreenShareSource = { + id: string; + kind: "screen" | "window"; + name: string; + subtitle?: string | null; + thumbnail?: string | null; +}; + +export type DesktopScreenShareAudioOutput = { + id: string; + name: string; + isDefault: boolean; +}; + +export type DesktopScreenShareCapabilities = { + runtime: "electron"; + platform: "linux" | "macos" | "windows" | "other"; + showAudioOutputSelector: boolean; + showAudioSwitch: boolean; + hasReliableSystemAudio: boolean; +}; + +export type ReleaseArtifact = { + name: string; + platform: string; + arch: string; + url: string; + sha256: string; + size: number; +}; + +export type ReleaseMetadata = { + version: string; + tag: string; + publishedAt: string; + artifacts: ReleaseArtifact[]; +}; + +export type UpdateCheckResult = + | { available: false; currentVersion: string; latestVersion: string } + | { + available: true; + currentVersion: string; + latestVersion: string; + artifact: ReleaseArtifact; + }; + +export const ipcChannels = { + listScreenShareSources: "desktopMedia:listScreenShareSources", + listScreenShareAudioOutputs: "desktopMedia:listScreenShareAudioOutputs", + getScreenShareCapabilities: "desktopMedia:getScreenShareCapabilities", + selectScreenShareSource: "desktopMedia:selectScreenShareSource", + minimizeWindow: "window:minimize", + maximizeWindow: "window:maximize", + closeWindow: "window:close", + getVersion: "app:getVersion", + checkForUpdates: "updates:checkForUpdates", +} as const; diff --git a/apps/electron/tsconfig.json b/apps/electron/tsconfig.json new file mode 100644 index 0000000..8d5bda2 --- /dev/null +++ b/apps/electron/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "rootDir": "src", + "outDir": "dist", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "types": ["node", "electron"] + }, + "include": ["src/**/*.ts"] +} diff --git a/apps/tauri/android.png b/apps/tauri/android.png new file mode 100644 index 0000000..f77258e Binary files /dev/null and b/apps/tauri/android.png differ diff --git a/apps/tauri/background.png b/apps/tauri/background.png new file mode 100644 index 0000000..e6157bd Binary files /dev/null and b/apps/tauri/background.png differ diff --git a/apps/tauri/flake.nix b/apps/tauri/flake.nix index 86a1b36..4e491e8 100644 --- a/apps/tauri/flake.nix +++ b/apps/tauri/flake.nix @@ -1,5 +1,5 @@ { - description = "Tauri development environment"; + description = "Tauri mobile development environment"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -53,34 +53,6 @@ ]; }; - desktopRuntimeLibs = with pkgs; [ - glib - gtk3 - gdk-pixbuf - pango - cairo - atk - at-spi2-atk - alsa-lib - dbus - cups - expat - libdrm - libgbm - libglvnd - mesa - libxkbcommon - libx11 - libxcomposite - libxdamage - libxext - libxfixes - libxrandr - libxcb - systemd - nspr - nss - ]; in { devShells.default = pkgs.mkShell { buildInputs = with pkgs; @@ -91,7 +63,6 @@ nodejs pkg-config ] - ++ desktopRuntimeLibs ++ [ android.androidsdk pkgs.android-studio-tools @@ -130,7 +101,6 @@ export NDK_HOME="$ANDROID_NDK_ROOT" export NDK_PATH="$ANDROID_NDK_ROOT" export JAVA_HOME="${pkgs.jdk17}" - export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath desktopRuntimeLibs}:$LD_LIBRARY_PATH" ''; }; } diff --git a/apps/tauri/logo.json b/apps/tauri/logo.json new file mode 100644 index 0000000..9cd2fcb --- /dev/null +++ b/apps/tauri/logo.json @@ -0,0 +1,8 @@ +{ + "default": "./logo.svg", + + "android_fg": "./android.png", + "android_bg": "./background.png", + "android_fg_scale": 100, + "android_monochrome": "./monochrome.png" +} \ No newline at end of file diff --git a/apps/tauri/logo.png b/apps/tauri/logo.png deleted file mode 100644 index 7cb66f6..0000000 Binary files a/apps/tauri/logo.png and /dev/null differ diff --git a/apps/tauri/logo.svg b/apps/tauri/logo.svg new file mode 100644 index 0000000..e40aa0f --- /dev/null +++ b/apps/tauri/logo.svg @@ -0,0 +1,237 @@ + + + + diff --git a/apps/tauri/monochrome.png b/apps/tauri/monochrome.png new file mode 100644 index 0000000..04d3abd Binary files /dev/null and b/apps/tauri/monochrome.png differ diff --git a/apps/tauri/package.json b/apps/tauri/package.json index db9894e..77ba3ea 100644 --- a/apps/tauri/package.json +++ b/apps/tauri/package.json @@ -4,10 +4,6 @@ "version": "0.0.0", "type": "module", "exports": { - "./context": { - "types": "./src/context.tsx", - "default": "./src/context.tsx" - }, "./controls": { "types": "./src/windowControls.tsx", "default": "./src/windowControls.tsx" @@ -25,12 +21,8 @@ "dev:mobile:raw": "tauri android dev", "build:mobile:raw": "tauri android build", "dev:mobile": "if command -v nix >/dev/null 2>&1; then nix develop --command bun dev:mobile:raw; else bun dev:mobile:raw; fi", - "build:mobile": "if command -v nix >/dev/null 2>&1; then nix develop --command bun build:mobile:raw; else bun build:mobile:raw; fi", - "dev:desktop:raw": "tauri dev", - "build:desktop:raw": "tauri build", - "dev:desktop": "if command -v nix >/dev/null 2>&1; then nix develop --command bun dev:desktop:raw; else bun dev:desktop:raw; fi", - "build:desktop": "if command -v nix >/dev/null 2>&1; then nix develop --command bun build:desktop:raw; else bun build:desktop:raw; fi", - "gen-icons": "tauri icon ./logo.png", + "build:mobile": "bun run render-version.ts && if command -v nix >/dev/null 2>&1; then nix develop --command bun build:mobile:raw; else bun build:mobile:raw; fi && bun run render-version.ts --unrender", + "gen-icons": "tauri icon ./logo.json", "format": "bunx prettier --write .", "lint": "eslint src" }, @@ -46,6 +38,7 @@ "react-dom": "^19.2.0" }, "devDependencies": { - "@tauri-apps/cli": "^2" + "@tauri-apps/cli": "^2", + "@types/node": "^25.9.1" } } diff --git a/apps/tauri/readme.md b/apps/tauri/readme.md deleted file mode 100644 index 38cb730..0000000 --- a/apps/tauri/readme.md +++ /dev/null @@ -1,5 +0,0 @@ -# Outputs - -- ./src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk -- ./src-tauri/target/release/bundle/deb/Tensamin*{version}*{arch}.deb -- ./src-tauri/target/release/bundle/rpm/Tensamin-{version}-1.{arch}.rpm diff --git a/apps/tauri/render-version.ts b/apps/tauri/render-version.ts new file mode 100644 index 0000000..0bd3fca --- /dev/null +++ b/apps/tauri/render-version.ts @@ -0,0 +1,65 @@ +import fs from "fs"; +import path from "path"; + +// Config +const PLACEHOLDER_VERSION = "0.0.0"; + +const rootPackageJsonPath = path.resolve(__dirname, "../../package.json"); +const cargoTomlPath = path.resolve(__dirname, "./src-tauri/Cargo.toml"); +const tauriConfigPath = path.resolve(__dirname, "./src-tauri/tauri.conf.json"); + +// Args +const isUnrender = process.argv.includes("--unrender"); + +// Version Source +const packageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, "utf8")); + +const packageVersion: string = packageJson.version; + +if (!packageVersion && !isUnrender) { + throw new Error("No version found in package.json"); +} + +const targetVersion = isUnrender ? PLACEHOLDER_VERSION : packageVersion; + +// Helpers +function updateCargoToml(content: string): string { + const regex = /^version\s*=\s*".*"$/m; + + if (!regex.test(content)) { + throw new Error("Could not find version field in Cargo.toml"); + } + + return content.replace(regex, `version = "${targetVersion}"`); +} + +function updateTauriConfig(content: string): string { + const regex = /"version"\s*:\s*".*"/; + + if (!regex.test(content)) { + throw new Error("Could not find version field in tauri.conf.json"); + } + + return content.replace(regex, `"version": "${targetVersion}"`); +} + +// Update Cargo.toml +const cargoToml = fs.readFileSync(cargoTomlPath, "utf8"); + +const updatedCargoToml = updateCargoToml(cargoToml); + +fs.writeFileSync(cargoTomlPath, updatedCargoToml, "utf8"); + +// Update tauri.conf.json +const tauriConfig = fs.readFileSync(tauriConfigPath, "utf8"); + +const updatedTauriConfig = updateTauriConfig(tauriConfig); + +fs.writeFileSync(tauriConfigPath, updatedTauriConfig, "utf8"); + +// Finished +if (isUnrender) { + console.log(`Unrendered versions back to ${PLACEHOLDER_VERSION}`); +} else { + console.log(`Rendered version ${targetVersion}`); +} diff --git a/apps/tauri/src-tauri/Cargo.lock b/apps/tauri/src-tauri/Cargo.lock index e65d82e..77d465a 100644 --- a/apps/tauri/src-tauri/Cargo.lock +++ b/apps/tauri/src-tauri/Cargo.lock @@ -4838,7 +4838,7 @@ dependencies = [ [[package]] name = "tensamin" -version = "0.1.0" +version = "0.0.0" dependencies = [ "base64 0.22.1", "image 0.25.10", diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml index 90b9504..46ae16e 100644 --- a/apps/tauri/src-tauri/Cargo.toml +++ b/apps/tauri/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tensamin" -version = "0.1.0" +version = "0.0.0" description = "Privacy focused messanger" authors = ["methanium"] edition = "2021" @@ -18,36 +18,16 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -base64 = "0.22" -image = { version = "0.25", default-features = false, features = ["jpeg"] } tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" tauri-plugin-deep-link = "2" -[target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))'.dependencies] -xcap = "0.4.1" - [target.'cfg(target_os = "android")'.dependencies.tauri] version = "2" features = [] default-features = true -[target.'cfg(target_os = "windows")'.dependencies.tauri] -version = "2" -features = ["compression", "common-controls-v6", "dynamic-acl"] -default-features = true - -[target.'cfg(target_os = "linux")'.dependencies.tauri] -version = "2" -features = ["common-controls-v6", "cef", "compression", "dynamic-acl", "x11"] -default-features = false - -[target.'cfg(target_os = "macos")'.dependencies.tauri] -version = "2" -features = ["x11", "common-controls-v6", "cef", "compression", "dynamic-acl"] -default-features = false - [target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] tauri-plugin-barcode-scanner = "2" tauri-plugin-app-events = "0.2" diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 2ffbf24..0343c28 100644 --- a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,6 @@ - + + \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 6de2c44..fbd7184 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..b038821 Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index c6da8ae..0eac8d0 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..b1a2c84 Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index c42517c..7b90201 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 1d61eb2..10832d3 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 0000000..5635294 Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index eceb132..b2a3603 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..b13236e Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index be24593..f5fb52c 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 5aeb1dc..4a07614 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000..16908ba Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index cebe007..ac4b850 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..c929b14 Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 1b7aa3d..7ed0607 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 14abb66..7aee207 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..15cf062 Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index a918033..6ab46b6 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..21f0293 Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 5bd12fa..f596b89 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4583924..1030031 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..0b02649 Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index f480384..f20325b 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..8c3f92e Binary files /dev/null and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 17279d6..d70a938 100644 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/apps/tauri/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/values/strings.xml b/apps/tauri/src-tauri/gen/android/app/src/main/res/values/strings.xml index e9951d1..2551e9e 100644 --- a/apps/tauri/src-tauri/gen/android/app/src/main/res/values/strings.xml +++ b/apps/tauri/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - tensamin - tensamin + Tensamin + Tensamin \ No newline at end of file diff --git a/apps/tauri/src-tauri/icons/128x128.png b/apps/tauri/src-tauri/icons/128x128.png index 2e64037..425ffb4 100644 Binary files a/apps/tauri/src-tauri/icons/128x128.png and b/apps/tauri/src-tauri/icons/128x128.png differ diff --git a/apps/tauri/src-tauri/icons/128x128@2x.png b/apps/tauri/src-tauri/icons/128x128@2x.png index d379bfa..9fe4117 100644 Binary files a/apps/tauri/src-tauri/icons/128x128@2x.png and b/apps/tauri/src-tauri/icons/128x128@2x.png differ diff --git a/apps/tauri/src-tauri/icons/32x32.png b/apps/tauri/src-tauri/icons/32x32.png index c98f4e9..3c9a880 100644 Binary files a/apps/tauri/src-tauri/icons/32x32.png and b/apps/tauri/src-tauri/icons/32x32.png differ diff --git a/apps/tauri/src-tauri/icons/64x64.png b/apps/tauri/src-tauri/icons/64x64.png index e579c32..0f9e690 100644 Binary files a/apps/tauri/src-tauri/icons/64x64.png and b/apps/tauri/src-tauri/icons/64x64.png differ diff --git a/apps/tauri/src-tauri/icons/Square107x107Logo.png b/apps/tauri/src-tauri/icons/Square107x107Logo.png index 32c273f..1ec3cb3 100644 Binary files a/apps/tauri/src-tauri/icons/Square107x107Logo.png and b/apps/tauri/src-tauri/icons/Square107x107Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square142x142Logo.png b/apps/tauri/src-tauri/icons/Square142x142Logo.png index b344198..5b92228 100644 Binary files a/apps/tauri/src-tauri/icons/Square142x142Logo.png and b/apps/tauri/src-tauri/icons/Square142x142Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square150x150Logo.png b/apps/tauri/src-tauri/icons/Square150x150Logo.png index a055b76..e1c1b1b 100644 Binary files a/apps/tauri/src-tauri/icons/Square150x150Logo.png and b/apps/tauri/src-tauri/icons/Square150x150Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square284x284Logo.png b/apps/tauri/src-tauri/icons/Square284x284Logo.png index 322f113..2d33951 100644 Binary files a/apps/tauri/src-tauri/icons/Square284x284Logo.png and b/apps/tauri/src-tauri/icons/Square284x284Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square30x30Logo.png b/apps/tauri/src-tauri/icons/Square30x30Logo.png index 563a719..686bcb7 100644 Binary files a/apps/tauri/src-tauri/icons/Square30x30Logo.png and b/apps/tauri/src-tauri/icons/Square30x30Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square310x310Logo.png b/apps/tauri/src-tauri/icons/Square310x310Logo.png index 2c05099..20207a5 100644 Binary files a/apps/tauri/src-tauri/icons/Square310x310Logo.png and b/apps/tauri/src-tauri/icons/Square310x310Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square44x44Logo.png b/apps/tauri/src-tauri/icons/Square44x44Logo.png index 9e7d8cb..7fcb138 100644 Binary files a/apps/tauri/src-tauri/icons/Square44x44Logo.png and b/apps/tauri/src-tauri/icons/Square44x44Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square71x71Logo.png b/apps/tauri/src-tauri/icons/Square71x71Logo.png index 949270a..6be9a53 100644 Binary files a/apps/tauri/src-tauri/icons/Square71x71Logo.png and b/apps/tauri/src-tauri/icons/Square71x71Logo.png differ diff --git a/apps/tauri/src-tauri/icons/Square89x89Logo.png b/apps/tauri/src-tauri/icons/Square89x89Logo.png index 2e7bf91..84bab9e 100644 Binary files a/apps/tauri/src-tauri/icons/Square89x89Logo.png and b/apps/tauri/src-tauri/icons/Square89x89Logo.png differ diff --git a/apps/tauri/src-tauri/icons/StoreLogo.png b/apps/tauri/src-tauri/icons/StoreLogo.png index 4b82c14..df2463e 100644 Binary files a/apps/tauri/src-tauri/icons/StoreLogo.png and b/apps/tauri/src-tauri/icons/StoreLogo.png differ diff --git a/apps/tauri/src-tauri/icons/icon.icns b/apps/tauri/src-tauri/icons/icon.icns index dbdc5e3..daf2362 100644 Binary files a/apps/tauri/src-tauri/icons/icon.icns and b/apps/tauri/src-tauri/icons/icon.icns differ diff --git a/apps/tauri/src-tauri/icons/icon.ico b/apps/tauri/src-tauri/icons/icon.ico index 7b2783e..fc67bfb 100644 Binary files a/apps/tauri/src-tauri/icons/icon.ico and b/apps/tauri/src-tauri/icons/icon.ico differ diff --git a/apps/tauri/src-tauri/icons/icon.png b/apps/tauri/src-tauri/icons/icon.png index 33853b3..8e56359 100644 Binary files a/apps/tauri/src-tauri/icons/icon.png and b/apps/tauri/src-tauri/icons/icon.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@1x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@1x.png index 294193c..d7880c7 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@1x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x-1.png index 02a5874..a447149 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x.png index 02a5874..a447149 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@3x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@3x.png index 3453672..3524382 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@3x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png index 10af386..5aa9fac 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png index a13e395..36d8549 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x.png index a13e395..36d8549 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@3x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@3x.png index f4f2ccf..4ece183 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@3x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png index 02a5874..a447149 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x-1.png index 5414b1d..8b4aaa8 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png index 5414b1d..8b4aaa8 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png index aa8cbd8..dd1ff43 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-512@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-512@2x.png index 1beaa86..25d9e54 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-512@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png index aa8cbd8..dd1ff43 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png index 17aa19b..e46366a 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@1x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@1x.png index 67faaf8..c732ee4 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@1x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@2x.png index c00d463..52368fe 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/apps/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/apps/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png index f3ca0dd..d328f97 100644 Binary files a/apps/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and b/apps/tauri/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 9eef4be..57b97fb 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -1,204 +1,7 @@ -use serde::Serialize; - -#[derive(Clone, Serialize)] -#[serde(rename_all = "camelCase")] -struct ScreenShareSource { - id: String, - kind: String, - name: String, - subtitle: Option, -} - -#[derive(Clone, Serialize)] -#[serde(rename_all = "camelCase")] -struct ScreenShareAudioOutput { - id: String, - name: String, - is_default: bool, -} - -#[derive(Clone, Serialize)] -#[serde(rename_all = "camelCase")] -struct ScreenShareCapabilities { - platform: String, - show_audio_output_selector: bool, - show_audio_switch: bool, - has_reliable_system_audio: bool, -} - -#[tauri::command] -fn list_screen_share_sources() -> Result, String> { - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - { - use xcap::{Monitor, Window}; - - let mut sources = Vec::new(); - let mut errors = Vec::new(); - - match Monitor::all() { - Ok(monitors) => { - for (index, monitor) in monitors.into_iter().enumerate() { - let mut subtitle = None; - - if monitor.is_primary().unwrap_or(false) { - subtitle = Some("Primary display".to_string()); - } - - sources.push(ScreenShareSource { - id: format!("screen:{index}"), - kind: "screen".to_string(), - name: monitor - .name() - .unwrap_or_else(|_| format!("Display {}", index + 1)), - subtitle, - }); - } - } - Err(error) => errors.push(format!("display listing failed: {error}")), - } - - match Window::all() { - Ok(windows) => { - for (index, window) in windows.into_iter().enumerate() { - if window.is_minimized().unwrap_or(false) { - continue; - } - - let title = window.title().unwrap_or_default(); - - if title.trim().is_empty() { - continue; - } - - sources.push(ScreenShareSource { - id: format!("window:{index}"), - kind: "window".to_string(), - name: title, - subtitle: None, - }); - } - } - Err(error) => errors.push(format!("window listing failed: {error}")), - } - - if sources.is_empty() { - if errors.is_empty() { - return Ok(sources); - } - - return Err(errors.join("; ")); - } - - return Ok(sources); - } - - #[allow(unreachable_code)] - Ok(Vec::new()) -} - -#[tauri::command] -fn list_audio_outputs() -> Result, String> { - #[cfg(target_os = "linux")] - { - use serde_json::Value; - use std::process::Command; - - let output = Command::new("pactl") - .args(["--format=json", "list", "sinks"]) - .output() - .map_err(|error| error.to_string())?; - - if !output.status.success() { - return Err(String::from_utf8_lossy(&output.stderr).trim().to_string()); - } - - let default_sink = Command::new("pactl") - .arg("get-default-sink") - .output() - .ok() - .filter(|result| result.status.success()) - .map(|result| String::from_utf8_lossy(&result.stdout).trim().to_string()); - - let sinks: Value = serde_json::from_slice(&output.stdout).map_err(|error| error.to_string())?; - let sink_entries = sinks - .as_array() - .ok_or_else(|| "Unexpected pactl sink response".to_string())?; - - let mut outputs = Vec::new(); - - for sink in sink_entries { - let Some(index) = sink.get("index").and_then(Value::as_i64) else { - continue; - }; - - let Some(name) = sink.get("name").and_then(Value::as_str) else { - continue; - }; - - let description = sink - .get("description") - .and_then(Value::as_str) - .or_else(|| { - sink.get("properties") - .and_then(|properties| properties.get("device.description")) - .and_then(Value::as_str) - }) - .unwrap_or(name) - .to_string(); - - let is_default = default_sink.as_deref() == Some(name); - - outputs.push(ScreenShareAudioOutput { - id: index.to_string(), - name: description, - is_default, - }); - } - - outputs.sort_by_key(|output| !output.is_default); - - return Ok(outputs); - } - - #[allow(unreachable_code)] - Ok(Vec::new()) -} - -#[tauri::command] -fn get_screen_share_capabilities() -> ScreenShareCapabilities { - ScreenShareCapabilities { - platform: if cfg!(target_os = "linux") { - "linux" - } else if cfg!(target_os = "windows") { - "windows" - } else if cfg!(target_os = "macos") { - "macos" - } else { - "other" - } - .to_string(), - show_audio_output_selector: cfg!(target_os = "linux"), - show_audio_switch: cfg!(any(target_os = "windows", target_os = "macos")), - has_reliable_system_audio: cfg!(target_os = "windows"), - } -} - #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let builder = tauri::Builder::default(); - #[cfg(any(target_os = "linux", target_os = "macos"))] - let builder = builder.command_line_args([ - ("enable-media-stream", None::), - ("enable-usermedia-screen-capturing", None::), - ("allow-http-screen-capture", None::), - #[cfg(target_os = "linux")] - ( - "enable-features", - Some("WebRTCPipeWireCapturer".to_string()), - ), - ]); - let builder = builder .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_opener::init()); @@ -225,11 +28,6 @@ pub fn run() { } Ok(()) }) - .invoke_handler(tauri::generate_handler![ - list_screen_share_sources, - list_audio_outputs, - get_screen_share_capabilities - ]) .run(tauri::generate_context!()) { eprintln!("error while running tauri application: {error}"); diff --git a/apps/tauri/src-tauri/tauri.conf.json b/apps/tauri/src-tauri/tauri.conf.json index c416f81..15e46c0 100644 --- a/apps/tauri/src-tauri/tauri.conf.json +++ b/apps/tauri/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Tensamin", - "version": "0.1.0", + "version": "0.0.0", "mainBinaryName": "tensamin", "identifier": "net.tensamin.client", "build": { @@ -26,7 +26,7 @@ }, "bundle": { "active": true, - "targets": ["app", "deb", "rpm", "nsis", "msi", "dmg"], + "targets": [], "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -37,9 +37,6 @@ }, "plugins": { "deep-link": { - "desktop": { - "schemes": ["tensamin"] - }, "mobile": [ { "scheme": ["tensamin"], diff --git a/apps/tauri/src/deeplinkHandler.tsx b/apps/tauri/src/deeplinkHandler.tsx index 36def6d..1053ea3 100644 --- a/apps/tauri/src/deeplinkHandler.tsx +++ b/apps/tauri/src/deeplinkHandler.tsx @@ -7,6 +7,7 @@ import { } from "react"; import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"; import { isTauri } from "@tauri-apps/api/core"; +import { useIsMobile } from "@tensamin/ui"; type DeeplinkContextValue = { deeplinks: readonly string[]; @@ -31,10 +32,10 @@ export default function DeeplinkProvider({ children: ReactNode; }) { const [deeplinks, setDeeplinks] = useState([]); - const isTauriEnv = isTauri(); + const isMobile = useIsMobile(); useEffect(() => { - if (!isTauriEnv) return; + if (!isTauri() || !isMobile) return; let mounted = true; let unlisten: (() => void) | undefined; @@ -57,7 +58,7 @@ export default function DeeplinkProvider({ mounted = false; unlisten?.(); }; - }, [isTauriEnv]); + }, [isMobile]); return ( - - - {reduceDisplay(props.user.display)} - -
-

{props.user.display}

+
+ + + + {user.display.slice(0, 2).toUpperCase()} + + + + +
+
+ } + /> + + {user.online_status + .split("_") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" ")} + +
+
+

{user.display}

+
+
{extra}
); diff --git a/apps/web/src/components/modals/profile.tsx b/apps/web/src/components/modals/profile.tsx new file mode 100644 index 0000000..6a31e36 --- /dev/null +++ b/apps/web/src/components/modals/profile.tsx @@ -0,0 +1,34 @@ +import type { User } from "@tensamin/user/context"; +import { Avatar, AvatarFallback, AvatarImage } from "@tensamin/ui"; +import Text from "@tensamin/markdown/text"; + +export default function Profile({ user }: { user: User }) { + return ( +
+
+ + + + {user.display.slice(0, 2).toUpperCase()} + + +
+

{user.display}

+

{user.username}

+
+
+ +
+

+ iota: {user.iota_id} +

+

+ user: {user.user_id} +

+

+ pub key: {user.public_key} +

+
+
+ ); +} diff --git a/apps/web/src/components/modals/utils.ts b/apps/web/src/components/modals/utils.ts deleted file mode 100644 index 248dcd5..0000000 --- a/apps/web/src/components/modals/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Executes reduceDisplay. - * @param display Parameter display. - * @returns unknown. - */ -export function reduceDisplay(display: string) { - const words = display.split(" "); - if (words.length === 1) { - return display.slice(0, 2).toUpperCase(); - } else { - return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase(); - } -} diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx index 162a057..a49a318 100644 --- a/apps/web/src/components/navbar.tsx +++ b/apps/web/src/components/navbar.tsx @@ -1,5 +1,13 @@ -import { Button } from "@tensamin/ui"; -import { ArrowLeft, House, Phone, Settings, User } from "lucide-react"; +import { Button, Popover, PopoverContent, PopoverTrigger } from "@tensamin/ui"; +import { + ArrowLeft, + ChevronDown, + ChevronUp, + House, + Phone, + Settings, + User, +} from "lucide-react"; import { useLocation, useNavigate, useSearch } from "@tanstack/react-router"; import { joinCall, useCall } from "@tensamin/call/store"; import Wrapper from "@tensamin/user/wrapper"; @@ -10,6 +18,7 @@ import { useState } from "react"; import { SidebarTrigger, useSidebar } from "@tensamin/ui"; import { WindowControls as Controls } from "@tensamin/ui"; import { useSession } from "@tensamin/storage/session"; +import Profile from "./modals/profile"; export default function Navbar({ forMobile }: { forMobile: boolean }) { const navigate = useNavigate(); @@ -23,10 +32,11 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { const currentCalls = calls.filter((call) => call.call_members.some((member) => member === id), ); - //const currentCalls = ["leck", "schleck", "und", "eck"]; const [selectOpen, setSelectOpen] = useState(false); + const [userInfoOpen, setUserInfoOpen] = useState(false); + return (
( -

{user?.display}

+ <> + + +

{user?.display}

+ {userInfoOpen ? : } + + } + /> + + + +
+ )} loading={} /> @@ -126,7 +156,7 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { )} )} - +
); diff --git a/apps/web/src/components/screens/login/form.tsx b/apps/web/src/components/screens/login/form.tsx index ff76307..e318c4b 100644 --- a/apps/web/src/components/screens/login/form.tsx +++ b/apps/web/src/components/screens/login/form.tsx @@ -1,4 +1,4 @@ -import { Button } from "@tensamin/ui"; +import { Button, cn, useIsMobile } from "@tensamin/ui"; import { Input } from "@tensamin/ui"; import { Label } from "@tensamin/ui"; import { useStorage } from "@tensamin/storage/context"; @@ -83,25 +83,19 @@ function parseTuFileContent(rawFileContent: string): { return { userId, privateKey, domain }; } -/** - * Renders the login form for file upload and manual credential login. - * @returns Login form JSX. - */ export default function Form() { + const isMobile = useIsMobile(); const uploadRef = React.useRef(null); + const [isDragging, setIsDragging] = React.useState(false); const { save } = useStorage(); - /** - * Handles uploaded .tu files and stores resolved credentials. - * @param event Change event from the hidden file input. - * @returns Promise that resolves when processing has finished. - */ - const handleFileInputChange = React.useCallback( - async (event: React.ChangeEvent): Promise => { + // Process dropped files + const processDroppedFile = React.useCallback( + async (file: globalThis.File): Promise => { try { - const file = event.currentTarget.files?.[0]; - if (!file) { - throw new Error("No file selected"); + if (!file.name.endsWith(".tu")) { + toast("error", "Please upload a .tu file"); + return; } const raw = await file.text(); @@ -123,13 +117,94 @@ export default function Form() { [save], ); + // Handle .tu files + const handleFileInputChange = React.useCallback( + async (event: React.ChangeEvent): Promise => { + const file = event.currentTarget.files?.[0]; + + if (!file) { + toast("error", "No file selected"); + return; + } + + await processDroppedFile(file); + }, + [processDroppedFile], + ); + + // Drag and drop listener + React.useEffect(() => { + let dragCounter = 0; + + const handleDragEnter = (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + dragCounter++; + + if (event.dataTransfer?.items?.length) { + setIsDragging(true); + } + }; + + const handleDragLeave = (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + dragCounter--; + + if (dragCounter <= 0) { + setIsDragging(false); + } + }; + + const handleDragOver = (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + if (event.dataTransfer) { + event.dataTransfer.dropEffect = "copy"; + } + }; + + const handleDrop = async (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + dragCounter = 0; + + setIsDragging(false); + + const file = event.dataTransfer?.files?.[0]; + + if (!file) { + toast("error", "No file dropped"); + return; + } + + await processDroppedFile(file); + }; + + window.addEventListener("dragenter", handleDragEnter); + window.addEventListener("dragleave", handleDragLeave); + window.addEventListener("dragover", handleDragOver); + window.addEventListener("drop", handleDrop); + + return () => { + window.removeEventListener("dragenter", handleDragEnter); + window.removeEventListener("dragleave", handleDragLeave); + window.removeEventListener("dragover", handleDragOver); + window.removeEventListener("drop", handleDrop); + }; + }, [processDroppedFile]); + /** * Handles username and private key login submission. * @param event Form submit event. * @returns Promise that resolves after login processing. */ const handleCredentialsSubmit = React.useCallback( - async (event: React.SubmitEvent): Promise => { + async (event: React.FormEvent): Promise => { event.preventDefault(); const formData = new FormData(event.currentTarget); @@ -190,34 +265,35 @@ export default function Form() { [save], ); - const isTauriEnv = isTauri(); - return ( -
- {isTauriEnv ? ( +
+ {isTauri() && isMobile ? ( <> { if (!data.startsWith("tensamin://tu::")) { toast("error", "Invalid QR code"); return; - } else { - const decoded = data.replace("tensamin://tu::", ""); + } - try { - const { userId, privateKey, domain } = - parseTuFileContent(decoded); - await save("session_id", Date.now()); - await save("user_id", userId); - await save("private_key", privateKey); - if (domain) { - await save("ttp_url", `https://${domain}/`); - } - location.href = "/"; - } catch (error) { - log(0, "login", "red", error); - toast("error", "Failed to parse QR code data"); + const decoded = data.replace("tensamin://tu::", ""); + + try { + const { userId, privateKey, domain } = + parseTuFileContent(decoded); + + await save("session_id", Date.now()); + await save("user_id", userId); + await save("private_key", privateKey); + + if (domain) { + await save("ttp_url", `https://${domain}/`); } + + location.href = "/"; + } catch (error) { + log(0, "login", "red", error); + toast("error", "Failed to parse QR code data"); } }} /> @@ -228,10 +304,30 @@ export default function Form() { ) : (
uploadRef.current?.click()} - className="flex flex-col gap-3 cursor-pointer w-55 aspect-square bg-input/13 hover:bg-input/30 transition-all duration-300 ease-in-out border-3 items-center justify-center rounded-lg" + className={cn( + "flex flex-col gap-3 cursor-pointer w-55 aspect-square", + "border-3 items-center justify-center rounded-lg", + "transition-all duration-300 ease-in-out", + "border-input", + "bg-input/13 hover:bg-input/30", + isDragging ? "animate-wiggle" : "", + )} > - -

Select .tu file

+ + +

+ Select .tu file +

)} ; + +const onlineStatusLabels: Record = { + user_online: "Online", + user_offline: "Offline", + user_dnd: "Do not disturb", + user_idle: "Idle", + user_wc: "Away", + user_borked: "Borked", + iota_offline: "Iota offline", + iota_online: "Iota online", + iota_borked: "Iota borked", +}; + +function StatusDialog({ + user, + open, + onOpenChange, + send, + draftStatus, + setDraftStatus, + draftOnlineStatus, + setDraftOnlineStatus, + errorMessage, + setErrorMessage, + saveSucceeded, + setSaveSucceeded, +}: { + user: User; + open: boolean; + onOpenChange: (open: boolean) => void; + send: ReturnType["send"]; + draftStatus: string; + setDraftStatus: (value: string) => void; + draftOnlineStatus: OnlineStatus; + setDraftOnlineStatus: (value: OnlineStatus) => void; + errorMessage: string; + setErrorMessage: (value: string) => void; + saveSucceeded: boolean; + setSaveSucceeded: (value: boolean) => void; +}) { + return ( + { + if (!nextOpen) { + setDraftStatus(user.status ?? ""); + setDraftOnlineStatus(user.online_status); + setErrorMessage(""); + setSaveSucceeded(false); + } + + onOpenChange(nextOpen); + }} + > + + + Update Status + +
+ + { + setSaveSucceeded(false); + setErrorMessage(""); + setDraftStatus(e.target.value); + }} + /> + + +
+ {errorMessage && ( +

{errorMessage}

+ )} + + Cancel} /> + + +
+
+ ); +} -/** - * Renders the conversation sidebar with account summary and conversation list. - * @returns Sidebar JSX. - */ export default function Sidebar() { const isMobile = useIsMobile(); const showMobileNavbar = useShowMobileNavbar(); + const { openMobile, setOpenMobile } = useSidebar(); + const [draftStatus, setDraftStatus] = useState(""); + const [draftOnlineStatus, setDraftOnlineStatus] = + useState("user_online"); + const [dialogOpen, setDialogOpen] = useState(false); + const [statusErrorMessage, setStatusErrorMessage] = useState(""); + const [statusSaveSucceeded, setStatusSaveSucceeded] = useState(false); - return ( - + const { send } = useTTP(); + + const content = ( + <> } userId={"own"} - component={(user) => } + component={(user) => ( + <> + + + + + } + /> + + + Profile + { + setDraftStatus(user.status ?? ""); + setDraftOnlineStatus(user.online_status); + setStatusErrorMessage(""); + setStatusSaveSucceeded(false); + setDialogOpen(true); + }} + > + Set Status + + + + + } + /> + { + if (!nextOpen) { + setDraftStatus(user.status ?? ""); + setDraftOnlineStatus(user.online_status); + setStatusErrorMessage(""); + setStatusSaveSucceeded(false); + } + + setDialogOpen(nextOpen); + }} + send={send} + draftStatus={draftStatus} + setDraftStatus={setDraftStatus} + draftOnlineStatus={draftOnlineStatus} + setDraftOnlineStatus={setDraftOnlineStatus} + errorMessage={statusErrorMessage} + setErrorMessage={setStatusErrorMessage} + saveSucceeded={statusSaveSucceeded} + setSaveSucceeded={setStatusSaveSucceeded} + /> + + )} />
@@ -52,6 +287,34 @@ export default function Sidebar() { )} - + ); + + if (isMobile) { + return ( + <> + {openMobile && ( +
setOpenMobile(false)} + /> + )} +
+
{content}
+
+ + ); + } + + return {content}; } diff --git a/apps/web/src/features/conversation/list/body.tsx b/apps/web/src/features/conversation/list/body.tsx index 7092b6f..d240655 100644 --- a/apps/web/src/features/conversation/list/body.tsx +++ b/apps/web/src/features/conversation/list/body.tsx @@ -76,6 +76,16 @@ export default function List() {
); })} +
diff --git a/apps/web/src/features/settings/components.tsx b/apps/web/src/features/settings/components.tsx index ff1d7f1..7b4157b 100644 --- a/apps/web/src/features/settings/components.tsx +++ b/apps/web/src/features/settings/components.tsx @@ -36,7 +36,7 @@ export function Switch({ label, id, }: { - label: string; + label: React.ReactNode; id: keyof typeof settingsStorageDefaults & BooleanStorageKey; }) { const { save, load } = useStorage(); diff --git a/apps/web/src/index.css b/apps/web/src/index.css index d243481..8175c9c 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -6,6 +6,20 @@ @source "./**/*.{ts,tsx}"; @source "../../../packages/**/src/**/*.{ts,tsx}"; +@theme { + --animate-wiggle: wiggle 0.5s ease-in-out infinite; + + @keyframes wiggle { + 0%, + 100% { + transform: rotate(-2deg); + } + 50% { + transform: rotate(2deg); + } + } +} + html, body, #root { @@ -17,3 +31,32 @@ body, #root { min-height: 0; } + +@layer base { + * { + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; + } + + ::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + ::-webkit-scrollbar-track { + background: transparent; + } + + ::-webkit-scrollbar-thumb { + background-color: var(--border); + border-radius: 9999px; + } + + ::-webkit-scrollbar-thumb:hover { + opacity: 0.8; + } + + ::-webkit-scrollbar-corner { + background: transparent; + } +} diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index 0dfffed..7ea7afc 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -4,6 +4,7 @@ import { RouterProvider, createRootRoute, createRoute, + createHashHistory, createRouter, } from "@tanstack/react-router"; @@ -37,7 +38,7 @@ import { useEffect, useState, type ReactNode } from "react"; import Storage from "@tensamin/storage/context"; import Session from "@tensamin/storage/session"; import Crypto from "@tensamin/crypto/context"; -import Mobile from "@tensamin/tauri/context"; +import DesktopMediaProvider from "@tensamin/shared/desktopMedia"; import LegalWrapper from "@/features/legal/screen"; @@ -126,9 +127,9 @@ function RootShell() { - + - + @@ -271,6 +272,8 @@ const routeTree = rootRoute.addChildren([ const router = createRouter({ routeTree, + history: + window.location.protocol === "file:" ? createHashHistory() : undefined, defaultNotFoundComponent: NotFound, }); diff --git a/apps/web/src/routes/app/home.tsx b/apps/web/src/routes/app/home.tsx index 90f6a26..3e2c477 100644 --- a/apps/web/src/routes/app/home.tsx +++ b/apps/web/src/routes/app/home.tsx @@ -11,12 +11,12 @@ import { useIsMobile, } from "@tensamin/ui"; import z from "zod"; -import { toast } from "@tensamin/shared/log"; import { useTTP } from "@tensamin/ttp"; import { useState } from "react"; import { Loader2 } from "lucide-react"; import { isTauri } from "@tauri-apps/api/core"; import { useStorage } from "@tensamin/storage/context"; +import { useSession } from "@tensamin/storage/session"; // The page export default function Page() { @@ -43,11 +43,16 @@ export default function Page() { // Add Conversation Button Component function AddConversationButton() { const { send } = useTTP(); + const { contacts, insertContact } = useSession(); const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [error, setError] = useState(null); - function submit(username: string | null) { + async function submit(username: string | null) { if (loading) return; + setError(null); + // username check const schema = z .string() .min(1, "Username is too short") @@ -56,26 +61,50 @@ function AddConversationButton() { const result = schema.safeParse(username?.toLowerCase().trim()); if (!result.success) { - toast("error", result.error.issues[0].message); + setError(result.error.issues[0].message); return; } + // user existence check + const user = await send("get_user_data", { + username: result.data, + }) + .then((data) => { + if (data.data.user_id === 0) { + throw new Error(); + } + + return data; + }) + .catch(() => { + setError("User not found"); + return; + }); + if (!user) return; + + // alrady added check + if (contacts.some((contact) => contact.user_id === user.data.user_id)) { + setError("Conversation already exists"); + return; + } + + // add the conv const timeout = setTimeout(() => setLoading(true), 500); send("add_conversation", { chat_partner_name: result.data, }) .then(() => { - toast("success", "Conversation added"); + insertContact(user.data.user_id); + setOpen(false); }) .catch((error) => { if (String(error).includes("error_not_found")) { - toast("error", "User not found"); + setError("User not found"); return; } - toast("error", "Failed to add conversation, check console for details"); - console.error("Failed to add conversation", error); + setError(String(error)); }) .finally(() => { clearTimeout(timeout); @@ -84,7 +113,15 @@ function AddConversationButton() { } return ( - + { + if (value) { + setError(null); + } + setOpen(value); + }} + > Add Conversation} /> @@ -110,7 +147,10 @@ function AddConversationButton() { name="username" placeholder="Enter username..." /> -
+
+ {error && ( +

{error}

+ )} Cancel} /> + +
+

+ GIFs are supported in decentralised mode or with Tensamin Premium. +
+ Maximum file size is 16mb. +

+
+ + + updateDraftUser((prev) => ({ + ...prev, + display: event.target.value, + })) + } + placeholder="Display Name" + value={draftUser.display || ""} + /> + + updateDraftUser((prev) => ({ + ...prev, + username: event.target.value, + })) + } + placeholder="Username" + value={draftUser.username || ""} + /> + + updateDraftUser((prev) => ({ ...prev, about: value })) + } + value={draftUser.about || ""} + /> + + {errorMessage && ( +

{errorMessage}

+ )} + + + ) : ( +

Loading...

+ ); } diff --git a/apps/web/src/routes/settings/security.tsx b/apps/web/src/routes/settings/security.tsx index 15a6155..86c580e 100644 --- a/apps/web/src/routes/settings/security.tsx +++ b/apps/web/src/routes/settings/security.tsx @@ -46,6 +46,7 @@ function QrCodeLogin() { const [qrCodeBase64, setQrCodeBase64] = useState( undefined, ); + const [connectionString, setConnectionString] = useState(null); useEffect(() => { load("private_key").then((value) => { @@ -58,15 +59,25 @@ function QrCodeLogin() { setUserId(value); } }); + load("ttp_url") + .then((value) => { + if (value) { + const url = new URL(value); + setConnectionString(`@${url.host}`); + } else { + setConnectionString(""); + } + }) + .catch(() => setConnectionString("")); }, [load]); useEffect(() => { - if (userId && privateKey) { - generateQR(`tensamin://tu::${userId}::${privateKey}`).then( - setQrCodeBase64, - ); + if (userId && privateKey && connectionString !== null) { + generateQR( + `tensamin://tu::${userId}${connectionString}::${privateKey}`, + ).then(setQrCodeBase64); } - }, [userId, privateKey]); + }, [userId, privateKey, connectionString]); const [qrCodeVisible, setQrCodeVisible] = useState(false); diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 92f0d91..f9d51eb 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -51,6 +51,7 @@ function deepFilterAssetHeaders(rootDir: string): Plugin { } export default defineConfig({ + base: "./", clearScreen: false, resolve: { tsconfigPaths: true, diff --git a/bun.lock b/bun.lock index 325be20..f3f63a0 100644 --- a/bun.lock +++ b/bun.lock @@ -24,6 +24,20 @@ "typescript-eslint": "^8.59.1", }, }, + "apps/electron": { + "name": "@tensamin/electron", + "version": "0.0.3", + "dependencies": { + "electron-updater": "^6.6.2", + }, + "devDependencies": { + "@types/node": "^25.9.1", + "electron": "^39.2.7", + "electron-builder": "^26.0.12", + "esbuild": "^0.25.11", + "typescript": "~6.0.3", + }, + }, "apps/tauri": { "name": "@tensamin/tauri", "version": "0.0.0", @@ -40,6 +54,7 @@ }, "devDependencies": { "@tauri-apps/cli": "^2", + "@types/node": "^25.9.1", }, }, "apps/web": { @@ -55,6 +70,7 @@ "@tensamin/call": "workspace:*", "@tensamin/chat": "workspace:*", "@tensamin/crypto": "workspace:*", + "@tensamin/markdown": "workspace:*", "@tensamin/notifications": "workspace:*", "@tensamin/shared": "workspace:*", "@tensamin/storage": "workspace:*", @@ -169,6 +185,7 @@ "name": "@tensamin/notifications", "version": "0.0.0", "dependencies": { + "@tanstack/react-router": "^1.169.1", "@tauri-apps/api": "^2.11.0", "@tensamin/chat": "workspace:*", "@tensamin/crypto": "workspace:*", @@ -258,10 +275,12 @@ }, }, "overrides": { - "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz", - "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.34.tar.gz", + "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.19.tar.gz", + "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.36.tar.gz", }, "packages": { + "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="], + "@antfu/ni": ["@antfu/ni@25.0.0", "", { "dependencies": { "ansis": "^4.0.0", "fzf": "^0.5.2", "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" }, "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs", "nup": "bin/nup.mjs" } }, "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA=="], "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], @@ -350,10 +369,28 @@ "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], + "@develar/schema-utils": ["@develar/schema-utils@2.6.5", "", { "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } }, "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig=="], + "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.64.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-6+xRpZaWuHXEqnhBjae+VmQI9Uaqw5Uzu/ScpO+W7ww9Zp3lHSNBoNjFcUxhrCyc7pRGQzyDjhKzloqrPHERiQ=="], "@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="], + "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], + + "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="], + + "@electron/get": ["@electron/get@2.0.3", "", { "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-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], + + "@electron/notarize": ["@electron/notarize@2.5.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" } }, "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A=="], + + "@electron/osx-sign": ["@electron/osx-sign@1.3.3", "", { "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" } }, "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg=="], + + "@electron/rebuild": ["@electron/rebuild@4.0.4", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^12.2.0", "read-binary-file-arch": "^1.0.6" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg=="], + + "@electron/universal": ["@electron/universal@2.0.3", "", { "dependencies": { "@electron/asar": "^3.3.1", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" } }, "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g=="], + + "@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=="], + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -460,6 +497,8 @@ "@inquirer/type": ["@inquirer/type@4.0.5", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q=="], + "@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=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -492,6 +531,10 @@ "@livekit/protocol": ["@livekit/protocol@1.45.3", "", { "dependencies": { "@bufbuild/protobuf": "^1.10.0" } }, "sha512-WmMxBTsy4dRBqcrswFwUUlgq3Z0nnhOqKR6tX749Rb/PcB1yBMUtrHxZvcsS6qi3/5+86zHeVG+exmu1sZqfJg=="], + "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], + + "@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=="], + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], @@ -590,12 +633,16 @@ "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], + "@tabby_ai/hijri-converter": ["@tabby_ai/hijri-converter@1.0.5", "", {}, "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], @@ -684,6 +731,8 @@ "@tensamin/crypto": ["@tensamin/crypto@workspace:packages/crypto"], + "@tensamin/electron": ["@tensamin/electron@workspace:apps/electron"], + "@tensamin/markdown": ["@tensamin/markdown@workspace:packages/markdown"], "@tensamin/notifications": ["@tensamin/notifications@workspace:packages/notifications"], @@ -698,9 +747,9 @@ "@tensamin/ttp": ["@tensamin/ttp@workspace:packages/ttp"], - "@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.59.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.3", "typescript-eslint": "^8.59.1", "zod": "^4.4.1" } }, "sha512-smyx+04hSnWM1oyWJfJrRWmxu7OInwyHeIlaD1h3tUrkrSxiKWHl1qCmxVO1bC+cDwyUXhJ5HUnNCbx1aWx7Dg=="], + "@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.19.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.59.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.3", "typescript-eslint": "^8.59.1", "zod": "^4.4.1" } }, "sha512-La9VqXqJFtzzsRQotXVp+3Vr6u8kj4mQ4wTlSIMRDxKFBnbCvtZyB3V/f8HiIlf7FlZ0Xg0suUUpnamvtcvs9w=="], - "@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.34.tar.gz", { "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/inter": "^5.2.6", "@tauri-apps/api": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^1.8.0", "next-themes": "^0.4.6", "react-day-picker": "^9.14.0", "react-resizable-panels": "^4.10.0", "recharts": "3.8.0", "shadcn": "^3.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.3.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-hp7rV0a0gfD/rNw9et+PqM1PPkgFC6/Z7eYfza6NIp/m+a8/r5Um+S/8tzBDCgZmQC9Y1sJsjesH7mXZz6Jmuw=="], + "@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.36.tar.gz", { "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/inter": "^5.2.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^1.8.0", "next-themes": "^0.4.6", "react-day-picker": "^9.14.0", "react-resizable-panels": "^4.10.0", "recharts": "3.8.0", "shadcn": "^3.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.3.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-5zql8LtBKVn7WuQDCIdLbthKvLEyxjWShAceXCYbk+kwO41VVILhS1EWuDvjgvv2BBdyuxoVWvdblPIW4BcdSQ=="], "@tensamin/user": ["@tensamin/user@workspace:packages/user"], @@ -710,6 +759,8 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], @@ -728,22 +779,36 @@ "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + "@types/dom-mediacapture-record": ["@types/dom-mediacapture-record@1.0.22", "", {}, "sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw=="], "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@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=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + "@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="], + "@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="], "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], + "@types/set-cookie-parser": ["@types/set-cookie-parser@2.4.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw=="], "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], @@ -752,6 +817,10 @@ "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], + "@types/verror": ["@types/verror@1.10.11", "", {}, "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/type-utils": "8.59.1", "@typescript-eslint/utils": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA=="], @@ -776,6 +845,10 @@ "@webtransport-bun/webtransport": ["@webtransport-bun/webtransport@0.3.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-/OX/TCgBD64n/0BjMNytObq5NK2pAM0KOoXBEw49l3sTmbVXNTPP1ZRPIdIyV43agM+mKtZBaV3Er3k3YoB6sw=="], + "@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="], + + "abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], @@ -788,34 +861,70 @@ "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], + "app-builder-bin": ["app-builder-bin@5.0.0-alpha.12", "", {}, "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w=="], + + "app-builder-lib": ["app-builder-lib@26.8.1", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA=="], "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "builder-util": ["builder-util@26.8.1", "", { "dependencies": { "7zip-bin": "~5.2.0", "@types/debug": "^4.1.6", "app-builder-bin": "5.0.0-alpha.12", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw=="], + + "builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="], + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], + + "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], @@ -826,7 +935,13 @@ "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], @@ -834,9 +949,13 @@ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + "cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="], + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], - "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], @@ -848,10 +967,16 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "comlink": ["comlink@4.4.2", "", {}, "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g=="], "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -864,12 +989,18 @@ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], + "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], "cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], + "crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="], + "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + "cross-dirname": ["cross-dirname@0.1.0", "", {}, "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], @@ -910,6 +1041,8 @@ "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], @@ -922,19 +1055,37 @@ "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], + "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], "dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="], - "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="], + + "dmg-builder": ["dmg-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg=="], + + "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="], + + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -942,8 +1093,22 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], + + "electron": ["electron@39.8.10", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-zbYtGPYUI7PzqLAzkk21Rk6j67WN0hxn0Mq/njErZo1d0HSf33is4f8ICI5fMLy5vYe0JtCtM5sYunNOaochSQ=="], + + "electron-builder": ["electron-builder@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "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-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw=="], + + "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.8.1", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "electron-winstaller": "5.4.0" } }, "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA=="], + + "electron-publish": ["electron-publish@26.8.1", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w=="], + "electron-to-chromium": ["electron-to-chromium@1.5.349", "", {}, "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A=="], + "electron-updater": ["electron-updater@6.8.3", "", { "dependencies": { "builder-util-runtime": "9.5.1", "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-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ=="], + + "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=="], + "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="], "embla-carousel-react": ["embla-carousel-react@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA=="], @@ -954,10 +1119,14 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -966,8 +1135,12 @@ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "es-toolkit": ["es-toolkit@1.46.1", "", {}, "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + "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=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -1008,10 +1181,16 @@ "execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="], + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], "express-rate-limit": ["express-rate-limit@8.4.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw=="], + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], @@ -1030,6 +1209,8 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], @@ -1038,6 +1219,8 @@ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], @@ -1048,6 +1231,8 @@ "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], @@ -1056,7 +1241,9 @@ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], - "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + "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=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -1080,20 +1267,34 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + "globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="], + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graphql": ["graphql@16.13.2", "", {}, "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], "headers-polyfill": ["headers-polyfill@5.0.1", "", { "dependencies": { "@types/set-cookie-parser": "^2.4.10", "set-cookie-parser": "^3.0.1" } }, "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA=="], @@ -1104,13 +1305,25 @@ "hono": ["hono@4.12.16", "", {}, "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg=="], + "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], - "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -1120,6 +1333,8 @@ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], @@ -1164,10 +1379,14 @@ "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="], + "isbot": ["isbot@5.1.39", "", {}, "sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], @@ -1188,6 +1407,8 @@ "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], @@ -1198,6 +1419,8 @@ "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "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=="], @@ -1230,18 +1453,28 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], + + "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], + "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], + "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lucide-react": ["lucide-react@1.14.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], @@ -1254,6 +1487,8 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], @@ -1262,10 +1497,18 @@ "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], + "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], @@ -1284,18 +1527,32 @@ "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], + "node-abi": ["node-abi@4.31.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw=="], + + "node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], + + "node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "node-gyp": ["node-gyp@12.3.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg=="], + "node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], + "nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], + + "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + "object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -1312,6 +1569,8 @@ "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], @@ -1332,22 +1591,32 @@ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + "pe-library": ["pe-library@0.4.1", "", {}, "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], + "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], "postcss": ["postcss@8.5.13", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag=="], "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + "postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="], + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], @@ -1356,10 +1625,20 @@ "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], + + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], @@ -1368,6 +1647,8 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], @@ -1390,6 +1671,8 @@ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], + "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], "recharts": ["recharts@3.8.1", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg=="], @@ -1404,16 +1687,28 @@ "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], + "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], + "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + "rettime": ["rettime@0.11.8", "", {}, "sha512-0fERGXktJTyJ+h8fBEiPxHPEFOu0h15JY7JtwrOVqR5K+vb99ho6IyOo7ekLS3h4sJCzIDy4VWKIbZUfe9njmg=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="], + + "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=="], + "rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="], "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], @@ -1426,6 +1721,10 @@ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="], + + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "sdp": ["sdp@3.2.2", "", {}, "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA=="], @@ -1434,8 +1733,12 @@ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], + "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + "seroval": ["seroval@1.5.2", "", {}, "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q=="], "seroval-plugins": ["seroval-plugins@1.5.2", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg=="], @@ -1464,14 +1767,26 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + "slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + + "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], @@ -1490,6 +1805,10 @@ "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], + "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], @@ -1500,10 +1819,20 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + "tar": ["tar@7.5.15", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="], + "tauri-plugin-app-events-api": ["tauri-plugin-app-events-api@0.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.3" } }, "sha512-CnlAeucWhT+Rgx6CCsqUpq2D3pqgx0fhjSPKomH7O2t1rGVu5kGLMkYvaCX6gqVTUYpIWX5BLV7LGY2DRri0QA=="], + "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="], + + "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="], + + "tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + "tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="], + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], @@ -1512,12 +1841,18 @@ "tldts-core": ["tldts-core@7.0.30", "", {}, "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q=="], + "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + + "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="], + "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], "ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="], @@ -1540,6 +1875,8 @@ "typescript-eslint": ["typescript-eslint@8.59.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.1", "@typescript-eslint/parser": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ=="], + "undici": ["undici@6.25.0", "", {}, "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], @@ -1562,6 +1899,8 @@ "usehooks-ts": ["usehooks-ts@3.1.1", "", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="], + "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], @@ -1570,6 +1909,8 @@ "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="], + "verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="], + "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], "vite": ["vite@8.0.10", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "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-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="], @@ -1586,19 +1927,23 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="], - "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], + "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], @@ -1622,16 +1967,40 @@ "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + "@dotenvx/dotenvx/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], "@dotenvx/dotenvx/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], + + "@electron/asar/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "@electron/fuses/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "@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/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], + + "@electron/universal/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + + "@electron/universal/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "@electron/windows-sign/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], "@livekit/components-core/loglevel": ["loglevel@1.9.1", "", {}, "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg=="], + "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + "@modelcontextprotocol/sdk/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], "@mswjs/interceptors/@open-draft/deferred-promise": ["@open-draft/deferred-promise@2.2.0", "", {}, "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA=="], @@ -1660,49 +2029,109 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tensamin/electron/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@tensamin/tauri/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + "@tensamin/ui/recharts": ["recharts@3.8.0", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ=="], "@tensamin/ui/shadcn": ["shadcn@3.8.5", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA=="], + "@types/cacheable-request/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@types/fs-extra/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@types/keyv/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@types/plist/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@types/responselike/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "@types/yauzl/@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "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=="], + + "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], + + "app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + + "body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], + + "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "eciesjs/@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], "eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "electron/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], + + "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=="], + + "execa/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + + "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "log-symbols/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], - "msw/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + "node-gyp/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + "ora/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "ora/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=="], + "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + + "raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="], "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], + "serialize-error/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + + "shadcn/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + "shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], - "yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], @@ -1718,36 +2147,102 @@ "@dotenvx/dotenvx/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "@electron/asar/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + + "@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "@electron/universal/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "@tensamin/electron/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tensamin/tauri/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@tensamin/ui/shadcn/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + "@tensamin/ui/shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@types/cacheable-request/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@types/fs-extra/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@types/keyv/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@types/plist/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@types/responselike/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "@types/yauzl/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "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=="], + + "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "msw/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], - "msw/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "electron-winstaller/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - "msw/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "electron-winstaller/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "electron/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "filelist/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], + + "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + + "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "node-gyp/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + + "qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "qrcode/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], + + "qrcode/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + "@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "msw/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "msw/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "app-builder-lib/@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - "yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "msw/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "filelist/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + + "qrcode/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "qrcode/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], } } diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ea7bf8c --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1779508470, + "narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "29916453413845e54a65b8a1cf996842300cd299", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..0ee94f1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,40 @@ +{ + description = "Tensamin desktop client"; + + inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; + + outputs = {nixpkgs, ...}: let + systems = ["x86_64-linux" "aarch64-linux"]; + forAllSystems = nixpkgs.lib.genAttrs systems; + version = "0.0.3"; + forgejoBaseUrl = "https://git.methanium.net/tensamin/client/releases/download/${version}"; + in { + packages = forAllSystems (system: let + pkgs = import nixpkgs {inherit system;}; + artifact = if system == "aarch64-linux" then "Tensamin-${version}-linux-arm64.AppImage" else "Tensamin-${version}-linux-x86_64.AppImage"; + appImagePath = /. + builtins.getEnv "TENSAMIN_APPIMAGE"; + desktopItem = pkgs.makeDesktopItem { + name = "tensamin"; + desktopName = "Tensamin"; + exec = "tensamin %U"; + icon = "tensamin"; + categories = ["Network"]; + }; + wrapAppImage = src: + pkgs.appimageTools.wrapType2 { + pname = "tensamin"; + inherit version src; + extraInstallCommands = '' + install -Dm444 ${desktopItem}/share/applications/tensamin.desktop $out/share/applications/tensamin.desktop + ''; + }; + in { + default = wrapAppImage (pkgs.fetchurl { + url = "${forgejoBaseUrl}/${artifact}"; + hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }); + + localPathForDev = wrapAppImage appImagePath; + }); + }; +} diff --git a/licenses/@tensamin_ttp-core@0.0.15/LICENSE b/licenses/@tensamin_ttp-core@0.0.19/LICENSE similarity index 100% rename from licenses/@tensamin_ttp-core@0.0.15/LICENSE rename to licenses/@tensamin_ttp-core@0.0.19/LICENSE diff --git a/licenses/THIRD_PARTY_NOTICES.md b/licenses/THIRD_PARTY_NOTICES.md index 51009a7..2dcc4e3 100644 --- a/licenses/THIRD_PARTY_NOTICES.md +++ b/licenses/THIRD_PARTY_NOTICES.md @@ -163,18 +163,18 @@ Generated from bun.lock and installed packages in workspace node_modules folders - Folder: `licenses/@tauri-apps_plugin-opener@2.5.4` - Source package dir: `apps/tauri/node_modules/@tauri-apps/plugin-opener` -## @tensamin/ttp-core@0.0.15 +## @tensamin/ttp-core@0.0.19 - License: UNKNOWN - Included files: LICENSE -- Folder: `licenses/@tensamin_ttp-core@0.0.15` +- Folder: `licenses/@tensamin_ttp-core@0.0.19` - Source package dir: `packages/ttp/node_modules/@tensamin/ttp-core` -## @tensamin/ui@0.0.30 +## @tensamin/ui@0.0.34 - License: UNKNOWN - Included files: none found -- Folder: `licenses/@tensamin_ui@0.0.30` +- Folder: `licenses/@tensamin_ui@0.0.34` - Source package dir: `apps/tauri/node_modules/@tensamin/ui` ## @types/node@25.6.0 @@ -449,6 +449,16 @@ Generated from bun.lock and installed packages in workspace node_modules folders - Folder: `licenses/tailwindcss@4.2.4` - Source package dir: `apps/web/node_modules/tailwindcss` +## tauri-plugin-app-events-api@0.2.0 + +- License: MIT +- Homepage: https://github.com/wtto00/tauri-plugin-app-events#readme +- Repository: git+https://github.com/wtto00/tauri-plugin-app-events.git +- Description: A plugin for tauri@v2 to listen some events on iOS and Android. +- Included files: LICENSE +- Folder: `licenses/tauri-plugin-app-events-api@0.2.0` +- Source package dir: `apps/web/node_modules/tauri-plugin-app-events-api` + ## tw-animate-css@1.4.0 - License: MIT diff --git a/licenses/sbom.cyclonedx.json b/licenses/sbom.cyclonedx.json index 6e10a91..4d1284c 100644 --- a/licenses/sbom.cyclonedx.json +++ b/licenses/sbom.cyclonedx.json @@ -3,7 +3,7 @@ "specVersion": "1.5", "version": 1, "metadata": { - "timestamp": "2026-05-04T13:05:17.472Z", + "timestamp": "2026-05-21T10:15:15.541Z", "tools": [ { "vendor": "OpenAI", @@ -580,15 +580,15 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.15", + "bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.19", "name": "@tensamin/ttp-core", - "version": "0.0.15", - "purl": "pkg:npm/%40tensamin/ttp-core@0.0.15", + "version": "0.0.19", + "purl": "pkg:npm/%40tensamin/ttp-core@0.0.19", "externalReferences": [], "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tensamin_ttp-core@0.0.15" + "value": "licenses/@tensamin_ttp-core@0.0.19" }, { "name": "local:sourcePackageDir", @@ -598,15 +598,15 @@ }, { "type": "library", - "bomRef": "pkg:npm/%40tensamin/ui@0.0.30", + "bomRef": "pkg:npm/%40tensamin/ui@0.0.34", "name": "@tensamin/ui", - "version": "0.0.30", - "purl": "pkg:npm/%40tensamin/ui@0.0.30", + "version": "0.0.34", + "purl": "pkg:npm/%40tensamin/ui@0.0.34", "externalReferences": [], "properties": [ { "name": "local:licenseFolder", - "value": "licenses/@tensamin_ui@0.0.30" + "value": "licenses/@tensamin_ui@0.0.34" }, { "name": "local:sourcePackageDir", @@ -1562,6 +1562,41 @@ } ] }, + { + "type": "library", + "bomRef": "pkg:npm/tauri-plugin-app-events-api@0.2.0", + "name": "tauri-plugin-app-events-api", + "version": "0.2.0", + "purl": "pkg:npm/tauri-plugin-app-events-api@0.2.0", + "description": "A plugin for tauri@v2 to listen some events on iOS and Android.", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/wtto00/tauri-plugin-app-events#readme" + }, + { + "type": "vcs", + "url": "git+https://github.com/wtto00/tauri-plugin-app-events.git" + } + ], + "properties": [ + { + "name": "local:licenseFolder", + "value": "licenses/tauri-plugin-app-events-api@0.2.0" + }, + { + "name": "local:sourcePackageDir", + "value": "apps/web/node_modules/tauri-plugin-app-events-api" + } + ] + }, { "type": "library", "bomRef": "pkg:npm/tw-animate-css@1.4.0", diff --git a/licenses/tauri-plugin-app-events-api@0.2.0/LICENSE b/licenses/tauri-plugin-app-events-api@0.2.0/LICENSE new file mode 100644 index 0000000..637004f --- /dev/null +++ b/licenses/tauri-plugin-app-events-api@0.2.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 简静凡 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/licenses/third-party-credits.json b/licenses/third-party-credits.json index a1d7968..bf9eab8 100644 --- a/licenses/third-party-credits.json +++ b/licenses/third-party-credits.json @@ -1,6 +1,6 @@ { - "generatedAt": "2026-05-04T13:05:17.471Z", - "packageCount": 53, + "generatedAt": "2026-05-21T10:15:15.540Z", + "packageCount": 54, "packages": [ { "name": "@codemirror/commands", @@ -227,7 +227,7 @@ }, { "name": "@tensamin/ttp-core", - "version": "0.0.15", + "version": "0.0.19", "license": "UNKNOWN", "homepage": null, "repository": null, @@ -235,18 +235,18 @@ "files": [ "LICENSE" ], - "licenseFolder": "licenses/@tensamin_ttp-core@0.0.15", + "licenseFolder": "licenses/@tensamin_ttp-core@0.0.19", "sourcePackageDir": "packages/ttp/node_modules/@tensamin/ttp-core" }, { "name": "@tensamin/ui", - "version": "0.0.30", + "version": "0.0.34", "license": "UNKNOWN", "homepage": null, "repository": null, "description": null, "files": [], - "licenseFolder": "licenses/@tensamin_ui@0.0.30", + "licenseFolder": "licenses/@tensamin_ui@0.0.34", "sourcePackageDir": "apps/tauri/node_modules/@tensamin/ui" }, { @@ -614,6 +614,19 @@ "licenseFolder": "licenses/tailwindcss@4.2.4", "sourcePackageDir": "apps/web/node_modules/tailwindcss" }, + { + "name": "tauri-plugin-app-events-api", + "version": "0.2.0", + "license": "MIT", + "homepage": "https://github.com/wtto00/tauri-plugin-app-events#readme", + "repository": "git+https://github.com/wtto00/tauri-plugin-app-events.git", + "description": "A plugin for tauri@v2 to listen some events on iOS and Android.", + "files": [ + "LICENSE" + ], + "licenseFolder": "licenses/tauri-plugin-app-events-api@0.2.0", + "sourcePackageDir": "apps/web/node_modules/tauri-plugin-app-events-api" + }, { "name": "tw-animate-css", "version": "1.4.0", diff --git a/package.json b/package.json index 7f969e8..55353bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tensamin", - "version": "0.0.1", + "version": "0.0.5", "private": true, "workspaces": [ "packages/*", @@ -21,8 +21,9 @@ "preview:web": "cd apps/web && bun run preview", "dev:mobile": "cd apps/tauri && bun dev:mobile", "build:mobile": "cd apps/tauri && bun run build:mobile", - "dev:desktop": "cd apps/tauri && bun dev:desktop", - "build:desktop": "cd apps/tauri && bun run build:desktop", + "dev:desktop": "cd apps/electron && bun run dev", + "build:desktop": "cd apps/electron && bun run package", + "run:desktop": "TENSAMIN_APPIMAGE=\"$PWD/apps/electron/release/Tensamin-0.0.3-linux-x86_64.AppImage\" nix run .#localPathForDev --impure", "delete:mobile": "cd apps/tauri && nix develop --command adb uninstall net.tensamin.client" }, "devDependencies": { @@ -40,8 +41,8 @@ "jsonc-parser": "^3.3.1" }, "overrides": { - "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.34.tar.gz", - "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz" + "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.36.tar.gz", + "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.19.tar.gz" }, "dependencies": { "@tensamin/ttp-core": "*", diff --git a/packages/call/src/components/buttons/screenshare.tsx b/packages/call/src/components/buttons/screenshare.tsx index 4471539..21ba118 100644 --- a/packages/call/src/components/buttons/screenshare.tsx +++ b/packages/call/src/components/buttons/screenshare.tsx @@ -18,10 +18,12 @@ export default function ScreenshareButton({ className, iconSize, tooltip, + defaultPortal, }: { className?: string; iconSize?: number; tooltip?: string; + defaultPortal?: boolean; }) { const isScreensharing = useCall((state) => state.screenShareEnabled); const screenRef = useCall((state) => state.screenRef); @@ -30,8 +32,9 @@ export default function ScreenshareButton({ const [menuOpen, setMenuOpen] = useState(false); useEffect(() => { + if (defaultPortal) return; setPortalContainer(screenRef?.current ?? undefined); - }, [screenRef]); + }, [screenRef, defaultPortal]); async function startWebShare() { try { @@ -110,7 +113,9 @@ export default function ScreenshareButton({ /> } /> - - Click to open call page - + Click to open call page ); } -export function TinyPingGraph({ - portalContainer, -}: { - portalContainer?: HTMLElement; -}) { - const room = useCall((store) => store.room); +export function TinyPingGraph() { + const room = getRoom(); const [mapData, setMapData] = useState>(() => new Map()); @@ -176,7 +164,7 @@ export function TinyPingGraph({ } /> - + {data.length > 0 ? `${data.at(-1)?.ping} ms` : "Measuring ping..."} diff --git a/packages/call/src/components/top.tsx b/packages/call/src/components/top.tsx index 62e8bfc..610493a 100644 --- a/packages/call/src/components/top.tsx +++ b/packages/call/src/components/top.tsx @@ -1,5 +1,5 @@ import { useUser, type User } from "@tensamin/user/context"; -import { useCall } from "../store"; +import { useCall, getRoom } from "../store"; import { useEffect, useState } from "react"; import { useStorage } from "@tensamin/storage/context"; import { @@ -14,7 +14,7 @@ import { export default function TopBar() { const { get } = useUser(); const { load } = useStorage(); - const room = useCall((state) => state.room); + const room = getRoom(); const screenRef = useCall((state) => state.screenRef); const [portalContainer, setPortalContainer] = useState(); diff --git a/packages/call/src/components/videoViewer.tsx b/packages/call/src/components/videoViewer.tsx index 40cf6ac..1268b22 100644 --- a/packages/call/src/components/videoViewer.tsx +++ b/packages/call/src/components/videoViewer.tsx @@ -1,6 +1,6 @@ import { VideoTrack, useParticipantTracks } from "@livekit/components-react"; import { TrackPublication } from "livekit-client"; -import { useCall } from "../store"; +import { getRoom } from "../store"; import { cn } from "@tensamin/ui"; import { Loader2 } from "lucide-react"; @@ -17,7 +17,7 @@ export default function VideoViewer({ publication: TrackPublication; participantId: string; }) { - const room = useCall((state) => state.room); + const room = getRoom(); const tracks = useParticipantTracks([publication.source], { participantIdentity: participantId, room, diff --git a/packages/call/src/screenshare.ts b/packages/call/src/screenshare.ts index 21b8b71..8911a71 100644 --- a/packages/call/src/screenshare.ts +++ b/packages/call/src/screenshare.ts @@ -1,4 +1,3 @@ -import { invoke } from "@tauri-apps/api/core"; import { log } from "@tensamin/shared/log"; import { type LocalTrack, @@ -22,6 +21,39 @@ type ScreenShareStoreSetState = ( | ((state: ScreenShareStoreState) => Partial), ) => void; +declare global { + interface Window { + tensaminDesktop?: { + media?: { + getScreenShareCapabilities?: () => Promise<{ + runtime?: "electron" | "tauri"; + platform: "linux" | "macos" | "windows" | "other"; + showAudioOutputSelector: boolean; + showAudioSwitch: boolean; + hasReliableSystemAudio: boolean; + }>; + listScreenShareSources?: () => Promise< + Array<{ + id: string; + kind: "screen" | "window"; + name: string; + subtitle?: string | null; + thumbnail?: string | null; + }> + >; + listScreenShareAudioOutputs?: () => Promise< + Array<{ + id: string; + name: string; + isDefault: boolean; + }> + >; + selectScreenShareSource?: (sourceId: string) => Promise; + }; + }; + } +} + type ScreenShareControllerOptions = { room: Room; getState: () => ScreenShareStoreState; @@ -128,84 +160,26 @@ export function createScreenShareController({ async function startLinuxDesktopScreenShare(sourceId: string) { await clearPublishedScreenShare(); - const canvas = document.createElement("canvas"); - canvas.width = 1280; - canvas.height = 720; - canvas.style.display = "none"; - document.body.appendChild(canvas); - - const context = canvas.getContext("2d"); - - if (!context) { - canvas.remove(); - throw new Error("Failed to initialize the screen share canvas."); - } - - const stream = canvas.captureStream(8); - const videoTrack = stream.getVideoTracks()[0]; - - if (!videoTrack) { - canvas.remove(); - throw new Error("Failed to create a video track for screen sharing."); - } - - const image = new Image(); - let stopped = false; - let frameRequestInFlight = false; - - const renderFrame = async () => { - if (stopped || frameRequestInFlight) { - return; - } - - frameRequestInFlight = true; - - try { - const dataUrl = await invoke("capture_screen_share_frame", { - sourceId, - }); - - await new Promise((resolve, reject) => { - image.onload = () => resolve(); - image.onerror = () => - reject(new Error("Failed to decode screen share frame.")); - image.src = dataUrl; - }); - - if ( - canvas.width !== image.naturalWidth || - canvas.height !== image.naturalHeight - ) { - canvas.width = image.naturalWidth; - canvas.height = image.naturalHeight; - } - - context.drawImage(image, 0, 0, canvas.width, canvas.height); - } finally { - frameRequestInFlight = false; - } - }; - - await renderFrame(); - - const interval = window.setInterval(() => { - void renderFrame().catch((error) => { - log( - 1, - "call", - "red", - "Failed to capture Linux screen share frame", - error, - ); + if (window.tensaminDesktop?.media?.selectScreenShareSource) { + await window.tensaminDesktop.media.selectScreenShareSource(sourceId); + const tracks = await room.localParticipant.createScreenTracks({ + audio: false, + video: true, + systemAudio: "exclude", + surfaceSwitching: "exclude", + selfBrowserSurface: "exclude", + contentHint: "detail", }); - }, 125); - await publishScreenShareTracks([videoTrack], () => { - stopped = true; - window.clearInterval(interval); - stream.getTracks().forEach((track) => track.stop()); - canvas.remove(); - }); + await publishScreenShareTracks(tracks, () => { + tracks.forEach((track) => track.stop()); + }); + return; + } + + throw new Error( + `Electron desktop media bridge is unavailable. Cannot capture ${sourceId}.`, + ); } async function stopScreenShare() { diff --git a/packages/call/src/speakingIndicator.ts b/packages/call/src/speakingIndicator.ts new file mode 100644 index 0000000..0c4f5a5 --- /dev/null +++ b/packages/call/src/speakingIndicator.ts @@ -0,0 +1,252 @@ +import { log } from "@tensamin/shared/log"; +import { useCall } from "./store"; + +const SPEAKING_THRESHOLD = 0.01; +const SPEAKING_HANGTIME_MS = 500; +const ANALYSIS_INTERVAL_MS = 30; +const FFT_SIZE = 256; + +type AnalyserEntry = { + source: MediaStreamAudioSourceNode; + analyser: AnalyserNode; + track: MediaStreamTrack; + originalTrack?: MediaStreamTrack; + lastSpeakingTime: number; + isSpeaking: boolean; +}; + +class SpeakingDetector { + private audioContext: AudioContext | null = null; + private entries = new Map(); + private intervalId: ReturnType | null = null; + private deaf = false; + private gateThresholdStart = -50; + private gateThresholdEnd = -40; + private localParticipantId: number | null = null; + private localMicGateClosed = false; + + private ensureAudioContext(): AudioContext { + if (!this.audioContext) { + this.audioContext = new AudioContext(); + } + if (this.audioContext.state === "suspended") { + void this.audioContext.resume(); + } + return this.audioContext; + } + + setLocalParticipantId(id: number) { + this.localParticipantId = id; + } + + setGateThresholds(start: number, end: number) { + this.gateThresholdStart = start; + this.gateThresholdEnd = end; + } + + addTrack( + participantId: number, + track: MediaStreamTrack, + originalTrack?: MediaStreamTrack, + ) { + if (track.kind !== "audio") return; + + this.removeParticipant(participantId); + + const ctx = this.ensureAudioContext(); + const stream = new MediaStream([track]); + const source = ctx.createMediaStreamSource(stream); + const analyser = ctx.createAnalyser(); + analyser.fftSize = FFT_SIZE; + source.connect(analyser); + + this.entries.set(participantId, { + source, + analyser, + track, + originalTrack, + lastSpeakingTime: 0, + isSpeaking: false, + }); + + if (!this.intervalId) { + this.startLoop(); + } + } + + removeParticipant(participantId: number) { + const entry = this.entries.get(participantId); + if (!entry) return; + + try { + entry.source.disconnect(); + } catch { + // ignore + } + this.entries.delete(participantId); + + useCall.setState((state) => { + if (!state.speakingParticipantIds.has(participantId)) return state; + const next = new Set(state.speakingParticipantIds); + next.delete(participantId); + return { speakingParticipantIds: next }; + }); + + if (participantId === this.localParticipantId && this.localMicGateClosed) { + this.muteLocalTrack(false); + } + } + + setDeaf(deaf: boolean) { + this.deaf = deaf; + if (deaf) { + for (const entry of this.entries.values()) { + entry.isSpeaking = false; + entry.lastSpeakingTime = 0; + } + useCall.setState({ speakingParticipantIds: new Set() }); + } + } + + private startLoop() { + if (this.intervalId) return; + this.intervalId = setInterval(() => this.analyse(), ANALYSIS_INTERVAL_MS); + } + + private stopLoop() { + if (this.intervalId) { + clearInterval(this.intervalId); + this.intervalId = null; + } + } + + private muteLocalTrack(muted: boolean) { + const entry = this.localParticipantId + ? this.entries.get(this.localParticipantId) + : undefined; + const target = entry?.originalTrack ?? entry?.track; + + if (target && target.enabled === muted) { + target.enabled = !muted; + } + + this.localMicGateClosed = muted; + useCall.setState({ micGated: muted }); + } + + private applyNoiseGate(rms: number) { + const db = 20 * Math.log10(Math.max(rms, 0.0001)); + + if (!this.localMicGateClosed && db < this.gateThresholdStart) { + log(3, "noise gate", "purple", "closed"); + this.muteLocalTrack(true); + } else if (this.localMicGateClosed && db > this.gateThresholdEnd) { + log(3, "noise gate", "purple", "opened"); + this.muteLocalTrack(false); + } + } + + private analyse() { + if (this.deaf || this.entries.size === 0) return; + + const now = Date.now(); + const changed = new Map(); + + for (const [participantId, entry] of this.entries) { + const { analyser, track } = entry; + + if (track.muted || track.readyState === "ended" || !track.enabled) { + if (entry.isSpeaking) { + entry.isSpeaking = false; + entry.lastSpeakingTime = 0; + changed.set(participantId, false); + } + continue; + } + + const bufferLength = analyser.frequencyBinCount; + const dataArray = new Uint8Array(bufferLength); + analyser.getByteTimeDomainData(dataArray); + + let sum = 0; + for (let i = 0; i < bufferLength; i++) { + const sample = (dataArray[i] - 128) / 128.0; + sum += sample * sample; + } + const rms = Math.sqrt(sum / bufferLength); + + let nextIsSpeaking = entry.isSpeaking; + if (rms > SPEAKING_THRESHOLD) { + entry.lastSpeakingTime = now; + nextIsSpeaking = true; + } else if (now - entry.lastSpeakingTime > SPEAKING_HANGTIME_MS) { + nextIsSpeaking = false; + } + + if (participantId === this.localParticipantId) { + this.applyNoiseGate(rms); + if (this.localMicGateClosed) { + nextIsSpeaking = false; + } + } + + if (nextIsSpeaking !== entry.isSpeaking) { + entry.isSpeaking = nextIsSpeaking; + changed.set(participantId, nextIsSpeaking); + } + } + + if (changed.size > 0) { + useCall.setState((state) => { + let hasDiff = false; + const next = new Set(state.speakingParticipantIds); + for (const [id, speaking] of changed) { + if (speaking) { + if (!next.has(id)) { + next.add(id); + hasDiff = true; + } + } else { + if (next.has(id)) { + next.delete(id); + hasDiff = true; + } + } + } + return hasDiff ? { speakingParticipantIds: next } : state; + }); + } + } + + dispose() { + this.stopLoop(); + for (const id of Array.from(this.entries.keys())) { + this.removeParticipant(id); + } + this.entries.clear(); + if (this.audioContext) { + void this.audioContext.close(); + this.audioContext = null; + } + } +} + +let detectorInstance: SpeakingDetector | null = null; + +export function getSpeakingDetector(): SpeakingDetector { + if (!detectorInstance) { + detectorInstance = new SpeakingDetector(); + } + return detectorInstance; +} + +export function disposeSpeakingDetector(): void { + if (detectorInstance) { + detectorInstance.dispose(); + detectorInstance = null; + } +} + +export function useIsSpeaking(participantId: number): boolean { + return useCall((state) => state.speakingParticipantIds.has(participantId)); +} diff --git a/packages/call/src/store.tsx b/packages/call/src/store.tsx index e605ece..0d28c61 100644 --- a/packages/call/src/store.tsx +++ b/packages/call/src/store.tsx @@ -11,6 +11,7 @@ import { DeepFilterNoiseFilterProcessor } from "deepfilternet3-noise-filter"; import { ExternalE2EEKeyProvider, LocalAudioTrack, + type LocalTrackPublication, type Participant, type RemoteParticipant, type RemoteTrackPublication, @@ -28,6 +29,10 @@ import { createScreenShareController, type ScreenShareSession, } from "./screenshare"; +import { + getSpeakingDetector, + disposeSpeakingDetector, +} from "./speakingIndicator"; // logging setLogExtension( @@ -86,6 +91,7 @@ type CallStore = { screenShareEnabled: boolean; screenShareSession: ScreenShareSession | null; focusedParticipantId: number | null; + focusedParticipantType: "user" | "stream" | null; usersInFocusedViewHidden: boolean; watchedStreamParticipantIds: number[]; pendingWatchedParticipantIds: number[]; @@ -95,25 +101,46 @@ type CallStore = { callIsPopout: boolean; layoutVersion: number; screenRef: React.RefObject | null; - room: Room; - keyProvider: ExternalE2EEKeyProvider; - e2eeWorker: Worker; runtime: Runtime | null; + speakingParticipantIds: Set; + micGated: boolean; }; -const keyProvider = new ExternalE2EEKeyProvider(); -const e2eeWorker = new Worker( - new URL("livekit-client/e2ee-worker", import.meta.url), -); -const room = new Room({ - dynacast: true, - adaptiveStream: true, - loggerName: "tensamin", - encryption: { - keyProvider, - worker: e2eeWorker, - }, -}); +let _keyProvider: ExternalE2EEKeyProvider | null = null; +let _e2eeWorker: Worker | null = null; +let _room: Room | null = null; + +function getKeyProvider(): ExternalE2EEKeyProvider { + if (!_keyProvider) { + _keyProvider = new ExternalE2EEKeyProvider(); + } + return _keyProvider; +} + +function getE2EEWorker(): Worker { + if (!_e2eeWorker) { + _e2eeWorker = new Worker( + new URL("livekit-client/e2ee-worker", import.meta.url), + ); + } + return _e2eeWorker; +} + +export function getRoom(): Room { + if (!_room) { + _room = new Room({ + dynacast: true, + adaptiveStream: true, + loggerName: "tensamin", + encryption: { + keyProvider: getKeyProvider(), + worker: getE2EEWorker(), + }, + }); + } + return _room; +} + const remoteAudioElements = new Map(); const SCREEN_SHARE_PREVIEW_MAX_WIDTH = 320; @@ -152,7 +179,7 @@ function clearRemoteAudio() { } } -function getParticipantId(identity: string | undefined): number | null { +export function getParticipantId(identity: string | undefined): number | null { if (!identity) { return null; } @@ -162,6 +189,7 @@ function getParticipantId(identity: string | undefined): number | null { } function getAllParticipants(): Participant[] { + const room = getRoom(); return [...room.remoteParticipants.values(), room.localParticipant]; } @@ -179,7 +207,7 @@ function getTrackPublicationBySource( } function getRemoteParticipant(participantId: number) { - return [...room.remoteParticipants.values()].find( + return [...getRoom().remoteParticipants.values()].find( (participant) => getParticipantId(participant.identity) === participantId, ); } @@ -199,12 +227,15 @@ function matchesRemoteTrackSelector( function syncRemoteParticipantTrackSubscriptions(participantId: number) { for (const publication of getRemoteTrackPublications(participantId)) { - publication.setSubscribed(publication.kind === Track.Kind.Audio); + publication.setSubscribed( + publication.kind === Track.Kind.Audio && + publication.source !== Track.Source.ScreenShareAudio, + ); } } function syncAllRemoteTrackSubscriptions() { - for (const participant of room.remoteParticipants.values()) { + for (const participant of getRoom().remoteParticipants.values()) { const participantId = getParticipantId(participant.identity); if (participantId != null) { @@ -245,7 +276,7 @@ function hasParticipant(participantId: number) { function getLocalScreenShareTrack(): MediaStreamTrack | null { const track = getTrackPublicationBySource( - room.localParticipant, + getRoom().localParticipant, Track.Source.ScreenShare, )?.track; @@ -264,13 +295,15 @@ async function updateLocalParticipantAttributes( screenSharePreviewLength: attributes.screenSharePreview?.length ?? 0, }); - await room.localParticipant.setAttributes(attributes).catch((error) => { - log(1, "call", "red", "Failed to update local participant attributes", { - attributes: Object.keys(attributes), - error, + await getRoom() + .localParticipant.setAttributes(attributes) + .catch((error) => { + log(1, "call", "red", "Failed to update local participant attributes", { + attributes: Object.keys(attributes), + error, + }); + throw error; }); - throw error; - }); log(2, "call", "purple", "Updated local participant attributes", { attributeKeys: Object.keys(attributes), @@ -457,6 +490,8 @@ function syncScreenShareParticipants() { watchedStreamParticipantIds, pendingWatchedParticipantIds, focusedParticipantId, + focusedParticipantType: + focusedParticipantId == null ? null : state.focusedParticipantType, view: state.view === "focused" && focusedParticipantId == null ? "grid" @@ -474,7 +509,7 @@ function requireRuntime(runtime: Runtime | null): Runtime { } export function getRoomMetadata() { - const roomMetadata = room.metadata; + const roomMetadata = getRoom().metadata; try { const data = JSON.parse(roomMetadata || '{"admins": []}'); return data as { admins: number[] }; @@ -485,7 +520,8 @@ export function getRoomMetadata() { // Sync local participant flags and screen-share derived state for the active call UI. export function syncParticipantState() { - const { room, screenShareSession } = useCall.getState(); + const { screenShareSession } = useCall.getState(); + const room = getRoom(); useCall.setState({ micEnabled: room.localParticipant.isMicrophoneEnabled, @@ -614,6 +650,7 @@ export function startWatchingStream(participantId: number) { const trackReady = getScreenShareTrackForParticipant(participantId) != null; setParticipantTrackSubscribed(participantId, Track.Source.ScreenShare); + setParticipantTrackSubscribed(participantId, Track.Source.ScreenShareAudio); useCall.setState((state) => ({ watchedStreamParticipantIds: state.watchedStreamParticipantIds.includes( @@ -627,6 +664,7 @@ export function startWatchingStream(participantId: number) { ? state.pendingWatchedParticipantIds : [...state.pendingWatchedParticipantIds, participantId], focusedParticipantId: participantId, + focusedParticipantType: "stream", })); } @@ -646,9 +684,13 @@ export function setParticipantTrackSubscribed( } // Focus a participant in the main call view even when they are not sharing a screen. -export function focusParticipant(participantId: number) { +export function focusParticipant( + participantId: number, + type: "user" | "stream" = "user", +) { useCall.setState({ focusedParticipantId: participantId, + focusedParticipantType: type, view: "focused", }); } @@ -656,6 +698,11 @@ export function focusParticipant(participantId: number) { // Stop tracking a participant's shared screen and clean up related UI state. export function stopWatchingStream(participantId: number) { setParticipantTrackSubscribed(participantId, Track.Source.ScreenShare, false); + setParticipantTrackSubscribed( + participantId, + Track.Source.ScreenShareAudio, + false, + ); useCall.setState((state) => ({ watchedStreamParticipantIds: state.watchedStreamParticipantIds.filter( @@ -668,6 +715,10 @@ export function stopWatchingStream(participantId: number) { state.focusedParticipantId === participantId ? null : state.focusedParticipantId, + focusedParticipantType: + state.focusedParticipantId === participantId + ? null + : state.focusedParticipantType, view: state.view === "focused" && state.focusedParticipantId === participantId ? "grid" @@ -690,10 +741,18 @@ let screenShareController: ReturnType< typeof createScreenShareController > | null = null; +function getNoiseFilterAssetBaseUrl() { + if (window.location.protocol === "file:") { + return new URL("./assets", document.baseURI).href.replace(/\/$/, ""); + } + + return "/assets"; +} + function getScreenShareController() { if (!screenShareController) { screenShareController = createScreenShareController({ - room, + room: getRoom(), getState: () => ({ screenShareSession: useCall.getState().screenShareSession, }), @@ -705,7 +764,7 @@ function getScreenShareController() { ); }, getLocalParticipantId: () => - getParticipantId(room.localParticipant.identity), + getParticipantId(getRoom().localParticipant.identity), startWatching: startWatchingStream, stopWatching: stopWatchingStream, syncParticipantState, @@ -730,7 +789,7 @@ export async function connect(callId: string) { callSecret: useCall.getState().callSecret, }); - await room + await getRoom() .connect("wss://call.tensamin.net", token, { autoSubscribe: false, }) @@ -746,17 +805,20 @@ export async function connect(callId: string) { syncAllRemoteTrackSubscriptions(); - await room.localParticipant.setMicrophoneEnabled(true).catch((error) => { - log(1, "call", "red", "Failed to enable microphone", error); - toast("error", "Failed to enable microphone."); - throw error; - }); + await getRoom() + .localParticipant.setMicrophoneEnabled(true) + .catch((error) => { + log(1, "call", "red", "Failed to enable microphone", error); + toast("error", "Failed to enable microphone."); + throw error; + }); syncParticipantState(); } // Tear down the active call session and return the store to a closed state. export async function disconnect() { + disposeSpeakingDetector(); await clearScreenSharePreview(); try { @@ -782,18 +844,20 @@ export async function disconnect() { view: "preview", screenShareSession: null, focusedParticipantId: null, + focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], + micGated: false, }); - room.remoteParticipants.forEach((participant) => { + getRoom().remoteParticipants.forEach((participant) => { participant.setVolume(1); }); try { - await room.disconnect(); + await getRoom().disconnect(); } catch (error) { log(1, "call", "red", "Failed to disconnect from room", error); } finally { @@ -836,8 +900,8 @@ export async function joinCall( callSecret, ); - await keyProvider.setKey(decryptedSecret); - await room.setE2EEEnabled(true); + await getKeyProvider().setKey(decryptedSecret); + await getRoom().setE2EEEnabled(true); useCall.setState({ callSecret: decryptedSecret }); } catch (err) { log(1, "call", "red", "Failed getting call secret", err); @@ -847,8 +911,8 @@ export async function joinCall( } else { const random = crypto.randomUUID(); - await keyProvider.setKey(random); - await room.setE2EEEnabled(true); + await getKeyProvider().setKey(random); + await getRoom().setE2EEEnabled(true); useCall.setState({ callSecret: random }); } @@ -867,11 +931,11 @@ export async function joinCall( export async function toggleDeaf() { const nextDeaf = !useCall.getState().deaf; - room.remoteParticipants.forEach((participant) => { + getRoom().remoteParticipants.forEach((participant) => { participant.setVolume(nextDeaf ? 0 : 1); }); - if (nextDeaf && room.localParticipant.isMicrophoneEnabled) { + if (nextDeaf && getRoom().localParticipant.isMicrophoneEnabled) { await toggleMute(); } @@ -879,6 +943,7 @@ export async function toggleDeaf() { deafened: nextDeaf ? "true" : "false", }); + getSpeakingDetector().setDeaf(nextDeaf); useCall.setState({ deaf: nextDeaf }); } @@ -890,7 +955,7 @@ export async function toggleMute() { await toggleDeaf(); } - await room.localParticipant.setMicrophoneEnabled(!micEnabled); + await getRoom().localParticipant.setMicrophoneEnabled(!micEnabled); syncParticipantState(); } @@ -940,6 +1005,7 @@ export function resetCallState() { deaf: false, screenShareSession: null, focusedParticipantId: null, + focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], @@ -953,7 +1019,7 @@ export function resetCallState() { async function ensureNoiseFilter( noiseFilter: DeepFilterNoiseFilterProcessor, ): Promise { - const microphoneTrack = room.localParticipant.getTrackPublication( + const microphoneTrack = getRoom().localParticipant.getTrackPublication( Track.Source.Microphone, )?.track; @@ -967,6 +1033,16 @@ async function ensureNoiseFilter( await microphoneTrack.setProcessor(noiseFilter).catch((err) => { log(1, "call", "red", "Failed to enable noise filter", err); }); + + const participantId = getParticipantId(getRoom().localParticipant.identity); + if (participantId != null) { + const processedTrack = microphoneTrack.mediaStreamTrack; + getSpeakingDetector().addTrack( + participantId, + processedTrack.clone(), + processedTrack, + ); + } } export const useCall = create(() => ({ @@ -978,24 +1054,23 @@ export const useCall = create(() => ({ livekitToken: null, currentCallData: null, deaf: false, - micEnabled: room.localParticipant.isMicrophoneEnabled, - screenShareEnabled: room.localParticipant.isScreenShareEnabled, + micEnabled: false, + screenShareEnabled: false, screenShareSession: null, focusedParticipantId: null, + focusedParticipantType: null, usersInFocusedViewHidden: false, watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], - isEncrypted: - room.localParticipant.isE2EEEnabled && room.localParticipant.isEncrypted, + isEncrypted: false, callIsFullscreen: false, callIsPopout: false, layoutVersion: 0, screenRef: null, - room, - keyProvider, - e2eeWorker, runtime: null, + speakingParticipantIds: new Set(), + micGated: false, })); // Register app-level call listeners and wire React dependencies into the store. @@ -1016,10 +1091,10 @@ export function useInitializeCall() { new DeepFilterNoiseFilterProcessor({ enabled: true, enableNoiseReduction: true, - noiseReductionLevel: 80, + noiseReductionLevel: 60, sampleRate: 48000, assetConfig: { - cdnUrl: "/assets", + cdnUrl: getNoiseFilterAssetBaseUrl(), }, }), [], @@ -1137,10 +1212,46 @@ export function useInitializeCall() { listenersRegistered.current = true; - const onConnected = () => { + const onConnected = async () => { useCall.setState({ state: "open" }); syncParticipantState(); + const detector = getSpeakingDetector(); + const localParticipantId = getParticipantId( + room.localParticipant.identity, + ); + if (localParticipantId != null) { + detector.setLocalParticipantId(localParticipantId); + } + + const [start, end] = await Promise.all([ + load("call_mute_range_start"), + load("call_mute_range_end"), + ]); + detector.setGateThresholds(start, end); + + // Scan existing audio tracks for speaking detection + for (const participant of getAllParticipants()) { + const participantId = getParticipantId(participant.identity); + if (participantId == null) continue; + + for (const publication of participant.trackPublications.values()) { + if ( + publication.kind === Track.Kind.Audio && + publication.source === Track.Source.Microphone && + publication.track + ) { + const mediaTrack = publication.track.mediaStreamTrack; + if (participant === room.localParticipant) { + const clonedTrack = mediaTrack.clone(); + detector.addTrack(participantId, clonedTrack, mediaTrack); + } else { + detector.addTrack(participantId, mediaTrack); + } + } + } + } + const invitedUserId = useCall.getState().invitedUserId; if (invitedUserId != null) { @@ -1177,6 +1288,7 @@ export function useInitializeCall() { if (participantId != null) { stopWatchingStream(participantId); + getSpeakingDetector().removeParticipant(participantId); } syncParticipantState(); @@ -1197,6 +1309,40 @@ export function useInitializeCall() { void ensureNoiseFilter(noiseFilter); }; + const onLocalTrackPublished = (publication: LocalTrackPublication) => { + if ( + publication.kind === Track.Kind.Audio && + publication.source === Track.Source.Microphone && + publication.track + ) { + const participantId = getParticipantId(room.localParticipant.identity); + if (participantId != null) { + const originalTrack = publication.track.mediaStreamTrack; + const clonedTrack = originalTrack.clone(); + getSpeakingDetector().addTrack( + participantId, + clonedTrack, + originalTrack, + ); + } + } + syncParticipantState(); + void ensureNoiseFilter(noiseFilter); + }; + + const onLocalTrackUnpublished = (publication: LocalTrackPublication) => { + if ( + publication.kind === Track.Kind.Audio && + publication.source === Track.Source.Microphone + ) { + const participantId = getParticipantId(room.localParticipant.identity); + if (participantId != null) { + getSpeakingDetector().removeParticipant(participantId); + } + } + syncParticipantState(); + }; + const onTrackPublished = ( publication: RemoteTrackPublication, participant: RemoteParticipant, @@ -1204,7 +1350,10 @@ export function useInitializeCall() { const participantId = getParticipantId(participant.identity); if (participantId != null) { - if (publication.kind === Track.Kind.Audio) { + if ( + publication.kind === Track.Kind.Audio && + publication.source !== Track.Source.ScreenShareAudio + ) { publication.setSubscribed(true); } else { publication.setSubscribed(false); @@ -1214,9 +1363,21 @@ export function useInitializeCall() { onParticipantStateChange(); }; - const onTrackSubscribed = (track: RemoteTrack) => { + const onTrackSubscribed = ( + track: RemoteTrack, + publication: RemoteTrackPublication, + participant: RemoteParticipant, + ) => { if (track.kind === "audio" && track.sid) { attachRemoteAudio(track.sid, track.attach()); + + const participantId = getParticipantId(participant.identity); + if ( + participantId != null && + publication.source === Track.Source.Microphone + ) { + getSpeakingDetector().addTrack(participantId, track.mediaStreamTrack); + } } syncParticipantState(); @@ -1224,15 +1385,22 @@ export function useInitializeCall() { const onTrackUnsubscribed = ( track: RemoteTrack, - _publication: unknown, + publication: RemoteTrackPublication, participant: Participant, ) => { + const participantId = getParticipantId(participant.identity); + if (track.kind === "audio" && track.sid) { track.detach(); detachRemoteAudio(track.sid); - } - const participantId = getParticipantId(participant.identity); + if ( + participantId != null && + publication.source === Track.Source.Microphone + ) { + getSpeakingDetector().removeParticipant(participantId); + } + } if ( participantId != null && @@ -1245,6 +1413,7 @@ export function useInitializeCall() { syncParticipantState(); }; + const room = getRoom(); room.on(RoomEvent.Connected, onConnected); room.on(RoomEvent.Reconnected, onConnected); room.on(RoomEvent.Disconnected, onDisconnected); @@ -1256,8 +1425,8 @@ export function useInitializeCall() { room.on(RoomEvent.ParticipantDisconnected, onParticipantDisconnected); room.on(RoomEvent.TrackMuted, onParticipantStateChange); room.on(RoomEvent.TrackUnmuted, onParticipantStateChange); - room.on(RoomEvent.LocalTrackPublished, onParticipantStateChange); - room.on(RoomEvent.LocalTrackUnpublished, onParticipantStateChange); + room.on(RoomEvent.LocalTrackPublished, onLocalTrackPublished); + room.on(RoomEvent.LocalTrackUnpublished, onLocalTrackUnpublished); room.on(RoomEvent.MediaDevicesError, onMediaDeviceFailure); room.on(RoomEvent.EncryptionError, onEncryptionError); room.on(RoomEvent.ConnectionStateChanged, onParticipantStateChange); @@ -1277,17 +1446,17 @@ export function useInitializeCall() { room.off(RoomEvent.ParticipantDisconnected, onParticipantDisconnected); room.off(RoomEvent.TrackMuted, onParticipantStateChange); room.off(RoomEvent.TrackUnmuted, onParticipantStateChange); - room.off(RoomEvent.LocalTrackPublished, onParticipantStateChange); - room.off(RoomEvent.LocalTrackUnpublished, onParticipantStateChange); + room.off(RoomEvent.LocalTrackPublished, onLocalTrackPublished); + room.off(RoomEvent.LocalTrackUnpublished, onLocalTrackUnpublished); room.off(RoomEvent.MediaDevicesError, onMediaDeviceFailure); room.off(RoomEvent.EncryptionError, onEncryptionError); room.off(RoomEvent.ConnectionStateChanged, onParticipantStateChange); clearRemoteAudio(); listenersRegistered.current = false; room.disconnect(); - e2eeWorker.terminate(); + if (_e2eeWorker) _e2eeWorker.terminate(); }; - }, [noiseFilter]); + }, [noiseFilter, load]); // fetch call data for preview page useEffect(() => { diff --git a/packages/call/src/views/main/focused.tsx b/packages/call/src/views/main/focused.tsx index d52362b..f8239c5 100644 --- a/packages/call/src/views/main/focused.tsx +++ b/packages/call/src/views/main/focused.tsx @@ -1,13 +1,13 @@ import { RoomEvent } from "livekit-client"; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; -import { useCall } from "../../store"; +import { useCall, getRoom } from "../../store"; import Base from "../../components/modals/base"; const SECONDARY_ROW_HEIGHT_PX = 180; const STACK_GAP_PX = 12; export default function View() { - const room = useCall((state) => state.room); + const room = getRoom(); const layoutVersion = useCall((state) => state.layoutVersion); const usersInFocusedViewHidden = useCall( @@ -16,6 +16,9 @@ export default function View() { const callIsFullscreen = useCall((state) => state.callIsFullscreen); const focusedParticipantId = useCall((state) => state.focusedParticipantId); + const focusedParticipantType = useCall( + (state) => state.focusedParticipantType, + ); const activeScreenShareParticipantIds = useCall( (state) => state.activeScreenShareParticipantIds, ); @@ -157,6 +160,11 @@ export default function View() { const focusedParticipant = getParticipantById(focusedParticipantId); const focusedParticipantHasActiveScreenShare = activeScreenShareParticipantIdSet.has(focusedParticipantId); + const focusedTileType: "user" | "stream" = + focusedParticipantType === "stream" && + focusedParticipantHasActiveScreenShare + ? "stream" + : "user"; const isImmersiveFocusedView = callIsFullscreen && usersInFocusedViewHidden; return ( @@ -179,7 +187,7 @@ export default function View() { diff --git a/packages/call/src/views/main/grid.tsx b/packages/call/src/views/main/grid.tsx index 833d7b5..956842d 100644 --- a/packages/call/src/views/main/grid.tsx +++ b/packages/call/src/views/main/grid.tsx @@ -1,6 +1,6 @@ import { RoomEvent } from "livekit-client"; import { useEffect, useMemo, useRef, useState } from "react"; -import { useCall } from "../../store"; +import { useCall, getRoom } from "../../store"; import Base from "../../components/modals/base"; const TILE_ASPECT_RATIO = 16 / 9; @@ -82,7 +82,7 @@ function calculateOptimalGridLayout( } export default function View() { - const room = useCall((state) => state.room); + const room = getRoom(); const layoutVersion = useCall((state) => state.layoutVersion); const activeScreenShareParticipantIds = useCall( (state) => state.activeScreenShareParticipantIds, diff --git a/packages/call/todo.md b/packages/call/todo.md index 952858f..2d8db1b 100644 --- a/packages/call/todo.md +++ b/packages/call/todo.md @@ -1,4 +1,3 @@ -- Speaking indicator - Overlay for stream modals - User modals - Bg based on avatar @@ -9,5 +8,6 @@ - Admin call actions - Timeout - Disconnect -- Desktop-App screenshares - Context menus +- Popout Window +- Add quality selection diff --git a/packages/chat/src/components/input.tsx b/packages/chat/src/components/input.tsx index 487c813..cf74848 100644 --- a/packages/chat/src/components/input.tsx +++ b/packages/chat/src/components/input.tsx @@ -97,6 +97,9 @@ export default function InputComponent({ > - {message.failed && message.message_state === "awaiting" && ( - - -

Failed to send message

-
- } /> -
- )} - + ( + <> + {grouped ? ( +

+ {new Date(message.send_time).toLocaleString([], { + hour: "2-digit", + minute: "2-digit", + })} +

+ ) : ( + + + + {user.display.slice(0, 2).toUpperCase()} + + + )} + {message.failed && message.message_state === "awaiting" && ( + + +

Failed to send message

+
+ } /> +
+ )} +
+ {!grouped && ( +
+

{user.display}

+

+ {new Date(message.send_time).toLocaleString([], { + hour: "2-digit", + minute: "2-digit", + })} +

+
+ )} + +
+ + )} + /> } /> diff --git a/packages/chat/src/screen.tsx b/packages/chat/src/screen.tsx index a23760d..44e581b 100644 --- a/packages/chat/src/screen.tsx +++ b/packages/chat/src/screen.tsx @@ -8,6 +8,7 @@ import Message from "./components/message"; import { PAGE_SIZE } from "./values"; import { useIsMobile } from "@tensamin/ui"; +import { Loader2 } from "lucide-react"; function getDistanceFromBottom(element: HTMLDivElement) { return element.scrollHeight - (element.scrollTop + element.clientHeight); @@ -101,12 +102,6 @@ export default function Screen() { const virtualRowCount = messages.length + (shouldShowConversationStart ? 1 : 0); - const messagesRef = React.useRef(messages); - - React.useEffect(() => { - messagesRef.current = messages; - }, [messages]); - const getItemKey = React.useCallback( (index: number) => { if (shouldShowConversationStart && index === 0) { @@ -114,9 +109,9 @@ export default function Screen() { } const messageIndex = shouldShowConversationStart ? index - 1 : index; - return messagesRef.current[messageIndex]?.send_time ?? index; + return messages[messageIndex]?.send_time ?? index; }, - [shouldShowConversationStart], + [shouldShowConversationStart, messages], ); const setMeasurementRef = React.useCallback( @@ -360,9 +355,38 @@ export default function Screen() { return; } - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - isAtBottomRef.current = true; - setHasScrolledToBottomInitially(true); + let rafId: number; + let stableFrames = 0; + let lastScrollHeight = 0; + + const settleToBottom = () => { + if (!scrollRef.current || hasScrolledToBottomInitially) { + return; + } + + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + const currentScrollHeight = scrollRef.current.scrollHeight; + + if (currentScrollHeight === lastScrollHeight) { + stableFrames++; + } else { + stableFrames = 0; + lastScrollHeight = currentScrollHeight; + } + + if (stableFrames >= 2) { + isAtBottomRef.current = true; + setHasScrolledToBottomInitially(true); + } else { + rafId = requestAnimationFrame(settleToBottom); + } + }; + + settleToBottom(); + + return () => { + cancelAnimationFrame(rafId); + }; }, [hasScrolledToBottomInitially, virtualRowCount]); React.useLayoutEffect(() => { @@ -435,13 +459,26 @@ export default function Screen() {
+ {messagesQuery.isPending && ( +
+

+ + Loading... +

+
+ )} + {messagesQuery.isFetchingNextPage && ( +
+ +
+ )}
-
- This is the start of your conversation with{" "} +
+ Conversation start
@@ -481,6 +517,13 @@ export default function Screen() { return null; } + const lastMessage = messages[messageIndex - 1]; + const isGrouped = + lastMessage && + lastMessage.sent_by_self === message.sent_by_self && + Math.round(lastMessage.send_time / 10000) === + Math.round(message.send_time / 10000); + return (
- +
); })} @@ -509,17 +551,25 @@ export default function Screen() { className="pointer-events-none absolute left-0 top-0 -z-10 overflow-hidden opacity-0" style={{ width: scrollWidth > 0 ? `${scrollWidth}px` : "100%" }} > - {messages.map((message) => ( -
{ - setMeasurementRef(message.send_time, element); - }} - style={{ padding: "4px 0" }} - > - -
- ))} + {messages.map((message, messageIndex) => { + const lastMessage = messages[messageIndex - 1]; + const isGrouped = + lastMessage && + lastMessage.sent_by_self === message.sent_by_self && + Math.round(lastMessage.send_time / 10000) === + Math.round(message.send_time / 10000); + + return ( +
{ + setMeasurementRef(message.send_time, element); + }} + > + +
+ ); + })}
); diff --git a/packages/chat/todo.md b/packages/chat/todo.md new file mode 100644 index 0000000..122f14e --- /dev/null +++ b/packages/chat/todo.md @@ -0,0 +1 @@ +- Add context menu to messages diff --git a/packages/markdown/src/input.tsx b/packages/markdown/src/input.tsx index f44ced6..3222ddf 100644 --- a/packages/markdown/src/input.tsx +++ b/packages/markdown/src/input.tsx @@ -23,6 +23,7 @@ import { indentWithTab, } from "@codemirror/commands"; import { useEffect, useRef } from "react"; +import type { CSSProperties } from "react"; import { collectInlineRanges, ensureMarkdownStyles } from "./markdown"; @@ -33,8 +34,36 @@ export type InputProps = { setValue: (value: string) => void; onSubmit?: () => void; invertEnterBehavior?: boolean; + styled?: boolean; + fontSize?: CSSProperties["fontSize"]; + paddingX?: CSSProperties["padding"]; + paddingY?: CSSProperties["padding"]; + className?: string; }; +type InputStyle = CSSProperties & { + "--tm-md-content-padding"?: string; +}; + +function toCssLength(value: CSSProperties["padding"]): string | undefined { + if (value === undefined) { + return undefined; + } + + return typeof value === "number" ? `${value}px` : value; +} + +function toCssPadding( + vertical: CSSProperties["padding"], + horizontal: CSSProperties["padding"], + styled: boolean, +): string { + const defaultVertical = styled ? "0.25rem" : "0"; + const defaultHorizontal = styled ? "0.625rem" : "0"; + + return `${toCssLength(vertical) ?? defaultVertical} ${toCssLength(horizontal) ?? defaultHorizontal}`; +} + type TokenRange = { from: number; to: number; @@ -80,6 +109,10 @@ const markdownDecorations = ViewPlugin.fromClass( export default function Input(props: InputProps) { ensureMarkdownStyles(); + const shellClassName = props.styled + ? "min-h-8 w-full min-w-0 rounded-lg border border-input bg-transparent text-base transition-colors outline-none placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40" + : ""; + const elementRef = useRef(null); const viewRef = useRef(undefined); const ignoreSyncRef = useRef(false); @@ -143,7 +176,22 @@ export default function Input(props: InputProps) { }); }, [props.value]); - return
; + return ( +
+ ); } /** @@ -204,7 +252,7 @@ function createEditorExtensions( }), EditorView.theme({ "&": { - fontSize: "1rem", + fontSize: "inherit", }, "&.cm-editor": { width: "100%", diff --git a/packages/markdown/src/markdown.tsx b/packages/markdown/src/markdown.tsx index c3a17a6..46ebb92 100644 --- a/packages/markdown/src/markdown.tsx +++ b/packages/markdown/src/markdown.tsx @@ -560,7 +560,7 @@ export function renderBlocks(blocks: MarkdownBlock[]): React.ReactElement { } return ( -

+

{block.text.split("\n").map((line, lineIndex) => ( {lineIndex > 0 ?
: null} @@ -645,7 +645,6 @@ export const markdownStyles = ` .tm-md-h4 { font-size: 1.1rem; } .tm-md-h5 { font-size: 1rem; } .tm-md-h6 { font-size: 0.95rem; opacity: 0.9; } -.tm-md-p { margin: 0.25rem 0; } .tm-md-blockquote { margin: 0.45rem 0; padding-left: 0.75rem; opacity: 0.95; } .tm-md-blockquote p { margin: 0.2rem 0; } .tm-md-pre { margin: 0.45rem 0; padding: 0.65rem 0.75rem; border-radius: 0.5rem; background: hsl(var(--muted)); overflow-x: auto; } @@ -665,12 +664,12 @@ export const markdownStyles = ` .tm-md-table th { background: hsl(var(--muted)); font-weight: 600; } .tm-md-hr { margin: 0.55rem 0; } -.cm-editor.tm-md-editor { border-radius: 0.65rem; background: hsl(var(--card)); caret-color: var(--foreground); } +.cm-editor.tm-md-editor { border-radius: inherit; background: transparent; caret-color: var(--foreground); } .cm-editor.tm-md-editor.cm-focused { outline: none; box-shadow: none; } .cm-editor.tm-md-editor .cm-scroller { font-family: inherit; line-height: 1.55; max-height: 30vh; overflow-y: auto; overflow-x: hidden; } .cm-editor.tm-md-editor .cm-content { caret-color: var(--foreground); } -.cm-editor.tm-md-editor .cm-content { padding: 0.7rem 0.85rem; min-height: 2.75rem; } -.cm-editor.tm-md-editor .cm-line { padding: 0 1px; color: hsl(var(--foreground)); } +.cm-editor.tm-md-editor .cm-content { padding: var(--tm-md-content-padding, 0.25rem 0.625rem); min-height: 2rem; } +.cm-editor.tm-md-editor .cm-line { padding: 0; color: hsl(var(--foreground)); } .cm-editor.tm-md-editor .tm-md-hidden-token { color: transparent; opacity: 0; font-size: inherit; } .cm-editor.tm-md-editor .tm-md-code-line { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; background: hsl(var(--muted)); border-radius: 0.3rem; } `; diff --git a/packages/notifications/package.json b/packages/notifications/package.json index af90c45..a6b06be 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -12,6 +12,7 @@ "build": "tsc -p tsconfig.json --noEmit" }, "dependencies": { + "@tanstack/react-router": "^1.169.1", "@tauri-apps/api": "^2.11.0", "@tensamin/crypto": "workspace:*", "@tensamin/shared": "workspace:*", diff --git a/packages/notifications/src/context.tsx b/packages/notifications/src/context.tsx index 4e433a3..8da45f4 100644 --- a/packages/notifications/src/context.tsx +++ b/packages/notifications/src/context.tsx @@ -10,25 +10,27 @@ import { message as messageSchema } from "@tensamin/shared/data"; import { Avatar, AvatarFallback, AvatarImage } from "@tensamin/ui"; import { isTauri } from "@tauri-apps/api/core"; import { useSession } from "@tensamin/storage/session"; +import { useNavigate } from "@tanstack/react-router"; export const context = createContext(undefined); -function reduceDisplay(display: string) { - const words = display.split(" "); - if (words.length === 1) { - return display.slice(0, 2).toUpperCase(); - } else { - return words[0].charAt(0).toUpperCase() + words[1].charAt(0).toUpperCase(); - } +async function requestNotificationPermission() { + if (!("Notification" in window)) return false; + + if (Notification.permission === "granted") return true; + + const permission = await Notification.requestPermission(); + return permission === "granted"; } export default function Provider(props: { children: React.ReactNode }) { - const { subscribePush } = useTTP(); + const { subscribePush, send } = useTTP(); const { load } = useStorage(); const { get } = useUser(); const { decryptText, getSharedSecret } = useCrypto(); const { addLiveMessage, userId } = useChat(); const { moveUserIdToTop } = useSession(); + const navigate = useNavigate(); useEffect(() => { return subscribePush(async (ttpMessage) => { @@ -49,7 +51,32 @@ export default function Provider(props: { children: React.ReactNode }) { message.content, ); - console.log(userId, sender_id, userId === sender_id); + // Update message state + if ( + (await load("settings.read_confirmations")) && + userId === sender_id + ) { + void send( + "message_state", + { + message_state: "read", + }, + { + id: ttpMessage.id, + }, + ); + } else { + void send( + "message_state", + { + message_state: "received", + }, + { + id: ttpMessage.id, + }, + ); + } + if (userId === sender_id) { addLiveMessage({ ...message, @@ -60,25 +87,48 @@ export default function Provider(props: { children: React.ReactNode }) { return; } - // add notification symbol to conversation cards + // todo: add notification symbol to conversation cards (incl. message start) moveUserIdToTop(sender_id); if (isTauri()) { console.log("weewoo"); } else { - sonnerToast(user.display, { - classNames: { - content: "pl-4", - }, - description: decryptedContent, - icon: ( - - - {reduceDisplay(user.display)} - - ), - }); + const hasPermissions = await requestNotificationPermission(); + + if (hasPermissions) { + const notification = new Notification(user.display, { + body: decryptedContent, + icon: user.avatar || user.display.slice(0, 2).toUpperCase(), + badge: user.avatar || user.display.slice(0, 2).toUpperCase(), + tag: `message-${user.user_id}`, + silent: true, + }); + + notification.onclick = () => { + window.focus(); + navigate({ + to: `/chat?id=${user.user_id}`, + }); + + notification.close(); + }; + } else { + sonnerToast(user.display, { + classNames: { + content: "pl-4", + }, + description: decryptedContent, + icon: ( + + + + {user.display.slice(0, 2).toUpperCase()} + + + ), + }); + } } } }); @@ -91,6 +141,8 @@ export default function Provider(props: { children: React.ReactNode }) { addLiveMessage, userId, moveUserIdToTop, + send, + navigate, ]); return ( diff --git a/packages/shared/package.json b/packages/shared/package.json index ab2ea1b..c009c34 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -5,6 +5,7 @@ "type": "module", "exports": { "./data": "./src/data.ts", + "./desktopMedia": "./src/desktopMedia.tsx", "./log": "./src/log.tsx", "./settings": "./src/settings.ts", "./features/legal/schema": "./src/features/legal/schema.ts", diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index e03ca3d..2210192 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -48,6 +48,31 @@ export type Communities = z.infer< export type Calls = z.infer; // TTP +const user = z.object({ + about: z.string().max(255).optional(), + avatar: z.string().optional(), + display: z.string().min(1).max(15), + iota_id: z.number(), + omikron_connections: z.array(z.number()), + omikron_id: z.number().optional(), + online_status: z.enum([ + "user_offline", + "user_online", + "user_dnd", + "user_idle", + "user_wc", + "user_borked", + "iota_offline", + "iota_online", + "iota_borked", + ]), + public_key: z.base64(), + status: z.string().max(15).optional(), + sub_end: z.number(), + sub_level: z.number(), + user_id: z.number(), + username: z.string().min(1).max(15), +}); export const ttp = { identification: { request: z.object({ @@ -90,33 +115,14 @@ export const ttp = { }, get_user_data: { request: z.object({ - user_id: z.number(), - }), - response: z.object({ - about: z.string().max(255).optional(), - avatar: z.string().optional(), - display: z.string().max(15), - iota_id: z.number(), - omikron_connections: z.array(z.number()), - omikron_id: z.number().optional(), - online_status: z.enum([ - "user_offline", - "user_online", - "user_dnd", - "user_idle", - "user_wc", - "user_borked", - "iota_offline", - "iota_online", - "iota_borked", - ]), - public_key: z.base64(), - status: z.string().max(15).optional(), - sub_end: z.number(), - sub_level: z.number(), - user_id: z.number(), - username: z.string().max(15), + user_id: z.number().optional(), + username: z.string().optional(), }), + response: user, + }, + change_user_data: { + request: user.partial(), + response: z.object({}), }, ping: { request: z.object({ @@ -161,11 +167,17 @@ export const ttp = { response: z.object({}), }, message_state: { - request: z.object({ - chat_partner_id: z.number(), - send_time: z.number(), - message_state: message.shape.message_state, - }), + request: z + .object({ + chat_partner_id: z.number(), + send_time: z.number(), + message_state: message.shape.message_state, + }) + .or( + z.object({ + message_state: message.shape.message_state, + }), + ), response: z.object({ chat_partner_id: z.number(), message_state: message.shape.message_state, @@ -245,6 +257,8 @@ export interface Storage extends SettingsStorageDefaults { cached_contacts: Contacts; cached_communities: Communities; ttp_url: string; + call_mute_range_start: number; + call_mute_range_end: number; } export const storageDefaults: Storage = { @@ -278,4 +292,31 @@ export const storageDefaults: Storage = { cached_contacts: [], cached_communities: [], ttp_url: "https://tensamin.net:959", + call_mute_range_start: -55, + call_mute_range_end: -45, }; + +// User Status +export function getStatusColor( + status: z.infer, +) { + switch (status) { + case "user_online": + return "#22c55e"; + case "iota_online": + return "#22c55e"; + case "user_dnd": + return "#ef4444"; + case "user_idle": + return "#f59e0b"; + case "user_wc": + return "#3b82f6"; + case "user_borked": + case "iota_borked": + return "#6b7280"; + case "user_offline": + case "iota_offline": + default: + return "#9ca3af"; + } +} diff --git a/apps/tauri/src/context.tsx b/packages/shared/src/desktopMedia.tsx similarity index 63% rename from apps/tauri/src/context.tsx rename to packages/shared/src/desktopMedia.tsx index 7d86f00..d117716 100644 --- a/apps/tauri/src/context.tsx +++ b/packages/shared/src/desktopMedia.tsx @@ -1,11 +1,11 @@ import { createContext, useContext, useMemo, type ReactNode } from "react"; -import { invoke, isTauri } from "@tauri-apps/api/core"; export type DesktopScreenShareSource = { id: string; kind: "screen" | "window"; name: string; subtitle?: string | null; + thumbnail?: string | null; }; export type DesktopScreenShareAudioOutput = { @@ -15,12 +15,28 @@ export type DesktopScreenShareAudioOutput = { }; export type DesktopScreenShareCapabilities = { + runtime?: "electron" | "tauri"; platform: "linux" | "macos" | "windows" | "other"; showAudioOutputSelector: boolean; showAudioSwitch: boolean; hasReliableSystemAudio: boolean; }; +type ElectronDesktopApi = { + media?: { + getScreenShareCapabilities?: () => Promise; + listScreenShareSources?: () => Promise; + listScreenShareAudioOutputs?: () => Promise; + selectScreenShareSource?: (sourceId: string) => Promise; + }; +}; + +declare global { + interface Window { + tensaminDesktop?: ElectronDesktopApi; + } +} + type DesktopMediaContextValue = { getScreenShareCapabilities: () => Promise; listScreenShareSources: () => Promise; @@ -28,6 +44,7 @@ type DesktopMediaContextValue = { }; const defaultCapabilities: DesktopScreenShareCapabilities = { + runtime: undefined, platform: "other", showAudioOutputSelector: false, showAudioSwitch: false, @@ -39,33 +56,29 @@ const desktopMediaContext = createContext( ); async function listScreenShareSources(): Promise { - if (!isTauri()) { - return []; + if (window.tensaminDesktop?.media?.listScreenShareSources) { + return window.tensaminDesktop.media.listScreenShareSources(); } - return invoke("list_screen_share_sources"); + return []; } async function listScreenShareAudioOutputs(): Promise< DesktopScreenShareAudioOutput[] > { - if (!isTauri()) { - return []; + if (window.tensaminDesktop?.media?.listScreenShareAudioOutputs) { + return window.tensaminDesktop.media.listScreenShareAudioOutputs(); } - return invoke( - "list_screen_share_audio_outputs", - ); + return []; } async function getScreenShareCapabilities(): Promise { - if (!isTauri()) { - return defaultCapabilities; + if (window.tensaminDesktop?.media?.getScreenShareCapabilities) { + return window.tensaminDesktop.media.getScreenShareCapabilities(); } - return invoke( - "get_screen_share_capabilities", - ); + return defaultCapabilities; } export function useDesktopMedia() { @@ -78,7 +91,11 @@ export function useDesktopMedia() { return value; } -export default function Provider({ children }: { children: ReactNode }) { +export default function DesktopMediaProvider({ + children, +}: { + children: ReactNode; +}) { const value = useMemo( () => ({ getScreenShareCapabilities, diff --git a/packages/shared/src/settings.ts b/packages/shared/src/settings.ts index 0099f64..aacc162 100644 --- a/packages/shared/src/settings.ts +++ b/packages/shared/src/settings.ts @@ -28,6 +28,21 @@ const settings = { type: "boolean", default: false, }, + read_confirmations: { + display: "Enable Read Confirmations", + type: "boolean", + default: true, + }, + receive_confirmations: { + display: "Enable Receive Confirmations", + type: "boolean", + default: true, + }, + show_start_of_last_message_in_sidebar: { + display: "Show Start of Last Message in Sidebar", + type: "boolean", + default: true, + }, }, }, application: { @@ -35,6 +50,7 @@ const settings = { }, } as const satisfies SettingsSchema; +// Assemble storage defaults export default settings; type BooleanSettingNames = { diff --git a/packages/storage/src/session.tsx b/packages/storage/src/session.tsx index 03b3826..d992a03 100644 --- a/packages/storage/src/session.tsx +++ b/packages/storage/src/session.tsx @@ -14,6 +14,7 @@ interface SessionContextType { communities: Communities; calls: Calls; moveUserIdToTop: (userId: number) => void; + insertContact: (userId: number) => void; } const SessionContext = createContext(undefined); @@ -69,9 +70,31 @@ export default function SessionProvider({ children }: { children: ReactNode }) { }); }; + const insertContact = (userId: number) => { + setContacts((prevContacts) => { + if (prevContacts.some((contact) => contact.user_id === userId)) { + return prevContacts; + } + + const newUser = { + user_id: userId, + last_message_at: new Date().getTime(), + messages: [], + } satisfies Contacts[0]; + + return [newUser, ...prevContacts]; + }); + }; + return ( {children} diff --git a/packages/ttp/src/context.tsx b/packages/ttp/src/context.tsx index 0a2ea2c..d33313d 100644 --- a/packages/ttp/src/context.tsx +++ b/packages/ttp/src/context.tsx @@ -191,6 +191,7 @@ export function Provider(props: { typeof createTransportClient > | null>(null); const identificationStartedRef = useRef(false); + const identificationCancelRef = useRef(false); // Load ttp url const [ttpUrl, setTtpUrl] = useState(null); @@ -242,6 +243,23 @@ export function Provider(props: { return client.subscribePush(handler); }, []); + // Check for error_no_iota + useEffect(() => { + if (!connected) return; + + return subscribePush((message) => { + if (message.type !== "error_no_iota") return; + + identificationCancelRef.current = true; + setIdentified(false); + setIdentifying(false); + setError("We couldn't reach your Iota"); + setErrorDescription( + "You could try to restart your Iota, check for updates or check your network connection.", + ); + }); + }, [connected, subscribePush]); + useEffect(() => { if (!connected || !identified) { return; @@ -363,6 +381,7 @@ export function Provider(props: { clearReconnectTimer(); scheduleReconnectReset(); identificationStartedRef.current = false; + identificationCancelRef.current = false; setConnected(true); setIdentified(false); setError(""); @@ -483,6 +502,11 @@ export function Provider(props: { return; } + if (identificationCancelRef.current) { + identificationStartedRef.current = false; + return; + } + if (identificationStartedRef.current) { return; } @@ -551,10 +575,12 @@ export function Provider(props: { const finalResponse = await send("challenge_response", { challenge: decryptedChallenge, }).catch((error) => { - setError("Identification Failed"); - setErrorDescription( - "Unable to complete secure identification. Please verify your credentials and try again.", - ); + if (!identificationCancelRef.current) { + setError("Identification Failed"); + setErrorDescription( + "Unable to complete secure identification. Please verify your credentials and try again.", + ); + } throw error; }); @@ -562,7 +588,7 @@ export function Provider(props: { setFreshContacts(finalResponse.data.contacts); setFreshCommunities(finalResponse.data.communities); setFreshCalls(finalResponse.data.calls); - if (cancelled) { + if (cancelled || identificationCancelRef.current) { return; } @@ -574,6 +600,10 @@ export function Provider(props: { return; } + if (identificationCancelRef.current) { + return; + } + if (isStopSendingError(identificationError)) { setError("Connection closed"); setErrorDescription( @@ -604,7 +634,7 @@ export function Provider(props: { : "Unable to complete secure identification because the transport request failed.", ); } finally { - if (!cancelled) { + if (!cancelled && !identificationCancelRef.current) { setIdentifying(false); } } diff --git a/packages/user/src/context.tsx b/packages/user/src/context.tsx index 1ab964b..962152a 100644 --- a/packages/user/src/context.tsx +++ b/packages/user/src/context.tsx @@ -51,7 +51,7 @@ export default function UserProvider(props: { children: React.ReactNode }) { const user = { ...userData.data, avatar: userData.data.avatar - ? `data:image/png;base64,${userData.data.avatar}` + ? `data:image/webp;base64,${atob(userData.data.avatar)}` : undefined, }; diff --git a/scripts/copy-releases.ts b/scripts/copy-releases.ts index e840169..3b0eeec 100644 --- a/scripts/copy-releases.ts +++ b/scripts/copy-releases.ts @@ -1,4 +1,6 @@ import { + copyFileSync, + createReadStream, existsSync, mkdirSync, readdirSync, @@ -6,11 +8,39 @@ import { rmSync, statSync, } from "node:fs"; +import { createHash } from "node:crypto"; import { join } from "node:path"; import packageJson from "../package.json" with { type: "json" }; const { version } = packageJson; +const releaseVersion = process.env.TENSAMIN_RELEASE_VERSION || version; +const releaseTag = process.env.TENSAMIN_RELEASE_TAG || releaseVersion; const releasesDir = join("releases"); +const releaseAssetBaseUrl = process.env.FORGEJO_RELEASE_ASSET_BASE_URL; + +function sha256(filePath: string) { + const hash = createHash("sha256"); + const stream = createReadStream(filePath); + + return new Promise((resolve, reject) => { + stream.on("data", (chunk) => hash.update(chunk)); + stream.on("error", reject); + stream.on("end", () => resolve(hash.digest("hex"))); + }); +} + +function platformFor(file: string) { + if (/\.apk$/i.test(file)) return "android"; + if (/win|nsis|portable|\.exe$/i.test(file)) return "windows"; + if (/mac|darwin|\.dmg$/i.test(file)) return "macos"; + return "linux"; +} + +function archFor(file: string) { + if (/arm64|aarch64/i.test(file)) return "arm64"; + if (/\.apk$/i.test(file)) return "universal"; + return "x64"; +} // directory rmSync(releasesDir, { recursive: true, force: true }); @@ -25,56 +55,56 @@ if (existsSync(apkSrc)) { console.warn(`Skipping .apk: ${apkSrc} does not exist.`); } -// deb & rpm -const debDir = "apps/tauri/src-tauri/target/release/bundle/deb"; -const rpmDir = "apps/tauri/src-tauri/target/release/bundle/rpm"; +// Electron desktop artifacts +const electronReleaseDir = "apps/electron/release"; -const moveBundleArtifact = ( - sourceDir: string, - extension: ".deb" | ".rpm", - destinationFileName: string, -) => { - if (!existsSync(sourceDir)) { - console.warn(`Skipping ${extension}: ${sourceDir} does not exist.`); +const copyElectronArtifacts = () => { + if (!existsSync(electronReleaseDir)) { + console.warn(`Skipping Electron artifacts: ${electronReleaseDir} does not exist.`); return; } - const candidates = readdirSync(sourceDir) - .filter((file) => file.endsWith(extension)) - .map((file) => { - const filePath = join(sourceDir, file); + for (const file of readdirSync(electronReleaseDir)) { + if (file.endsWith(".blockmap") || file.endsWith(".yml")) continue; + if (!file.includes(version) && !file.includes(releaseVersion)) continue; - return { - file, - filePath, - matchesVersion: file.includes(version), - modifiedAt: statSync(filePath).mtimeMs, - }; - }) - .sort((a, b) => { - if (a.matchesVersion !== b.matchesVersion) { - return Number(b.matchesVersion) - Number(a.matchesVersion); - } + const source = join(electronReleaseDir, file); + if (!statSync(source).isFile()) continue; - return b.modifiedAt - a.modifiedAt; - }); - - const selected = candidates[0]; - if (!selected) { - console.warn(`Skipping ${extension}: no files found in ${sourceDir}.`); - return; + copyFileSync(source, join(releasesDir, file)); } - - if (!selected.matchesVersion) { - console.warn( - `Using ${selected.file} for ${extension} even though it does not include version ${version}.`, - ); - } - - renameSync(selected.filePath, join(releasesDir, destinationFileName)); }; -moveBundleArtifact(debDir, ".deb", `Tensamin-${version}.deb`); -moveBundleArtifact(rpmDir, ".rpm", `Tensamin-${version}.rpm`); +copyElectronArtifacts(); + +const artifacts = await Promise.all( + readdirSync(releasesDir) + .map((file) => join(releasesDir, file)) + .filter((file) => statSync(file).isFile()) + .filter((file) => !file.endsWith("electron-release-metadata.json") && !file.endsWith("SHA256SUMS")) + .map(async (filePath) => { + const name = filePath.split(/[\\/]/).at(-1)!; + + return { + name, + platform: platformFor(name), + arch: archFor(name), + url: releaseAssetBaseUrl + ? `${releaseAssetBaseUrl.replace(/\/$/, "")}/${encodeURIComponent(name)}` + : `__FORGEJO_RELEASE_ASSET_URL__/${encodeURIComponent(name)}`, + sha256: await sha256(filePath), + size: statSync(filePath).size, + }; + }), +); + +await Bun.write( + join(releasesDir, "electron-release-metadata.json"), + `${JSON.stringify({ version: releaseVersion, tag: releaseTag, publishedAt: new Date().toISOString(), artifacts: artifacts.filter((artifact) => artifact.platform !== "android") }, null, 2)}\n`, +); +await Bun.write( + join(releasesDir, "SHA256SUMS"), + `${artifacts.map((artifact) => `${artifact.sha256} ${artifact.name}`).join("\n")}\n`, +); console.log("Releases copied successfully.");