diff --git a/.env.example b/.env.example index 51169ee..cb6ad7a 100644 --- a/.env.example +++ b/.env.example @@ -11,7 +11,7 @@ # security find-identity -v -p codesigning APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)" -# Optional CI-style path: base64 .p12 + password (Tauri imports into a temp keychain). +# Optional CI-style path: base64 .p12 + password (the package script imports it into a temp keychain). # Leave blank when the cert is already in Keychain Access → My Certificates. # APPLE_CERTIFICATE= # APPLE_CERTIFICATE_PASSWORD= diff --git a/.github/scripts/bump-version.sh b/.github/scripts/bump-version.sh index 6bfb986..792d447 100755 --- a/.github/scripts/bump-version.sh +++ b/.github/scripts/bump-version.sh @@ -33,8 +33,8 @@ CURRENT="" if LATEST_TAG="$(git describe --tags --match 'v*' --abbrev=0 2>/dev/null || true)" \ && [[ -n "${LATEST_TAG}" ]]; then CURRENT="${LATEST_TAG#v}" -elif [[ -f package.json ]]; then - CURRENT="$(node -pe 'require("./package.json").version')" +elif [[ -f app/Cargo.toml ]]; then + CURRENT="$(sed -n 's/^version = "\(.*\)"/\1/p' app/Cargo.toml | head -1)" else CURRENT="0.0.0" fi @@ -66,28 +66,12 @@ esac VERSION="${MAJOR}.${MINOR}.${PATCH}" TAG="v${VERSION}" -node -e " -const fs = require('fs'); -const path = 'package.json'; -const pkg = JSON.parse(fs.readFileSync(path, 'utf8')); -pkg.version = process.argv[1]; -fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); -" "${VERSION}" - -node -e " -const fs = require('fs'); -const path = 'src-tauri/tauri.conf.json'; -const conf = JSON.parse(fs.readFileSync(path, 'utf8')); -conf.version = process.argv[1]; -fs.writeFileSync(path, JSON.stringify(conf, null, 2) + '\n'); -" "${VERSION}" - # Replace only the package version line in Cargo.toml. -perl -i -0pe 's/(?m)^(\[package\]\s*(?:(?!\[).)*?^version\s*=\s*)"[^"]*"/${1}"'"${VERSION}"'"/s' src-tauri/Cargo.toml +perl -i -0pe 's/(?m)^(\[package\]\s*(?:(?!\[).)*?^version\s*=\s*)"[^"]*"/${1}"'"${VERSION}"'"/s' app/Cargo.toml # Keep Cargo.lock package stanza in sync when present. -if [[ -f src-tauri/Cargo.lock ]]; then - perl -i -0pe 's/(name = "grimoire"\nversion = )"[^"]*"/${1}"'"${VERSION}"'"/' src-tauri/Cargo.lock +if [[ -f app/Cargo.lock ]]; then + perl -i -0pe 's/(name = "grimoire"\nversion = )"[^"]*"/${1}"'"${VERSION}"'"/' app/Cargo.lock fi echo "VERSION=${VERSION}" diff --git a/.github/scripts/inject-macos-dmg-installer.sh b/.github/scripts/inject-macos-dmg-installer.sh deleted file mode 100755 index 97d2fda..0000000 --- a/.github/scripts/inject-macos-dmg-installer.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env bash -# Inject Gatekeeper installer files into built macOS DMGs. -# Usage: inject-macos-dmg-installer.sh [dmg-path...] -# With no args, finds DMGs under src-tauri/target/**/bundle/dmg/ -set -euo pipefail - -ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -INSTALLER_SRC="$ROOT/src-tauri/macos/Install Grimoire.command" -HOWTO_SRC="$ROOT/src-tauri/macos/How to install.txt" - -if [[ ! -f "$INSTALLER_SRC" ]]; then - echo "missing installer: $INSTALLER_SRC" >&2 - exit 1 -fi -if [[ ! -f "$HOWTO_SRC" ]]; then - echo "missing howto: $HOWTO_SRC" >&2 - exit 1 -fi - -if [[ $# -gt 0 ]]; then - DMGS=("$@") -else - # shellcheck disable=SC2207 - DMGS=($(find "$ROOT/src-tauri/target" -type f -path '*/bundle/dmg/*.dmg' ! -name 'rw.*.dmg' 2>/dev/null | sort -u)) -fi - -if [[ ${#DMGS[@]} -eq 0 ]]; then - echo "No DMGs found to patch." >&2 - exit 1 -fi - -inject_one() { - local dmg="$1" - local work rw mount staging out - - echo "Injecting installer into $(basename "$dmg")…" - - work="$(mktemp -d "${TMPDIR:-/tmp}/grimoire-dmg.XXXXXX")" - rw="$work/rw.dmg" - staging="$work/mount" - out="$work/out.dmg" - mkdir -p "$staging" - - # Writable copy with headroom for installer files. - hdiutil convert "$dmg" -format UDRW -o "$rw" >/dev/null - # Resize: current size + 2MB (hdiutil wants sector counts; -size accepts m). - local size_mb - size_mb="$(du -sm "$rw" | awk '{print $1 + 2}')" - hdiutil resize -size "${size_mb}m" "$rw" >/dev/null - - hdiutil attach "$rw" -readwrite -nobrowse -mountpoint "$staging" >/dev/null - - cp "$INSTALLER_SRC" "$staging/Install Grimoire.command" - chmod +x "$staging/Install Grimoire.command" - cp "$HOWTO_SRC" "$staging/How to install.txt" - - # Flush before detach. - sync - hdiutil detach "$staging" >/dev/null - - hdiutil convert "$rw" -format UDZO -imagekey zlib-level=9 -o "$out" >/dev/null - mv -f "$out" "$dmg" - rm -rf "$work" - echo " ok: $dmg" -} - -for dmg in "${DMGS[@]}"; do - if [[ ! -f "$dmg" ]]; then - echo "skip missing: $dmg" >&2 - continue - fi - inject_one "$dmg" -done diff --git a/.github/scripts/package-macos.sh b/.github/scripts/package-macos.sh new file mode 100755 index 0000000..928645c --- /dev/null +++ b/.github/scripts/package-macos.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# Build a Grimoire.app and a DMG from a release binary. +# Usage: package-macos.sh +# +# Signing: when APPLE_CERTIFICATE is set, import that .p12 and sign with the +# Developer ID Application identity inside it. Otherwise use +# APPLE_SIGNING_IDENTITY from the login keychain. Notarize when the App Store +# Connect API key variables are set. +set -euo pipefail + +BIN="${1:?path to the Grimoire binary}" +OUT="${2:?output directory}" +ARCH="${3:?arch label, such as macos_arm64}" + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' "$ROOT/app/Cargo.toml" | head -1)" +if [[ -z "${VERSION}" ]]; then + echo "Could not read version from app/Cargo.toml" >&2 + exit 1 +fi +if [[ ! -f "${BIN}" ]]; then + echo "Missing binary: ${BIN}" >&2 + exit 1 +fi + +STAGE="$(mktemp -d "${TMPDIR:-/tmp}/grimoire-app.XXXXXX")" +KEYCHAIN_PATH="" +PREVIOUS_KEYCHAINS="" + +cleanup() { + if [[ -n "${KEYCHAIN_PATH}" ]]; then + security delete-keychain "${KEYCHAIN_PATH}" >/dev/null 2>&1 || true + fi + if [[ -n "${PREVIOUS_KEYCHAINS}" ]]; then + # shellcheck disable=SC2086 + security list-keychains -d user -s ${PREVIOUS_KEYCHAINS} >/dev/null 2>&1 || true + fi + rm -rf "${STAGE}" +} +trap cleanup EXIT + +APP="${STAGE}/Grimoire.app" +mkdir -p "${APP}/Contents/MacOS" "${APP}/Contents/Resources" +cp "${BIN}" "${APP}/Contents/MacOS/Grimoire" +chmod +x "${APP}/Contents/MacOS/Grimoire" +cp "${ROOT}/app/icons/icon.icns" "${APP}/Contents/Resources/icon.icns" + +cat > "${APP}/Contents/Info.plist" < + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Grimoire + CFBundleExecutable + Grimoire + CFBundleIconFile + icon + CFBundleIdentifier + com.boxingoctopus.grimoire + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Grimoire + CFBundlePackageType + APPL + CFBundleShortVersionString + ${VERSION} + CFBundleVersion + ${VERSION} + LSMinimumSystemVersion + 12.0 + NSHighResolutionCapable + + + +EOF + +IDENTITY="" +if [[ -n "${APPLE_CERTIFICATE:-}" && -n "${APPLE_CERTIFICATE_PASSWORD:-}" ]]; then + KEYCHAIN_PATH="${STAGE}/build.keychain-db" + KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" + CERT_PATH="${STAGE}/certificate.p12" + printf '%s' "${APPLE_CERTIFICATE}" | base64 -D > "${CERT_PATH}" + PREVIOUS_KEYCHAINS="$(security list-keychains -d user | tr -d '"' | tr '\n' ' ')" + security create-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_PATH}" + security set-keychain-settings -lut 21600 "${KEYCHAIN_PATH}" + security unlock-keychain -p "${KEYCHAIN_PASSWORD}" "${KEYCHAIN_PATH}" + security import "${CERT_PATH}" \ + -k "${KEYCHAIN_PATH}" \ + -P "${APPLE_CERTIFICATE_PASSWORD}" \ + -A -t cert -f pkcs12 \ + -T /usr/bin/codesign \ + -T /usr/bin/security + security set-key-partition-list \ + -S apple-tool:,apple:,codesign: \ + -s -k "${KEYCHAIN_PASSWORD}" \ + "${KEYCHAIN_PATH}" + security list-keychains -d user -s "${KEYCHAIN_PATH}" ${PREVIOUS_KEYCHAINS} + IDENTITY="$(security find-identity -v -p codesigning "${KEYCHAIN_PATH}" | awk -F'"' '/Developer ID Application/{print $2; exit}')" +elif [[ -n "${APPLE_SIGNING_IDENTITY:-}" ]]; then + IDENTITY="${APPLE_SIGNING_IDENTITY}" +fi + +if [[ "${CI:-}" == "true" && -z "${IDENTITY}" ]]; then + echo "Could not find a Developer ID Application identity in APPLE_CERTIFICATE." >&2 + exit 1 +fi + +if [[ -n "${IDENTITY}" ]]; then + echo "Signing with ${IDENTITY}" + codesign --force --options runtime --timestamp --sign "${IDENTITY}" "${APP}/Contents/MacOS/Grimoire" + codesign --force --options runtime --timestamp --sign "${IDENTITY}" "${APP}" +fi + +DMG_ROOT="${STAGE}/dmg" +mkdir -p "${DMG_ROOT}" +cp -R "${APP}" "${DMG_ROOT}/" +ln -s /Applications "${DMG_ROOT}/Applications" +if [[ -z "${IDENTITY}" ]]; then + cp "${ROOT}/app/macos/Install Grimoire.command" "${DMG_ROOT}/Install Grimoire.command" + chmod +x "${DMG_ROOT}/Install Grimoire.command" + cp "${ROOT}/app/macos/How to install.txt" "${DMG_ROOT}/How to install.txt" +fi + +mkdir -p "${OUT}" +DMG="${OUT}/Grimoire_${VERSION}_${ARCH}.dmg" +rm -f "${DMG}" +hdiutil create -volname Grimoire -srcfolder "${DMG_ROOT}" -ov -format UDZO "${DMG}" >/dev/null + +if [[ -n "${IDENTITY}" ]]; then + codesign --force --timestamp --sign "${IDENTITY}" "${DMG}" +fi + +if [[ -n "${IDENTITY}" && -n "${APPLE_API_KEY:-}" && -n "${APPLE_API_ISSUER:-}" && -n "${APPLE_API_KEY_PATH:-}" ]]; then + echo "Notarizing ${DMG}" + xcrun notarytool submit "${DMG}" \ + --key "${APPLE_API_KEY_PATH}" \ + --key-id "${APPLE_API_KEY}" \ + --issuer "${APPLE_API_ISSUER}" \ + --wait + xcrun stapler staple "${DMG}" +fi + +echo "Wrote ${DMG}" diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 1eca700..010e79d 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -42,26 +42,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 + # Wayland, xkbcommon, and fontconfig are what the GPUI window links. - name: Install Linux build deps run: | sudo apt-get update sudo apt-get install -y \ - libwebkit2gtk-4.1-dev \ - libappindicator3-dev \ - librsvg2-dev \ - patchelf \ - xdg-utils - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm + pkg-config \ + libwayland-dev \ + libxkbcommon-dev \ + libxkbcommon-x11-dev \ + libfontconfig1-dev - name: Setup Rust uses: dtolnay/rust-toolchain@stable @@ -69,16 +59,10 @@ jobs: - name: Cache Rust uses: swatinem/rust-cache@v2 with: - workspaces: './src-tauri -> target' - - - name: Install frontend dependencies - run: pnpm install --frozen-lockfile - - - name: Frontend tests - run: pnpm test + workspaces: './app -> target' - name: Rust tests - run: cargo test --manifest-path src-tauri/Cargo.toml + run: cargo test --manifest-path app/Cargo.toml version-and-release: name: Tag and release @@ -156,7 +140,7 @@ jobs: echo "Missing VERSION or TAG from bump step" >&2 exit 1 fi - git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml src-tauri/Cargo.lock + git add app/Cargo.toml app/Cargo.lock git commit -m "chore: release ${TAG}" git tag -a "${TAG}" -m "Release ${TAG}" git push origin "HEAD:${BASE_REF}" @@ -216,20 +200,24 @@ jobs: include: - platform: macos-latest label: macOS arm64 - args: --target aarch64-apple-darwin + cargo_args: --target aarch64-apple-darwin rust_targets: aarch64-apple-darwin + archive_suffix: macos_arm64 - platform: macos-latest label: macOS x64 - args: --target x86_64-apple-darwin + cargo_args: --target x86_64-apple-darwin rust_targets: x86_64-apple-darwin + archive_suffix: macos_x64 - platform: ubuntu-22.04 label: Linux x64 - args: '' + cargo_args: '' rust_targets: '' + archive_suffix: linux_x64 - platform: windows-latest label: Windows x64 - args: --bundles nsis,msi + cargo_args: '' rust_targets: '' + archive_suffix: windows_x64 runs-on: ${{ matrix.platform }} steps: - name: Checkout release tag @@ -237,27 +225,17 @@ jobs: with: ref: ${{ needs.version-and-release.outputs.tag }} + # Wayland, xkbcommon, and fontconfig are what the GPUI window links. - name: Install Linux build deps if: matrix.platform == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install -y \ - libwebkit2gtk-4.1-dev \ - libappindicator3-dev \ - librsvg2-dev \ - patchelf \ - xdg-utils - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm + pkg-config \ + libwayland-dev \ + libxkbcommon-dev \ + libxkbcommon-x11-dev \ + libfontconfig1-dev - name: Setup Rust uses: dtolnay/rust-toolchain@stable @@ -267,19 +245,15 @@ jobs: - name: Cache Rust uses: swatinem/rust-cache@v2 with: - workspaces: './src-tauri -> target' - - - name: Install frontend dependencies - run: pnpm install --frozen-lockfile + workspaces: './app -> target' # Developer ID cert + App Store Connect API key for signing and notarization. - # Do not inject files into the DMG after this; that would break the notarization ticket. + # APPLE_SIGNING_IDENTITY is optional: when omitted, the package script uses the identity inside APPLE_CERTIFICATE. - name: Prepare Apple signing and notarization if: matrix.platform == 'macos-latest' env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} APPLE_API_KEY_CONTENTS: ${{ secrets.APPLE_API_KEY_CONTENTS }} @@ -289,7 +263,6 @@ jobs: for name in \ APPLE_CERTIFICATE \ APPLE_CERTIFICATE_PASSWORD \ - APPLE_SIGNING_IDENTITY \ APPLE_API_KEY \ APPLE_API_ISSUER \ APPLE_API_KEY_CONTENTS @@ -308,51 +281,56 @@ jobs: echo "APPLE_API_KEY_PATH=${KEY_PATH}" >> "$GITHUB_ENV" echo "Wrote App Store Connect API key to ${KEY_PATH}" - - name: Build and upload to draft release - if: matrix.platform != 'macos-latest' - uses: tauri-apps/tauri-action@v1 - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} - with: - tagName: ${{ needs.version-and-release.outputs.tag }} - releaseId: ${{ needs.version-and-release.outputs.release_id }} - releaseDraft: true - prerelease: false - args: ${{ matrix.args }} + - name: Build release binary + run: cargo build --manifest-path app/Cargo.toml --release --bin Grimoire ${{ matrix.cargo_args }} - - name: Build macOS bundles - id: build_mac + - name: Package macOS disk image if: matrix.platform == 'macos-latest' - uses: tauri-apps/tauri-action@v1 env: - GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} APPLE_API_KEY_PATH: ${{ env.APPLE_API_KEY_PATH }} - with: - args: ${{ matrix.args }} + run: | + set -euo pipefail + chmod +x .github/scripts/package-macos.sh + .github/scripts/package-macos.sh \ + "app/target/${{ matrix.rust_targets }}/release/Grimoire" \ + dist \ + "${{ matrix.archive_suffix }}" - - name: Upload macOS release assets - if: matrix.platform == 'macos-latest' + - name: Package Linux binary + if: matrix.platform == 'ubuntu-22.04' + run: | + set -euo pipefail + VERSION="${{ needs.version-and-release.outputs.version }}" + mkdir -p dist + tar -C app/target/release -czf "dist/Grimoire_${VERSION}_${{ matrix.archive_suffix }}.tar.gz" Grimoire + + - name: Package Windows binary + if: matrix.platform == 'windows-latest' + shell: bash + run: | + set -euo pipefail + VERSION="${{ needs.version-and-release.outputs.version }}" + mkdir -p dist + tar -a -C app/target/release -cf "dist/Grimoire_${VERSION}_${{ matrix.archive_suffix }}.zip" Grimoire.exe + + - name: Upload release asset + shell: bash env: GH_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} TAG: ${{ needs.version-and-release.outputs.tag }} run: | set -euo pipefail - # bash 3.2 on macOS has no mapfile; use a while-read loop instead. ASSETS=() while IFS= read -r asset; do ASSETS+=("$asset") - done < <(find src-tauri/target -type f \( \ - -path '*/bundle/dmg/*.dmg' -o \ - -path '*/bundle/macos/*.tar.gz' \ - \) ! -name 'rw.*.dmg' | sort -u) + done < <(find dist -type f | sort -u) if [[ ${#ASSETS[@]} -eq 0 ]]; then - echo "No macOS bundle artifacts found to upload" >&2 + echo "No release artifacts found to upload" >&2 exit 1 fi printf 'Uploading:\n' diff --git a/.gitignore b/.gitignore index 502f110..b689662 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ node_modules !.env.example vite.config.js.timestamp-* vite.config.ts.timestamp-* -src-tauri/target +app/target +/dist *.log diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 61343e9..b3c9d51 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,5 @@ { "recommendations": [ - "svelte.svelte-vscode", - "tauri-apps.tauri-vscode", "rust-lang.rust-analyzer" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 2f86c50..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "svelte.enable-ts-plugin": true -} diff --git a/Makefile b/Makefile index 0c20b46..80df8f9 100644 --- a/Makefile +++ b/Makefile @@ -1,212 +1,120 @@ -# Grimoire production builds (Tauri 2 release bundles) +# Grimoire release builds. # # Usage: # make help -# make build # current host -# make build-mac # macOS (universal by default) -# make build-windows # Windows x64 (NSIS when cross-compiling) -# make build-linux # Linux x64 -# -# Bundles land under src-tauri/target//release/bundle/ -# (or src-tauri/target/release/bundle/ for a native host build). +# make build # release binary for this machine +# make build-mac # macOS .app and DMG (loads .env for signing) +# make build-windows # Windows x64, on Windows +# make build-linux # Linux x64, on Linux -PNPM ?= pnpm -TAURI ?= $(PNPM) exec tauri -CARGO_XWIN ?= cargo-xwin -RUSTUP ?= rustup +CARGO ?= cargo HOST_OS := $(shell uname -s 2>/dev/null || echo unknown) HOST_ARCH := $(shell uname -m 2>/dev/null || echo unknown) -# True on native Windows (cmd/PowerShell make) or MSYS/MinGW shells. IS_WINDOWS := $(if $(filter Windows_NT,$(OS)),1,$(if $(findstring MINGW,$(HOST_OS)),1,$(if $(findstring MSYS,$(HOST_OS)),1,$(if $(findstring CYGWIN,$(HOST_OS)),1,)))) -# Production flags: release mode (tauri build default) and non-interactive CI mode. -TAURI_BUILD_FLAGS ?= --ci - # Optional env file for local Apple signing / notarization (see .env.example). -# Usage: make build-mac or make build-mac ENV_FILE=.env.apple ENV_FILE ?= .env -# Platform triples -MAC_TARGET ?= universal-apple-darwin MAC_ARM_TARGET ?= aarch64-apple-darwin MAC_INTEL_TARGET ?= x86_64-apple-darwin WIN_TARGET ?= x86_64-pc-windows-msvc LINUX_TARGET ?= x86_64-unknown-linux-gnu -LINUX_ARM_TARGET ?= aarch64-unknown-linux-gnu -.PHONY: help install build test test-frontend test-rust \ +ifeq ($(HOST_ARCH),x86_64) +MAC_ARCH_LABEL := macos_x64 +else +MAC_ARCH_LABEL := macos_arm64 +endif + +.PHONY: help build test test-rust \ build-mac build-mac-arm build-mac-intel \ - build-windows build-linux build-linux-arm \ - ensure-frontend ensure-mac-targets ensure-windows-target ensure-linux-target \ - ensure-cargo-xwin clean-dmg-mounts clean + build-windows build-linux \ + clean .DEFAULT_GOAL := help help: - @echo "Grimoire production builds" + @echo "Grimoire release builds" @echo "" - @echo " make build Build for the current host ($(HOST_OS)/$(HOST_ARCH))" - @echo " make build-mac macOS universal (arm64 + x86_64)" - @echo " make build-mac-arm macOS Apple Silicon only" - @echo " make build-mac-intel macOS Intel only" - @echo " make build-windows Windows x64" - @echo " make build-linux Linux x64" - @echo " make build-linux-arm Linux arm64" - @echo " make install Install frontend deps and common Rust targets" - @echo " make test Run frontend + Rust automated tests" - @echo " make test-frontend Run Vitest suite" - @echo " make test-rust Run cargo test in src-tauri" - @echo " make clean Remove frontend and Rust build outputs" - @echo " make clean-dmg-mounts Unmount leftover Tauri DMG staging volumes (macOS)" + @echo " make build Release binary for this machine ($(HOST_OS)/$(HOST_ARCH))" + @echo " make build-mac macOS disk image for this machine" + @echo " make build-mac-arm macOS Apple Silicon disk image" + @echo " make build-mac-intel macOS Intel disk image" + @echo " make build-windows Windows x64 executable" + @echo " make build-linux Linux x64 executable" + @echo " make test Run cargo test" + @echo " make clean Remove Rust build outputs and dist/" @echo "" @echo "Apple signing (macOS): copy .env.example to .env and fill values, then" @echo " make build-mac" @echo "Or: make build-mac ENV_FILE=/path/to/your.env" - @echo "When APPLE_SIGNING_IDENTITY or APPLE_CERTIFICATE is set, the Gatekeeper" - @echo "DMG inject is skipped so notarization stays valid." + @echo "CI reads the signing identity from APPLE_CERTIFICATE. For local Keychain" + @echo "signing, set APPLE_SIGNING_IDENTITY in .env." @echo "" - @echo "Run each platform target on that OS when possible. Windows can be" - @echo "cross-built from macOS/Linux with cargo-xwin + NSIS + LLVM." - @echo "Linux GUI deps (WebKitGTK, etc.) require a Linux host or matching sysroot." - -install: ensure-frontend - @$(RUSTUP) target add \ - $(MAC_ARM_TARGET) \ - $(MAC_INTEL_TARGET) \ - $(WIN_TARGET) \ - $(LINUX_TARGET) \ - $(LINUX_ARM_TARGET) || true + @echo "Linux builds need Wayland, xkbcommon, and fontconfig." -ensure-frontend: - $(PNPM) install - -# --- tests ------------------------------------------------------------------- - -test: test-frontend test-rust - -test-frontend: - $(PNPM) test +test: test-rust test-rust: - cargo test --manifest-path src-tauri/Cargo.toml - -ensure-mac-targets: - @$(RUSTUP) target add $(MAC_ARM_TARGET) $(MAC_INTEL_TARGET) - -ensure-windows-target: - @$(RUSTUP) target add $(WIN_TARGET) - -ensure-linux-target: - @$(RUSTUP) target add $(LINUX_TARGET) + $(CARGO) test --manifest-path app/Cargo.toml -ensure-cargo-xwin: - @command -v $(CARGO_XWIN) >/dev/null 2>&1 || cargo install cargo-xwin --locked +build: + $(CARGO) build --manifest-path app/Cargo.toml --release --bin Grimoire -# Stale create-dmg mounts (/Volumes/dmg.*) break subsequent `tauri build` DMG steps. -clean-dmg-mounts: +build-mac: ifeq ($(HOST_OS),Darwin) - @for vol in /Volumes/dmg.* /Volumes/Grimoire*; do \ - if [ -e "$$vol" ]; then \ - echo "Detaching $$vol"; \ - hdiutil detach "$$vol" -force >/dev/null 2>&1 || true; \ - fi; \ - done - @find src-tauri/target -maxdepth 5 -name 'rw.*.dmg' -delete 2>/dev/null || true -else - @echo "clean-dmg-mounts is only needed on macOS" -endif - -# Load ENV_FILE (if present), run tauri build, then inject Gatekeeper helpers only -# when Apple signing env is absent (injecting after notarize breaks the ticket). -define tauri-build-macos - set -euo pipefail; \ - if [ -f "$(ENV_FILE)" ]; then \ - echo "Loading $(ENV_FILE)"; \ - set -a; \ - . "./$(ENV_FILE)"; \ - set +a; \ - fi; \ - $(TAURI) build $(TAURI_BUILD_FLAGS) $(1); \ - if [ -n "$${APPLE_SIGNING_IDENTITY:-}" ] || [ -n "$${APPLE_CERTIFICATE:-}" ]; then \ - echo "Skipping Gatekeeper DMG inject (Apple signing env is set)"; \ - else \ - .github/scripts/inject-macos-dmg-installer.sh; \ - fi -endef - -# Native production build for the machine you are on. -build: ensure-frontend clean-dmg-mounts -ifeq ($(HOST_OS),Darwin) - $(call tauri-build-macos,) -else - $(TAURI) build $(TAURI_BUILD_FLAGS) -endif - -# --- macOS ------------------------------------------------------------------- - -build-mac: ensure-frontend ensure-mac-targets clean-dmg-mounts -ifeq ($(HOST_OS),Darwin) - $(call tauri-build-macos,--target $(MAC_TARGET)) + $(CARGO) build --manifest-path app/Cargo.toml --release --bin Grimoire + $(call sign-and-package,app/target/release/Grimoire,$(MAC_ARCH_LABEL)) else $(error macOS builds require a Darwin host (got $(HOST_OS))) endif -build-mac-arm: ensure-frontend clean-dmg-mounts +build-mac-arm: ifeq ($(HOST_OS),Darwin) - @$(RUSTUP) target add $(MAC_ARM_TARGET) - $(call tauri-build-macos,--target $(MAC_ARM_TARGET)) + rustup target add $(MAC_ARM_TARGET) + $(CARGO) build --manifest-path app/Cargo.toml --release --bin Grimoire --target $(MAC_ARM_TARGET) + $(call sign-and-package,app/target/$(MAC_ARM_TARGET)/release/Grimoire,macos_arm64) else $(error macOS builds require a Darwin host (got $(HOST_OS))) endif -build-mac-intel: ensure-frontend clean-dmg-mounts +build-mac-intel: ifeq ($(HOST_OS),Darwin) - @$(RUSTUP) target add $(MAC_INTEL_TARGET) - $(call tauri-build-macos,--target $(MAC_INTEL_TARGET)) + rustup target add $(MAC_INTEL_TARGET) + $(CARGO) build --manifest-path app/Cargo.toml --release --bin Grimoire --target $(MAC_INTEL_TARGET) + $(call sign-and-package,app/target/$(MAC_INTEL_TARGET)/release/Grimoire,macos_x64) else $(error macOS builds require a Darwin host (got $(HOST_OS))) endif -# --- Windows ----------------------------------------------------------------- - -# Native Windows builds produce both the NSIS setup.exe and the WiX MSI. -# Cross-builds from macOS/Linux can only produce NSIS (MSI needs WiX on Windows). -WIN_BUNDLES ?= nsis,msi - -build-windows: ensure-frontend ensure-windows-target +build-windows: ifeq ($(IS_WINDOWS),1) - $(TAURI) build $(TAURI_BUILD_FLAGS) --target $(WIN_TARGET) --bundles $(WIN_BUNDLES) -else ifeq ($(HOST_OS),Darwin) - @$(MAKE) ensure-cargo-xwin - $(TAURI) build $(TAURI_BUILD_FLAGS) --runner $(CARGO_XWIN) --target $(WIN_TARGET) --bundles nsis -else ifeq ($(HOST_OS),Linux) - @$(MAKE) ensure-cargo-xwin - $(TAURI) build $(TAURI_BUILD_FLAGS) --runner $(CARGO_XWIN) --target $(WIN_TARGET) --bundles nsis + $(CARGO) build --manifest-path app/Cargo.toml --release --bin Grimoire --target $(WIN_TARGET) else - $(error Unsupported host for Windows builds: $(HOST_OS)) + $(error Windows builds require a Windows host (got $(HOST_OS))) endif -# --- Linux ------------------------------------------------------------------- - -build-linux: ensure-frontend ensure-linux-target +build-linux: ifeq ($(HOST_OS),Linux) - $(TAURI) build $(TAURI_BUILD_FLAGS) --target $(LINUX_TARGET) + $(CARGO) build --manifest-path app/Cargo.toml --release --bin Grimoire --target $(LINUX_TARGET) else - $(error Linux builds require a Linux host with WebKitGTK and related deps (got $(HOST_OS))) + $(error Linux builds require a Linux host (got $(HOST_OS))) endif -build-linux-arm: ensure-frontend -ifeq ($(HOST_OS),Linux) - @$(RUSTUP) target add $(LINUX_ARM_TARGET) - $(TAURI) build $(TAURI_BUILD_FLAGS) --target $(LINUX_ARM_TARGET) -else - $(error Linux builds require a Linux host with WebKitGTK and related deps (got $(HOST_OS))) -endif - -# --- cleanup ----------------------------------------------------------------- - clean: - rm -rf build .svelte-kit - cd src-tauri && cargo clean + cd app && $(CARGO) clean + rm -rf dist + +define sign-and-package + set -euo pipefail; \ + if [ -f "$(ENV_FILE)" ]; then \ + echo "Loading $(ENV_FILE)"; \ + set -a; \ + . "./$(ENV_FILE)"; \ + set +a; \ + fi; \ + chmod +x .github/scripts/package-macos.sh; \ + .github/scripts/package-macos.sh "$(1)" dist "$(2)" +endef diff --git a/README.md b/README.md index a716d1d..28a81d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Grimoire -Cross-platform desktop eBook library manager built with [Tauri 2](https://v2.tauri.app/) and [SvelteKit](https://svelte.dev/). Local SQLite catalog, Calibre-style on-disk library layout, and USB/MTP Kindle sync (including Kindle Colorsoft). +Cross-platform desktop eBook library manager. The window is a native [GPUI](https://gpui-kit.com/) app. The catalog is a local SQLite database with a Calibre-style layout on disk, plus USB/MTP sync for Kindles and other eReaders. ## Features (v0.1) @@ -18,46 +18,41 @@ Cross-platform desktop eBook library manager built with [Tauri 2](https://v2.tau ## Requirements -- Node.js 20+ -- pnpm -- Rust toolchain (for Tauri) -- Platform Tauri dependencies ([guide](https://v2.tauri.app/start/prerequisites/)) +- Rust toolchain ## Develop ```bash -pnpm install -pnpm tauri dev +cargo run --manifest-path app/Cargo.toml --bin Grimoire ``` ## Build ```bash -pnpm tauri build +cargo build --manifest-path app/Cargo.toml --release --bin Grimoire ``` -Or use the Makefile for production platform builds: +Or use the Makefile: ```bash -make build # current host -make build-mac # macOS universal (loads .env for Apple signing if present) -make build-windows # Windows x64 +make build # release binary for this machine +make build-mac # macOS disk image (loads .env for Apple signing if present) +make build-windows # Windows x64 (Windows host) make build-linux # Linux x64 (Linux host) ``` For a signed and notarized local macOS build, copy `.env.example` to `.env`, fill in the Apple values, then run `make build-mac`. See that file for variable notes. -Run `make help` for the full target list. Artifacts are under `src-tauri/target/.../release/bundle/`. +Run `make help` for the full target list. Disk images land in `dist/`. The release binary is under `app/target/release/`. ## Test ```bash -make test # Vitest + cargo test -make test-frontend # frontend unit tests -make test-rust # Rust unit/integration tests +make test +cargo test --manifest-path app/Cargo.toml ``` -Or: `pnpm test`, `pnpm test:rust`, `pnpm test:all`. +The tests cover the catalog, conversion, Kindle detection, and the GPUI theme and reader-font choices. ## Release pipeline (GitHub Actions) @@ -69,7 +64,7 @@ Every pull request runs the test suite. When a PR is **merged**, or when you run - Title starts with `MINOR` → minor bump (`1.2.3` → `1.3.0`) - Title starts with `PATCH`, or anything else → patch bump (`1.2.3` → `1.2.4`) 3. Creates a draft `vX.Y.Z` GitHub Release (notes from commit subjects since the previous tag) -4. Builds production bundles for macOS (arm64 + x64, signed and notarized), Linux x64, and Windows x64 (NSIS setup.exe **and** MSI), then attaches them to that draft +4. Builds release binaries for macOS (arm64 + x64 disk images, signed and notarized), Linux x64, and Windows x64, then attaches them to that draft 5. Publishes the release (immutable releases lock assets only after publish) Example titles: `MINOR add dark mode default`, `MAJOR redesign library schema`, `add filter sidebar` (patch). @@ -94,21 +89,17 @@ macOS release jobs sign with a **Developer ID Application** certificate and nota | --- | --- | | `APPLE_CERTIFICATE` | Base64-encoded `.p12` (`openssl base64 -A -in cert.p12 -out cert-base64.txt`) | | `APPLE_CERTIFICATE_PASSWORD` | Password used when exporting the `.p12` | -| `APPLE_SIGNING_IDENTITY` | Full identity string from `security find-identity -v -p codesigning` | | `APPLE_API_KEY` | App Store Connect API Key ID | | `APPLE_API_ISSUER` | App Store Connect Issuer ID | | `APPLE_API_KEY_CONTENTS` | Full contents of the downloaded `.p8` private key | -See [Tauri macOS code signing](https://v2.tauri.app/distribute/sign/macos/) for creating the certificate and API key. Signed and notarized DMGs open normally; drag Grimoire into Applications. +CI reads the signing identity from `APPLE_CERTIFICATE`, so do not set `APPLE_SIGNING_IDENTITY` in Actions (a mismatch fails the build). For local Keychain signing, set `APPLE_SIGNING_IDENTITY` in `.env` instead. -### Windows installers and library path +Create the Developer ID certificate and the App Store Connect API key in [Apple Developer](https://developer.apple.com/account) and [App Store Connect](https://appstoreconnect.apple.com/access/integrations/api). Signed and notarized DMGs open normally; drag Grimoire into Applications. -Windows releases include both: - -- **NSIS** (`*-setup.exe`): interactive installs ask for the ebook library folder (default `Documents\Grimoire`). Silent/passive installs use that default, or `/LIBRARYPATH=D:\Books`. -- **MSI** (WiX): seeds the same default library path. Override with `msiexec /i Grimoire_*.msi LIBRARYPATH="D:\Books"`. +### Windows -Both write `%APPDATA%\grimoire\config.json` only when that file does not already exist, so upgrades keep an existing library path. +Windows releases are the `Grimoire.exe` binary. Choose the library folder in Settings. Grimoire writes `%APPDATA%\grimoire\config.json` and keeps that library path on later launches. ## Library layout @@ -158,10 +149,7 @@ Other USB devices that identify as an eReader or ebook device may appear as a ge ### Device detection -The Devices page keeps watching while the app is open: - -- **MTP hotplug:** connect/disconnect shows a toast; Devices and Send update quietly (no forced navigation) -- **Mounted volumes:** polled every few seconds for Kobo (`.kobo`), Kindle (`documents` + `system`), reMarkable markers, and volume names that match known brands +The Devices page checks for connected readers every few seconds. A notice appears when a reader connects or disconnects. Detection covers USB MTP devices and mounted volumes (Kobo, Kindle, reMarkable, and volume names that match known brands). ### Device library @@ -193,9 +181,9 @@ Kindles usually appear through the Windows Portable Devices stack with no extra ## Project layout -- `src/` - SvelteKit SPA UI -- `src-tauri/` - Rust backend (SQLite, import, conversion, MTP) -- `src-tauri/migrations/` - SQL schema migrations +- `app/` - Rust app (GPUI window, SQLite catalog, import, conversion, MTP) +- `app/migrations/` - SQL schema migrations +- `static/` - in-app icon artwork ## License diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..aba21e2 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,3 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ diff --git a/src-tauri/Cargo.lock b/app/Cargo.lock similarity index 61% rename from src-tauri/Cargo.lock rename to app/Cargo.lock index 5eb14c0..dac0a50 100644 --- a/src-tauri/Cargo.lock +++ b/app/Cargo.lock @@ -2,19 +2,120 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "enumn", + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023da0e5097f46df7092d5280b02efb9bbf8d93298daeced42652463e357d636" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "phf 0.13.1", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "objc2 0.5.3", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e156ed3802e35eefe894ef2671bc6c889303d8a7e110b5e1b48f504b91362f" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106c2b961215864d1c2e703ee63269c25c4e80a577ffb2c1017b9c17dcdf83a1" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", + "zeroize", +] + [[package]] name = "aes" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" dependencies = [ - "cipher", + "cipher 0.5.2", "cpubits", "cpufeatures 0.3.1", "zeroize", @@ -38,6 +139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "const-random", "getrandom 0.3.4", "once_cell", "version_check", @@ -72,27 +174,29 @@ dependencies = [ ] [[package]] -name = "alloc-no-stdlib" -version = "2.0.4" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "alloc-stdlib" -version = "0.2.4" +name = "android_system_properties" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ - "alloc-no-stdlib", + "libc", ] [[package]] -name = "android_system_properties" -version = "0.1.6" +name = "annotate-snippets" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" dependencies = [ - "libc", + "anstyle", + "memchr", + "unicode-width", ] [[package]] @@ -160,6 +264,15 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -171,12 +284,30 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + [[package]] name = "as-slice" version = "0.2.1" @@ -186,6 +317,55 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading 0.8.9", +] + +[[package]] +name = "ashpd" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.5", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "ashpd" +version = "0.13.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8421aaa9644a5faf26735f258b669b15f063313ef8f8e2bdb28912a1a6f111" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "getrandom 0.4.3", + "serde", + "serde_repr", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -210,6 +390,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee19bd99b43e3691acbad4e840420a4881cea6c0b66a208125a824f8fd53f5a1" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-io", + "pin-project-lite", +] + [[package]] name = "async-executor" version = "1.14.0" @@ -224,6 +416,17 @@ dependencies = [ "slab", ] +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + [[package]] name = "async-io" version = "2.6.0" @@ -253,6 +456,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + [[package]] name = "async-process" version = "2.5.0" @@ -318,33 +532,53 @@ dependencies = [ ] [[package]] -name = "atk" -version = "0.18.2" +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" dependencies = [ - "atk-sys", - "glib", - "libc", + "atspi-common", + "atspi-proxies", ] [[package]] -name = "atk-sys" -version = "0.18.2" +name = "atspi-common" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", ] [[package]] -name = "atomic-waker" -version = "1.1.2" +name = "atspi-proxies" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] [[package]] name = "autocfg" @@ -381,7 +615,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 8.0.0", "num-rational", "v_frame", ] @@ -396,10 +630,25 @@ dependencies = [ ] [[package]] -name = "base64" -version = "0.21.7" +name = "backtrace" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide 0.8.9", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "base62" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a647dce9631f152a99ec16cfc09267ae55fe280d5dc288964027d458076323" [[package]] name = "base64" @@ -422,20 +671,40 @@ dependencies = [ "simd-abstraction", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.13.2", + "cexpr", + "clang-sys", + "itertools 0.10.5", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + [[package]] name = "bit-set" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" [[package]] name = "bit_field" @@ -454,9 +723,6 @@ name = "bitflags" version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" -dependencies = [ - "serde_core", -] [[package]] name = "bitstream-io" @@ -479,6 +745,12 @@ dependencies = [ "wyz", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -497,13 +769,31 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.3", +] + [[package]] name = "block2" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2", + "objc2 0.6.4", ] [[package]] @@ -520,33 +810,13 @@ dependencies = [ ] [[package]] -name = "brotli" -version = "8.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bs58" -version = "0.5.1" +name = "bstr" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" dependencies = [ - "tinyvec", + "memchr", + "serde_core", ] [[package]] @@ -588,6 +858,20 @@ name = "bytemuck" version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] [[package]] name = "byteorder" @@ -606,75 +890,65 @@ name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -dependencies = [ - "serde", -] [[package]] -name = "cairo-rs" -version = "0.18.5" +name = "bzip2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" dependencies = [ - "bitflags 2.13.2", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", + "libbz2-rs-sys", ] [[package]] -name = "cairo-sys-rs" -version = "0.18.2" +name = "calloop" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e" dependencies = [ - "glib-sys", - "libc", - "system-deps", + "bitflags 2.13.2", + "polling", + "rustix", + "tracing", ] [[package]] -name = "camino" -version = "1.2.5" +name = "calloop-wayland-source" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ - "serde_core", + "calloop", + "rustix", + "wayland-backend", + "wayland-client", ] [[package]] -name = "cargo-platform" -version = "0.1.9" +name = "cbc" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "serde", + "cipher 0.4.4", ] [[package]] -name = "cargo_metadata" -version = "0.19.2" +name = "cbindgen" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" dependencies = [ - "camino", - "cargo-platform", - "semver", + "heck 0.4.1", + "indexmap 2.14.2", + "log", + "proc-macro2", + "quote", "serde", "serde_json", - "thiserror 2.0.20", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", + "syn 2.0.119", + "tempfile", + "toml 0.8.23", ] [[package]] @@ -686,34 +960,16 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", + "shlex 2.0.1", ] [[package]] -name = "cfg-expr" -version = "0.15.8" +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "smallvec", - "target-lexicon", + "nom 7.1.3", ] [[package]] @@ -728,6 +984,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + [[package]] name = "chacha20" version = "0.10.2" @@ -753,6 +1018,17 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout 0.1.4", + "zeroize", +] + [[package]] name = "cipher" version = "0.5.2" @@ -760,7 +1036,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "crypto-common 0.2.2", - "inout", + "inout 0.2.2", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", ] [[package]] @@ -809,6 +1096,76 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" +dependencies = [ + "bitflags 2.13.2", + "block", + "cocoa-foundation 0.2.1", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.13.2", + "block", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -822,15 +1179,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "combine" -version = "4.6.8" +name = "compression-codecs" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +checksum = "98fc98460ba0ad5317075d3632b8dfc45d0be8c4a49347c2a38272019717614a" dependencies = [ - "bytes", + "bzip2", + "compression-core", + "flate2", "memchr", ] +[[package]] +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -840,12 +1205,42 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-oid" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + [[package]] name = "const-str" version = "1.1.0" @@ -862,13 +1257,22 @@ dependencies = [ ] [[package]] -name = "cookie" -version = "0.18.2" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ - "time", - "version_check", + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -889,17 +1293,54 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.25.0" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" dependencies = [ "bitflags 2.13.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", "foreign-types", "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + [[package]] name = "core-graphics-types" version = "0.2.0" @@ -907,8 +1348,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.13.2", - "core-foundation", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-graphics2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146" +dependencies = [ + "bitflags 2.13.2", + "block", + "cfg-if", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-text" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" +dependencies = [ + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139679cc63eb9504bdbe37e37874b0247136177655f0008588781e90863afa62" +dependencies = [ + "block", + "core-foundation 0.10.1", + "core-graphics2", + "io-surface", "libc", + "metal", +] + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-text" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" +dependencies = [ + "bitflags 2.13.2", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 2.1.3", + "self_cell", + "skrifa 0.40.0", + "smol_str", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", ] [[package]] @@ -944,15 +1463,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.8" @@ -972,6 +1482,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.23" @@ -1060,20 +1579,14 @@ dependencies = [ [[package]] name = "ctor" -version = "0.8.0" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" dependencies = [ - "ctor-proc-macro", - "dtor", + "link-section", + "linktime-proc-macro", ] -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" - [[package]] name = "ctutils" version = "0.4.2" @@ -1083,40 +1596,6 @@ dependencies = [ "cmov", ] -[[package]] -name = "darling" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 3.0.5", -] - -[[package]] -name = "darling_macro" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" -dependencies = [ - "darling_core", - "quote", - "syn 3.0.5", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -1146,55 +1625,16 @@ dependencies = [ ] [[package]] -name = "dbus" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" -dependencies = [ - "libc", - "libdbus-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "defmt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" -dependencies = [ - "defmt-parser", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" +name = "data-url" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror 2.0.20", -] +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "serde_core", -] [[package]] name = "derive_arbitrary" @@ -1222,10 +1662,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case 0.10.0", "proc-macro2", "quote", "rustc_version", "syn 2.0.119", + "unicode-xid", ] [[package]] @@ -1236,6 +1678,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common 0.1.7", + "subtle", ] [[package]] @@ -1250,13 +1693,34 @@ dependencies = [ "ctutils", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", ] [[package]] @@ -1267,10 +1731,16 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.5.2", "windows-sys 0.61.2", ] +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + [[package]] name = "dispatch2" version = "0.3.1" @@ -1278,9 +1748,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ "bitflags 2.13.2", - "block2", + "block2 0.6.2", "libc", - "objc2", + "objc2 0.6.4", ] [[package]] @@ -1295,51 +1765,28 @@ dependencies = [ ] [[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" +name = "dlib" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "libloading 0.7.4", ] [[package]] -name = "dom_query" -version = "0.27.0" +name = "document-features" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ - "bit-set", - "cssparser 0.36.0", - "foldhash", - "html5ever 0.38.0", - "precomputed-hash", - "selectors", - "tendril", + "litrs", ] [[package]] -name = "dpi" -version = "0.1.2" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dtoa" @@ -1356,27 +1803,24 @@ dependencies = [ "dtoa", ] -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + [[package]] name = "dyn-clone" version = "1.0.20" @@ -1409,12 +1853,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - [[package]] name = "encoding" version = "0.2.33" @@ -1479,12 +1917,55 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "encoding_rs_io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" +dependencies = [ + "encoding_rs", +] + [[package]] name = "endi" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "enumflags2" version = "0.7.12" @@ -1506,6 +1987,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "equator" version = "0.4.2" @@ -1553,6 +2045,25 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "5.4.2" @@ -1607,6 +2118,9 @@ name = "fastrand" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +dependencies = [ + "getrandom 0.4.3", +] [[package]] name = "fax" @@ -1624,13 +2138,14 @@ dependencies = [ ] [[package]] -name = "field-offset" -version = "0.3.6" +name = "filedescriptor" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" dependencies = [ - "memoffset", - "rustc_version", + "libc", + "thiserror 1.0.69", + "winapi", ] [[package]] @@ -1639,6 +2154,12 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.10" @@ -1650,18 +2171,104 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "flume" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" +dependencies = [ + "fastrand", + "futures-core", + "futures-sink", + "spin 0.9.9", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "font-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1698,12 +2305,42 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.34" @@ -1729,6 +2366,19 @@ dependencies = [ "futures-sink", ] +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + [[package]] name = "futures-core" version = "0.3.34" @@ -1812,266 +2462,844 @@ dependencies = [ ] [[package]] -name = "gdk" -version = "0.18.2" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", + "typenum", + "version_check", ] [[package]] -name = "gdk-pixbuf" -version = "0.18.5" +name = "gethostname" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", + "rustix", + "windows-link 0.2.1", ] [[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" +name = "getopts" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", "libc", - "system-deps", + "wasi", + "wasm-bindgen", ] [[package]] -name = "gdk-sys" -version = "0.18.2" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "cfg-if", + "js-sys", "libc", - "pango-sys", - "pkg-config", - "system-deps", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", ] [[package]] -name = "gdkwayland-sys" -version = "0.18.2" +name = "getrandom" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", + "cfg-if", + "js-sys", "libc", - "pkg-config", - "system-deps", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.20", + "windows 0.62.2", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.2", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "gpui-base" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf698677b253118459f552fe15513858c669d79a25837a94e480db2ae330050" +dependencies = [ + "aho-corasick", + "anyhow", + "ashpd 0.13.13", + "async-channel", + "chrono", + "data-url 0.3.2", + "futures", + "gpui-pre", + "gpui-pre-macros", + "gpui-pre-sum-tree", + "html5ever 0.27.0", + "instant", + "itertools 0.13.0", + "lsp-types", + "markdown", + "markup5ever_rcdom", + "num-traits", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "raw-window-handle", + "regex", + "ropey", + "schemars", + "serde", + "serde_json", + "smallvec", + "smol", + "tracing", + "unicode-linebreak", + "unicode-segmentation", + "web-time", + "windows 0.58.0", +] + +[[package]] +name = "gpui-component" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc8b7def8e74b0d822d3e96f6b28fb5a2c20eb7a83d30d22fa59ca8088d1159b" +dependencies = [ + "anyhow", + "chrono", + "core-text", + "enum-iterator", + "gpui-base", + "gpui-component-macros", + "gpui-kit-assets", + "gpui-pre", + "gpui-pre-macros", + "gpui-pre-sum-tree", + "instant", + "itertools 0.13.0", + "log", + "lsp-types", + "markdown", + "notify", + "num-traits", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "once_cell", + "paste", + "raw-window-handle", + "regex", + "resvg 0.45.1", + "ropey", + "rust-i18n", + "schemars", + "serde", + "serde_json", + "serde_repr", + "smallvec", + "smol", + "tracing", + "uuid", + "windows 0.58.0", +] + +[[package]] +name = "gpui-component-macros" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2968cc7f4a2bd8f51b09cfe4f1e77409951b48a0e2d48f372b010e69c3d7fb49" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-kit" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be18e0417095ebfea77ac25962961ff4e41a120ea6cb8f05a7505b682478b83e" +dependencies = [ + "gpui-base", + "gpui-component", + "gpui-kit-assets", + "gpui-pre", + "gpui-pre-platform", + "gpui-pre-web", +] + +[[package]] +name = "gpui-kit-assets" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6c481ca987f1e6f10b1aed4d47f1b301cd1dc6f3016fac93c305f2a4f307c0" +dependencies = [ + "anyhow", + "gpui-pre", + "gpui-pre-reqwest", + "log", + "rust-embed", + "wasm-bindgen", + "wasm-bindgen-futures", ] [[package]] -name = "gdkx11" -version = "0.18.2" +name = "gpui-pre" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +checksum = "d7f0bc3ebba070f8981b3f8e905a12f47d27794a30d029e59cdb9edf7a4b3360" dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", + "accesskit", + "anyhow", + "async-channel", + "async-task", + "backtrace", + "bindgen", + "bitflags 2.13.2", + "chrono", + "core-video", + "ctor", + "derive_more", + "embed-resource", + "etagere", + "futures", + "futures-concurrency", + "getrandom 0.3.4", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-macros", + "gpui-pre-refineable", + "gpui-pre-scheduler", + "gpui-pre-shared-string", + "gpui-pre-sum-tree", + "gpui-pre-util", + "gpui-pre-util-macros", + "heapless", + "image", + "inventory", + "itertools 0.14.0", "libc", - "x11", + "log", + "lyon", + "num_cpus", + "objc2-core-foundation", + "objc2-core-video", + "parking", + "parking_lot", + "pin-project", + "pollster 0.4.0", + "postage", + "profiling", + "rand 0.9.5", + "raw-window-handle", + "regex", + "resvg 0.46.0", + "schemars", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "spin 0.10.1", + "strum", + "taffy", + "thiserror 2.0.20", + "ttf-parser", + "url", + "usvg 0.46.0", + "uuid", + "waker-fn", + "web-time", + "windows 0.62.2", + "zed-font-kit", + "zed-scap", ] [[package]] -name = "gdkx11-sys" -version = "0.18.2" +name = "gpui-pre-apple" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +checksum = "bfdd914311cd12894367c8b9908947bb0c27fd42f8b9a5ff7a0730551a83fa4c" dependencies = [ - "gdk-sys", - "glib-sys", + "anyhow", + "async-task", + "block2 0.6.2", + "cbindgen", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "core-video", + "derive_more", + "dispatch2", + "etagere", + "foreign-types", + "gpui-pre", + "gpui-pre-util", + "image", "libc", - "system-deps", - "x11", + "log", + "mach2", + "metal", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "parking_lot", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "gpui-pre-collections" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "2c060863025dc3b8e90febad2d1f4ffd23a5d0dcedba39d3f6b7820ea2fdd3a0" dependencies = [ - "typenum", - "version_check", + "gpui-pre-util", + "indexmap 2.14.2", + "rustc-hash 2.1.3", +] + +[[package]] +name = "gpui-pre-derive-refineable" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc834f30f5f49791e214f1fde01a009c7de91c9cd0a9f31dabfed130ba39e6cf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-http-client" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2935cf82333da3f41774e5757837756408f01356197b41c817ad2666034453" +dependencies = [ + "anyhow", + "async-compression", + "bytes", + "derive_more", + "futures", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "gpui-pre-linux" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1131746fad5c87b5de74dc9c73105655b0c820a2dd4bb247586573e7cf5720e" +dependencies = [ + "accesskit", + "accesskit_unix", + "anyhow", + "as-raw-xcb-connection", + "ashpd 0.13.13", + "bytemuck", + "calloop", + "calloop-wayland-source", + "filedescriptor", + "futures", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-http-client", + "gpui-pre-util", + "gpui-pre-wgpu", + "libc", + "log", + "notify-rust", + "oo7", + "open", + "parking_lot", + "raw-window-handle", + "smallvec", + "smol", + "strum", + "url", + "uuid", + "wayland-backend", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-plasma", + "wayland-protocols-wlr", + "x11rb", + "xkbcommon", + "zed-scap", + "zed-xim", +] + +[[package]] +name = "gpui-pre-macos" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d756813ff1cd741a502af60fd18869e0677795936d7349642b41c49f43120c0" +dependencies = [ + "accesskit", + "accesskit_macos", + "anyhow", + "block2 0.6.2", + "cocoa 0.26.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "core-video", + "ctor", + "dispatch2", + "foreign-types", + "futures", + "gpui-pre", + "gpui-pre-apple", + "gpui-pre-collections", + "gpui-pre-scheduler", + "gpui-pre-util", + "image", + "itertools 0.14.0", + "log", + "objc", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-graphics", + "objc2-core-media", + "objc2-foundation 0.3.2", + "objc2-screen-capture-kit", + "objc2-user-notifications", + "parking_lot", + "pathfinder_geometry", + "raw-window-handle", + "semver", + "smallvec", + "strum", + "uuid", + "zed-font-kit", +] + +[[package]] +name = "gpui-pre-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad26ca759364694cb55487bebff4c1b7dd6accab49990ff3cda6f7a818a9f4" +dependencies = [ + "heck 0.5.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-pre-perf" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcb288a506a476b6cbf97e6ec1cc0118db43f0ca91087588afa1c2d8cd4f842" +dependencies = [ + "gpui-pre-collections", + "serde", + "serde_json", +] + +[[package]] +name = "gpui-pre-platform" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e200b7b78836f1a0fc70767d971768e88f29d8b91b697285c15fb8e373635a3e" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "gpui-pre", + "gpui-pre-linux", + "gpui-pre-macos", + "gpui-pre-web", + "gpui-pre-windows", +] + +[[package]] +name = "gpui-pre-refineable" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4042345bb5bd3ecbc5d15b21ed128154f462a992058d7038cd397631f5462338" +dependencies = [ + "gpui-pre-derive-refineable", +] + +[[package]] +name = "gpui-pre-reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05be23908e707966824f8c51a609904b7f30739e8d915e120a53c1b995ba964c" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry 0.4.0", ] [[package]] -name = "getopts" -version = "0.2.24" +name = "gpui-pre-scheduler" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +checksum = "f4acd73345bc3c09f1b98c1b21eb4459ad8336f89bb2edcef619e11c5a08456e" dependencies = [ - "unicode-width", + "async-task", + "backtrace", + "chrono", + "flume", + "futures", + "parking_lot", + "rand 0.9.5", + "wasm_thread", + "web-time", ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "gpui-pre-shared-string" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "477529fd5aa90bff969785ee9a3938484d94aa5101cdf7b84719629c0345f98a" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", + "schemars", + "serde", + "smol_str", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "gpui-pre-sum-tree" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "9ec0ec176d576b82aed716cf47185856b54ea9528caaf9d01787024689bfd824" dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", + "gpui-pre-ztracing", + "heapless", + "log", + "rayon", + "tracing", ] [[package]] -name = "getrandom" -version = "0.4.3" +name = "gpui-pre-util" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +checksum = "e1635ae7d0b05400bfa211ddef6c67dc8d9f223c463022c2b3bf2c1c14c6044f" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", - "wasm-bindgen", + "anyhow", + "log", + "which", ] [[package]] -name = "gif" -version = "0.14.2" +name = "gpui-pre-util-macros" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +checksum = "01486fce7722e5d7d7e7bb71c719dfdcc6ed917112bdc7c56d0556fdb60d10ab" dependencies = [ - "color_quant", - "weezl", + "gpui-pre-perf", + "quote", + "syn 2.0.119", ] [[package]] -name = "gio" -version = "0.18.4" +name = "gpui-pre-web" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "184776d0763e6889dd2842dcb1204c15055945f2bdfe926b5ef57bdbd1952a65" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", + "anyhow", + "console_error_panic_hook", + "futures", + "gpui-pre", + "gpui-pre-http-client", + "gpui-pre-scheduler", + "gpui-pre-wgpu", + "js-sys", + "log", + "parking_lot", + "raw-window-handle", "smallvec", - "thiserror 1.0.69", + "unicode-properties", + "unicode-script", + "unicode-segmentation", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_thread", + "web-sys", + "web-time", ] [[package]] -name = "gio-sys" -version = "0.18.1" +name = "gpui-pre-wgpu" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +checksum = "439c5421cafe1a7cf68d0c0ae5cc82b26fad6b0e6345ac98d20acaf1f2463914" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", + "anyhow", + "bytemuck", + "cosmic-text", + "etagere", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-util", + "itertools 0.14.0", + "log", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "swash", + "unicode-bidi", + "unicode-segmentation", + "web-sys", + "wgpu", + "zed-font-kit", ] [[package]] -name = "glib" -version = "0.18.5" +name = "gpui-pre-windows" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +checksum = "371f9bb13f97e3bea2f8b8e5e81232bb74d735153c244ae67f7400e743925b1c" dependencies = [ - "bitflags 2.13.2", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", + "accesskit", + "accesskit_windows", + "anyhow", + "dunce", + "etagere", + "futures", + "gpui-pre", + "gpui-pre-collections", + "gpui-pre-scheduler", + "gpui-pre-util", + "image", + "itertools 0.14.0", + "log", + "parking_lot", + "rand 0.9.5", + "raw-window-handle", "smallvec", - "thiserror 1.0.69", + "uuid", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-numerics 0.3.1", + "windows-registry 0.6.1", ] [[package]] -name = "glib-macros" -version = "0.18.5" +name = "gpui-pre-zlog" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +checksum = "3bbe5b98630ef4bfed5a7105ec1062e78d571912bc491388f07ea22f2457c18b" dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", + "anyhow", + "chrono", + "gpui-pre-collections", + "log", ] [[package]] -name = "glib-sys" -version = "0.18.1" +name = "gpui-pre-ztracing" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +checksum = "f806400f6bda1fdd9fc627e158c0e44fb0c4b7767582a3621efcf92f4a850db7" dependencies = [ - "libc", - "system-deps", + "gpui-pre-zlog", + "gpui-pre-ztracing-macro", + "tracing", + "tracing-subscriber", ] [[package]] -name = "glob" -version = "0.3.4" +name = "gpui-pre-ztracing-macro" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" +checksum = "d337c9acf058238e45ce0570d8a5958439242889492f91ae2d8e324211a61ccd" [[package]] -name = "gobject-sys" -version = "0.18.0" +name = "granit-parser" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +checksum = "e20f99e46474f56bd905c56e817ebddcf377a611f94c53ac4649e4d3fa3c0cd0" dependencies = [ - "glib-sys", - "libc", - "system-deps", + "arraydeque", + "smallvec", ] [[package]] @@ -2082,23 +3310,26 @@ dependencies = [ "base64 0.22.1", "bytes", "chrono", - "dirs", + "dirs 6.0.0", + "embed-resource", "futures", + "gpui-kit", + "image", "kindling-mobi", "libc", "mobi", "mtp-rs", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", "parking_lot", "rars", - "reqwest 0.12.28", + "reqwest", + "rfd", "rusqlite", "sanitize-filename", "serde", "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-dialog", - "tauri-plugin-opener", "thiserror 2.0.20", "tokio", "uuid", @@ -2106,66 +3337,56 @@ dependencies = [ ] [[package]] -name = "gtk" -version = "0.18.2" +name = "h2" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.2", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "gtk-sys" -version = "0.18.2" +name = "half" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", ] [[package]] -name = "gtk3-macros" -version = "0.18.2" +name = "harfrust" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.119", + "bitflags 2.13.2", + "bytemuck", + "core_maths", + "read-fonts 0.37.0", + "smallvec", ] [[package]] -name = "half" -version = "2.7.1" +name = "hash32" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", + "byteorder", ] [[package]] @@ -2186,6 +3407,26 @@ dependencies = [ "ahash 0.8.12", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -2201,6 +3442,16 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -2225,6 +3476,30 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "hmac" version = "0.13.0" @@ -2236,12 +3511,16 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.38.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" dependencies = [ "log", - "markup5ever 0.38.0", + "mac", + "markup5ever 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -2287,12 +3566,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - [[package]] name = "httparse" version = "1.10.1" @@ -2318,6 +3591,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -2338,6 +3612,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", @@ -2391,16 +3666,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ico" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png 0.17.16", -] - [[package]] name = "icu_collections" version = "2.3.0" @@ -2484,12 +3749,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -2511,6 +3770,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.25.10" @@ -2521,7 +3796,7 @@ dependencies = [ "byteorder-lite", "color_quant", "exr", - "gif", + "gif 0.14.2", "image-webp", "moxcms", "num-traits", @@ -2531,8 +3806,8 @@ dependencies = [ "rayon", "rgb", "tiff", - "zune-core", - "zune-jpeg", + "zune-core 0.5.3", + "zune-jpeg 0.5.15", ] [[package]] @@ -2545,6 +3820,18 @@ dependencies = [ "quick-error", ] +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + [[package]] name = "imgref" version = "1.12.3" @@ -2559,28 +3846,48 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde", ] [[package]] name = "indexmap" version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592" +dependencies = [ + "bitflags 2.13.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", + "libc", ] [[package]] -name = "infer" -version = "0.19.0" +name = "inout" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "cfb", + "block-padding", + "generic-array", ] [[package]] @@ -2592,6 +3899,18 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "interpolate_name" version = "0.2.4" @@ -2603,6 +3922,15 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "inventory" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6928282826c822ad91bf1c9a1cb90a30ba1c26770749929b4656cd6be829cd7c" +dependencies = [ + "rustversion", +] + [[package]] name = "io-kit-sys" version = "0.5.0" @@ -2613,6 +3941,18 @@ dependencies = [ "mach2", ] +[[package]] +name = "io-surface" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" +dependencies = [ + "cgl", + "core-foundation 0.10.1", + "core-foundation-sys", + "leaky-cow", +] + [[package]] name = "ipnet" version = "2.12.2" @@ -2655,110 +3995,36 @@ dependencies = [ [[package]] name = "itertools" -version = "0.14.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jiff" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" -dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "jiff-tzdb-platform", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", - "windows-link 0.2.1", -] - -[[package]] -name = "jiff-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" -dependencies = [ - "defmt", -] - -[[package]] -name = "jiff-static" -version = "0.2.35" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", + "either", ] [[package]] -name = "jiff-tzdb" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" - -[[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ - "jiff-tzdb", + "either", ] [[package]] -name = "jni" -version = "0.21.1" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni-sys" @@ -2810,37 +4076,21 @@ dependencies = [ ] [[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" +name = "khronos-egl" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ - "serde", - "serde_json", + "libc", + "libloading 0.8.9", + "pkg-config", ] [[package]] -name = "keyboard-types" -version = "0.7.0" +name = "khronos_api" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.13.2", - "serde", - "unicode-segmentation", -] +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kindling-mobi" @@ -2862,56 +4112,87 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "kqueue" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" +dependencies = [ + "kqueue-sys", + "libc", +] [[package]] -name = "lebe" -version = "0.5.3" +name = "kqueue-sys" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.2", + "libc", +] [[package]] -name = "libappindicator" -version = "0.9.0" +name = "kurbo" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", + "arrayvec", + "euclid", + "smallvec", ] [[package]] -name = "libappindicator-sys" -version = "0.9.0" +name = "kurbo" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" dependencies = [ - "gtk-sys", - "libloading", - "once_cell", + "arrayvec", + "euclid", + "polycool", + "smallvec", ] [[package]] -name = "libc" -version = "0.2.189" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libdbus-sys" -version = "0.2.7" +name = "leak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" + +[[package]] +name = "leaky-cow" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" dependencies = [ - "pkg-config", + "leak", ] +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + [[package]] name = "libfuzzer-sys" version = "0.4.13" @@ -2932,6 +4213,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libm" version = "0.2.16" @@ -2992,12 +4283,30 @@ version = "1.0.0-alpha.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12744d1279367caed41739ef094c325d53fb0ffcd4f9b84a368796f870252" dependencies = [ - "convert_case", + "convert_case 0.6.0", "proc-macro2", "quote", "syn 1.0.109", ] +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "link-section" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -3010,6 +4319,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -3023,23 +4338,112 @@ dependencies = [ name = "log" version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +dependencies = [ + "serde_core", + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lsp-types" +version = "0.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071" +dependencies = [ + "bitflags 1.3.2", + "fluent-uri", + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "lyon" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b8dcf906637ecef61b3c0740c7a4e7f27caeb31257cfac0cc579ce15be6005" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] -name = "loop9" -version = "0.1.5" +name = "mac-notification-sys" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" dependencies = [ - "imgref", + "cc", + "log", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "time", + "uuid", ] -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "mach2" version = "0.5.0" @@ -3049,15 +4453,37 @@ dependencies = [ "libc", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markdown" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" +dependencies = [ + "serde", + "unicode-id", +] + [[package]] name = "markup5ever" -version = "0.38.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ "log", - "tendril", - "web_atoms", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", + "tendril 0.4.3", ] [[package]] @@ -3067,10 +4493,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" dependencies = [ "log", - "tendril", + "tendril 0.5.1", "web_atoms", ] +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever 0.27.0", + "markup5ever 0.12.1", + "tendril 0.4.3", + "xml5ever", +] + [[package]] name = "matches" version = "0.1.10" @@ -3087,12 +4525,31 @@ dependencies = [ "rayon", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -3102,12 +4559,43 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.13.2", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -3135,6 +4623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] @@ -3178,39 +4667,35 @@ dependencies = [ ] [[package]] -name = "muda" -version = "0.19.3" +name = "multiversion_no_op" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png 0.18.1", - "serde", - "thiserror 2.0.20", - "windows-sys 0.61.2", -] +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" [[package]] -name = "ndk" -version = "0.9.0" +name = "naga" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +checksum = "b2bf919621e7975acb27d881bae2fb993e0d45c8e0446e85e6272971e00dc8df" dependencies = [ + "arrayvec", + "bit-set", "bitflags 2.13.2", - "jni-sys 0.3.1", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap 2.14.2", + "libm", "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.20", + "unicode-ident", ] [[package]] @@ -3237,6 +4722,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom" version = "8.0.0" @@ -3252,6 +4753,88 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "normpath" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11ce00d2594068e8a27c9146fdc5cf9f3ac38eb42c7cd34d05fea618873ac9f" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.13.2", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-rust" +version = "4.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4587364a9a0074333429b3df75a30a205340c56a536ca3eb6ca0e59b87bbf8af" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.8" @@ -3262,6 +4845,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" +dependencies = [ + "libm", + "num-integer", + "num-iter", + "num-traits", + "once_cell", + "rand 0.9.5", + "serde", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -3298,6 +4898,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -3316,6 +4926,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", ] [[package]] @@ -3334,7 +4955,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.119", @@ -3346,7 +4967,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18ef13beb3b3a8fc16fd7aea912ebd3d45dde00a9a5b968d0742297468065845" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "futures-core", "io-kit-sys", @@ -3363,37 +4984,131 @@ dependencies = [ ] [[package]] -name = "objc2" -version = "0.6.4" +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d5490aaf8f1d7cf7688dfa9b0ce07900e168852c45cd2c03f534dfd27cfd0b" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "libc", + "objc2 0.5.3", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "objc2-cloud-kit", + "objc2-core-data 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.2", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-audio" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" dependencies = [ - "objc2-encode", - "objc2-exception-helper", + "dispatch2", + "objc2 0.6.4", + "objc2-core-audio-types", + "objc2-core-foundation", ] [[package]] -name = "objc2-app-kit" +name = "objc2-core-audio-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" dependencies = [ "bitflags 2.13.2", - "block2", - "objc2", - "objc2-core-foundation", - "objc2-foundation", + "objc2 0.6.4", ] [[package]] -name = "objc2-cloud-kit" -version = "0.3.2" +name = "objc2-core-data" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.13.2", - "objc2", - "objc2-foundation", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", ] [[package]] @@ -3402,8 +5117,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ - "objc2", - "objc2-foundation", + "bitflags 2.13.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", ] [[package]] @@ -3414,7 +5130,7 @@ checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.2", "dispatch2", - "objc2", + "objc2 0.6.4", ] [[package]] @@ -3425,19 +5141,31 @@ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.13.2", "dispatch2", - "objc2", + "objc2 0.6.4", "objc2-core-foundation", "objc2-io-surface", ] +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + [[package]] name = "objc2-core-image" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.6.4", + "objc2-foundation 0.3.2", ] [[package]] @@ -3446,8 +5174,23 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" dependencies = [ - "objc2", - "objc2-foundation", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-media" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.4", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-core-video", ] [[package]] @@ -3457,9 +5200,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ "bitflags 2.13.2", - "objc2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", + "objc2-io-surface", ] [[package]] @@ -3469,12 +5225,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] -name = "objc2-exception-helper" -version = "0.1.1" +name = "objc2-foundation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "cc", + "bitflags 2.13.2", + "block2 0.5.1", + "libc", + "objc2 0.5.3", ] [[package]] @@ -3484,9 +5243,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.2", - "block2", + "block2 0.6.2", "libc", - "objc2", + "objc2 0.6.4", "objc2-core-foundation", ] @@ -3497,10 +5256,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.13.2", - "objc2", + "objc2 0.6.4", "objc2-core-foundation", ] +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + [[package]] name = "objc2-quartz-core" version = "0.3.2" @@ -3508,30 +5304,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.13.2", - "objc2", + "objc2 0.6.4", "objc2-core-foundation", - "objc2-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", ] [[package]] -name = "objc2-ui-kit" +name = "objc2-screen-capture-kit" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394" dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", + "block2 0.6.2", + "dispatch2", + "objc2 0.6.4", "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", + "objc2-core-media", + "objc2-foundation 0.3.2", ] [[package]] @@ -3540,22 +5330,38 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" dependencies = [ - "objc2", - "objc2-foundation", + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-location", + "objc2-foundation 0.3.2", ] [[package]] -name = "objc2-web-kit" -version = "0.3.2" +name = "objc_exception" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", ] [[package]] @@ -3570,23 +5376,72 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oo7" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f2bfed90f1618b4b48dcad9307f25e14ae894e2949642c87c351601d62cebd" +dependencies = [ + "aes 0.8.4", + "ashpd 0.13.13", + "async-fs", + "async-io", + "async-lock", + "blocking", + "cbc", + "cipher 0.4.4", + "digest 0.10.7", + "endi", + "futures-lite", + "futures-util", + "getrandom 0.4.3", + "hkdf", + "hmac 0.12.1", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "serde", + "serde_bytes", + "sha2 0.10.9", + "subtle", + "zbus", + "zbus_macros", + "zeroize", + "zvariant", +] + [[package]] name = "open" version = "5.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" dependencies = [ - "dunce", "is-wsl", "libc", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3603,31 +5458,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "parcel_selectors" version = "0.28.3" @@ -3640,7 +5470,7 @@ dependencies = [ "phf 0.11.3", "phf_codegen 0.11.3", "precomputed-hash", - "rustc-hash", + "rustc-hash 2.1.3", "smallvec", ] @@ -3651,7 +5481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" dependencies = [ "base64-simd", - "data-url", + "data-url 0.1.1", "rkyv", "serde", "serde_json", @@ -3705,6 +5535,35 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3802,6 +5661,32 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -3825,19 +5710,6 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" -[[package]] -name = "plist" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" -dependencies = [ - "base64 0.23.1", - "indexmap 2.14.2", - "quick-xml 0.42.0", - "serde", - "time", -] - [[package]] name = "png" version = "0.17.16" @@ -3878,6 +5750,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + [[package]] name = "portable-atomic" version = "1.15.0" @@ -3893,6 +5786,23 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic", + "crossbeam-queue", + "futures", + "log", + "parking_lot", + "pin-project", + "pollster 0.2.5", + "static_assertions", + "thiserror 1.0.69", +] + [[package]] name = "potential_utf" version = "0.1.6" @@ -3924,23 +5834,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] -name = "proc-macro-crate" -version = "1.3.1" +name = "presser" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] -name = "proc-macro-crate" -version = "2.0.2" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", + "proc-macro2", + "syn 2.0.119", ] [[package]] @@ -3952,30 +5858,6 @@ dependencies = [ "toml_edit 0.25.15+spec-1.1.0", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -4079,9 +5961,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.42.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] @@ -4097,7 +5979,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.3", "rustls", "socket2", "thiserror 2.0.20", @@ -4118,7 +6000,7 @@ dependencies = [ "rand 0.10.2", "rand_pcg", "ring", - "rustc-hash", + "rustc-hash 2.1.3", "rustls", "rustls-pki-types", "slab", @@ -4175,6 +6057,8 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ + "libc", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -4184,7 +6068,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.5", ] @@ -4192,11 +6076,21 @@ dependencies = [ name = "rand" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -4214,6 +6108,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] [[package]] name = "rand_core" @@ -4239,16 +6136,28 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rangemap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" + [[package]] name = "rars" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d61643d05cb7e5045cc4f3e066ecf4b4e7ac394b9a052067906f530e91d3c88" dependencies = [ - "aes", + "aes 0.9.3", "aho-corasick", "getrandom 0.4.3", - "hmac", + "hmac 0.13.0", "rayon", "sha1", "sha2 0.11.0", @@ -4283,7 +6192,7 @@ dependencies = [ "paste", "profiling", "rand 0.9.5", - "rand_chacha", + "rand_chacha 0.9.0", "simd_helpers", "thiserror 2.0.20", "v_frame", @@ -4320,6 +6229,18 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + [[package]] name = "rayon" version = "1.12.0" @@ -4340,6 +6261,28 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "core_maths", + "font-types 0.11.3", +] + +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types 0.12.6", + "once_cell", +] + [[package]] name = "reborrow" version = "0.5.5" @@ -4355,6 +6298,17 @@ dependencies = [ "bitflags 2.13.2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -4424,6 +6378,12 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] name = "reqwest" version = "0.12.28" @@ -4463,61 +6423,61 @@ dependencies = [ ] [[package]] -name = "reqwest" -version = "0.13.5" +name = "resvg" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" dependencies = [ - "base64 0.23.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", + "gif 0.13.3", + "image-webp", "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", + "pico-args", + "rgb", + "svgtypes 0.15.3", + "tiny-skia", + "usvg 0.45.1", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "resvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" +dependencies = [ + "gif 0.14.2", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes 0.16.1", + "tiny-skia", + "usvg 0.46.0", + "zune-jpeg 0.5.15", ] [[package]] name = "rfd" -version = "0.16.0" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" dependencies = [ - "block2", + "ashpd 0.11.1", + "block2 0.6.2", "dispatch2", - "glib-sys", - "gobject-sys", - "gtk-sys", "js-sys", "log", - "objc2", - "objc2-app-kit", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", "objc2-core-foundation", - "objc2-foundation", + "objc2-foundation 0.3.2", + "pollster 0.4.0", "raw-window-handle", + "urlencoding", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -4525,6 +6485,9 @@ name = "rgb" version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] [[package]] name = "ring" @@ -4569,6 +6532,30 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ropey" +version = "2.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4045a00dc327d084a2bbf126976e14125b54f23bd30511d45b842eba76c52d74" +dependencies = [ + "str_indices", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + [[package]] name = "rusqlite" version = "0.32.1" @@ -4583,6 +6570,103 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust-embed" +version = "8.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19afa5b4b6a611de00bd1bdae6ae6f39084c9399f0679c3f52d8469cf335cc23" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d8afda6374eac59e066abee06d265247ebbaf3006cf878e2879e8356e34053" +dependencies = [ + "mime_guess", + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d84e8ba78bd384263e5922f084cbe1b081c3b7e69add59c8fb097b879ba968a" +dependencies = [ + "globset", + "sha2 0.11.0", + "walkdir", +] + +[[package]] +name = "rust-i18n" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8502e73e6461c738659455c5d2cf95bc132760a6f203c717e66ee72b78906d7f" +dependencies = [ + "globwalk", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54a28dd9d46108d56b9f0c85b220908844233e561c03087890325104f90f2c6" +dependencies = [ + "glob", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "syn 2.0.119", +] + +[[package]] +name = "rust-i18n-support" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e806c2a9701cbd6043992cd935c30cb1fdb128fa15e7b506f3b46f97045737c0" +dependencies = [ + "arc-swap", + "base62", + "foldhash 0.2.0", + "globwalk", + "itertools 0.11.0", + "normpath", + "serde", + "serde-saphyr", + "serde_json", + "siphasher", + "toml 0.8.23", + "triomphe", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.3" @@ -4625,6 +6709,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.15.1" @@ -4652,6 +6757,24 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.23" @@ -4677,30 +6800,12 @@ dependencies = [ ] [[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", + "windows-sys 0.61.2", ] [[package]] @@ -4710,23 +6815,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" dependencies = [ "dyn-clone", + "indexmap 2.14.2", "ref-cast", + "schemars_derive", "serde", "serde_json", ] [[package]] name = "schemars_derive" -version = "0.8.22" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.119", + "syn 3.0.5", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -4745,7 +6858,30 @@ dependencies = [ "html5ever 0.39.0", "precomputed-hash", "selectors", - "tendril", + "tendril 0.5.1", +] + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", ] [[package]] @@ -4754,6 +6890,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.36.1" @@ -4768,11 +6927,17 @@ dependencies = [ "phf 0.13.1", "phf_codegen 0.13.1", "precomputed-hash", - "rustc-hash", + "rustc-hash 2.1.3", "servo_arc", "smallvec", ] +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + [[package]] name = "semver" version = "1.0.28" @@ -4803,15 +6968,30 @@ dependencies = [ ] [[package]] -name = "serde-untagged" -version = "0.1.9" +name = "serde-saphyr" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8050abb251097357e24aff63ba2c52a6309ecb7d23a5474023df960a02694d8" +dependencies = [ + "annotate-snippets", + "base64 0.23.1", + "encoding_rs_io", + "granit-parser", + "nohash-hasher", + "num-traits", + "serde_core", + "smallvec", + "zmij", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ - "erased-serde", "serde", "serde_core", - "typeid", ] [[package]] @@ -4836,13 +7016,22 @@ dependencies = [ [[package]] name = "serde_derive_internals" -version = "0.29.1" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", +] + +[[package]] +name = "serde_fmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" +dependencies = [ + "serde_core", ] [[package]] @@ -4851,6 +7040,7 @@ version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ + "indexmap 2.14.2", "itoa", "memchr", "serde", @@ -4899,61 +7089,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" -dependencies = [ - "base64 0.23.1", - "bs58", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.2", - "jiff", - "schemars 0.9.0", - "schemars 1.2.2", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "servo_arc" version = "0.4.3" @@ -4974,6 +7109,12 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -4996,6 +7137,30 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +dependencies = [ + "dirs 6.0.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" @@ -5042,24 +7207,88 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", +] + +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts 0.41.0", +] + [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "serde_core", +] + [[package]] name = "socket2" version = "0.6.5" @@ -5071,51 +7300,30 @@ dependencies = [ ] [[package]] -name = "softbuffer" -version = "0.4.8" +name = "spin" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", + "lock_api", ] [[package]] -name = "soup3" -version = "0.5.0" +name = "spin" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", + "lock_api", ] [[package]] -name = "soup3-sys" -version = "0.5.0" +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", + "bitflags 2.13.2", ] [[package]] @@ -5124,6 +7332,40 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + [[package]] name = "string_cache" version = "0.9.0" @@ -5137,38 +7379,176 @@ dependencies = [ ] [[package]] -name = "string_cache_codegen" -version = "0.6.1" +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sval" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81b254da21fe1fcc4e3a74fe39b46e25e3a863078f8b71c954d47f84889dbc6" + +[[package]] +name = "sval_buffer" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50be352d2822ffafb59e3e2ddac9d5ee60f2eeadbb7b5a2a951b9f3651e87a6f" +dependencies = [ + "sval", + "sval_ref", + "zerocopy", +] + +[[package]] +name = "sval_dynamic" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048ca293b998d9a45659159f94a64063791e74cdc670164943dbb434405573d" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b5888e40f80568733217f27b7317b845f463400ced36c424b1a804730e53b2" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17664d6bb6b74947afaab9d7c991caa9bf5638d4dee16fcbef637f440796049" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c059969ca5ca163ea7fef6c9661758973d17691aba92abdcf5c428f4ec122c" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d6b29ff568c85c87561807f51d2adfff4b6016c6363133f7cd1652a12548f3" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f33ec9edc42b12764d5c90ca0a1d84189c6bde81ed27507f1e661c6e4e05853" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" -dependencies = [ - "phf_generator 0.13.1", - "phf_shared 0.13.1", - "proc-macro2", - "quote", -] +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" [[package]] -name = "strsim" -version = "0.11.1" +name = "svgtypes" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo 0.11.3", + "siphasher", +] [[package]] -name = "subtle" -version = "2.6.1" +name = "svgtypes" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo 0.13.1", + "siphasher", +] [[package]] -name = "swift-rs" -version = "1.0.8" +name = "swash" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45c444e496845d3f2a351146bff59aae4975b2280238df1dfaa0c7d1846f38e" +checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", + "skrifa 0.44.0", + "yazi", + "zeno", ] [[package]] @@ -5225,373 +7605,88 @@ dependencies = [ ] [[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.35.3" +name = "sys-locale" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" dependencies = [ - "bitflags 2.13.2", - "block2", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dbus", - "dispatch2", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", "libc", - "log", - "ndk", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "once_cell", - "parking_lot", - "percent-encoding", - "raw-window-handle", - "tao-macros", - "unicode-segmentation", - "url", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", ] [[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.11.5" +name = "sysinfo" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "http-range", - "jni", + "core-foundation-sys", "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest 0.13.5", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.20", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows 0.61.3", -] - -[[package]] -name = "tauri-build" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png 0.17.16", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2 0.10.9", - "syn 2.0.119", - "tauri-utils", - "thiserror 2.0.20", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "walkdir", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", ] [[package]] -name = "tauri-plugin-dialog" -version = "2.7.3" +name = "system-configuration" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61854a36651aa48381e5e209f69a01273b77f3f9f91f0c430b1b98d33bd47229" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "log", - "raw-window-handle", - "rfd", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "tauri-plugin-fs", - "thiserror 2.0.20", - "url", + "bitflags 2.13.2", + "core-foundation 0.9.4", + "system-configuration-sys", ] [[package]] -name = "tauri-plugin-fs" -version = "2.5.2" +name = "system-configuration-sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de22eef34fd78c0da050e748710edd50bf127e651d02ea1b2bfada1523cc5c51" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ - "anyhow", - "dunce", - "glob", - "log", - "objc2-foundation", - "percent-encoding", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "tauri-utils", - "thiserror 2.0.20", - "toml 1.1.6+spec-1.1.0", - "url", + "core-foundation-sys", + "libc", ] [[package]] -name = "tauri-plugin-opener" -version = "2.5.5" +name = "taffy" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d60366174b745b4ef5824b8bbc1c457fd08f0ce101ff643c0a49181a9f4e91" +checksum = "c034e05f6ee85a12daa63863c2245797715075c70649947aa0da54f3f2ab1d0f" dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation", - "open", - "schemars 0.8.22", + "arrayvec", "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.20", - "url", - "windows 0.61.3", - "zbus", + "slotmap", + "smallvec", ] [[package]] -name = "tauri-runtime" -version = "2.11.3" +name = "tao-core-video-sys" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.20", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", + "cfg-if", + "core-foundation-sys", + "libc", + "objc", ] [[package]] -name = "tauri-runtime-wry" -version = "2.11.4" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2", - "objc2-app-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows 0.61.3", - "wry", -] +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] -name = "tauri-utils" -version = "2.9.3" +name = "tauri-winrt-notification" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +checksum = "f37a6c354fd28fc9e322ed9bd47e3959576dad28c9d58ea1cf888cce1c7ccb36" dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dom_query", - "dunce", - "glob", - "http", - "infer", - "json-patch", - "log", - "memchr", - "phf 0.13.1", - "plist", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", "thiserror 2.0.20", - "toml 1.1.6+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" -dependencies = [ - "dunce", - "embed-resource", - "toml 1.1.6+spec-1.1.0", + "windows 0.62.2", + "windows-version", ] [[package]] @@ -5607,6 +7702,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "tendril" version = "0.5.1" @@ -5616,6 +7722,15 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -5656,6 +7771,15 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + [[package]] name = "tiff" version = "0.11.3" @@ -5667,7 +7791,7 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg", + "zune-jpeg 0.5.15", ] [[package]] @@ -5681,7 +7805,6 @@ dependencies = [ "powerfmt", "serde_core", "time-core", - "time-macros", ] [[package]] @@ -5691,13 +7814,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] -name = "time-macros" -version = "0.2.32" +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ - "num-conv", - "time-core", + "arrayref", + "bytemuck", + "strict-num", ] [[package]] @@ -5761,6 +7909,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-socks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.19" @@ -5770,35 +7930,21 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] [[package]] name = "toml" -version = "0.8.2" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.14.2", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -5818,22 +7964,13 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -5845,26 +7982,16 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.14.2", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.14.2", "serde", "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", ] [[package]] @@ -5888,6 +8015,12 @@ dependencies = [ "winnow 1.0.4", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" version = "1.1.2+spec-1.1.0" @@ -5945,6 +8078,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5968,28 +8102,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", ] [[package]] -name = "tray-icon" -version = "0.24.2" +name = "tracing-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", + "log", "once_cell", - "png 0.18.1", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "triomphe" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d15f4f5898bcfc5d8a8e45ed30b2f108d710c1a2e5489dbf80e078f707181bc" +dependencies = [ + "arc-swap", "serde", - "thiserror 2.0.20", - "windows-sys 0.61.2", + "stable_deref_trait", ] [[package]] @@ -5998,6 +8147,15 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + [[package]] name = "typeid" version = "1.0.3" @@ -6022,45 +8180,34 @@ dependencies = [ ] [[package]] -name = "unic-char-property" -version = "0.9.0" +name = "unicase" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" [[package]] -name = "unic-char-range" -version = "0.9.0" +name = "unicode-bidi" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] -name = "unic-common" -version = "0.9.0" +name = "unicode-bidi-mirroring" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" [[package]] -name = "unic-ucd-ident" -version = "0.9.0" +name = "unicode-ccc" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" [[package]] -name = "unic-ucd-version" -version = "0.9.0" +name = "unicode-id" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] +checksum = "65dd75ab2d9d379dbe86d4059e91d8b5a6e7054865eaba1ddbfc7a817ff96392" [[package]] name = "unicode-ident" @@ -6068,18 +8215,48 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + [[package]] name = "unicode-segmentation" version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -6100,17 +8277,71 @@ dependencies = [ ] [[package]] -name = "urlpattern" -version = "0.3.0" +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "usvg" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", + "base64 0.22.1", + "data-url 0.3.2", + "flate2", + "fontdb", + "imagesize 0.13.0", + "kurbo 0.11.3", + "log", + "pico-args", + "roxmltree 0.20.0", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes 0.15.3", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +dependencies = [ + "base64 0.22.1", + "data-url 0.3.2", + "flate2", + "fontdb", + "imagesize 0.14.0", + "kurbo 0.13.1", + "log", + "pico-args", + "roxmltree 0.21.1", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes 0.16.1", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -6132,6 +8363,7 @@ dependencies = [ "getrandom 0.4.3", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] @@ -6147,16 +8379,52 @@ dependencies = [ ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] -name = "version-compare" -version = "0.2.1" +name = "value-bag" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941feceafbe7a8f59ea1096d45b97002884a41306315ad797b3684b63a81d8c" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839752af8179287d27eb2b94164641b1ede9e60ab7424163388dc21ebd0508cd" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" @@ -6190,6 +8458,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" @@ -6210,86 +8484,197 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm_thread" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7516db7f32decdadb1c3b8deb1b7d78b9df7606c5cc2f6241737c2ab3a0258e" +dependencies = [ + "futures", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" +dependencies = [ + "cc", + "downcast-rs", + "log", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] [[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" +name = "wayland-client" +version = "0.31.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" dependencies = [ - "wit-bindgen", + "bitflags 2.13.2", + "rustix", + "wayland-backend", + "wayland-scanner", ] [[package]] -name = "wasm-bindgen" -version = "0.2.128" +name = "wayland-cursor" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", + "rustix", + "wayland-client", + "xcursor", ] [[package]] -name = "wasm-bindgen-futures" -version = "0.4.78" +name = "wayland-protocols" +version = "0.32.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" dependencies = [ - "js-sys", - "wasm-bindgen", + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.128" +name = "wayland-protocols-plasma" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.128" +name = "wayland-protocols-wlr" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.5", - "wasm-bindgen-shared", + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.128" +name = "wayland-scanner" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ - "unicode-ident", + "proc-macro2", + "quick-xml 0.41.0", + "quote", ] [[package]] -name = "wasm-streams" -version = "0.5.0" +name = "wayland-sys" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "dlib", + "log", + "once_cell", + "pkg-config", ] [[package]] @@ -6320,104 +8705,211 @@ checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" dependencies = [ "phf 0.13.1", "phf_codegen 0.13.1", - "string_cache", - "string_cache_codegen", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", ] [[package]] -name = "webkit2gtk" -version = "2.0.2" +name = "webpki-roots" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8840e1ba2881d4cbb18d2147627a56af426ff064c0401eb0c8410c6325d07" +dependencies = [ + "arrayvec", + "bitflags 2.13.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f519832254e56965a9940c4af57dcb75f702b6f6fa4a0b172f685395843a4d7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.13.2", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap 2.14.2", + "log", + "naga", "once_cell", - "soup3", - "webkit2gtk-sys", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.20", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", ] [[package]] -name = "webkit2gtk-sys" -version = "2.0.2" +name = "wgpu-core-deps-apple" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +checksum = "f5e39e26c4c0e07589e67d18546cf79ff45383659fc72fca4dd293358a0347f3" dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", + "wgpu-hal", ] [[package]] -name = "webpki-roots" -version = "1.0.9" +name = "wgpu-core-deps-emscripten" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +checksum = "01e09be551dc939498bdd5f6b2c66e55ab275dad25825267a08605a80fc9f0af" dependencies = [ - "rustls-pki-types", + "wgpu-hal", ] [[package]] -name = "webview2-com" -version = "0.38.2" +name = "wgpu-core-deps-wasm" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +checksum = "af1fb1798be2a912497d4c224f72d39bb0cb34af50e8bcc29865bc339c943059" dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "wgpu-hal", ] [[package]] -name = "webview2-com-macros" -version = "0.8.1" +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +checksum = "4e592c1bbef6ad047647ae6e666ebd8cee7a32bb4544d9700ec96cbf73230257" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "wgpu-hal", ] [[package]] -name = "webview2-com-sys" -version = "0.38.2" +name = "wgpu-hal" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +checksum = "97ace1c17727311c22a46e4e3faf56ea6de81af99dcc839bdfb54857b94d448d" dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.13.2", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.9", + "log", + "naga", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", "thiserror 2.0.20", - "windows 0.61.3", - "windows-core 0.61.2", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-result 0.4.1", ] [[package]] -name = "weezl" -version = "0.1.12" +name = "wgpu-naga-bridge" +version = "29.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +checksum = "95226013f547544b223281cd16a4fb549aa9dcb562adbda0faae4c73ffbbc161" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bf84cd9ca8ca45e2b223a3868f1adf9bfc0c66aeac212e76ee7e40fdadf8f5" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "which" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f" +dependencies = [ + "libc", +] [[package]] name = "winapi" @@ -6451,18 +8943,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "window-vibrancy" -version = "0.6.0" +name = "windows" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", ] [[package]] @@ -6490,6 +8987,19 @@ dependencies = [ "windows-numerics 0.3.1", ] +[[package]] +name = "windows-capture" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.20", + "windows 0.61.3", + "windows-future 0.2.1", +] + [[package]] name = "windows-collections" version = "0.2.0" @@ -6508,14 +9018,39 @@ dependencies = [ "windows-core 0.62.2", ] +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.60.2", + "windows-interface 0.59.3", "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", @@ -6527,40 +9062,84 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "windows-future" -version = "0.2.1" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "windows-future" -version = "0.3.2" +name = "windows-interface" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", - "windows-threading 0.2.1", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] -name = "windows-implement" -version = "0.60.2" +name = "windows-interface" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", @@ -6610,6 +9189,46 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -6628,6 +9247,25 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -6648,11 +9286,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.48.5", ] [[package]] @@ -6693,17 +9331,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -6768,9 +9406,9 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" @@ -6786,9 +9424,9 @@ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" @@ -6804,9 +9442,9 @@ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" @@ -6834,9 +9472,9 @@ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" @@ -6852,9 +9490,9 @@ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" @@ -6870,9 +9508,9 @@ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" @@ -6888,9 +9526,9 @@ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" @@ -6906,19 +9544,13 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.5.40" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - [[package]] name = "winnow" version = "1.0.4" @@ -6938,6 +9570,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "wit-bindgen" version = "0.57.1" @@ -6950,50 +9591,6 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" -[[package]] -name = "wry" -version = "0.55.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dom_query", - "dpi", - "dunce", - "gdkx11", - "gtk", - "http", - "javascriptcore-rs", - "jni", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2 0.10.9", - "soup3", - "tao-macros", - "thiserror 2.0.20", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - [[package]] name = "wyz" version = "0.5.1" @@ -7014,22 +9611,125 @@ dependencies = [ ] [[package]] -name = "x11-dl" -version = "2.21.0" +name = "x11rb" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ + "as-raw-xcb-connection", + "gethostname", "libc", - "once_cell", - "pkg-config", + "rustix", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcb" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566" +dependencies = [ + "bitflags 2.13.2", + "libc", + "quick-xml 0.41.0", + "x11", +] + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xim-ctext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac61a7062c40f3c37b6e82eeeef835d5cc7824b632a72784a89b3963c33284c" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "xim-parser" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcee45f89572d5a65180af3a84e7ddb24f5ea690a6d3aa9de231281544dd7b7" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "xkbcommon" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" +dependencies = [ + "as-raw-xcb-connection", + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + +[[package]] +name = "xml5ever" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" +dependencies = [ + "log", + "mac", + "markup5ever 0.12.1", ] +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "y4m" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "yoke" version = "0.8.3" @@ -7088,13 +9788,37 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + [[package]] name = "zbus_macros" version = "5.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.5", @@ -7114,6 +9838,18 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zbus_xml" +version = "5.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" +dependencies = [ + "serde", + "winnow 1.0.4", + "zbus_names", + "zvariant", +] + [[package]] name = "zcheapstr" version = "1.1.0" @@ -7123,6 +9859,73 @@ dependencies = [ "serde", ] +[[package]] +name = "zed-font-kit" +version = "0.14.1-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3898e450f36f852edda72e3f985c34426042c4951790b23b107f93394f9bff5" +dependencies = [ + "bitflags 2.13.2", + "byteorder", + "core-foundation 0.10.1", + "core-graphics 0.24.0", + "core-text", + "dirs 5.0.1", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "zed-scap" +version = "0.0.8-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b338d705ae33a43ca00287c11129303a7a0aa57b101b72a1c08c863f698ac8" +dependencies = [ + "anyhow", + "cocoa 0.25.0", + "core-graphics-helmer-fork", + "log", + "objc", + "rand 0.8.8", + "screencapturekit", + "screencapturekit-sys", + "sysinfo", + "tao-core-video-sys", + "windows 0.61.3", + "windows-capture", + "x11", + "xcb", +] + +[[package]] +name = "zed-xim" +version = "0.4.0-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b46ed118eba34d9ba53d94ddc0b665e0e06a2cf874cfa2dd5dec278148642" +dependencies = [ + "ahash 0.8.12", + "hashbrown 0.14.5", + "log", + "x11rb", + "xim-ctext", + "xim-parser", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + [[package]] name = "zerocopy" version = "0.8.57" @@ -7258,6 +10061,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-core" version = "0.5.3" @@ -7273,13 +10082,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + [[package]] name = "zune-jpeg" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "zune-core", + "zune-core 0.5.3", ] [[package]] @@ -7291,6 +10109,8 @@ dependencies = [ "endi", "enumflags2", "serde", + "serde_bytes", + "url", "winnow 1.0.4", "zcheapstr", "zvariant_derive", @@ -7303,7 +10123,7 @@ version = "5.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" dependencies = [ - "proc-macro-crate 3.5.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.5", diff --git a/src-tauri/Cargo.toml b/app/Cargo.toml similarity index 73% rename from src-tauri/Cargo.toml rename to app/Cargo.toml index abdd7b8..f79a4c0 100644 --- a/src-tauri/Cargo.toml +++ b/app/Cargo.toml @@ -13,15 +13,11 @@ path = "src/main.rs" [lib] name = "grimoire_lib" -crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2", features = [] } +embed-resource = "3" [dependencies] -tauri = { version = "2", features = ["protocol-asset", "macos-private-api"] } -tauri-plugin-opener = "2" -tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" rusqlite = { version = "0.32", features = ["bundled", "functions"] } @@ -43,6 +39,14 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus kindling-mobi = "0.42" mobi = "0.8" rars = "0.9.4" +gpui-kit = "0.7" +rfd = "0.15" +image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "webp"] } + +[target.'cfg(target_os = "macos")'.dependencies] +objc2 = "0.6" +objc2-app-kit = { version = "0.3", default-features = false, features = ["NSApplication", "NSImage"] } +objc2-foundation = { version = "0.3", default-features = false, features = ["NSData"] } [profile.release] codegen-units = 1 diff --git a/src-tauri/app-icon.png b/app/app-icon.png similarity index 100% rename from src-tauri/app-icon.png rename to app/app-icon.png diff --git a/app/build.rs b/app/build.rs new file mode 100644 index 0000000..1dc06c7 --- /dev/null +++ b/app/build.rs @@ -0,0 +1,10 @@ +fn main() { + println!("cargo:rerun-if-changed=icons/icon.rc"); + println!("cargo:rerun-if-changed=icons/icon.ico"); + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + if target_os == "windows" { + embed_resource::compile("icons/icon.rc", embed_resource::NONE) + .manifest_optional() + .unwrap(); + } +} diff --git a/src-tauri/icons/128x128.png b/app/icons/128x128.png similarity index 100% rename from src-tauri/icons/128x128.png rename to app/icons/128x128.png diff --git a/src-tauri/icons/128x128@2x.png b/app/icons/128x128@2x.png similarity index 100% rename from src-tauri/icons/128x128@2x.png rename to app/icons/128x128@2x.png diff --git a/src-tauri/icons/32x32.png b/app/icons/32x32.png similarity index 100% rename from src-tauri/icons/32x32.png rename to app/icons/32x32.png diff --git a/src-tauri/icons/64x64.png b/app/icons/64x64.png similarity index 100% rename from src-tauri/icons/64x64.png rename to app/icons/64x64.png diff --git a/src-tauri/icons/Square107x107Logo.png b/app/icons/Square107x107Logo.png similarity index 100% rename from src-tauri/icons/Square107x107Logo.png rename to app/icons/Square107x107Logo.png diff --git a/src-tauri/icons/Square142x142Logo.png b/app/icons/Square142x142Logo.png similarity index 100% rename from src-tauri/icons/Square142x142Logo.png rename to app/icons/Square142x142Logo.png diff --git a/src-tauri/icons/Square150x150Logo.png b/app/icons/Square150x150Logo.png similarity index 100% rename from src-tauri/icons/Square150x150Logo.png rename to app/icons/Square150x150Logo.png diff --git a/src-tauri/icons/Square284x284Logo.png b/app/icons/Square284x284Logo.png similarity index 100% rename from src-tauri/icons/Square284x284Logo.png rename to app/icons/Square284x284Logo.png diff --git a/src-tauri/icons/Square30x30Logo.png b/app/icons/Square30x30Logo.png similarity index 100% rename from src-tauri/icons/Square30x30Logo.png rename to app/icons/Square30x30Logo.png diff --git a/src-tauri/icons/Square310x310Logo.png b/app/icons/Square310x310Logo.png similarity index 100% rename from src-tauri/icons/Square310x310Logo.png rename to app/icons/Square310x310Logo.png diff --git a/src-tauri/icons/Square44x44Logo.png b/app/icons/Square44x44Logo.png similarity index 100% rename from src-tauri/icons/Square44x44Logo.png rename to app/icons/Square44x44Logo.png diff --git a/src-tauri/icons/Square71x71Logo.png b/app/icons/Square71x71Logo.png similarity index 100% rename from src-tauri/icons/Square71x71Logo.png rename to app/icons/Square71x71Logo.png diff --git a/src-tauri/icons/Square89x89Logo.png b/app/icons/Square89x89Logo.png similarity index 100% rename from src-tauri/icons/Square89x89Logo.png rename to app/icons/Square89x89Logo.png diff --git a/src-tauri/icons/StoreLogo.png b/app/icons/StoreLogo.png similarity index 100% rename from src-tauri/icons/StoreLogo.png rename to app/icons/StoreLogo.png diff --git a/src-tauri/icons/icon.icns b/app/icons/icon.icns similarity index 100% rename from src-tauri/icons/icon.icns rename to app/icons/icon.icns diff --git a/src-tauri/icons/icon.ico b/app/icons/icon.ico similarity index 100% rename from src-tauri/icons/icon.ico rename to app/icons/icon.ico diff --git a/src-tauri/icons/icon.png b/app/icons/icon.png similarity index 100% rename from src-tauri/icons/icon.png rename to app/icons/icon.png diff --git a/app/icons/icon.rc b/app/icons/icon.rc new file mode 100644 index 0000000..a1ec328 --- /dev/null +++ b/app/icons/icon.rc @@ -0,0 +1 @@ +1 ICON "icon.ico" diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/app/icons/ios/AppIcon-20x20@1x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-20x20@1x.png rename to app/icons/ios/AppIcon-20x20@1x.png diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/app/icons/ios/AppIcon-20x20@2x-1.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-20x20@2x-1.png rename to app/icons/ios/AppIcon-20x20@2x-1.png diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/app/icons/ios/AppIcon-20x20@2x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-20x20@2x.png rename to app/icons/ios/AppIcon-20x20@2x.png diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/app/icons/ios/AppIcon-20x20@3x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-20x20@3x.png rename to app/icons/ios/AppIcon-20x20@3x.png diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/app/icons/ios/AppIcon-29x29@1x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-29x29@1x.png rename to app/icons/ios/AppIcon-29x29@1x.png diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/app/icons/ios/AppIcon-29x29@2x-1.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-29x29@2x-1.png rename to app/icons/ios/AppIcon-29x29@2x-1.png diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/app/icons/ios/AppIcon-29x29@2x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-29x29@2x.png rename to app/icons/ios/AppIcon-29x29@2x.png diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/app/icons/ios/AppIcon-29x29@3x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-29x29@3x.png rename to app/icons/ios/AppIcon-29x29@3x.png diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/app/icons/ios/AppIcon-40x40@1x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-40x40@1x.png rename to app/icons/ios/AppIcon-40x40@1x.png diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/app/icons/ios/AppIcon-40x40@2x-1.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-40x40@2x-1.png rename to app/icons/ios/AppIcon-40x40@2x-1.png diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/app/icons/ios/AppIcon-40x40@2x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-40x40@2x.png rename to app/icons/ios/AppIcon-40x40@2x.png diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/app/icons/ios/AppIcon-40x40@3x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-40x40@3x.png rename to app/icons/ios/AppIcon-40x40@3x.png diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/app/icons/ios/AppIcon-512@2x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-512@2x.png rename to app/icons/ios/AppIcon-512@2x.png diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/app/icons/ios/AppIcon-60x60@2x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-60x60@2x.png rename to app/icons/ios/AppIcon-60x60@2x.png diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/app/icons/ios/AppIcon-60x60@3x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-60x60@3x.png rename to app/icons/ios/AppIcon-60x60@3x.png diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/app/icons/ios/AppIcon-76x76@1x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-76x76@1x.png rename to app/icons/ios/AppIcon-76x76@1x.png diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/app/icons/ios/AppIcon-76x76@2x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-76x76@2x.png rename to app/icons/ios/AppIcon-76x76@2x.png diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/app/icons/ios/AppIcon-83.5x83.5@2x.png similarity index 100% rename from src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png rename to app/icons/ios/AppIcon-83.5x83.5@2x.png diff --git a/src-tauri/macos/How to install.txt b/app/macos/How to install.txt similarity index 100% rename from src-tauri/macos/How to install.txt rename to app/macos/How to install.txt diff --git a/src-tauri/macos/Install Grimoire.command b/app/macos/Install Grimoire.command similarity index 100% rename from src-tauri/macos/Install Grimoire.command rename to app/macos/Install Grimoire.command diff --git a/src-tauri/migrations/001_init.sql b/app/migrations/001_init.sql similarity index 100% rename from src-tauri/migrations/001_init.sql rename to app/migrations/001_init.sql diff --git a/src-tauri/migrations/002_reading_progress.sql b/app/migrations/002_reading_progress.sql similarity index 100% rename from src-tauri/migrations/002_reading_progress.sql rename to app/migrations/002_reading_progress.sql diff --git a/src-tauri/src/comic.rs b/app/src/comic.rs similarity index 96% rename from src-tauri/src/comic.rs rename to app/src/comic.rs index 7d833d0..da5b18c 100644 --- a/src-tauri/src/comic.rs +++ b/app/src/comic.rs @@ -3,7 +3,6 @@ use crate::error::{err, AppResult}; use crate::metadata::cover_file_info; use base64::{engine::general_purpose::STANDARD, Engine as _}; -use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::Read; use std::path::Path; @@ -11,23 +10,6 @@ use zip::ZipArchive; const IMAGE_EXTS: &[&str] = &["jpg", "jpeg", "png", "gif", "webp", "bmp"]; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ComicPageList { - pub book_id: i64, - pub format: String, - pub page_count: usize, - pub pages: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ComicPage { - pub book_id: i64, - pub index: usize, - pub page_count: usize, - pub name: String, - pub data_url: String, -} - pub fn list_comic_image_names(path: &Path) -> AppResult> { let ext = path .extension() diff --git a/src-tauri/src/config.rs b/app/src/config.rs similarity index 62% rename from src-tauri/src/config.rs rename to app/src/config.rs index 59b61ae..aa0c55a 100644 --- a/src-tauri/src/config.rs +++ b/app/src/config.rs @@ -3,9 +3,33 @@ use serde::{Deserialize, Serialize}; use std::fs; use std::path::{Path, PathBuf}; -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct AppConfig { pub library_path: Option, + /// `light`, `dark`, or `system`. + #[serde(default = "default_theme")] + pub theme: String, + /// `publisher`, `serif`, `sans`, or `opendyslexic`. + #[serde(default = "default_reader_font")] + pub reader_font: String, +} + +fn default_theme() -> String { + "dark".into() +} + +fn default_reader_font() -> String { + "serif".into() +} + +impl Default for AppConfig { + fn default() -> Self { + Self { + library_path: None, + theme: default_theme(), + reader_font: default_reader_font(), + } + } } fn config_path() -> AppResult { @@ -58,6 +82,20 @@ pub fn clear_library_path() -> AppResult { Ok(config) } +pub fn set_theme_preference(theme: &str) -> AppResult { + let mut config = load_config()?; + config.theme = theme.to_string(); + save_config(&config)?; + Ok(config) +} + +pub fn set_reader_font(font: &str) -> AppResult { + let mut config = load_config()?; + config.reader_font = font.to_string(); + save_config(&config)?; + Ok(config) +} + #[cfg(test)] mod tests { use super::*; @@ -73,7 +111,17 @@ mod tests { fn parse_config_treats_empty_as_default() { let config = parse_config("").expect("empty config should default"); assert!(config.library_path.is_none()); + assert_eq!(config.theme, "dark"); + assert_eq!(config.reader_font, "serif"); let config = parse_config("\u{feff}").expect("BOM-only config should default"); assert!(config.library_path.is_none()); } + + #[test] + fn missing_theme_and_reader_font_use_defaults() { + let config = parse_config(r#"{"library_path":"C:\\Books"}"#).expect("partial config"); + assert_eq!(config.library_path.as_deref(), Some("C:\\Books")); + assert_eq!(config.theme, "dark"); + assert_eq!(config.reader_font, "serif"); + } } diff --git a/src-tauri/src/convert.rs b/app/src/convert.rs similarity index 100% rename from src-tauri/src/convert.rs rename to app/src/convert.rs diff --git a/src-tauri/src/db.rs b/app/src/db.rs similarity index 100% rename from src-tauri/src/db.rs rename to app/src/db.rs diff --git a/src-tauri/src/error.rs b/app/src/error.rs similarity index 100% rename from src-tauri/src/error.rs rename to app/src/error.rs diff --git a/src-tauri/src/kf8_extract.rs b/app/src/kf8_extract.rs similarity index 100% rename from src-tauri/src/kf8_extract.rs rename to app/src/kf8_extract.rs diff --git a/src-tauri/src/kindle.rs b/app/src/kindle.rs similarity index 97% rename from src-tauri/src/kindle.rs rename to app/src/kindle.rs index bdb41f1..259816f 100644 --- a/src-tauri/src/kindle.rs +++ b/app/src/kindle.rs @@ -80,18 +80,6 @@ pub struct EbookReaderDevice { pub notes: Option, } -/// Backward-compatible alias used by existing Kindle send paths. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KindleDeviceInfo { - pub location_id: u64, - pub manufacturer: String, - pub product: String, - pub serial_number: Option, - pub is_kindle: bool, - pub free_space_bytes: Option, - pub display: String, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TransferResult { pub remote_path: String, @@ -195,26 +183,6 @@ pub async fn list_ebook_readers() -> AppResult> { Ok(out) } -pub async fn list_kindles() -> AppResult> { - let readers = list_ebook_readers().await?; - Ok(readers - .into_iter() - .filter(|d| d.family == ReaderFamily::Kindle) - .filter_map(|d| { - let location_id = d.location_id?; - Some(KindleDeviceInfo { - location_id, - manufacturer: d.manufacturer, - product: d.product, - serial_number: d.serial_number, - is_kindle: true, - free_space_bytes: d.free_space_bytes, - display: d.display, - }) - }) - .collect()) -} - async fn list_mtp_readers() -> AppResult> { let devices = match MtpDevice::list_devices() { Ok(d) => d, diff --git a/app/src/lib.rs b/app/src/lib.rs new file mode 100644 index 0000000..bc9e8c9 --- /dev/null +++ b/app/src/lib.rs @@ -0,0 +1,17 @@ +mod comic; +mod config; +mod convert; +mod db; +mod error; +mod kf8_extract; +mod kindle; +mod library; +mod metadata; +mod openlibrary; +mod reader; +mod state; +mod ui; + +pub fn run() { + ui::launch(); +} diff --git a/src-tauri/src/library.rs b/app/src/library.rs similarity index 98% rename from src-tauri/src/library.rs rename to app/src/library.rs index c24d553..9cfa691 100644 --- a/src-tauri/src/library.rs +++ b/app/src/library.rs @@ -3,7 +3,6 @@ use crate::error::{err, AppResult}; use crate::openlibrary::MetadataMatch; use crate::metadata::{cover_file_info, extract_from_file}; use crate::state::LibrarySession; -use base64::{engine::general_purpose::STANDARD, Engine as _}; use chrono::Utc; use rusqlite::{params, Connection, OptionalExtension}; use serde::{Deserialize, Serialize}; @@ -743,7 +742,7 @@ fn patch_one_book(conn: &mut Connection, id: i64, patch: &BookBulkPatch) -> AppR Ok(()) } -pub fn cover_data_url(conn: &Connection, root: &Path, id: i64) -> AppResult> { +pub fn cover_path(conn: &Connection, root: &Path, id: i64) -> AppResult> { let (path, has_cover): (String, i64) = conn.query_row( "SELECT path, has_cover FROM books WHERE id = ?1", [id], @@ -753,24 +752,17 @@ pub fn cover_data_url(conn: &Connection, root: &Path, id: i64) -> AppResult, + pub kindle_convert_available: bool, +} + +pub fn status(state: &AppState) -> AppResult { + let config = load_config()?; + Ok(LibraryStatus { + open: state.is_open(), + path: config.library_path, + kindle_convert_available: kindle_convert_available(), + }) +} + +pub fn open_saved(state: &AppState) -> AppResult { + let config = load_config()?; + let Some(path) = config.library_path else { + return status(state); + }; + let root = PathBuf::from(&path); + if !root.exists() { + return status(state); + } + let session = if root.join("metadata.db").exists() { + crate::library::open_library(&root)? + } else { + crate::library::create_library(&root)? + }; + state.set_session(session); + status(state) +} + +pub fn create_or_open(state: &AppState, path: &Path) -> AppResult { + let session = if path.join("metadata.db").exists() { + crate::library::open_library(path)? + } else { + crate::library::create_library(path)? + }; + set_library_path(path)?; + state.set_session(session); + status(state) +} + +pub fn clear_library(state: &AppState) -> AppResult { + clear_library_path()?; + state.clear_session(); + status(state) +} + +pub fn list(state: &AppState, query: Option<&str>) -> AppResult> { + state.with_conn(|conn, _| list_books(conn, query)) +} + +pub fn covers_for(state: &AppState, books: &[BookSummary]) -> std::collections::HashMap { + let mut covers = std::collections::HashMap::new(); + let _ = state.with_conn(|conn, root| { + for book in books { + if book.has_cover { + if let Ok(Some(path)) = cover_path(conn, root, book.id) { + covers.insert(book.id, path); + } + } + } + Ok(()) + }); + covers +} + +pub fn import(state: &AppState, path: &Path) -> AppResult { + state.with_conn_mut(|conn, root| import_book(conn, root, path)) +} + +pub fn book(state: &AppState, id: i64) -> AppResult { + state.with_conn(|conn, root| get_book(conn, root, id)) +} + +pub fn save_book(state: &AppState, id: i64, update: BookUpdate) -> AppResult { + state.with_conn_mut(|conn, root| update_book(conn, root, id, update)) +} + +pub fn remove_book(state: &AppState, id: i64) -> AppResult<()> { + state.with_conn_mut(|conn, root| delete_book(conn, root, id)) +} + +pub fn remove_books(state: &AppState, ids: &[i64]) -> AppResult { + state.with_conn_mut(|conn, root| delete_ebooks(conn, root, ids)) +} + +pub fn patch_books(state: &AppState, ids: &[i64], patch: BookBulkPatch) -> AppResult { + state.with_conn_mut(|conn, root| bulk_patch_ebooks(conn, root, ids, &patch)) +} + +pub fn convert_targets(state: &AppState, id: i64) -> AppResult> { + state.with_conn(|conn, _| list_convertible_targets(conn, id)) +} + +pub fn convert(state: &AppState, id: i64, target: &str) -> AppResult { + state.with_conn_mut(|conn, root| convert_book_format(conn, root, id, target)) +} + +pub fn reader_source(state: &AppState, id: i64) -> AppResult { + state.with_conn_mut(|conn, root| resolve_reader_source(conn, root, id, None)) +} + +pub fn progress(state: &AppState, id: i64) -> AppResult> { + state.with_conn(|conn, _| get_reading_progress(conn, id)) +} + +pub fn save_progress(state: &AppState, id: i64, update: ReadingProgressUpdate) -> AppResult { + state.with_conn(|conn, _| save_reading_progress(conn, id, update)) +} + +pub fn comic_page(path: &Path, index: usize) -> AppResult<(String, String, usize)> { + comic_page_data_url(path, index) +} + +pub fn sync_history(state: &AppState) -> AppResult> { + state.with_conn(|conn, _| list_sync_records(conn)) +} + +pub async fn readers() -> AppResult> { + list_ebook_readers().await +} + +pub async fn scan_device(id: String) -> AppResult { + scan_device_library(id).await +} + +pub async fn delete_on_device(id: String, books: Vec) -> AppResult { + delete_device_books(id, books).await +} + +pub async fn send(state: &AppState, book_id: i64, device_id: &str) -> AppResult { + let ready = state.with_conn_mut(|conn, root| resolve_kindle_file(conn, root, book_id))?; + let local = PathBuf::from(&ready.path); + let remote_name = local + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_else(|| format!("book.{}", ready.format.to_ascii_lowercase())); + let (remote_path, serial) = send_file_to_kindle(device_id, &local, &remote_name).await?; + state.with_conn(|conn, _| record_sync(conn, book_id, &serial, &remote_path, &ready.format))?; + Ok(TransferResult { + remote_path, + device_serial: serial, + format: ready.format, + }) +} + +pub async fn enrich(state: &AppState, id: i64) -> AppResult { + let (title, authors, isbn, has_cover) = state.with_conn(|conn, root| { + let book = get_book(conn, root, id)?; + let isbn = isbn_of(&book); + Ok((book.title, book.authors, isbn, book.has_cover)) + })?; + let hit = lookup_book(&title, &authors, isbn.as_deref()) + .await? + .ok_or_else(|| err("No Open Library match found for this title/author/ISBN."))?; + let cover_bytes = if !has_cover { + match download_cover_from_urls(&hit.cover_urls).await { + Ok(bytes) => Some(bytes), + Err(error) => { + eprintln!("Grimoire cover download skipped: {error}"); + None + } + } + } else { + None + }; + state.with_conn_mut(|conn, root| { + let current = get_book(conn, root, id)?; + apply_remote_match(conn, root, id, ¤t, &hit, cover_bytes.as_deref()) + }) +} + +pub async fn cover_candidates(state: &AppState, id: i64) -> AppResult> { + let (title, authors, isbn) = state.with_conn(|conn, root| { + let book = get_book(conn, root, id)?; + let isbn = isbn_of(&book); + Ok((book.title, book.authors, isbn)) + })?; + let matches = lookup_book_candidates(&title, &authors, isbn.as_deref(), 12).await?; + let candidates = cover_candidates_from_matches(&matches); + if candidates.is_empty() { + return Err(err( + "No cover images found on Open Library for this title/author/ISBN.", + )); + } + Ok(candidates) +} + +pub async fn apply_cover(state: &AppState, id: i64, urls: &[String]) -> AppResult { + if urls.is_empty() { + return Err(err("Pick a cover first.")); + } + state.with_conn(|conn, root| { + let _ = get_book(conn, root, id)?; + Ok(()) + })?; + let bytes = download_cover_from_urls(urls).await?; + state.with_conn_mut(|conn, root| set_book_cover(conn, root, id, &bytes)) +} + +pub fn config() -> AppResult { + load_config() +} + +pub fn save_reader_font(font: &str) -> AppResult { + set_reader_font(font) +} + +pub fn isbn_of(book: &BookDetail) -> Option { + book.identifiers + .iter() + .find(|id| id.type_name.eq_ignore_ascii_case("isbn")) + .map(|id| id.value.clone()) +} + +pub fn with_isbn(book: &BookDetail, isbn: &str) -> Vec { + let preserved = book + .identifiers + .iter() + .filter(|id| !id.type_name.eq_ignore_ascii_case("isbn")) + .cloned() + .collect::>(); + let trimmed = isbn.trim(); + if trimmed.is_empty() { + preserved + } else { + let mut ids = vec![IdentifierInfo { + type_name: "isbn".into(), + value: trimmed.to_string(), + }]; + ids.extend(preserved); + ids + } +} + +pub const READABLE: &[&str] = &["EPUB", "PDF", "TXT", "MOBI", "CBZ", "CBR"]; +pub const SUPPORTED_EXT: &[&str] = &["epub", "pdf", "azw3", "mobi", "txt", "cbz", "cbr"]; + +pub fn can_read(formats: &[String]) -> bool { + formats + .iter() + .any(|format| READABLE.iter().any(|ok| format.eq_ignore_ascii_case(ok))) +} + +pub fn extension_of(path: &Path) -> String { + path.extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("") + .to_ascii_lowercase() +} diff --git a/app/src/ui/book.rs b/app/src/ui/book.rs new file mode 100644 index 0000000..415d2cd --- /dev/null +++ b/app/src/ui/book.rs @@ -0,0 +1,531 @@ +use gpui_kit::component::button::{Button, ButtonVariants as _}; +use gpui_kit::component::input::{Input, Textarea}; +use gpui_kit::component::{ActiveTheme as _, Disableable as _, WindowExt as _}; +use gpui_kit::prelude::{ + FluentBuilder as _, InteractiveElement as _, StatefulInteractiveElement as _, Styled as _, + StyledImage as _, +}; +use gpui_kit::{Context, IntoElement, ObjectFit, ParentElement as _, Window, div, img, px}; + +use super::backend; +use super::shell::Shell; +use super::theme::DISPLAY; +use crate::library::{BookDetail, BookUpdate}; + +impl Shell { + pub fn book_page(&mut self, window: &mut Window, cx: &mut Context) -> gpui_kit::AnyElement { + let Some(id) = self.book_id else { + return div().child("No book selected.").into_any_element(); + }; + if self.book.as_ref().map(|book| book.id) != Some(id) && !self.busy && self.error.is_empty() + { + self.load_book(id, window, cx); + } + let title = self.book.as_ref().map(|book| book.title.clone()).unwrap_or_else(|| "Book".into()); + let cover = self.book_cover.clone(); + let formats = self + .book + .as_ref() + .map(|book| { + book.formats + .iter() + .map(|format| format.format.clone()) + .collect::>() + .join(", ") + }) + .unwrap_or_default(); + let can_read = self + .book + .as_ref() + .map(|book| backend::can_read(&book.formats.iter().map(|format| format.format.clone()).collect::>())) + .unwrap_or(false); + let targets = self.convert_targets.clone(); + let devices = self.devices.clone(); + + div() + .id("book-page") + .size_full() + .overflow_y_scroll() + .p(px(24.)) + .flex() + .flex_col() + .gap(px(14.)) + .child( + Button::new("back-library") + .ghost() + .label("Back to library") + .on_click(cx.listener(|this, _, window, cx| { + this.book_id = None; + this.book = None; + this.reload_books(window, cx); + })), + ) + .child(div().font_family(DISPLAY).text_size(px(28.)).child(title)) + .when(!self.message.is_empty(), |this| { + this.child(div().text_color(cx.theme().primary).child(self.message.clone())) + }) + .when(!self.error.is_empty(), |this| { + this.child(div().text_color(cx.theme().danger).child(self.error.clone())) + }) + .child( + div().flex().gap(px(20.)).child(book_cover(cover, cx)).child( + div() + .flex_1() + .flex() + .flex_col() + .gap(px(8.)) + .child(field("Title", Input::new(&self.title_input))) + .child(field("Authors", Input::new(&self.authors_input))) + .child(field("Tags", Input::new(&self.tags_input))) + .child(field("Series", Input::new(&self.series_input))) + .child(field("Series index", Input::new(&self.series_index_input))) + .child(field("ISBN", Input::new(&self.isbn_input))) + .child(field("Notes", Textarea::new(&self.comment_input).h(px(90.)))), + ), + ) + .child( + div() + .flex() + .flex_wrap() + .gap(px(8.)) + .child( + Button::new("save-book") + .primary() + .label(if self.busy { "Working…" } else { "Save" }) + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.save_book_form(window, cx))), + ) + .child( + Button::new("fetch-ol") + .outline() + .label("Fetch from Open Library") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.fetch_book(window, cx))), + ) + .child( + Button::new("choose-cover") + .outline() + .label("Choose cover") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.choose_cover(window, cx))), + ) + .when(can_read, |this| { + this.child( + Button::new("read-book") + .outline() + .label("Read") + .on_click(cx.listener(|this, _, _, cx| { + if let Some(id) = this.book_id { + this.open_reader(id, cx); + } + })), + ) + }) + .child( + Button::new("delete-book") + .danger() + .label("Delete") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.confirm_delete_book(window, cx))), + ), + ) + .child( + div() + .text_color(cx.theme().muted_foreground) + .child(format!("Formats: {formats}")), + ) + .when(!targets.is_empty(), |this| { + let buttons = targets.into_iter().map(|target| { + let label = format!("Convert to {target}"); + let target_format = target.clone(); + Button::new(SharedId(format!("convert-{target}"))) + .outline() + .label(label) + .disabled(self.busy) + .on_click(cx.listener(move |this, _, window, cx| { + this.convert_to(target_format.clone(), window, cx); + })) + }); + this.child(div().flex().flex_wrap().gap(px(8.)).children(buttons)) + }) + .child(div().font_family(DISPLAY).text_size(px(18.)).child("Send to device")) + .children(devices.into_iter().map(|device| { + let id = device.id.clone(); + let label = format!( + "{} · {}", + device.product, + if device.supports_send { "Send" } else { "Detected" } + ); + Button::new(SharedId(format!("send-{}", device.id))) + .outline() + .label(label) + .disabled(self.busy || !device.supports_send) + .on_click(cx.listener(move |this, _, window, cx| { + this.send_open_book(id.clone(), window, cx); + })) + })) + .into_any_element() + } + + pub fn load_book(&mut self, id: i64, window: &mut Window, cx: &mut Context) { + if self.busy { + return; + } + let state = self.state.clone(); + self.busy = true; + self.error.clear(); + self.spawn_io( + window, + cx, + move || { + let book = backend::book(&state, id).map_err(|e| e.to_string())?; + let cover = state + .with_conn(|conn, root| crate::library::cover_path(conn, root, id)) + .ok() + .flatten(); + let targets = backend::convert_targets(&state, id).unwrap_or_default(); + Ok((book, cover, targets)) + }, + |this, result, window, cx| { + this.busy = false; + match result { + Ok((book, cover, targets)) => { + this.sync_form(&book, window, cx); + this.book_cover = cover; + this.convert_targets = targets; + this.book = Some(book); + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn sync_form(&mut self, book: &BookDetail, window: &mut Window, cx: &mut Context) { + let title = book.title.clone(); + let authors = book.authors.join(", "); + let tags = book.tags.join(", "); + let series = book.series.clone().unwrap_or_default(); + let index = book + .series_index + .map(|value| value.to_string()) + .unwrap_or_default(); + let isbn = backend::isbn_of(book).unwrap_or_default(); + let comment = book.comment.clone(); + self.title_input + .update(cx, |input, cx| input.set_value(title, window, cx)); + self.authors_input + .update(cx, |input, cx| input.set_value(authors, window, cx)); + self.tags_input + .update(cx, |input, cx| input.set_value(tags, window, cx)); + self.series_input + .update(cx, |input, cx| input.set_value(series, window, cx)); + self.series_index_input + .update(cx, |input, cx| input.set_value(index, window, cx)); + self.isbn_input + .update(cx, |input, cx| input.set_value(isbn, window, cx)); + self.comment_input + .update(cx, |input, cx| input.set_value(comment, window, cx)); + } + + fn save_book_form(&mut self, window: &mut Window, cx: &mut Context) { + let Some(book) = self.book.clone() else { + return; + }; + let title = self.title_input.read(cx).value().to_string(); + let authors = split_list(&self.authors_input.read(cx).value()); + let tags = split_list(&self.tags_input.read(cx).value()); + let series = self.series_input.read(cx).value().to_string(); + let index = self.series_index_input.read(cx).value().to_string(); + let isbn = self.isbn_input.read(cx).value().to_string(); + let comment = self.comment_input.read(cx).value().to_string(); + let update = BookUpdate { + title: if title.trim().is_empty() { + "Untitled".into() + } else { + title.trim().to_string() + }, + authors, + tags, + series: { + let series = series.trim().to_string(); + if series.is_empty() { None } else { Some(series) } + }, + series_index: index.trim().parse().ok(), + comment, + identifiers: backend::with_isbn(&book, &isbn), + }; + let state = self.state.clone(); + self.busy = true; + self.spawn_io( + window, + cx, + move || backend::save_book(&state, book.id, update).map_err(|e| e.to_string()), + |this, result, window, cx| { + this.busy = false; + match result { + Ok(book) => { + this.sync_form(&book, window, cx); + this.book = Some(book); + this.message = "Saved.".into(); + this.error.clear(); + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn fetch_book(&mut self, window: &mut Window, cx: &mut Context) { + let Some(id) = self.book_id else { + return; + }; + let state = self.state.clone(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { backend::enrich(&state, id).await.map_err(|e| e.to_string()) }, + |this, result, window, cx| { + this.busy = false; + match result { + Ok(result) => { + this.message = if result.updated_fields.is_empty() { + "No missing fields to fill.".into() + } else { + format!("Updated: {}", result.updated_fields.join(", ")) + }; + this.sync_form(&result.book, window, cx); + let id = result.book.id; + this.book = Some(result.book); + if let Ok(cover) = this + .state + .with_conn(|conn, root| crate::library::cover_path(conn, root, id)) + { + this.book_cover = cover; + } + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn choose_cover(&mut self, window: &mut Window, cx: &mut Context) { + let Some(id) = self.book_id else { + return; + }; + let state = self.state.clone(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { backend::cover_candidates(&state, id).await.map_err(|e| e.to_string()) }, + |this, result, window, cx| { + this.busy = false; + match result { + Ok(candidates) => this.open_cover_picker(candidates, window, cx), + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn open_cover_picker( + &mut self, + candidates: Vec, + window: &mut Window, + cx: &mut Context, + ) { + let shell = cx.entity().clone(); + window.open_dialog(cx, move |dialog, _, _| { + let mut dialog = dialog.title("Choose cover"); + for candidate in &candidates { + let urls = candidate.download_urls.clone(); + let shell = shell.clone(); + let label = format!("{} · {}", candidate.title, candidate.authors); + dialog = dialog.child( + Button::new(SharedId(format!("cover-{}", candidate.id))) + .outline() + .label(label) + .on_click(move |_, _, cx| { + let urls = urls.clone(); + shell + .downgrade().update_in(cx, |this, window, cx| this.apply_picked_cover(urls, window, cx)) + .ok(); + }), + ); + } + dialog + }); + } + + fn apply_picked_cover(&mut self, urls: Vec, window: &mut Window, cx: &mut Context) { + let Some(id) = self.book_id else { + return; + }; + let state = self.state.clone(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { backend::apply_cover(&state, id, &urls).await.map_err(|e| e.to_string()) }, + |this, result, window, cx| { + this.busy = false; + match result { + Ok(book) => { + this.message = "Cover updated.".into(); + let id = book.id; + this.sync_form(&book, window, cx); + this.book = Some(book); + if let Ok(cover) = this + .state + .with_conn(|conn, root| crate::library::cover_path(conn, root, id)) + { + this.book_cover = cover; + } + window.close_dialog(cx); + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn convert_to(&mut self, target: String, window: &mut Window, cx: &mut Context) { + let Some(id) = self.book_id else { + return; + }; + let state = self.state.clone(); + self.busy = true; + self.spawn_io( + window, + cx, + move || backend::convert(&state, id, &target).map_err(|e| e.to_string()), + |this, result, window, cx| { + this.busy = false; + match result { + Ok(result) => { + this.message = if result.converted { + format!("Converted to {}.", result.format) + } else { + format!("{} already available.", result.format) + }; + if let Some(id) = this.book_id { + this.busy = false; + this.load_book(id, window, cx); + } + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn send_open_book(&mut self, device_id: String, window: &mut Window, cx: &mut Context) { + let Some(id) = self.book_id else { + return; + }; + let state = self.state.clone(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { backend::send(&state, id, &device_id).await.map_err(|e| e.to_string()) }, + |this, result, _, cx| { + this.busy = false; + match result { + Ok(result) => { + this.message = format!("Sent {} as {}.", result.format, result.remote_path); + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn confirm_delete_book(&mut self, window: &mut Window, cx: &mut Context) { + let shell = cx.entity().clone(); + window.open_alert_dialog(cx, move |alert, _, _| { + alert + .confirm() + .title("Delete book") + .description("Delete this book from the library?") + .on_ok({ + let shell = shell.clone(); + move |_, _, cx| { + shell + .downgrade().update_in(cx, |this, window, cx| { + if let Some(id) = this.book_id { + let state = this.state.clone(); + this.spawn_io( + window, + cx, + move || backend::remove_book(&state, id).map_err(|e| e.to_string()), + |this, result, window, cx| { + if let Err(error) = result { + this.error = error; + cx.notify(); + return; + } + this.book_id = None; + this.book = None; + this.reload_books(window, cx); + }, + ); + } + }) + .ok(); + true + } + }) + }); + } +} + +fn field(label: &str, control: impl IntoElement) -> impl IntoElement { + div() + .flex() + .flex_col() + .gap(px(4.)) + .child(div().text_size(px(12.)).child(label.to_string())) + .child(control) +} + +fn book_cover(path: Option, cx: &gpui_kit::App) -> impl IntoElement { + div() + .w(px(160.)) + .h(px(240.)) + .rounded(px(6.)) + .overflow_hidden() + .bg(cx.theme().muted) + .child(match path { + Some(path) => img(path).size_full().object_fit(ObjectFit::Cover).into_any_element(), + None => div().size_full().into_any_element(), + }) +} + +fn split_list(value: &str) -> Vec { + value + .split(',') + .map(str::trim) + .filter(|part| !part.is_empty()) + .map(str::to_string) + .collect() +} + +/// ElementId from an owned string. GPUI accepts `Into`. +struct SharedId(String); + +impl From for gpui_kit::ElementId { + fn from(value: SharedId) -> Self { + value.0.into() + } +} diff --git a/app/src/ui/devices.rs b/app/src/ui/devices.rs new file mode 100644 index 0000000..7f0fc64 --- /dev/null +++ b/app/src/ui/devices.rs @@ -0,0 +1,404 @@ +use gpui_kit::component::button::{Button, ButtonVariants as _}; +use gpui_kit::component::checkbox::Checkbox; +use gpui_kit::component::input::Input; +use gpui_kit::component::{ActiveTheme as _, Disableable as _, Selectable as _, WindowExt as _}; +use gpui_kit::prelude::{ + FluentBuilder as _, InteractiveElement as _, StatefulInteractiveElement as _, Styled as _, +}; +use gpui_kit::{Context, IntoElement, ParentElement as _, Window, div, px}; + +use super::backend; +use super::shell::Shell; +use super::theme::DISPLAY; +use crate::kindle::{ConnectionKind, DeviceBook, DeviceBookTarget}; + +impl Shell { + pub fn devices_page(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let devices = self.devices.clone(); + let history = self.sync_history.clone(); + div() + .id("devices-page") + .size_full() + .overflow_y_scroll() + .p(px(24.)) + .flex() + .flex_col() + .gap(px(16.)) + .child( + div() + .flex() + .items_center() + .justify_between() + .child(div().flex().flex_col().gap(px(4.)).child( + div() + .font_family(DISPLAY) + .text_size(px(28.)) + .child("Devices"), + ).child( + div().text_color(cx.theme().muted_foreground).child( + "Detects Kindles, Kobos, BOOX, PocketBook, reMarkable, Tolino, and Nook over USB/MTP or mounted volumes.", + ), + )) + .child( + Button::new("refresh-devices") + .outline() + .label(if self.devices_loading { "Scanning…" } else { "Refresh" }) + .disabled(self.devices_loading) + .on_click(cx.listener(|this, _, window, cx| this.refresh_devices(window, cx))), + ), + ) + .when(!self.error.is_empty(), |this| { + this.child(div().text_color(cx.theme().danger).child(self.error.clone())) + }) + .child(panel_title("Connected eReaders", cx)) + .when(devices.is_empty(), |this| { + this.child( + div().text_color(cx.theme().muted_foreground).child( + "No eReaders found yet. Grimoire checks USB/MTP and mounted volumes every few seconds. On macOS, quit Android File Transfer if an MTP device stays locked.", + ), + ) + }) + .children(devices.into_iter().map(|device| { + let id = device.id.clone(); + let send = if device.supports_send { "Send ready" } else { "Detected" }; + let connection = match device.connection { + ConnectionKind::Mtp => "USB/MTP", + ConnectionKind::Mount => "Mounted volume", + }; + div() + .p(px(12.)) + .rounded(px(10.)) + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().border) + .flex() + .items_center() + .justify_between() + .gap(px(12.)) + .child( + div() + .flex() + .flex_col() + .gap(px(2.)) + .child(device.product.clone()) + .child( + div() + .text_color(cx.theme().muted_foreground) + .child(format!( + "{} · {} · {} · prefers {}", + device.family_label, + connection, + format_bytes(device.free_space_bytes), + device.preferred_formats.join(", ") + )), + ), + ) + .child( + div().flex().items_center().gap(px(8.)).child( + Button::new(Id(format!("view-{id}"))) + .outline() + .label("View books") + .on_click(cx.listener(move |this, _, window, cx| { + this.device_id = Some(id.clone()); + this.device_library = None; + this.scan_device(window, cx); + })), + ).child(send), + ) + })) + .child(panel_title("Recent sends", cx)) + .when(history.is_empty(), |this| { + this.child(div().text_color(cx.theme().muted_foreground).child( + "No transfers recorded yet.", + )) + }) + .children(history.into_iter().take(20).map(|record| { + div().text_size(px(14.)).child(format!( + "Book {} · {} · {} · {}", + record.book_id, record.format, record.remote_path, record.last_sent_at + )) + })) + } + + pub fn refresh_devices(&mut self, window: &mut Window, cx: &mut Context) { + self.devices_loading = true; + let state = self.state.clone(); + self.spawn_task( + window, + cx, + async move { + let devices = backend::readers().await.map_err(|e| e.to_string())?; + let history = if state.is_open() { + backend::sync_history(&state).map_err(|e| e.to_string())? + } else { + Vec::new() + }; + Ok((devices, history)) + }, + |this, result, _, cx| { + this.devices_loading = false; + match result { + Ok((devices, history)) => { + this.devices = devices; + this.sync_history = history; + this.error.clear(); + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + pub fn device_library_page(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let books = self + .device_library + .as_ref() + .map(|library| library.books.clone()) + .unwrap_or_default(); + let query = self.device_query.read(cx).value().to_string().to_lowercase(); + let filtered: Vec = books + .into_iter() + .filter(|book| { + query.is_empty() + || book.title.to_lowercase().contains(&query) + || book.filename.to_lowercase().contains(&query) + || book.format.to_lowercase().contains(&query) + }) + .collect(); + let name = self + .device_library + .as_ref() + .map(|library| library.device_name.clone()) + .unwrap_or_else(|| "Device".into()); + let selected = self.device_selected.len(); + + div() + .size_full() + .flex() + .flex_col() + .child( + div() + .flex() + .items_center() + .gap(px(8.)) + .px(px(16.)) + .py(px(10.)) + .border_b_1() + .border_color(cx.theme().border) + .child( + Button::new("back-devices") + .ghost() + .label("Devices") + .on_click(cx.listener(|this, _, _, cx| { + this.device_id = None; + this.device_library = None; + cx.notify(); + })), + ) + .child(div().font_family(DISPLAY).text_size(px(22.)).child(name)) + .child(div().flex_1().child(Input::new(&self.device_query))) + .child( + Button::new("device-select") + .ghost() + .label(if self.device_select_mode { "Done" } else { "Select" }) + .selected(self.device_select_mode) + .on_click(cx.listener(|this, _, _, cx| { + this.device_select_mode = !this.device_select_mode; + if !this.device_select_mode { + this.device_selected.clear(); + } + cx.notify(); + })), + ) + .when(self.device_select_mode && selected > 0, |this| { + this.child( + Button::new("device-delete") + .danger() + .label(format!("Delete {selected}")) + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.confirm_device_delete(window, cx))), + ) + }), + ) + .child( + div() + .id("device-books") + .flex_1() + .overflow_y_scroll() + .p(px(16.)) + .flex() + .flex_col() + .gap(px(8.)) + .when(self.device_library.is_none(), |this| { + this.child(div().text_color(cx.theme().muted_foreground).child("Scanning device…")) + }) + .when(!self.message.is_empty(), |this| { + this.child(div().text_color(cx.theme().primary).child(self.message.clone())) + }) + .when(!self.error.is_empty(), |this| { + this.child(div().text_color(cx.theme().danger).child(self.error.clone())) + }) + .children(filtered.into_iter().map(|book| { + let key = book_key(&book); + let checked = self.device_selected.contains(&key); + let select_mode = self.device_select_mode; + div() + .flex() + .items_center() + .gap(px(10.)) + .p(px(8.)) + .rounded(px(8.)) + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().border) + .when(select_mode, |row| { + let key = key.clone(); + row.child( + Checkbox::new(Id(format!("dev-{key}"))) + .checked(checked) + .on_change(cx.listener(move |this, checked, _, cx| { + if *checked { + this.device_selected.insert(key.clone()); + } else { + this.device_selected.remove(&key); + } + cx.notify(); + })), + ) + }) + .child( + div() + .flex_1() + .flex() + .flex_col() + .child(book.title.clone()) + .child( + div().text_size(px(12.)).text_color(cx.theme().muted_foreground).child( + format!("{} · {} · {}", book.format, book.filename, format_size(book.size)), + ), + ), + ) + })), + ) + } + + pub fn scan_device(&mut self, window: &mut Window, cx: &mut Context) { + let Some(id) = self.device_id.clone() else { + return; + }; + self.busy = true; + self.error.clear(); + self.spawn_task( + window, + cx, + async move { backend::scan_device(id).await.map_err(|e| e.to_string()) }, + |this, result, _, cx| { + this.busy = false; + match result { + Ok(library) => this.device_library = Some(library), + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn confirm_device_delete(&mut self, window: &mut Window, cx: &mut Context) { + let count = self.device_selected.len(); + let shell = cx.entity().clone(); + window.open_alert_dialog(cx, move |alert, _, _| { + alert + .confirm() + .title(format!("Delete {count} books")) + .description("Remove the selected files from the device?") + .on_ok({ + let shell = shell.clone(); + move |_, _, cx| { + shell + .downgrade().update_in(cx, |this, window, cx| this.delete_device_selection(window, cx)) + .ok(); + true + } + }) + }); + } + + fn delete_device_selection(&mut self, window: &mut Window, cx: &mut Context) { + let Some(device_id) = self.device_id.clone() else { + return; + }; + let Some(library) = self.device_library.clone() else { + return; + }; + let targets = library + .books + .iter() + .filter(|book| self.device_selected.contains(&book_key(book))) + .map(|book| DeviceBookTarget { + path: book.path.clone(), + filename: book.filename.clone(), + handle: book.handle, + }) + .collect::>(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { backend::delete_on_device(device_id, targets).await.map_err(|e| e.to_string()) }, + |this, result, window, cx| { + this.busy = false; + match result { + Ok(result) => { + this.message = format!("Removed {} books from the device.", result.removed); + this.device_selected.clear(); + this.scan_device(window, cx); + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } +} + +fn book_key(book: &DeviceBook) -> String { + format!("{}|{}|{}", book.path, book.handle.unwrap_or(0), book.filename) +} + +fn panel_title(text: &str, _cx: &gpui_kit::App) -> impl IntoElement { + div() + .font_family(DISPLAY) + .text_size(px(20.)) + .child(text.to_string()) +} + +fn format_bytes(bytes: Option) -> String { + let Some(bytes) = bytes else { + return "Unknown free space".into(); + }; + let gb = bytes as f64 / 1e9; + if gb >= 1.0 { + format!("{gb:.1} GB free") + } else { + format!("{:.0} MB free", bytes as f64 / 1e6) + } +} + +fn format_size(bytes: u64) -> String { + if bytes < 1024 { + format!("{bytes} B") + } else if bytes < 1024 * 1024 { + format!("{:.1} KB", bytes as f64 / 1024.0) + } else { + format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0)) + } +} + +struct Id(String); + +impl From for gpui_kit::ElementId { + fn from(value: Id) -> Self { + value.0.into() + } +} diff --git a/app/src/ui/document.rs b/app/src/ui/document.rs new file mode 100644 index 0000000..e2e8a67 --- /dev/null +++ b/app/src/ui/document.rs @@ -0,0 +1,208 @@ +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use zip::ZipArchive; + +#[derive(Clone, Debug)] +pub struct Chapter { + pub title: String, + pub body: String, +} + +pub fn load_chapters(path: &Path, format: &str) -> Result, String> { + match format.to_ascii_uppercase().as_str() { + "TXT" => { + let text = std::fs::read_to_string(path).map_err(|e| e.to_string())?; + Ok(vec![Chapter { + title: "Text".into(), + body: text, + }]) + } + "EPUB" => load_epub(path), + other => Err(format!("{other} is not a text chapter format.")), + } +} + +fn load_epub(path: &Path) -> Result, String> { + let file = File::open(path).map_err(|e| e.to_string())?; + let mut archive = ZipArchive::new(file).map_err(|e| e.to_string())?; + let container = read_zip(&mut archive, "META-INF/container.xml")?; + let opf_path = attr_value(&container, "full-path") + .ok_or_else(|| "EPUB is missing a rootfile path.".to_string())?; + let opf = read_zip(&mut archive, &opf_path)?; + let base = opf_path + .rsplit_once('/') + .map(|(dir, _)| format!("{dir}/")) + .unwrap_or_default(); + + let manifest = manifest_hrefs(&opf); + let spine = spine_ids(&opf); + if spine.is_empty() { + return Err("EPUB spine is empty.".into()); + } + + let mut chapters = Vec::new(); + for (index, id) in spine.iter().enumerate() { + let Some(href) = manifest.get(id) else { + continue; + }; + let full = join_zip(&base, href); + let html = match read_zip(&mut archive, &full) { + Ok(html) => html, + Err(_) => continue, + }; + let body = html_to_text(&html); + if body.trim().is_empty() { + continue; + } + let title = html_title(&html).unwrap_or_else(|| format!("Chapter {}", index + 1)); + chapters.push(Chapter { title, body }); + } + if chapters.is_empty() { + return Err("No readable chapters in this EPUB.".into()); + } + Ok(chapters) +} + +fn read_zip(archive: &mut ZipArchive, name: &str) -> Result { + let mut buf = String::new(); + if let Ok(mut file) = archive.by_name(name) { + file.read_to_string(&mut buf).map_err(|e| e.to_string())?; + return Ok(buf); + } + let alt = name.replace('\\', "/"); + let mut file = archive + .by_name(&alt) + .map_err(|_| format!("Missing {name} in the EPUB."))?; + file.read_to_string(&mut buf).map_err(|e| e.to_string())?; + Ok(buf) +} + +fn join_zip(base: &str, href: &str) -> String { + let href = href.split('#').next().unwrap_or(href); + if href.starts_with('/') { + return href.trim_start_matches('/').to_string(); + } + format!("{base}{href}") +} + +fn attr_value(xml: &str, name: &str) -> Option { + let needle = format!("{name}=\""); + let start = xml.find(&needle)? + needle.len(); + let rest = &xml[start..]; + let end = rest.find('"')?; + Some(rest[..end].to_string()) +} + +fn manifest_hrefs(opf: &str) -> std::collections::HashMap { + let mut map = std::collections::HashMap::new(); + for item in opf.split("").next().unwrap_or(item); + let chunk = chunk.split("/>").next().unwrap_or(chunk); + if let (Some(id), Some(href)) = (attr_value(chunk, "id"), attr_value(chunk, "href")) { + map.insert(id, href); + } + } + map +} + +fn spine_ids(opf: &str) -> Vec { + let mut ids = Vec::new(); + for item in opf.split("").next().unwrap_or(item); + if let Some(id) = attr_value(chunk, "idref") { + ids.push(id); + } + } + ids +} + +fn html_title(html: &str) -> Option { + let lower = html.to_ascii_lowercase(); + let start = lower.find("')? + start + 1; + let end_rel = lower[after..].find(" String { + let mut out = String::new(); + let mut chars = html.chars().peekable(); + let mut in_tag = false; + let mut tag = String::new(); + while let Some(ch) = chars.next() { + if ch == '<' { + in_tag = true; + tag.clear(); + continue; + } + if in_tag { + if ch == '>' { + in_tag = false; + let name = tag + .split_whitespace() + .next() + .unwrap_or("") + .trim_start_matches('/') + .to_ascii_lowercase(); + if matches!( + name.as_str(), + "p" | "div" | "br" | "h1" | "h2" | "h3" | "h4" | "li" | "tr" | "blockquote" + ) { + if !out.ends_with("\n\n") { + out.push_str("\n\n"); + } + } + } else { + tag.push(ch); + } + continue; + } + if ch == '&' { + let mut entity = String::new(); + while let Some(next) = chars.peek().copied() { + if next == ';' || entity.len() > 12 { + break; + } + entity.push(next); + chars.next(); + } + if chars.peek() == Some(&';') { + chars.next(); + } + out.push_str(match entity.as_str() { + "amp" => "&", + "lt" => "<", + "gt" => ">", + "quot" => "\"", + "apos" | "#39" => "'", + "nbsp" => " ", + _ => " ", + }); + continue; + } + out.push(ch); + } + let mut collapsed = String::new(); + let mut blank = false; + for line in out.lines() { + let line = line.split_whitespace().collect::>().join(" "); + if line.is_empty() { + if !blank && !collapsed.is_empty() { + collapsed.push('\n'); + } + blank = true; + } else { + if !collapsed.is_empty() && !collapsed.ends_with('\n') { + collapsed.push('\n'); + } + collapsed.push_str(&line); + collapsed.push('\n'); + blank = false; + } + } + collapsed.trim().to_string() +} diff --git a/app/src/ui/icon.rs b/app/src/ui/icon.rs new file mode 100644 index 0000000..a995a1f --- /dev/null +++ b/app/src/ui/icon.rs @@ -0,0 +1,42 @@ +use std::sync::Arc; + +use gpui_kit::WindowOptions; + +const ICON_PNG: &[u8] = include_bytes!("../../icons/icon.png"); + +pub fn apply(options: &mut WindowOptions) { + options.icon = Some(raster()); +} + +/// Dock and Cmd-Tab icon. The Windows executable icon is `icons/icon.ico`, embedded as resource 1. +pub fn install() { + #[cfg(target_os = "macos")] + install_macos(); +} + +fn raster() -> Arc { + Arc::new( + image::load_from_memory(ICON_PNG) + .expect("Grimoire icon.png") + .into_rgba8(), + ) +} + +#[cfg(target_os = "macos")] +fn install_macos() { + use objc2::{AnyThread, MainThreadMarker}; + use objc2_app_kit::{NSApplication, NSImage}; + use objc2_foundation::NSData; + + let Some(marker) = MainThreadMarker::new() else { + return; + }; + let data = NSData::with_bytes(include_bytes!("../../icons/icon.icns")); + let Some(image) = NSImage::initWithData(NSImage::alloc(), &data) else { + return; + }; + let app = NSApplication::sharedApplication(marker); + unsafe { + app.setApplicationIconImage(Some(&image)); + } +} diff --git a/app/src/ui/library.rs b/app/src/ui/library.rs new file mode 100644 index 0000000..c18fb18 --- /dev/null +++ b/app/src/ui/library.rs @@ -0,0 +1,1244 @@ +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; + +use gpui_kit::component::button::{Button, ButtonVariants as _}; +use gpui_kit::component::checkbox::Checkbox; +use gpui_kit::component::input::Input; +use gpui_kit::component::menu::{ContextMenuExt as _, PopupMenuItem}; +use gpui_kit::component::{ + ActiveTheme as _, Disableable as _, IconName, Selectable as _, WindowExt as _, +}; +use gpui_kit::prelude::{ + FluentBuilder as _, InteractiveElement as _, StatefulInteractiveElement as _, Styled as _, + StyledImage as _, +}; +use gpui_kit::AppContext as _; +use gpui_kit::{AnyElement, Context, IntoElement, ObjectFit, ParentElement as _, Window, div, img, px}; + +use super::backend::{self, LibraryStatus}; +use super::shell::{FormatKind, ReadingStatus, Route, Shell}; +use super::theme::DISPLAY; +use crate::library::{BookBulkPatch, BookSummary}; + +impl Shell { + pub fn library_page(&mut self, window: &mut Window, cx: &mut Context) -> AnyElement { + let shell = cx.entity().clone(); + if self.loading && self.books.is_empty() && self.library_open { + return super::shell::muted_page("Opening library…", cx).into_any_element(); + } + if !self.library_open { + return self.closed_library(window, cx).into_any_element(); + } + + let filtered = self.filtered_books(); + let mut reading = Vec::new(); + let mut to_read = Vec::new(); + let mut read = Vec::new(); + for book in &filtered { + match book_status(book) { + ReadingStatus::Reading => reading.push(book.clone()), + ReadingStatus::ToRead => to_read.push(book.clone()), + ReadingStatus::Read => read.push(book.clone()), + } + } + + div() + .size_full() + .flex() + .flex_col() + .child(self.library_bar(filtered.is_empty(), window, cx)) + .child( + div() + .flex_1() + .min_h(px(0.)) + .flex() + .overflow_hidden() + .when(self.filters_open, |this| { + let books = self.books.clone(); + this.child(self.filters(&books, cx)) + }) + .child(self.library_grid( + &reading, + &to_read, + &read, + filtered.is_empty(), + shell, + window, + cx, + )), + ) + .into_any_element() + } + + fn closed_library(&mut self, _window: &mut Window, cx: &mut Context) -> gpui_kit::Div { + div().p(px(28.)).child( + div() + .max_w(px(560.)) + .p(px(22.)) + .rounded(px(10.)) + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().border) + .flex() + .flex_col() + .gap(px(10.)) + .child( + div() + .font_family(DISPLAY) + .text_size(px(26.)) + .child("Your library starts here"), + ) + .child( + div().text_color(cx.theme().muted_foreground).child( + "Set a library folder. Grimoire stores metadata.db and your book files locally, Calibre-style.", + ), + ) + .child( + Button::new("choose-library") + .primary() + .label(if self.busy { + "Working…" + } else { + "Choose library folder" + }) + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.pick_library(window, cx))), + ) + .child( + Button::new("open-settings") + .ghost() + .label("Open Settings") + .on_click(cx.listener(|this, _, _, cx| this.go(Route::Settings, cx))), + ) + .when(!self.error.is_empty(), |this| { + this.child(div().text_color(cx.theme().danger).child(self.error.clone())) + }), + ) + } + + fn library_bar( + &mut self, + empty: bool, + _window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + div() + .flex() + .items_center() + .gap(px(8.)) + .px(px(16.)) + .py(px(10.)) + .border_b_1() + .border_color(cx.theme().border) + .bg(cx.theme().sidebar) + .child( + div() + .flex_1() + .min_w(px(180.)) + .child(Input::new(&self.search)), + ) + .child( + Button::new("toggle-filters") + .ghost() + .compact() + .icon(IconName::PanelLeft) + .selected(self.filters_open) + .tooltip(if self.filters_open { + "Hide filters" + } else { + "Show filters" + }) + .on_click(cx.listener(|this, _, _, cx| { + this.filters_open = !this.filters_open; + cx.notify(); + })), + ) + .child( + Button::new("select-books") + .ghost() + .compact() + .icon(if self.select_mode { + IconName::Check + } else { + IconName::Menu + }) + .selected(self.select_mode) + .disabled(self.busy || empty) + .tooltip(if self.select_mode { + "Done selecting" + } else { + "Select books" + }) + .on_click(cx.listener(|this, _, _, cx| { + this.select_mode = !this.select_mode; + if !this.select_mode { + this.selected.clear(); + this.bulk_open = false; + } + cx.notify(); + })), + ) + .child( + Button::new("import-books") + .primary() + .compact() + .icon(IconName::Plus) + .tooltip("Import books") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.import_files(window, cx))), + ) + .child( + Button::new("switch-library") + .ghost() + .compact() + .icon(IconName::FolderOpen) + .tooltip("Switch library") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.pick_library(window, cx))), + ) + } + + fn filters(&mut self, books: &[BookSummary], cx: &mut Context) -> impl IntoElement { + let mut genres: HashMap = HashMap::new(); + let mut authors: HashMap = HashMap::new(); + let mut status_counts = [0usize; 3]; + let mut format_counts = [0usize; 2]; + for book in books { + match book_status(book) { + ReadingStatus::Reading => status_counts[0] += 1, + ReadingStatus::ToRead => status_counts[1] += 1, + ReadingStatus::Read => status_counts[2] += 1, + } + match format_kind(book) { + FormatKind::Novel => format_counts[0] += 1, + FormatKind::Comic => format_counts[1] += 1, + } + for tag in &book.tags { + let tag = tag.trim(); + if !tag.is_empty() { + *genres.entry(tag.to_string()).or_default() += 1; + } + } + for author in author_labels(book) { + *authors.entry(author).or_default() += 1; + } + } + let mut genre_list: Vec<_> = genres.into_iter().collect(); + genre_list.sort_by(|a, b| a.0.cmp(&b.0)); + let mut author_list: Vec<_> = authors.into_iter().collect(); + author_list.sort_by(|a, b| a.0.cmp(&b.0)); + + let active = !self.filter_status.is_empty() + || !self.filter_genres.is_empty() + || !self.filter_formats.is_empty() + || !self.filter_authors.is_empty(); + + div() + .id("library-filters") + .w(px(230.)) + .h_full() + .overflow_y_scroll() + .border_r_1() + .border_color(cx.theme().border) + .p(px(14.)) + .flex() + .flex_col() + .gap(px(12.)) + .child(filter_heading("Status")) + .children(status_row( + "reading", + "Reading", + status_counts[0], + self.filter_status.contains(&ReadingStatus::Reading), + cx, + )) + .children(status_row( + "to-read", + "To Read", + status_counts[1], + self.filter_status.contains(&ReadingStatus::ToRead), + cx, + )) + .children(status_row( + "read", + "Read", + status_counts[2], + self.filter_status.contains(&ReadingStatus::Read), + cx, + )) + .child(filter_heading("Format")) + .children(format_row( + "novel", + "Novel", + format_counts[0], + self.filter_formats.contains(&FormatKind::Novel), + cx, + )) + .children(format_row( + "comic", + "Comic Book", + format_counts[1], + self.filter_formats.contains(&FormatKind::Comic), + cx, + )) + .when(!genre_list.is_empty(), |this| { + let rows = genre_list.into_iter().map(|(name, count)| { + let checked = self.filter_genres.contains(&name); + let id = format!("genre-{name}"); + let label = format!("{name} {count}"); + let key = name.clone(); + Checkbox::new(id) + .label(label) + .checked(checked) + .on_change(cx.listener(move |this, checked, _, cx| { + if *checked { + this.filter_genres.insert(key.clone()); + } else { + this.filter_genres.remove(&key); + } + cx.notify(); + })) + }); + this.child(filter_heading("Genres")).children(rows) + }) + .when(!author_list.is_empty(), |this| { + let rows = author_list.into_iter().take(40).map(|(name, count)| { + let checked = self.filter_authors.contains(&name); + let id = format!("author-{name}"); + let label = format!("{name} {count}"); + let key = name.clone(); + Checkbox::new(id) + .label(label) + .checked(checked) + .on_change(cx.listener(move |this, checked, _, cx| { + if *checked { + this.filter_authors.insert(key.clone()); + } else { + this.filter_authors.remove(&key); + } + cx.notify(); + })) + }); + this.child(filter_heading("Authors")).children(rows) + }) + .when(active, |this| { + this.child( + Button::new("clear-filters") + .ghost() + .label("Clear filters") + .on_click(cx.listener(|this, _, _, cx| { + this.filter_status.clear(); + this.filter_genres.clear(); + this.filter_formats.clear(); + this.filter_authors.clear(); + cx.notify(); + })), + ) + }) + } + + fn library_grid( + &mut self, + reading: &[BookSummary], + to_read: &[BookSummary], + read: &[BookSummary], + none: bool, + shell: gpui_kit::Entity, + _window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let selected_count = self.selected.len(); + div() + .id("library-grid") + .flex_1() + .h_full() + .overflow_y_scroll() + .p(px(18.)) + .flex() + .flex_col() + .gap(px(14.)) + .when(!self.message.is_empty(), |this| { + this.child(div().text_color(cx.theme().primary).child(self.message.clone())) + }) + .when(!self.error.is_empty(), |this| { + this.child(div().text_color(cx.theme().danger).child(self.error.clone())) + }) + .when(self.select_mode && selected_count > 0, |this| { + this.child(self.selection_bar(cx)) + }) + .when(self.bulk_open, |this| this.child(self.bulk_panel(cx))) + .when(self.books.is_empty(), |this| { + this.child(empty_panel( + "No books yet", + "Import EPUB, PDF, TXT, CBZ/CBR comics, or DRM-free MOBI to read in Grimoire. AZW3 can be imported for Kindle send.", + cx, + )) + }) + .when(!self.books.is_empty() && none, |this| { + this.child(empty_panel( + "No books match these filters", + "Try clearing some filters, or adjust your search.", + cx, + )) + }) + .child(book_section("Reading", reading, &self.covers, self.select_mode, &self.selected, shell.clone(), cx)) + .child(book_section("To Read", to_read, &self.covers, self.select_mode, &self.selected, shell.clone(), cx)) + .child(book_section("Read", read, &self.covers, self.select_mode, &self.selected, shell, cx)) + } + + fn selection_bar(&mut self, cx: &mut Context) -> impl IntoElement { + let count = self.selected.len(); + let can_send = self.devices.iter().any(|device| device.supports_send); + div() + .flex() + .flex_wrap() + .items_center() + .gap(px(8.)) + .p(px(10.)) + .rounded(px(10.)) + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().border) + .child(format!("{count} selected")) + .child( + Button::new("select-all") + .outline() + .label("Select all") + .disabled(self.busy) + .on_click(cx.listener(|this, _, _, cx| { + this.selected = this.filtered_books().into_iter().map(|book| book.id).collect(); + cx.notify(); + })), + ) + .child( + Button::new("clear-selection") + .outline() + .label("Clear") + .on_click(cx.listener(|this, _, _, cx| { + this.selected.clear(); + cx.notify(); + })), + ) + .child( + Button::new("bulk-fetch") + .outline() + .label("Fetch metadata") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.bulk_fetch(window, cx))), + ) + .child( + Button::new("bulk-edit") + .outline() + .label("Edit metadata") + .disabled(self.busy) + .on_click(cx.listener(|this, _, _, cx| { + this.bulk_open = true; + cx.notify(); + })), + ) + .child( + Button::new("bulk-send") + .outline() + .label("Send to device") + .disabled(self.busy || !can_send) + .on_click(cx.listener(|this, _, window, cx| this.bulk_send(window, cx))), + ) + .child( + Button::new("bulk-delete") + .danger() + .label("Delete") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.confirm_bulk_delete(window, cx))), + ) + } + + fn bulk_panel(&mut self, cx: &mut Context) -> impl IntoElement { + div() + .p(px(14.)) + .rounded(px(10.)) + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().border) + .flex() + .flex_col() + .gap(px(8.)) + .child("Only checked fields are applied. Tags are added, not replaced.") + .child(Input::new(&self.bulk_tags)) + .child( + Checkbox::new("bulk-series") + .label("Apply series") + .checked(self.bulk_series) + .on_change(cx.listener(|this, checked, _, cx| { + this.bulk_series = *checked; + cx.notify(); + })), + ) + .child(Input::new(&self.bulk_series_input)) + .child( + Checkbox::new("bulk-index") + .label("Apply series index") + .checked(self.bulk_index) + .on_change(cx.listener(|this, checked, _, cx| { + this.bulk_index = *checked; + cx.notify(); + })), + ) + .child(Input::new(&self.bulk_index_input)) + .child( + Checkbox::new("bulk-notes") + .label("Replace notes") + .checked(self.bulk_notes) + .on_change(cx.listener(|this, checked, _, cx| { + this.bulk_notes = *checked; + cx.notify(); + })), + ) + .child(gpui_kit::component::input::Textarea::new(&self.bulk_notes_input)) + .child( + div().flex().gap(px(8.)).child( + Button::new("bulk-cancel") + .outline() + .label("Cancel") + .on_click(cx.listener(|this, _, _, cx| { + this.bulk_open = false; + cx.notify(); + })), + ).child( + Button::new("bulk-apply") + .primary() + .label("Apply") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.apply_bulk(window, cx))), + ), + ) + } + + pub fn filtered_books(&self) -> Vec { + self.books + .iter() + .filter(|book| book_matches(book, self)) + .cloned() + .collect() + } + + pub fn reload_books(&mut self, window: &mut Window, cx: &mut Context) { + if !self.library_open { + self.books.clear(); + self.covers.clear(); + self.loading = false; + cx.notify(); + return; + } + let state = self.state.clone(); + let query = self.search.read(cx).value().to_string(); + self.loading = true; + self.spawn_io( + window, + cx, + move || { + let query = query.trim(); + let books = backend::list(&state, if query.is_empty() { None } else { Some(query) }) + .map_err(|e| e.to_string())?; + let covers = backend::covers_for(&state, &books); + Ok((books, covers)) + }, + |this, result, _, cx| { + this.loading = false; + match result { + Ok((books, covers)) => { + this.books = books; + this.covers = covers; + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + pub fn pick_library(&mut self, window: &mut Window, cx: &mut Context) { + let Some(path) = rfd::FileDialog::new() + .set_title("Choose or create a Grimoire library folder") + .pick_folder() + else { + return; + }; + match backend::create_or_open(&self.state, &path) { + Ok(status) => { + self.apply_status(status); + self.error.clear(); + self.message = "Library path saved. Grimoire will open it on the next launch too.".into(); + self.reload_books(window, cx); + } + Err(error) => self.error = error.to_string(), + } + cx.notify(); + } + + pub fn import_files(&mut self, window: &mut Window, cx: &mut Context) { + let Some(paths) = rfd::FileDialog::new() + .set_title("Import eBooks") + .add_filter("eBooks", backend::SUPPORTED_EXT) + .pick_files() + else { + return; + }; + self.import_paths(paths, window, cx); + } + + pub fn import_paths(&mut self, paths: Vec, window: &mut Window, cx: &mut Context) { + let ebook_paths: Vec<_> = paths + .into_iter() + .filter(|path| backend::SUPPORTED_EXT.contains(&backend::extension_of(path).as_str())) + .collect(); + if ebook_paths.is_empty() { + self.error = "No supported eBooks in that drop. Use EPUB, PDF, AZW3, MOBI, TXT, CBZ, or CBR.".into(); + cx.notify(); + return; + } + if !self.library_open { + self.error = "Open a library folder before importing books.".into(); + cx.notify(); + return; + } + self.busy = true; + self.error.clear(); + self.message.clear(); + let state = self.state.clone(); + self.spawn_task( + window, + cx, + async move { + let mut imported = 0; + let mut failures = Vec::new(); + for path in ebook_paths { + match backend::import(&state, &path) { + Ok(book) => { + imported += 1; + if needs_enrichment(&book) { + let _ = backend::enrich(&state, book.id).await; + } + } + Err(error) => { + let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("book"); + failures.push(format!("{name}: {error}")); + } + } + } + Ok((imported, failures)) + }, + |this, result, window, cx| { + this.busy = false; + match result { + Ok((imported, failures)) => { + if imported > 0 { + this.message = if imported == 1 { + "Imported 1 book.".into() + } else { + format!("Imported {imported} books.") + }; + } + if !failures.is_empty() { + this.error = failures.into_iter().take(3).collect::>().join(" · "); + } + } + Err(error) => this.error = error, + } + this.reload_books(window, cx); + }, + ); + } + + fn apply_bulk(&mut self, window: &mut Window, cx: &mut Context) { + let ids: Vec = self.selected.iter().copied().collect(); + let tags = self + .bulk_tags + .read(cx) + .value() + .split(',') + .map(|tag| tag.trim().to_string()) + .filter(|tag| !tag.is_empty()) + .collect::>(); + let series = self.bulk_series_input.read(cx).value().to_string(); + let index = self.bulk_index_input.read(cx).value().to_string(); + let notes = self.bulk_notes_input.read(cx).value().to_string(); + if tags.is_empty() && !self.bulk_series && !self.bulk_index && !self.bulk_notes { + self.error = "Choose at least one field to update.".into(); + cx.notify(); + return; + } + let patch = BookBulkPatch { + add_tags: tags, + set_series: self.bulk_series, + series: if self.bulk_series { + let series = series.trim().to_string(); + if series.is_empty() { None } else { Some(series) } + } else { + None + }, + set_series_index: self.bulk_index, + series_index: if self.bulk_index { + Some(index.trim().parse().unwrap_or(1.0)) + } else { + None + }, + set_comment: self.bulk_notes, + comment: notes, + }; + self.busy = true; + let state = self.state.clone(); + self.spawn_io( + window, + cx, + move || backend::patch_books(&state, &ids, patch).map_err(|e| e.to_string()), + |this, result, window, cx| { + this.busy = false; + match result { + Ok(result) => { + this.message = format!("Updated {} books.", result.updated); + this.bulk_open = false; + if !result.failed.is_empty() { + this.error = format!("{} failed.", result.failed.len()); + } + } + Err(error) => this.error = error, + } + this.reload_books(window, cx); + }, + ); + } + + fn bulk_fetch(&mut self, window: &mut Window, cx: &mut Context) { + let ids: Vec = self.selected.iter().copied().collect(); + let state = self.state.clone(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { + let mut updated = 0; + for id in ids { + if let Ok(result) = backend::enrich(&state, id).await { + if !result.updated_fields.is_empty() { + updated += 1; + } + } + } + Ok(updated) + }, + |this, result, window, cx| { + this.busy = false; + if let Ok(updated) = result { + this.message = format!("Fetched metadata for {updated} books."); + } + this.reload_books(window, cx); + }, + ); + } + + fn bulk_send(&mut self, window: &mut Window, cx: &mut Context) { + let Some(device) = self.devices.iter().find(|device| device.supports_send).cloned() else { + self.error = "No Kindle connected. Connect over MTP / USB file transfer, then try again.".into(); + cx.notify(); + return; + }; + let ids: Vec = self.selected.iter().copied().collect(); + let state = self.state.clone(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { + let mut sent = 0; + for id in ids { + if backend::send(&state, id, &device.id).await.is_ok() { + sent += 1; + } + } + Ok((sent, device.product)) + }, + |this, result, _, cx| { + this.busy = false; + if let Ok((sent, product)) = result { + this.message = format!("Sent {sent} books to {product}."); + } + cx.notify(); + }, + ); + } + + fn confirm_bulk_delete(&mut self, window: &mut Window, cx: &mut Context) { + let count = self.selected.len(); + let shell = cx.entity().clone(); + window.open_alert_dialog(cx, move |alert, _, _| { + alert + .confirm() + .title(format!("Delete {count} books")) + .description("Delete the selected books from the library?") + .on_ok({ + let shell = shell.clone(); + move |_, window, cx| { + shell + .downgrade().update_in(cx, |this, window, cx| this.bulk_delete(window, cx)) + .ok(); + let _ = window; + true + } + }) + }); + } + + fn bulk_delete(&mut self, window: &mut Window, cx: &mut Context) { + let ids: Vec = self.selected.iter().copied().collect(); + let state = self.state.clone(); + self.busy = true; + self.spawn_io( + window, + cx, + move || backend::remove_books(&state, &ids).map_err(|e| e.to_string()), + |this, result, window, cx| { + this.busy = false; + match result { + Ok(result) => { + this.message = format!("Deleted {} books.", result.updated); + this.selected.clear(); + } + Err(error) => this.error = error, + } + this.reload_books(window, cx); + }, + ); + } + + pub fn show_book(&mut self, id: i64, window: &mut Window, cx: &mut Context) { + self.book_id = Some(id); + self.load_book(id, window, cx); + } + + pub fn open_reader_for(&mut self, book: &BookSummary, window: &mut Window, cx: &mut Context) { + if backend::can_read(&book.formats) { + self.open_reader(book.id, cx); + return; + } + let title = book.title.clone(); + let formats = if book.formats.is_empty() { + "none".into() + } else { + book.formats.join(", ") + }; + window.open_alert_dialog(cx, move |alert, _, _| { + alert.title("Cannot open book").description(format!( + "\"{title}\" can't be opened in the reader. Available formats: {formats}. Grimoire can open EPUB, PDF, TXT, MOBI, CBZ, and CBR." + )) + }); + } + + pub fn open_reader(&mut self, id: i64, cx: &mut Context) { + let state = self.state.clone(); + let _ = gpui_kit::open_window(super::reader_window_options("Grimoire", cx), cx, |window, cx| { + cx.new(|cx| super::reader::ReaderWindow::new(state, id, window, cx)) + }); + } +} + +fn book_matches(book: &BookSummary, shell: &Shell) -> bool { + if !shell.filter_status.is_empty() && !shell.filter_status.contains(&book_status(book)) { + return false; + } + if !shell.filter_formats.is_empty() && !shell.filter_formats.contains(&format_kind(book)) { + return false; + } + if !shell.filter_genres.is_empty() + && !book.tags.iter().any(|tag| shell.filter_genres.contains(tag.trim())) + { + return false; + } + if !shell.filter_authors.is_empty() + && !author_labels(book) + .iter() + .any(|author| shell.filter_authors.contains(author)) + { + return false; + } + true +} + +fn book_status(book: &BookSummary) -> ReadingStatus { + match book.progress_percent { + None => ReadingStatus::ToRead, + Some(percent) if percent >= 100.0 => ReadingStatus::Read, + Some(_) => ReadingStatus::Reading, + } +} + +fn format_kind(book: &BookSummary) -> FormatKind { + if book + .formats + .iter() + .any(|format| format.eq_ignore_ascii_case("cbz") || format.eq_ignore_ascii_case("cbr")) + { + FormatKind::Comic + } else { + FormatKind::Novel + } +} + +fn author_labels(book: &BookSummary) -> Vec { + let labels = book + .authors + .iter() + .map(|author| author.trim().to_string()) + .filter(|author| !author.is_empty()) + .collect::>(); + if labels.is_empty() { + vec!["Unknown".into()] + } else { + labels + } +} + +fn needs_enrichment(book: &crate::library::BookDetail) -> bool { + let authors_missing = book.authors.is_empty() + || book + .authors + .iter() + .all(|author| author.trim().eq_ignore_ascii_case("unknown")); + !book.has_cover || authors_missing +} + +fn status_row( + id: &str, + label: &str, + count: usize, + checked: bool, + cx: &mut Context, +) -> Option { + let status = match id { + "reading" => ReadingStatus::Reading, + "read" => ReadingStatus::Read, + _ => ReadingStatus::ToRead, + }; + Some( + Checkbox::new(id.to_string()) + .label(format!("{label} {count}")) + .checked(checked) + .on_change(cx.listener(move |this, checked, _, cx| { + if *checked { + this.filter_status.insert(status); + } else { + this.filter_status.remove(&status); + } + cx.notify(); + })), + ) +} + +fn format_row( + id: &str, + label: &str, + count: usize, + checked: bool, + cx: &mut Context, +) -> Option { + let kind = if id == "comic" { + FormatKind::Comic + } else { + FormatKind::Novel + }; + Some( + Checkbox::new(id.to_string()) + .label(format!("{label} {count}")) + .checked(checked) + .on_change(cx.listener(move |this, checked, _, cx| { + if *checked { + this.filter_formats.insert(kind); + } else { + this.filter_formats.remove(&kind); + } + cx.notify(); + })), + ) +} + +fn filter_heading(text: &str) -> impl IntoElement { + div() + .text_size(px(12.)) + .font_weight(gpui_kit::FontWeight::SEMIBOLD) + .child(text.to_string()) +} + +fn empty_panel(title: &str, body: &str, cx: &gpui_kit::App) -> impl IntoElement { + div() + .p(px(22.)) + .rounded(px(10.)) + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().border) + .child(div().font_family(DISPLAY).text_size(px(22.)).child(title.to_string())) + .child(div().mt(px(6.)).text_color(cx.theme().muted_foreground).child(body.to_string())) +} + +fn book_section( + title: &str, + books: &[BookSummary], + covers: &HashMap, + select_mode: bool, + selected: &HashSet, + shell: gpui_kit::Entity, + cx: &mut Context, +) -> impl IntoElement { + if books.is_empty() { + return div(); + } + div() + .flex() + .flex_col() + .gap(px(10.)) + .child(super::shell::section_title(title, Some(books.len()))) + .child( + div().flex().flex_wrap().gap(px(16.)).children(books.iter().map(|book| { + book_card(book, covers.get(&book.id), select_mode, selected.contains(&book.id), shell.clone(), cx) + })), + ) +} + +fn book_card( + book: &BookSummary, + cover: Option<&PathBuf>, + select_mode: bool, + is_selected: bool, + shell: gpui_kit::Entity, + cx: &mut Context, +) -> impl IntoElement { + let id = book.id; + let title = book.title.clone(); + let authors = author_labels(book).join(", "); + let can_read = backend::can_read(&book.formats); + let progress = book.progress_percent.filter(|percent| *percent < 100.0); + let menu_shell = shell.clone(); + let open_shell = shell.clone(); + let card_title = title.clone(); + let menu_title = title.clone(); + + div() + .id(format!("book-card-{id}")) + .w(px(148.)) + .cursor_pointer() + .when(is_selected, |this| { + this.border_1().border_color(cx.theme().primary).rounded(px(8.)).p(px(4.)) + }) + .on_click(move |_, window, cx| { + open_shell + .downgrade().update_in(cx, |this, window, cx| { + if this.select_mode { + if this.selected.contains(&id) { + this.selected.remove(&id); + } else { + this.selected.insert(id); + } + cx.notify(); + } else if let Some(book) = this.books.iter().find(|book| book.id == id).cloned() { + this.open_reader_for(&book, window, cx); + } + }) + .ok(); + let _ = window; + }) + .context_menu(move |menu, _, _| { + let info = menu_shell.clone(); + let read = menu_shell.clone(); + let fetch = menu_shell.clone(); + let send = menu_shell.clone(); + let delete = menu_shell.clone(); + let title_for_delete = card_title.clone(); + let send_title = menu_title.clone(); + menu.item( + PopupMenuItem::new("Info").on_click(move |_, _, cx| { + info.downgrade().update_in(cx, |this, window, cx| this.show_book(id, window, cx)).ok(); + }), + ) + .when(can_read, |menu| { + menu.item(PopupMenuItem::new("Read").on_click(move |_, _, cx| { + read.update(cx, |this, cx| this.open_reader(id, cx)); + })) + }) + .item(PopupMenuItem::new("Edit metadata").on_click({ + let info = menu_shell.clone(); + move |_, _, cx| { + info.downgrade().update_in(cx, |this, window, cx| this.show_book(id, window, cx)).ok(); + } + })) + .item(PopupMenuItem::new("Fetch metadata").on_click(move |_, _, cx| { + fetch + .downgrade().update_in(cx, |this, window, cx| this.fetch_one(id, window, cx)) + .ok(); + })) + .item(PopupMenuItem::new("Send to device").on_click({ + let send_title = send_title.clone(); + move |_, _, cx| { + send.downgrade().update_in(cx, |this, window, cx| this.send_one(id, &send_title, window, cx)) + .ok(); + } + })) + .separator() + .item( + PopupMenuItem::new("Delete").on_click(move |_, window, cx| { + let delete = delete.clone(); + let title = title_for_delete.clone(); + window.open_alert_dialog(cx, move |alert, _, _| { + alert + .confirm() + .title("Delete book") + .description(format!("Delete \"{title}\" from the library?")) + .on_ok({ + let delete = delete.clone(); + move |_, _, cx| { + delete + .downgrade().update_in(cx, |this, window, cx| this.delete_one(id, window, cx)) + .ok(); + true + } + }) + }); + }), + ) + }) + .child(cover_box(cover, &title, progress, cx)) + .child( + div() + .mt(px(8.)) + .font_family(DISPLAY) + .text_size(px(15.)) + .line_height(px(19.)) + .child(title.clone()), + ) + .child( + div() + .text_size(px(13.)) + .text_color(cx.theme().muted_foreground) + .child(authors), + ) + .when(select_mode, |this| { + this.child(if is_selected { "Selected" } else { "" }) + }) +} + +fn cover_box( + cover: Option<&PathBuf>, + title: &str, + progress: Option, + cx: &gpui_kit::App, +) -> impl IntoElement { + let letter = title.chars().next().unwrap_or('?').to_string(); + div() + .relative() + .w(px(140.)) + .h(px(210.)) + .rounded(px(6.)) + .overflow_hidden() + .bg(cx.theme().muted) + .child(match cover { + Some(path) => img(path.clone()).size_full().object_fit(ObjectFit::Cover).into_any_element(), + None => div() + .size_full() + .flex() + .items_center() + .justify_center() + .font_family(DISPLAY) + .text_size(px(36.)) + .text_color(cx.theme().muted_foreground) + .child(letter) + .into_any_element(), + }) + .when_some(progress, |this, percent| { + this.child( + div() + .absolute() + .bottom(px(6.)) + .right(px(6.)) + .px(px(6.)) + .py(px(2.)) + .rounded(px(99.)) + .text_size(px(11.)) + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().border) + .child(format!("{}%", percent.round() as i64)), + ) + }) +} + +impl Shell { + fn fetch_one(&mut self, id: i64, window: &mut Window, cx: &mut Context) { + let state = self.state.clone(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { backend::enrich(&state, id).await.map_err(|e| e.to_string()) }, + |this, result, window, cx| { + this.busy = false; + match result { + Ok(result) => { + this.message = if result.updated_fields.is_empty() { + "No missing fields to fill.".into() + } else { + format!("Updated: {}", result.updated_fields.join(", ")) + }; + } + Err(error) => this.error = error, + } + this.reload_books(window, cx); + }, + ); + } + + fn send_one(&mut self, id: i64, title: &str, window: &mut Window, cx: &mut Context) { + let Some(device) = self.devices.iter().find(|device| device.supports_send).cloned() else { + self.error = "No Kindle connected. Connect over MTP / USB file transfer, then try again.".into(); + cx.notify(); + return; + }; + let state = self.state.clone(); + let title = title.to_string(); + self.busy = true; + self.spawn_task( + window, + cx, + async move { backend::send(&state, id, &device.id).await.map(|result| (result, device.product)).map_err(|e| e.to_string()) }, + move |this, result, _, cx| { + this.busy = false; + match result { + Ok((result, product)) => { + this.message = format!( + "Sent \"{title}\" ({}) to {product} as {}.", + result.format, result.remote_path + ); + } + Err(error) => this.error = error, + } + cx.notify(); + }, + ); + } + + fn delete_one(&mut self, id: i64, window: &mut Window, cx: &mut Context) { + let state = self.state.clone(); + self.spawn_io( + window, + cx, + move || backend::remove_book(&state, id).map_err(|e| e.to_string()), + |this, result, window, cx| { + if let Err(error) = result { + this.error = error; + } + this.reload_books(window, cx); + }, + ); + } +} + +#[allow(dead_code)] +fn _status_used(status: LibraryStatus) -> bool { + status.open +} diff --git a/app/src/ui/mod.rs b/app/src/ui/mod.rs new file mode 100644 index 0000000..1b08d20 --- /dev/null +++ b/app/src/ui/mod.rs @@ -0,0 +1,71 @@ +mod backend; +mod book; +mod devices; +mod document; +mod icon; +mod library; +mod reader; +mod settings; +mod shell; +mod theme; + +use gpui_kit::AppContext as _; +use gpui_kit::{ + App, TitlebarOptions, WindowBounds, WindowOptions, application, point, px, size, +}; +use std::future::pending; + +pub use shell::Shell; + +pub fn launch() { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("tokio runtime"); + let handle = runtime.handle().clone(); + std::thread::Builder::new() + .name("grimoire-tokio".into()) + .spawn(move || runtime.block_on(pending::<()>())) + .expect("tokio thread"); + + application() + .with_assets(gpui_kit::assets::Assets) + .run(move |cx| { + gpui_kit::init(cx); + cx.set_global(backend::TokioRt(handle)); + theme::apply_saved(cx); + icon::install(); + gpui_kit::open_window(main_window_options(cx), cx, |window, cx| { + cx.new(|cx| Shell::new(window, cx)) + }) + .expect("failed to open Grimoire"); + }); +} + +fn main_window_options(cx: &App) -> WindowOptions { + let mut options = WindowOptions::default(); + options.window_bounds = Some(WindowBounds::centered(size(px(1180.), px(780.)), cx)); + options.window_min_size = Some(size(px(880.), px(560.))); + options.titlebar = Some(TitlebarOptions { + title: Some("Grimoire".into()), + appears_transparent: true, + traffic_light_position: Some(point(px(14.), px(18.))), + }); + options.app_owns_titlebar_drag = true; + icon::apply(&mut options); + options +} + +pub(crate) fn reader_window_options(title: &str, cx: &App) -> WindowOptions { + let mut options = WindowOptions::default(); + options.window_bounds = Some(WindowBounds::centered(size(px(920.), px(760.)), cx)); + options.window_min_size = Some(size(px(640.), px(480.))); + options.titlebar = Some(TitlebarOptions { + title: Some(title.to_string().into()), + appears_transparent: true, + traffic_light_position: Some(point(px(14.), px(16.))), + }); + options.app_owns_titlebar_drag = true; + icon::apply(&mut options); + options +} diff --git a/app/src/ui/reader.rs b/app/src/ui/reader.rs new file mode 100644 index 0000000..bffc26e --- /dev/null +++ b/app/src/ui/reader.rs @@ -0,0 +1,522 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use gpui_kit::component::button::{Button, ButtonVariants as _}; +use gpui_kit::base::InteractiveElementExt as _; +use gpui_kit::component::{ActiveTheme as _, Disableable as _}; +use gpui_kit::prelude::{ + InteractiveElement as _, StatefulInteractiveElement as _, Styled as _, StyledImage as _, +}; +use gpui_kit::{ + Context, Image, ImageFormat, IntoElement, MouseButton, ObjectFit, ParentElement as _, Render, + ScrollHandle, Window, div, img, px, +}; + +use super::backend; +use super::document::{self, Chapter}; +use super::settings::reader_family; +use super::theme::DISPLAY; +use crate::reader::ReadingProgressUpdate; +use crate::state::AppState; + +pub struct ReaderWindow { + state: AppState, + book_id: i64, + title: String, + authors: String, + format: String, + path: PathBuf, + error: String, + loading: bool, + chapters: Vec, + chapter: usize, + comic_count: usize, + comic_index: usize, + comic_image: Option>, + scroll: ScrollHandle, + percent: f64, + titlebar_drag: bool, +} + +impl ReaderWindow { + pub fn new(state: AppState, book_id: i64, window: &mut Window, cx: &mut Context) -> Self { + let reader = Self { + state, + book_id, + title: "Reading".into(), + authors: String::new(), + format: String::new(), + path: PathBuf::new(), + error: String::new(), + loading: true, + chapters: Vec::new(), + chapter: 0, + comic_count: 0, + comic_index: 0, + comic_image: None, + scroll: ScrollHandle::new(), + percent: 0.0, + titlebar_drag: false, + }; + reader.load(window, cx); + reader + } + + fn runtime(&self, cx: &gpui_kit::App) -> tokio::runtime::Handle { + cx.global::().0.clone() + } + + fn load(&self, window: &mut Window, cx: &mut Context) { + let state = self.state.clone(); + let book_id = self.book_id; + let runtime = self.runtime(cx); + cx.spawn_in(window, async move |this, cx| { + let loaded = runtime + .spawn_blocking(move || { + let source = backend::reader_source(&state, book_id).map_err(|e| e.to_string())?; + let saved = backend::progress(&state, book_id).ok().flatten(); + Ok::<_, String>((source, saved)) + }) + .await + .unwrap_or_else(|error| Err(error.to_string())); + this.update_in(cx, |this, window, cx| { + this.loading = false; + match loaded { + Ok((source, saved)) => { + this.title = source.title.clone(); + this.authors = source.authors.join(", "); + this.format = source.format.clone(); + this.path = PathBuf::from(&source.path); + if let Some(saved) = saved { + this.percent = saved.percent; + if let Some(index) = saved.location.strip_prefix("chapter:") { + this.chapter = index.parse().unwrap_or(0); + } + if let Some(index) = saved.location.strip_prefix("page:") { + this.comic_index = index.parse().unwrap_or(0); + } + } + this.open_format(window, cx); + } + Err(error) => this.error = error, + } + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + fn open_format(&mut self, window: &mut Window, cx: &mut Context) { + let format = self.format.to_ascii_uppercase(); + if format == "PDF" { + return; + } + if format == "CBZ" || format == "CBR" { + self.load_comic_page(window, cx); + return; + } + let path = self.path.clone(); + let format = format.clone(); + let runtime = self.runtime(cx); + cx.spawn_in(window, async move |this, cx| { + let chapters = runtime + .spawn_blocking(move || document::load_chapters(&path, &format)) + .await + .unwrap_or_else(|error| Err(error.to_string())); + this.update(cx, |this, cx| { + match chapters { + Ok(chapters) => { + if this.chapter >= chapters.len() { + this.chapter = 0; + } + this.chapters = chapters; + } + Err(error) => this.error = error, + } + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + fn load_comic_page(&mut self, window: &mut Window, cx: &mut Context) { + let path = self.path.clone(); + let index = self.comic_index; + let runtime = self.runtime(cx); + cx.spawn_in(window, async move |this, cx| { + let page = runtime + .spawn_blocking(move || backend::comic_page(&path, index).map_err(|e| e.to_string())) + .await + .unwrap_or_else(|error| Err(error.to_string())); + this.update(cx, |this, cx| { + match page { + Ok((name, data_url, count)) => { + this.comic_count = count; + this.comic_image = image_from_data_url(&data_url); + let _ = name; + this.remember("page", this.comic_index, cx); + } + Err(error) => this.error = error, + } + cx.notify(); + }) + .ok(); + }) + .detach(); + } + + fn remember(&mut self, kind: &str, index: usize, cx: &mut Context) { + let total = if kind == "page" { + self.comic_count.max(1) + } else { + self.chapters.len().max(1) + }; + let percent = ((index as f64 + 1.0) / total as f64) * 100.0; + self.percent = percent; + let state = self.state.clone(); + let book_id = self.book_id; + let format = self.format.clone(); + let location = format!("{kind}:{index}"); + let runtime = self.runtime(cx); + cx.spawn(async move |_this, _cx| { + let _ = runtime + .spawn_blocking(move || { + backend::save_progress( + &state, + book_id, + ReadingProgressUpdate { + format, + location, + percent, + }, + ) + }) + .await; + }) + .detach(); + } + + fn note_scroll(&mut self, cx: &mut Context) { + let max = self.scroll.max_offset().y.as_f32(); + if max <= 1.0 || self.chapters.is_empty() { + return; + } + let offset = self.scroll.offset().y.as_f32().max(0.0); + let within = (offset / max).clamp(0.0, 1.0) as f64; + let total = self.chapters.len() as f64; + let percent = ((self.chapter as f64 + within) / total) * 100.0; + if (percent - self.percent).abs() < 1.0 { + return; + } + self.percent = percent; + let state = self.state.clone(); + let book_id = self.book_id; + let format = self.format.clone(); + let location = format!("chapter:{}", self.chapter); + let runtime = self.runtime(cx); + cx.spawn(async move |_this, _cx| { + let _ = runtime + .spawn_blocking(move || { + backend::save_progress( + &state, + book_id, + ReadingProgressUpdate { + format, + location, + percent, + }, + ) + }) + .await; + }) + .detach(); + } +} + +impl Render for ReaderWindow { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let font = backend::config() + .map(|config| config.reader_font) + .unwrap_or_else(|_| "serif".into()); + let family = reader_family(&font); + let paper = cx.theme().popover; + let ink = cx.theme().foreground; + + div() + .size_full() + .flex() + .flex_col() + .bg(cx.theme().background) + .text_color(ink) + .child(self.chrome(window, cx)) + .child(self.content(family, paper, window, cx)) + } +} + +impl ReaderWindow { + fn chrome(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .h(px(48.)) + .px(px(16.)) + .flex() + .items_center() + .gap(px(10.)) + .id("reader-titlebar") + .border_b_1() + .border_color(cx.theme().border) + .on_double_click(cx.listener(|this, _, window, _| { + this.titlebar_drag = false; + window.zoom_window(); + })) + .on_mouse_down( + MouseButton::Left, + cx.listener(|this, _, _, _| this.titlebar_drag = true), + ) + .on_mouse_up( + MouseButton::Left, + cx.listener(|this, _, _, _| this.titlebar_drag = false), + ) + .on_mouse_down_out(cx.listener(|this, _, _, _| this.titlebar_drag = false)) + .on_mouse_move(cx.listener(|this, _, window, _| { + if this.titlebar_drag { + this.titlebar_drag = false; + window.start_window_move(); + } + })) + .child( + div() + .pl(px(72.)) + .font_family(DISPLAY) + .text_size(px(16.)) + .child(self.title.clone()), + ) + .child( + div() + .text_size(px(12.)) + .text_color(cx.theme().muted_foreground) + .child(format!("{} · {:.0}%", self.format, self.percent)), + ) + } + + fn content( + &mut self, + family: &str, + paper: gpui_kit::Hsla, + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + if self.loading { + return div().p(px(24.)).child("Opening book…").into_any_element(); + } + if !self.error.is_empty() { + return div() + .p(px(24.)) + .text_color(cx.theme().danger) + .child(self.error.clone()) + .into_any_element(); + } + let format = self.format.to_ascii_uppercase(); + if format == "PDF" { + let path = self.path.clone(); + return div() + .p(px(28.)) + .flex() + .flex_col() + .gap(px(12.)) + .child(div().font_family(DISPLAY).text_size(px(24.)).child(self.title.clone())) + .child("PDF opens in your system viewer. EPUB, TXT, and comics stay inside Grimoire.") + .child( + Button::new("open-pdf") + .primary() + .label("Open PDF") + .on_click(move |_, _, _| open_path(&path)), + ) + .into_any_element(); + } + if format == "CBZ" || format == "CBR" { + return self.comic_view(cx).into_any_element(); + } + self.text_view(family, paper, window, cx).into_any_element() + } + + fn text_view( + &mut self, + family: &str, + paper: gpui_kit::Hsla, + _window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let chapter = self.chapters.get(self.chapter).cloned(); + let count = self.chapters.len(); + let index = self.chapter; + div() + .flex_1() + .min_h(px(0.)) + .flex() + .flex_col() + .child( + div() + .flex() + .items_center() + .justify_between() + .px(px(20.)) + .py(px(8.)) + .child( + Button::new("prev-chapter") + .ghost() + .label("Previous") + .disabled(index == 0) + .on_click(cx.listener(|this, _, _, cx| { + if this.chapter > 0 { + this.chapter -= 1; + this.scroll.set_offset(gpui_kit::point(px(0.), px(0.))); + this.remember("chapter", this.chapter, cx); + cx.notify(); + } + })), + ) + .child( + div().text_color(cx.theme().muted_foreground).child(format!( + "{} / {}", + index + 1, + count.max(1) + )), + ) + .child( + Button::new("next-chapter") + .ghost() + .label("Next") + .disabled(count == 0 || index + 1 >= count) + .on_click(cx.listener(|this, _, _, cx| { + if this.chapter + 1 < this.chapters.len() { + this.chapter += 1; + this.scroll.set_offset(gpui_kit::point(px(0.), px(0.))); + this.remember("chapter", this.chapter, cx); + cx.notify(); + } + })), + ), + ) + .child( + div() + .id("reader-scroll") + .flex_1() + .overflow_y_scroll() + .track_scroll(&self.scroll) + .on_scroll_wheel(cx.listener(|this, _, _, cx| this.note_scroll(cx))) + .px(px(28.)) + .py(px(18.)) + .child( + div() + .max_w(px(680.)) + .mx_auto() + .p(px(22.)) + .rounded(px(10.)) + .bg(paper) + .font_family(family) + .text_size(px(18.)) + .line_height(px(30.)) + .child(match chapter { + Some(Chapter { title, body }) => div() + .flex() + .flex_col() + .gap(px(14.)) + .child( + div() + .font_family(DISPLAY) + .text_size(px(26.)) + .child(title), + ) + .child(body) + .into_any_element(), + None => div().child("No chapter text.").into_any_element(), + }), + ), + ) + } + + fn comic_view(&mut self, cx: &mut Context) -> impl IntoElement { + let image = self.comic_image.clone(); + let index = self.comic_index; + let count = self.comic_count; + div() + .flex_1() + .min_h(px(0.)) + .flex() + .flex_col() + .child( + div() + .flex() + .items_center() + .justify_center() + .gap(px(12.)) + .py(px(8.)) + .child( + Button::new("prev-page") + .ghost() + .label("Previous") + .disabled(index == 0) + .on_click(cx.listener(|this, _, window, cx| { + if this.comic_index > 0 { + this.comic_index -= 1; + this.load_comic_page(window, cx); + } + })), + ) + .child(format!("{} / {}", index + 1, count.max(1))) + .child( + Button::new("next-page") + .ghost() + .label("Next") + .disabled(count == 0 || index + 1 >= count) + .on_click(cx.listener(|this, _, window, cx| { + if this.comic_index + 1 < this.comic_count { + this.comic_index += 1; + this.load_comic_page(window, cx); + } + })), + ), + ) + .child( + div() + .id("comic-scroll") + .flex_1() + .overflow_y_scroll() + .flex() + .justify_center() + .p(px(12.)) + .child(match image { + Some(image) => img(image).max_w(px(900.)).object_fit(ObjectFit::Contain).into_any_element(), + None => div().child("Loading page…").into_any_element(), + }), + ) + } +} + +fn image_from_data_url(data_url: &str) -> Option> { + let encoded = data_url.split_once("base64,")?.1; + let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, encoded).ok()?; + let format = image::guess_format(&bytes).ok()?; + let gpui_format = match format { + image::ImageFormat::Png => ImageFormat::Png, + image::ImageFormat::Jpeg => ImageFormat::Jpeg, + image::ImageFormat::WebP => ImageFormat::Webp, + image::ImageFormat::Gif => ImageFormat::Gif, + image::ImageFormat::Bmp => ImageFormat::Bmp, + _ => return None, + }; + Some(Arc::new(Image::from_bytes(gpui_format, bytes))) +} + +fn open_path(path: &std::path::Path) { + #[cfg(target_os = "macos")] + let _ = std::process::Command::new("open").arg(path).spawn(); + #[cfg(target_os = "windows")] + let _ = std::process::Command::new("cmd") + .args(["/C", "start", "", &path.display().to_string()]) + .spawn(); + #[cfg(all(unix, not(target_os = "macos")))] + let _ = std::process::Command::new("xdg-open").arg(path).spawn(); +} diff --git a/app/src/ui/settings.rs b/app/src/ui/settings.rs new file mode 100644 index 0000000..040e9d3 --- /dev/null +++ b/app/src/ui/settings.rs @@ -0,0 +1,221 @@ +use gpui_kit::component::button::{Button, ButtonVariants as _}; +use gpui_kit::component::{ActiveTheme as _, Disableable as _, WindowExt as _}; +use gpui_kit::prelude::{ + FluentBuilder as _, InteractiveElement as _, StatefulInteractiveElement as _, Styled as _, +}; +use gpui_kit::{Context, FontWeight, IntoElement, ParentElement as _, Window, div, px}; + +use super::backend; +use super::shell::Shell; +use super::theme::DISPLAY; + +const FONTS: &[(&str, &str, &str)] = &[ + ( + "publisher", + "Publisher default", + "Keep the ebook's own fonts when the file supplies them.", + ), + ( + "serif", + "Serif", + "A reading serif, Iowan Old Style or Georgia.", + ), + ( + "sans", + "Sans", + "A clean sans for long stretches of text.", + ), + ( + "opendyslexic", + "OpenDyslexic", + "Used when OpenDyslexic is installed on this computer.", + ), +]; + +impl Shell { + pub fn settings_page(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let path = self + .library_path + .clone() + .unwrap_or_else(|| "No library path saved yet.".into()); + let open = if self.library_open { + "Open and ready" + } else if self.library_path.is_some() { + "Saved, but not open right now" + } else { + "" + }; + let font = self.reader_font.clone(); + + div() + .id("settings-page") + .size_full() + .overflow_y_scroll() + .p(px(24.)) + .flex() + .flex_col() + .gap(px(16.)) + .child(div().font_family(DISPLAY).text_size(px(28.)).child("Settings")) + .child( + div() + .text_color(cx.theme().muted_foreground) + .child("Choose a library folder to open automatically when Grimoire starts."), + ) + .when(!self.message.is_empty(), |this| { + this.child(div().text_color(cx.theme().primary).child(self.message.clone())) + }) + .when(!self.error.is_empty(), |this| { + this.child(div().text_color(cx.theme().danger).child(self.error.clone())) + }) + .child(settings_block( + "Library location", + "Grimoire stores metadata.db and book files in this folder. The path is saved in your OS config directory.", + cx, + ).child(div().mt(px(8.)).child(path)).when(!open.is_empty(), |this| { + this.child(div().text_color(cx.theme().muted_foreground).child(open)) + }).child( + div().mt(px(10.)).flex().gap(px(8.)).child( + Button::new("settings-choose") + .primary() + .label(if self.library_path.is_some() { + "Change library folder" + } else { + "Choose library folder" + }) + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.pick_library(window, cx))), + ).when(self.library_path.is_some(), |this| { + this.child( + Button::new("settings-clear") + .outline() + .label("Clear saved path") + .disabled(self.busy) + .on_click(cx.listener(|this, _, window, cx| this.confirm_clear(window, cx))), + ) + }), + )) + .child({ + let mut block = settings_block( + "Reader font", + "Applies to EPUB and TXT reading.", + cx, + ); + for (id, label, description) in FONTS { + let selected = font == *id; + let font_id = (*id).to_string(); + block = block.child( + div() + .mt(px(8.)) + .child( + Button::new(format!("font-{id}")) + .when(selected, |button| button.primary()) + .when(!selected, |button| button.outline()) + .label(*label) + .on_click(cx.listener(move |this, _, _, cx| { + this.reader_font = font_id.clone(); + let _ = backend::save_reader_font(&this.reader_font); + this.message = format!("Reader font set to {label}."); + cx.notify(); + })), + ) + .child( + div() + .text_size(px(13.)) + .text_color(cx.theme().muted_foreground) + .child(*description), + ), + ); + } + block.child( + div() + .mt(px(10.)) + .font_family(reader_family(&font)) + .child("The quick brown fox jumps over the lazy dog."), + ) + }) + } + + fn confirm_clear(&mut self, window: &mut Window, cx: &mut Context) { + let shell = cx.entity().clone(); + window.open_alert_dialog(cx, move |alert, _, _| { + alert + .confirm() + .title("Clear library path") + .description("Grimoire will not open a library until you choose one again.") + .on_ok({ + let shell = shell.clone(); + move |_, _, cx| { + shell + .downgrade().update_in(cx, |this, _, cx| { + match backend::clear_library(&this.state) { + Ok(status) => { + this.apply_status(status); + this.books.clear(); + this.message = "Saved library path cleared.".into(); + } + Err(error) => this.error = error.to_string(), + } + cx.notify(); + }) + .ok(); + true + } + }) + }); + } +} + +fn settings_block(title: &str, body: &str, cx: &gpui_kit::App) -> gpui_kit::Div { + div() + .max_w(px(640.)) + .p(px(16.)) + .rounded(px(10.)) + .bg(cx.theme().popover) + .border_1() + .border_color(cx.theme().border) + .child( + div() + .font_family(DISPLAY) + .text_size(px(18.)) + .font_weight(FontWeight::SEMIBOLD) + .child(title.to_string()), + ) + .child( + div() + .mt(px(6.)) + .text_color(cx.theme().muted_foreground) + .child(body.to_string()), + ) +} + +pub fn reader_family(id: &str) -> &'static str { + match id { + "sans" => ".SystemUIFont", + "opendyslexic" => "OpenDyslexic", + _ => DISPLAY, + } +} + +#[cfg(test)] +mod tests { + use super::{reader_family, DISPLAY, FONTS}; + + #[test] + fn settings_offers_the_reader_font_choices() { + let ids: Vec<_> = FONTS.iter().map(|font| font.0).collect(); + assert_eq!(ids, ["publisher", "serif", "sans", "opendyslexic"]); + } + + #[test] + fn reader_family_maps_each_choice() { + assert_eq!(reader_family("publisher"), DISPLAY); + assert_eq!(reader_family("serif"), DISPLAY); + assert_eq!(reader_family("sans"), ".SystemUIFont"); + assert_eq!(reader_family("opendyslexic"), "OpenDyslexic"); + } + + #[test] + fn unknown_reader_font_uses_the_reading_serif() { + assert_eq!(reader_family("comic-sans"), DISPLAY); + } +} diff --git a/app/src/ui/shell.rs b/app/src/ui/shell.rs new file mode 100644 index 0000000..ecec8da --- /dev/null +++ b/app/src/ui/shell.rs @@ -0,0 +1,648 @@ +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; +use std::time::Duration; + +use gpui_kit::component::button::{Button, ButtonVariants as _}; +use gpui_kit::component::input::{InputEvent, InputState, TextareaState}; +use gpui_kit::base::InteractiveElementExt as _; +use gpui_kit::component::menu::{DropdownMenu as _, PopupMenuItem}; +use gpui_kit::component::{ActiveTheme as _, IconName, Selectable as _}; +use gpui_kit::prelude::FluentBuilder as _; +use gpui_kit::AppContext as _; +use gpui_kit::{ + Anchor, App, Context, Entity, ExternalPaths, FontWeight, Image, ImageFormat, + InteractiveElement as _, IntoElement, MouseButton, ParentElement as _, Render, Styled as _, + Subscription, Window, div, img, px, +}; + +use super::backend::{self, LibraryStatus, TokioRt}; +use super::theme::{self, DISPLAY}; +use crate::kindle::{DeviceLibrary, EbookReaderDevice, SyncRecord}; +use crate::library::{BookDetail, BookSummary}; +use crate::state::AppState; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Route { + Library, + Devices, + Settings, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ReadingStatus { + Reading, + ToRead, + Read, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum FormatKind { + Novel, + Comic, +} + +pub struct Shell { + pub state: AppState, + pub route: Route, + pub book_id: Option, + pub device_id: Option, + pub theme_pref: String, + pub reader_font: String, + pub devices: Vec, + pub notice: Option, + pub booting: bool, + pub boot_error: String, + pub library_open: bool, + pub library_path: Option, + pub kindle_convert: bool, + pub books: Vec, + pub covers: HashMap, + pub search: Entity, + pub search_gen: u64, + pub loading: bool, + pub busy: bool, + pub error: String, + pub message: String, + pub filters_open: bool, + pub select_mode: bool, + pub selected: HashSet, + pub filter_status: HashSet, + pub filter_genres: HashSet, + pub filter_formats: HashSet, + pub filter_authors: HashSet, + pub bulk_open: bool, + pub bulk_series: bool, + pub bulk_index: bool, + pub bulk_notes: bool, + pub bulk_tags: Entity, + pub bulk_series_input: Entity, + pub bulk_index_input: Entity, + pub bulk_notes_input: Entity, + pub book: Option, + pub book_cover: Option, + pub convert_targets: Vec, + pub title_input: Entity, + pub authors_input: Entity, + pub tags_input: Entity, + pub series_input: Entity, + pub series_index_input: Entity, + pub isbn_input: Entity, + pub comment_input: Entity, + pub sync_history: Vec, + pub devices_loading: bool, + pub device_library: Option, + pub device_query: Entity, + pub device_selected: HashSet, + pub device_select_mode: bool, + titlebar_drag: bool, + _subs: Vec, +} + +impl Shell { + pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let search = cx.new(|cx| { + InputState::new(window, cx).placeholder("Search title, author, or tag") + }); + let bulk_tags = cx.new(|cx| InputState::new(window, cx).placeholder("Tags to add")); + let bulk_series_input = + cx.new(|cx| InputState::new(window, cx).placeholder("Series name (blank clears)")); + let bulk_index_input = cx.new(|cx| InputState::new(window, cx).placeholder("Index")); + let bulk_notes_input = cx.new(|cx| TextareaState::new(window, cx).placeholder("Notes")); + let title_input = cx.new(|cx| InputState::new(window, cx)); + let authors_input = cx.new(|cx| InputState::new(window, cx).placeholder("Author, Author")); + let tags_input = cx.new(|cx| InputState::new(window, cx).placeholder("tag, tag")); + let series_input = cx.new(|cx| InputState::new(window, cx)); + let series_index_input = cx.new(|cx| InputState::new(window, cx)); + let isbn_input = cx.new(|cx| InputState::new(window, cx)); + let comment_input = cx.new(|cx| TextareaState::new(window, cx).placeholder("Notes")); + let device_query = cx.new(|cx| InputState::new(window, cx).placeholder("Filter device books")); + + let search_sub = cx.subscribe_in(&search, window, |this, input, event, window, cx| { + if matches!(event, InputEvent::Change) { + let query = input.read(cx).value().to_string(); + this.queue_search(query, window, cx); + } + }); + + let config = backend::config().unwrap_or_default(); + let theme_pref = config.theme.clone(); + let reader_font = config.reader_font.clone(); + + let shell = Self { + state: AppState::new(), + route: Route::Library, + book_id: None, + device_id: None, + theme_pref, + reader_font, + devices: Vec::new(), + notice: None, + booting: true, + boot_error: String::new(), + library_open: false, + library_path: None, + kindle_convert: true, + books: Vec::new(), + covers: HashMap::new(), + search, + search_gen: 0, + loading: true, + busy: false, + error: String::new(), + message: String::new(), + filters_open: true, + select_mode: false, + selected: HashSet::new(), + filter_status: HashSet::new(), + filter_genres: HashSet::new(), + filter_formats: HashSet::new(), + filter_authors: HashSet::new(), + bulk_open: false, + bulk_series: false, + bulk_index: false, + bulk_notes: false, + bulk_tags, + bulk_series_input, + bulk_index_input, + bulk_notes_input, + book: None, + book_cover: None, + convert_targets: Vec::new(), + title_input, + authors_input, + tags_input, + series_input, + series_index_input, + isbn_input, + comment_input, + sync_history: Vec::new(), + devices_loading: false, + device_library: None, + device_query, + device_selected: HashSet::new(), + device_select_mode: false, + titlebar_drag: false, + _subs: vec![search_sub], + }; + shell.boot(window, cx); + shell.watch_devices(window, cx); + shell + } + + pub fn runtime(&self, cx: &App) -> tokio::runtime::Handle { + cx.global::().0.clone() + } + + pub fn spawn_io( + &self, + window: &mut Window, + cx: &mut Context, + work: impl FnOnce() -> Result + Send + 'static, + done: Done, + ) where + T: Send + 'static, + Done: FnOnce(&mut Self, Result, &mut Window, &mut Context) + 'static, + { + let runtime = self.runtime(cx); + cx.spawn_in(window, async move |this, cx| { + let result = match runtime.spawn_blocking(work).await { + Ok(result) => result, + Err(error) => Err(error.to_string()), + }; + this.update_in(cx, |this, window, cx| done(this, result, window, cx)) + .ok(); + }) + .detach(); + } + + pub fn spawn_task( + &self, + window: &mut Window, + cx: &mut Context, + work: Fut, + done: Done, + ) where + T: Send + 'static, + Fut: std::future::Future> + Send + 'static, + Done: FnOnce(&mut Self, Result, &mut Window, &mut Context) + 'static, + { + let runtime = self.runtime(cx); + cx.spawn_in(window, async move |this, cx| { + let result = match runtime.spawn(work).await { + Ok(result) => result, + Err(error) => Err(error.to_string()), + }; + this.update_in(cx, |this, window, cx| done(this, result, window, cx)) + .ok(); + }) + .detach(); + } + + fn boot(&self, window: &mut Window, cx: &mut Context) { + let state = self.state.clone(); + self.spawn_io( + window, + cx, + move || backend::open_saved(&state).map_err(|e| e.to_string()), + |this, result, window, cx| { + this.booting = false; + match result { + Ok(status) => { + this.apply_status(status); + if this.library_open { + this.reload_books(window, cx); + } else { + this.loading = false; + } + } + Err(error) => { + this.boot_error = error; + this.loading = false; + } + } + cx.notify(); + }, + ); + } + + fn watch_devices(&self, window: &mut Window, cx: &mut Context) { + let runtime = self.runtime(cx); + cx.spawn_in(window, async move |this, cx| { + let mut seen: Vec = Vec::new(); + loop { + let next = runtime.spawn(async { backend::readers().await }).await; + let _ = this.update(cx, |this, cx| { + if let Ok(Ok(devices)) = next { + let ids: Vec = devices.iter().map(|d| d.id.clone()).collect(); + if !seen.is_empty() { + for device in &devices { + if !seen.iter().any(|id| id == &device.id) { + this.notice = Some(format!("{} connected.", device.product)); + } + } + for id in &seen { + if !ids.iter().any(|next| next == id) { + let name = this + .devices + .iter() + .find(|device| &device.id == id) + .map(|device| device.product.clone()) + .unwrap_or_else(|| "eReader".into()); + this.notice = Some(format!("{name} disconnected.")); + } + } + } + seen = ids; + this.devices = devices; + cx.notify(); + } + }); + runtime + .spawn(async { tokio::time::sleep(Duration::from_secs(4)).await }) + .await + .ok(); + } + }) + .detach(); + } + + pub fn apply_status(&mut self, status: LibraryStatus) { + self.library_open = status.open; + self.library_path = status.path; + self.kindle_convert = status.kindle_convert_available; + } + + pub fn go(&mut self, route: Route, cx: &mut Context) { + self.route = route; + if !matches!(route, Route::Library) { + self.book_id = None; + } + cx.notify(); + } + + pub fn queue_search(&mut self, _query: String, window: &mut Window, cx: &mut Context) { + self.search_gen = self.search_gen.wrapping_add(1); + let generation = self.search_gen; + let runtime = self.runtime(cx); + cx.spawn_in(window, async move |this, cx| { + runtime + .spawn(async { tokio::time::sleep(Duration::from_millis(220)).await }) + .await + .ok(); + this.update_in(cx, |this, window, cx| { + if this.search_gen == generation { + this.reload_books(window, cx); + } + }) + .ok(); + }) + .detach(); + } + + pub fn set_theme(&mut self, pref: &str, cx: &mut Context) { + self.theme_pref = pref.to_string(); + theme::persist_and_apply(pref, cx); + cx.notify(); + } +} + +impl Render for Shell { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let paper = cx.theme().background; + let ink = cx.theme().foreground; + let line = cx.theme().border; + let panel = cx.theme().popover; + + div() + .id("grimoire-root") + .size_full() + .flex() + .flex_col() + .bg(paper) + .text_color(ink) + .font_family(super::theme::UI_FONT) + .text_size(px(15.)) + .on_drop(cx.listener(|this, paths: &ExternalPaths, window, cx| { + let files = paths.paths().to_vec(); + this.import_paths(files, window, cx); + })) + .child(self.topbar(window, cx)) + .child( + div() + .flex_1() + .min_h(px(0.)) + .overflow_hidden() + .child(self.body(window, cx)), + ) + .when_some(self.notice.clone(), |this, notice| { + this.child(notice_toast(notice, panel, line, cx)) + }) + } +} + +impl Shell { + fn topbar(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let accent = cx.theme().primary; + let muted = cx.theme().muted_foreground; + let line = cx.theme().border; + let bar = cx.theme().title_bar; + #[cfg(target_os = "macos")] + let leading = px(78.); + #[cfg(not(target_os = "macos"))] + let leading = px(16.); + + div() + .h(px(52.)) + .flex() + .items_center() + .px(px(12.)) + .gap(px(8.)) + .bg(bar) + .border_b_1() + .border_color(line) + .child( + div() + .id("titlebar-drag") + .h_full() + .flex() + .items_center() + .gap(px(8.)) + .pl(leading) + .on_double_click(cx.listener(|this, _, window, _| { + this.titlebar_drag = false; + window.zoom_window(); + })) + .on_mouse_down( + MouseButton::Left, + cx.listener(|this, _, _, _| this.titlebar_drag = true), + ) + .on_mouse_up( + MouseButton::Left, + cx.listener(|this, _, _, _| this.titlebar_drag = false), + ) + .on_mouse_down_out(cx.listener(|this, _, _, _| this.titlebar_drag = false)) + .on_mouse_move(cx.listener(|this, _, window, _| { + if this.titlebar_drag { + this.titlebar_drag = false; + window.start_window_move(); + } + })) + .child( + img(std::sync::Arc::new(Image::from_bytes( + ImageFormat::Svg, + include_bytes!("../../../static/grimoire_icon.svg").to_vec(), + ))) + .size(px(28.)), + ) + .child( + div() + .flex() + .items_end() + .font_family(DISPLAY) + .text_size(px(24.)) + .font_weight(FontWeight::SEMIBOLD) + .child("Grimoire") + .child(div().text_color(accent).child(".")), + ), + ) + .child( + div() + .id("titlebar-spacer") + .flex_1() + .h_full() + .on_double_click(cx.listener(|this, _, window, _| { + this.titlebar_drag = false; + window.zoom_window(); + })) + .on_mouse_down( + MouseButton::Left, + cx.listener(|this, _, _, _| this.titlebar_drag = true), + ) + .on_mouse_up( + MouseButton::Left, + cx.listener(|this, _, _, _| this.titlebar_drag = false), + ) + .on_mouse_down_out(cx.listener(|this, _, _, _| this.titlebar_drag = false)) + .on_mouse_move(cx.listener(|this, _, window, _| { + if this.titlebar_drag { + this.titlebar_drag = false; + window.start_window_move(); + } + })), + ) + .child(self.nav_button("nav-library", IconName::BookOpen, "Library", self.route == Route::Library && self.book_id.is_none(), Route::Library, cx)) + .child(self.nav_button("nav-devices", IconName::HardDrive, "Devices", self.route == Route::Devices, Route::Devices, cx)) + .child(self.nav_button("nav-settings", IconName::Settings, "Settings", self.route == Route::Settings, Route::Settings, cx)) + .child(self.theme_menu(cx)) + .child(div().w(px(4.)).text_color(muted)) + } + + fn theme_menu(&mut self, cx: &mut Context) -> impl IntoElement { + let current = self.theme_pref.clone(); + let shell = cx.entity(); + Button::new("theme") + .ghost() + .compact() + .icon(if cx.theme().is_dark() { + IconName::Sun + } else { + IconName::Moon + }) + .tooltip(format!("Theme: {}", theme::label(¤t))) + .dropdown_menu_with_anchor(Anchor::TopRight, move |menu, _, _| { + menu.item(theme_choice("Dark", "dark", ¤t, shell.clone())) + .item(theme_choice("Light", "light", ¤t, shell.clone())) + .item(theme_choice( + "Follow System", + "system", + ¤t, + shell.clone(), + )) + }) + } + + fn nav_button( + &self, + id: &'static str, + icon: IconName, + tip: &'static str, + active: bool, + route: Route, + cx: &mut Context, + ) -> impl IntoElement { + let count = if id == "nav-devices" { + self.devices.len() + } else { + 0 + }; + div().relative().child( + Button::new(id) + .ghost() + .compact() + .icon(icon) + .selected(active) + .tooltip(tip) + .on_click(cx.listener(move |this, _, window, cx| { + this.book_id = None; + this.device_id = None; + this.route = route; + if route == Route::Devices { + this.refresh_devices(window, cx); + } + cx.notify(); + })), + ).when(count > 0, |this| { + this.child( + div() + .absolute() + .top(px(0.)) + .right(px(0.)) + .px(px(4.)) + .rounded(px(99.)) + .text_size(px(10.)) + .bg(cx.theme().primary) + .text_color(cx.theme().primary_foreground) + .child(count.to_string()), + ) + }) + } + + fn body(&mut self, window: &mut Window, cx: &mut Context) -> gpui_kit::AnyElement { + if self.booting { + return muted_page("Opening library…", cx).into_any_element(); + } + if !self.boot_error.is_empty() && !self.library_open && self.route == Route::Library { + return muted_page(&self.boot_error, cx).into_any_element(); + } + if self.book_id.is_some() { + return self.book_page(window, cx).into_any_element(); + } + match self.route { + Route::Library => self.library_page(window, cx).into_any_element(), + Route::Devices => { + if self.device_id.is_some() { + self.device_library_page(window, cx).into_any_element() + } else { + self.devices_page(window, cx).into_any_element() + } + } + Route::Settings => self.settings_page(window, cx).into_any_element(), + } + } +} + +fn notice_toast( + notice: String, + panel: gpui_kit::Hsla, + line: gpui_kit::Hsla, + cx: &mut Context, +) -> impl IntoElement { + div() + .absolute() + .bottom(px(18.)) + .right(px(18.)) + .max_w(px(360.)) + .flex() + .items_center() + .gap(px(10.)) + .px(px(14.)) + .py(px(10.)) + .rounded(px(10.)) + .bg(panel) + .border_1() + .border_color(line) + .child(div().flex_1().child(notice)) + .child( + Button::new("dismiss-notice") + .ghost() + .compact() + .icon(IconName::Close) + .tooltip("Dismiss") + .on_click(cx.listener(|this, _, _, cx| { + this.notice = None; + cx.notify(); + })), + ) +} + +pub fn muted_page(text: &str, cx: &App) -> impl IntoElement { + div() + .p(px(28.)) + .text_color(cx.theme().muted_foreground) + .child(text.to_string()) +} + +pub fn section_title(text: &str, count: Option) -> impl IntoElement { + div() + .flex() + .items_end() + .gap(px(8.)) + .child( + div() + .font_family(DISPLAY) + .text_size(px(20.)) + .font_weight(FontWeight::SEMIBOLD) + .child(text.to_string()), + ) + .when_some(count, |this, count| { + this.child( + div() + .text_size(px(13.)) + .font_weight(FontWeight::SEMIBOLD) + .child(count.to_string()), + ) + }) +} + +fn theme_choice( + label: &'static str, + pref: &'static str, + current: &str, + shell: Entity, +) -> PopupMenuItem { + PopupMenuItem::new(label) + .checked(current == pref) + .on_click(move |_, _, cx| { + shell.update(cx, |this, cx| this.set_theme(pref, cx)); + }) +} diff --git a/app/src/ui/theme.rs b/app/src/ui/theme.rs new file mode 100644 index 0000000..f5a2d41 --- /dev/null +++ b/app/src/ui/theme.rs @@ -0,0 +1,214 @@ +use gpui_kit::component::theme::{Theme, ThemeColor, ThemeMode}; +use gpui_kit::{App, Hsla, WindowAppearance, px, rgb}; + +use crate::config::{load_config, set_theme_preference}; + +pub const DISPLAY: &str = "Iowan Old Style"; +pub const UI_FONT: &str = ".SystemUIFont"; + +pub fn apply_saved(cx: &mut App) { + let pref = load_config() + .map(|config| config.theme) + .unwrap_or_else(|_| "dark".into()); + apply(&pref, cx); +} + +pub fn label(pref: &str) -> &'static str { + match pref { + "light" => "Light", + "system" => "Follow System", + _ => "Dark", + } +} + +pub fn persist_and_apply(pref: &str, cx: &mut App) { + let _ = set_theme_preference(pref); + apply(pref, cx); +} + +pub fn apply(pref: &str, cx: &mut App) { + let dark = match pref { + "light" => false, + "system" => matches!( + cx.window_appearance(), + WindowAppearance::Dark | WindowAppearance::VibrantDark + ), + _ => true, + }; + if pref == "system" { + cx.set_window_appearance(None); + } else if dark { + cx.set_window_appearance(Some(WindowAppearance::Dark)); + } else { + cx.set_window_appearance(Some(WindowAppearance::Light)); + } + Theme::change( + if dark { ThemeMode::Dark } else { ThemeMode::Light }, + None, + cx, + ); + Theme::update(cx, |theme| { + theme.colors = if dark { dark_palette() } else { light_palette() }; + theme.font_family = UI_FONT.into(); + theme.font_size = px(15.); + theme.radius = px(10.); + theme.radius_lg = px(14.); + }); +} + +fn paint(hex: u32) -> Hsla { + rgb(hex).into() +} + +fn paint_a(hex: u32, alpha: f32) -> Hsla { + let mut color: Hsla = rgb(hex).into(); + color.a = alpha; + color +} + +fn light_palette() -> ThemeColor { + let mut color = *ThemeColor::light(); + let ink = paint(0x1c1814); + let muted = paint(0x5c534a); + let paper = paint(0xf3ece0); + let paper_deep = paint(0xe6dccb); + let panel = paint(0xfaf6ee); + let accent = paint(0x2f5d50); + let accent_hover = paint(0x244a40); + let danger = paint(0x8b3a2f); + let line = paint_a(0x1c1814, 0.12); + let control = paint(0xfffdf8); + let btn = paint(0xf8f5ef); + fill( + &mut color, + ink, + muted, + paper, + paper_deep, + panel, + accent, + accent_hover, + danger, + line, + control, + btn, + paint(0xe4efe9), + ); + color +} + +fn dark_palette() -> ThemeColor { + let mut color = *ThemeColor::dark(); + let ink = paint(0xefe6d8); + let muted = paint(0xb0a493); + let paper = paint(0x1a1714); + let paper_deep = paint(0x12100e); + let panel = paint(0x24201c); + let accent = paint(0x7fb5a4); + let accent_hover = paint(0x9ac9ba); + let danger = paint(0xd88476); + let line = paint_a(0xefe6d8, 0.12); + let control = paint(0x2c2823); + let btn = paint(0x1a1714); + fill( + &mut color, + ink, + muted, + paper, + paper_deep, + panel, + accent, + accent_hover, + danger, + line, + control, + btn, + paint(0x2c3834), + ); + color +} + +fn fill( + color: &mut ThemeColor, + ink: Hsla, + muted: Hsla, + paper: Hsla, + paper_deep: Hsla, + panel: Hsla, + accent: Hsla, + accent_hover: Hsla, + danger: Hsla, + line: Hsla, + control: Hsla, + btn: Hsla, + accent_wash: Hsla, +) { + color.background = paper; + color.foreground = ink; + color.muted = paper_deep; + color.muted_foreground = muted; + color.border = line; + color.popover = panel; + color.popover_foreground = ink; + color.group_box = panel; + color.group_box_foreground = ink; + color.primary = accent; + color.primary_hover = accent_hover; + color.primary_active = accent_hover; + color.primary_foreground = btn; + color.accent = accent; + color.accent_foreground = btn; + color.ring = accent; + color.danger = danger; + color.danger_hover = danger; + color.danger_active = danger; + color.danger_foreground = paint(0xfff8f5); + color.input = control; + color.caret = accent; + color.title_bar = panel; + color.title_bar_border = line; + color.sidebar = paper_deep; + color.sidebar_foreground = ink; + color.sidebar_border = line; + color.sidebar_accent = accent_wash; + color.sidebar_accent_foreground = accent; + color.sidebar_primary = accent; + color.sidebar_primary_foreground = btn; + color.list = panel; + color.list_hover = accent_wash; + color.list_active = accent_wash; + color.list_active_border = accent; + color.selection = accent_wash; + color.link = accent; + color.link_hover = accent_hover; + color.link_active = accent_hover; + color.button_primary = accent; + color.button_primary_hover = accent_hover; + color.button_primary_active = accent_hover; + color.button_primary_foreground = btn; + color.button_danger = danger; + color.button_danger_hover = danger; + color.button_danger_active = danger; + color.button_danger_foreground = paint(0xfff8f5); + color.tab_active = accent; + color.tab_active_foreground = btn; + color.scrollbar_thumb = line; + color.window_border = line; +} + +#[cfg(test)] +mod tests { + use super::label; + + #[test] + fn labels_known_preferences() { + assert_eq!(label("light"), "Light"); + assert_eq!(label("dark"), "Dark"); + assert_eq!(label("system"), "Follow System"); + } + + #[test] + fn unknown_preference_labels_as_dark() { + assert_eq!(label("neon"), "Dark"); + } +} diff --git a/package.json b/package.json deleted file mode 100644 index e7623c2..0000000 --- a/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "grimoire", - "version": "0.1.9", - "description": "Grimoire - eBook collection manager", - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "prepare": "svelte-kit sync || echo ''", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "test": "vitest run", - "test:watch": "vitest", - "test:rust": "cargo test --manifest-path src-tauri/Cargo.toml", - "test:all": "pnpm test && pnpm test:rust", - "tauri": "tauri" - }, - "license": "MPL-2.0", - "dependencies": { - "@lucide/svelte": "^1.44.0", - "@tauri-apps/api": "^2", - "@tauri-apps/plugin-dialog": "^2.7.3", - "@tauri-apps/plugin-fs": "^2.5.2", - "@tauri-apps/plugin-opener": "^2", - "epubjs": "^0.3.93" - }, - "devDependencies": { - "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.65.1", - "@sveltejs/vite-plugin-svelte": "^7.1.2", - "@tauri-apps/cli": "^2", - "happy-dom": "^20.14.3", - "svelte": "^5.56.3", - "svelte-check": "^4.6.0", - "typescript": "~6.0.3", - "vite": "^8.0.16", - "vitest": "^5.0.0" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 6caf776..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,1462 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -overrides: - '@xmldom/xmldom': 0.8.15 - -importers: - - .: - dependencies: - '@lucide/svelte': - specifier: ^1.44.0 - version: 1.44.0(svelte@5.57.0) - '@tauri-apps/api': - specifier: ^2 - version: 2.11.1 - '@tauri-apps/plugin-dialog': - specifier: ^2.7.3 - version: 2.7.3 - '@tauri-apps/plugin-fs': - specifier: ^2.5.2 - version: 2.5.2 - '@tauri-apps/plugin-opener': - specifier: ^2 - version: 2.5.5 - epubjs: - specifier: ^0.3.93 - version: 0.3.93 - devDependencies: - '@sveltejs/adapter-static': - specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.70.3(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0)(vite@8.2.2(@types/node@22.20.2)))(svelte@5.57.0)(typescript@6.0.3)(vite@8.2.2(@types/node@22.20.2))) - '@sveltejs/kit': - specifier: ^2.65.1 - version: 2.70.3(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0)(vite@8.2.2(@types/node@22.20.2)))(svelte@5.57.0)(typescript@6.0.3)(vite@8.2.2(@types/node@22.20.2)) - '@sveltejs/vite-plugin-svelte': - specifier: ^7.1.2 - version: 7.3.0(svelte@5.57.0)(vite@8.2.2(@types/node@22.20.2)) - '@tauri-apps/cli': - specifier: ^2 - version: 2.11.4 - happy-dom: - specifier: ^20.14.3 - version: 20.14.3 - svelte: - specifier: ^5.56.3 - version: 5.57.0 - svelte-check: - specifier: ^4.6.0 - version: 4.7.6(picomatch@4.0.7)(svelte@5.57.0)(typescript@6.0.3) - typescript: - specifier: ~6.0.3 - version: 6.0.3 - vite: - specifier: ^8.0.16 - version: 8.2.2(@types/node@22.20.2) - vitest: - specifier: ^5.0.0 - version: 5.0.0(@types/node@22.20.2)(happy-dom@20.14.3)(vite@8.2.2(@types/node@22.20.2)) - -packages: - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.6.0': - resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@lucide/svelte@1.44.0': - resolution: {integrity: sha512-67510ne/YowceSyN9BECQaWfp5Ed446X6xpUE5C0e4rRV0e1jRj1KzIlKJCXcGc21hgr/VBDkqQXY7v4VQDeBw==} - peerDependencies: - svelte: ^5 - - '@oxc-project/types@0.149.0': - resolution: {integrity: sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==} - - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - - '@rolldown/binding-android-arm-eabi@1.2.8': - resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@rolldown/binding-android-arm64@1.2.8': - resolution: {integrity: sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.2.8': - resolution: {integrity: sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.2.8': - resolution: {integrity: sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.2.8': - resolution: {integrity: sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.2.8': - resolution: {integrity: sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.2.8': - resolution: {integrity: sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.2.8': - resolution: {integrity: sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.2.8': - resolution: {integrity: sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.2.8': - resolution: {integrity: sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.2.8': - resolution: {integrity: sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.2.8': - resolution: {integrity: sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.2.8': - resolution: {integrity: sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-win32-arm64-msvc@1.2.8': - resolution: {integrity: sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.2.8': - resolution: {integrity: sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@sveltejs/acorn-typescript@1.0.13': - resolution: {integrity: sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ==} - peerDependencies: - acorn: ^8.9.0 - - '@sveltejs/adapter-static@3.0.10': - resolution: {integrity: sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==} - peerDependencies: - '@sveltejs/kit': ^2.0.0 - - '@sveltejs/kit@2.70.3': - resolution: {integrity: sha512-UDvEYuZqAMbfB/oXIoqKvbKcb7YczK5zYrzmsGV1zRJk03jntwp8dXiYoIJotxAndsKvcPFtx9H1GRSKFdSHgg==} - engines: {node: '>=18.13'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.0.0 - '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 - svelte: ^4.0.0 || ^5.0.0-next.0 - typescript: ^5.3.3 || ^6.0.0 - vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - typescript: - optional: true - - '@sveltejs/load-config@0.2.3': - resolution: {integrity: sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ==} - engines: {node: '>= 18.0.0'} - - '@sveltejs/vite-plugin-svelte@7.3.0': - resolution: {integrity: sha512-QbRoJyD92e9R0ufeQIWRHrCC0ObcqSv/aBDdrQMoU+sypav3cDx5wytdQ6GLdXjEMO6xjrXGzfkUygng8JMv0A==} - engines: {node: ^20.19 || ^22.12 || >=24} - peerDependencies: - svelte: ^5.46.4 - vite: ^8.0.0-beta.7 || ^8.0.0 - - '@tauri-apps/api@2.11.1': - resolution: {integrity: sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==} - - '@tauri-apps/cli-darwin-arm64@2.11.4': - resolution: {integrity: sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tauri-apps/cli-darwin-x64@2.11.4': - resolution: {integrity: sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': - resolution: {integrity: sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tauri-apps/cli-linux-arm64-gnu@2.11.4': - resolution: {integrity: sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tauri-apps/cli-linux-arm64-musl@2.11.4': - resolution: {integrity: sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': - resolution: {integrity: sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==} - engines: {node: '>= 10'} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@tauri-apps/cli-linux-x64-gnu@2.11.4': - resolution: {integrity: sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tauri-apps/cli-linux-x64-musl@2.11.4': - resolution: {integrity: sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tauri-apps/cli-win32-arm64-msvc@2.11.4': - resolution: {integrity: sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tauri-apps/cli-win32-ia32-msvc@2.11.4': - resolution: {integrity: sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@tauri-apps/cli-win32-x64-msvc@2.11.4': - resolution: {integrity: sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tauri-apps/cli@2.11.4': - resolution: {integrity: sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==} - engines: {node: '>= 10'} - hasBin: true - - '@tauri-apps/plugin-dialog@2.7.3': - resolution: {integrity: sha512-CRgE+7TP4tvq9MjBU6f04NLTFIqVMLKHk3hAqlhil00ngK9ACTrXPH3oHpKMProxILodd3YjBoKbMwSI4IEcfA==} - - '@tauri-apps/plugin-fs@2.5.2': - resolution: {integrity: sha512-XXvMSnFiob+G1H+YHCDf+bzWVumseQuEIhzpbOJzevUfL4k0U+sTApZWJHpoLiamggnVPJm5dJ5sDsniRyWxlg==} - - '@tauri-apps/plugin-opener@2.5.5': - resolution: {integrity: sha512-xvzGai5aQds8j8R8RsUK/lW6pGG50YgOYIPLzvkqmkwAj7dfySOD7sGtejRzvVdMmv1EQfKVEFh1MvmDp8QR0g==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/localforage@0.0.34': - resolution: {integrity: sha512-tJxahnjm9dEI1X+hQSC5f2BSd/coZaqbIl1m3TCl0q9SVuC52XcXfV0XmoCU1+PmjyucuVITwoTnN8OlTbEXXA==} - deprecated: This is a stub types definition for localforage (https://github.com/localForage/localForage). localforage provides its own type definitions, so you don't need @types/localforage installed! - - '@types/node@22.20.2': - resolution: {integrity: sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==} - - '@types/whatwg-mimetype@3.0.2': - resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} - - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@vitest/mocker@5.0.0': - resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/spy@5.0.0': - resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==} - - '@xmldom/xmldom@0.8.15': - resolution: {integrity: sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==} - engines: {node: '>=10.0.0'} - - acorn@8.18.0: - resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} - engines: {node: '>=0.4.0'} - hasBin: true - - aria-query@5.3.1: - resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} - engines: {node: '>= 0.4'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} - - buffer-image-size@0.6.4: - resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} - engines: {node: '>=4.0'} - - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} - - core-js@3.50.0: - resolution: {integrity: sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - d@1.0.2: - resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} - engines: {node: '>=0.12'} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - devalue@5.9.4: - resolution: {integrity: sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==} - - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - - epubjs@0.3.93: - resolution: {integrity: sha512-c06pNSdBxcXv3dZSbXAVLE1/pmleRhOT6mXNZo6INKmvuKpYB65MwU/lO7830czCtjIiK9i+KR+3S+p0wtljrw==} - - es-module-lexer@2.3.2: - resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} - - es5-ext@0.10.64: - resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} - engines: {node: '>=0.10'} - - es6-iterator@2.0.3: - resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} - - es6-symbol@3.1.4: - resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} - engines: {node: '>=0.12'} - - esm-env@1.2.2: - resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} - - esniff@2.0.1: - resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} - engines: {node: '>=0.10'} - - esrap@2.3.7: - resolution: {integrity: sha512-n2nf7fZR3c9yXf0BPEuHuXqT+KW0SJVj4cN5FMEkpCZ3scLjOQWpiccyCxVzCC2q1wubTghuEGzngJY/7Ah0Ow==} - peerDependencies: - '@typescript-eslint/types': ^8.2.0 - peerDependenciesMeta: - '@typescript-eslint/types': - optional: true - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - event-emitter@0.3.5: - resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} - - expect-type@1.4.0: - resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} - engines: {node: '>=12.0.0'} - - ext@1.7.0: - resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - happy-dom@20.14.3: - resolution: {integrity: sha512-0KMb/Eh8rsd+aMNkOk5h8LkAjo9Na1aksnEGmsuZf+GXl6UkAdIi0VbUHU7d59lXhNmawODgwwuCMiZpCxwwCw==} - engines: {node: '>=20.0.0'} - - immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - is-reference@3.0.3: - resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - jszip@3.10.2: - resolution: {integrity: sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - lie@3.1.1: - resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} - - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - - lightningcss-android-arm64@1.33.0: - resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.33.0: - resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.33.0: - resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.33.0: - resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.33.0: - resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.33.0: - resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.33.0: - resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.33.0: - resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.33.0: - resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.33.0: - resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.33.0: - resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.33.0: - resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} - engines: {node: '>= 12.0.0'} - - localforage@1.10.0: - resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} - - locate-character@3.0.0: - resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} - - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - magic-string@1.3.1: - resolution: {integrity: sha512-rm91zr2Ou+XueDTohjQQjdQEcYM6zVi8KVUCG8Ec3vHwUEKrhSdCNyfuIywkA6hcCAteIn0ZOtAHA6eGpiX+Pg==} - - marks-pane@1.0.9: - resolution: {integrity: sha512-Ahs4oeG90tbdPWwAJkAAoHg2lRR8lAs9mZXETNPO9hYg3AkjUJBKi1NQ4aaIQZVGrig7c/3NUV1jANl8rFTeMg==} - - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - - nanoid@3.3.18: - resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - next-tick@1.1.0: - resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - - obug@2.2.1: - resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==} - engines: {node: '>=12.20.0'} - - pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - - path-webpack@0.0.3: - resolution: {integrity: sha512-AmeDxedoo5svf7aB3FYqSAKqMxys014lVKBzy1o/5vv9CtU7U4wgGWL1dA2o6MOzcD53ScN4Jmiq6VbtLz1vIQ==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.7: - resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} - engines: {node: '>=12'} - - postcss@8.5.28: - resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} - engines: {node: ^10 || ^12 || >=14} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - rolldown@1.2.8: - resolution: {integrity: sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - set-cookie-parser@3.1.2: - resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==} - - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - sirv@3.0.2: - resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} - engines: {node: '>=18'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@4.2.0: - resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - svelte-check@4.7.6: - resolution: {integrity: sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw==} - engines: {node: '>= 18.0.0'} - hasBin: true - peerDependencies: - svelte: ^4.0.0 || ^5.0.0-next.0 - typescript: ^5.0.0 || ^6.0.0 - - svelte@5.57.0: - resolution: {integrity: sha512-NdbDn7fl4be1ViUG0oq/lvG6OZy3oENolV2ONjiqqsfVoeAfzaQAKUcEX3MrQod/Bebv1PgwET9rfXhgn9s4Kg==} - engines: {node: '>=18'} - - tinybench@6.1.4: - resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} - engines: {node: '>=20.0.0'} - - tinyexec@1.3.0: - resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} - engines: {node: '>=18'} - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - - type@2.7.3: - resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - vite@8.2.2: - resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 || ^0.5.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 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitefu@1.1.3: - resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - vite: - optional: true - - vitest@5.0.0: - resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==} - engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 5.0.0 - '@vitest/browser-preview': 5.0.0 - '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 - '@vitest/coverage-istanbul': 5.0.0 - '@vitest/coverage-v8': 5.0.0 - '@vitest/ui': 5.0.0 - happy-dom: '*' - jsdom: '*' - vite: ^6.4.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - ws@8.21.3: - resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - zimmerframe@1.1.5: - resolution: {integrity: sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==} - -snapshots: - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.6.0': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.6.0 - - '@lucide/svelte@1.44.0(svelte@5.57.0)': - dependencies: - svelte: 5.57.0 - - '@oxc-project/types@0.149.0': {} - - '@polka/url@1.0.0-next.29': {} - - '@rolldown/binding-android-arm-eabi@1.2.8': - optional: true - - '@rolldown/binding-android-arm64@1.2.8': - optional: true - - '@rolldown/binding-darwin-arm64@1.2.8': - optional: true - - '@rolldown/binding-darwin-x64@1.2.8': - optional: true - - '@rolldown/binding-freebsd-x64@1.2.8': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.2.8': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.2.8': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.2.8': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.2.8': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.2.8': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.2.8': - optional: true - - '@rolldown/binding-linux-x64-musl@1.2.8': - optional: true - - '@rolldown/binding-openharmony-arm64@1.2.8': - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.2.8': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.2.8': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@standard-schema/spec@1.1.0': {} - - '@sveltejs/acorn-typescript@1.0.13(acorn@8.18.0)': - dependencies: - acorn: 8.18.0 - - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.70.3(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0)(vite@8.2.2(@types/node@22.20.2)))(svelte@5.57.0)(typescript@6.0.3)(vite@8.2.2(@types/node@22.20.2)))': - dependencies: - '@sveltejs/kit': 2.70.3(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0)(vite@8.2.2(@types/node@22.20.2)))(svelte@5.57.0)(typescript@6.0.3)(vite@8.2.2(@types/node@22.20.2)) - - '@sveltejs/kit@2.70.3(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0)(vite@8.2.2(@types/node@22.20.2)))(svelte@5.57.0)(typescript@6.0.3)(vite@8.2.2(@types/node@22.20.2))': - dependencies: - '@standard-schema/spec': 1.1.0 - '@sveltejs/acorn-typescript': 1.0.13(acorn@8.18.0) - '@sveltejs/vite-plugin-svelte': 7.3.0(svelte@5.57.0)(vite@8.2.2(@types/node@22.20.2)) - '@types/cookie': 0.6.0 - acorn: 8.18.0 - cookie: 0.6.0 - devalue: 5.9.4 - esm-env: 1.2.2 - kleur: 4.1.5 - magic-string: 0.30.21 - mrmime: 2.0.1 - set-cookie-parser: 3.1.2 - sirv: 3.0.2 - svelte: 5.57.0 - vite: 8.2.2(@types/node@22.20.2) - optionalDependencies: - typescript: 6.0.3 - - '@sveltejs/load-config@0.2.3': {} - - '@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.57.0)(vite@8.2.2(@types/node@22.20.2))': - dependencies: - deepmerge: 4.3.1 - magic-string: 1.3.1 - obug: 2.2.1 - svelte: 5.57.0 - vite: 8.2.2(@types/node@22.20.2) - vitefu: 1.1.3(vite@8.2.2(@types/node@22.20.2)) - - '@tauri-apps/api@2.11.1': {} - - '@tauri-apps/cli-darwin-arm64@2.11.4': - optional: true - - '@tauri-apps/cli-darwin-x64@2.11.4': - optional: true - - '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': - optional: true - - '@tauri-apps/cli-linux-arm64-gnu@2.11.4': - optional: true - - '@tauri-apps/cli-linux-arm64-musl@2.11.4': - optional: true - - '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': - optional: true - - '@tauri-apps/cli-linux-x64-gnu@2.11.4': - optional: true - - '@tauri-apps/cli-linux-x64-musl@2.11.4': - optional: true - - '@tauri-apps/cli-win32-arm64-msvc@2.11.4': - optional: true - - '@tauri-apps/cli-win32-ia32-msvc@2.11.4': - optional: true - - '@tauri-apps/cli-win32-x64-msvc@2.11.4': - optional: true - - '@tauri-apps/cli@2.11.4': - optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.11.4 - '@tauri-apps/cli-darwin-x64': 2.11.4 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.4 - '@tauri-apps/cli-linux-arm64-gnu': 2.11.4 - '@tauri-apps/cli-linux-arm64-musl': 2.11.4 - '@tauri-apps/cli-linux-riscv64-gnu': 2.11.4 - '@tauri-apps/cli-linux-x64-gnu': 2.11.4 - '@tauri-apps/cli-linux-x64-musl': 2.11.4 - '@tauri-apps/cli-win32-arm64-msvc': 2.11.4 - '@tauri-apps/cli-win32-ia32-msvc': 2.11.4 - '@tauri-apps/cli-win32-x64-msvc': 2.11.4 - - '@tauri-apps/plugin-dialog@2.7.3': - dependencies: - '@tauri-apps/api': 2.11.1 - - '@tauri-apps/plugin-fs@2.5.2': - dependencies: - '@tauri-apps/api': 2.11.1 - - '@tauri-apps/plugin-opener@2.5.5': - dependencies: - '@tauri-apps/api': 2.11.1 - - '@types/chai@5.2.3': - dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 - - '@types/cookie@0.6.0': {} - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.9': {} - - '@types/localforage@0.0.34': - dependencies: - localforage: 1.10.0 - - '@types/node@22.20.2': - dependencies: - undici-types: 6.21.0 - - '@types/whatwg-mimetype@3.0.2': {} - - '@types/ws@8.18.1': - dependencies: - '@types/node': 22.20.2 - - '@vitest/mocker@5.0.0(vite@8.2.2(@types/node@22.20.2))': - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.0 - estree-walker: 3.0.3 - magic-string: 1.3.1 - optionalDependencies: - vite: 8.2.2(@types/node@22.20.2) - - '@vitest/spy@5.0.0': {} - - '@xmldom/xmldom@0.8.15': {} - - acorn@8.18.0: {} - - aria-query@5.3.1: {} - - assertion-error@2.0.1: {} - - axobject-query@4.1.0: {} - - buffer-image-size@0.6.4: - dependencies: - '@types/node': 22.20.2 - - chai@6.2.2: {} - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - clsx@2.1.1: {} - - cookie@0.6.0: {} - - core-js@3.50.0: {} - - core-util-is@1.0.3: {} - - d@1.0.2: - dependencies: - es5-ext: 0.10.64 - type: 2.7.3 - - deepmerge@4.3.1: {} - - detect-libc@2.1.2: {} - - devalue@5.9.4: {} - - entities@7.0.1: {} - - epubjs@0.3.93: - dependencies: - '@types/localforage': 0.0.34 - '@xmldom/xmldom': 0.8.15 - core-js: 3.50.0 - event-emitter: 0.3.5 - jszip: 3.10.2 - localforage: 1.10.0 - lodash: 4.18.1 - marks-pane: 1.0.9 - path-webpack: 0.0.3 - - es-module-lexer@2.3.2: {} - - es5-ext@0.10.64: - dependencies: - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - esniff: 2.0.1 - next-tick: 1.1.0 - - es6-iterator@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-symbol: 3.1.4 - - es6-symbol@3.1.4: - dependencies: - d: 1.0.2 - ext: 1.7.0 - - esm-env@1.2.2: {} - - esniff@2.0.1: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - event-emitter: 0.3.5 - type: 2.7.3 - - esrap@2.3.7: - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.9 - - event-emitter@0.3.5: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - - expect-type@1.4.0: {} - - ext@1.7.0: - dependencies: - type: 2.7.3 - - fdir@6.5.0(picomatch@4.0.7): - optionalDependencies: - picomatch: 4.0.7 - - fsevents@2.3.3: - optional: true - - happy-dom@20.14.3: - dependencies: - '@types/node': 22.20.2 - '@types/whatwg-mimetype': 3.0.2 - '@types/ws': 8.18.1 - buffer-image-size: 0.6.4 - entities: 7.0.1 - whatwg-mimetype: 3.0.0 - ws: 8.21.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - immediate@3.0.6: {} - - inherits@2.0.4: {} - - is-reference@3.0.3: - dependencies: - '@types/estree': 1.0.9 - - isarray@1.0.0: {} - - jszip@3.10.2: - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - - kleur@4.1.5: {} - - lie@3.1.1: - dependencies: - immediate: 3.0.6 - - lie@3.3.0: - dependencies: - immediate: 3.0.6 - - lightningcss-android-arm64@1.33.0: - optional: true - - lightningcss-darwin-arm64@1.33.0: - optional: true - - lightningcss-darwin-x64@1.33.0: - optional: true - - lightningcss-freebsd-x64@1.33.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.33.0: - optional: true - - lightningcss-linux-arm64-gnu@1.33.0: - optional: true - - lightningcss-linux-arm64-musl@1.33.0: - optional: true - - lightningcss-linux-x64-gnu@1.33.0: - optional: true - - lightningcss-linux-x64-musl@1.33.0: - optional: true - - lightningcss-win32-arm64-msvc@1.33.0: - optional: true - - lightningcss-win32-x64-msvc@1.33.0: - optional: true - - lightningcss@1.33.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.33.0 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-freebsd-x64: 1.33.0 - lightningcss-linux-arm-gnueabihf: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-arm64-msvc: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 - - localforage@1.10.0: - dependencies: - lie: 3.1.1 - - locate-character@3.0.0: {} - - lodash@4.18.1: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - - magic-string@1.3.1: - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - - marks-pane@1.0.9: {} - - mri@1.2.0: {} - - mrmime@2.0.1: {} - - nanoid@3.3.18: {} - - next-tick@1.1.0: {} - - obug@2.2.1: {} - - pako@1.0.11: {} - - path-webpack@0.0.3: {} - - picocolors@1.1.1: {} - - picomatch@4.0.7: {} - - postcss@8.5.28: - dependencies: - nanoid: 3.3.18 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - process-nextick-args@2.0.1: {} - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - readdirp@4.1.2: {} - - rolldown@1.2.8: - dependencies: - '@oxc-project/types': 0.149.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.8 - '@rolldown/binding-android-arm64': 1.2.8 - '@rolldown/binding-darwin-arm64': 1.2.8 - '@rolldown/binding-darwin-x64': 1.2.8 - '@rolldown/binding-freebsd-x64': 1.2.8 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.8 - '@rolldown/binding-linux-arm64-gnu': 1.2.8 - '@rolldown/binding-linux-arm64-musl': 1.2.8 - '@rolldown/binding-linux-ppc64-gnu': 1.2.8 - '@rolldown/binding-linux-s390x-gnu': 1.2.8 - '@rolldown/binding-linux-x64-gnu': 1.2.8 - '@rolldown/binding-linux-x64-musl': 1.2.8 - '@rolldown/binding-openharmony-arm64': 1.2.8 - '@rolldown/binding-win32-arm64-msvc': 1.2.8 - '@rolldown/binding-win32-x64-msvc': 1.2.8 - - sade@1.8.1: - dependencies: - mri: 1.2.0 - - safe-buffer@5.1.2: {} - - set-cookie-parser@3.1.2: {} - - setimmediate@1.0.5: {} - - siginfo@2.0.0: {} - - sirv@3.0.2: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - - source-map-js@1.2.1: {} - - stackback@0.0.2: {} - - std-env@4.2.0: {} - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - svelte-check@4.7.6(picomatch@4.0.7)(svelte@5.57.0)(typescript@6.0.3): - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@sveltejs/load-config': 0.2.3 - chokidar: 4.0.3 - fdir: 6.5.0(picomatch@4.0.7) - picocolors: 1.1.1 - sade: 1.8.1 - svelte: 5.57.0 - typescript: 6.0.3 - transitivePeerDependencies: - - picomatch - - svelte@5.57.0: - dependencies: - '@jridgewell/remapping': 2.3.5 - '@jridgewell/sourcemap-codec': 1.6.0 - '@sveltejs/acorn-typescript': 1.0.13(acorn@8.18.0) - '@types/estree': 1.0.9 - acorn: 8.18.0 - aria-query: 5.3.1 - axobject-query: 4.1.0 - clsx: 2.1.1 - devalue: 5.9.4 - esm-env: 1.2.2 - esrap: 2.3.7 - is-reference: 3.0.3 - locate-character: 3.0.0 - magic-string: 0.30.21 - zimmerframe: 1.1.5 - transitivePeerDependencies: - - '@typescript-eslint/types' - - tinybench@6.1.4: {} - - tinyexec@1.3.0: {} - - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.7) - picomatch: 4.0.7 - - totalist@3.0.1: {} - - type@2.7.3: {} - - typescript@6.0.3: {} - - undici-types@6.21.0: {} - - util-deprecate@1.0.2: {} - - vite@8.2.2(@types/node@22.20.2): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.7 - postcss: 8.5.28 - rolldown: 1.2.8 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 22.20.2 - fsevents: 2.3.3 - - vitefu@1.1.3(vite@8.2.2(@types/node@22.20.2)): - optionalDependencies: - vite: 8.2.2(@types/node@22.20.2) - - vitest@5.0.0(@types/node@22.20.2)(happy-dom@20.14.3)(vite@8.2.2(@types/node@22.20.2)): - dependencies: - '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@22.20.2)) - chai: 6.2.2 - es-module-lexer: 2.3.2 - expect-type: 1.4.0 - magic-string: 1.3.1 - obug: 2.2.1 - picomatch: 4.0.7 - std-env: 4.2.0 - tinybench: 6.1.4 - tinyexec: 1.3.0 - tinyglobby: 0.2.17 - vite: 8.2.2(@types/node@22.20.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.20.2 - happy-dom: 20.14.3 - transitivePeerDependencies: - - msw - - whatwg-mimetype@3.0.0: {} - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - ws@8.21.3: {} - - zimmerframe@1.1.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index 78ffc6d..0000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,13 +0,0 @@ -packages: - - . - -# pnpm 12+: approve packages that need install-time build scripts. -# See https://pnpm.io/settings/build#allowbuilds -allowBuilds: - core-js: true - es5-ext: true - esbuild: true - -# Force patched xmldom (epubjs still pins ^0.7.5; Dependabot high alerts). -overrides: - "@xmldom/xmldom": "0.8.15" diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore deleted file mode 100644 index b21bd68..0000000 --- a/src-tauri/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Generated by Tauri -# will have schema files for capabilities auto-completion -/gen/schemas diff --git a/src-tauri/build.rs b/src-tauri/build.rs deleted file mode 100644 index d860e1e..0000000 --- a/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json deleted file mode 100644 index f6553e6..0000000 --- a/src-tauri/capabilities/default.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for Grimoire windows", - "windows": ["main", "reader-*"], - "permissions": [ - "core:default", - "core:webview:allow-create-webview-window", - "core:window:allow-set-focus", - "core:window:allow-close", - "core:window:allow-show", - "core:window:allow-set-title", - "core:window:allow-start-dragging", - "core:window:allow-minimize", - "core:window:allow-maximize", - "core:window:allow-unmaximize", - "core:window:allow-toggle-maximize", - "core:window:allow-is-maximized", - "opener:default", - "dialog:default" - ] -} diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 2ffbf24..0000000 --- a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 0281c8b..0000000 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 4e0db18..0000000 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 8b73a02..0000000 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index a31b971..0000000 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 42e1348..0000000 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 49ecdae..0000000 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 28531ca..0000000 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index d9e5007..0000000 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index f81b245..0000000 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index a32ee35..0000000 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index d51f60f..0000000 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 6cb3fb3..0000000 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index c85138e..0000000 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 5514c9a..0000000 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index d31d736..0000000 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml deleted file mode 100644 index ea9c223..0000000 --- a/src-tauri/icons/android/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #fff - \ No newline at end of file diff --git a/src-tauri/package.json b/src-tauri/package.json deleted file mode 100644 index f4a8a58..0000000 --- a/src-tauri/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "src-tauri", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "MPL-2.0", - "dependencies": { - "@tauri-apps/plugin-dialog": "~2.7.3" - } -} diff --git a/src-tauri/pnpm-lock.yaml b/src-tauri/pnpm-lock.yaml deleted file mode 100644 index f24c186..0000000 --- a/src-tauri/pnpm-lock.yaml +++ /dev/null @@ -1,29 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@tauri-apps/plugin-dialog': - specifier: ~2.7.3 - version: 2.7.3 - -packages: - - '@tauri-apps/api@2.11.1': - resolution: {integrity: sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==} - - '@tauri-apps/plugin-dialog@2.7.3': - resolution: {integrity: sha512-CRgE+7TP4tvq9MjBU6f04NLTFIqVMLKHk3hAqlhil00ngK9ACTrXPH3oHpKMProxILodd3YjBoKbMwSI4IEcfA==} - -snapshots: - - '@tauri-apps/api@2.11.1': {} - - '@tauri-apps/plugin-dialog@2.7.3': - dependencies: - '@tauri-apps/api': 2.11.1 diff --git a/src-tauri/src/device_watch.rs b/src-tauri/src/device_watch.rs deleted file mode 100644 index eab2f26..0000000 --- a/src-tauri/src/device_watch.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::kindle::{ - classify_reader, list_ebook_readers, list_mounted_readers, EbookReaderDevice, -}; -use futures::StreamExt; -use mtp_rs::mtp::{watch_devices, HotplugEvent, MtpDeviceInfo}; -use serde::Serialize; -use std::collections::HashMap; -use std::time::{Duration, Instant}; -use tauri::{AppHandle, Emitter}; -use tokio::time::{interval, sleep}; - -pub const EREADER_PRESENCE_EVENT: &str = "ereader-presence"; - -#[derive(Debug, Clone, Serialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum PresenceChange { - Arrived, - Left, - Snapshot, -} - -#[derive(Debug, Clone, Serialize)] -pub struct EreaderPresenceEvent { - pub change: PresenceChange, - pub label: Option, - pub device_id: Option, - pub devices: Vec, -} - -pub fn start_device_watcher(app: AppHandle) { - let app_mtp = app.clone(); - tauri::async_runtime::spawn(async move { - if let Err(e) = watch_mtp_presence(app_mtp).await { - eprintln!("Grimoire MTP device watch stopped: {e}"); - } - }); - - tauri::async_runtime::spawn(async move { - watch_mount_presence(app).await; - }); -} - -async fn watch_mtp_presence(app: AppHandle) -> Result<(), String> { - let mut watch = watch_devices().map_err(|e| e.to_string())?; - // Devices already connected are reported as Arrived when the stream starts. - // Treat the first second as bootstrap so those do not toast. - let bootstrap_until = Instant::now() + Duration::from_millis(1000); - let mut emitted_bootstrap = false; - - while let Some(event) = watch.next().await { - let (arrived, info) = match event { - HotplugEvent::Arrived(info) => (true, info), - HotplugEvent::Left(info) => (false, info), - }; - - if !mtp_info_is_ereader(&info) { - continue; - } - - let in_bootstrap = Instant::now() < bootstrap_until; - if in_bootstrap { - if !emitted_bootstrap { - sleep(Duration::from_millis(450)).await; - emit_presence(&app, PresenceChange::Snapshot, None, None).await; - emitted_bootstrap = true; - } - continue; - } - - // Give the device a moment to finish enumerating before we open it. - sleep(Duration::from_millis(450)).await; - - let label = device_label_from_mtp(&info); - let change = if arrived { - PresenceChange::Arrived - } else { - PresenceChange::Left - }; - - emit_presence(&app, change, Some(label), None).await; - } - - Ok(()) -} - -async fn watch_mount_presence(app: AppHandle) { - let mut known: HashMap = HashMap::new(); - let mut primed = false; - let mut tick = interval(Duration::from_secs(2)); - - loop { - tick.tick().await; - let mounts = match list_mounted_readers() { - Ok(list) => list, - Err(_) => continue, - }; - let current: HashMap = mounts - .iter() - .map(|d| (d.id.clone(), d.product.clone())) - .collect(); - - if !primed { - let had_mounts = !current.is_empty(); - known = current; - primed = true; - if had_mounts { - emit_presence(&app, PresenceChange::Snapshot, None, None).await; - } - continue; - } - - let arrived: Vec<(String, String)> = current - .iter() - .filter(|(id, _)| !known.contains_key(*id)) - .map(|(id, product)| (id.clone(), product.clone())) - .collect(); - let left: Vec<(String, String)> = known - .iter() - .filter(|(id, _)| !current.contains_key(*id)) - .map(|(id, product)| (id.clone(), product.clone())) - .collect(); - - for (id, product) in arrived { - emit_presence(&app, PresenceChange::Arrived, Some(product), Some(id)).await; - } - - for (id, product) in left { - emit_presence(&app, PresenceChange::Left, Some(product), Some(id)).await; - } - - known = current; - } -} - -fn mtp_info_is_ereader(info: &MtpDeviceInfo) -> bool { - let manufacturer = info.manufacturer.clone().unwrap_or_default(); - let product = info.product.clone().unwrap_or_default(); - classify_reader(&manufacturer, &product).is_some() -} - -fn device_label_from_mtp(info: &MtpDeviceInfo) -> String { - info.product - .clone() - .filter(|s| !s.is_empty()) - .unwrap_or_else(|| "eReader".to_string()) -} - -async fn emit_presence( - app: &AppHandle, - change: PresenceChange, - label: Option, - device_id: Option, -) { - let devices = match list_ebook_readers().await { - Ok(list) => list, - Err(e) => { - eprintln!("Grimoire could not refresh eReaders after presence change: {e}"); - Vec::new() - } - }; - - let resolved_id = device_id.or_else(|| { - label.as_ref().and_then(|name| { - devices - .iter() - .find(|d| d.product.eq_ignore_ascii_case(name)) - .map(|d| d.id.clone()) - }) - }); - - let payload = EreaderPresenceEvent { - change, - label, - device_id: resolved_id, - devices, - }; - - if let Err(e) = app.emit(EREADER_PRESENCE_EVENT, payload) { - eprintln!("Grimoire failed to emit eReader presence event: {e}"); - } -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs deleted file mode 100644 index ba70a15..0000000 --- a/src-tauri/src/lib.rs +++ /dev/null @@ -1,440 +0,0 @@ -mod comic; -mod config; -mod convert; -mod db; -mod device_watch; -mod error; -mod kf8_extract; -mod kindle; -mod library; -mod metadata; -mod openlibrary; -mod reader; -mod state; - -use comic::{comic_page_data_url, list_comic_image_names, ComicPage, ComicPageList}; -use config::{clear_library_path, load_config, set_library_path, AppConfig}; -use convert::{ - convert_book_format, kindle_convert_available, list_convertible_targets, resolve_kindle_file, - ConvertResult, KindleReadyFile, -}; -use device_watch::start_device_watcher; -use error::{err, AppResult}; -use kindle::{ - delete_device_books, list_ebook_readers, list_kindles, list_sync_records, record_sync, - scan_device_library, send_file_to_kindle, DeviceBookTarget, DeviceBulkResult, DeviceLibrary, - EbookReaderDevice, KindleDeviceInfo, SyncRecord, TransferResult, -}; -use library::{ - apply_remote_match, bulk_patch_ebooks, cover_data_url, create_library, delete_book, - delete_ebooks, format_file_path, get_book, import_book, list_books, open_library, set_book_cover, - update_book, BookBulkPatch, BookDetail, BookSummary, BookUpdate, BulkActionResult, - EnrichmentResult, -}; -use openlibrary::{ - cover_candidates_from_matches, download_cover_from_urls, lookup_book, lookup_book_candidates, - CoverCandidate, -}; -use reader::{ - get_reading_progress, resolve_reader_source, save_reading_progress, ReaderSource, - ReadingProgress, ReadingProgressUpdate, -}; -use state::AppState; -use std::path::{Path, PathBuf}; -use tauri::State; - -#[tauri::command] -fn get_config() -> AppResult { - load_config() -} - -#[tauri::command] -fn get_library_status(state: State<'_, AppState>) -> AppResult { - let config = load_config()?; - Ok(LibraryStatus { - open: state.is_open(), - path: config.library_path, - kindle_convert_available: kindle_convert_available(), - }) -} - -#[derive(serde::Serialize)] -struct LibraryStatus { - open: bool, - path: Option, - kindle_convert_available: bool, -} - -#[tauri::command] -fn create_or_open_library(state: State<'_, AppState>, path: String) -> AppResult { - let root = PathBuf::from(&path); - let session = if root.join("metadata.db").exists() { - open_library(&root)? - } else { - create_library(&root)? - }; - set_library_path(&root)?; - state.set_session(session); - get_library_status(state) -} - -#[tauri::command] -fn open_saved_library(state: State<'_, AppState>) -> AppResult { - let config = load_config()?; - let Some(path) = config.library_path else { - return get_library_status(state); - }; - let root = PathBuf::from(&path); - if !root.exists() { - return get_library_status(state); - } - let session = if root.join("metadata.db").exists() { - open_library(&root)? - } else { - // Saved folder exists but has no library yet; create one so startup stays automatic. - create_library(&root)? - }; - state.set_session(session); - get_library_status(state) -} - -#[tauri::command] -fn clear_saved_library(state: State<'_, AppState>) -> AppResult { - clear_library_path()?; - state.clear_session(); - get_library_status(state) -} - -#[tauri::command] -fn import_ebook(state: State<'_, AppState>, path: String) -> AppResult { - state.with_conn_mut(|conn, root| import_book(conn, root, Path::new(&path))) -} - -#[tauri::command] -async fn enrich_ebook_metadata( - state: State<'_, AppState>, - id: i64, -) -> AppResult { - let (title, authors, isbn, has_cover) = state.with_conn(|conn, root| { - let book = get_book(conn, root, id)?; - let isbn = book - .identifiers - .iter() - .find(|i| i.type_name.eq_ignore_ascii_case("isbn")) - .map(|i| i.value.clone()); - Ok((book.title, book.authors, isbn, book.has_cover)) - })?; - - let hit = lookup_book(&title, &authors, isbn.as_deref()) - .await? - .ok_or_else(|| err("No Open Library match found for this title/author/ISBN."))?; - - let cover_bytes = if !has_cover { - match download_cover_from_urls(&hit.cover_urls).await { - Ok(bytes) => Some(bytes), - Err(e) => { - eprintln!("Grimoire cover download skipped: {e}"); - None - } - } - } else { - None - }; - - state.with_conn_mut(|conn, root| { - let current = get_book(conn, root, id)?; - apply_remote_match(conn, root, id, ¤t, &hit, cover_bytes.as_deref()) - }) -} - -#[tauri::command] -async fn list_ebook_cover_candidates( - state: State<'_, AppState>, - id: i64, -) -> AppResult> { - let (title, authors, isbn) = state.with_conn(|conn, root| { - let book = get_book(conn, root, id)?; - let isbn = book - .identifiers - .iter() - .find(|i| i.type_name.eq_ignore_ascii_case("isbn")) - .map(|i| i.value.clone()); - Ok((book.title, book.authors, isbn)) - })?; - - let matches = lookup_book_candidates(&title, &authors, isbn.as_deref(), 12).await?; - let candidates = cover_candidates_from_matches(&matches); - if candidates.is_empty() { - return Err(err( - "No cover images found on Open Library for this title/author/ISBN.", - )); - } - Ok(candidates) -} - -#[tauri::command] -async fn set_ebook_cover_from_urls( - state: State<'_, AppState>, - id: i64, - urls: Vec, -) -> AppResult { - if urls.is_empty() { - return Err(err("Pick a cover first.")); - } - // Confirm the book exists before downloading. - state.with_conn(|conn, root| { - let _ = get_book(conn, root, id)?; - Ok(()) - })?; - - let bytes = download_cover_from_urls(&urls).await?; - state.with_conn_mut(|conn, root| set_book_cover(conn, root, id, &bytes)) -} - -#[tauri::command] -fn list_ebooks(state: State<'_, AppState>, query: Option) -> AppResult> { - state.with_conn(|conn, _| list_books(conn, query.as_deref())) -} - -#[tauri::command] -fn get_ebook(state: State<'_, AppState>, id: i64) -> AppResult { - state.with_conn(|conn, root| get_book(conn, root, id)) -} - -#[tauri::command] -fn update_ebook(state: State<'_, AppState>, id: i64, update: BookUpdate) -> AppResult { - state.with_conn_mut(|conn, root| update_book(conn, root, id, update)) -} - -#[tauri::command] -fn delete_ebook(state: State<'_, AppState>, id: i64) -> AppResult<()> { - state.with_conn_mut(|conn, root| delete_book(conn, root, id)) -} - -#[tauri::command] -fn delete_ebooks_batch(state: State<'_, AppState>, ids: Vec) -> AppResult { - state.with_conn_mut(|conn, root| delete_ebooks(conn, root, &ids)) -} - -#[tauri::command] -fn bulk_patch_ebooks_cmd( - state: State<'_, AppState>, - ids: Vec, - patch: BookBulkPatch, -) -> AppResult { - state.with_conn_mut(|conn, root| bulk_patch_ebooks(conn, root, &ids, &patch)) -} - -#[tauri::command] -fn get_cover(state: State<'_, AppState>, id: i64) -> AppResult> { - state.with_conn(|conn, root| cover_data_url(conn, root, id)) -} - -#[tauri::command] -fn get_reader_source( - state: State<'_, AppState>, - book_id: i64, - format: Option, -) -> AppResult { - state.with_conn_mut(|conn, root| { - resolve_reader_source(conn, root, book_id, format.as_deref()) - }) -} - -#[tauri::command] -fn list_comic_pages( - state: State<'_, AppState>, - book_id: i64, -) -> AppResult { - state.with_conn(|conn, root| { - let source = { - // Prefer CBZ then CBR if both somehow exist. - let formats: Vec = { - let mut stmt = conn.prepare("SELECT format FROM formats WHERE book = ?1")?; - let rows = stmt.query_map([book_id], |row| row.get(0))?; - rows.collect::, _>>()? - }; - let format = ["CBZ", "CBR"] - .iter() - .find(|f| formats.iter().any(|have| have == **f)) - .ok_or_else(|| err("This book has no CBZ or CBR format."))?; - let path = format_file_path(conn, root, book_id, format)?; - (*format, path) - }; - let pages = list_comic_image_names(&source.1)?; - Ok(ComicPageList { - book_id, - format: source.0.to_string(), - page_count: pages.len(), - pages, - }) - }) -} - -#[tauri::command] -fn get_comic_page( - state: State<'_, AppState>, - book_id: i64, - index: usize, -) -> AppResult { - state.with_conn(|conn, root| { - let formats: Vec = { - let mut stmt = conn.prepare("SELECT format FROM formats WHERE book = ?1")?; - let rows = stmt.query_map([book_id], |row| row.get(0))?; - rows.collect::, _>>()? - }; - let format = ["CBZ", "CBR"] - .iter() - .find(|f| formats.iter().any(|have| have == **f)) - .ok_or_else(|| err("This book has no CBZ or CBR format."))?; - let path = format_file_path(conn, root, book_id, format)?; - let (name, data_url, page_count) = comic_page_data_url(&path, index)?; - Ok(ComicPage { - book_id, - index, - page_count, - name, - data_url, - }) - }) -} - -#[tauri::command] -fn list_ebook_convert_targets( - state: State<'_, AppState>, - book_id: i64, -) -> AppResult> { - state.with_conn(|conn, _| list_convertible_targets(conn, book_id)) -} - -#[tauri::command] -fn convert_ebook_format( - state: State<'_, AppState>, - book_id: i64, - target_format: String, -) -> AppResult { - state.with_conn_mut(|conn, root| convert_book_format(conn, root, book_id, &target_format)) -} - -#[tauri::command] -fn get_ebook_reading_progress( - state: State<'_, AppState>, - book_id: i64, -) -> AppResult> { - state.with_conn(|conn, _| get_reading_progress(conn, book_id)) -} - -#[tauri::command] -fn save_ebook_reading_progress( - state: State<'_, AppState>, - book_id: i64, - update: ReadingProgressUpdate, -) -> AppResult { - state.with_conn(|conn, _| save_reading_progress(conn, book_id, update)) -} - -#[tauri::command] -fn prepare_kindle_file(state: State<'_, AppState>, book_id: i64) -> AppResult { - state.with_conn_mut(|conn, root| resolve_kindle_file(conn, root, book_id)) -} - -#[tauri::command] -async fn list_kindle_devices() -> AppResult> { - list_kindles().await -} - -#[tauri::command] -async fn list_ebook_reader_devices() -> AppResult> { - list_ebook_readers().await -} - -#[tauri::command] -async fn scan_ebook_reader(device_id: String) -> AppResult { - scan_device_library(device_id).await -} - -#[tauri::command] -async fn delete_ebook_reader_books( - device_id: String, - books: Vec, -) -> AppResult { - delete_device_books(device_id, books).await -} - -#[tauri::command] -async fn send_book_to_kindle( - state: State<'_, AppState>, - book_id: i64, - device_id: String, -) -> AppResult { - let ready = state.with_conn_mut(|conn, root| resolve_kindle_file(conn, root, book_id))?; - let local = PathBuf::from(&ready.path); - let remote_name = local - .file_name() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| format!("book.{}", ready.format.to_ascii_lowercase())); - - let (remote_path, serial) = - send_file_to_kindle(&device_id, &local, &remote_name).await?; - - state.with_conn(|conn, _| { - record_sync(conn, book_id, &serial, &remote_path, &ready.format) - })?; - - Ok(TransferResult { - remote_path, - device_serial: serial, - format: ready.format, - }) -} - -#[tauri::command] -fn get_sync_history(state: State<'_, AppState>) -> AppResult> { - state.with_conn(|conn, _| list_sync_records(conn)) -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .plugin(tauri_plugin_dialog::init()) - .manage(AppState::new()) - .setup(|app| { - start_device_watcher(app.handle().clone()); - Ok(()) - }) - .invoke_handler(tauri::generate_handler![ - get_config, - get_library_status, - create_or_open_library, - open_saved_library, - clear_saved_library, - import_ebook, - enrich_ebook_metadata, - list_ebook_cover_candidates, - set_ebook_cover_from_urls, - list_ebooks, - get_ebook, - update_ebook, - delete_ebook, - delete_ebooks_batch, - bulk_patch_ebooks_cmd, - get_cover, - get_reader_source, - list_comic_pages, - get_comic_page, - get_ebook_reading_progress, - save_ebook_reading_progress, - list_ebook_convert_targets, - convert_ebook_format, - prepare_kindle_file, - list_kindle_devices, - list_ebook_reader_devices, - scan_ebook_reader, - delete_ebook_reader_books, - send_book_to_kindle, - get_sync_history, - ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json deleted file mode 100644 index 9b4673a..0000000 --- a/src-tauri/tauri.conf.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "Grimoire", - "mainBinaryName": "Grimoire", - "version": "0.1.9", - "identifier": "com.boxingoctopus.grimoire", - "build": { - "beforeDevCommand": "pnpm dev", - "devUrl": "http://localhost:1420", - "beforeBuildCommand": "pnpm build", - "frontendDist": "../build" - }, - "app": { - "macOSPrivateApi": true, - "windows": [ - { - "title": "Grimoire", - "width": 1280, - "height": 840, - "minWidth": 900, - "minHeight": 600, - "decorations": false, - "transparent": true, - "shadow": true - } - ], - "security": { - "csp": null, - "assetProtocol": { - "enable": true, - "scope": [ - "**" - ] - } - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "macOS": { - "bundleName": "Grimoire" - }, - "windows": { - "nsis": { - "installerHooks": "./windows/hooks.nsh", - "installMode": "currentUser" - }, - "wix": { - "fragmentPaths": [ - "./windows/fragments/library-path.wxs" - ] - } - } - } -} diff --git a/src-tauri/windows/fragments/library-path.wxs b/src-tauri/windows/fragments/library-path.wxs deleted file mode 100644 index c402d8c..0000000 --- a/src-tauri/windows/fragments/library-path.wxs +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - NOT Installed - - - diff --git a/src-tauri/windows/hooks.nsh b/src-tauri/windows/hooks.nsh deleted file mode 100644 index 335464d..0000000 --- a/src-tauri/windows/hooks.nsh +++ /dev/null @@ -1,31 +0,0 @@ -# Capture this file's directory at include time (macro expansion uses a different path). -!define GRIMOIRE_HOOK_DIR "${__FILEDIR__}" - -Var GrimoireLibraryDir -Var GrimoireLibraryArgs - -!macro NSIS_HOOK_POSTINSTALL - ; Default ebook library location for new installs. - StrCpy $GrimoireLibraryDir "$DOCUMENTS\Grimoire" - StrCpy $GrimoireLibraryArgs '-LibraryPath "$GrimoireLibraryDir"' - - ; Optional silent / scripted override: - ; Grimoire_x64-setup.exe /LIBRARYPATH=D:\Books - ${GetOptions} $CMDLINE "/LIBRARYPATH=" $0 - ${IfNot} ${Errors} - ${AndIf} $0 != "" - StrCpy $GrimoireLibraryDir $0 - StrCpy $GrimoireLibraryArgs '-LibraryPath "$GrimoireLibraryDir"' - ${ElseIf} $PassiveMode != 1 - ${AndIfNot} ${Silent} - ; Interactive installs can choose a different library folder. - StrCpy $GrimoireLibraryArgs "-Prompt" - ${EndIf} - - ; Pack and run the helper so JSON escaping stays correct for Windows paths. - SetOutPath "$PLUGINSDIR" - File "/oname=set-library-path.ps1" "${GRIMOIRE_HOOK_DIR}\set-library-path.ps1" - nsExec::ExecToLog 'powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$PLUGINSDIR\set-library-path.ps1" $GrimoireLibraryArgs' - Pop $0 - DetailPrint "Grimoire library path setup exit code: $0" -!macroend diff --git a/src-tauri/windows/set-library-path.ps1 b/src-tauri/windows/set-library-path.ps1 deleted file mode 100644 index 1fe1a69..0000000 --- a/src-tauri/windows/set-library-path.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -param( - [Parameter(Mandatory = $false)] - [string]$LibraryPath, - - [Parameter(Mandatory = $false)] - [switch]$Prompt -) - -$ErrorActionPreference = 'Stop' - -if ($Prompt) { - Add-Type -AssemblyName System.Windows.Forms - $dialog = New-Object System.Windows.Forms.FolderBrowserDialog - $dialog.Description = 'Choose where Grimoire should store your ebook library' - $dialog.SelectedPath = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'Grimoire' - $dialog.ShowNewFolderButton = $true - if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK -and $dialog.SelectedPath) { - $LibraryPath = $dialog.SelectedPath - } -} - -if ([string]::IsNullOrWhiteSpace($LibraryPath)) { - $LibraryPath = Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'Grimoire' -} - -New-Item -ItemType Directory -Force -Path $LibraryPath | Out-Null - -$cfgDir = Join-Path $env:APPDATA 'grimoire' -New-Item -ItemType Directory -Force -Path $cfgDir | Out-Null - -$cfgPath = Join-Path $cfgDir 'config.json' -if (Test-Path -LiteralPath $cfgPath) { - Write-Host "Grimoire config already exists at $cfgPath; leaving library_path unchanged." - exit 0 -} - -# UTF-8 without BOM. Windows PowerShell 5.1's Set-Content -Encoding utf8 adds a -# BOM that serde_json rejects ("expected value at line 1 column 1"). -$json = @{ library_path = $LibraryPath } | ConvertTo-Json -[System.IO.File]::WriteAllText($cfgPath, $json, [System.Text.UTF8Encoding]::new($false)) -Write-Host "Wrote initial Grimoire library path: $LibraryPath" diff --git a/src/app.css b/src/app.css deleted file mode 100644 index 76f0737..0000000 --- a/src/app.css +++ /dev/null @@ -1,458 +0,0 @@ -:root, -[data-theme='light'] { - --ink: #1c1814; - --ink-muted: #5c534a; - --paper: #f3ece0; - --paper-deep: #e6dccb; - --panel: #faf6ee; - --accent: #2f5d50; - --accent-hover: #244a40; - --danger: #8b3a2f; - --line: rgba(28, 24, 20, 0.12); - --shadow: 0 12px 40px rgba(28, 24, 20, 0.08); - --topbar-bg: rgba(250, 246, 238, 0.82); - --control-bg: #fffdf8; - --control-hover: rgba(28, 24, 20, 0.05); - --cover-from: #d9d0bf; - --cover-to: #c4b8a2; - --page-bg: - radial-gradient(circle at 12% 0%, rgba(47, 93, 80, 0.08), transparent 42%), - radial-gradient(circle at 88% 8%, rgba(139, 58, 47, 0.06), transparent 36%), - linear-gradient(180deg, #f7f1e6 0%, var(--paper) 48%, #ebe2d2 100%); - --btn-fg: #f8f5ef; - --radius: 10px; - --window-radius: 14px; - --font-display: 'Fraunces', 'Palatino Linotype', Palatino, Georgia, serif; - --font-body: 'Source Serif 4', Georgia, 'Times New Roman', serif; - --font-ui: 'Source Sans 3', 'Segoe UI', sans-serif; - --font-opendyslexic: 'OpenDyslexic', 'Source Sans 3', sans-serif; - color: var(--ink); - font-family: var(--font-body); - font-size: 16px; - line-height: 1.5; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; -} - -[data-theme='dark'] { - --ink: #efe6d8; - --ink-muted: #b0a493; - --paper: #1a1714; - --paper-deep: #12100e; - --panel: #24201c; - --accent: #7fb5a4; - --accent-hover: #9ac9ba; - --danger: #d88476; - --line: rgba(239, 230, 216, 0.12); - --shadow: 0 12px 40px rgba(0, 0, 0, 0.35); - --topbar-bg: rgba(26, 23, 20, 0.88); - --control-bg: #2c2823; - --control-hover: rgba(239, 230, 216, 0.08); - --cover-from: #3a342c; - --cover-to: #2a2520; - --page-bg: - radial-gradient(circle at 14% 0%, rgba(127, 181, 164, 0.1), transparent 40%), - radial-gradient(circle at 90% 10%, rgba(216, 132, 118, 0.08), transparent 34%), - linear-gradient(180deg, #1f1b17 0%, var(--paper) 50%, #141210 100%); - --btn-fg: #1a1714; -} - -* { - box-sizing: border-box; -} - -@font-face { - font-family: 'OpenDyslexic'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url('/fonts/opendyslexic/OpenDyslexic-Regular.woff2') format('woff2'); -} - -@font-face { - font-family: 'OpenDyslexic'; - font-style: italic; - font-weight: 400; - font-display: swap; - src: url('/fonts/opendyslexic/OpenDyslexic-Italic.woff2') format('woff2'); -} - -@font-face { - font-family: 'OpenDyslexic'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url('/fonts/opendyslexic/OpenDyslexic-Bold.woff2') format('woff2'); -} - -@font-face { - font-family: 'OpenDyslexic'; - font-style: italic; - font-weight: 700; - font-display: swap; - src: url('/fonts/opendyslexic/OpenDyslexic-BoldItalic.woff2') format('woff2'); -} - -html, -body { - margin: 0; - height: 100%; - overflow: hidden; - background: transparent; -} - -body { - min-height: 100%; - color: var(--ink); - transition: color 180ms ease; -} - -a { - color: inherit; - text-decoration: none; -} - -button, -input, -textarea { - font: inherit; -} - -button { - cursor: pointer; -} - -img { - max-width: 100%; - display: block; -} - -.app-shell { - height: 100vh; - display: grid; - grid-template-rows: auto 1fr; - background: var(--page-bg); - border-radius: var(--window-radius); - overflow: hidden; -} - -html.window-maximized .app-shell { - border-radius: 0; -} - -.shell-body { - min-height: 0; - overflow: auto; - overflow-x: hidden; - display: flex; - flex-direction: column; -} - -.topbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 0.75rem; - padding: 0.55rem 0.75rem 0.55rem 1.1rem; - border-bottom: 1px solid var(--line); - backdrop-filter: blur(8px); - background: var(--topbar-bg); - position: relative; - z-index: 20; -} - -.topbar[data-chrome='macos'] { - padding-left: 0.85rem; - gap: 0.65rem; -} - -.topbar[data-chrome='windows'] { - padding-right: 0; - padding-top: 0; - padding-bottom: 0; - min-height: 2.75rem; -} - -.topbar[data-chrome='linux'] { - padding-right: 0.35rem; -} - -.topbar-drag { - flex: 1 1 auto; - align-self: stretch; - min-width: 1rem; - cursor: default; -} - -.topbar-end { - display: flex; - align-items: center; - gap: 0.5rem; - flex: 0 0 auto; -} - -.topbar[data-chrome='windows'] .topbar-end { - padding-block: 0.55rem; -} - -.topbar[data-chrome='windows'] .brand { - padding-block: 0.55rem; -} - -.topbar .brand { - flex: 0 0 auto; -} - -.brand { - display: flex; - align-items: center; - gap: 0.5rem; - font-family: var(--font-display); - font-size: 1.55rem; - font-weight: 600; - letter-spacing: -0.02em; - filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.28)) drop-shadow(0 2px 6px rgba(0, 0, 0, 0.22)); -} - -.brand-logo { - display: block; - height: 1.85rem; - width: auto; -} - -.brand-text { - display: inline; -} - -.brand-dot { - color: var(--accent); -} - -.nav { - display: flex; - align-items: center; - gap: 0.35rem; - font-family: var(--font-ui); -} - -.nav a { - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 2.1rem; - min-height: 2.1rem; - padding: 0.45rem; - border-radius: 999px; - color: var(--ink-muted); - transition: background 160ms ease, color 160ms ease; -} - -.nav a:hover, -.nav a.active { - background: color-mix(in srgb, var(--accent) 16%, transparent); - color: var(--accent); -} - -.topbar [data-tooltip], -.library-menubar [data-tooltip] { - position: relative; -} - -.topbar [data-tooltip]::after, -.library-menubar [data-tooltip]::after { - content: attr(data-tooltip); - position: absolute; - top: calc(100% + 0.45rem); - left: 50%; - transform: translateX(-50%) translateY(-2px); - padding: 0.28rem 0.5rem; - border-radius: 6px; - border: 1px solid var(--line); - background: var(--panel); - color: var(--ink); - font-family: var(--font-ui); - font-size: 0.75rem; - font-weight: 600; - line-height: 1.2; - white-space: nowrap; - box-shadow: var(--shadow); - opacity: 0; - pointer-events: none; - transition: opacity 120ms ease, transform 120ms ease; - z-index: 40; -} - -.topbar [data-tooltip]:hover::after, -.topbar [data-tooltip]:focus-visible::after, -.library-menubar [data-tooltip]:hover:not(:disabled)::after, -.library-menubar [data-tooltip]:focus-visible:not(:disabled)::after { - opacity: 1; - transform: translateX(-50%) translateY(0); -} - -.page { - padding: 1.4rem clamp(1rem, 3vw, 2rem) 2.5rem; - width: min(1200px, 100%); - margin: 0 auto; -} - -.panel { - background: var(--panel); - border: 1px solid var(--line); - border-radius: var(--radius); - box-shadow: var(--shadow); -} - -.btn { - font-family: var(--font-ui); - border: 1px solid transparent; - border-radius: 8px; - padding: 0.55rem 1rem; - background: var(--accent); - color: var(--btn-fg); - transition: background 160ms ease, transform 120ms ease; - display: inline-flex; - align-items: center; - justify-content: center; - text-decoration: none; -} - -.btn:hover { - background: var(--accent-hover); -} - -.btn:active { - transform: translateY(1px); -} - -.btn.secondary { - background: transparent; - border-color: var(--line); - color: var(--ink); -} - -.btn.secondary:hover { - background: var(--control-hover); -} - -.btn.danger { - background: var(--danger); - color: #fff8f5; -} - -.btn:disabled { - opacity: 0.55; - cursor: not-allowed; -} - -.field { - display: flex; - flex-direction: column; - gap: 0.35rem; - font-family: var(--font-ui); -} - -.field input, -.field textarea { - border: 1px solid var(--line); - border-radius: 8px; - padding: 0.55rem 0.7rem; - background: var(--control-bg); - color: var(--ink); -} - -.muted { - color: var(--ink-muted); -} - -.error { - color: var(--danger); - font-family: var(--font-ui); - font-size: 0.95rem; -} - -.toolbar { - display: flex; - flex-wrap: wrap; - gap: 0.75rem; - align-items: center; - margin-bottom: 1.25rem; -} - -.search { - flex: 1 1 220px; - min-width: 180px; - font-family: var(--font-ui); - border: 1px solid var(--line); - border-radius: 8px; - padding: 0.55rem 0.8rem; - background: var(--control-bg); - color: var(--ink); -} - -.grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - gap: 1.1rem; -} - -.cover { - position: relative; - aspect-ratio: 2 / 3; - border-radius: 6px; - background: linear-gradient(160deg, var(--cover-from), var(--cover-to)); - overflow: hidden; - box-shadow: 0 8px 18px color-mix(in srgb, var(--ink) 12%, transparent); - transition: transform 180ms ease, box-shadow 180ms ease; -} - -.cover img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.book-card:hover .cover { - transform: translateY(-3px); - box-shadow: 0 14px 28px color-mix(in srgb, var(--ink) 18%, transparent); -} - -.book-card h3 { - font-family: var(--font-display); - font-size: 1rem; - margin: 0.65rem 0 0.2rem; - line-height: 1.25; -} - -.book-card p { - margin: 0; - font-size: 0.9rem; - color: var(--ink-muted); -} - -.empty { - padding: 3rem 1.5rem; - text-align: center; -} - -.empty h2 { - font-family: var(--font-display); - margin: 0 0 0.5rem; -} - -@keyframes fade-up { - from { - opacity: 0; - transform: translateY(8px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.fade-up { - animation: fade-up 280ms ease both; -} diff --git a/src/app.html b/src/app.html deleted file mode 100644 index 53c1a8e..0000000 --- a/src/app.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - Grimoire - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/src/epubjs.d.ts b/src/epubjs.d.ts deleted file mode 100644 index 4ef63a6..0000000 --- a/src/epubjs.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -declare module 'epubjs' { - const ePub: (url: string | ArrayBuffer, options?: Record) => { - renderTo: ( - element: HTMLElement | string, - options?: Record - ) => { - display: (target?: string) => Promise; - prev: () => Promise; - next: () => Promise; - on: (event: string, callback: (...args: unknown[]) => void) => void; - currentLocation: () => { - start?: { cfi?: string; displayed?: { page?: number; total?: number } }; - atEnd?: boolean; - }; - destroy?: () => void; - }; - locations: { - generate: (chars: number) => Promise; - locationFromCfi: (cfi: string) => number; - length: () => number; - }; - destroy?: () => void; - }; - export default ePub; -} diff --git a/src/lib/CoverPickerModal.svelte b/src/lib/CoverPickerModal.svelte deleted file mode 100644 index 2775453..0000000 --- a/src/lib/CoverPickerModal.svelte +++ /dev/null @@ -1,248 +0,0 @@ - - -
- -

- Pick the edition that matches your book. Covers come from Open Library. -

- - {#if error} -

{error}

- {/if} - -
- {#if loading} -

Loading covers…

- {:else if candidates.length === 0 && !error} -

No cover candidates found for this book.

- {:else} -
- {#each candidates as candidate (candidate.id)} - - {/each} -
- {/if} -
- - {#snippet footer()} - - - {/snippet} -
-
- - diff --git a/src/lib/LibraryFilters.svelte b/src/lib/LibraryFilters.svelte deleted file mode 100644 index 6521993..0000000 --- a/src/lib/LibraryFilters.svelte +++ /dev/null @@ -1,332 +0,0 @@ - - - - - diff --git a/src/lib/ThemeToggle.svelte b/src/lib/ThemeToggle.svelte deleted file mode 100644 index 65d263e..0000000 --- a/src/lib/ThemeToggle.svelte +++ /dev/null @@ -1,71 +0,0 @@ - - - - - diff --git a/src/lib/WindowControls.svelte b/src/lib/WindowControls.svelte deleted file mode 100644 index ef13bee..0000000 --- a/src/lib/WindowControls.svelte +++ /dev/null @@ -1,313 +0,0 @@ - - -{#if os === 'macos'} -
- - - -
-{:else} -
- - - -
-{/if} - - diff --git a/src/lib/api.ts b/src/lib/api.ts deleted file mode 100644 index e1bf007..0000000 --- a/src/lib/api.ts +++ /dev/null @@ -1,348 +0,0 @@ -import { invoke } from '@tauri-apps/api/core'; - -export type AppConfig = { - library_path: string | null; -}; - -export type LibraryStatus = { - open: boolean; - path: string | null; - kindle_convert_available: boolean; -}; - -export type BookSummary = { - id: number; - uuid: string; - title: string; - authors: string[]; - tags: string[]; - formats: string[]; - has_cover: boolean; - series: string | null; - series_index: number | null; - /** Null when the book has never been opened in the reader. */ - progress_percent: number | null; -}; - -export type FormatInfo = { - format: string; - filename: string; - size: number; -}; - -export type IdentifierInfo = { - type_name: string; - value: string; -}; - -export type BookDetail = { - id: number; - uuid: string; - title: string; - authors: string[]; - tags: string[]; - formats: FormatInfo[]; - has_cover: boolean; - series: string | null; - series_index: number | null; - comment: string; - identifiers: IdentifierInfo[]; - path: string; -}; - -export type BookUpdate = { - title: string; - authors: string[]; - tags: string[]; - series: string | null; - series_index: number | null; - comment: string; - identifiers?: IdentifierInfo[]; -}; - -export type ReaderFamily = - | 'kindle' - | 'kobo' - | 'pocketbook' - | 'remarkable' - | 'boox' - | 'tolino' - | 'nook' - | 'other'; - -export type ConnectionKind = 'mtp' | 'mount'; - -export type EbookReaderDevice = { - id: string; - connection: ConnectionKind; - location_id: number | null; - mount_path: string | null; - manufacturer: string; - product: string; - serial_number: string | null; - family: ReaderFamily; - family_label: string; - free_space_bytes: number | null; - display: string; - supports_send: boolean; - preferred_formats: string[]; - notes: string | null; -}; - -export type KindleDeviceInfo = { - location_id: number; - manufacturer: string; - product: string; - serial_number: string | null; - is_kindle: boolean; - free_space_bytes: number | null; - display: string; -}; - -export type TransferResult = { - remote_path: string; - device_serial: string; - format: string; -}; - -export type SyncRecord = { - book_id: number; - device_serial: string; - last_sent_at: string; - remote_path: string; - format: string; -}; - -export async function getConfig() { - return invoke('get_config'); -} - -export async function getLibraryStatus() { - return invoke('get_library_status'); -} - -export async function openSavedLibrary() { - return invoke('open_saved_library'); -} - -export async function createOrOpenLibrary(path: string) { - return invoke('create_or_open_library', { path }); -} - -export async function clearSavedLibrary() { - return invoke('clear_saved_library'); -} - -export async function listEbooks(query?: string) { - return invoke('list_ebooks', { query: query ?? null }); -} - -export async function importEbook(path: string) { - return invoke('import_ebook', { path }); -} - -export async function getEbook(id: number) { - return invoke('get_ebook', { id }); -} - -export async function updateEbook(id: number, update: BookUpdate) { - return invoke('update_ebook', { id, update }); -} - -export async function deleteEbook(id: number) { - return invoke('delete_ebook', { id }); -} - -export type BookBulkPatch = { - add_tags: string[]; - set_series: boolean; - series: string | null; - set_series_index: boolean; - series_index: number | null; - set_comment: boolean; - comment: string; -}; - -export type BulkFailure = { - id: number; - error: string; -}; - -export type BulkActionResult = { - updated: number; - failed: BulkFailure[]; -}; - -export async function deleteEbooks(ids: number[]) { - return invoke('delete_ebooks_batch', { ids }); -} - -export async function bulkPatchEbooks(ids: number[], patch: BookBulkPatch) { - return invoke('bulk_patch_ebooks_cmd', { ids, patch }); -} - -export async function getCover(id: number) { - return invoke('get_cover', { id }); -} - -export type EnrichmentResult = { - book: BookDetail; - updated_fields: string[]; - source: string; -}; - -export async function enrichEbookMetadata(id: number) { - return invoke('enrich_ebook_metadata', { id }); -} - -export type CoverCandidate = { - id: string; - volume_id: string | null; - title: string; - authors: string; - preview_url: string; - download_urls: string[]; -}; - -export async function listEbookCoverCandidates(id: number) { - return invoke('list_ebook_cover_candidates', { id }); -} - -export async function setEbookCoverFromUrls(id: number, urls: string[]) { - return invoke('set_ebook_cover_from_urls', { id, urls }); -} - -export type ReaderSource = { - book_id: number; - title: string; - authors: string[]; - format: string; - path: string; - available_formats: string[]; -}; - -export type ReadingProgress = { - book_id: number; - format: string; - location: string; - percent: number; - updated_at: string; -}; - -export type ReadingProgressUpdate = { - format: string; - location: string; - percent: number; -}; - -export async function getReaderSource(bookId: number, format?: string) { - return invoke('get_reader_source', { - bookId, - format: format ?? null - }); -} - -export async function getEbookReadingProgress(bookId: number) { - return invoke('get_ebook_reading_progress', { bookId }); -} - -export async function saveEbookReadingProgress(bookId: number, update: ReadingProgressUpdate) { - return invoke('save_ebook_reading_progress', { bookId, update }); -} - -export type ComicPageList = { - book_id: number; - format: string; - page_count: number; - pages: string[]; -}; - -export type ComicPage = { - book_id: number; - index: number; - page_count: number; - name: string; - data_url: string; -}; - -export async function listComicPages(bookId: number) { - return invoke('list_comic_pages', { bookId }); -} - -export async function getComicPage(bookId: number, index: number) { - return invoke('get_comic_page', { bookId, index }); -} - -export type ConvertResult = { - book: BookDetail; - format: string; - path: string; - converted: boolean; -}; - -export async function listEbookConvertTargets(bookId: number) { - return invoke('list_ebook_convert_targets', { bookId }); -} - -export async function convertEbookFormat(bookId: number, targetFormat: string) { - return invoke('convert_ebook_format', { bookId, targetFormat }); -} - -export async function listKindleDevices() { - return invoke('list_kindle_devices'); -} - -export async function listEbookReaders() { - return invoke('list_ebook_reader_devices'); -} - -export type DeviceBook = { - title: string; - format: string; - filename: string; - path: string; - size: number; - handle: number | null; -}; - -export type DeviceLibrary = { - device_id: string; - device_name: string; - connection: ConnectionKind; - books: DeviceBook[]; -}; - -export type DeviceBookTarget = { - path: string; - filename: string; - handle: number | null; -}; - -export type DeviceBulkFailure = { - path: string; - error: string; -}; - -export type DeviceBulkResult = { - removed: number; - failed: DeviceBulkFailure[]; -}; - -export async function scanEbookReader(deviceId: string) { - return invoke('scan_ebook_reader', { deviceId }); -} - -export async function deleteEbookReaderBooks(deviceId: string, books: DeviceBookTarget[]) { - return invoke('delete_ebook_reader_books', { deviceId, books }); -} - -export async function sendBookToKindle(bookId: number, deviceId: string) { - return invoke('send_book_to_kindle', { - bookId, - deviceId - }); -} - -export async function getSyncHistory() { - return invoke('get_sync_history'); -} diff --git a/src/lib/device-presence.svelte.ts b/src/lib/device-presence.svelte.ts deleted file mode 100644 index 6a857c1..0000000 --- a/src/lib/device-presence.svelte.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { listen, type UnlistenFn } from '@tauri-apps/api/event'; -import { listEbookReaders, type EbookReaderDevice } from '$lib/api'; - -export const EREADER_PRESENCE_EVENT = 'ereader-presence'; - -export type PresenceChange = 'arrived' | 'left' | 'snapshot'; - -export type EreaderPresenceEvent = { - change: PresenceChange; - label: string | null; - device_id: string | null; - devices: EbookReaderDevice[]; -}; - -export type DeviceNotice = { - kind: 'arrived' | 'left'; - message: string; -}; - -/** Shared connected-reader state for the whole app shell. */ -export const devicePresence = $state({ - devices: [] as EbookReaderDevice[], - notice: null as DeviceNotice | null, - listening: false -}); - -let unlisten: UnlistenFn | null = null; -let noticeTimer: ReturnType | null = null; -const listeners = new Set<(event: EreaderPresenceEvent) => void>(); - -function applyEvent(event: EreaderPresenceEvent) { - devicePresence.devices = event.devices; - - if (event.change === 'arrived' || event.change === 'left') { - const name = event.label?.trim() || 'eReader'; - const message = - event.change === 'arrived' ? `${name} connected` : `${name} disconnected`; - - if (noticeTimer) clearTimeout(noticeTimer); - devicePresence.notice = { kind: event.change, message }; - noticeTimer = setTimeout(() => { - devicePresence.notice = null; - noticeTimer = null; - }, 5000); - } - - for (const listener of listeners) { - listener(event); - } -} - -export function onEreaderPresence(listener: (event: EreaderPresenceEvent) => void) { - listeners.add(listener); - return () => listeners.delete(listener); -} - -export function dismissDeviceNotice() { - if (noticeTimer) clearTimeout(noticeTimer); - noticeTimer = null; - devicePresence.notice = null; -} - -export async function startDevicePresenceListener() { - if (devicePresence.listening) return; - devicePresence.listening = true; - - try { - unlisten = await listen(EREADER_PRESENCE_EVENT, (event) => { - applyEvent(event.payload); - }); - try { - devicePresence.devices = await listEbookReaders(); - } catch { - devicePresence.devices = []; - } - } catch (e) { - devicePresence.listening = false; - console.error('Could not listen for eReader presence', e); - } -} - -export function stopDevicePresenceListener() { - unlisten?.(); - unlisten = null; - devicePresence.listening = false; - listeners.clear(); - if (noticeTimer) clearTimeout(noticeTimer); - noticeTimer = null; -} diff --git a/src/lib/library-filters.ts b/src/lib/library-filters.ts deleted file mode 100644 index a1fe07b..0000000 --- a/src/lib/library-filters.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { BookSummary } from '$lib/api'; - -export type ReadingStatus = 'reading' | 'to-read' | 'read'; -export type FormatKind = 'novel' | 'comic'; - -export function bookStatus(b: BookSummary): ReadingStatus { - if (b.progress_percent == null) return 'to-read'; - if (b.progress_percent >= 100) return 'read'; - return 'reading'; -} - -export function bookFormatKind(b: BookSummary): FormatKind { - return b.formats.some((f) => { - const u = f.toUpperCase(); - return u === 'CBZ' || u === 'CBR'; - }) - ? 'comic' - : 'novel'; -} - -export function bookAuthorLabels(b: BookSummary): string[] { - const cleaned = b.authors.map((a) => a.trim()).filter(Boolean); - return cleaned.length ? cleaned : ['Unknown']; -} - -export function bookMatchesFilters( - b: BookSummary, - statuses: Set, - genres: Set, - formats: Set, - authors: Set -): boolean { - if (statuses.size > 0 && !statuses.has(bookStatus(b))) return false; - if (formats.size > 0 && !formats.has(bookFormatKind(b))) return false; - if (genres.size > 0 && !b.tags.some((t) => genres.has(t.trim()))) return false; - if (authors.size > 0 && !bookAuthorLabels(b).some((a) => authors.has(a))) return false; - return true; -} diff --git a/src/lib/reader-prefs.test.ts b/src/lib/reader-prefs.test.ts deleted file mode 100644 index d932e24..0000000 --- a/src/lib/reader-prefs.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { beforeEach, describe, expect, it } from 'vitest'; -import { - getReaderFontOption, - getStoredReaderFont, - isReaderFontId, - openDyslexicFontFaceCss, - setStoredReaderFont -} from './reader-prefs'; - -describe('reader-prefs', () => { - beforeEach(() => { - localStorage.clear(); - }); - - it('validates font ids', () => { - expect(isReaderFontId('serif')).toBe(true); - expect(isReaderFontId('opendyslexic')).toBe(true); - expect(isReaderFontId('comic-sans')).toBe(false); - expect(isReaderFontId(null)).toBe(false); - }); - - it('defaults stored font to serif', () => { - expect(getStoredReaderFont()).toBe('serif'); - }); - - it('persists a valid reader font', () => { - expect(setStoredReaderFont('opendyslexic')).toBe('opendyslexic'); - expect(getStoredReaderFont()).toBe('opendyslexic'); - }); - - it('returns publisher option with null css family', () => { - expect(getReaderFontOption('publisher').cssFamily).toBeNull(); - expect(getReaderFontOption('serif').cssFamily).toContain('Source Serif'); - }); - - it('builds OpenDyslexic @font-face CSS from an origin', () => { - const css = openDyslexicFontFaceCss('https://example.test'); - expect(css).toContain("font-family: 'OpenDyslexic'"); - expect(css).toContain( - "url('https://example.test/fonts/opendyslexic/OpenDyslexic-Regular.woff2')" - ); - }); -}); diff --git a/src/lib/reader-prefs.ts b/src/lib/reader-prefs.ts deleted file mode 100644 index 4285675..0000000 --- a/src/lib/reader-prefs.ts +++ /dev/null @@ -1,94 +0,0 @@ -export type ReaderFontId = 'publisher' | 'serif' | 'sans' | 'opendyslexic'; - -export type ReaderFontOption = { - id: ReaderFontId; - label: string; - description: string; - /** CSS font-family stack, or null to keep publisher fonts in EPUB. */ - cssFamily: string | null; -}; - -const STORAGE_KEY = 'grimoire-reader-font'; - -export const READER_FONTS: ReaderFontOption[] = [ - { - id: 'publisher', - label: 'Publisher default', - description: "Keep the ebook's own fonts when possible.", - cssFamily: null - }, - { - id: 'serif', - label: 'Source Serif', - description: 'Grimoire’s reading serif.', - cssFamily: "'Source Serif 4', Georgia, 'Times New Roman', serif" - }, - { - id: 'sans', - label: 'Source Sans', - description: 'Clean sans-serif for long stretches of text.', - cssFamily: "'Source Sans 3', 'Segoe UI', system-ui, sans-serif" - }, - { - id: 'opendyslexic', - label: 'OpenDyslexic', - description: 'Shapes designed to help with some symptoms of dyslexia.', - cssFamily: "'OpenDyslexic', 'Source Sans 3', sans-serif" - } -]; - -export function isReaderFontId(value: string | null | undefined): value is ReaderFontId { - return READER_FONTS.some((font) => font.id === value); -} - -export function getReaderFontOption(id: ReaderFontId): ReaderFontOption { - return READER_FONTS.find((font) => font.id === id) ?? READER_FONTS[0]; -} - -export function getStoredReaderFont(): ReaderFontId { - if (typeof localStorage === 'undefined') return 'serif'; - const value = localStorage.getItem(STORAGE_KEY); - return isReaderFontId(value) ? value : 'serif'; -} - -export function setStoredReaderFont(id: ReaderFontId): ReaderFontId { - if (typeof localStorage !== 'undefined') { - localStorage.setItem(STORAGE_KEY, id); - } - return id; -} - -/** Absolute @font-face CSS for injecting into EPUB iframes. */ -export function openDyslexicFontFaceCss(origin = typeof window !== 'undefined' ? window.location.origin : '') { - const base = `${origin}/fonts/opendyslexic`; - return ` -@font-face { - font-family: 'OpenDyslexic'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url('${base}/OpenDyslexic-Regular.woff2') format('woff2'); -} -@font-face { - font-family: 'OpenDyslexic'; - font-style: italic; - font-weight: 400; - font-display: swap; - src: url('${base}/OpenDyslexic-Italic.woff2') format('woff2'); -} -@font-face { - font-family: 'OpenDyslexic'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url('${base}/OpenDyslexic-Bold.woff2') format('woff2'); -} -@font-face { - font-family: 'OpenDyslexic'; - font-style: italic; - font-weight: 700; - font-display: swap; - src: url('${base}/OpenDyslexic-BoldItalic.woff2') format('woff2'); -} -`.trim(); -} diff --git a/src/lib/theme.test.ts b/src/lib/theme.test.ts deleted file mode 100644 index aaa6110..0000000 --- a/src/lib/theme.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { beforeEach, describe, expect, it } from 'vitest'; -import { - applyTheme, - cycleTheme, - getStoredTheme, - resolveTheme, - setThemePreference, - themeLabel -} from './theme'; - -describe('theme', () => { - beforeEach(() => { - localStorage.clear(); - document.documentElement.removeAttribute('data-theme'); - document.documentElement.style.colorScheme = ''; - }); - - it('defaults to dark when nothing is stored', () => { - expect(getStoredTheme()).toBe('dark'); - }); - - it('reads a stored preference', () => { - localStorage.setItem('grimoire-theme', 'light'); - expect(getStoredTheme()).toBe('light'); - localStorage.setItem('grimoire-theme', 'system'); - expect(getStoredTheme()).toBe('system'); - }); - - it('ignores invalid stored values', () => { - localStorage.setItem('grimoire-theme', 'neon'); - expect(getStoredTheme()).toBe('dark'); - }); - - it('resolves explicit preferences without matchMedia', () => { - expect(resolveTheme('light')).toBe('light'); - expect(resolveTheme('dark')).toBe('dark'); - }); - - it('cycles system → light → dark → system', () => { - expect(cycleTheme('system')).toBe('light'); - expect(cycleTheme('light')).toBe('dark'); - expect(cycleTheme('dark')).toBe('system'); - }); - - it('labels preferences for the UI', () => { - expect(themeLabel('light')).toBe('Light'); - expect(themeLabel('dark')).toBe('Dark'); - expect(themeLabel('system')).toBe('System'); - }); - - it('persists preference and applies dataset theme', () => { - const resolved = setThemePreference('light'); - expect(resolved).toBe('light'); - expect(localStorage.getItem('grimoire-theme')).toBe('light'); - expect(document.documentElement.dataset.theme).toBe('light'); - expect(document.documentElement.style.colorScheme).toBe('light'); - }); - - it('applyTheme updates the document without requiring storage', () => { - expect(applyTheme('dark')).toBe('dark'); - expect(document.documentElement.dataset.theme).toBe('dark'); - }); -}); diff --git a/src/lib/theme.ts b/src/lib/theme.ts deleted file mode 100644 index 2319196..0000000 --- a/src/lib/theme.ts +++ /dev/null @@ -1,45 +0,0 @@ -export type ThemePreference = 'light' | 'dark' | 'system'; -export type ResolvedTheme = 'light' | 'dark'; - -const STORAGE_KEY = 'grimoire-theme'; - -export function getStoredTheme(): ThemePreference { - if (typeof localStorage === 'undefined') return 'dark'; - const value = localStorage.getItem(STORAGE_KEY); - if (value === 'light' || value === 'dark' || value === 'system') return value; - return 'dark'; -} - -export function resolveTheme(preference: ThemePreference): ResolvedTheme { - if (preference === 'light' || preference === 'dark') return preference; - if (typeof window === 'undefined' || !window.matchMedia) return 'dark'; - return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; -} - -export function applyTheme(preference: ThemePreference): ResolvedTheme { - const resolved = resolveTheme(preference); - if (typeof document !== 'undefined') { - document.documentElement.dataset.theme = resolved; - document.documentElement.style.colorScheme = resolved; - } - return resolved; -} - -export function setThemePreference(preference: ThemePreference): ResolvedTheme { - if (typeof localStorage !== 'undefined') { - localStorage.setItem(STORAGE_KEY, preference); - } - return applyTheme(preference); -} - -export function cycleTheme(current: ThemePreference): ThemePreference { - if (current === 'system') return 'light'; - if (current === 'light') return 'dark'; - return 'system'; -} - -export function themeLabel(preference: ThemePreference): string { - if (preference === 'light') return 'Light'; - if (preference === 'dark') return 'Dark'; - return 'System'; -} diff --git a/src/lib/ui/ActionButton.svelte b/src/lib/ui/ActionButton.svelte deleted file mode 100644 index 4ae3539..0000000 --- a/src/lib/ui/ActionButton.svelte +++ /dev/null @@ -1,70 +0,0 @@ - - - - - diff --git a/src/lib/ui/Button.svelte b/src/lib/ui/Button.svelte deleted file mode 100644 index ee52647..0000000 --- a/src/lib/ui/Button.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - - diff --git a/src/lib/ui/Checkbox.svelte b/src/lib/ui/Checkbox.svelte deleted file mode 100644 index 301f8c3..0000000 --- a/src/lib/ui/Checkbox.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - - - - diff --git a/src/lib/ui/Dialog.svelte b/src/lib/ui/Dialog.svelte deleted file mode 100644 index 0de18d3..0000000 --- a/src/lib/ui/Dialog.svelte +++ /dev/null @@ -1,144 +0,0 @@ - - -{#if open} - -{/if} - - diff --git a/src/lib/ui/SearchField.svelte b/src/lib/ui/SearchField.svelte deleted file mode 100644 index 0683f5b..0000000 --- a/src/lib/ui/SearchField.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - { - syncValue(event); - onchange?.(event); - }} - {...rest} -/> diff --git a/src/lib/ui/TextField.svelte b/src/lib/ui/TextField.svelte deleted file mode 100644 index 5ed7c9b..0000000 --- a/src/lib/ui/TextField.svelte +++ /dev/null @@ -1,100 +0,0 @@ - - -{#if label} - -{/if} -{#if multiline} - -{:else} - { - syncValue(event); - onchange?.(event); - }} - {...rest} - /> -{/if} - - diff --git a/src/lib/view-mode.test.ts b/src/lib/view-mode.test.ts deleted file mode 100644 index 3731151..0000000 --- a/src/lib/view-mode.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { beforeEach, describe, expect, it } from 'vitest'; -import { getStoredViewMode, setStoredViewMode } from './view-mode'; - -describe('view-mode', () => { - beforeEach(() => { - localStorage.clear(); - }); - - it('defaults to grid', () => { - expect(getStoredViewMode()).toBe('grid'); - }); - - it('persists list mode', () => { - expect(setStoredViewMode('list')).toBe('list'); - expect(getStoredViewMode()).toBe('list'); - }); - - it('ignores invalid stored values', () => { - localStorage.setItem('grimoire-device-view', 'masonry'); - expect(getStoredViewMode()).toBe('grid'); - }); -}); diff --git a/src/lib/view-mode.ts b/src/lib/view-mode.ts deleted file mode 100644 index 7c40f0e..0000000 --- a/src/lib/view-mode.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type ViewMode = 'grid' | 'list'; - -const STORAGE_KEY = 'grimoire-device-view'; - -export function getStoredViewMode(): ViewMode { - if (typeof localStorage === 'undefined') return 'grid'; - const value = localStorage.getItem(STORAGE_KEY); - if (value === 'grid' || value === 'list') return value; - return 'grid'; -} - -export function setStoredViewMode(mode: ViewMode): ViewMode { - if (typeof localStorage !== 'undefined') { - localStorage.setItem(STORAGE_KEY, mode); - } - return mode; -} diff --git a/src/lib/window-chrome.ts b/src/lib/window-chrome.ts deleted file mode 100644 index fe7fa3d..0000000 --- a/src/lib/window-chrome.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type ChromeOs = 'macos' | 'windows' | 'linux'; - -/** OS for window-chrome placement and styling. Sync-safe for SSR. */ -export function detectChromeOs(): ChromeOs { - if (typeof navigator === 'undefined') return 'macos'; - - const platform = navigator.platform?.toLowerCase() ?? ''; - const ua = navigator.userAgent.toLowerCase(); - - if (platform.includes('mac') || ua.includes('mac os')) return 'macos'; - if (platform.includes('win') || ua.includes('windows')) return 'windows'; - return 'linux'; -} - -export function windowControlsOnLeft(os: ChromeOs = detectChromeOs()) { - return os === 'macos'; -} diff --git a/src/lib/windows.test.ts b/src/lib/windows.test.ts deleted file mode 100644 index d2e171e..0000000 --- a/src/lib/windows.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; - -vi.mock('@tauri-apps/api/webviewWindow', () => ({ - WebviewWindow: class { - static async getByLabel() { - return null; - } - } -})); - -import { readerWindowLabel } from './windows'; - -describe('windows', () => { - it('builds a stable reader window label', () => { - expect(readerWindowLabel(42)).toBe('reader-42'); - }); -}); diff --git a/src/lib/windows.ts b/src/lib/windows.ts deleted file mode 100644 index f76fbab..0000000 --- a/src/lib/windows.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; - -export function readerWindowLabel(bookId: number) { - return `reader-${bookId}`; -} - -/** Open (or focus) the in-app reader for a book. */ -export async function openReaderWindow(bookId: number) { - const label = readerWindowLabel(bookId); - const existing = await WebviewWindow.getByLabel(label); - if (existing) { - await existing.setFocus(); - return; - } - - const reader = new WebviewWindow(label, { - url: `/read/${bookId}`, - title: 'Reading', - width: 980, - height: 860, - minWidth: 640, - minHeight: 520, - center: true, - resizable: true, - focus: true, - decorations: false, - transparent: true, - shadow: true - }); - - await new Promise((resolve, reject) => { - reader.once('tauri://created', () => resolve()); - reader.once('tauri://error', (event) => { - reject(new Error(String(event.payload ?? 'Could not open reader window'))); - }); - }); -} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte deleted file mode 100644 index 1239993..0000000 --- a/src/routes/+layout.svelte +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - Grimoire - - -{#if isAuxWindow} - {@render children()} -{:else} -
-
- {#if controlsLeft} - - {/if} - - - Grimoire. - - - - {#if !controlsLeft} - - {/if} -
- - {#if devicePresence.notice} -
-

{devicePresence.notice.message}

- -
- {/if} - -
- {#if booting} -
-

Opening library…

-
- {:else if bootError} -
-

{bootError}

-
- {:else} - {@render children()} - {/if} -
-
-{/if} - - diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts deleted file mode 100644 index 9d24899..0000000 --- a/src/routes/+layout.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Tauri doesn't have a Node.js server to do proper SSR -// so we use adapter-static with a fallback to index.html to put the site in SPA mode -// See: https://svelte.dev/docs/kit/single-page-apps -// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info -export const ssr = false; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte deleted file mode 100644 index ece9f56..0000000 --- a/src/routes/+page.svelte +++ /dev/null @@ -1,1432 +0,0 @@ - - - - -
- {#if dragActive} -
-
-

Drop to import

-

- {#if status?.open} - EPUB, PDF, MOBI, AZW3, TXT, CBZ, or CBR (AZW3 is Kindle send; comics and DRM-free - MOBI can be read) - {:else} - Open a library folder first, then drop books here - {/if} -

-
-
- {/if} - - {#if loading} -

Opening library…

- {:else if !status?.open} -
-

Your library starts here

-

- If you already chose a folder in Settings it should open automatically. If you still - see this, set the library in Settings. Grimoire stores - metadata.db and your book files locally, Calibre-style. -

- -

- Open Settings -

- {#if error} -

{error}

- {/if} -
- {:else} -
-
- - -
-
- -
- {#if filtersOpen} - - {/if} -
- {#if importMessage} -

{importMessage}

- {/if} - {#if error} -

{error}

- {/if} - - {#if selectMode && selectedCount > 0} - - {/if} - - {#if books.length === 0} -
-

No books yet

-

- Import EPUB, PDF, TXT, CBZ/CBR comics, or DRM-free MOBI to read in Grimoire. AZW3 can be - imported for Kindle send; Amazon store AZW3 is usually DRM-protected and is not opened - in the reader. -

-
- {:else if filteredBooks.length === 0} -
-

No books match these filters

-

- {#if filtersActive} - Try clearing some filters, or adjust your search. - {:else} - Nothing to show. - {/if} -

-
- {:else} -
- {#each librarySections as section (section.id)} - {#if section.books.length > 0} -
-

- {section.title} - {section.books.length} -

-
- {#each section.books as book (book.id)} - {@render bookCard(book)} - {/each} -
-
- {/if} - {/each} -
- {/if} -
-
- {/if} - - {#snippet bookCard(book: BookSummary)} - {#if selectMode} - - {:else} - - {/if} - {/snippet} - - {#snippet bookCover(book: BookSummary)} -
- {#if covers[book.id]} - - {:else} -
- {book.title.slice(0, 1)} -
- {/if} - {#if book.progress_percent != null && book.progress_percent < 100} - - {Math.round(book.progress_percent)}% - - {/if} -
- {/snippet} - - (bulkEditOpen = false)} - > -

Only checked fields are applied. Tags are added (not replaced).

-
- -
-
- Apply series - -
-
- Apply series index - -
-
- Replace notes - -
- {#snippet footer()} - - - {/snippet} -
- - {#if contextMenu && !selectMode} - {@const menu = contextMenu} - - - {/if} -
- - diff --git a/src/routes/book/[id]/+page.svelte b/src/routes/book/[id]/+page.svelte deleted file mode 100644 index c15528d..0000000 --- a/src/routes/book/[id]/+page.svelte +++ /dev/null @@ -1,588 +0,0 @@ - - -
-

← Library

- - {#if error} -

{error}

- {/if} - {#if message} -

{message}

- {/if} - - {#if !book} -

Loading…

- {:else} -
-
-
- {#if cover} - - {:else} -
{book.title.slice(0, 1)}
- {/if} -
- -
- -
-
- -
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
- -
-

- {#if readableFormats.length || kindleFormats.length} - {#if readableFormats.length} - Readable: {readableFormats.join(', ')} - {/if} - {#if readableFormats.length && kindleFormats.length} - · - {/if} - {#if kindleFormats.length} - Kindle: {kindleFormats.join(', ')} - {/if} - {:else} - Formats: None - {/if} -

- {#if kindleOnly} -

- AZW3 is not opened in the in-app reader. Kindle Store books are usually - DRM-protected, so Grimoire cannot decrypt them. Use Send to device for Kindle, - or add a DRM-free EPUB, PDF, TXT, or MOBI to read here. See Settings for details. -

- {/if} - {#if otherIdentifiers.length} -

- Other identifiers: {otherIdentifiers - .map((id) => `${id.type_name}: ${id.value}`) - .join(' · ')} -

- {/if} -
- -
- {#if canRead} - - {/if} - - - - -
- {#if convertTargets.length} -
- Convert to - {#each convertTargets as target (target)} - - {/each} -
-

- Native conversion for Kindle sideload (EPUB → MOBI/AZW3) and TXT → EPUB. DRM-free - MOBI can be opened in the reader (converted to EPUB for display). Amazon store AZW3 - is usually DRM-locked and is not opened here. -

- {/if} -

Only fills empty fields and missing covers from Open Library.

- -
-

Send to device

-

- Detected eReaders appear here. Kindle USB/MTP send converts EPUB to MOBI - natively when needed. -

- {#if devices.length === 0} -

- No eReader detected yet. Plug in over USB; Grimoire detects devices automatically. - You can also refresh. -

- - {:else} -
    - {#each devices as device (device.id)} -
  • -
    - {device.product} - {device.family_label} · {device.connection === 'mtp' - ? 'USB/MTP' - : 'Mounted'} -
    - -
  • - {/each} -
- {/if} -
-
-
- - (coverPickerOpen = false)} - onapplied={onCoverApplied} - /> - {/if} -
- - diff --git a/src/routes/devices/+page.svelte b/src/routes/devices/+page.svelte deleted file mode 100644 index f66e191..0000000 --- a/src/routes/devices/+page.svelte +++ /dev/null @@ -1,306 +0,0 @@ - - -
-
-
-

Devices

-

- Detects Kindles, Kobos, BOOX, PocketBook, reMarkable, Tolino, and Nook over USB/MTP or - mounted volumes. -

-
- -
- - {#if error} -

{error}

- {/if} - -
-

Connected eReaders

- {#if loading} -

Scanning MTP devices and mounted volumes…

- {:else if devices.length === 0} -

- No eReaders found yet. Grimoire watches for USB/MTP and mounted volumes automatically; - use Refresh if a device does not appear. On macOS, quit Android File Transfer and stop - ptpcamerad if an MTP device stays locked. -

- {:else} -
    - {#each devices as device (device.id)} -
  • -
    -
    - {device.product} - {device.family_label} -
    -
    {device.display}
    -
    - {connectionLabel(device)} · {formatBytes(device.free_space_bytes)} · prefers - {device.preferred_formats.join(', ')} -
    - {#if device.notes} -
    {device.notes}
    - {/if} - {#if !device.supports_send} -
    - Detected. USB send for this family is not wired yet (Kindle MTP is supported). -
    - {/if} -
    -
    - - - {device.supports_send ? 'Send ready' : 'Detected'} - -
    -
  • - {/each} -
- {/if} - - {#if status && !status.kindle_convert_available} -

- Native Kindle conversion is unavailable in this build. EPUB-only titles need an AZW3, - MOBI, or PDF format before USB send. -

- {/if} -
- -
-

Recent transfers

- {#if !status?.open} -

Open a library to see sync history.

- {:else if history.length === 0} -

No transfers recorded yet. Send a book from its detail page.

- {:else} - - - - - - - - - - - - {#each history as row (row.book_id + row.device_serial + row.last_sent_at)} - - - - - - - - {/each} - -
BookDeviceFormatWhenPath
#{row.book_id}{row.device_serial}{row.format}{row.last_sent_at}{row.remote_path}
- {/if} -
-
- - diff --git a/src/routes/devices/library/+page.svelte b/src/routes/devices/library/+page.svelte deleted file mode 100644 index 4c9fbf8..0000000 --- a/src/routes/devices/library/+page.svelte +++ /dev/null @@ -1,750 +0,0 @@ - - - - -
-

← Devices

- -
-
-

{device?.product ?? 'Device library'}

- {#if device} -

- {device.family_label} · {device.connection === 'mtp' ? 'USB/MTP' : 'Mounted volume'} - {#if library} - · {library.books.length} - {library.books.length === 1 ? 'ebook' : 'ebooks'} - {/if} -

- {/if} -
-
- -
- setView('grid')} - quiet={false} - > - Grid - - setView('list')} - quiet={false} - > - List - -
- - -
-
- - {#if message} -

{message}

- {/if} - {#if error} -

{error}

- {#if !device} - - {/if} - {/if} - - {#if selectMode && selectedCount > 0} - - {/if} - -
- {#if loading && !library} -

Scanning the device for ebooks…

- {:else if library && library.books.length === 0} -
-

No ebooks found

-

- Grimoire looked for EPUB, PDF, AZW3, MOBI, and similar files under the device storage. -

-
- {:else if library && filteredBooks.length === 0} -

No books match "{query}".

- {:else if library && viewMode === 'grid'} -
- {#each filteredBooks as book (bookKey(book))} - {#if selectMode} - - {:else} -
openContextMenu(e, book)} - > -
-
- {book.title.slice(0, 1)} - {book.format} -
-
-

{book.title}

-

{book.format} · {formatBytes(book.size)}

-

{book.path}

-
- {/if} - {/each} -
- {:else if library} - - - - {#if selectMode} - - {/if} - - - - - - - - {#each filteredBooks as book (bookKey(book))} - {#if selectMode} - toggleSelected(bookKey(book))} - > - - - - - - - {:else} - openContextMenu(e, book)} - > - - - - - - {/if} - {/each} - -
TitleFormatSizePath
- e.stopPropagation()} - /> - {book.title}{book.format}{formatBytes(book.size)}{book.path}
{book.title}{book.format}{formatBytes(book.size)}{book.path}
- {/if} -
- - {#if contextMenu && !selectMode} - {@const menu = contextMenu} - - - {/if} -
- - diff --git a/src/routes/read/[id]/+page.svelte b/src/routes/read/[id]/+page.svelte deleted file mode 100644 index 173ab28..0000000 --- a/src/routes/read/[id]/+page.svelte +++ /dev/null @@ -1,744 +0,0 @@ - - - - {source?.title ? `${source.title} · Grimoire` : 'Reading · Grimoire'} - - - - -
-
- {#if controlsLeft} - - {/if} - -
- {#if (source?.format === 'EPUB' || (source && isComicFormat(source.format))) && ready} - - - {/if} - {Math.round(percent)}% -
- {#if !controlsLeft} - - {/if} -
- - {#if error} -
-

{error}

-
- {:else if loading} -
-

Opening book…

-
- {:else if source?.format === 'EPUB'} -
- {:else if source?.format === 'TXT'} -
-
{textContent || 'Loading text…'}
-
- {:else if source?.format === 'PDF' && pdfUrl} - -

- PDF page tracking is limited. Grimoire remembers that you opened this file. -

- {:else if source && isComicFormat(source.format)} -
- {#if comicDataUrl} - {comicPageName - {:else} -

Loading page…

- {/if} -
- {/if} -
- - diff --git a/src/routes/read/[id]/+page.ts b/src/routes/read/[id]/+page.ts deleted file mode 100644 index a3d1578..0000000 --- a/src/routes/read/[id]/+page.ts +++ /dev/null @@ -1 +0,0 @@ -export const ssr = false; diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte deleted file mode 100644 index a53d418..0000000 --- a/src/routes/settings/+page.svelte +++ /dev/null @@ -1,338 +0,0 @@ - - -
-
-
-

Settings

-

Choose a library folder to open automatically when Grimoire starts.

-
-
- - {#if error} -

{error}

- {/if} - {#if message} -

{message}

- {/if} - -
-

Library location

-

- Grimoire stores metadata.db and book files in this folder. The path is saved in - your OS config directory and loaded on every launch. -

- -
- Current path - {#if status?.path} - {status.path} -

- {status.open ? 'Open and ready' : 'Saved, but not open right now'} -

- {:else} -

No library path saved yet.

- {/if} -
- -
- - {#if status?.path} - - {/if} -
-
- -
-

Reader font

-

- Applies to EPUB and TXT reading. OpenDyslexic is bundled in Grimoire for offline use - (SIL Open Font License). -

- -
- Reader font - {#each READER_FONTS as font (font.id)} - - {/each} -
- -

- The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs. -

-
- -
-

Formats and reading

-

- The in-app reader opens EPUB, PDF, TXT, - CBZ/CBR comics, and DRM-free MOBI (MOBI is - converted to EPUB for display). -

-

- AZW3 can still be imported and sent to a Kindle. It is not opened in the - Grimoire reader. Kindle Store purchases are usually DRM-protected, so - Grimoire cannot decrypt them. -

-

- For reading in Grimoire, prefer a DRM-free EPUB, PDF, TXT, CBZ/CBR, or MOBI. For Kindle - reading, keep or convert to AZW3/MOBI and use Send to device. -

-
- -
-

Metadata

-

- Missing details and covers are looked up on - Open Library. No API - key is required. -

-
-
- - diff --git a/static/favicon.svg b/static/favicon.svg deleted file mode 100644 index 6e87f22..0000000 --- a/static/favicon.svg +++ /dev/null @@ -1,1061 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/static/svelte.svg b/static/svelte.svg deleted file mode 100644 index c5e0848..0000000 --- a/static/svelte.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/tauri.svg b/static/tauri.svg deleted file mode 100644 index 31b62c9..0000000 --- a/static/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/static/vite.svg b/static/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/static/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/svelte.config.js b/svelte.config.js deleted file mode 100644 index a7830ea..0000000 --- a/svelte.config.js +++ /dev/null @@ -1,18 +0,0 @@ -// Tauri doesn't have a Node.js server to do proper SSR -// so we use adapter-static with a fallback to index.html to put the site in SPA mode -// See: https://svelte.dev/docs/kit/single-page-apps -// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info -import adapter from "@sveltejs/adapter-static"; -import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - preprocess: vitePreprocess(), - kit: { - adapter: adapter({ - fallback: "index.html", - }), - }, -}; - -export default config; diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index f4d0a0e..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "bundler" - } - // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias - // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files - // - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes - // from the referenced tsconfig.json - TypeScript does not merge them in -} diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index 218424c..0000000 --- a/vite.config.js +++ /dev/null @@ -1,37 +0,0 @@ -import { defineConfig } from "vite"; -import { sveltekit } from "@sveltejs/kit/vite"; -// @ts-expect-error type error without @types/node package -import process from "node:process"; -const host = process.env.TAURI_DEV_HOST; - -// https://vite.dev/config/ -export default defineConfig(() => ({ - plugins: [sveltekit()], - - // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` - // - // 1. prevent Vite from obscuring rust errors - clearScreen: false, - // 2. tauri expects a fixed port, fail if that port is not available - server: { - port: 1420, - strictPort: true, - host: host || "127.0.0.1", - hmr: host - ? { - protocol: "ws", - host, - port: 1421, - } - : undefined, - watch: { - // 3. tell Vite to ignore watching `src-tauri` - ignored: ["**/src-tauri/**"], - }, - }, - - test: { - include: ["src/**/*.{test,spec}.{js,ts}"], - environment: "happy-dom", - }, -}));