mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-04 12:01:52 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c42b27edb | |||
| 44ae19ff64 | |||
| 23bac3cff5 | |||
| cc05fec59d | |||
| 1206d5889d | |||
| 273e9c184d | |||
| 3f9baca85e | |||
| 0497a54928 | |||
| 5db2008fae |
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: yaak-changelog
|
||||
description: Create or edit Yaak changelogs. Beta and draft prerelease changelogs live only in the GitHub release body; stable release changelogs live in `src/content/changelog/YYYYMMDD_VERSION/`. Use when Codex needs to update a beta GitHub release body, generate a stable website changelog from beta releases, update `_release.yaml` or `_intro.md`, expand major entries into markdown files, or preserve Yaak's changelog writing style.
|
||||
---
|
||||
|
||||
# Yaak Changelog
|
||||
|
||||
Use this skill to create Yaak changelogs in the correct place:
|
||||
|
||||
- Beta or draft prerelease changelogs live only on the GitHub release.
|
||||
- Stable release changelogs live in website files under `src/content/changelog/YYYYMMDD_VERSION/`.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Identify the target release.
|
||||
- If the target tag contains `-beta` or the GitHub release is a draft prerelease, update the GitHub release body only. Do not create or edit `src/content/changelog/` files for beta releases.
|
||||
- For a new stable changelog, gather all beta release notes for that version since the previous stable release, then create or edit website changelog files.
|
||||
- Prefer `gh api` or GitHub release pages. If network access is restricted, request permission before querying GitHub.
|
||||
- Extract PR numbers and `yaak.app/feedback` URLs while fetching. If most bullets do not include PR references, fetch again with a more specific prompt.
|
||||
- Fetch PR authors when generating or revising release notes. Include contributor attribution for non-`@gschier` PR authors.
|
||||
|
||||
2. Parse release bullets.
|
||||
- Treat each release-note bullet as one changelog entry.
|
||||
- Skip dependency-only, generated, build-only, test-only, CI-only, and internal maintenance bullets unless they have a clear user-facing impact that can be described in user terms.
|
||||
- For stable website changelogs, skip bullets prefixed with `[beta-only]`.
|
||||
- Preserve the entry wording closely. Remove wrapping quotes from titles.
|
||||
- Map categories to `feature`, `fix`, `improvement`, or `breaking`.
|
||||
- Convert `#NNN` into `https://github.com/mountain-loop/yaak/pull/NNN`.
|
||||
|
||||
3. For beta or draft prerelease changelogs, update the GitHub release.
|
||||
- Keep the changelog in the GitHub release body. Do not create a website changelog directory.
|
||||
- Do not add a changelog badge or link to `yaak.app/changelog/VERSION` for beta releases.
|
||||
- Prefer concise bullets with PR links and feedback links when available.
|
||||
- When a bullet has a feedback URL, wrap the changelog item text itself in the feedback link, then put the PR link after it. Example: `- [Fixed request history timestamps](https://yaak.app/feedback/posts/request-history-time-stamp) in [#492](https://github.com/mountain-loop/yaak/pull/492)`.
|
||||
- Append `by [@handle](https://github.com/handle)` to PR-backed bullets authored by external contributors. Do not append `by @gschier` for `@gschier` PRs.
|
||||
- Include a `**Full Changelog**` comparison link using the previous beta tag when it exists, or the previous stable tag for `beta.1`.
|
||||
- Use `gh release edit TAG --repo mountain-loop/yaak --notes-file ...` or the GitHub release API to update the draft/prerelease body.
|
||||
- Stop after verifying the GitHub release body. The website checks below do not apply.
|
||||
|
||||
4. For stable website changelogs, create or edit the release directory.
|
||||
- Path format: `src/content/changelog/YYYYMMDD_VERSION/`.
|
||||
- For a new release, use today's date for `YYYYMMDD`.
|
||||
- For an existing release, keep the original directory date.
|
||||
- Do not create changelog directories for beta releases.
|
||||
|
||||
5. Write `_release.yaml`.
|
||||
- Include `draft`, optional `title`, `summary`, `image`, `youtube`, and `entries`.
|
||||
- Keep minor items as quick entries without `content`.
|
||||
- Use `content` only when an entry needs its own markdown section.
|
||||
|
||||
```yaml
|
||||
title: "What's New in 2026.1.0"
|
||||
summary: "Brief overview of the most important additions and fixes"
|
||||
draft: true
|
||||
entries:
|
||||
- title: "Request debugging"
|
||||
category: feature
|
||||
pr: "https://github.com/mountain-loop/yaak/pull/123"
|
||||
feedback: "https://feedback.yaak.app/p/request-debugging"
|
||||
content: "request-debugging.md"
|
||||
- title: "Fix broken cookie clearing"
|
||||
category: fix
|
||||
pr: "https://github.com/mountain-loop/yaak/pull/124"
|
||||
```
|
||||
|
||||
6. Expand major entries.
|
||||
- Expand 3 to 6 major items when enough context exists.
|
||||
- Create slugified markdown files and reference them with `content`.
|
||||
- Read the related PR before writing expanded content.
|
||||
- Add emoji prefixes only for expanded entry titles if it helps distinguish major sections.
|
||||
|
||||
7. Handle images.
|
||||
- Reuse screenshots from PRs when they exist.
|
||||
- Convert GitHub private attachment URLs to `https://github.com/user-attachments/assets/UUID` before upload.
|
||||
- Upload with `go run cmd/yaakadmin/main.go upload "URL"` when the environment permits it.
|
||||
- If no real image is available, use a placeholder with real alt text and a caption.
|
||||
|
||||
8. Write `_intro.md`.
|
||||
- Add a short overview paragraph at the top of the release.
|
||||
- Focus on the major themes across the release instead of repeating every bullet.
|
||||
|
||||
9. Follow Yaak writing style.
|
||||
- Be direct and factual. Avoid hype.
|
||||
- State what changed and how to use it.
|
||||
- Keep paragraphs short.
|
||||
- Use backticks for code symbols, settings, and literal values.
|
||||
- Use bold sparingly for the most important phrase in a section.
|
||||
|
||||
## File Rules
|
||||
|
||||
- Beta releases must not create or edit files in `src/content/changelog/`.
|
||||
- Main files are `_release.yaml` and optional `_intro.md`.
|
||||
- Expanded entry files are regular markdown files such as `request-debugging.md`.
|
||||
- `entries[].content` must match an existing markdown filename in the same directory.
|
||||
- Images for changelog pages live under `static/changelog/VERSION/` when committed to the repo.
|
||||
|
||||
## Checks
|
||||
|
||||
- For beta releases, verify `gh release view TAG --repo mountain-loop/yaak --json body,tagName,isDraft,isPrerelease` and ensure no website changelog files were created.
|
||||
- For beta releases, verify feedback-backed bullets use the feedback URL as the link target for the whole item text, not as a separate trailing `Feedback:` link.
|
||||
- For stable releases, ensure each user-facing source bullet becomes exactly one changelog entry unless it is `[beta-only]` or dependency-only/internal maintenance.
|
||||
- Ensure most entries include `pr` when the source release notes provide one.
|
||||
- For stable releases, ensure every referenced `content` file exists.
|
||||
- If the user wants stable website verification, run the site and inspect `/changelog/VERSION` and `/rss.xml`.
|
||||
@@ -0,0 +1,7 @@
|
||||
interface:
|
||||
display_name: "Yaak Changelog"
|
||||
short_description: "Generate Yaak changelog releases"
|
||||
default_prompt: "Use $yaak-changelog to create or update a Yaak changelog release from GitHub release notes."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -10,8 +10,7 @@ const REVIEWER_LOGIN = "gschier";
|
||||
const LARGE_DIFF_CHANGED_FILES = 20;
|
||||
const LARGE_DIFF_CHANGED_LINES = 800;
|
||||
const SUMMARY_TITLE_MAX_LENGTH = 80;
|
||||
const AUTOMATIC_PR_CREATED_AFTER = "2026-06-30T07:00:00.000Z";
|
||||
const AUTOMATIC_PR_CREATED_AFTER_LABEL = "June 30, 2026";
|
||||
const MIN_AUTOMATIC_PR_NUMBER = 494;
|
||||
|
||||
const LABELS = {
|
||||
inScope: {
|
||||
@@ -186,6 +185,18 @@ function analyzePullRequest(pr) {
|
||||
};
|
||||
}
|
||||
|
||||
if (labelNames.has(LABELS.explicitPermission.name)) {
|
||||
return {
|
||||
blockers: [],
|
||||
changedFiles,
|
||||
desiredLabels: [LABELS.explicitPermission.name],
|
||||
largeDiff,
|
||||
status: "in_scope",
|
||||
templateUsed,
|
||||
totalChangedLines,
|
||||
};
|
||||
}
|
||||
|
||||
if (!templateUsed) {
|
||||
blockers.push({
|
||||
label: LABELS.missingTemplate.name,
|
||||
@@ -428,10 +439,6 @@ function summarizeResult({ pr, analysis, skipped, skipReason }) {
|
||||
};
|
||||
}
|
||||
|
||||
function wasCreatedBefore(value, cutoff) {
|
||||
return Date.parse(value) < Date.parse(cutoff);
|
||||
}
|
||||
|
||||
async function isOfficialMaintainer({ github, owner, repo, pr }) {
|
||||
if (MAINTAINER_LOGINS.has(pr.user.login)) {
|
||||
return true;
|
||||
@@ -603,7 +610,7 @@ async function checkPullRequest({
|
||||
repo,
|
||||
pullNumber,
|
||||
dryRun,
|
||||
skipCreatedBefore,
|
||||
minimumAutomaticPullNumber,
|
||||
}) {
|
||||
const response = await github.rest.pulls.get({
|
||||
owner,
|
||||
@@ -614,11 +621,11 @@ async function checkPullRequest({
|
||||
const issueNumber = pr.number;
|
||||
|
||||
if (
|
||||
skipCreatedBefore != null &&
|
||||
wasCreatedBefore(pr.created_at, skipCreatedBefore)
|
||||
minimumAutomaticPullNumber != null &&
|
||||
pr.number < minimumAutomaticPullNumber
|
||||
) {
|
||||
core.notice(
|
||||
`Skipping contribution policy for PR #${pr.number} because it was created before ${AUTOMATIC_PR_CREATED_AFTER_LABEL}.`,
|
||||
`Skipping contribution policy for PR #${pr.number} because automatic checks start at PR #${minimumAutomaticPullNumber}.`,
|
||||
);
|
||||
return {
|
||||
blocked: false,
|
||||
@@ -626,7 +633,7 @@ async function checkPullRequest({
|
||||
summary: summarizeResult({
|
||||
pr,
|
||||
skipped: true,
|
||||
skipReason: `created before ${AUTOMATIC_PR_CREATED_AFTER_LABEL}`,
|
||||
skipReason: `before automatic rollout PR #${minimumAutomaticPullNumber}`,
|
||||
}),
|
||||
skipped: true,
|
||||
};
|
||||
@@ -756,8 +763,8 @@ async function run({ github, context, core }) {
|
||||
context.eventName === "workflow_dispatch" &&
|
||||
dryRunInput !== false &&
|
||||
dryRunInput !== "false";
|
||||
const skipCreatedBefore =
|
||||
payloadPr == null ? null : AUTOMATIC_PR_CREATED_AFTER;
|
||||
const minimumAutomaticPullNumber =
|
||||
payloadPr == null ? null : MIN_AUTOMATIC_PR_NUMBER;
|
||||
let pullNumbers;
|
||||
|
||||
if (payloadPr != null) {
|
||||
@@ -795,7 +802,7 @@ async function run({ github, context, core }) {
|
||||
repo,
|
||||
pullNumber: pr.number,
|
||||
dryRun,
|
||||
skipCreatedBefore,
|
||||
minimumAutomaticPullNumber,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,4 +30,4 @@ jobs:
|
||||
- name: Run JS Tests
|
||||
run: vp test
|
||||
- name: Run Rust Tests
|
||||
run: cargo test --all
|
||||
run: cargo test --all --features yaak-app-client/wry
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Checkout policy script
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha || github.ref }}
|
||||
ref: main
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Check contribution policy
|
||||
|
||||
@@ -14,35 +14,59 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- platform: "macos-latest" # for Arm-based Macs (M1 and above).
|
||||
args: "--target aarch64-apple-darwin"
|
||||
args: '--target aarch64-apple-darwin --config ./tauri.release.conf.json --config ''{"build":{"features":["updater","license","wry"]}}'''
|
||||
yaak_arch: "arm64"
|
||||
os: "macos"
|
||||
runtime: "wry"
|
||||
targets: "aarch64-apple-darwin"
|
||||
- platform: "macos-latest" # for Intel-based Macs.
|
||||
args: "--target x86_64-apple-darwin"
|
||||
args: '--target x86_64-apple-darwin --config ./tauri.release.conf.json --config ''{"build":{"features":["updater","license","wry"]}}'''
|
||||
yaak_arch: "x64"
|
||||
os: "macos"
|
||||
runtime: "wry"
|
||||
targets: "x86_64-apple-darwin"
|
||||
- platform: "ubuntu-22.04"
|
||||
args: ""
|
||||
args: '--config ./tauri.release.conf.json --config ''{"build":{"features":["updater","license","wry"]}}'''
|
||||
yaak_arch: "x64"
|
||||
os: "ubuntu"
|
||||
runtime: "wry"
|
||||
targets: ""
|
||||
- platform: "ubuntu-22.04-arm"
|
||||
args: ""
|
||||
args: '--config ./tauri.release.conf.json --config ''{"build":{"features":["updater","license","wry"]}}'''
|
||||
yaak_arch: "arm64"
|
||||
os: "ubuntu"
|
||||
runtime: "wry"
|
||||
targets: ""
|
||||
- platform: "ubuntu-22.04"
|
||||
args: >-
|
||||
--bundles deb
|
||||
--config ./tauri.release.conf.json
|
||||
--config '{"productName":"yaak-cef","mainBinaryName":"yaak-cef","identifier":"app.yaak.desktop.cef","build":{"features":["license","cef"]},"bundle":{"createUpdaterArtifacts":false}}'
|
||||
yaak_arch: "x64"
|
||||
os: "ubuntu"
|
||||
runtime: "cef"
|
||||
targets: ""
|
||||
- platform: "ubuntu-22.04-arm"
|
||||
args: >-
|
||||
--bundles deb
|
||||
--config ./tauri.release.conf.json
|
||||
--config '{"productName":"yaak-cef","mainBinaryName":"yaak-cef","identifier":"app.yaak.desktop.cef","build":{"features":["license","cef"]},"bundle":{"createUpdaterArtifacts":false}}'
|
||||
yaak_arch: "arm64"
|
||||
os: "ubuntu"
|
||||
runtime: "cef"
|
||||
targets: ""
|
||||
- platform: "windows-latest"
|
||||
args: ""
|
||||
args: '--config ./tauri.release.conf.json --config ''{"build":{"features":["updater","license","wry"]}}'''
|
||||
yaak_arch: "x64"
|
||||
os: "windows"
|
||||
runtime: "wry"
|
||||
targets: ""
|
||||
# Windows ARM64
|
||||
- platform: "windows-latest"
|
||||
args: "--target aarch64-pc-windows-msvc"
|
||||
args: '--target aarch64-pc-windows-msvc --config ./tauri.release.conf.json --config ''{"build":{"features":["updater","license","wry"]}}'''
|
||||
yaak_arch: "arm64"
|
||||
os: "windows"
|
||||
runtime: "wry"
|
||||
targets: "aarch64-pc-windows-msvc"
|
||||
runs-on: ${{ matrix.platform }}
|
||||
timeout-minutes: 40
|
||||
@@ -66,11 +90,18 @@ jobs:
|
||||
shared-key: ci
|
||||
cache-on-failure: true
|
||||
|
||||
- name: Cache CEF (Linux only)
|
||||
if: matrix.os == 'ubuntu' && matrix.runtime == 'cef'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/tauri-cef
|
||||
key: cef-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.lock') }}
|
||||
|
||||
- name: install dependencies (Linux only)
|
||||
if: matrix.os == 'ubuntu'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
|
||||
sudo apt-get install -y cmake ninja-build libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libnss3 patchelf xdg-utils
|
||||
|
||||
- name: Install Protoc for plugin-runtime
|
||||
uses: arduino/setup-protoc@v3
|
||||
@@ -98,7 +129,7 @@ jobs:
|
||||
- name: Run JS Tests
|
||||
run: vp test
|
||||
- name: Run Rust Tests
|
||||
run: cargo test --all --exclude yaak-cli
|
||||
run: cargo test --all --exclude yaak-cli --features yaak-app-client/wry
|
||||
|
||||
- name: Set version
|
||||
run: npm run replace-version
|
||||
@@ -150,13 +181,30 @@ jobs:
|
||||
AZURE_CLIENT_SECRET: ${{ matrix.os == 'windows' && secrets.AZURE_CLIENT_SECRET }}
|
||||
AZURE_TENANT_ID: ${{ matrix.os == 'windows' && secrets.AZURE_TENANT_ID }}
|
||||
with:
|
||||
tauriScript: "node ../../node_modules/@tauri-apps/cli/tauri.js"
|
||||
tagName: "v__VERSION__"
|
||||
releaseName: "Release __VERSION__"
|
||||
releaseBody: "[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)"
|
||||
releaseDraft: true
|
||||
prerelease: true
|
||||
projectPath: ./crates-tauri/yaak-app-client
|
||||
args: "${{ matrix.args }} --config ./tauri.release.conf.json"
|
||||
args: "${{ matrix.args }}"
|
||||
|
||||
- name: Build and upload CEF tarball from deb (Linux only)
|
||||
if: matrix.os == 'ubuntu' && matrix.runtime == 'cef'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
deb=$(find target/release/bundle/deb -maxdepth 1 -type f -name '*.deb' | head -n 1)
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
extract_dir="target/release/bundle/deb/yaak-cef-linux-${{ matrix.yaak_arch }}"
|
||||
tarball="target/release/bundle/deb/yaak-cef_${version}_linux_${{ matrix.yaak_arch }}.tar.gz"
|
||||
rm -rf "$extract_dir"
|
||||
mkdir -p "$extract_dir"
|
||||
dpkg-deb -x "$deb" "$extract_dir"
|
||||
tar -C "$extract_dir" -czf "$tarball" .
|
||||
gh release upload "${{ github.ref_name }}" "$tarball" --clobber
|
||||
|
||||
# Build a per-machine NSIS installer for enterprise deployment (PDQ, SCCM, Intune)
|
||||
- name: Build and upload machine-wide installer (Windows only)
|
||||
@@ -173,7 +221,7 @@ jobs:
|
||||
run: |
|
||||
Get-ChildItem -Recurse -Path target -File -Filter "*.exe.sig" | Remove-Item -Force
|
||||
Push-Location crates-tauri/yaak-app-client
|
||||
npx tauri bundle ${{ matrix.args }} --bundles nsis --config ./tauri.release.conf.json --config '{"bundle":{"createUpdaterArtifacts":true,"windows":{"nsis":{"installMode":"perMachine"}}}}'
|
||||
npx tauri bundle ${{ matrix.args }} --bundles nsis --config '{"bundle":{"createUpdaterArtifacts":true,"windows":{"nsis":{"installMode":"perMachine"}}}}'
|
||||
Pop-Location
|
||||
$setup = Get-ChildItem -Recurse -Path target -Filter "*setup*.exe" | Select-Object -First 1
|
||||
$setupSig = "$($setup.FullName).sig"
|
||||
|
||||
Generated
+966
-112
File diff suppressed because it is too large
Load Diff
+12
-2
@@ -31,6 +31,7 @@ members = [
|
||||
"crates-tauri/yaak-fonts",
|
||||
"crates-tauri/yaak-license",
|
||||
"crates-tauri/yaak-mac-window",
|
||||
"crates-tauri/yaak-system-appearance",
|
||||
"crates-tauri/yaak-tauri-utils",
|
||||
"crates-tauri/yaak-window",
|
||||
]
|
||||
@@ -47,9 +48,13 @@ schemars = { version = "0.8.22", features = ["chrono"] }
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
sha2 = "0.10.9"
|
||||
tauri = "2.11.1"
|
||||
tauri = { version = "2.11.1", default-features = false, features = [
|
||||
"common-controls-v6",
|
||||
"compression",
|
||||
"dynamic-acl",
|
||||
] }
|
||||
tauri-plugin = "2.6.1"
|
||||
tauri-plugin-dialog = "2.7.1"
|
||||
tauri-plugin-dialog = { version = "2.7.1", default-features = false }
|
||||
tauri-plugin-shell = "2.3.5"
|
||||
thiserror = "2.0.17"
|
||||
tokio = "1.48.0"
|
||||
@@ -84,8 +89,13 @@ yaak-proxy-lib = { path = "crates-proxy/yaak-proxy-lib" }
|
||||
yaak-fonts = { path = "crates-tauri/yaak-fonts" }
|
||||
yaak-license = { path = "crates-tauri/yaak-license" }
|
||||
yaak-mac-window = { path = "crates-tauri/yaak-mac-window" }
|
||||
yaak-system-appearance = { path = "crates-tauri/yaak-system-appearance" }
|
||||
yaak-tauri-utils = { path = "crates-tauri/yaak-tauri-utils" }
|
||||
yaak-window = { path = "crates-tauri/yaak-window" }
|
||||
|
||||
[profile.release]
|
||||
strip = false
|
||||
|
||||
[patch.crates-io]
|
||||
tauri = { git = "https://github.com/tauri-apps/tauri", rev = "d9bc695c18d9a25baec21d8a5f36d72e3a14ee53" }
|
||||
tauri-build = { git = "https://github.com/tauri-apps/tauri", rev = "d9bc695c18d9a25baec21d8a5f36d72e3a14ee53" }
|
||||
|
||||
@@ -116,7 +116,7 @@ export function SettingsGeneral() {
|
||||
<DismissibleBanner
|
||||
id="workspace-settings-moved-2026-06-30"
|
||||
color="info"
|
||||
className="p-4 max-w-xl mx-auto"
|
||||
className="w-full p-4 max-w-xl mr-auto"
|
||||
>
|
||||
<p>
|
||||
Workspace specific settings have moved to{" "}
|
||||
|
||||
+51
-11
@@ -1,32 +1,49 @@
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { type as osType } from "@tauri-apps/plugin-os";
|
||||
import { setWindowTheme } from "@yaakapp-internal/mac-window";
|
||||
import type { ModelPayload } from "@yaakapp-internal/models";
|
||||
import type { Appearance } from "@yaakapp-internal/theme";
|
||||
import {
|
||||
applyThemeToDocument,
|
||||
getCSSAppearance,
|
||||
subscribeToPreferredAppearance,
|
||||
subscribeToPreferredAppearanceChange,
|
||||
subscribeToSystemAppearanceChange,
|
||||
} from "@yaakapp-internal/theme";
|
||||
import { getSettings } from "./lib/settings";
|
||||
import { getResolvedTheme } from "./lib/themes";
|
||||
|
||||
// NOTE: CSS appearance isn't as accurate as getting it async from the window (next step), but we want
|
||||
// a good appearance guess so we're not waiting too long
|
||||
let preferredAppearance: Appearance = getCSSAppearance();
|
||||
subscribeToPreferredAppearance(async (a) => {
|
||||
let preferredAppearance: Appearance = getInitialAppearance();
|
||||
let linuxSystemAppearanceAvailable =
|
||||
osType() === "linux" && window.__YAAK_INITIAL_APPEARANCE_SOURCE__ === "linux-system";
|
||||
let configureThemeGeneration = 0;
|
||||
let windowShown = false;
|
||||
|
||||
configureThemeAndShow().catch((err) => console.log("Failed to configure theme", err));
|
||||
|
||||
subscribeToPreferredAppearanceChange(async (a) => {
|
||||
if (linuxSystemAppearanceAvailable) return;
|
||||
preferredAppearance = a;
|
||||
await configureTheme();
|
||||
await configureThemeAndShow();
|
||||
});
|
||||
|
||||
configureTheme().then(
|
||||
async () => {
|
||||
subscribeToSystemAppearanceChange(async (a) => {
|
||||
linuxSystemAppearanceAvailable = true;
|
||||
preferredAppearance = a;
|
||||
await configureThemeAndShow();
|
||||
});
|
||||
|
||||
async function configureThemeAndShow() {
|
||||
const applied = await configureTheme();
|
||||
if (applied && !windowShown) {
|
||||
windowShown = true;
|
||||
// To prevent theme flashing, the backend hides new windows by default, so we
|
||||
// need to show it here, after configuring the theme for the first time.
|
||||
await getCurrentWebviewWindow().show();
|
||||
},
|
||||
(err) => console.log("Failed to configure theme", err),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for settings changes, the re-compute theme
|
||||
listen<ModelPayload>("model_write", async (event) => {
|
||||
@@ -34,10 +51,11 @@ listen<ModelPayload>("model_write", async (event) => {
|
||||
|
||||
const model = event.payload.model.model;
|
||||
if (model !== "settings" && model !== "plugin") return;
|
||||
await configureTheme();
|
||||
await configureThemeAndShow();
|
||||
}).catch(console.error);
|
||||
|
||||
async function configureTheme() {
|
||||
async function configureTheme(): Promise<boolean> {
|
||||
const generation = ++configureThemeGeneration;
|
||||
const settings = await getSettings();
|
||||
const theme = await getResolvedTheme(
|
||||
preferredAppearance,
|
||||
@@ -45,8 +63,30 @@ async function configureTheme() {
|
||||
settings.themeLight,
|
||||
settings.themeDark,
|
||||
);
|
||||
|
||||
if (generation !== configureThemeGeneration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
applyThemeToDocument(theme.active);
|
||||
if (theme.active.base.surface != null) {
|
||||
setWindowTheme(theme.active.base.surface);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getInitialAppearance(): Appearance {
|
||||
const initialAppearance = window.__YAAK_INITIAL_APPEARANCE__;
|
||||
if (initialAppearance === "dark" || initialAppearance === "light") {
|
||||
return initialAppearance;
|
||||
}
|
||||
return getCSSAppearance();
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__YAAK_INITIAL_APPEARANCE__?: Appearance;
|
||||
__YAAK_INITIAL_APPEARANCE_SOURCE__?: "settings" | "linux-system";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,8 +855,6 @@ exports.plugin = {
|
||||
assert_eq!(metadata["apis"]["workspaceActions"]["items"][0]["label"], "Sync workspace");
|
||||
assert_eq!(metadata["apis"]["lifecycle"]["items"][0]["name"], "init");
|
||||
assert!(metadata["apis"]["templateFunctions"]["items"][0]["onRender"].is_null());
|
||||
assert!(
|
||||
metadata["apis"]["templateFunctions"]["items"][0]["args"][0]["dynamic"].is_null()
|
||||
);
|
||||
assert!(metadata["apis"]["templateFunctions"]["items"][0]["args"][0]["dynamic"].is_null());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,11 +470,7 @@ async fn build_plugin_reply(
|
||||
}
|
||||
};
|
||||
|
||||
let names = cookie_jar
|
||||
.cookies
|
||||
.into_iter()
|
||||
.map(|c| c.name)
|
||||
.collect();
|
||||
let names = cookie_jar.cookies.into_iter().map(|c| c.name).collect();
|
||||
|
||||
Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse {
|
||||
names,
|
||||
|
||||
@@ -13,6 +13,8 @@ crate-type = ["staticlib", "cdylib", "lib"]
|
||||
[features]
|
||||
cargo-clippy = []
|
||||
default = []
|
||||
cef = ["tauri/cef", "tauri-plugin-dialog/xdg-portal"]
|
||||
wry = ["tauri/wry", "tauri/x11", "tauri/dbus", "tauri-plugin-dialog/gtk3"]
|
||||
updater = []
|
||||
license = ["yaak-license"]
|
||||
|
||||
@@ -59,7 +61,6 @@ tauri-plugin-os = "2.3.2"
|
||||
tauri-plugin-shell = { workspace = true }
|
||||
tauri-plugin-single-instance = { version = "2.4.2", features = ["deep-link"] }
|
||||
tauri-plugin-updater = "2.10.1"
|
||||
tauri-plugin-window-state = "2.4.1"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
tokio-stream = "0.1.17"
|
||||
@@ -83,6 +84,7 @@ yaak-mac-window = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
yaak-plugins = { workspace = true }
|
||||
yaak-sse = { workspace = true }
|
||||
yaak-system-appearance = { workspace = true }
|
||||
yaak-sync = { workspace = true }
|
||||
yaak-templates = { workspace = true }
|
||||
yaak-tls = { workspace = true }
|
||||
|
||||
@@ -26,7 +26,6 @@ use tauri::{Manager, WindowEvent};
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
||||
use tauri_plugin_log::{Builder, Target, TargetKind, log};
|
||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::block_in_place;
|
||||
use tokio::time;
|
||||
@@ -83,6 +82,14 @@ mod uri_scheme;
|
||||
mod window_menu;
|
||||
mod ws_ext;
|
||||
|
||||
#[cfg(not(any(feature = "cef", feature = "wry")))]
|
||||
compile_error!("Enable one Tauri runtime feature: `cef` or `wry`.");
|
||||
|
||||
#[cfg(feature = "cef")]
|
||||
type TauriRuntime = tauri::Cef;
|
||||
#[cfg(all(not(feature = "cef"), feature = "wry"))]
|
||||
type TauriRuntime = tauri::Wry;
|
||||
|
||||
fn setup_window_menu<R: Runtime>(win: &WebviewWindow<R>) -> Result<()> {
|
||||
#[allow(unused_variables)]
|
||||
let menu = window_menu::app_menu(win.app_handle())?;
|
||||
@@ -151,6 +158,22 @@ fn setup_window_menu<R: Runtime>(win: &WebviewWindow<R>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn initial_appearance_script<R: Runtime>(app_handle: &AppHandle<R>) -> Option<String> {
|
||||
use yaak_system_appearance::{Appearance, InitialAppearanceSource};
|
||||
|
||||
let settings = app_handle.db().get_settings();
|
||||
let (appearance, source) = match settings.appearance.as_str() {
|
||||
"dark" => (Appearance::Dark, InitialAppearanceSource::Settings),
|
||||
"light" => (Appearance::Light, InitialAppearanceSource::Settings),
|
||||
_ => (
|
||||
yaak_system_appearance::system_appearance()?,
|
||||
InitialAppearanceSource::LinuxSystem,
|
||||
),
|
||||
};
|
||||
|
||||
Some(yaak_system_appearance::initialization_script(appearance, source))
|
||||
}
|
||||
|
||||
/// Extension trait for easily creating a PluginContext from a WebviewWindow
|
||||
pub trait PluginContextExt<R: Runtime> {
|
||||
fn plugin_context(&self) -> PluginContext;
|
||||
@@ -178,7 +201,7 @@ struct AppMetaData {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
|
||||
async fn cmd_metadata<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<AppMetaData> {
|
||||
let app_data_dir = app_handle.path().app_data_dir()?;
|
||||
let app_log_dir = app_handle.path().app_log_dir()?;
|
||||
let vendored_plugin_dir =
|
||||
@@ -963,7 +986,7 @@ async fn cmd_send_ephemeral_request<R: Runtime>(
|
||||
mut request: HttpRequest,
|
||||
environment_id: Option<&str>,
|
||||
cookie_jar_id: Option<&str>,
|
||||
window: WebviewWindow,
|
||||
window: WebviewWindow<R>,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<HttpResponse> {
|
||||
let response = HttpResponse::default();
|
||||
@@ -1589,20 +1612,22 @@ async fn cmd_get_workspace_meta<R: Runtime>(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_new_child_window(
|
||||
parent_window: WebviewWindow,
|
||||
async fn cmd_new_child_window<R: Runtime>(
|
||||
parent_window: WebviewWindow<R>,
|
||||
url: &str,
|
||||
label: &str,
|
||||
title: &str,
|
||||
inner_size: (f64, f64),
|
||||
) -> YaakResult<()> {
|
||||
let use_native_titlebar = parent_window.app_handle().db().get_settings().use_native_titlebar;
|
||||
let initialization_script = initial_appearance_script(&parent_window.app_handle());
|
||||
let win = yaak_window::window::create_child_window(
|
||||
&parent_window,
|
||||
url,
|
||||
label,
|
||||
title,
|
||||
inner_size,
|
||||
initialization_script,
|
||||
use_native_titlebar,
|
||||
)?;
|
||||
setup_window_menu(&win)?;
|
||||
@@ -1610,9 +1635,15 @@ async fn cmd_new_child_window(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> YaakResult<()> {
|
||||
async fn cmd_new_main_window<R: Runtime>(app_handle: AppHandle<R>, url: &str) -> YaakResult<()> {
|
||||
let use_native_titlebar = app_handle.db().get_settings().use_native_titlebar;
|
||||
let win = yaak_window::window::create_main_window(&app_handle, url, use_native_titlebar)?;
|
||||
let initialization_script = initial_appearance_script(&app_handle);
|
||||
let win = yaak_window::window::create_main_window(
|
||||
&app_handle,
|
||||
url,
|
||||
initialization_script,
|
||||
use_native_titlebar,
|
||||
)?;
|
||||
setup_window_menu(&win)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1632,8 +1663,9 @@ async fn cmd_check_for_updates<R: Runtime>(
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
#[cfg_attr(feature = "cef", tauri::cef_entry_point)]
|
||||
pub fn run() {
|
||||
let mut builder = tauri::Builder::default().plugin(
|
||||
let mut builder = tauri::Builder::<TauriRuntime>::default().plugin(
|
||||
Builder::default()
|
||||
.targets([
|
||||
Target::new(TargetKind::Stdout),
|
||||
@@ -1677,13 +1709,6 @@ pub fn run() {
|
||||
builder = builder
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
// Don't restore StateFlags::DECORATIONS because we want to be able to toggle them on/off on a restart
|
||||
// We could* make this work if we toggled them in the frontend before the window closes, but, this is nicer.
|
||||
.plugin(
|
||||
tauri_plugin_window_state::Builder::new()
|
||||
.with_state_flags(StateFlags::all() - StateFlags::DECORATIONS)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
@@ -1714,6 +1739,10 @@ pub fn run() {
|
||||
app.state::<yaak_models::query_manager::QueryManager>().inner().clone();
|
||||
let app_id = app.config().identifier.to_string();
|
||||
app.manage(yaak_crypto::manager::EncryptionManager::new(query_manager, app_id));
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(state) = yaak_system_appearance::watch(app.app_handle().clone()) {
|
||||
app.manage(state);
|
||||
}
|
||||
|
||||
{
|
||||
let app_handle = app.app_handle().clone();
|
||||
@@ -1902,9 +1931,11 @@ pub fn run() {
|
||||
match event {
|
||||
RunEvent::Ready => {
|
||||
let use_native_titlebar = app_handle.db().get_settings().use_native_titlebar;
|
||||
let initialization_script = initial_appearance_script(app_handle);
|
||||
if let Ok(win) = yaak_window::window::create_main_window(
|
||||
app_handle,
|
||||
"/",
|
||||
initialization_script,
|
||||
use_native_titlebar,
|
||||
) {
|
||||
let _ = setup_window_menu(&win);
|
||||
@@ -1925,6 +1956,13 @@ pub fn run() {
|
||||
});
|
||||
}
|
||||
RunEvent::WindowEvent { event: WindowEvent::Focused(true), label, .. } => {
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(state) =
|
||||
app_handle.try_state::<yaak_system_appearance::SystemAppearanceState>()
|
||||
{
|
||||
yaak_system_appearance::emit_change(app_handle, &state);
|
||||
}
|
||||
|
||||
if cfg!(feature = "updater") {
|
||||
// Run update check whenever the window is focused
|
||||
let w = app_handle.get_webview_window(&label).unwrap();
|
||||
@@ -1959,13 +1997,6 @@ pub fn run() {
|
||||
}
|
||||
});
|
||||
}
|
||||
RunEvent::WindowEvent { event: WindowEvent::CloseRequested { .. }, .. } => {
|
||||
if let Err(e) = app_handle.save_window_state(StateFlags::all()) {
|
||||
warn!("Failed to save window state {e:?}");
|
||||
} else {
|
||||
info!("Saved window state");
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"beforeBuildCommand": "npm --prefix ../.. run client:tauri-before-build",
|
||||
"beforeDevCommand": "npm --prefix ../.. run client:tauri-before-dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"frontendDist": "../../dist/apps/yaak-client"
|
||||
"frontendDist": "../../dist/apps/yaak-client",
|
||||
"features": ["wry"]
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": false,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"build": {
|
||||
"features": ["updater", "license"]
|
||||
},
|
||||
"app": {
|
||||
"security": {
|
||||
"capabilities": [
|
||||
|
||||
@@ -12,6 +12,10 @@ crate-type = ["staticlib", "cdylib", "lib"]
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.6.1", features = [] }
|
||||
|
||||
[features]
|
||||
default = ["wry"]
|
||||
wry = ["tauri/wry", "tauri/x11", "tauri/dbus"]
|
||||
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -92,7 +92,7 @@ pub fn run() {
|
||||
label: "main_0",
|
||||
title: "Yaak Proxy",
|
||||
inner_size: Some((1000.0, 700.0)),
|
||||
visible: false,
|
||||
hidden: true,
|
||||
hide_titlebar: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "yaak-system-appearance"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
dark-light = "2.0.0"
|
||||
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
@@ -0,0 +1,151 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{debug, warn};
|
||||
use tauri::{AppHandle, Emitter, Runtime};
|
||||
|
||||
pub const INITIAL_APPEARANCE_GLOBAL: &str = "__YAAK_INITIAL_APPEARANCE__";
|
||||
pub const INITIAL_APPEARANCE_SOURCE_GLOBAL: &str = "__YAAK_INITIAL_APPEARANCE_SOURCE__";
|
||||
pub const SYSTEM_APPEARANCE_CHANGE_EVENT: &str = "system_appearance_change";
|
||||
|
||||
const SYSTEM_APPEARANCE_POLL_INTERVAL: Duration = Duration::from_secs(1);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Appearance {
|
||||
Dark,
|
||||
Light,
|
||||
}
|
||||
|
||||
impl Appearance {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Dark => "dark",
|
||||
Self::Light => "light",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum InitialAppearanceSource {
|
||||
Settings,
|
||||
LinuxSystem,
|
||||
}
|
||||
|
||||
impl InitialAppearanceSource {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Settings => "settings",
|
||||
Self::LinuxSystem => "linux-system",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SystemAppearanceState {
|
||||
last_appearance: Arc<Mutex<Option<Appearance>>>,
|
||||
}
|
||||
|
||||
pub fn initialization_script(appearance: Appearance, source: InitialAppearanceSource) -> String {
|
||||
let appearance = appearance.as_str();
|
||||
let source = source.as_str();
|
||||
format!(
|
||||
"window.{INITIAL_APPEARANCE_GLOBAL} = {appearance:?};\
|
||||
window.{INITIAL_APPEARANCE_SOURCE_GLOBAL} = {source:?};"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn system_appearance() -> Option<Appearance> {
|
||||
if let Some(appearance) = gsettings_system_appearance() {
|
||||
return Some(appearance);
|
||||
}
|
||||
|
||||
match dark_light::detect() {
|
||||
Ok(dark_light::Mode::Dark) => Some(Appearance::Dark),
|
||||
Ok(dark_light::Mode::Light) => Some(Appearance::Light),
|
||||
Ok(dark_light::Mode::Unspecified) => None,
|
||||
Err(err) => {
|
||||
debug!("Failed to detect Linux system appearance: {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn system_appearance() -> Option<Appearance> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn watch<R: Runtime>(app_handle: AppHandle<R>) -> Option<SystemAppearanceState> {
|
||||
let last_appearance = system_appearance();
|
||||
if last_appearance.is_none() {
|
||||
debug!("Linux system appearance detection unavailable");
|
||||
return None;
|
||||
}
|
||||
|
||||
let state = SystemAppearanceState { last_appearance: Arc::new(Mutex::new(last_appearance)) };
|
||||
let thread_state = state.clone();
|
||||
let _ = std::thread::spawn(move || {
|
||||
loop {
|
||||
std::thread::sleep(SYSTEM_APPEARANCE_POLL_INTERVAL);
|
||||
emit_change(&app_handle, &thread_state);
|
||||
}
|
||||
});
|
||||
|
||||
Some(state)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn watch<R: Runtime>(_app_handle: AppHandle<R>) -> Option<SystemAppearanceState> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn emit_change<R: Runtime>(app_handle: &AppHandle<R>, state: &SystemAppearanceState) {
|
||||
let appearance = system_appearance();
|
||||
let mut last_appearance =
|
||||
state.last_appearance.lock().expect("system appearance lock poisoned");
|
||||
if appearance == *last_appearance {
|
||||
return;
|
||||
}
|
||||
|
||||
*last_appearance = appearance;
|
||||
if let Some(appearance) = appearance {
|
||||
let appearance = appearance.as_str();
|
||||
debug!("System appearance changed to {appearance}");
|
||||
if let Err(err) = app_handle.emit(SYSTEM_APPEARANCE_CHANGE_EVENT, appearance) {
|
||||
warn!("Failed to emit system appearance change: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn gsettings_system_appearance() -> Option<Appearance> {
|
||||
let color_scheme = std::process::Command::new("gsettings")
|
||||
.args(["get", "org.gnome.desktop.interface", "color-scheme"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
if color_scheme.contains("prefer-dark") {
|
||||
return Some(Appearance::Dark);
|
||||
}
|
||||
if color_scheme.contains("prefer-light") {
|
||||
return Some(Appearance::Light);
|
||||
}
|
||||
|
||||
let gtk_theme = std::process::Command::new("gsettings")
|
||||
.args(["get", "org.gnome.desktop.interface", "gtk-theme"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
if gtk_theme.to_lowercase().contains("dark") {
|
||||
return Some(Appearance::Dark);
|
||||
}
|
||||
|
||||
(!gtk_theme.trim().is_empty()).then_some(Appearance::Light)
|
||||
}
|
||||
@@ -8,5 +8,7 @@ publish = false
|
||||
log = { workspace = true }
|
||||
md5 = "0.8.0"
|
||||
rand = "0.9.0"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
mod window_state;
|
||||
|
||||
pub mod window;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::window_state;
|
||||
use log::info;
|
||||
use rand::random;
|
||||
use tauri::{AppHandle, Manager, Runtime, WebviewUrl, WebviewWindow, WindowEvent};
|
||||
@@ -11,18 +12,22 @@ const MIN_WINDOW_HEIGHT: f64 = 300.0;
|
||||
|
||||
pub const MAIN_WINDOW_PREFIX: &str = "main_";
|
||||
const OTHER_WINDOW_PREFIX: &str = "other_";
|
||||
const MAIN_WINDOW_STATE_KEY: &str = "main";
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct CreateWindowConfig<'s> {
|
||||
pub url: &'s str,
|
||||
pub label: &'s str,
|
||||
pub title: &'s str,
|
||||
pub state_key: Option<String>,
|
||||
pub inner_size: Option<(f64, f64)>,
|
||||
pub position: Option<(f64, f64)>,
|
||||
pub restore_position: Option<bool>,
|
||||
pub navigation_tx: Option<mpsc::Sender<String>>,
|
||||
pub close_tx: Option<mpsc::Sender<()>>,
|
||||
pub data_dir_key: Option<String>,
|
||||
pub visible: bool,
|
||||
pub initialization_script: Option<String>,
|
||||
pub hidden: bool,
|
||||
pub hide_titlebar: bool,
|
||||
pub use_native_titlebar: bool,
|
||||
}
|
||||
@@ -32,15 +37,33 @@ pub fn create_window<R: Runtime>(
|
||||
config: CreateWindowConfig,
|
||||
) -> tauri::Result<WebviewWindow<R>> {
|
||||
info!("Create new window label={}", config.label);
|
||||
let state_key = config.state_key.clone().unwrap_or_else(|| config.label.to_string());
|
||||
let restore_position = config.restore_position.unwrap_or(true);
|
||||
let mut inner_size = config.inner_size;
|
||||
let mut position = config.position;
|
||||
let mut maximized = false;
|
||||
window_state::apply_saved_state(
|
||||
handle,
|
||||
&state_key,
|
||||
&mut inner_size,
|
||||
&mut position,
|
||||
&mut maximized,
|
||||
restore_position,
|
||||
);
|
||||
|
||||
let mut win_builder =
|
||||
tauri::WebviewWindowBuilder::new(handle, config.label, WebviewUrl::App(config.url.into()))
|
||||
.title(config.title)
|
||||
.resizable(true)
|
||||
.visible(config.visible)
|
||||
.visible(!config.hidden)
|
||||
.fullscreen(false)
|
||||
.maximized(maximized)
|
||||
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
|
||||
|
||||
if let Some(script) = config.initialization_script {
|
||||
win_builder = win_builder.initialization_script(script);
|
||||
}
|
||||
|
||||
if let Some(key) = config.data_dir_key {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
@@ -61,13 +84,13 @@ pub fn create_window<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((w, h)) = config.inner_size {
|
||||
if let Some((w, h)) = inner_size {
|
||||
win_builder = win_builder.inner_size(w, h);
|
||||
} else {
|
||||
win_builder = win_builder.inner_size(600.0, 600.0);
|
||||
}
|
||||
|
||||
if let Some((x, y)) = config.position {
|
||||
if let Some((x, y)) = position {
|
||||
win_builder = win_builder.position(x, y);
|
||||
} else {
|
||||
win_builder = win_builder.center();
|
||||
@@ -103,6 +126,7 @@ pub fn create_window<R: Runtime>(
|
||||
}
|
||||
|
||||
let win = win_builder.build()?;
|
||||
window_state::track_window(&win, &state_key);
|
||||
|
||||
if let Some(tx) = config.close_tx {
|
||||
win.on_window_event(move |event| match event {
|
||||
@@ -119,11 +143,12 @@ pub fn create_window<R: Runtime>(
|
||||
Ok(win)
|
||||
}
|
||||
|
||||
pub fn create_main_window(
|
||||
handle: &AppHandle,
|
||||
pub fn create_main_window<R: Runtime>(
|
||||
handle: &AppHandle<R>,
|
||||
url: &str,
|
||||
initialization_script: Option<String>,
|
||||
use_native_titlebar: bool,
|
||||
) -> tauri::Result<WebviewWindow> {
|
||||
) -> tauri::Result<WebviewWindow<R>> {
|
||||
let mut counter = 0;
|
||||
let label = loop {
|
||||
let label = format!("{MAIN_WINDOW_PREFIX}{counter}");
|
||||
@@ -138,12 +163,16 @@ pub fn create_main_window(
|
||||
url,
|
||||
label: label.as_str(),
|
||||
title: "Yaak",
|
||||
state_key: Some(MAIN_WINDOW_STATE_KEY.to_string()),
|
||||
inner_size: Some((DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)),
|
||||
position: Some((
|
||||
// Offset by random amount so it's easier to differentiate
|
||||
100.0 + random::<f64>() * 20.0,
|
||||
100.0 + random::<f64>() * 20.0,
|
||||
)),
|
||||
restore_position: Some(counter == 0),
|
||||
initialization_script,
|
||||
hidden: true,
|
||||
hide_titlebar: true,
|
||||
use_native_titlebar,
|
||||
..Default::default()
|
||||
@@ -152,15 +181,17 @@ pub fn create_main_window(
|
||||
create_window(handle, config)
|
||||
}
|
||||
|
||||
pub fn create_child_window(
|
||||
parent_window: &WebviewWindow,
|
||||
pub fn create_child_window<R: Runtime>(
|
||||
parent_window: &WebviewWindow<R>,
|
||||
url: &str,
|
||||
label: &str,
|
||||
title: &str,
|
||||
inner_size: (f64, f64),
|
||||
initialization_script: Option<String>,
|
||||
use_native_titlebar: bool,
|
||||
) -> tauri::Result<WebviewWindow> {
|
||||
) -> tauri::Result<WebviewWindow<R>> {
|
||||
let app_handle = parent_window.app_handle();
|
||||
let state_key = label.to_string();
|
||||
let label = format!("{OTHER_WINDOW_PREFIX}_{label}");
|
||||
let scale_factor = parent_window.scale_factor()?;
|
||||
|
||||
@@ -176,9 +207,12 @@ pub fn create_child_window(
|
||||
let config = CreateWindowConfig {
|
||||
label: label.as_str(),
|
||||
title,
|
||||
state_key: Some(state_key),
|
||||
url,
|
||||
inner_size: Some(inner_size),
|
||||
position: Some(position),
|
||||
initialization_script,
|
||||
hidden: true,
|
||||
hide_titlebar: true,
|
||||
use_native_titlebar,
|
||||
..Default::default()
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
use log::{debug, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Manager, Monitor, Runtime, WebviewWindow, WindowEvent};
|
||||
|
||||
const WINDOW_STATE_FILE: &str = "window-state.json";
|
||||
const SAVE_DEBOUNCE: Duration = Duration::from_millis(1000);
|
||||
static WINDOW_STATE_FILE_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize)]
|
||||
struct WindowState {
|
||||
width: f64,
|
||||
height: f64,
|
||||
x: f64,
|
||||
y: f64,
|
||||
maximized: bool,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
fn has_size(self) -> bool {
|
||||
self.width > 0.0 && self.height > 0.0
|
||||
}
|
||||
|
||||
fn has_position(self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_saved_state<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
state_key: &str,
|
||||
inner_size: &mut Option<(f64, f64)>,
|
||||
position: &mut Option<(f64, f64)>,
|
||||
maximized: &mut bool,
|
||||
restore_position: bool,
|
||||
) {
|
||||
let Some(state) = read_window_state(app_handle, state_key) else {
|
||||
debug!("No saved window state for {state_key}");
|
||||
return;
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Applying saved window state for {state_key}: width={} height={} x={} y={} maximized={} restore_position={restore_position}",
|
||||
state.width, state.height, state.x, state.y, state.maximized
|
||||
);
|
||||
|
||||
if state.has_size() {
|
||||
*inner_size = Some((state.width, state.height));
|
||||
}
|
||||
|
||||
if restore_position && state.has_position() {
|
||||
if is_position_visible(app_handle, state) {
|
||||
*position = Some((state.x, state.y));
|
||||
} else {
|
||||
debug!("Ignoring saved window position for {state_key} because it is off-screen");
|
||||
}
|
||||
}
|
||||
|
||||
*maximized = state.maximized;
|
||||
}
|
||||
|
||||
pub fn track_window<R: Runtime>(window: &WebviewWindow<R>, state_key: &str) {
|
||||
let state_key = state_key.to_string();
|
||||
let save_generation = Arc::new(AtomicU64::new(0));
|
||||
let tracked_window = window.clone();
|
||||
|
||||
window.clone().on_window_event(move |event| match event {
|
||||
WindowEvent::Moved(_) | WindowEvent::Resized(_) => {
|
||||
schedule_save(tracked_window.clone(), state_key.clone(), save_generation.clone());
|
||||
}
|
||||
WindowEvent::CloseRequested { .. } => {
|
||||
save_generation.fetch_add(1, Ordering::Relaxed);
|
||||
if let Err(e) = save_window_state(&tracked_window, &state_key) {
|
||||
warn!("Failed to save window state for {state_key}: {e}");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
||||
fn schedule_save<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
state_key: String,
|
||||
save_generation: Arc<AtomicU64>,
|
||||
) {
|
||||
let generation = save_generation.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
let window_for_dispatch = window.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(SAVE_DEBOUNCE);
|
||||
|
||||
if save_generation.load(Ordering::Relaxed) != generation {
|
||||
return;
|
||||
}
|
||||
|
||||
let state_key_for_save = state_key.clone();
|
||||
let window_for_save = window.clone();
|
||||
if let Err(e) = window_for_dispatch.run_on_main_thread(move || {
|
||||
if let Err(e) = save_window_state(&window_for_save, &state_key_for_save) {
|
||||
warn!("Failed to save window state for {state_key_for_save}: {e}");
|
||||
}
|
||||
}) {
|
||||
debug!("Failed to dispatch debounced window state save for {state_key}: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn save_window_state<R: Runtime>(window: &WebviewWindow<R>, state_key: &str) -> tauri::Result<()> {
|
||||
let app_handle = window.app_handle();
|
||||
let state_path = window_state_path(&app_handle)?;
|
||||
let _lock = WINDOW_STATE_FILE_LOCK.lock().unwrap();
|
||||
let mut states = read_window_states(&state_path);
|
||||
let mut state = states.get(state_key).copied().unwrap_or_default();
|
||||
|
||||
let maximized = window.is_maximized().unwrap_or(false);
|
||||
let minimized = window.is_minimized().unwrap_or(false);
|
||||
let scale_factor = window.scale_factor().unwrap_or(1.0);
|
||||
|
||||
if !minimized && (!maximized || !state.has_size()) {
|
||||
let size = window.inner_size()?.to_logical::<f64>(scale_factor);
|
||||
if size.width > 0.0 && size.height > 0.0 {
|
||||
state.width = size.width;
|
||||
state.height = size.height;
|
||||
}
|
||||
}
|
||||
|
||||
if !minimized && (!maximized || !state.has_position()) {
|
||||
let position = window.outer_position()?.to_logical::<f64>(scale_factor);
|
||||
state.x = position.x;
|
||||
state.y = position.y;
|
||||
}
|
||||
|
||||
state.maximized = maximized;
|
||||
states.insert(state_key.to_string(), state);
|
||||
write_window_states(&state_path, &states)?;
|
||||
debug!(
|
||||
"Saved window state for {state_key} to {}: width={} height={} x={} y={} maximized={} minimized={minimized}",
|
||||
state_path.display(),
|
||||
state.width,
|
||||
state.height,
|
||||
state.x,
|
||||
state.y,
|
||||
state.maximized
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_window_state<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
state_key: &str,
|
||||
) -> Option<WindowState> {
|
||||
let state_path = window_state_path(app_handle).ok()?;
|
||||
debug!("Reading window state for {state_key} from {}", state_path.display());
|
||||
read_window_states(&state_path).get(state_key).copied()
|
||||
}
|
||||
|
||||
fn window_state_path<R: Runtime>(app_handle: &AppHandle<R>) -> tauri::Result<PathBuf> {
|
||||
Ok(app_handle.path().app_config_dir()?.join(WINDOW_STATE_FILE))
|
||||
}
|
||||
|
||||
fn read_window_states(state_path: &PathBuf) -> HashMap<String, WindowState> {
|
||||
let Ok(bytes) = fs::read(state_path) else {
|
||||
return HashMap::new();
|
||||
};
|
||||
|
||||
match serde_json::from_slice(&bytes) {
|
||||
Ok(states) => states,
|
||||
Err(e) => {
|
||||
warn!("Failed to read window state {}: {e}", state_path.display());
|
||||
HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_window_states(
|
||||
state_path: &PathBuf,
|
||||
states: &HashMap<String, WindowState>,
|
||||
) -> tauri::Result<()> {
|
||||
if let Some(parent) = state_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
fs::write(state_path, serde_json::to_vec_pretty(states)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_position_visible<R: Runtime>(app_handle: &AppHandle<R>, state: WindowState) -> bool {
|
||||
let Ok(monitors) = app_handle.available_monitors() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
monitors.into_iter().any(|monitor| monitor_intersects_window(&monitor, state))
|
||||
}
|
||||
|
||||
fn monitor_intersects_window(monitor: &Monitor, state: WindowState) -> bool {
|
||||
let scale_factor = monitor.scale_factor();
|
||||
let position = monitor.position().to_logical::<f64>(scale_factor);
|
||||
let size = monitor.size().to_logical::<f64>(scale_factor);
|
||||
|
||||
let left = position.x;
|
||||
let right = position.x + size.width;
|
||||
let top = position.y;
|
||||
let bottom = position.y + size.height;
|
||||
|
||||
[
|
||||
(state.x, state.y),
|
||||
(state.x + state.width, state.y),
|
||||
(state.x, state.y + state.height),
|
||||
(state.x + state.width, state.y + state.height),
|
||||
]
|
||||
.into_iter()
|
||||
.any(|(x, y)| x >= left && x < right && y >= top && y < bottom)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use log::info;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -22,6 +22,19 @@ pub mod query_manager;
|
||||
pub mod render;
|
||||
pub mod util;
|
||||
|
||||
fn sqlite_file_manager(path: impl Into<PathBuf>) -> SqliteConnectionManager {
|
||||
SqliteConnectionManager::file(path.into()).with_init(|conn| {
|
||||
conn.pragma_update(None, "journal_mode", "WAL")?;
|
||||
conn.pragma_update(None, "synchronous", "NORMAL")?;
|
||||
conn.busy_timeout(Duration::from_millis(5000))
|
||||
})
|
||||
}
|
||||
|
||||
fn sqlite_memory_manager() -> SqliteConnectionManager {
|
||||
SqliteConnectionManager::memory()
|
||||
.with_init(|conn| conn.busy_timeout(Duration::from_millis(5000)))
|
||||
}
|
||||
|
||||
/// Initialize the database managers for standalone (non-Tauri) usage.
|
||||
///
|
||||
/// Returns a tuple of (QueryManager, BlobManager, event_receiver).
|
||||
@@ -43,7 +56,7 @@ pub fn init_standalone(
|
||||
|
||||
// Main database pool
|
||||
info!("Initializing app database {db_path:?}");
|
||||
let manager = SqliteConnectionManager::file(db_path);
|
||||
let manager = sqlite_file_manager(db_path);
|
||||
let pool = Pool::builder()
|
||||
.max_size(100)
|
||||
.connection_timeout(Duration::from_secs(10))
|
||||
@@ -55,7 +68,7 @@ pub fn init_standalone(
|
||||
info!("Initializing blobs database {blob_path:?}");
|
||||
|
||||
// Blob database pool
|
||||
let blob_manager = SqliteConnectionManager::file(blob_path);
|
||||
let blob_manager = sqlite_file_manager(blob_path);
|
||||
let blob_pool = Pool::builder()
|
||||
.max_size(50)
|
||||
.connection_timeout(Duration::from_secs(10))
|
||||
@@ -75,7 +88,7 @@ pub fn init_standalone(
|
||||
/// Useful for testing and CI environments.
|
||||
pub fn init_in_memory() -> Result<(QueryManager, BlobManager, mpsc::Receiver<ModelPayload>)> {
|
||||
// Main database pool
|
||||
let manager = SqliteConnectionManager::memory();
|
||||
let manager = sqlite_memory_manager();
|
||||
let pool = Pool::builder()
|
||||
.max_size(1) // In-memory DB doesn't support multiple connections
|
||||
.build(manager)
|
||||
@@ -84,7 +97,7 @@ pub fn init_in_memory() -> Result<(QueryManager, BlobManager, mpsc::Receiver<Mod
|
||||
migrate_db(&pool)?;
|
||||
|
||||
// Blob database pool
|
||||
let blob_manager = SqliteConnectionManager::memory();
|
||||
let blob_manager = sqlite_memory_manager();
|
||||
let blob_pool = Pool::builder()
|
||||
.max_size(1)
|
||||
.build(blob_manager)
|
||||
|
||||
@@ -29,10 +29,7 @@ pub async fn delete_and_uninstall(
|
||||
let db = query_manager.connect();
|
||||
db.delete_plugin_by_id(plugin_id, &update_source)?
|
||||
};
|
||||
if let Err(err) = plugin_manager
|
||||
.uninstall(plugin_context, plugin.directory.as_str())
|
||||
.await
|
||||
{
|
||||
if let Err(err) = plugin_manager.uninstall(plugin_context, plugin.directory.as_str()).await {
|
||||
if !matches!(err, PluginNotFoundErr(_)) {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
+10
-10
@@ -684,6 +684,7 @@ pub async fn send_http_request<T: TemplateCallback>(
|
||||
}
|
||||
})?;
|
||||
let body_path = params.response_dir.join(&response.id);
|
||||
let response_body_path = body_path.to_string_lossy().to_string();
|
||||
let connected_response = HttpResponse {
|
||||
state: HttpResponseState::Connected,
|
||||
elapsed_headers: headers_elapsed,
|
||||
@@ -693,7 +694,7 @@ pub async fn send_http_request<T: TemplateCallback>(
|
||||
remote_addr: http_response.remote_addr.clone(),
|
||||
version: http_response.version.clone(),
|
||||
elapsed_dns: dns_elapsed.load(Ordering::Relaxed),
|
||||
body_path: Some(body_path.to_string_lossy().to_string()),
|
||||
body_path: Some(response_body_path.clone()),
|
||||
content_length: http_response.content_length.map(u64_to_i32),
|
||||
headers: http_response
|
||||
.headers
|
||||
@@ -724,6 +725,8 @@ pub async fn send_http_request<T: TemplateCallback>(
|
||||
let mut body_stream =
|
||||
http_response.into_body_stream().map_err(SendHttpRequestError::ReadResponseBody)?;
|
||||
let mut response_body = Vec::new();
|
||||
let mut read_buf = vec![0; 64 * 1024];
|
||||
let collect_response_body = !persist_response && params.emit_response_body_chunks_to.is_none();
|
||||
let mut body_read_error = None;
|
||||
let mut written_bytes: usize = 0;
|
||||
let mut last_progress_update = started_at;
|
||||
@@ -740,12 +743,12 @@ pub async fn send_http_request<T: TemplateCallback>(
|
||||
_ = cancelled_rx.changed() => {
|
||||
None
|
||||
}
|
||||
result = body_stream.read_buf(&mut response_body) => {
|
||||
result = body_stream.read(&mut read_buf) => {
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(body_stream.read_buf(&mut response_body).await)
|
||||
Some(body_stream.read(&mut read_buf).await)
|
||||
};
|
||||
|
||||
let Some(read_result) = read_result else {
|
||||
@@ -756,17 +759,14 @@ pub async fn send_http_request<T: TemplateCallback>(
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
written_bytes += n;
|
||||
let start_idx = response_body.len() - n;
|
||||
let chunk = &response_body[start_idx..];
|
||||
let chunk = &read_buf[..n];
|
||||
file.write_all(chunk).await.map_err(|source| {
|
||||
SendHttpRequestError::WriteResponseBody { path: body_path.clone(), source }
|
||||
})?;
|
||||
file.flush().await.map_err(|source| SendHttpRequestError::WriteResponseBody {
|
||||
path: body_path.clone(),
|
||||
source,
|
||||
})?;
|
||||
if let Some(tx) = params.emit_response_body_chunks_to.as_ref() {
|
||||
let _ = tx.send(chunk.to_vec());
|
||||
} else if collect_response_body {
|
||||
response_body.extend_from_slice(chunk);
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
@@ -854,7 +854,7 @@ pub async fn send_http_request<T: TemplateCallback>(
|
||||
|
||||
let compressed_length = http_response.content_length.unwrap_or(written_bytes as u64);
|
||||
let final_response = HttpResponse {
|
||||
body_path: Some(body_path.to_string_lossy().to_string()),
|
||||
body_path: Some(response_body_path),
|
||||
content_length: Some(usize_to_i32(written_bytes)),
|
||||
content_length_compressed: Some(u64_to_i32(compressed_length)),
|
||||
elapsed: duration_to_i32(started_at.elapsed()),
|
||||
|
||||
Generated
+126
-59
@@ -80,7 +80,7 @@
|
||||
"devDependencies": {
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@tailwindcss/postcss": "^4.3.2",
|
||||
"@tauri-apps/cli": "^2.11.1",
|
||||
"@tauri-apps/cli": "npm:@tauri-apps/cli-cef@3.0.0-alpha.6",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@yaakapp/cli": "^0.5.1",
|
||||
@@ -4143,6 +4143,72 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.11.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.2",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.2",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz",
|
||||
@@ -4458,9 +4524,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.1.tgz",
|
||||
"integrity": "sha512-rpEbaJ/HzNb6fwsquwoAbq29/Vt4gADhS423A8fdkwL4edJ0wZmoB8ar7O6JPDL834MUKOCm/rrJ7c9oAaEaYQ==",
|
||||
"name": "@tauri-apps/cli-cef",
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef/-/cli-cef-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-4Y52FZZuK6rpDDsJgxMp5q85QpQjf7Yo5IvLZs/CUBaIuMkGBDguDRcfkxhMJlu9qM/cLlBRW27OtqNdOW730w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
@@ -4474,23 +4541,23 @@
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.11.1",
|
||||
"@tauri-apps/cli-darwin-x64": "2.11.1",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.11.1",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.11.1",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.11.1",
|
||||
"@tauri-apps/cli-linux-riscv64-gnu": "2.11.1",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.11.1",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.11.1",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.11.1",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.11.1",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.11.1"
|
||||
"@tauri-apps/cli-cef-darwin-arm64": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-darwin-x64": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-linux-arm-gnueabihf": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-linux-arm64-gnu": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-linux-arm64-musl": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-linux-riscv64-gnu": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-linux-x64-gnu": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-linux-x64-musl": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-win32-arm64-msvc": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-win32-ia32-msvc": "3.0.0-alpha.6",
|
||||
"@tauri-apps/cli-cef-win32-x64-msvc": "3.0.0-alpha.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.1.tgz",
|
||||
"integrity": "sha512-6eEKMBXsQPCuM1EmvrjT2+aBuxWQuFdKdW8pzNuNQtpq45nEEpBlD5gr8pUeAyOU1DQKlkFaEc/MPBxb/Pfjtg==",
|
||||
"node_modules/@tauri-apps/cli-cef-darwin-arm64": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-darwin-arm64/-/cli-cef-darwin-arm64-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-j6XHXCku1q82SXSXlQdzT7MbgOBX/ziOD+Z4c+B5D51zsaP3h6kcefpMzs71n9ZUegdw6AXpZpk+c+knOh2trQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4504,10 +4571,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.1.tgz",
|
||||
"integrity": "sha512-LQUO7exfRWjWALNhetph5guWpMeHphRpokOLk0OIbTTExaNwJNFu3I4vb+CCM/4G/QGoZe/5XikZOJdNEFP1ig==",
|
||||
"node_modules/@tauri-apps/cli-cef-darwin-x64": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-darwin-x64/-/cli-cef-darwin-x64-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-zBNxrXomAXSZPCYspOjvoi8aVAnJtkSOy3cVD+9aYDpf2zZL4R7VQXcY4M76PvqbGUmCOXT9ztzxV2MyWnBoDQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4521,10 +4588,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.1.tgz",
|
||||
"integrity": "sha512-5i/awiBCRRhOUG8yjn0fMHXIWD5Ez8eEk5LtvOxyQrKuJkRaZDvnbIjZbE183blAwkoA4xN3aO/prJiqscl02Q==",
|
||||
"node_modules/@tauri-apps/cli-cef-linux-arm-gnueabihf": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-linux-arm-gnueabihf/-/cli-cef-linux-arm-gnueabihf-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-EcNXX9xsAZj0BuRXBwliR35XML6B5aIbgH8blbEGAr3pDaSCBnNyS/dpORgSdCKuysLidBT7K8fer5mMtbSoUQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -4538,10 +4605,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.1.tgz",
|
||||
"integrity": "sha512-9LrwDw3S9Fygtw/Q6WDhOP+3svJRGAsejeE+GKrc0eO1ThMVhwi2LL6hw4dlKw93IfS7VY1G19sWGxJ/NcU4nA==",
|
||||
"node_modules/@tauri-apps/cli-cef-linux-arm64-gnu": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-linux-arm64-gnu/-/cli-cef-linux-arm64-gnu-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-/AwSRWuE5UKGumrXdpRnIBDWVjFAMEMkSFbB/vpFx9PogQ/A0R+i6+zuBh7JPV7FKmyRzVJuNxZ8EmZ319Ir8g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4555,10 +4622,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.1.tgz",
|
||||
"integrity": "sha512-mNA5dbbqPqDUdTIwdUYYuhO2GvIe9UnB2r0VU2njxBOS3Opbx4gKNC5yP0Iu4rYmEmqdlwry9VzGZQ3wq9dyFg==",
|
||||
"node_modules/@tauri-apps/cli-cef-linux-arm64-musl": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-linux-arm64-musl/-/cli-cef-linux-arm64-musl-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-OZ5gjROy4/TlaYQ33roxQYcFIFJH+HZMER9gwTkWxfYUC1gTLLhsBeIYUZzWqm1NQiIc07likg081wsHkCRrWw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4572,10 +4639,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.1.tgz",
|
||||
"integrity": "sha512-fZj3Gwq+6fUs305T5WQiD5iSGJw+j/4w/HGmk4sHDAcy+rp9zU5eaxB7nOyz5/I/nkNAuKPqfp6uIbiUBXkBCw==",
|
||||
"node_modules/@tauri-apps/cli-cef-linux-riscv64-gnu": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-linux-riscv64-gnu/-/cli-cef-linux-riscv64-gnu-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-r9TQKmN4KfeXiiusn0cppRxljKsf96vTsC1ehYi4beh+6JlmGR4pK7eb+LuOGJscnjurAA9b153fpeYO/O1PTw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -4589,10 +4656,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.1.tgz",
|
||||
"integrity": "sha512-XFxGxOvHM7jjeD6ozCKdGfhzJ7lERYDGZl1/Kb4fsvchaJsfLJ981TlyTG8Qy/gFq+f5GitH3bfrX9JAkjPEyw==",
|
||||
"node_modules/@tauri-apps/cli-cef-linux-x64-gnu": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-linux-x64-gnu/-/cli-cef-linux-x64-gnu-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-9rrMBVlqbNlp8nTJuZIDQ7iKFwGaVMRCNPIsUIZ56DiaCLoP4po+yvR48n+TyRPZYp1sb3q2Nr/5/zI2q/jFAQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4606,10 +4673,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.1.tgz",
|
||||
"integrity": "sha512-d5C2/Zm+68v7R9wTuTCjRQEVrWjcdMkJBZ1+rXse+QdMMlTB9+u9PDNDLw9PQflWxYLaYZ7tjxxL9Nb9II6PbA==",
|
||||
"node_modules/@tauri-apps/cli-cef-linux-x64-musl": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-linux-x64-musl/-/cli-cef-linux-x64-musl-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-pcFL71xscjEM/03lrH/GiqIr4SwBWlbvaRh8GxDePJqqrQojNn+kTzVvqowaXVbTrNr5rbHbXzY4rC/5/1Tbsw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4623,10 +4690,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.1.tgz",
|
||||
"integrity": "sha512-YdeVWFAR1pTXzUU6NLstPq4G6OLxuDrXCXEBdmBH+5EZIDXUx0D2kJlz3+YjpazkKvAzYpgziTsyRagls0OfRQ==",
|
||||
"node_modules/@tauri-apps/cli-cef-win32-arm64-msvc": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-win32-arm64-msvc/-/cli-cef-win32-arm64-msvc-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-IrMgveBs0HNO7C7BaZCUHGtO9jPbop/iU/5XWNqBx7uYHJL565+yIlpzWPoXhT3qbt8h8FQFO6SU3N+zfvLipA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4640,10 +4707,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.1.tgz",
|
||||
"integrity": "sha512-VBGkuH0eB9K9LLSMv361Gzr5Ou72sCS4+ztpmkWEQ+wd/amhcYOsf3X6qn1RJZDzIhiOYHJEOysZUC3baD01rA==",
|
||||
"node_modules/@tauri-apps/cli-cef-win32-ia32-msvc": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-win32-ia32-msvc/-/cli-cef-win32-ia32-msvc-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-qA/VaRdRwdMt9L2oDGNZPur108mUleKRUSCk6X8sVPcnPjzMfN4/aESeOKsW+zHXN/VhZbV3UieEg4IcYeI2RQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -4657,10 +4724,10 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.1.tgz",
|
||||
"integrity": "sha512-b3ORhIAKgp9ZYY+zBt7b7r0kLU2kjvyGF0+MS2SBym3emsweGPybEqocJcmtMuxyBhkOKHP4CiuEJEDuAlTx6A==",
|
||||
"node_modules/@tauri-apps/cli-cef-win32-x64-msvc": {
|
||||
"version": "3.0.0-alpha.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-cef-win32-x64-msvc/-/cli-cef-win32-x64-msvc-3.0.0-alpha.6.tgz",
|
||||
"integrity": "sha512-EzlhstDVyvHy3M5ieSoH3VBlikgqQS99vFEDKFoy16mwIWUrTG1LkoSI0mKbqmO8DWIEzeSV17XiCw2XEcYslQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@
|
||||
"devDependencies": {
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@tailwindcss/postcss": "^4.3.2",
|
||||
"@tauri-apps/cli": "^2.11.1",
|
||||
"@tauri-apps/cli": "npm:@tauri-apps/cli-cef@3.0.0-alpha.6",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"@yaakapp/cli": "^0.5.1",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
|
||||
export type Appearance = "light" | "dark";
|
||||
|
||||
const SYSTEM_APPEARANCE_CHANGE_EVENT = "system_appearance_change";
|
||||
|
||||
export function getCSSAppearance(): Appearance {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
@@ -11,6 +14,13 @@ export async function getWindowAppearance(): Promise<Appearance> {
|
||||
return appearance ?? getCSSAppearance();
|
||||
}
|
||||
|
||||
export function subscribeToCSSAppearanceChange(cb: (appearance: Appearance) => void): () => void {
|
||||
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const listener = () => cb(media.matches ? "dark" : "light");
|
||||
media.addEventListener("change", listener);
|
||||
return () => media.removeEventListener("change", listener);
|
||||
}
|
||||
|
||||
export function subscribeToWindowAppearanceChange(
|
||||
cb: (appearance: Appearance) => void,
|
||||
): () => void {
|
||||
@@ -29,6 +39,22 @@ export function subscribeToWindowAppearanceChange(
|
||||
return () => container.unsubscribe();
|
||||
}
|
||||
|
||||
export function subscribeToSystemAppearanceChange(
|
||||
cb: (appearance: Appearance) => void,
|
||||
): () => void {
|
||||
const container = {
|
||||
unsubscribe: () => {},
|
||||
};
|
||||
|
||||
void listen<Appearance>(SYSTEM_APPEARANCE_CHANGE_EVENT, (event) => {
|
||||
cb(event.payload);
|
||||
}).then((listener) => {
|
||||
container.unsubscribe = listener;
|
||||
});
|
||||
|
||||
return () => container.unsubscribe();
|
||||
}
|
||||
|
||||
export function resolveAppearance(
|
||||
preferredAppearance: Appearance,
|
||||
appearanceSetting: string,
|
||||
@@ -40,5 +66,16 @@ export function resolveAppearance(
|
||||
export function subscribeToPreferredAppearance(cb: (appearance: Appearance) => void) {
|
||||
cb(getCSSAppearance());
|
||||
void getWindowAppearance().then(cb);
|
||||
subscribeToWindowAppearanceChange(cb);
|
||||
return subscribeToPreferredAppearanceChange(cb);
|
||||
}
|
||||
|
||||
export function subscribeToPreferredAppearanceChange(cb: (appearance: Appearance) => void) {
|
||||
const unsubscribeCSS = subscribeToCSSAppearanceChange(cb);
|
||||
const unsubscribeWindow = subscribeToWindowAppearanceChange(cb);
|
||||
const unsubscribeSystem = subscribeToSystemAppearanceChange(cb);
|
||||
return () => {
|
||||
unsubscribeCSS();
|
||||
unsubscribeWindow();
|
||||
unsubscribeSystem();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
export type { Appearance } from "./appearance";
|
||||
export {
|
||||
subscribeToCSSAppearanceChange,
|
||||
getCSSAppearance,
|
||||
getWindowAppearance,
|
||||
resolveAppearance,
|
||||
subscribeToPreferredAppearance,
|
||||
subscribeToPreferredAppearanceChange,
|
||||
subscribeToSystemAppearanceChange,
|
||||
subscribeToWindowAppearanceChange,
|
||||
} from "./appearance";
|
||||
export { defaultDarkTheme, defaultLightTheme } from "./defaultThemes";
|
||||
|
||||
+2
-1
@@ -70,7 +70,8 @@ const normalizedAdditionalArgs = [];
|
||||
for (let i = 0; i < additionalArgs.length; i++) {
|
||||
const arg = additionalArgs[i];
|
||||
if (arg === "--") {
|
||||
continue;
|
||||
normalizedAdditionalArgs.push(arg, ...additionalArgs.slice(i + 1));
|
||||
break;
|
||||
}
|
||||
if (arg === "--config" && i + 1 < additionalArgs.length) {
|
||||
const value = additionalArgs[i + 1];
|
||||
|
||||
Reference in New Issue
Block a user