Compare commits

..

10 Commits

Author SHA1 Message Date
Gregory Schier
b9b90188a0 Revert "plugin-events: move model/find handlers to shared path"
This reverts commit 072b486857.
2026-02-26 10:13:36 -08:00
Gregory Schier
e64404d7a5 plugins: keep dev workspace plugins in watch mode 2026-02-26 09:53:58 -08:00
Gregory Schier
072b486857 plugin-events: move model/find handlers to shared path 2026-02-26 09:51:17 -08:00
Gregory Schier
23e9cbb376 cli: inline cwd plugins resolver 2026-02-26 09:22:54 -08:00
Gregory Schier
9c09e32a56 cli: only use cwd plugins in debug builds 2026-02-26 09:19:59 -08:00
Gregory Schier
be26cc4db4 cli: prefer cwd plugins dir when present 2026-02-26 09:19:18 -08:00
Gregory Schier
a2f12aef35 cli: use explicit flag for workspace plugin dir 2026-02-26 09:17:11 -08:00
Gregory Schier
e34301ccab plugins: have callers provide bundled plugin dir 2026-02-26 08:36:42 -08:00
Gregory Schier
8f0062f917 cli: avoid tauri dev path logic in plugin manager 2026-02-26 08:30:35 -08:00
Gregory Schier
68d68035a1 cli: use workspace plugins dir in dev mode 2026-02-26 08:22:44 -08:00
1117 changed files with 18930 additions and 27794 deletions

View File

@@ -1,27 +1,23 @@
# Claude Context: Detaching Tauri from Yaak # Claude Context: Detaching Tauri from Yaak
## Goal ## Goal
Make Yaak runnable as a standalone CLI without Tauri as a dependency. The core Rust crates in `crates/` should be usable independently, while Tauri-specific code lives in `crates-tauri/`. Make Yaak runnable as a standalone CLI without Tauri as a dependency. The core Rust crates in `crates/` should be usable independently, while Tauri-specific code lives in `crates-tauri/`.
## Project Structure ## Project Structure
``` ```
crates/ # Core crates - should NOT depend on Tauri crates/ # Core crates - should NOT depend on Tauri
crates-tauri/ # Tauri-specific crates (yaak-app-client, yaak-tauri-utils, etc.) crates-tauri/ # Tauri-specific crates (yaak-app, yaak-tauri-utils, etc.)
crates-cli/ # CLI crate (yaak-cli) crates-cli/ # CLI crate (yaak-cli)
``` ```
## Completed Work ## Completed Work
### 1. Folder Restructure ### 1. Folder Restructure
- Moved Tauri-dependent app code to `crates-tauri/yaak-app/`
- Moved Tauri-dependent app code to `crates-tauri/yaak-app-client/`
- Created `crates-tauri/yaak-tauri-utils/` for shared Tauri utilities (window traits, api_client, error handling) - Created `crates-tauri/yaak-tauri-utils/` for shared Tauri utilities (window traits, api_client, error handling)
- Created `crates-cli/yaak-cli/` for the standalone CLI - Created `crates-cli/yaak-cli/` for the standalone CLI
### 2. Decoupled Crates (no longer depend on Tauri) ### 2. Decoupled Crates (no longer depend on Tauri)
- **yaak-models**: Uses `init_standalone()` pattern for CLI database access - **yaak-models**: Uses `init_standalone()` pattern for CLI database access
- **yaak-http**: Removed Tauri plugin, HttpConnectionManager initialized in yaak-app setup - **yaak-http**: Removed Tauri plugin, HttpConnectionManager initialized in yaak-app setup
- **yaak-common**: Only contains Tauri-free utilities (serde, platform) - **yaak-common**: Only contains Tauri-free utilities (serde, platform)
@@ -29,7 +25,6 @@ crates-cli/ # CLI crate (yaak-cli)
- **yaak-grpc**: Replaced AppHandle with GrpcConfig struct, uses tokio::process::Command instead of Tauri sidecar - **yaak-grpc**: Replaced AppHandle with GrpcConfig struct, uses tokio::process::Command instead of Tauri sidecar
### 3. CLI Implementation ### 3. CLI Implementation
- Basic CLI at `crates-cli/yaak-cli/src/main.rs` - Basic CLI at `crates-cli/yaak-cli/src/main.rs`
- Commands: workspaces, requests, send (by ID), get (ad-hoc URL), create - Commands: workspaces, requests, send (by ID), get (ad-hoc URL), create
- Uses same database as Tauri app via `yaak_models::init_standalone()` - Uses same database as Tauri app via `yaak_models::init_standalone()`
@@ -37,36 +32,31 @@ crates-cli/ # CLI crate (yaak-cli)
## Remaining Work ## Remaining Work
### Crates Still Depending on Tauri (in `crates/`) ### Crates Still Depending on Tauri (in `crates/`)
1. **yaak-git** (3 files) - Moderate complexity 1. **yaak-git** (3 files) - Moderate complexity
2. **yaak-plugins** (13 files) - **Hardest** - deeply integrated with Tauri for plugin-to-window communication 2. **yaak-plugins** (13 files) - **Hardest** - deeply integrated with Tauri for plugin-to-window communication
3. **yaak-sync** (4 files) - Moderate complexity 3. **yaak-sync** (4 files) - Moderate complexity
4. **yaak-ws** (5 files) - Moderate complexity 4. **yaak-ws** (5 files) - Moderate complexity
### Pattern for Decoupling ### Pattern for Decoupling
1. Remove Tauri plugin `init()` function from the crate 1. Remove Tauri plugin `init()` function from the crate
2. Move commands to `yaak-app/src/commands.rs` or keep inline in `lib.rs` 2. Move commands to `yaak-app/src/commands.rs` or keep inline in `lib.rs`
3. Move extension traits (e.g., `SomethingManagerExt`) to yaak-app or yaak-tauri-utils 3. Move extension traits (e.g., `SomethingManagerExt`) to yaak-app or yaak-tauri-utils
4. Initialize managers in yaak-app's `.setup()` block 4. Initialize managers in yaak-app's `.setup()` block
5. Remove `tauri` from Cargo.toml dependencies 5. Remove `tauri` from Cargo.toml dependencies
6. Update `crates-tauri/yaak-app-client/capabilities/default.json` to remove the plugin permission 6. Update `crates-tauri/yaak-app/capabilities/default.json` to remove the plugin permission
7. Replace `tauri::async_runtime::block_on` with `tokio::runtime::Handle::current().block_on()` 7. Replace `tauri::async_runtime::block_on` with `tokio::runtime::Handle::current().block_on()`
## Key Files ## Key Files
- `crates-tauri/yaak-app/src/lib.rs` - Main Tauri app, setup block initializes managers
- `crates-tauri/yaak-app-client/src/lib.rs` - Main Tauri app, setup block initializes managers - `crates-tauri/yaak-app/src/commands.rs` - Migrated Tauri commands
- `crates-tauri/yaak-app-client/src/commands.rs` - Migrated Tauri commands - `crates-tauri/yaak-app/src/models_ext.rs` - Database plugin and extension traits
- `crates-tauri/yaak-app-client/src/models_ext.rs` - Database plugin and extension traits
- `crates-tauri/yaak-tauri-utils/src/window.rs` - WorkspaceWindowTrait for window state - `crates-tauri/yaak-tauri-utils/src/window.rs` - WorkspaceWindowTrait for window state
- `crates/yaak-models/src/lib.rs` - Contains `init_standalone()` for CLI usage - `crates/yaak-models/src/lib.rs` - Contains `init_standalone()` for CLI usage
## Git Branch ## Git Branch
Working on `detach-tauri` branch. Working on `detach-tauri` branch.
## Recent Commits ## Recent Commits
``` ```
c40cff40 Remove Tauri dependencies from yaak-crypto and yaak-grpc c40cff40 Remove Tauri dependencies from yaak-crypto and yaak-grpc
df495f1d Move Tauri utilities from yaak-common to yaak-tauri-utils df495f1d Move Tauri utilities from yaak-common to yaak-tauri-utils
@@ -77,7 +67,6 @@ e718a5f1 Refactor models_ext to use init_standalone from yaak-models
``` ```
## Testing ## Testing
- Run `cargo check -p <crate>` to verify a crate builds without Tauri - Run `cargo check -p <crate>` to verify a crate builds without Tauri
- Run `npm run client:dev` to test the Tauri app still works - Run `npm run app-dev` to test the Tauri app still works
- Run `cargo run -p yaak-cli -- --help` to test the CLI - Run `cargo run -p yaak-cli -- --help` to test the CLI

View File

@@ -0,0 +1,62 @@
---
description: Review a PR in a new worktree
allowed-tools: Bash(git worktree:*), Bash(gh pr:*), Bash(git branch:*)
---
Check out a GitHub pull request for review.
## Usage
```
/check-out-pr <PR_NUMBER>
```
## What to do
1. If no PR number is provided, list all open pull requests and ask the user to select one
2. Get PR information using `gh pr view <PR_NUMBER> --json number,headRefName`
3. **Ask the user** whether they want to:
- **A) Check out in current directory** — simple `gh pr checkout <PR_NUMBER>`
- **B) Create a new worktree** — isolated copy at `../yaak-worktrees/pr-<PR_NUMBER>`
4. Follow the appropriate path below
## Option A: Check out in current directory
1. Run `gh pr checkout <PR_NUMBER>`
2. Inform the user which branch they're now on
## Option B: Create a new worktree
1. Create a new worktree at `../yaak-worktrees/pr-<PR_NUMBER>` using `git worktree add` with a timeout of at least 300000ms (5 minutes) since the post-checkout hook runs a bootstrap script
2. Checkout the PR branch in the new worktree using `gh pr checkout <PR_NUMBER>`
3. The post-checkout hook will automatically:
- Create `.env.local` with unique ports
- Copy editor config folders
- Run `npm install && npm run bootstrap`
4. Inform the user:
- Where the worktree was created
- What ports were assigned
- How to access it (cd command)
- How to run the dev server
- How to remove the worktree when done
### Example worktree output
```
Created worktree for PR #123 at ../yaak-worktrees/pr-123
Branch: feature-auth
Ports: Vite (1421), MCP (64344)
To start working:
cd ../yaak-worktrees/pr-123
npm run app-dev
To remove when done:
git worktree remove ../yaak-worktrees/pr-123
```
## Error Handling
- If the PR doesn't exist, show a helpful error
- If the worktree already exists, inform the user and ask if they want to remove and recreate it
- If `gh` CLI is not available, inform the user to install it

View File

@@ -8,7 +8,7 @@ Generate formatted release notes for Yaak releases by analyzing git history and
## What to do ## What to do
1. Identifies the version tag and previous version 1. Identifies the version tag and previous version
2. Retrieves all commits between versions 2. Retrieves all commits between versions
- If the version is a beta version, it retrieves commits between the beta version and previous beta version - If the version is a beta version, it retrieves commits between the beta version and previous beta version
- If the version is a stable version, it retrieves commits between the stable version and the previous stable version - If the version is a stable version, it retrieves commits between the stable version and the previous stable version
3. Fetches PR descriptions for linked issues to find: 3. Fetches PR descriptions for linked issues to find:
@@ -37,7 +37,6 @@ The skill generates markdown-formatted release notes following this structure:
**IMPORTANT**: Always add a blank lines around the markdown code fence and output the markdown code block last **IMPORTANT**: Always add a blank lines around the markdown code fence and output the markdown code block last
**IMPORTANT**: PRs by `@gschier` should not mention the @username **IMPORTANT**: PRs by `@gschier` should not mention the @username
**IMPORTANT**: These are app release notes. Exclude CLI-only changes (commits prefixed with `cli:` or only touching `crates-cli/`) since the CLI has its own release process.
## After Generating Release Notes ## After Generating Release Notes

View File

@@ -0,0 +1,35 @@
# Worktree Management Skill
## Creating Worktrees
When creating git worktrees for this project, ALWAYS use the path format:
```
../yaak-worktrees/<NAME>
```
For example:
- `git worktree add ../yaak-worktrees/feature-auth`
- `git worktree add ../yaak-worktrees/bugfix-login`
- `git worktree add ../yaak-worktrees/refactor-api`
## What Happens Automatically
The post-checkout hook will automatically:
1. Create `.env.local` with unique ports (YAAK_DEV_PORT and YAAK_PLUGIN_MCP_SERVER_PORT)
2. Copy gitignored editor config folders (.zed, .idea, etc.)
3. Run `npm install && npm run bootstrap`
## Deleting Worktrees
```bash
git worktree remove ../yaak-worktrees/<NAME>
```
## Port Assignments
- Main worktree: 1420 (Vite), 64343 (MCP)
- First worktree: 1421, 64344
- Second worktree: 1422, 64345
- etc.
Each worktree can run `npm run app-dev` simultaneously without conflicts.

View File

@@ -0,0 +1,46 @@
---
name: release-check-out-pr
description: Check out a GitHub pull request for review in this repo, either in the current directory or in a new isolated worktree at ../yaak-worktrees/pr-<PR_NUMBER>. Use when asked to run or replace the old Claude check-out-pr command.
---
# Check Out PR
Check out a PR by number and let the user choose between current-directory checkout and isolated worktree checkout.
## Workflow
1. Confirm `gh` CLI is available.
2. If no PR number is provided, list open PRs (`gh pr list`) and ask the user to choose one.
3. Read PR metadata:
- `gh pr view <PR_NUMBER> --json number,headRefName`
4. Ask the user to choose:
- Option A: check out in the current directory
- Option B: create a new worktree at `../yaak-worktrees/pr-<PR_NUMBER>`
## Option A: Current Directory
1. Run:
- `gh pr checkout <PR_NUMBER>`
2. Report the checked-out branch.
## Option B: New Worktree
1. Use path:
- `../yaak-worktrees/pr-<PR_NUMBER>`
2. Create the worktree with a timeout of at least 5 minutes because checkout hooks run bootstrap.
3. In the new worktree, run:
- `gh pr checkout <PR_NUMBER>`
4. Report:
- Worktree path
- Assigned ports from `.env.local` if present
- How to start work:
- `cd ../yaak-worktrees/pr-<PR_NUMBER>`
- `npm run app-dev`
- How to remove when done:
- `git worktree remove ../yaak-worktrees/pr-<PR_NUMBER>`
## Error Handling
- If PR does not exist, show a clear error.
- If worktree already exists, ask whether to reuse it or remove/recreate it.
- If `gh` is missing, instruct the user to install/authenticate it.

View File

@@ -32,7 +32,6 @@ Generate formatted markdown release notes for a Yaak tag.
- Keep a blank line before and after the code fence. - Keep a blank line before and after the code fence.
- Output the markdown code block last. - Output the markdown code block last.
- Do not append `by @gschier` for PRs authored by `@gschier`. - Do not append `by @gschier` for PRs authored by `@gschier`.
- These are app release notes. Exclude CLI-only changes (commits prefixed with `cli:` or only touching `crates-cli/`) since the CLI has its own release process.
## Release Creation Prompt ## Release Creation Prompt

View File

@@ -0,0 +1,37 @@
---
name: worktree-management
description: Manage Yaak git worktrees using the standard ../yaak-worktrees/<NAME> layout, including creation, removal, and expected automatic setup behavior and port assignments.
---
# Worktree Management
Use the Yaak-standard worktree path layout and lifecycle commands.
## Path Convention
Always create worktrees under:
`../yaak-worktrees/<NAME>`
Examples:
- `git worktree add ../yaak-worktrees/feature-auth`
- `git worktree add ../yaak-worktrees/bugfix-login`
- `git worktree add ../yaak-worktrees/refactor-api`
## Automatic Setup After Checkout
Project git hooks automatically:
1. Create `.env.local` with unique `YAAK_DEV_PORT` and `YAAK_PLUGIN_MCP_SERVER_PORT`
2. Copy gitignored editor config folders
3. Run `npm install && npm run bootstrap`
## Remove Worktree
`git worktree remove ../yaak-worktrees/<NAME>`
## Port Pattern
- Main worktree: Vite `1420`, MCP `64343`
- First extra worktree: `1421`, `64344`
- Second extra worktree: `1422`, `64345`
- Continue incrementally for additional worktrees

4
.gitattributes vendored
View File

@@ -1,5 +1,5 @@
crates-tauri/yaak-app-client/vendored/**/* linguist-generated=true crates-tauri/yaak-app/vendored/**/* linguist-generated=true
crates-tauri/yaak-app-client/gen/schemas/**/* linguist-generated=true crates-tauri/yaak-app/gen/schemas/**/* linguist-generated=true
**/bindings/* linguist-generated=true **/bindings/* linguist-generated=true
crates/yaak-templates/pkg/* linguist-generated=true crates/yaak-templates/pkg/* linguist-generated=true

View File

@@ -1,9 +1,10 @@
--- ---
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: "" title: ''
labels: "" labels: ''
assignees: "" assignees: ''
--- ---
**Describe the bug** **Describe the bug**
@@ -11,7 +12,6 @@ A clear and concise description of what the bug is.
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
@@ -24,17 +24,15 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
- OS: [e.g. iOS]
- OS: [e.g. iOS] - Browser [e.g. chrome, safari]
- Browser [e.g. chrome, safari] - Version [e.g. 22]
- Version [e.g. 22]
**Smartphone (please complete the following information):** **Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- Device: [e.g. iPhone6] - OS: [e.g. iOS8.1]
- OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari]
- Browser [e.g. stock browser, safari] - Version [e.g. 22]
- Version [e.g. 22]
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.

View File

@@ -11,7 +11,6 @@
- [ ] I added or updated tests when reasonable. - [ ] I added or updated tests when reasonable.
Approved feedback item (required if not a bug fix or small-scope improvement): Approved feedback item (required if not a bug fix or small-scope improvement):
<!-- https://yaak.app/feedback/... --> <!-- https://yaak.app/feedback/... -->
## Related ## Related

View File

@@ -14,20 +14,17 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: voidzero-dev/setup-vp@v1 - uses: actions/setup-node@v4
with:
node-version: "24"
cache: true
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
shared-key: ci shared-key: ci
cache-on-failure: true cache-on-failure: true
- run: vp install - run: npm ci
- run: npm run bootstrap - run: npm run bootstrap
- run: npm run lint - run: npm run lint
- name: Run JS Tests - name: Run JS Tests
run: vp test run: npm test
- name: Run Rust Tests - name: Run Rust Tests
run: cargo test --all run: cargo test --all

View File

@@ -47,3 +47,4 @@ jobs:
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options # or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)' # claude_args: '--allowed-tools Bash(gh pr:*)'

View File

@@ -50,11 +50,8 @@ jobs:
- name: Checkout yaakapp/app - name: Checkout yaakapp/app
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Vite+ - name: Setup Node
uses: voidzero-dev/setup-vp@v1 uses: actions/setup-node@v4
with:
node-version: "24"
cache: true
- name: install Rust stable - name: install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
@@ -90,15 +87,15 @@ jobs:
echo $dir >> $env:GITHUB_PATH echo $dir >> $env:GITHUB_PATH
& $exe --version & $exe --version
- run: vp install - run: npm ci
- run: npm run bootstrap - run: npm run bootstrap
env: env:
YAAK_TARGET_ARCH: ${{ matrix.yaak_arch }} YAAK_TARGET_ARCH: ${{ matrix.yaak_arch }}
- run: npm run lint - run: npm run lint
- name: Run JS Tests - name: Run JS Tests
run: vp test run: npm test
- name: Run Rust Tests - name: Run Rust Tests
run: cargo test --all --exclude yaak-cli run: cargo test --all
- name: Set version - name: Set version
run: npm run replace-version run: npm run replace-version
@@ -125,8 +122,8 @@ jobs:
security list-keychain -d user -s $KEYCHAIN_PATH security list-keychain -d user -s $KEYCHAIN_PATH
# Sign vendored binaries with hardened runtime and their specific entitlements # Sign vendored binaries with hardened runtime and their specific entitlements
codesign --force --options runtime --entitlements crates-tauri/yaak-app-client/macos/entitlements.yaakprotoc.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app-client/vendored/protoc/yaakprotoc || true codesign --force --options runtime --entitlements crates-tauri/yaak-app/macos/entitlements.yaakprotoc.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app/vendored/protoc/yaakprotoc || true
codesign --force --options runtime --entitlements crates-tauri/yaak-app-client/macos/entitlements.yaaknode.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app-client/vendored/node/yaaknode || true codesign --force --options runtime --entitlements crates-tauri/yaak-app/macos/entitlements.yaaknode.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app/vendored/node/yaaknode || true
- uses: tauri-apps/tauri-action@v0 - uses: tauri-apps/tauri-action@v0
env: env:
@@ -155,7 +152,7 @@ jobs:
releaseBody: "[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)" releaseBody: "[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)"
releaseDraft: true releaseDraft: true
prerelease: true prerelease: true
args: "${{ matrix.args }} --config ./crates-tauri/yaak-app-client/tauri.release.conf.json" args: "${{ matrix.args }} --config ./crates-tauri/yaak-app/tauri.release.conf.json"
# Build a per-machine NSIS installer for enterprise deployment (PDQ, SCCM, Intune) # Build a per-machine NSIS installer for enterprise deployment (PDQ, SCCM, Intune)
- name: Build and upload machine-wide installer (Windows only) - name: Build and upload machine-wide installer (Windows only)
@@ -171,7 +168,7 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
run: | run: |
Get-ChildItem -Recurse -Path target -File -Filter "*.exe.sig" | Remove-Item -Force Get-ChildItem -Recurse -Path target -File -Filter "*.exe.sig" | Remove-Item -Force
npx tauri bundle ${{ matrix.args }} --bundles nsis --config ./crates-tauri/yaak-app-client/tauri.release.conf.json --config '{"bundle":{"createUpdaterArtifacts":true,"windows":{"nsis":{"installMode":"perMachine"}}}}' npx tauri bundle ${{ matrix.args }} --bundles nsis --config ./crates-tauri/yaak-app/tauri.release.conf.json --config '{"bundle":{"createUpdaterArtifacts":true,"windows":{"nsis":{"installMode":"perMachine"}}}}'
$setup = Get-ChildItem -Recurse -Path target -Filter "*setup*.exe" | Select-Object -First 1 $setup = Get-ChildItem -Recurse -Path target -Filter "*setup*.exe" | Select-Object -First 1
$setupSig = "$($setup.FullName).sig" $setupSig = "$($setup.FullName).sig"
$dest = $setup.FullName -replace '-setup\.exe$', '-setup-machine.exe' $dest = $setup.FullName -replace '-setup\.exe$', '-setup-machine.exe'

View File

@@ -45,8 +45,8 @@ jobs:
with: with:
name: vendored-assets name: vendored-assets
path: | path: |
crates-tauri/yaak-app-client/vendored/plugin-runtime/index.cjs crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs
crates-tauri/yaak-app-client/vendored/plugins crates-tauri/yaak-app/vendored/plugins
if-no-files-found: error if-no-files-found: error
build-binaries: build-binaries:
@@ -107,7 +107,7 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: vendored-assets name: vendored-assets
path: crates-tauri/yaak-app-client/vendored path: crates-tauri/yaak-app/vendored
- name: Set CLI build version - name: Set CLI build version
shell: bash shell: bash

View File

@@ -16,23 +16,23 @@ jobs:
uses: JamesIves/github-sponsors-readme-action@v1 uses: JamesIves/github-sponsors-readme-action@v1
with: with:
token: ${{ secrets.SPONSORS_PAT }} token: ${{ secrets.SPONSORS_PAT }}
file: "README.md" file: 'README.md'
maximum: 1999 maximum: 1999
template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="50px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;' template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="50px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;'
active-only: false active-only: false
include-private: true include-private: true
marker: "sponsors-base" marker: 'sponsors-base'
- name: Generate Sponsors - name: Generate Sponsors
uses: JamesIves/github-sponsors-readme-action@v1 uses: JamesIves/github-sponsors-readme-action@v1
with: with:
token: ${{ secrets.SPONSORS_PAT }} token: ${{ secrets.SPONSORS_PAT }}
file: "README.md" file: 'README.md'
minimum: 2000 minimum: 2000
template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="80px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;' template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="80px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;'
active-only: false active-only: false
include-private: true include-private: true
marker: "sponsors-premium" marker: 'sponsors-premium'
# ⚠️ Note: You can use any deployment step here to automatically push the README # ⚠️ Note: You can use any deployment step here to automatically push the README
# changes back to your branch. # changes back to your branch.
@@ -41,4 +41,4 @@ jobs:
with: with:
branch: main branch: main
force: false force: false
folder: "." folder: '.'

6
.gitignore vendored
View File

@@ -39,8 +39,7 @@ codebook.toml
target target
# Per-worktree Tauri config (generated by post-checkout hook) # Per-worktree Tauri config (generated by post-checkout hook)
crates-tauri/yaak-app-client/tauri.worktree.conf.json crates-tauri/yaak-app/tauri.worktree.conf.json
crates-tauri/yaak-app-proxy/tauri.worktree.conf.json
# Tauri auto-generated permission files # Tauri auto-generated permission files
**/permissions/autogenerated **/permissions/autogenerated
@@ -55,6 +54,3 @@ flatpak/node-sources.json
# Local Codex desktop env state # Local Codex desktop env state
.codex/environments/environment.toml .codex/environments/environment.toml
# Claude Code local settings
.claude/settings.local.json

View File

@@ -1 +0,0 @@
24.14.0

2
.npmrc
View File

@@ -1,2 +0,0 @@
# vite-plugin-wasm has not yet declared Vite 8 in its peerDependencies
legacy-peer-deps=true

View File

@@ -1,3 +0,0 @@
**/bindings/**
**/routeTree.gen.ts
crates/yaak-templates/pkg/**

View File

@@ -1 +0,0 @@
vp lint

View File

@@ -1,7 +1,3 @@
{ {
"recommendations": [ "recommendations": ["biomejs.biome", "rust-lang.rust-analyzer", "bradlc.vscode-tailwindcss"]
"rust-lang.rust-analyzer",
"bradlc.vscode-tailwindcss",
"VoidZero.vite-plus-extension-pack"
]
} }

View File

@@ -1,8 +1,6 @@
{ {
"editor.defaultFormatter": "oxc.oxc-vscode", "editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.formatOnSaveMode": "file", "biome.enabled": true,
"editor.codeActionsOnSave": { "biome.lint.format.enable": true
"source.fixAll.oxc": "explicit"
}
} }

View File

@@ -1,2 +0,0 @@
- Tag safety: app releases use `v*` tags and CLI releases use `yaak-cli-*` tags; always confirm which one is requested before retagging.
- Do not commit, push, or tag without explicit approval

397
Cargo.lock generated
View File

@@ -173,17 +173,6 @@ version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "apollo-parser"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "947e21ff51879f8a40d7519dfe619268de2afba4042a8a43878276de3cb910f0"
dependencies = [
"memchr",
"rowan",
"thiserror 2.0.17",
]
[[package]] [[package]]
name = "append-only-vec" name = "append-only-vec"
version = "0.1.8" version = "0.1.8"
@@ -488,28 +477,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "aws-lc-rs"
version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.7.9" version = "0.7.9"
@@ -1233,7 +1200,7 @@ dependencies = [
"encode_unicode", "encode_unicode",
"libc", "libc",
"once_cell", "once_cell",
"unicode-width 0.2.2", "unicode-width",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -1380,12 +1347,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "countme"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
[[package]] [[package]]
name = "cow-utils" name = "cow-utils"
version = "0.1.3" version = "0.1.3"
@@ -1444,31 +1405,6 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossterm"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio 0.8.11",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "crunchy" name = "crunchy"
version = "0.2.3" version = "0.2.3"
@@ -2231,12 +2167,6 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "fsevent-sys" name = "fsevent-sys"
version = "4.1.0" version = "4.1.0"
@@ -2364,15 +2294,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "fuzzy-matcher"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
dependencies = [
"thread_local 1.1.9",
]
[[package]] [[package]]
name = "fxhash" name = "fxhash"
version = "0.2.1" version = "0.2.1"
@@ -3243,24 +3164,6 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "inquire"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
dependencies = [
"bitflags 2.11.0",
"crossterm",
"dyn-clone",
"fuzzy-matcher",
"fxhash",
"newline-converter",
"once_cell",
"tempfile",
"unicode-segmentation",
"unicode-width 0.1.14",
]
[[package]] [[package]]
name = "interfaces" name = "interfaces"
version = "0.0.8" version = "0.0.8"
@@ -3853,18 +3756,6 @@ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log 0.4.29",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.4" version = "1.0.4"
@@ -3960,15 +3851,6 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "newline-converter"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "nibble_vec" name = "nibble_vec"
version = "0.1.0" version = "0.1.0"
@@ -4060,7 +3942,7 @@ dependencies = [
"kqueue", "kqueue",
"libc", "libc",
"log 0.4.29", "log 0.4.29",
"mio 1.0.4", "mio",
"notify-types", "notify-types",
"walkdir", "walkdir",
"windows-sys 0.59.0", "windows-sys 0.59.0",
@@ -4601,7 +4483,7 @@ checksum = "75b1853bc34cadaa90aa09f95713d8b77ec0c0d3e2d90ccf7a74216f40d20850"
dependencies = [ dependencies = [
"flate2", "flate2",
"postcard", "postcard",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.17", "thiserror 2.0.17",
@@ -4619,7 +4501,7 @@ dependencies = [
"textwrap", "textwrap",
"thiserror 2.0.17", "thiserror 2.0.17",
"unicode-segmentation", "unicode-segmentation",
"unicode-width 0.2.2", "unicode-width",
] ]
[[package]] [[package]]
@@ -4644,7 +4526,7 @@ dependencies = [
"hashbrown 0.16.1", "hashbrown 0.16.1",
"oxc_data_structures", "oxc_data_structures",
"oxc_estree", "oxc_estree",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
] ]
@@ -4700,7 +4582,7 @@ dependencies = [
"oxc_index", "oxc_index",
"oxc_syntax", "oxc_syntax",
"petgraph 0.8.3", "petgraph 0.8.3",
"rustc-hash 2.1.1", "rustc-hash",
] ]
[[package]] [[package]]
@@ -4721,7 +4603,7 @@ dependencies = [
"oxc_sourcemap", "oxc_sourcemap",
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"rustc-hash 2.1.1", "rustc-hash",
] ]
[[package]] [[package]]
@@ -4733,7 +4615,7 @@ dependencies = [
"cow-utils", "cow-utils",
"oxc-browserslist", "oxc-browserslist",
"oxc_syntax", "oxc_syntax",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
] ]
@@ -4808,7 +4690,7 @@ dependencies = [
"oxc_ecmascript", "oxc_ecmascript",
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"rustc-hash 2.1.1", "rustc-hash",
] ]
[[package]] [[package]]
@@ -4825,7 +4707,7 @@ dependencies = [
"oxc_semantic", "oxc_semantic",
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"rustc-hash 2.1.1", "rustc-hash",
] ]
[[package]] [[package]]
@@ -4850,7 +4732,7 @@ dependencies = [
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"oxc_traverse", "oxc_traverse",
"rustc-hash 2.1.1", "rustc-hash",
] ]
[[package]] [[package]]
@@ -4872,7 +4754,7 @@ dependencies = [
"oxc_regular_expression", "oxc_regular_expression",
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"rustc-hash 2.1.1", "rustc-hash",
"seq-macro", "seq-macro",
] ]
@@ -4888,7 +4770,7 @@ dependencies = [
"oxc_diagnostics", "oxc_diagnostics",
"oxc_span", "oxc_span",
"phf 0.13.1", "phf 0.13.1",
"rustc-hash 2.1.1", "rustc-hash",
"unicode-id-start", "unicode-id-start",
] ]
@@ -4905,7 +4787,7 @@ dependencies = [
"once_cell", "once_cell",
"papaya", "papaya",
"pnp", "pnp",
"rustc-hash 2.1.1", "rustc-hash",
"self_cell", "self_cell",
"serde", "serde",
"serde_json", "serde_json",
@@ -4935,7 +4817,7 @@ dependencies = [
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"phf 0.13.1", "phf 0.13.1",
"rustc-hash 2.1.1", "rustc-hash",
"self_cell", "self_cell",
] ]
@@ -4947,7 +4829,7 @@ checksum = "c7f89482522f3cd820817d48ee4ade5b10822060d6e5e4d419f05f6d8bd29d70"
dependencies = [ dependencies = [
"base64-simd", "base64-simd",
"json-escape-simd", "json-escape-simd",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
] ]
@@ -5011,7 +4893,7 @@ dependencies = [
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"oxc_traverse", "oxc_traverse",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"sha1", "sha1",
@@ -5036,7 +4918,7 @@ dependencies = [
"oxc_syntax", "oxc_syntax",
"oxc_transformer", "oxc_transformer",
"oxc_traverse", "oxc_traverse",
"rustc-hash 2.1.1", "rustc-hash",
] ]
[[package]] [[package]]
@@ -5054,7 +4936,7 @@ dependencies = [
"oxc_semantic", "oxc_semantic",
"oxc_span", "oxc_span",
"oxc_syntax", "oxc_syntax",
"rustc-hash 2.1.1", "rustc-hash",
] ]
[[package]] [[package]]
@@ -5160,16 +5042,6 @@ dependencies = [
"hmac", "hmac",
] ]
[[package]]
name = "pem"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64 0.22.1",
"serde_core",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.2" version = "2.3.2"
@@ -5469,7 +5341,7 @@ dependencies = [
"nodejs-built-in-modules", "nodejs-built-in-modules",
"pathdiff", "pathdiff",
"radix_trie", "radix_trie",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.17", "thiserror 2.0.17",
@@ -5588,18 +5460,6 @@ dependencies = [
"termtree", "termtree",
] ]
[[package]]
name = "pretty_graphql"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea8c38ecedb3d28a998ea783469a78587f5f984d61226cf071f6979861e9e6a9"
dependencies = [
"apollo-parser",
"memchr",
"rowan",
"tiny_pretty",
]
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "1.3.1" version = "1.3.1"
@@ -5780,7 +5640,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"quinn-proto", "quinn-proto",
"quinn-udp", "quinn-udp",
"rustc-hash 2.1.1", "rustc-hash",
"rustls", "rustls",
"socket2 0.5.10", "socket2 0.5.10",
"thiserror 2.0.17", "thiserror 2.0.17",
@@ -5800,7 +5660,7 @@ dependencies = [
"lru-slab", "lru-slab",
"rand 0.9.1", "rand 0.9.1",
"ring", "ring",
"rustc-hash 2.1.1", "rustc-hash",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"slab", "slab",
@@ -6022,19 +5882,6 @@ dependencies = [
"cipher", "cipher",
] ]
[[package]]
name = "rcgen"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
dependencies = [
"pem",
"ring",
"rustls-pki-types",
"time",
"yasna",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.12" version = "0.5.12"
@@ -6307,7 +6154,7 @@ dependencies = [
"rolldown_tracing", "rolldown_tracing",
"rolldown_utils", "rolldown_utils",
"rolldown_watcher", "rolldown_watcher",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"string_wizard", "string_wizard",
@@ -6324,7 +6171,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77dff57c9de498bb1eb5b1ce682c2e3a0ae956b266fa0933c3e151b87b078967" checksum = "77dff57c9de498bb1eb5b1ce682c2e3a0ae956b266fa0933c3e151b87b078967"
dependencies = [ dependencies = [
"unicode-width 0.2.2", "unicode-width",
"yansi", "yansi",
] ]
@@ -6349,7 +6196,7 @@ dependencies = [
"kqueue", "kqueue",
"libc", "libc",
"log 0.4.29", "log 0.4.29",
"mio 1.0.4", "mio",
"rolldown-notify-types", "rolldown-notify-types",
"walkdir", "walkdir",
"windows-sys 0.61.2", "windows-sys 0.61.2",
@@ -6397,7 +6244,7 @@ dependencies = [
"rolldown_sourcemap", "rolldown_sourcemap",
"rolldown_std_utils", "rolldown_std_utils",
"rolldown_utils", "rolldown_utils",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"simdutf8", "simdutf8",
@@ -6415,7 +6262,7 @@ dependencies = [
"blake3", "blake3",
"dashmap", "dashmap",
"rolldown_debug_action", "rolldown_debug_action",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"tracing", "tracing",
@@ -6472,7 +6319,7 @@ dependencies = [
"rolldown-ariadne", "rolldown-ariadne",
"rolldown_utils", "rolldown_utils",
"ropey", "ropey",
"rustc-hash 2.1.1", "rustc-hash",
"sugar_path", "sugar_path",
] ]
@@ -6506,7 +6353,7 @@ dependencies = [
"rolldown_resolver", "rolldown_resolver",
"rolldown_sourcemap", "rolldown_sourcemap",
"rolldown_utils", "rolldown_utils",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",
"string_wizard", "string_wizard",
@@ -6526,7 +6373,7 @@ dependencies = [
"rolldown_common", "rolldown_common",
"rolldown_plugin", "rolldown_plugin",
"rolldown_utils", "rolldown_utils",
"rustc-hash 2.1.1", "rustc-hash",
"serde_json", "serde_json",
"xxhash-rust", "xxhash-rust",
] ]
@@ -6597,7 +6444,7 @@ dependencies = [
"oxc", "oxc",
"oxc_sourcemap", "oxc_sourcemap",
"rolldown_utils", "rolldown_utils",
"rustc-hash 2.1.1", "rustc-hash",
] ]
[[package]] [[package]]
@@ -6649,7 +6496,7 @@ dependencies = [
"regex 1.11.1", "regex 1.11.1",
"regress", "regress",
"rolldown_std_utils", "rolldown_std_utils",
"rustc-hash 2.1.1", "rustc-hash",
"serde_json", "serde_json",
"simdutf8", "simdutf8",
"sugar_path", "sugar_path",
@@ -6679,18 +6526,6 @@ dependencies = [
"str_indices", "str_indices",
] ]
[[package]]
name = "rowan"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21"
dependencies = [
"countme",
"hashbrown 0.14.5",
"rustc-hash 1.1.0",
"text-size",
]
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.32.1" version = "0.32.1"
@@ -6733,12 +6568,6 @@ dependencies = [
"serde_json", "serde_json",
] ]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.1" version = "2.1.1"
@@ -6786,8 +6615,6 @@ version = "0.23.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
dependencies = [ dependencies = [
"aws-lc-rs",
"log 0.4.29",
"once_cell", "once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -6860,7 +6687,6 @@ version = "0.103.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
dependencies = [ dependencies = [
"aws-lc-rs",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"untrusted", "untrusted",
@@ -7347,27 +7173,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
dependencies = [
"libc",
"mio 0.8.11",
"signal-hook",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.5" version = "1.4.5"
@@ -7554,7 +7359,7 @@ dependencies = [
"memchr", "memchr",
"oxc_index", "oxc_index",
"oxc_sourcemap", "oxc_sourcemap",
"rustc-hash 2.1.1", "rustc-hash",
"serde", "serde",
] ]
@@ -8255,12 +8060,6 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]]
name = "text-size"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.16.2" version = "0.16.2"
@@ -8269,7 +8068,7 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
dependencies = [ dependencies = [
"smawk", "smawk",
"unicode-linebreak", "unicode-linebreak",
"unicode-width 0.2.2", "unicode-width",
] ]
[[package]] [[package]]
@@ -8383,12 +8182,6 @@ dependencies = [
"crunchy", "crunchy",
] ]
[[package]]
name = "tiny_pretty"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650d82e943da333637be9f1567d33d605e76810a26464edfd7ae74f7ef181e95"
[[package]] [[package]]
name = "tinystr" name = "tinystr"
version = "0.8.1" version = "0.8.1"
@@ -8422,7 +8215,7 @@ checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
"mio 1.0.4", "mio",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2 0.6.1", "socket2 0.6.1",
@@ -8992,12 +8785,6 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.2.2" version = "0.2.2"
@@ -9775,15 +9562,6 @@ dependencies = [
"windows-targets 0.42.2", "windows-targets 0.42.2",
] ]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
@@ -10299,7 +10077,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "yaak-app-client" name = "yaak-app"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"charset", "charset",
@@ -10311,7 +10089,6 @@ dependencies = [
"md5 0.8.0", "md5 0.8.0",
"mime_guess", "mime_guess",
"openssl-sys", "openssl-sys",
"pretty_graphql",
"r2d2", "r2d2",
"r2d2_sqlite", "r2d2_sqlite",
"rand 0.9.1", "rand 0.9.1",
@@ -10357,30 +10134,13 @@ dependencies = [
"yaak-tauri-utils", "yaak-tauri-utils",
"yaak-templates", "yaak-templates",
"yaak-tls", "yaak-tls",
"yaak-window",
"yaak-ws", "yaak-ws",
] ]
[[package]]
name = "yaak-app-proxy"
version = "0.0.0"
dependencies = [
"log 0.4.29",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-os",
"yaak-mac-window",
"yaak-proxy-lib",
"yaak-rpc",
"yaak-window",
]
[[package]] [[package]]
name = "yaak-cli" name = "yaak-cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arboard",
"assert_cmd", "assert_cmd",
"base64 0.22.1", "base64 0.22.1",
"clap", "clap",
@@ -10390,7 +10150,6 @@ dependencies = [
"futures", "futures",
"hex", "hex",
"include_dir", "include_dir",
"inquire",
"keyring", "keyring",
"log 0.4.29", "log 0.4.29",
"oxc_resolver", "oxc_resolver",
@@ -10407,7 +10166,6 @@ dependencies = [
"walkdir", "walkdir",
"webbrowser", "webbrowser",
"yaak", "yaak",
"yaak-api",
"yaak-crypto", "yaak-crypto",
"yaak-http", "yaak-http",
"yaak-models", "yaak-models",
@@ -10445,25 +10203,6 @@ dependencies = [
"yaak-models", "yaak-models",
] ]
[[package]]
name = "yaak-database"
version = "0.1.0"
dependencies = [
"chrono",
"include_dir",
"log 0.4.29",
"nanoid",
"r2d2",
"r2d2_sqlite",
"rusqlite",
"sea-query",
"sea-query-rusqlite",
"serde",
"serde_json",
"thiserror 2.0.17",
"ts-rs",
]
[[package]] [[package]]
name = "yaak-fonts" name = "yaak-fonts"
version = "0.1.0" version = "0.1.0"
@@ -10536,7 +10275,6 @@ dependencies = [
"hyper-util", "hyper-util",
"log 0.4.29", "log 0.4.29",
"mime_guess", "mime_guess",
"native-tls",
"regex 1.11.1", "regex 1.11.1",
"reqwest", "reqwest",
"serde", "serde",
@@ -10549,7 +10287,6 @@ dependencies = [
"urlencoding", "urlencoding",
"yaak-common", "yaak-common",
"yaak-models", "yaak-models",
"yaak-templates",
"yaak-tls", "yaak-tls",
"zstd", "zstd",
] ]
@@ -10606,7 +10343,6 @@ dependencies = [
"thiserror 2.0.17", "thiserror 2.0.17",
"ts-rs", "ts-rs",
"yaak-core", "yaak-core",
"yaak-database",
] ]
[[package]] [[package]]
@@ -10623,6 +10359,7 @@ dependencies = [
"md5 0.7.0", "md5 0.7.0",
"path-slash", "path-slash",
"rand 0.9.1", "rand 0.9.1",
"regex 1.11.1",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
@@ -10638,52 +10375,6 @@ dependencies = [
"zip-extract", "zip-extract",
] ]
[[package]]
name = "yaak-proxy"
version = "0.1.0"
dependencies = [
"bytes",
"http",
"http-body-util",
"hyper",
"hyper-util",
"pem",
"rcgen",
"rustls",
"rustls-native-certs",
"tokio",
"tokio-rustls",
]
[[package]]
name = "yaak-proxy-lib"
version = "0.0.0"
dependencies = [
"chrono",
"include_dir",
"log 0.4.29",
"r2d2",
"r2d2_sqlite",
"rusqlite",
"sea-query",
"serde",
"serde_json",
"ts-rs",
"yaak-database",
"yaak-proxy",
"yaak-rpc",
]
[[package]]
name = "yaak-rpc"
version = "0.0.0"
dependencies = [
"log 0.4.29",
"serde",
"serde_json",
"ts-rs",
]
[[package]] [[package]]
name = "yaak-sse" name = "yaak-sse"
version = "0.1.0" version = "0.1.0"
@@ -10749,17 +10440,6 @@ dependencies = [
"yaak-models", "yaak-models",
] ]
[[package]]
name = "yaak-window"
version = "0.1.0"
dependencies = [
"log 0.4.29",
"md5 0.8.0",
"rand 0.9.1",
"tauri",
"tokio",
]
[[package]] [[package]]
name = "yaak-ws" name = "yaak-ws"
version = "0.1.0" version = "0.1.0"
@@ -10791,9 +10471,6 @@ name = "yasna"
version = "0.5.2" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
"time",
]
[[package]] [[package]]
name = "yoke" name = "yoke"

View File

@@ -1,38 +1,30 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"crates/yaak", "crates/yaak",
# Common/foundation crates # Shared crates (no Tauri dependency)
"crates/common/yaak-database", "crates/yaak-core",
"crates/common/yaak-rpc", "crates/yaak-common",
# Shared crates (no Tauri dependency) "crates/yaak-crypto",
"crates/yaak-core", "crates/yaak-git",
"crates/yaak-common", "crates/yaak-grpc",
"crates/yaak-crypto", "crates/yaak-http",
"crates/yaak-git", "crates/yaak-models",
"crates/yaak-grpc", "crates/yaak-plugins",
"crates/yaak-http", "crates/yaak-sse",
"crates/yaak-models", "crates/yaak-sync",
"crates/yaak-plugins", "crates/yaak-templates",
"crates/yaak-sse", "crates/yaak-tls",
"crates/yaak-sync", "crates/yaak-ws",
"crates/yaak-templates", "crates/yaak-api",
"crates/yaak-tls", # CLI crates
"crates/yaak-ws", "crates-cli/yaak-cli",
"crates/yaak-api", # Tauri-specific crates
"crates/yaak-proxy", "crates-tauri/yaak-app",
# Proxy-specific crates "crates-tauri/yaak-fonts",
"crates-proxy/yaak-proxy-lib", "crates-tauri/yaak-license",
# CLI crates "crates-tauri/yaak-mac-window",
"crates-cli/yaak-cli", "crates-tauri/yaak-tauri-utils",
# Tauri-specific crates
"crates-tauri/yaak-app-client",
"crates-tauri/yaak-app-proxy",
"crates-tauri/yaak-fonts",
"crates-tauri/yaak-license",
"crates-tauri/yaak-mac-window",
"crates-tauri/yaak-tauri-utils",
"crates-tauri/yaak-window",
] ]
[workspace.dependencies] [workspace.dependencies]
@@ -55,10 +47,6 @@ thiserror = "2.0.17"
tokio = "1.48.0" tokio = "1.48.0"
ts-rs = "11.1.0" ts-rs = "11.1.0"
# Internal crates - common/foundation
yaak-database = { path = "crates/common/yaak-database" }
yaak-rpc = { path = "crates/common/yaak-rpc" }
# Internal crates - shared # Internal crates - shared
yaak-core = { path = "crates/yaak-core" } yaak-core = { path = "crates/yaak-core" }
yaak = { path = "crates/yaak" } yaak = { path = "crates/yaak" }
@@ -75,17 +63,12 @@ yaak-templates = { path = "crates/yaak-templates" }
yaak-tls = { path = "crates/yaak-tls" } yaak-tls = { path = "crates/yaak-tls" }
yaak-ws = { path = "crates/yaak-ws" } yaak-ws = { path = "crates/yaak-ws" }
yaak-api = { path = "crates/yaak-api" } yaak-api = { path = "crates/yaak-api" }
yaak-proxy = { path = "crates/yaak-proxy" }
# Internal crates - proxy
yaak-proxy-lib = { path = "crates-proxy/yaak-proxy-lib" }
# Internal crates - Tauri-specific # Internal crates - Tauri-specific
yaak-fonts = { path = "crates-tauri/yaak-fonts" } yaak-fonts = { path = "crates-tauri/yaak-fonts" }
yaak-license = { path = "crates-tauri/yaak-license" } yaak-license = { path = "crates-tauri/yaak-license" }
yaak-mac-window = { path = "crates-tauri/yaak-mac-window" } yaak-mac-window = { path = "crates-tauri/yaak-mac-window" }
yaak-tauri-utils = { path = "crates-tauri/yaak-tauri-utils" } yaak-tauri-utils = { path = "crates-tauri/yaak-tauri-utils" }
yaak-window = { path = "crates-tauri/yaak-window" }
[profile.release] [profile.release]
strip = false strip = false

View File

@@ -1,26 +1,24 @@
# Developer Setup # Developer Setup
Yaak is a combined Node.js and Rust monorepo. It is a [Tauri](https://tauri.app) project, so Yaak is a combined Node.js and Rust monorepo. It is a [Tauri](https://tauri.app) project, so
uses Rust and HTML/CSS/JS for the main application but there is also a plugin system powered uses Rust and HTML/CSS/JS for the main application but there is also a plugin system powered
by a Node.js sidecar that communicates to the app over gRPC. by a Node.js sidecar that communicates to the app over gRPC.
Because of the moving parts, there are a few setup steps required before development can Because of the moving parts, there are a few setup steps required before development can
begin. begin.
## Prerequisites ## Prerequisites
Make sure you have the following tools installed: Make sure you have the following tools installed:
- [Node.js](https://nodejs.org/en/download/package-manager) (v24+) - [Node.js](https://nodejs.org/en/download/package-manager)
- [Rust](https://www.rust-lang.org/tools/install) - [Rust](https://www.rust-lang.org/tools/install)
- [Vite+](https://vite.dev/guide/vite-plus) (`vp` CLI)
Check the installations with the following commands: Check the installations with the following commands:
```shell ```shell
node -v node -v
npm -v npm -v
vp --version
rustc --version rustc --version
``` ```
@@ -47,12 +45,12 @@ npm start
## SQLite Migrations ## SQLite Migrations
New migrations can be created from the `src-tauri/` directory: New migrations can be created from the `src-tauri/` directory:
```shell ```shell
npm run migration npm run migration
``` ```
Rerun the app to apply the migrations. Rerun the app to apply the migrations.
_Note: For safety, development builds use a separate database location from production builds._ _Note: For safety, development builds use a separate database location from production builds._
@@ -63,9 +61,9 @@ _Note: For safety, development builds use a separate database location from prod
lezer-generator components/core/Editor/<LANG>/<LANG>.grammar > components/core/Editor/<LANG>/<LANG>.ts lezer-generator components/core/Editor/<LANG>/<LANG>.grammar > components/core/Editor/<LANG>/<LANG>.ts
``` ```
## Linting and Formatting ## Linting & Formatting
This repo uses [Vite+](https://vite.dev/guide/vite-plus) for linting (oxlint) and formatting (oxfmt). This repo uses Biome for linting and formatting (replacing ESLint + Prettier).
- Lint the entire repo: - Lint the entire repo:
@@ -73,6 +71,12 @@ This repo uses [Vite+](https://vite.dev/guide/vite-plus) for linting (oxlint) an
npm run lint npm run lint
``` ```
- Auto-fix lint issues where possible:
```sh
npm run lint:fix
```
- Format code: - Format code:
```sh ```sh
@@ -80,7 +84,5 @@ npm run format
``` ```
Notes: Notes:
- Many workspace packages also expose the same scripts (`lint`, `lint:fix`, and `format`).
- A pre-commit hook runs `vp lint` automatically on commit. - TypeScript type-checking still runs separately via `tsc --noEmit` in relevant packages.
- Some workspace packages also run `tsc --noEmit` for type-checking.
- VS Code users should install the recommended extensions for format-on-save support.

View File

@@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<a href="https://github.com/JamesIves/github-sponsors-readme-action"> <a href="https://github.com/JamesIves/github-sponsors-readme-action">
<img width="200px" src="https://github.com/mountain-loop/yaak/raw/main/crates-tauri/yaak-app-client/icons/icon.png"> <img width="200px" src="https://github.com/mountain-loop/yaak/raw/main/crates-tauri/yaak-app/icons/icon.png">
</a> </a>
</p> </p>
@@ -16,6 +16,8 @@
</p> </p>
<br> <br>
<p align="center"> <p align="center">
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https:&#x2F;&#x2F;github.com&#x2F;MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a>&nbsp;&nbsp;<a href="https://github.com/dharsanb"><img src="https:&#x2F;&#x2F;github.com&#x2F;dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a>&nbsp;&nbsp;<a href="https://github.com/railwayapp"><img src="https:&#x2F;&#x2F;github.com&#x2F;railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a>&nbsp;&nbsp;<a href="https://github.com/caseyamcl"><img src="https:&#x2F;&#x2F;github.com&#x2F;caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a>&nbsp;&nbsp;<a href="https://github.com/bytebase"><img src="https:&#x2F;&#x2F;github.com&#x2F;bytebase.png" width="80px" alt="User avatar: bytebase" /></a>&nbsp;&nbsp;<a href="https://github.com/"><img src="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;JamesIves&#x2F;github-sponsors-readme-action&#x2F;dev&#x2F;.github&#x2F;assets&#x2F;placeholder.png" width="80px" alt="User avatar: " /></a>&nbsp;&nbsp;<!-- sponsors-premium --> <!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https:&#x2F;&#x2F;github.com&#x2F;MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a>&nbsp;&nbsp;<a href="https://github.com/dharsanb"><img src="https:&#x2F;&#x2F;github.com&#x2F;dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a>&nbsp;&nbsp;<a href="https://github.com/railwayapp"><img src="https:&#x2F;&#x2F;github.com&#x2F;railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a>&nbsp;&nbsp;<a href="https://github.com/caseyamcl"><img src="https:&#x2F;&#x2F;github.com&#x2F;caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a>&nbsp;&nbsp;<a href="https://github.com/bytebase"><img src="https:&#x2F;&#x2F;github.com&#x2F;bytebase.png" width="80px" alt="User avatar: bytebase" /></a>&nbsp;&nbsp;<a href="https://github.com/"><img src="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;JamesIves&#x2F;github-sponsors-readme-action&#x2F;dev&#x2F;.github&#x2F;assets&#x2F;placeholder.png" width="80px" alt="User avatar: " /></a>&nbsp;&nbsp;<!-- sponsors-premium -->
</p> </p>
@@ -25,10 +27,12 @@
![Yaak API Client](https://yaak.app/static/screenshot.png) ![Yaak API Client](https://yaak.app/static/screenshot.png)
## Features ## Features
Yaak is an offline-first API client designed to stay out of your way while giving you everything you need when you need it. Yaak is an offline-first API client designed to stay out of your way while giving you everything you need when you need it.
Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight, and private. No telemetry, no VC funding, and no cloud lock-in. Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight, and private. No telemetry, no VC funding, and no cloud lock-in.
### 🌐 Work with any API ### 🌐 Work with any API
@@ -37,23 +41,21 @@ Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight
- Filter and inspect responses with JSONPath or XPath. - Filter and inspect responses with JSONPath or XPath.
### 🔐 Stay secure ### 🔐 Stay secure
- Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication. - Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication.
- Secure sensitive values with encrypted secrets. - Secure sensitive values with encrypted secrets.
- Store secrets in your OS keychain. - Store secrets in your OS keychain.
### ☁️ Organize & collaborate ### ☁️ Organize & collaborate
- Group requests into workspaces and nested folders. - Group requests into workspaces and nested folders.
- Use environment variables to switch between dev, staging, and prod. - Use environment variables to switch between dev, staging, and prod.
- Mirror workspaces to your filesystem for versioning in Git or syncing with Dropbox. - Mirror workspaces to your filesystem for versioning in Git or syncing with Dropbox.
### 🧩 Extend & customize ### 🧩 Extend & customize
- Insert dynamic values like UUIDs or timestamps with template tags. - Insert dynamic values like UUIDs or timestamps with template tags.
- Pick from built-in themes or build your own. - Pick from built-in themes or build your own.
- Create plugins to extend authentication, template tags, or the UI. - Create plugins to extend authentication, template tags, or the UI.
## Contribution Policy ## Contribution Policy
> [!IMPORTANT] > [!IMPORTANT]

View File

@@ -1,17 +0,0 @@
import { getModel } from "@yaakapp-internal/models";
import type { FolderSettingsTab } from "../components/FolderSettingsDialog";
import { FolderSettingsDialog } from "../components/FolderSettingsDialog";
import { showDialog } from "../lib/dialog";
export function openFolderSettings(folderId: string, tab?: FolderSettingsTab) {
const folder = getModel("folder", folderId);
if (folder == null) return;
showDialog({
id: "folder-settings",
title: null,
size: "lg",
className: "h-[50rem]",
noPadding: true,
render: () => <FolderSettingsDialog folderId={folderId} tab={tab} />,
});
}

View File

@@ -1,27 +0,0 @@
import { applySync, calculateSyncFsOnly } from "@yaakapp-internal/sync";
import { createFastMutation } from "../hooks/useFastMutation";
import { showSimpleAlert } from "../lib/alert";
import { router } from "../lib/router";
export const openWorkspaceFromSyncDir = createFastMutation<void, void, string>({
mutationKey: [],
mutationFn: async (dir) => {
const ops = await calculateSyncFsOnly(dir);
const workspace = ops
.map((o) => (o.type === "dbCreate" && o.fs.model.type === "workspace" ? o.fs.model : null))
.filter((m) => m)[0];
if (workspace == null) {
showSimpleAlert("Failed to Open", "No workspace found in directory");
return;
}
await applySync(workspace.id, dir, ops);
await router.navigate({
to: "/workspaces/$workspaceId",
params: { workspaceId: workspace.id },
});
},
});

View File

@@ -1,19 +0,0 @@
import type { WorkspaceSettingsTab } from "../components/WorkspaceSettingsDialog";
import { WorkspaceSettingsDialog } from "../components/WorkspaceSettingsDialog";
import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { showDialog } from "../lib/dialog";
import { jotaiStore } from "../lib/jotai";
export function openWorkspaceSettings(tab?: WorkspaceSettingsTab) {
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
if (workspaceId == null) return;
showDialog({
id: "workspace-settings",
size: "md",
className: "h-[calc(100vh-5rem)] !max-h-[40rem]",
noPadding: true,
render: ({ hide }) => (
<WorkspaceSettingsDialog workspaceId={workspaceId} hide={hide} tab={tab} />
),
});
}

View File

@@ -1,33 +0,0 @@
import { useTimedBoolean } from "@yaakapp-internal/ui";
import { copyToClipboard } from "../lib/copy";
import { showToast } from "../lib/toast";
import type { ButtonProps } from "./core/Button";
import { Button } from "./core/Button";
interface Props extends Omit<ButtonProps, "onClick"> {
text: string | (() => Promise<string | null>);
}
export function CopyButton({ text, ...props }: Props) {
const [copied, setCopied] = useTimedBoolean();
return (
<Button
{...props}
onClick={async () => {
const content = typeof text === "function" ? await text() : text;
if (content == null) {
showToast({
id: "failed-to-copy",
color: "danger",
message: "Failed to copy",
});
} else {
copyToClipboard(content, { disableToast: true });
setCopied();
}
}}
>
{copied ? "Copied" : "Copy"}
</Button>
);
}

View File

@@ -1,31 +0,0 @@
import { IconButton, type IconButtonProps, useTimedBoolean } from "@yaakapp-internal/ui";
import { copyToClipboard } from "../lib/copy";
import { showToast } from "../lib/toast";
interface Props extends Omit<IconButtonProps, "onClick" | "icon"> {
text: string | (() => Promise<string | null>);
}
export function CopyIconButton({ text, ...props }: Props) {
const [copied, setCopied] = useTimedBoolean();
return (
<IconButton
{...props}
icon={copied ? "check" : "copy"}
showConfirm
onClick={async () => {
const content = typeof text === "function" ? await text() : text;
if (content == null) {
showToast({
id: "failed-to-copy",
color: "danger",
message: "Failed to copy",
});
} else {
copyToClipboard(content, { disableToast: true });
setCopied();
}
}}
/>
);
}

View File

@@ -1,34 +0,0 @@
import classNames from "classnames";
import type { CSSProperties } from "react";
import { memo } from "react";
interface Props {
className?: string;
style?: CSSProperties;
orientation?: "horizontal" | "vertical";
}
export const DropMarker = memo(
function DropMarker({ className, style, orientation = "horizontal" }: Props) {
return (
<div
style={style}
className={classNames(
className,
"absolute pointer-events-none z-50",
orientation === "horizontal" && "w-full",
orientation === "vertical" && "w-0 top-0 bottom-0",
)}
>
<div
className={classNames(
"absolute bg-primary rounded-full",
orientation === "horizontal" && "left-2 right-2 -bottom-[0.1rem] h-[0.2rem]",
orientation === "vertical" && "-left-[0.1rem] top-0 bottom-0 w-[0.2rem]",
)}
/>
</div>
);
},
() => true,
);

View File

@@ -1,38 +0,0 @@
import { activeRequestAtom } from "../hooks/useActiveRequest";
import { useSubscribeActiveWorkspaceId } from "../hooks/useActiveWorkspace";
import { useActiveWorkspaceChangedToast } from "../hooks/useActiveWorkspaceChangedToast";
import { useHotKey, useSubscribeHotKeys } from "../hooks/useHotKey";
import { useSubscribeHttpAuthentication } from "../hooks/useHttpAuthentication";
import { useSyncFontSizeSetting } from "../hooks/useSyncFontSizeSetting";
import { useSyncWorkspaceChildModels } from "../hooks/useSyncWorkspaceChildModels";
import { useSyncZoomSetting } from "../hooks/useSyncZoomSetting";
import { useSubscribeTemplateFunctions } from "../hooks/useTemplateFunctions";
import { jotaiStore } from "../lib/jotai";
import { renameModelWithPrompt } from "../lib/renameModelWithPrompt";
export function GlobalHooks() {
useSyncZoomSetting();
useSyncFontSizeSetting();
useSubscribeActiveWorkspaceId();
useSyncWorkspaceChildModels();
useSubscribeTemplateFunctions();
useSubscribeHttpAuthentication();
// Other useful things
useActiveWorkspaceChangedToast();
useSubscribeHotKeys();
useHotKey(
"request.rename",
async () => {
const model = jotaiStore.get(activeRequestAtom);
if (model == null) return;
await renameModelWithPrompt(model);
},
{ allowDefault: true },
);
return null;
}

View File

@@ -1,427 +0,0 @@
import type { HttpResponse, HttpResponseEvent } from "@yaakapp-internal/models";
import { Banner, HStack, Icon, LoadingIcon, VStack } from "@yaakapp-internal/ui";
import classNames from "classnames";
import type { ComponentType, CSSProperties } from "react";
import { lazy, Suspense, useMemo } from "react";
import { useCancelHttpResponse } from "../hooks/useCancelHttpResponse";
import { useHttpResponseEvents } from "../hooks/useHttpResponseEvents";
import { usePinnedHttpResponse } from "../hooks/usePinnedHttpResponse";
import { useResponseBodyBytes, useResponseBodyText } from "../hooks/useResponseBodyText";
import { useResponseViewMode } from "../hooks/useResponseViewMode";
import { useTimelineViewMode } from "../hooks/useTimelineViewMode";
import { getMimeTypeFromContentType } from "../lib/contentType";
import { getContentTypeFromHeaders, getCookieCounts } from "../lib/model_util";
import { ConfirmLargeResponse } from "./ConfirmLargeResponse";
import { ConfirmLargeResponseRequest } from "./ConfirmLargeResponseRequest";
import { Button } from "./core/Button";
import { CountBadge } from "./core/CountBadge";
import { HotkeyList } from "./core/HotkeyList";
import { HttpResponseDurationTag } from "./core/HttpResponseDurationTag";
import { HttpStatusTag } from "./core/HttpStatusTag";
import { PillButton } from "./core/PillButton";
import { SizeTag } from "./core/SizeTag";
import type { TabItem } from "./core/Tabs/Tabs";
import { TabContent, Tabs } from "./core/Tabs/Tabs";
import { Tooltip } from "./core/Tooltip";
import { EmptyStateText } from "./EmptyStateText";
import { ErrorBoundary } from "./ErrorBoundary";
import { HttpResponseTimeline } from "./HttpResponseTimeline";
import { RecentHttpResponsesDropdown } from "./RecentHttpResponsesDropdown";
import { RequestBodyViewer } from "./RequestBodyViewer";
import { ResponseCookies } from "./ResponseCookies";
import { ResponseHeaders } from "./ResponseHeaders";
import { AudioViewer } from "./responseViewers/AudioViewer";
import { CsvViewer } from "./responseViewers/CsvViewer";
import { EventStreamViewer } from "./responseViewers/EventStreamViewer";
import { HTMLOrTextViewer } from "./responseViewers/HTMLOrTextViewer";
import { ImageViewer } from "./responseViewers/ImageViewer";
import { MultipartViewer } from "./responseViewers/MultipartViewer";
import { SvgViewer } from "./responseViewers/SvgViewer";
import { VideoViewer } from "./responseViewers/VideoViewer";
const PdfViewer = lazy(() =>
import("./responseViewers/PdfViewer").then((m) => ({ default: m.PdfViewer })),
);
interface Props {
style?: CSSProperties;
className?: string;
activeRequestId: string;
}
const TAB_BODY = "body";
const TAB_REQUEST = "request";
const TAB_HEADERS = "headers";
const TAB_COOKIES = "cookies";
const TAB_TIMELINE = "timeline";
export type TimelineViewMode = "timeline" | "text";
interface RedirectDropWarning {
droppedBodyCount: number;
droppedHeaders: string[];
}
export function HttpResponsePane({ style, className, activeRequestId }: Props) {
const { activeResponse, setPinnedResponseId, responses } = usePinnedHttpResponse(activeRequestId);
const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId);
const [timelineViewMode, setTimelineViewMode] = useTimelineViewMode();
const contentType = getContentTypeFromHeaders(activeResponse?.headers ?? null);
const mimeType = contentType == null ? null : getMimeTypeFromContentType(contentType).essence;
const responseEvents = useHttpResponseEvents(activeResponse);
const redirectDropWarning = useMemo(
() => getRedirectDropWarning(responseEvents.data),
[responseEvents.data],
);
const shouldShowRedirectDropWarning =
activeResponse?.state === "closed" && redirectDropWarning != null;
const cookieCounts = useMemo(() => getCookieCounts(responseEvents.data), [responseEvents.data]);
const tabs = useMemo<TabItem[]>(
() => [
{
value: TAB_BODY,
label: "Response",
options: {
value: viewMode,
onChange: setViewMode,
items: [
{ label: "Response", value: "pretty" },
...(mimeType?.startsWith("image")
? []
: [{ label: "Response (Raw)", shortLabel: "Raw", value: "raw" }]),
],
},
},
{
value: TAB_REQUEST,
label: "Request",
rightSlot:
(activeResponse?.requestContentLength ?? 0) > 0 ? <CountBadge count={true} /> : null,
},
{
value: TAB_HEADERS,
label: "Headers",
rightSlot: (
<CountBadge
count={activeResponse?.requestHeaders.length ?? 0}
count2={activeResponse?.headers.length ?? 0}
showZero
/>
),
},
{
value: TAB_COOKIES,
label: "Cookies",
rightSlot:
cookieCounts.sent > 0 || cookieCounts.received > 0 ? (
<CountBadge count={cookieCounts.sent} count2={cookieCounts.received} showZero />
) : null,
},
{
value: TAB_TIMELINE,
rightSlot: <CountBadge count={responseEvents.data?.length ?? 0} />,
options: {
value: timelineViewMode,
onChange: (v) => setTimelineViewMode((v as TimelineViewMode) ?? "timeline"),
items: [
{ label: "Timeline", value: "timeline" },
{ label: "Timeline (Text)", shortLabel: "Timeline", value: "text" },
],
},
},
],
[
activeResponse?.headers,
activeResponse?.requestContentLength,
activeResponse?.requestHeaders.length,
cookieCounts.sent,
cookieCounts.received,
mimeType,
responseEvents.data?.length,
setViewMode,
viewMode,
timelineViewMode,
setTimelineViewMode,
],
);
const cancel = useCancelHttpResponse(activeResponse?.id ?? null);
return (
<div
style={style}
className={classNames(
className,
"x-theme-responsePane",
"max-h-full h-full",
"bg-surface rounded-md border border-border-subtle overflow-hidden",
"relative",
)}
>
{activeResponse == null ? (
<HotkeyList hotkeys={["request.send", "model.create", "sidebar.focus", "url_bar.focus"]} />
) : (
<div className="h-full w-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1">
<HStack
className={classNames(
"text-text-subtle w-full flex-shrink-0",
// Remove a bit of space because the tabs have lots too
"-mb-1.5",
)}
>
{activeResponse && (
<div
className={classNames(
"grid grid-cols-[auto_minmax(4rem,1fr)_auto]",
"cursor-default select-none",
"whitespace-nowrap w-full pl-3 overflow-x-auto font-mono text-sm hide-scrollbars",
)}
>
<HStack space={2} className="w-full flex-shrink-0">
{activeResponse.state !== "closed" && <LoadingIcon size="sm" />}
<HttpStatusTag showReason response={activeResponse} />
<span>&bull;</span>
<HttpResponseDurationTag response={activeResponse} />
<span>&bull;</span>
<SizeTag
contentLength={activeResponse.contentLength ?? 0}
contentLengthCompressed={activeResponse.contentLengthCompressed}
/>
</HStack>
{shouldShowRedirectDropWarning ? (
<Tooltip
tabIndex={0}
className="my-auto pl-3 flex-shrink-0 max-w-full justify-self-end overflow-hidden"
content={
<VStack alignItems="start" space={1} className="text-xs">
<span className="font-medium text-warning">
Redirect changed this request
</span>
{redirectDropWarning.droppedBodyCount > 0 && (
<span>
Body dropped on {redirectDropWarning.droppedBodyCount}{" "}
{redirectDropWarning.droppedBodyCount === 1
? "redirect hop"
: "redirect hops"}
</span>
)}
{redirectDropWarning.droppedHeaders.length > 0 && (
<span>
Headers dropped:{" "}
<span className="font-mono">
{redirectDropWarning.droppedHeaders.join(", ")}
</span>
</span>
)}
<span className="text-text-subtle">See Timeline for details.</span>
</VStack>
}
>
<span className="inline-flex min-w-0">
<PillButton
color="warning"
className="font-sans text-sm !flex-shrink max-w-full"
innerClassName="flex items-center"
leftSlot={<Icon icon="alert_triangle" size="xs" color="warning" />}
>
<span className="truncate">
{getRedirectWarningLabel(redirectDropWarning)}
</span>
</PillButton>
</span>
</Tooltip>
) : (
<span />
)}
<div className="justify-self-end flex-shrink-0">
<RecentHttpResponsesDropdown
responses={responses}
activeResponse={activeResponse}
onPinnedResponseId={setPinnedResponseId}
/>
</div>
</div>
)}
</HStack>
<div className="overflow-hidden flex flex-col min-h-0">
{activeResponse?.error && (
<Banner color="danger" className="mx-3 mt-1 flex-shrink-0">
{activeResponse.error}
</Banner>
)}
{/* Show tabs if we have any data (headers, body, etc.) even if there's an error */}
<Tabs
tabs={tabs}
label="Response"
className="ml-3 mr-3 mb-3 min-h-0 flex-1"
tabListClassName="mt-0.5 -mb-1.5"
storageKey="http_response_tabs"
activeTabKey={activeRequestId}
>
<TabContent value={TAB_BODY}>
<ErrorBoundary name="Http Response Viewer">
<Suspense>
<ConfirmLargeResponse response={activeResponse}>
{activeResponse.state === "initialized" ? (
<EmptyStateText>
<VStack space={3}>
<HStack space={3}>
<LoadingIcon className="text-text-subtlest" />
Sending Request
</HStack>
<Button size="sm" variant="border" onClick={() => cancel.mutate()}>
Cancel
</Button>
</VStack>
</EmptyStateText>
) : activeResponse.state === "closed" &&
(activeResponse.contentLength ?? 0) === 0 ? (
<EmptyStateText>Empty</EmptyStateText>
) : mimeType?.match(/^text\/event-stream/i) && viewMode === "pretty" ? (
<EventStreamViewer response={activeResponse} />
) : mimeType?.match(/^image\/svg/) ? (
<HttpSvgViewer response={activeResponse} />
) : mimeType?.match(/^image/i) ? (
<EnsureCompleteResponse response={activeResponse} Component={ImageViewer} />
) : mimeType?.match(/^audio/i) ? (
<EnsureCompleteResponse response={activeResponse} Component={AudioViewer} />
) : mimeType?.match(/^video/i) ? (
<EnsureCompleteResponse response={activeResponse} Component={VideoViewer} />
) : mimeType?.match(/^multipart/i) && viewMode === "pretty" ? (
<HttpMultipartViewer response={activeResponse} />
) : mimeType?.match(/pdf/i) ? (
<EnsureCompleteResponse response={activeResponse} Component={PdfViewer} />
) : mimeType?.match(/csv|tab-separated/i) && viewMode === "pretty" ? (
<HttpCsvViewer className="pb-2" response={activeResponse} />
) : (
<HTMLOrTextViewer
textViewerClassName="-mr-2 bg-surface" // Pull to the right
response={activeResponse}
pretty={viewMode === "pretty"}
/>
)}
</ConfirmLargeResponse>
</Suspense>
</ErrorBoundary>
</TabContent>
<TabContent value={TAB_REQUEST}>
<ConfirmLargeResponseRequest response={activeResponse}>
<RequestBodyViewer response={activeResponse} />
</ConfirmLargeResponseRequest>
</TabContent>
<TabContent value={TAB_HEADERS}>
<ResponseHeaders response={activeResponse} />
</TabContent>
<TabContent value={TAB_COOKIES}>
<ResponseCookies response={activeResponse} />
</TabContent>
<TabContent value={TAB_TIMELINE}>
<HttpResponseTimeline response={activeResponse} viewMode={timelineViewMode} />
</TabContent>
</Tabs>
</div>
</div>
)}
</div>
);
}
function getRedirectDropWarning(
events: HttpResponseEvent[] | undefined,
): RedirectDropWarning | null {
if (events == null || events.length === 0) return null;
let droppedBodyCount = 0;
const droppedHeaders = new Set<string>();
for (const e of events) {
const event = e.event;
if (event.type !== "redirect") {
continue;
}
if (event.dropped_body) {
droppedBodyCount += 1;
}
for (const headerName of event.dropped_headers ?? []) {
pushHeaderName(droppedHeaders, headerName);
}
}
if (droppedBodyCount === 0 && droppedHeaders.size === 0) {
return null;
}
return {
droppedBodyCount,
droppedHeaders: Array.from(droppedHeaders).sort(),
};
}
function pushHeaderName(headers: Set<string>, headerName: string): void {
const existing = Array.from(headers).find((h) => h.toLowerCase() === headerName.toLowerCase());
if (existing == null) {
headers.add(headerName);
}
}
function getRedirectWarningLabel(warning: RedirectDropWarning): string {
if (warning.droppedBodyCount > 0 && warning.droppedHeaders.length > 0) {
return "Dropped body and headers";
}
if (warning.droppedBodyCount > 0) {
return "Dropped body";
}
return "Dropped headers";
}
function EnsureCompleteResponse({
response,
Component,
}: {
response: HttpResponse;
Component: ComponentType<{ bodyPath: string }>;
}) {
if (response.bodyPath === null) {
return <div>Empty response body</div>;
}
// Wait until the response has been fully-downloaded
if (response.state !== "closed") {
return (
<EmptyStateText>
<LoadingIcon />
</EmptyStateText>
);
}
return <Component bodyPath={response.bodyPath} />;
}
function HttpSvgViewer({ response }: { response: HttpResponse }) {
const body = useResponseBodyText({ response, filter: null });
if (!body.data) return null;
return <SvgViewer text={body.data} />;
}
function HttpCsvViewer({ response, className }: { response: HttpResponse; className?: string }) {
const body = useResponseBodyText({ response, filter: null });
return <CsvViewer text={body.data ?? null} className={className} />;
}
function HttpMultipartViewer({ response }: { response: HttpResponse }) {
const body = useResponseBodyBytes({ response });
if (body.data == null) return null;
const contentTypeHeader = getContentTypeFromHeaders(response.headers);
const boundary = contentTypeHeader?.split("boundary=")[1] ?? "unknown";
return <MultipartViewer data={body.data} boundary={boundary} idPrefix={response.id} />;
}

View File

@@ -1,122 +0,0 @@
import { linter } from "@codemirror/lint";
import type { HttpRequest } from "@yaakapp-internal/models";
import { patchModel } from "@yaakapp-internal/models";
import { Banner, Icon } from "@yaakapp-internal/ui";
import { useCallback, useMemo } from "react";
import { useKeyValue } from "../hooks/useKeyValue";
import { fireAndForget } from "../lib/fireAndForget";
import { textLikelyContainsJsonComments } from "../lib/jsonComments";
import type { DropdownItem } from "./core/Dropdown";
import { Dropdown } from "./core/Dropdown";
import type { EditorProps } from "./core/Editor/Editor";
import { jsonParseLinter } from "./core/Editor/json-lint";
import { Editor } from "./core/Editor/LazyEditor";
import { IconButton } from "./core/IconButton";
import { IconTooltip } from "./core/IconTooltip";
interface Props {
forceUpdateKey: string;
heightMode: EditorProps["heightMode"];
request: HttpRequest;
}
export function JsonBodyEditor({ forceUpdateKey, heightMode, request }: Props) {
const handleChange = useCallback(
(text: string) => patchModel(request, { body: { ...request.body, text } }),
[request],
);
const autoFix = request.body?.sendJsonComments !== true;
const lintExtension = useMemo(
() =>
linter(
jsonParseLinter(
autoFix
? { allowComments: true, allowTrailingCommas: true }
: { allowComments: false, allowTrailingCommas: false },
),
),
[autoFix],
);
const hasComments = useMemo(
() => textLikelyContainsJsonComments(request.body?.text ?? ""),
[request.body?.text],
);
const { value: bannerDismissed, set: setBannerDismissed } = useKeyValue<boolean>({
namespace: "no_sync",
key: ["json-fix-3", request.workspaceId],
fallback: false,
});
const handleToggleAutoFix = useCallback(() => {
const newBody = { ...request.body };
if (autoFix) {
newBody.sendJsonComments = true;
} else {
delete newBody.sendJsonComments;
}
fireAndForget(patchModel(request, { body: newBody }));
}, [request, autoFix]);
const handleDropdownOpen = useCallback(() => {
if (!bannerDismissed) {
fireAndForget(setBannerDismissed(true));
}
}, [bannerDismissed, setBannerDismissed]);
const showBanner = hasComments && autoFix && !bannerDismissed;
const stripMessage = "Automatically strip comments and trailing commas before sending";
const actions = useMemo<EditorProps["actions"]>(
() => [
showBanner && (
<Banner color="notice" className="!opacity-100 h-sm !py-0 !px-2 flex items-center text-xs">
<p className="inline-flex items-center gap-1 min-w-0">
<span className="truncate">Auto-fix enabled</span>
<Icon icon="arrow_right" size="sm" className="opacity-disabled" />
</p>
</Banner>
),
<div key="settings" className="!opacity-100 !shadow">
<Dropdown
onOpen={handleDropdownOpen}
items={
[
{
label: "Automatically Fix JSON",
keepOpenOnSelect: true,
onSelect: handleToggleAutoFix,
rightSlot: <IconTooltip content={stripMessage} />,
leftSlot: (
<Icon icon={autoFix ? "check_square_checked" : "check_square_unchecked"} />
),
},
] satisfies DropdownItem[]
}
>
<IconButton size="sm" variant="border" icon="settings" title="JSON Settings" />
</Dropdown>
</div>,
],
[handleDropdownOpen, handleToggleAutoFix, autoFix, showBanner],
);
return (
<Editor
forceUpdateKey={forceUpdateKey}
autocompleteFunctions
autocompleteVariables
placeholder="..."
heightMode={heightMode}
defaultValue={`${request.body?.text ?? ""}`}
language="json"
onChange={handleChange}
stateKey={`json.${request.id}`}
actions={actions}
lintExtension={lintExtension}
/>
);
}

View File

@@ -1,113 +0,0 @@
import type { CSSProperties } from "react";
import ReactMarkdown, { type Components } from "react-markdown";
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import remarkGfm from "remark-gfm";
import { ErrorBoundary } from "./ErrorBoundary";
import { Prose } from "./Prose";
interface Props {
children: string | null;
className?: string;
}
export function Markdown({ children, className }: Props) {
if (children == null) return null;
return (
<Prose className={className}>
<ErrorBoundary name="Markdown">
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
{children}
</ReactMarkdown>
</ErrorBoundary>
</Prose>
);
}
const prismTheme = {
'pre[class*="language-"]': {
// Needs to be here, so the lib doesn't add its own
},
// Syntax tokens
comment: { color: "var(--textSubtle)" },
prolog: { color: "var(--textSubtle)" },
doctype: { color: "var(--textSubtle)" },
cdata: { color: "var(--textSubtle)" },
punctuation: { color: "var(--textSubtle)" },
property: { color: "var(--primary)" },
"attr-name": { color: "var(--primary)" },
string: { color: "var(--notice)" },
char: { color: "var(--notice)" },
number: { color: "var(--info)" },
constant: { color: "var(--info)" },
symbol: { color: "var(--info)" },
boolean: { color: "var(--warning)" },
"attr-value": { color: "var(--warning)" },
variable: { color: "var(--success)" },
tag: { color: "var(--info)" },
operator: { color: "var(--danger)" },
keyword: { color: "var(--danger)" },
function: { color: "var(--success)" },
"class-name": { color: "var(--primary)" },
builtin: { color: "var(--danger)" },
selector: { color: "var(--danger)" },
inserted: { color: "var(--success)" },
deleted: { color: "var(--danger)" },
regex: { color: "var(--warning)" },
important: { color: "var(--danger)", fontWeight: "bold" },
italic: { fontStyle: "italic" },
bold: { fontWeight: "bold" },
entity: { cursor: "help" },
};
const lineStyle: CSSProperties = {
paddingRight: "1.5em",
paddingLeft: "0",
opacity: 0.5,
};
const markdownComponents: Partial<Components> = {
// Ensure links open in external browser by adding target="_blank"
a: ({ href, children, ...rest }) => {
if (href && !href.match(/https?:\/\//)) {
href = `http://${href}`;
}
return (
<a target="_blank" rel="noreferrer noopener" href={href} {...rest}>
{children}
</a>
);
},
code(props) {
const { children, className, ref, ...extraProps } = props;
extraProps.node = undefined;
const match = /language-(\w+)/.exec(className || "");
return match ? (
<SyntaxHighlighter
{...extraProps}
CodeTag="code"
showLineNumbers
PreTag="div"
lineNumberStyle={lineStyle}
language={match[1]}
style={prismTheme}
>
{String(children as string).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code {...extraProps} ref={ref} className={className}>
{children}
</code>
);
},
};

View File

@@ -1,12 +0,0 @@
import classNames from "classnames";
import type { ReactNode } from "react";
import "./Prose.css";
interface Props {
children: ReactNode;
className?: string;
}
export function Prose({ className, ...props }: Props) {
return <div className={classNames("prose", className)} {...props} />;
}

View File

@@ -1,44 +0,0 @@
import { workspacesAtom } from "@yaakapp-internal/models";
import { useAtomValue } from "jotai";
import { useEffect } from "react";
import { getRecentCookieJars } from "../hooks/useRecentCookieJars";
import { getRecentEnvironments } from "../hooks/useRecentEnvironments";
import { getRecentRequests } from "../hooks/useRecentRequests";
import { useRecentWorkspaces } from "../hooks/useRecentWorkspaces";
import { fireAndForget } from "../lib/fireAndForget";
import { router } from "../lib/router";
export function RedirectToLatestWorkspace() {
const workspaces = useAtomValue(workspacesAtom);
const recentWorkspaces = useRecentWorkspaces();
useEffect(() => {
if (workspaces.length === 0 || recentWorkspaces == null) {
console.log("No workspaces found to redirect to. Skipping.", {
workspaces,
recentWorkspaces,
});
return;
}
fireAndForget(
(async () => {
const workspaceId = recentWorkspaces[0] ?? workspaces[0]?.id ?? "n/a";
const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? null;
const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? null;
const requestId = (await getRecentRequests(workspaceId))[0] ?? null;
const params = { workspaceId };
const search = {
cookie_jar_id: cookieJarId,
environment_id: environmentId,
request_id: requestId,
};
console.log("Redirecting to workspace", params, search);
await router.navigate({ to: "/workspaces/$workspaceId", params, search });
})(),
);
}, [recentWorkspaces, workspaces, workspaces.length]);
return null;
}

View File

@@ -1,111 +0,0 @@
import { openUrl } from "@tauri-apps/plugin-opener";
import { useLicense } from "@yaakapp-internal/license";
import { useRef } from "react";
import { openSettings } from "../commands/openSettings";
import { useCheckForUpdates } from "../hooks/useCheckForUpdates";
import { useExportData } from "../hooks/useExportData";
import { appInfo } from "../lib/appInfo";
import { showDialog } from "../lib/dialog";
import { importData } from "../lib/importData";
import type { DropdownRef } from "./core/Dropdown";
import { Dropdown } from "./core/Dropdown";
import { Icon } from "@yaakapp-internal/ui";
import { IconButton } from "./core/IconButton";
import { KeyboardShortcutsDialog } from "./KeyboardShortcutsDialog";
export function SettingsDropdown() {
const exportData = useExportData();
const dropdownRef = useRef<DropdownRef>(null);
const checkForUpdates = useCheckForUpdates();
const { check } = useLicense();
return (
<Dropdown
ref={dropdownRef}
items={[
{
label: "Settings",
hotKeyAction: "settings.show",
leftSlot: <Icon icon="settings" />,
onSelect: () => openSettings.mutate(null),
},
{
label: "Keyboard shortcuts",
hotKeyAction: "hotkeys.showHelp",
leftSlot: <Icon icon="keyboard" />,
onSelect: () => {
showDialog({
id: "hotkey",
title: "Keyboard Shortcuts",
size: "dynamic",
render: () => <KeyboardShortcutsDialog />,
});
},
},
{
label: "Plugins",
leftSlot: <Icon icon="puzzle" />,
onSelect: () => openSettings.mutate("plugins"),
},
{ type: "separator", label: "Share Workspace(s)" },
{
label: "Import Data",
leftSlot: <Icon icon="folder_input" />,
onSelect: () => importData.mutate(),
},
{
label: "Export Data",
leftSlot: <Icon icon="folder_output" />,
onSelect: () => exportData.mutate(),
},
{
label: "Create Run Button",
leftSlot: <Icon icon="rocket" />,
onSelect: () => openUrl("https://yaak.app/button/new"),
},
{ type: "separator", label: `Yaak v${appInfo.version}` },
{
label: "Check for Updates",
leftSlot: <Icon icon="update" />,
hidden: !appInfo.featureUpdater,
onSelect: () => checkForUpdates.mutate(),
},
{
label: "Purchase License",
color: "success",
hidden: check.data == null || check.data.status === "active",
leftSlot: <Icon icon="circle_dollar_sign" />,
rightSlot: <Icon icon="external_link" color="success" className="opacity-60" />,
onSelect: () => openUrl("https://yaak.app/pricing"),
},
{
label: "Install CLI",
hidden: appInfo.cliVersion != null,
leftSlot: <Icon icon="square_terminal" />,
rightSlot: <Icon icon="external_link" color="secondary" />,
onSelect: () => openUrl("https://yaak.app/docs/cli"),
},
{
label: "Feedback",
leftSlot: <Icon icon="chat" />,
rightSlot: <Icon icon="external_link" color="secondary" />,
onSelect: () => openUrl("https://yaak.app/feedback"),
},
{
label: "Changelog",
leftSlot: <Icon icon="cake" />,
rightSlot: <Icon icon="external_link" color="secondary" />,
onSelect: () => openUrl(`https://yaak.app/changelog/${appInfo.version}`),
},
]}
>
<IconButton
size="sm"
title="Main Menu"
icon="settings"
iconColor="secondary"
className="pointer-events-auto"
/>
</Dropdown>
);
}

View File

@@ -1,39 +0,0 @@
import { HStack } from "@yaakapp-internal/ui";
import { useMemo } from "react";
import { useFloatingSidebarHidden } from "../hooks/useFloatingSidebarHidden";
import { useSidebarHidden } from "../hooks/useSidebarHidden";
import { CreateDropdown } from "./CreateDropdown";
import { IconButton } from "./core/IconButton";
interface Props {
floating?: boolean;
}
export function SidebarActions({ floating = false }: Props) {
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
const [floatingHidden, setFloatingHidden] = useFloatingSidebarHidden();
const hidden = floating ? floatingHidden : sidebarHidden;
const setHidden = useMemo(
() => (floating ? setFloatingHidden : setSidebarHidden),
[floating, setFloatingHidden, setSidebarHidden],
);
return (
<HStack className="h-full">
<IconButton
onClick={async () => {
await setHidden(!hidden);
}}
className="pointer-events-auto"
size="sm"
title="Toggle sidebar"
icon={hidden ? "left_panel_hidden" : "left_panel_visible"}
iconColor="secondary"
/>
<CreateDropdown hotKeyAction="model.create">
<IconButton size="sm" icon="plus_circle" iconColor="secondary" title="Add Resource" />
</CreateDropdown>
</HStack>
);
}

View File

@@ -1,49 +0,0 @@
import type { WebsocketRequest } from "@yaakapp-internal/models";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import type { CSSProperties } from "react";
import { SplitLayout } from "@yaakapp-internal/ui";
import { activeWorkspaceAtom } from "../hooks/useActiveWorkspace";
import { workspaceLayoutAtom } from "../lib/atoms";
import { WebsocketRequestPane } from "./WebsocketRequestPane";
import { WebsocketResponsePane } from "./WebsocketResponsePane";
interface Props {
activeRequest: WebsocketRequest;
style: CSSProperties;
}
export function WebsocketRequestLayout({ activeRequest, style }: Props) {
const workspaceLayout = useAtomValue(workspaceLayoutAtom);
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
const wsId = activeWorkspace?.id ?? "n/a";
return (
<SplitLayout
storageKey={`websocket_layout::${wsId}`}
className="p-3 gap-1.5"
layout={workspaceLayout}
style={style}
firstSlot={({ orientation, style }) => (
<WebsocketRequestPane
style={style}
activeRequest={activeRequest}
fullHeight={orientation === "horizontal"}
/>
)}
secondSlot={({ style }) => (
<div
style={style}
className={classNames(
"x-theme-responsePane",
"max-h-full h-full grid grid-rows-[minmax(0,1fr)] grid-cols-1",
"bg-surface rounded-md border border-border-subtle",
"shadow relative",
)}
>
<WebsocketResponsePane activeRequest={activeRequest} />
</div>
)}
/>
);
}

View File

@@ -1,221 +0,0 @@
import { type } from "@tauri-apps/plugin-os";
import { settingsAtom, workspacesAtom } from "@yaakapp-internal/models";
import { Banner, HeaderSize, HStack, SidebarLayout } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import * as m from "motion/react-m";
import { useMemo, useState } from "react";
import {
useEnsureActiveCookieJar,
useSubscribeActiveCookieJarId,
} from "../hooks/useActiveCookieJar";
import {
activeEnvironmentAtom,
useSubscribeActiveEnvironmentId,
} from "../hooks/useActiveEnvironment";
import { activeFolderAtom } from "../hooks/useActiveFolder";
import { useSubscribeActiveFolderId } from "../hooks/useActiveFolderId";
import { activeRequestAtom } from "../hooks/useActiveRequest";
import { useSubscribeActiveRequestId } from "../hooks/useActiveRequestId";
import { activeWorkspaceAtom } from "../hooks/useActiveWorkspace";
import { useFloatingSidebarHidden } from "../hooks/useFloatingSidebarHidden";
import { useHotKey } from "../hooks/useHotKey";
import { useSubscribeRecentCookieJars } from "../hooks/useRecentCookieJars";
import { useSubscribeRecentEnvironments } from "../hooks/useRecentEnvironments";
import { useSubscribeRecentRequests } from "../hooks/useRecentRequests";
import { useSubscribeRecentWorkspaces } from "../hooks/useRecentWorkspaces";
import { useSidebarHidden } from "../hooks/useSidebarHidden";
import { useSidebarWidth } from "../hooks/useSidebarWidth";
import { useSyncWorkspaceRequestTitle } from "../hooks/useSyncWorkspaceRequestTitle";
import { duplicateRequestOrFolderAndNavigate } from "../lib/duplicateRequestOrFolderAndNavigate";
import { importData } from "../lib/importData";
import { jotaiStore } from "../lib/jotai";
import { CreateDropdown } from "./CreateDropdown";
import { Button } from "./core/Button";
import { HotkeyList } from "./core/HotkeyList";
import { FeedbackLink } from "./core/Link";
import { ErrorBoundary } from "./ErrorBoundary";
import { FolderLayout } from "./FolderLayout";
import { GrpcConnectionLayout } from "./GrpcConnectionLayout";
import { HttpRequestLayout } from "./HttpRequestLayout";
import Sidebar from "./Sidebar";
import { SidebarActions } from "./SidebarActions";
import { WebsocketRequestLayout } from "./WebsocketRequestLayout";
import { WorkspaceHeader } from "./WorkspaceHeader";
const body = { gridArea: "body" };
export function Workspace() {
// First, subscribe to some things applicable to workspaces
useGlobalWorkspaceHooks();
const workspaces = useAtomValue(workspacesAtom);
const settings = useAtomValue(settingsAtom);
const osType = type();
const [width, setWidth] = useSidebarWidth();
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
const activeEnvironment = useAtomValue(activeEnvironmentAtom);
const [floating, setFloating] = useState(false);
const environmentBgStyle = useMemo(() => {
if (activeEnvironment?.color == null) return undefined;
const background = `linear-gradient(to right, ${activeEnvironment.color} 15%, transparent 40%)`;
return { background };
}, [activeEnvironment?.color]);
// We're loading still
if (workspaces.length === 0) {
return null;
}
const header = (
<HeaderSize
data-tauri-drag-region
size="lg"
className="relative x-theme-appHeader bg-surface"
osType={osType}
hideWindowControls={settings.hideWindowControls}
useNativeTitlebar={settings.useNativeTitlebar}
interfaceScale={settings.interfaceScale}
>
<div className="absolute inset-0 pointer-events-none">
<div style={environmentBgStyle} className="absolute inset-0 opacity-[0.07]" />
<div
style={environmentBgStyle}
className="absolute left-0 right-0 -bottom-[1px] h-[1px] opacity-20"
/>
</div>
<WorkspaceHeader className="pointer-events-none" floatingSidebar={floating} />
</HeaderSize>
);
const workspaceBody = (
<ErrorBoundary name="Workspace Body">
<WorkspaceBody />
</ErrorBoundary>
);
const sidebarContent = floating ? (
<div
className={classNames(
"x-theme-sidebar",
"h-full bg-surface border-r border-border-subtle",
"grid grid-rows-[auto_1fr]",
)}
>
<HeaderSize
hideControls
size="lg"
className="border-transparent flex items-center"
osType={osType}
hideWindowControls={settings.hideWindowControls}
useNativeTitlebar={settings.useNativeTitlebar}
interfaceScale={settings.interfaceScale}
>
<SidebarActions floating />
</HeaderSize>
<ErrorBoundary name="Sidebar (Floating)">
<Sidebar />
</ErrorBoundary>
</div>
) : (
<div className="x-theme-sidebar overflow-hidden bg-surface h-full">
<ErrorBoundary name="Sidebar">
<Sidebar className="border-r border-border-subtle" />
</ErrorBoundary>
</div>
);
return (
<div className="grid w-full h-full grid-rows-[auto_1fr]">
{header}
<SidebarLayout
width={width ?? 250}
onWidthChange={setWidth}
hidden={sidebarHidden ?? false}
onHiddenChange={(hidden) => setSidebarHidden(hidden)}
floatingHidden={floatingSidebarHidden ?? true}
onFloatingHiddenChange={(hidden) => setFloatingSidebarHidden(hidden)}
onFloatingChange={setFloating}
sidebar={sidebarContent}
>
{workspaceBody}
</SidebarLayout>
</div>
);
}
function WorkspaceBody() {
const activeRequest = useAtomValue(activeRequestAtom);
const activeFolder = useAtomValue(activeFolderAtom);
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
if (activeWorkspace == null) {
return (
<m.div
className="m-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
// Delay the entering because the workspaces might load after a slight delay
transition={{ delay: 0.5 }}
>
<Banner color="warning" className="max-w-[30rem]">
The active workspace was not found. Select a workspace from the header menu or report this
bug to <FeedbackLink />
</Banner>
</m.div>
);
}
if (activeRequest?.model === "grpc_request") {
return <GrpcConnectionLayout style={body} />;
}
if (activeRequest?.model === "websocket_request") {
return <WebsocketRequestLayout style={body} activeRequest={activeRequest} />;
}
if (activeRequest?.model === "http_request") {
return <HttpRequestLayout activeRequest={activeRequest} style={body} />;
}
if (activeFolder != null) {
return <FolderLayout folder={activeFolder} style={body} />;
}
return (
<HotkeyList
hotkeys={["model.create", "sidebar.focus", "settings.show"]}
bottomSlot={
<HStack space={1} justifyContent="center" className="mt-3">
<Button variant="border" size="sm" onClick={() => importData.mutate()}>
Import
</Button>
<CreateDropdown hideFolder>
<Button variant="border" forDropdown size="sm">
New Request
</Button>
</CreateDropdown>
</HStack>
}
/>
);
}
function useGlobalWorkspaceHooks() {
useEnsureActiveCookieJar();
useSubscribeActiveRequestId();
useSubscribeActiveFolderId();
useSubscribeActiveEnvironmentId();
useSubscribeActiveCookieJarId();
useSubscribeRecentRequests();
useSubscribeRecentWorkspaces();
useSubscribeRecentEnvironments();
useSubscribeRecentCookieJars();
useSyncWorkspaceRequestTitle();
useHotKey("model.duplicate", () =>
duplicateRequestOrFolderAndNavigate(jotaiStore.get(activeRequestAtom)),
);
}

View File

@@ -1,94 +0,0 @@
import { HStack, Icon } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useAtom, useAtomValue } from "jotai";
import { memo } from "react";
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from "../hooks/useActiveWorkspace";
import { useToggleCommandPalette } from "../hooks/useToggleCommandPalette";
import { workspaceLayoutAtom } from "../lib/atoms";
import { setupOrConfigureEncryption } from "../lib/setupOrConfigureEncryption";
import { CookieDropdown } from "./CookieDropdown";
import { IconButton } from "./core/IconButton";
import { PillButton } from "./core/PillButton";
import { EnvironmentActionsDropdown } from "./EnvironmentActionsDropdown";
import { ImportCurlButton } from "./ImportCurlButton";
import { LicenseBadge } from "./LicenseBadge";
import { RecentRequestsDropdown } from "./RecentRequestsDropdown";
import { SettingsDropdown } from "./SettingsDropdown";
import { SidebarActions } from "./SidebarActions";
import { WorkspaceActionsDropdown } from "./WorkspaceActionsDropdown";
interface Props {
className?: string;
floatingSidebar?: boolean;
}
export const WorkspaceHeader = memo(function WorkspaceHeader({
className,
floatingSidebar,
}: Props) {
const togglePalette = useToggleCommandPalette();
const [workspaceLayout, setWorkspaceLayout] = useAtom(workspaceLayoutAtom);
const workspace = useAtomValue(activeWorkspaceAtom);
const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom);
const showEncryptionSetup =
workspace != null &&
workspaceMeta != null &&
workspace.encryptionKeyChallenge != null &&
workspaceMeta.encryptionKey == null;
return (
<div
className={classNames(
className,
"grid grid-cols-[auto_minmax(0,1fr)_auto] items-center w-full h-full",
)}
>
<HStack space={0.5} className={classNames("flex-1 pointer-events-none")}>
<SidebarActions floating={floatingSidebar} />
<CookieDropdown />
<HStack className="min-w-0">
<WorkspaceActionsDropdown />
<Icon icon="chevron_right" color="secondary" />
<EnvironmentActionsDropdown className="w-auto pointer-events-auto" />
</HStack>
</HStack>
<div className="pointer-events-none w-full max-w-[30vw] mx-auto flex justify-center">
<RecentRequestsDropdown />
</div>
<div className="flex-1 flex gap-1 items-center h-full justify-end pointer-events-none pr-1">
<ImportCurlButton />
{showEncryptionSetup ? (
<PillButton color="danger" onClick={setupOrConfigureEncryption}>
Enter Encryption Key
</PillButton>
) : (
<LicenseBadge />
)}
<IconButton
icon={
workspaceLayout === "responsive"
? "magic_wand"
: workspaceLayout === "horizontal"
? "columns_2"
: "rows_2"
}
title={`Change to ${workspaceLayout === "horizontal" ? "vertical" : "horizontal"} layout`}
size="sm"
iconColor="secondary"
onClick={() =>
setWorkspaceLayout((prev) => (prev === "horizontal" ? "vertical" : "horizontal"))
}
/>
<IconButton
icon="search"
title="Search or execute a command"
size="sm"
hotkeyAction="command_palette.toggle"
iconColor="secondary"
onClick={togglePalette}
/>
<SettingsDropdown />
</div>
</div>
);
});

View File

@@ -1,34 +0,0 @@
import { Button as BaseButton, type ButtonProps as BaseButtonProps } from "@yaakapp-internal/ui";
import { forwardRef, useImperativeHandle, useRef } from "react";
import type { HotkeyAction } from "../../hooks/useHotKey";
import { useFormattedHotkey, useHotKey } from "../../hooks/useHotKey";
export type ButtonProps = BaseButtonProps & {
hotkeyAction?: HotkeyAction;
hotkeyLabelOnly?: boolean;
hotkeyPriority?: number;
};
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
{ hotkeyAction, hotkeyPriority, hotkeyLabelOnly, title, ...props }: ButtonProps,
ref,
) {
const hotkeyTrigger = useFormattedHotkey(hotkeyAction ?? null)?.join("");
const fullTitle = hotkeyTrigger ? `${title ?? ""} ${hotkeyTrigger}`.trim() : title;
const buttonRef = useRef<HTMLButtonElement>(null);
useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
ref,
() => buttonRef.current,
);
useHotKey(
hotkeyAction ?? null,
() => {
buttonRef.current?.click();
},
{ priority: hotkeyPriority, enable: !hotkeyLabelOnly },
);
return <BaseButton ref={buttonRef} title={fullTitle} {...props} />;
});

View File

@@ -1,12 +0,0 @@
import { lazy, Suspense } from "react";
import type { EditorProps } from "./Editor";
const Editor_ = lazy(() => import("./Editor").then((m) => ({ default: m.Editor })));
export function Editor(props: EditorProps) {
return (
<Suspense>
<Editor_ {...props} />
</Suspense>
);
}

View File

@@ -1,21 +0,0 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import { LRParser } from "@lezer/lr";
import { highlight } from "./highlight";
export const parser = LRParser.deserialize({
version: 14,
states:
"!pQQOPOOO`OPO'#C^OeOPO'#CaOjOPO'#CcOoOPO'#CeOOOO'#Ci'#CiOOOO'#Cg'#CgQQOPOOOOOO,58x,58xOOOO,58{,58{OOOO,58},58}OOOO,59P,59POOOO-E6e-E6e",
stateData: "z~ORPOUQOWROYSO~OSWO~OSXO~OSYO~OSZO~ORUWYW~",
goto: "m^PP_PP_P_P_PcPiTTOVQVOR[VTUOV",
nodeNames:
"⚠ Timeline OutgoingLine OutgoingText Newline IncomingLine IncomingText InfoLine InfoText PlainLine PlainText",
maxTerm: 13,
propSources: [highlight],
skippedNodes: [0],
repeatNodeCount: 1,
tokenData:
"%h~RZOYtYZ!]Zztz{!b{!^t!^!_#d!_!`t!`!a$f!a;'St;'S;=`!V<%lOt~ySY~OYtZ;'St;'S;=`!V<%lOt~!YP;=`<%lt~!bOS~~!gUY~OYtZptpq!yq;'St;'S;=`!V<%lOt~#QSW~Y~OY!yZ;'S!y;'S;=`#^<%lO!y~#aP;=`<%l!y~#iUY~OYtZptpq#{q;'St;'S;=`!V<%lOt~$SSU~Y~OY#{Z;'S#{;'S;=`$`<%lO#{~$cP;=`<%l#{~$kUY~OYtZptpq$}q;'St;'S;=`!V<%lOt~%USR~Y~OY$}Z;'S$};'S;=`%b<%lO$}~%eP;=`<%l$}",
tokenizers: [0],
topRules: { Timeline: [0, 1] },
tokenPrec: 36,
});

View File

@@ -1,108 +0,0 @@
/* oxlint-disable no-template-curly-in-string */
import { describe, expect, test } from "vite-plus/test";
import { parser } from "./twig";
function getNodeNames(input: string): string[] {
const tree = parser.parse(input);
const nodes: string[] = [];
const cursor = tree.cursor();
do {
if (cursor.name !== "Template") {
nodes.push(cursor.name);
}
} while (cursor.next());
return nodes;
}
function hasTag(input: string): boolean {
return getNodeNames(input).includes("Tag");
}
function hasError(input: string): boolean {
return getNodeNames(input).includes("⚠");
}
describe("twig grammar", () => {
describe("${[var]} format (valid template tags)", () => {
test("parses simple variable as Tag", () => {
expect(hasTag("${[var]}")).toBe(true);
expect(hasError("${[var]}")).toBe(false);
});
test("parses variable with whitespace as Tag", () => {
expect(hasTag("${[ var ]}")).toBe(true);
expect(hasError("${[ var ]}")).toBe(false);
});
test("parses embedded variable as Tag", () => {
expect(hasTag("hello ${[name]} world")).toBe(true);
expect(hasError("hello ${[name]} world")).toBe(false);
});
test("parses function call as Tag", () => {
expect(hasTag("${[fn()]}")).toBe(true);
expect(hasError("${[fn()]}")).toBe(false);
});
});
describe("${var} format (should be plain text, not tags)", () => {
test("parses ${var} as plain Text without errors", () => {
expect(hasTag("${var}")).toBe(false);
expect(hasError("${var}")).toBe(false);
});
test("parses embedded ${var} as plain Text", () => {
expect(hasTag("hello ${name} world")).toBe(false);
expect(hasError("hello ${name} world")).toBe(false);
});
test("parses JSON with ${var} as plain Text", () => {
const json = '{"key": "${value}"}';
expect(hasTag(json)).toBe(false);
expect(hasError(json)).toBe(false);
});
test("parses multiple ${var} as plain Text", () => {
expect(hasTag("${a} and ${b}")).toBe(false);
expect(hasError("${a} and ${b}")).toBe(false);
});
});
describe("mixed content", () => {
test("distinguishes ${var} from ${[var]} in same string", () => {
const input = "${plain} and ${[tag]}";
expect(hasTag(input)).toBe(true);
expect(hasError(input)).toBe(false);
});
test("parses JSON with ${[var]} as having Tag", () => {
const json = '{"key": "${[value]}"}';
expect(hasTag(json)).toBe(true);
expect(hasError(json)).toBe(false);
});
});
describe("edge cases", () => {
test("handles $ at end of string", () => {
expect(hasError("hello$")).toBe(false);
expect(hasTag("hello$")).toBe(false);
});
test("handles ${ at end of string without crash", () => {
// Incomplete syntax may produce errors, but should not crash
expect(() => parser.parse("hello${")).not.toThrow();
});
test("handles ${[ without closing without crash", () => {
// Unclosed tag may produce partial match, but should not crash
expect(() => parser.parse("${[unclosed")).not.toThrow();
});
test("handles empty ${[]}", () => {
// Empty tags may or may not be valid depending on grammar
// Just ensure no crash
expect(() => parser.parse("${[]}")).not.toThrow();
});
});
});

View File

@@ -1,9 +0,0 @@
import { genericCompletion } from "../genericCompletion";
export const completions = genericCompletion({
options: [
{ label: "http://", type: "constant" },
{ label: "https://", type: "constant" },
],
minMatch: 1,
});

View File

@@ -1,15 +0,0 @@
import classNames from "classnames";
import type { HotkeyAction } from "../../hooks/useHotKey";
import { useHotkeyLabel } from "../../hooks/useHotKey";
interface Props {
action: HotkeyAction;
className?: string;
}
export function HotkeyLabel({ action, className }: Props) {
const label = useHotkeyLabel(action);
return (
<span className={classNames(className, "text-text-subtle whitespace-nowrap")}>{label}</span>
);
}

View File

@@ -1,91 +0,0 @@
import type { GrpcRequest, HttpRequest, WebsocketRequest } from "@yaakapp-internal/models";
import { settingsAtom } from "@yaakapp-internal/models";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import { memo } from "react";
interface Props {
request: HttpRequest | GrpcRequest | WebsocketRequest;
className?: string;
short?: boolean;
noAlias?: boolean;
}
const methodNames: Record<string, string> = {
get: "GET",
put: "PUT",
post: "POST",
patch: "PTCH",
delete: "DELE",
options: "OPTN",
head: "HEAD",
query: "QURY",
graphql: "GQL",
grpc: "GRPC",
websocket: "WS",
};
export const HttpMethodTag = memo(function HttpMethodTag({
request,
className,
short,
noAlias,
}: Props) {
const method =
request.model === "http_request" && request.bodyType === "graphql" && !noAlias
? "graphql"
: request.model === "grpc_request"
? "grpc"
: request.model === "websocket_request"
? "websocket"
: request.method;
return <HttpMethodTagRaw method={method} className={className} short={short} />;
});
export function HttpMethodTagRaw({
className,
method,
short,
forceColor,
}: {
method: string;
className?: string;
short?: boolean;
forceColor?: boolean;
}) {
let label = method.toUpperCase();
if (short) {
label = methodNames[method.toLowerCase()] ?? method.slice(0, 4);
label = label.padStart(4, " ");
}
const m = method.toUpperCase();
const settings = useAtomValue(settingsAtom);
const colored = forceColor || settings.coloredMethods;
return (
<span
className={classNames(
className,
!colored && "text-text-subtle",
colored && m === "GRAPHQL" && "text-info",
colored && m === "WEBSOCKET" && "text-info",
colored && m === "GRPC" && "text-info",
colored && m === "QUERY" && "text-text-subtle",
colored && m === "OPTIONS" && "text-info",
colored && m === "HEAD" && "text-text-subtle",
colored && m === "GET" && "text-primary",
colored && m === "PUT" && "text-warning",
colored && m === "PATCH" && "text-notice",
colored && m === "POST" && "text-success",
colored && m === "DELETE" && "text-danger",
"font-mono flex-shrink-0 whitespace-pre",
"pt-[0.15em]", // Fix for monospace font not vertically centering
)}
>
{label}
</span>
);
}

View File

@@ -1,37 +0,0 @@
import {
IconButton as BaseIconButton,
type IconButtonProps as BaseIconButtonProps,
} from "@yaakapp-internal/ui";
import { forwardRef, useImperativeHandle, useRef } from "react";
import type { HotkeyAction } from "../../hooks/useHotKey";
import { useFormattedHotkey, useHotKey } from "../../hooks/useHotKey";
export type IconButtonProps = BaseIconButtonProps & {
hotkeyAction?: HotkeyAction;
hotkeyLabelOnly?: boolean;
hotkeyPriority?: number;
};
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(
{ hotkeyAction, hotkeyPriority, hotkeyLabelOnly, title, ...props }: IconButtonProps,
ref,
) {
const hotkeyTrigger = useFormattedHotkey(hotkeyAction ?? null)?.join("");
const fullTitle = hotkeyTrigger ? `${title ?? ""} ${hotkeyTrigger}`.trim() : title;
const buttonRef = useRef<HTMLButtonElement>(null);
useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
ref,
() => buttonRef.current,
);
useHotKey(
hotkeyAction ?? null,
() => {
buttonRef.current?.click();
},
{ priority: hotkeyPriority, enable: !hotkeyLabelOnly },
);
return <BaseIconButton ref={buttonRef} title={fullTitle} {...props} />;
});

View File

@@ -1,9 +0,0 @@
import { generateId } from "../../lib/generateId";
import type { Pair, PairWithId } from "./PairEditor";
export function ensurePairId(p: Pair): PairWithId {
if (typeof p.id === "string") {
return p as PairWithId;
}
return { ...p, id: p.id ?? generateId() };
}

View File

@@ -1,14 +0,0 @@
import classNames from "classnames";
import type { ButtonProps } from "./Button";
import { Button } from "./Button";
export function PillButton({ className, ...props }: ButtonProps) {
return (
<Button
size="2xs"
variant="border"
className={classNames(className, "!rounded-full mx-1 !px-3")}
{...props}
/>
);
}

View File

@@ -1,43 +0,0 @@
import type { Color } from "@yaakapp-internal/plugins";
import classNames from "classnames";
import type { ReactNode } from "react";
interface Props {
orientation?: "horizontal" | "vertical";
dashed?: boolean;
className?: string;
children?: ReactNode;
color?: Color;
}
export function Separator({
color,
className,
dashed,
orientation = "horizontal",
children,
}: Props) {
return (
<div role="presentation" className={classNames(className, "flex items-center w-full")}>
{children && (
<div className="text-sm text-text-subtlest mr-2 whitespace-nowrap">{children}</div>
)}
<div
className={classNames(
"h-0 border-t opacity-60",
color == null && "border-border",
color === "primary" && "border-primary",
color === "secondary" && "border-secondary",
color === "success" && "border-success",
color === "notice" && "border-notice",
color === "warning" && "border-warning",
color === "danger" && "border-danger",
color === "info" && "border-info",
dashed && "border-dashed",
orientation === "horizontal" && "w-full h-[1px]",
orientation === "vertical" && "h-full w-[1px]",
)}
/>
</div>
);
}

View File

@@ -1,31 +0,0 @@
import type { WebsocketConnection } from "@yaakapp-internal/models";
import classNames from "classnames";
interface Props {
connection: WebsocketConnection;
className?: string;
}
export function WebsocketStatusTag({ connection, className }: Props) {
const { state, error } = connection;
let label: string;
let colorClass = "text-text-subtle";
if (error) {
label = "ERROR";
colorClass = "text-danger";
} else if (state === "connected") {
label = "CONNECTED";
colorClass = "text-success";
} else if (state === "closing") {
label = "CLOSING";
} else if (state === "closed") {
label = "CLOSED";
colorClass = "text-warning";
} else {
label = "CONNECTING";
}
return <span className={classNames(className, "font-mono", colorClass)}>{label}</span>;
}

View File

@@ -1,49 +0,0 @@
import { showPromptForm } from "../../lib/prompt-form";
import { Banner, InlineCode } from "@yaakapp-internal/ui";
export interface GitCredentials {
username: string;
password: string;
}
export async function promptCredentials({
url: remoteUrl,
error,
}: {
url: string;
error: string | null;
}): Promise<GitCredentials | null> {
const isGitHub = /github\.com/i.test(remoteUrl);
const userLabel = isGitHub ? "GitHub Username" : "Username";
const passLabel = isGitHub ? "GitHub Personal Access Token" : "Password / Token";
const userDescription = isGitHub ? "Use your GitHub username (not your email)." : undefined;
const passDescription = isGitHub
? "GitHub requires a Personal Access Token (PAT) for write operations over HTTPS. Passwords are not supported."
: "Enter your password or access token for this Git server.";
const r = await showPromptForm({
id: "git-credentials",
title: "Credentials Required",
description: error ? (
<Banner color="danger">{error}</Banner>
) : (
<>
Enter credentials for <InlineCode>{remoteUrl}</InlineCode>
</>
),
inputs: [
{ type: "text", name: "username", label: userLabel, description: userDescription },
{
type: "text",
name: "password",
label: passLabel,
description: passDescription,
password: true,
},
],
});
if (r == null) return null;
const username = String(r.username || "");
const password = String(r.password || "");
return { username, password };
}

View File

@@ -1,36 +0,0 @@
import type { PullResult, PushResult } from "@yaakapp-internal/git";
import { showToast } from "../../lib/toast";
export function handlePushResult(r: PushResult) {
switch (r.type) {
case "needs_credentials":
showToast({ id: "push-error", message: "Credentials not found", color: "danger" });
break;
case "success":
showToast({ id: "push-success", message: r.message, color: "success" });
break;
case "up_to_date":
showToast({ id: "push-nothing", message: "Already up-to-date", color: "info" });
break;
}
}
export function handlePullResult(r: PullResult) {
switch (r.type) {
case "needs_credentials":
showToast({ id: "pull-error", message: "Credentials not found", color: "danger" });
break;
case "success":
showToast({ id: "pull-success", message: r.message, color: "success" });
break;
case "up_to_date":
showToast({ id: "pull-nothing", message: "Already up-to-date", color: "info" });
break;
case "diverged":
// Handled by mutation callback before reaching here
break;
case "uncommitted_changes":
// Handled by mutation callback before reaching here
break;
}
}

View File

@@ -1,20 +0,0 @@
import type { GitRemote } from "@yaakapp-internal/git";
import { gitMutations } from "@yaakapp-internal/git";
import { showPromptForm } from "../../lib/prompt-form";
import { gitCallbacks } from "./callbacks";
export async function addGitRemote(dir: string, defaultName?: string): Promise<GitRemote> {
const r = await showPromptForm({
id: "add-remote",
title: "Add Remote",
inputs: [
{ type: "text", label: "Name", name: "name", defaultValue: defaultName },
{ type: "text", label: "URL", name: "url" },
],
});
if (r == null) throw new Error("Cancelled remote prompt");
const name = String(r.name ?? "");
const url = String(r.url ?? "");
return gitMutations(dir, gitCallbacks(dir)).addRemote.mutateAsync({ name, url });
}

View File

@@ -1,13 +0,0 @@
import type { UncommittedChangesStrategy } from "@yaakapp-internal/git";
import { showConfirm } from "../../lib/confirm";
export async function promptUncommittedChangesStrategy(): Promise<UncommittedChangesStrategy> {
const confirmed = await showConfirm({
id: "git-uncommitted-changes",
title: "Uncommitted Changes",
description: "You have uncommitted changes. Commit or reset your changes before pulling.",
confirmText: "Reset and Pull",
color: "danger",
});
return confirmed ? "reset" : "cancel";
}

View File

@@ -1,17 +0,0 @@
// Listen for settings changes, the re-compute theme
import { listen } from "@tauri-apps/api/event";
import type { ModelPayload } from "@yaakapp-internal/models";
import { fireAndForget } from "./lib/fireAndForget";
import { getSettings } from "./lib/settings";
function setFontSizeOnDocument(fontSize: number) {
document.documentElement.style.fontSize = `${fontSize}px`;
}
listen<ModelPayload>("model_write", async (event) => {
if (event.payload.change.type !== "upsert") return;
if (event.payload.model.model !== "settings") return;
setFontSizeOnDocument(event.payload.model.interfaceFontSize);
}).catch(console.error);
fireAndForget(getSettings().then((settings) => setFontSizeOnDocument(settings.interfaceFontSize)));

View File

@@ -1,21 +0,0 @@
// Listen for settings changes, the re-compute theme
import { listen } from "@tauri-apps/api/event";
import type { ModelPayload, Settings } from "@yaakapp-internal/models";
import { fireAndForget } from "./lib/fireAndForget";
import { getSettings } from "./lib/settings";
function setFonts(settings: Settings) {
document.documentElement.style.setProperty("--font-family-editor", settings.editorFont ?? "");
document.documentElement.style.setProperty(
"--font-family-interface",
settings.interfaceFont ?? "",
);
}
listen<ModelPayload>("model_write", async (event) => {
if (event.payload.change.type !== "upsert") return;
if (event.payload.model.model !== "settings") return;
setFonts(event.payload.model);
}).catch(console.error);
fireAndForget(getSettings().then((settings) => setFonts(settings)));

View File

@@ -1,26 +0,0 @@
import { useMutation } from "@tanstack/react-query";
import { InlineCode } from "@yaakapp-internal/ui";
import { showAlert } from "../lib/alert";
import { appInfo } from "../lib/appInfo";
import { minPromiseMillis } from "../lib/minPromiseMillis";
import { invokeCmd } from "../lib/tauri";
export function useCheckForUpdates() {
return useMutation({
mutationKey: ["check_for_updates"],
mutationFn: async () => {
const hasUpdate: boolean = await minPromiseMillis(invokeCmd("cmd_check_for_updates"), 500);
if (!hasUpdate) {
showAlert({
id: "no-updates",
title: "No Update Available",
body: (
<>
You are currently on the latest version <InlineCode>{appInfo.version}</InlineCode>
</>
),
});
}
},
});
}

View File

@@ -1,14 +0,0 @@
import type { HttpResponse } from "@yaakapp-internal/models";
import { copyToClipboard } from "../lib/copy";
import { getResponseBodyText } from "../lib/responseBody";
import { useFastMutation } from "./useFastMutation";
export function useCopyHttpResponse(response: HttpResponse) {
return useFastMutation({
mutationKey: ["copy_http_response", response.id],
async mutationFn() {
const body = await getResponseBodyText({ response, filter: null });
copyToClipboard(body);
},
});
}

View File

@@ -1,33 +0,0 @@
import { createWorkspaceModel } from "@yaakapp-internal/models";
import { jotaiStore } from "../lib/jotai";
import { showPrompt } from "../lib/prompt";
import { setWorkspaceSearchParams } from "../lib/setWorkspaceSearchParams";
import { activeWorkspaceIdAtom } from "./useActiveWorkspace";
import { useFastMutation } from "./useFastMutation";
export function useCreateCookieJar() {
return useFastMutation({
mutationKey: ["create_cookie_jar"],
mutationFn: async () => {
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
if (workspaceId == null) {
throw new Error("Cannot create cookie jar when there's no active workspace");
}
const name = await showPrompt({
id: "new-cookie-jar",
title: "New CookieJar",
placeholder: "My Jar",
confirmText: "Create",
label: "Name",
defaultValue: "My Jar",
});
if (name == null) return null;
return createWorkspaceModel({ model: "cookie_jar", workspaceId, name });
},
onSuccess: async (cookieJarId) => {
setWorkspaceSearchParams({ cookie_jar_id: cookieJarId });
},
});
}

View File

@@ -1,14 +0,0 @@
import { useCallback } from "react";
import { CreateWorkspaceDialog } from "../components/CreateWorkspaceDialog";
import { showDialog } from "../lib/dialog";
export function useCreateWorkspace() {
return useCallback(() => {
showDialog({
id: "create-workspace",
title: "Create Workspace",
size: "sm",
render: ({ hide }) => <CreateWorkspaceDialog hide={hide} />,
});
}, []);
}

View File

@@ -1,12 +0,0 @@
import { invokeCmd } from "../lib/tauri";
import { useFastMutation } from "./useFastMutation";
export function useDeleteGrpcConnections(requestId?: string) {
return useFastMutation({
mutationKey: ["delete_grpc_connections", requestId],
mutationFn: async () => {
if (requestId === undefined) return;
await invokeCmd("cmd_delete_all_grpc_connections", { requestId });
},
});
}

View File

@@ -1,12 +0,0 @@
import { invokeCmd } from "../lib/tauri";
import { useFastMutation } from "./useFastMutation";
export function useDeleteHttpResponses(requestId?: string) {
return useFastMutation({
mutationKey: ["delete_http_responses", requestId],
mutationFn: async () => {
if (requestId === undefined) return;
await invokeCmd("cmd_delete_all_http_responses", { requestId });
},
});
}

View File

@@ -1,52 +0,0 @@
import {
grpcConnectionsAtom,
httpResponsesAtom,
websocketConnectionsAtom,
} from "@yaakapp-internal/models";
import { useAtomValue } from "jotai";
import { showAlert } from "../lib/alert";
import { showConfirmDelete } from "../lib/confirm";
import { jotaiStore } from "../lib/jotai";
import { pluralizeCount } from "../lib/pluralize";
import { invokeCmd } from "../lib/tauri";
import { activeWorkspaceIdAtom } from "./useActiveWorkspace";
import { useFastMutation } from "./useFastMutation";
export function useDeleteSendHistory() {
const httpResponses = useAtomValue(httpResponsesAtom);
const grpcConnections = useAtomValue(grpcConnectionsAtom);
const websocketConnections = useAtomValue(websocketConnectionsAtom);
const labels = [
httpResponses.length > 0 ? pluralizeCount("Http Response", httpResponses.length) : null,
grpcConnections.length > 0 ? pluralizeCount("Grpc Connection", grpcConnections.length) : null,
websocketConnections.length > 0
? pluralizeCount("WebSocket Connection", websocketConnections.length)
: null,
].filter((l) => l != null);
return useFastMutation({
mutationKey: ["delete_send_history", labels],
mutationFn: async () => {
if (labels.length === 0) {
showAlert({
id: "no-responses",
title: "Nothing to Delete",
body: "There is no Http, Grpc, or Websocket history",
});
return;
}
const confirmed = await showConfirmDelete({
id: "delete-send-history",
title: "Clear Send History",
description: <>Delete {labels.join(" and ")}?</>,
});
if (!confirmed) return false;
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
await invokeCmd("cmd_delete_send_history", { workspaceId });
return true;
},
});
}

View File

@@ -1,10 +0,0 @@
import type { Environment } from "@yaakapp-internal/models";
import { useKeyValue } from "./useKeyValue";
export function useEnvironmentValueVisibility(environment: Environment) {
return useKeyValue<boolean>({
namespace: "global",
key: ["environmentValueVisibility", environment.workspaceId],
fallback: false,
});
}

View File

@@ -1,41 +0,0 @@
import { workspacesAtom } from "@yaakapp-internal/models";
import { ExportDataDialog } from "../components/ExportDataDialog";
import { showAlert } from "../lib/alert";
import { showDialog } from "../lib/dialog";
import { jotaiStore } from "../lib/jotai";
import { showToast } from "../lib/toast";
import { activeWorkspaceAtom } from "./useActiveWorkspace";
import { useFastMutation } from "./useFastMutation";
export function useExportData() {
return useFastMutation({
mutationKey: ["export_data"],
onError: (err: string) => {
showAlert({ id: "export-failed", title: "Export Failed", body: err });
},
mutationFn: async () => {
const activeWorkspace = jotaiStore.get(activeWorkspaceAtom);
const workspaces = jotaiStore.get(workspacesAtom);
if (activeWorkspace == null || workspaces.length === 0) return;
showDialog({
id: "export-data",
title: "Export Data",
size: "md",
noPadding: true,
render: ({ hide }) => (
<ExportDataDialog
onHide={hide}
onSuccess={() => {
showToast({
color: "success",
message: "Data export successful",
});
}}
/>
),
});
},
});
}

View File

@@ -1,14 +0,0 @@
import { useAtomValue } from "jotai";
import { activeWorkspaceAtom } from "./useActiveWorkspace";
import { useKeyValue } from "./useKeyValue";
export function useFloatingSidebarHidden() {
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
const { set, value } = useKeyValue<boolean>({
namespace: "no_sync",
key: ["floating_sidebar_hidden", activeWorkspace?.id ?? "n/a"],
fallback: false,
});
return [value, set] as const;
}

View File

@@ -1,71 +0,0 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import { emit } from "@tauri-apps/api/event";
import type { GrpcConnection, GrpcRequest } from "@yaakapp-internal/models";
import { jotaiStore } from "../lib/jotai";
import { minPromiseMillis } from "../lib/minPromiseMillis";
import { invokeCmd } from "../lib/tauri";
import { activeEnvironmentIdAtom, useActiveEnvironment } from "./useActiveEnvironment";
import { useDebouncedValue } from "@yaakapp-internal/ui";
export interface ReflectResponseService {
name: string;
methods: { name: string; schema: string; serverStreaming: boolean; clientStreaming: boolean }[];
}
export function useGrpc(
req: GrpcRequest | null,
conn: GrpcConnection | null,
protoFiles: string[],
) {
const requestId = req?.id ?? "n/a";
const environment = useActiveEnvironment();
const go = useMutation<void, string>({
mutationKey: ["grpc_go", conn?.id],
mutationFn: () =>
invokeCmd<void>("cmd_grpc_go", { requestId, environmentId: environment?.id, protoFiles }),
});
const send = useMutation({
mutationKey: ["grpc_send", conn?.id],
mutationFn: ({ message }: { message: string }) =>
emit(`grpc_client_msg_${conn?.id ?? "none"}`, { Message: message }),
});
const cancel = useMutation({
mutationKey: ["grpc_cancel", conn?.id ?? "n/a"],
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? "none"}`, "Cancel"),
});
const commit = useMutation({
mutationKey: ["grpc_commit", conn?.id ?? "n/a"],
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? "none"}`, "Commit"),
});
const debouncedUrl = useDebouncedValue<string>(req?.url ?? "", 1000);
const reflect = useQuery<ReflectResponseService[], string>({
enabled: req != null,
queryKey: ["grpc_reflect", req?.id ?? "n/a", debouncedUrl, protoFiles],
staleTime: Infinity,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
queryFn: () => {
const environmentId = jotaiStore.get(activeEnvironmentIdAtom);
return minPromiseMillis<ReflectResponseService[]>(
invokeCmd("cmd_grpc_reflect", { requestId, protoFiles, environmentId }),
300,
);
},
});
return {
go,
reflect,
cancel,
commit,
isStreaming: conn != null && conn.state !== "closed",
send,
};
}

View File

@@ -1,32 +0,0 @@
import { invoke } from "@tauri-apps/api/core";
import type { HttpResponse, HttpResponseEvent } from "@yaakapp-internal/models";
import {
httpResponseEventsAtom,
mergeModelsInStore,
replaceModelsInStore,
} from "@yaakapp-internal/models";
import { useAtomValue } from "jotai";
import { useEffect } from "react";
import { fireAndForget } from "../lib/fireAndForget";
export function useHttpResponseEvents(response: HttpResponse | null) {
const allEvents = useAtomValue(httpResponseEventsAtom);
useEffect(() => {
if (response?.id == null) {
replaceModelsInStore("http_response_event", []);
return;
}
// Fetch events from database, filtering out events from other responses and merging atomically
fireAndForget(
invoke<HttpResponseEvent[]>("cmd_get_http_response_events", { responseId: response.id }).then(
(events) =>
mergeModelsInStore("http_response_event", events, (e) => e.responseId === response.id),
),
);
}, [response?.id]);
const events = allEvents.filter((e) => e.responseId === response?.id);
return { data: events, error: null, isLoading: false };
}

View File

@@ -1,11 +0,0 @@
import { installPluginFromDirectory } from "@yaakapp-internal/plugins";
import { useFastMutation } from "./useFastMutation";
export function useInstallPlugin() {
return useFastMutation<void, unknown, string>({
mutationKey: ["install_plugin"],
mutationFn: async (directory: string) => {
await installPluginFromDirectory(directory);
},
});
}

View File

@@ -1,34 +0,0 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import type { ModelPayload } from "@yaakapp-internal/models";
import { atom, useAtomValue } from "jotai";
import { generateId } from "../lib/generateId";
import { jotaiStore } from "../lib/jotai";
const requestUpdateKeyAtom = atom<Record<string, string>>({});
getCurrentWebviewWindow()
.listen<ModelPayload>("model_write", ({ payload }) => {
if (payload.change.type !== "upsert") return;
if (
(payload.model.model === "http_request" ||
payload.model.model === "grpc_request" ||
payload.model.model === "websocket_request") &&
((payload.updateSource.type === "window" &&
payload.updateSource.label !== getCurrentWebviewWindow().label) ||
payload.updateSource.type !== "window")
) {
wasUpdatedExternally(payload.model.id);
}
})
.catch(console.error);
export function wasUpdatedExternally(changedRequestId: string) {
jotaiStore.set(requestUpdateKeyAtom, (m) => ({ ...m, [changedRequestId]: generateId() }));
}
export function useRequestUpdateKey(requestId: string | null) {
const keys = useAtomValue(requestUpdateKeyAtom);
const key = keys[requestId ?? "n/a"];
return `${requestId}::${key ?? "default"}`;
}

View File

@@ -1,10 +0,0 @@
import { settingsAtom } from "@yaakapp-internal/models";
import { useAtomValue } from "jotai";
import { resolveAppearance } from "../lib/theme/appearance";
import { usePreferredAppearance } from "./usePreferredAppearance";
export function useResolvedAppearance() {
const preferredAppearance = usePreferredAppearance();
const settings = useAtomValue(settingsAtom);
return resolveAppearance(preferredAppearance, settings.appearance);
}

View File

@@ -1,12 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import type { HttpResponse } from "@yaakapp-internal/models";
import type { ServerSentEvent } from "@yaakapp-internal/sse";
import { getResponseBodyEventSource } from "../lib/responseBody";
export function useResponseBodyEventSource(response: HttpResponse) {
return useQuery<ServerSentEvent[]>({
placeholderData: (prev) => prev, // Keep previous data on refetch
queryKey: ["response-body-event-source", response.id, response.contentLength],
queryFn: () => getResponseBodyEventSource(response),
});
}

View File

@@ -1,8 +0,0 @@
import { useLocalStorage } from "react-use";
const DEFAULT_VIEW_MODE = "pretty";
export function useResponseViewMode(requestId?: string): [string, (m: "pretty" | "raw") => void] {
const [value, setValue] = useLocalStorage<"pretty" | "raw">(`response_view_mode::${requestId}`);
return [value ?? DEFAULT_VIEW_MODE, setValue];
}

View File

@@ -1,36 +0,0 @@
import { save } from "@tauri-apps/plugin-dialog";
import type { HttpResponse } from "@yaakapp-internal/models";
import { getModel } from "@yaakapp-internal/models";
import mime from "mime";
import slugify from "slugify";
import { InlineCode } from "@yaakapp-internal/ui";
import { getContentTypeFromHeaders } from "../lib/model_util";
import { invokeCmd } from "../lib/tauri";
import { showToast } from "../lib/toast";
import { useFastMutation } from "./useFastMutation";
export function useSaveResponse(response: HttpResponse) {
return useFastMutation({
mutationKey: ["save_response", response.id],
mutationFn: async () => {
const request = getModel("http_request", response.requestId);
if (request == null) return null;
const contentType = getContentTypeFromHeaders(response.headers) ?? "unknown";
const ext = mime.getExtension(contentType);
const slug = slugify(request.name || "response", { lower: true });
const filepath = await save({
defaultPath: ext ? `${slug}.${ext}` : slug,
title: "Save Response",
});
await invokeCmd("cmd_save_response", { responseId: response.id, filepath });
showToast({
message: (
<>
Response saved to <InlineCode>{filepath}</InlineCode>
</>
),
});
},
});
}

View File

@@ -1,14 +0,0 @@
import { useAtomValue } from "jotai";
import { activeWorkspaceIdAtom } from "./useActiveWorkspace";
import { useKeyValue } from "./useKeyValue";
export function useSidebarHidden() {
const activeWorkspaceId = useAtomValue(activeWorkspaceIdAtom);
const { set, value } = useKeyValue<boolean>({
namespace: "no_sync",
key: ["sidebar_hidden", activeWorkspaceId ?? "n/a"],
fallback: false,
});
return [value, set] as const;
}

View File

@@ -1,8 +0,0 @@
import { type } from "@tauri-apps/plugin-os";
import { useIsFullscreen } from "@yaakapp-internal/ui";
export function useStoplightsVisible() {
const fullscreen = useIsFullscreen();
const stoplightsVisible = type() === "macos" && !fullscreen;
return stoplightsVisible;
}

View File

@@ -1,16 +0,0 @@
import { useHotKey } from "./useHotKey";
import { useListenToTauriEvent } from "./useListenToTauriEvent";
import { useZoom } from "./useZoom";
export function useSyncZoomSetting() {
// Handle Zoom.
// Note, Mac handles it in the app menu, so need to also handle keyboard
// shortcuts for Windows/Linux
const zoom = useZoom();
useHotKey("app.zoom_in", zoom.zoomIn);
useListenToTauriEvent("zoom_in", zoom.zoomIn);
useHotKey("app.zoom_out", zoom.zoomOut);
useListenToTauriEvent("zoom_out", zoom.zoomOut);
useHotKey("app.zoom_reset", zoom.zoomReset);
useListenToTauriEvent("zoom_reset", zoom.zoomReset);
}

View File

@@ -1,15 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import type { Tokens } from "@yaakapp-internal/templates";
import { invokeCmd } from "../lib/tauri";
export function useTemplateTokensToString(tokens: Tokens) {
return useQuery<string>({
refetchOnWindowFocus: false,
queryKey: ["template_tokens_to_string", tokens],
queryFn: () => templateTokensToString(tokens),
});
}
export async function templateTokensToString(tokens: Tokens): Promise<string> {
return invokeCmd("cmd_template_tokens_to_string", { tokens });
}

View File

@@ -1,14 +0,0 @@
import type { TimelineViewMode } from "../components/HttpResponsePane";
import { useKeyValue } from "./useKeyValue";
const DEFAULT_VIEW_MODE: TimelineViewMode = "timeline";
export function useTimelineViewMode() {
const { set, value } = useKeyValue<TimelineViewMode>({
namespace: "no_sync",
key: "timeline_view_mode",
fallback: DEFAULT_VIEW_MODE,
});
return [value ?? DEFAULT_VIEW_MODE, set] as const;
}

View File

@@ -1,20 +0,0 @@
import { useCallback } from "react";
import { CommandPaletteDialog } from "../components/CommandPaletteDialog";
import { toggleDialog } from "../lib/dialog";
export function useToggleCommandPalette() {
const togglePalette = useCallback(() => {
toggleDialog({
id: "command_palette",
size: "dynamic",
hideX: true,
className: "mb-auto mt-[4rem] !max-h-[min(30rem,calc(100vh-4rem))]",
vAlign: "top",
noPadding: true,
noScroll: true,
render: ({ hide }) => <CommandPaletteDialog onClose={hide} />,
});
}, []);
return togglePalette;
}

View File

@@ -1,36 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Yaak App</title>
<!-- <script src="http://localhost:8097"></script>-->
<!-- Certain elements like webview (and maybe <select>?) will use background
color depending on document background color-->
<style>
html,
body {
background-color: white;
}
@media (prefers-color-scheme: dark) {
html,
body {
background-color: #1b1a29;
}
}
</style>
</head>
<body class="text-base">
<div id="root"></div>
<div id="cm-portal" class="cm-portal"></div>
<div id="react-portal"></div>
<div id="radix-portal" class="cm-portal"></div>
<script type="module" src="/theme.ts"></script>
<script type="module" src="/font-size.ts"></script>
<script type="module" src="/font.ts"></script>
<script type="module" src="/main.tsx"></script>
</body>
</html>

View File

@@ -1,30 +0,0 @@
import type { AlertProps } from "../components/core/Alert";
import { Alert } from "../components/core/Alert";
import type { DialogProps } from "../components/core/Dialog";
import { showDialog } from "./dialog";
interface AlertArgs {
id: string;
title: DialogProps["title"];
body: AlertProps["body"];
size?: DialogProps["size"];
}
export function showAlert({ id, title, body, size = "sm" }: AlertArgs) {
showDialog({
id,
title,
hideX: true,
size,
disableBackdropClose: true, // Prevent accidental dismisses
render: ({ hide }) => Alert({ onHide: hide, body }),
});
}
export function showSimpleAlert(title: string, message: string) {
showAlert({
id: "simple-alert",
body: message,
title: title,
});
}

View File

@@ -1,22 +0,0 @@
import deepEqual from "@gilbarbara/deep-equal";
import type { UpdateInfo } from "@yaakapp-internal/tauri-client";
import type { Atom } from "jotai";
import { atom } from "jotai";
import { selectAtom } from "jotai/utils";
import type { SplitLayoutLayout } from "@yaakapp-internal/ui";
import { atomWithKVStorage } from "./atoms/atomWithKVStorage";
export function deepEqualAtom<T>(a: Atom<T>) {
return selectAtom(
a,
(v) => v,
(a, b) => deepEqual(a, b),
);
}
export const workspaceLayoutAtom = atomWithKVStorage<SplitLayoutLayout>(
"workspace_layout",
"horizontal",
);
export const updateAvailableAtom = atom<Omit<UpdateInfo, "replyEventId"> | null>(null);

View File

@@ -1,99 +0,0 @@
import MimeType from "whatwg-mimetype";
import type { EditorProps } from "../components/core/Editor/Editor";
export function languageFromContentType(
contentType: string | null,
content: string | null = null,
): EditorProps["language"] {
const justContentType = contentType?.split(";")[0] ?? contentType ?? "";
if (justContentType.includes("json")) {
return "json";
}
if (justContentType.includes("xml")) {
return "xml";
}
if (justContentType.includes("html")) {
const detected = languageFromContent(content);
if (detected === "xml") {
// If it's detected as XML, but is already HTML, don't change it
return "html";
}
return detected;
}
if (justContentType.includes("javascript")) {
// Sometimes `application/javascript` returns JSON, so try detecting that
return languageFromContent(content, "javascript");
}
if (justContentType.includes("markdown")) {
return "markdown";
}
return languageFromContent(content, "text");
}
export function languageFromContent(
content: string | null,
fallback?: EditorProps["language"],
): EditorProps["language"] {
if (content == null) return "text";
const firstBytes = content.slice(0, 20).trim();
if (firstBytes.startsWith("{") || firstBytes.startsWith("[")) {
return "json";
}
if (
firstBytes.toLowerCase().startsWith("<!doctype") ||
firstBytes.toLowerCase().startsWith("<html")
) {
return "html";
}
if (firstBytes.startsWith("<")) {
return "xml";
}
return fallback;
}
export function isJSON(content: string | null | undefined): boolean {
if (typeof content !== "string") return false;
try {
JSON.parse(content);
return true;
} catch {
return false;
}
}
export function isProbablyTextContentType(contentType: string | null): boolean {
if (contentType == null) return false;
const mimeType = getMimeTypeFromContentType(contentType).essence;
const normalized = mimeType.toLowerCase();
// Check if it starts with "text/"
if (normalized.startsWith("text/")) {
return true;
}
// Common text mimetypes and suffixes
return [
"application/json",
"application/xml",
"application/javascript",
"application/yaml",
"+json",
"+xml",
"+yaml",
"+text",
].some((textType) => normalized === textType || normalized.endsWith(textType));
}
export function getMimeTypeFromContentType(contentType: string): MimeType {
try {
return new MimeType(contentType);
} catch {
return new MimeType("text/plain");
}
}

View File

@@ -1,121 +0,0 @@
export const charsets = [
"utf-8",
"us-ascii",
"950",
"ASMO-708",
"CP1026",
"CP870",
"DOS-720",
"DOS-862",
"EUC-CN",
"IBM437",
"Johab",
"Windows-1252",
"X-EBCDIC-Spain",
"big5",
"cp866",
"csISO2022JP",
"ebcdic-cp-us",
"euc-kr",
"gb2312",
"hz-gb-2312",
"ibm737",
"ibm775",
"ibm850",
"ibm852",
"ibm857",
"ibm861",
"ibm869",
"iso-2022-jp",
"iso-2022-jp",
"iso-2022-kr",
"iso-8859-1",
"iso-8859-15",
"iso-8859-2",
"iso-8859-3",
"iso-8859-4",
"iso-8859-5",
"iso-8859-6",
"iso-8859-7",
"iso-8859-8",
"iso-8859-8-i",
"iso-8859-9",
"koi8-r",
"koi8-u",
"ks_c_5601-1987",
"macintosh",
"shift_jis",
"unicode",
"unicodeFFFE",
"utf-7",
"windows-1250",
"windows-1251",
"windows-1253",
"windows-1254",
"windows-1255",
"windows-1256",
"windows-1257",
"windows-1258",
"windows-874",
"x-Chinese-CNS",
"x-Chinese-Eten",
"x-EBCDIC-Arabic",
"x-EBCDIC-CyrillicRussian",
"x-EBCDIC-CyrillicSerbianBulgarian",
"x-EBCDIC-DenmarkNorway",
"x-EBCDIC-FinlandSweden",
"x-EBCDIC-Germany",
"x-EBCDIC-Greek",
"x-EBCDIC-GreekModern",
"x-EBCDIC-Hebrew",
"x-EBCDIC-Icelandic",
"x-EBCDIC-Italy",
"x-EBCDIC-JapaneseAndJapaneseLatin",
"x-EBCDIC-JapaneseAndKana",
"x-EBCDIC-JapaneseAndUSCanada",
"x-EBCDIC-JapaneseKatakana",
"x-EBCDIC-KoreanAndKoreanExtended",
"x-EBCDIC-KoreanExtended",
"x-EBCDIC-SimplifiedChinese",
"x-EBCDIC-Thai",
"x-EBCDIC-TraditionalChinese",
"x-EBCDIC-Turkish",
"x-EBCDIC-UK",
"x-Europa",
"x-IA5",
"x-IA5-German",
"x-IA5-Norwegian",
"x-IA5-Swedish",
"x-ebcdic-cp-us-euro",
"x-ebcdic-denmarknorway-euro",
"x-ebcdic-finlandsweden-euro",
"x-ebcdic-finlandsweden-euro",
"x-ebcdic-france-euro",
"x-ebcdic-germany-euro",
"x-ebcdic-icelandic-euro",
"x-ebcdic-international-euro",
"x-ebcdic-italy-euro",
"x-ebcdic-spain-euro",
"x-ebcdic-uk-euro",
"x-euc-jp",
"x-iscii-as",
"x-iscii-be",
"x-iscii-de",
"x-iscii-gu",
"x-iscii-ka",
"x-iscii-ma",
"x-iscii-or",
"x-iscii-pa",
"x-iscii-ta",
"x-iscii-te",
"x-mac-arabic",
"x-mac-ce",
"x-mac-chinesesimp",
"x-mac-cyrillic",
"x-mac-greek",
"x-mac-hebrew",
"x-mac-icelandic",
"x-mac-japanese",
"x-mac-korean",
"x-mac-turkish",
];

View File

@@ -1 +0,0 @@
export const connections = ["close", "keep-alive"];

View File

@@ -1 +0,0 @@
export const encodings = ["*", "gzip", "compress", "deflate", "br", "zstd", "identity"];

View File

@@ -1,70 +0,0 @@
import type { GenericCompletionOption } from "@yaakapp-internal/plugins";
export const headerNames: (GenericCompletionOption | string)[] = [
{
type: "constant",
label: "Content-Type",
info: "The original media type of the resource (prior to any content encoding applied for sending)",
},
{
type: "constant",
label: "Content-Length",
info: "The size of the message body, in bytes, sent to the recipient",
},
{
type: "constant",
label: "Accept",
info:
"The content types, expressed as MIME types, the client is able to understand. " +
"The server uses content negotiation to select one of the proposals and informs " +
"the client of the choice with the Content-Type response header. Browsers set required " +
"values for this header based on the context of the request. For example, a browser uses " +
"different values in a request when fetching a CSS stylesheet, image, video, or a script.",
},
{
type: "constant",
label: "Accept-Encoding",
info:
"The content encoding (usually a compression algorithm) that the client can understand. " +
"The server uses content negotiation to select one of the proposals and informs the client " +
"of that choice with the Content-Encoding response header.",
},
{
type: "constant",
label: "Accept-Language",
info:
"The natural language and locale that the client prefers. The server uses content " +
"negotiation to select one of the proposals and informs the client of the choice with " +
"the Content-Language response header.",
},
{
type: "constant",
label: "Authorization",
info: "Provide credentials that authenticate a user agent with a server, allowing access to a protected resource.",
},
"Cache-Control",
"Cookie",
"Connection",
"Content-MD5",
"Date",
"Expect",
"Forwarded",
"From",
"Host",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Range",
"If-Unmodified-Since",
"Max-Forwards",
"Origin",
"Pragma",
"Proxy-Authorization",
"Range",
"Referer",
"TE",
"User-Agent",
"Upgrade",
"Via",
"Warning",
];

View File

@@ -1,213 +0,0 @@
export const mimeTypes = [
"application/json",
"application/xml",
"application/x-www-form-urlencoded",
"multipart/form-data",
"multipart/byteranges",
"application/octet-stream",
"text/plain",
"application/javascript",
"application/pdf",
"text/html",
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
"text/css",
"application/x-pkcs12",
"application/xhtml+xml",
"application/andrew-inset",
"application/applixware",
"application/atom+xml",
"application/atomcat+xml",
"application/atomsvc+xml",
"application/bdoc",
"application/cu-seeme",
"application/davmount+xml",
"application/docbook+xml",
"application/dssc+xml",
"application/ecmascript",
"application/epub+zip",
"application/exi",
"application/font-tdpfr",
"application/font-woff",
"application/font-woff2",
"application/geo+json",
"application/graphql",
"application/java-serialized-object",
"application/json5",
"application/jsonml+json",
"application/ld+json",
"application/lost+xml",
"application/manifest+json",
"application/mp4",
"application/msword",
"application/mxf",
"application/n-triples",
"application/n-quads",
"application/oda",
"application/ogg",
"application/pgp-encrypted",
"application/pgp-signature",
"application/pics-rules",
"application/pkcs10",
"application/pkcs7-mime",
"application/pkcs7-signature",
"application/pkcs8",
"application/postscript",
"application/pskc+xml",
"application/rdf+xml",
"application/resource-lists+xml",
"application/resource-lists-diff+xml",
"application/rls-services+xml",
"application/rsd+xml",
"application/rss+xml",
"application/rtf",
"application/sdp",
"application/shf+xml",
"application/timestamped-data",
"application/trig",
"application/vnd.android.package-archive",
"application/vnd.api+json",
"application/vnd.apple.installer+xml",
"application/vnd.apple.mpegurl",
"application/vnd.apple.pkpass",
"application/vnd.bmi",
"application/vnd.curl.car",
"application/vnd.curl.pcurl",
"application/vnd.dna",
"application/vnd.google-apps.document",
"application/vnd.google-apps.presentation",
"application/vnd.google-apps.spreadsheet",
"application/vnd.hal+xml",
"application/vnd.handheld-entertainment+xml",
"application/vnd.macports.portpkg",
"application/vnd.unity",
"application/vnd.zul",
"application/widget",
"application/wsdl+xml",
"application/x-7z-compressed",
"application/x-ace-compressed",
"application/x-bittorrent",
"application/x-bzip",
"application/x-bzip2",
"application/x-cfs-compressed",
"application/x-chrome-extension",
"application/x-cocoa",
"application/x-envoy",
"application/x-eva",
"font/opentype",
"application/x-gca-compressed",
"application/x-gtar",
"application/x-hdf",
"application/x-httpd-php",
"application/x-install-instructions",
"application/x-latex",
"application/x-lua-bytecode",
"application/x-lzh-compressed",
"application/x-ms-application",
"application/x-ms-shortcut",
"application/x-ndjson",
"application/x-perl",
"application/x-pkcs7-certificates",
"application/x-pkcs7-certreqresp",
"application/x-rar-compressed",
"application/x-sh",
"application/x-sql",
"application/x-subrip",
"application/x-t3vm-image",
"application/x-tads",
"application/x-tar",
"application/x-tcl",
"application/x-tex",
"application/x-x509-ca-cert",
"application/xop+xml",
"application/xslt+xml",
"application/zip",
"audio/3gpp",
"audio/adpcm",
"audio/basic",
"audio/midi",
"audio/mpeg",
"audio/mp4",
"audio/ogg",
"audio/silk",
"audio/wave",
"audio/webm",
"audio/x-aac",
"audio/x-aiff",
"audio/x-caf",
"audio/x-flac",
"audio/xm",
"image/bmp",
"image/cgm",
"image/sgi",
"image/svg+xml",
"image/tiff",
"image/x-3ds",
"image/x-freehand",
"image/x-icon",
"image/x-jng",
"image/x-mrsid-image",
"image/x-pcx",
"image/x-pict",
"image/x-rgb",
"image/x-tga",
"message/rfc822",
"text/cache-manifest",
"text/calendar",
"text/coffeescript",
"text/csv",
"text/hjson",
"text/jade",
"text/jsx",
"text/less",
"text/mathml",
"text/n3",
"text/richtext",
"text/sgml",
"text/slim",
"text/stylus",
"text/tab-separated-values",
"text/turtle",
"text/uri-list",
"text/vcard",
"text/vnd.curl",
"text/vnd.fly",
"text/vtt",
"text/x-asm",
"text/x-c",
"text/x-component",
"text/x-fortran",
"text/x-handlebars-template",
"text/x-java-source",
"text/x-lua",
"text/x-markdown",
"text/x-nfo",
"text/x-opml",
"text/x-pascal",
"text/x-processing",
"text/x-sass",
"text/x-scss",
"text/x-vcalendar",
"text/xml",
"text/yaml",
"video/3gpp",
"video/3gpp2",
"video/h261",
"video/h263",
"video/h264",
"video/jpeg",
"video/jpm",
"video/mj2",
"video/mp2t",
"video/mp4",
"video/mpeg",
"video/ogg",
"video/quicktime",
"video/webm",
"video/x-f4v",
"video/x-fli",
"video/x-flv",
"video/x-m4v",
];

View File

@@ -1,19 +0,0 @@
import type { Folder, GrpcRequest, HttpRequest, WebsocketRequest } from "@yaakapp-internal/models";
import { duplicateModel } from "@yaakapp-internal/models";
import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { jotaiStore } from "./jotai";
import { navigateToRequestOrFolderOrWorkspace } from "./setWorkspaceSearchParams";
export async function duplicateRequestOrFolderAndNavigate(
model: Folder | HttpRequest | GrpcRequest | WebsocketRequest | null,
) {
if (model == null) {
throw new Error("Cannot duplicate null item");
}
const newId = await duplicateModel(model);
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
if (workspaceId == null || model.model === "folder") return;
navigateToRequestOrFolderOrWorkspace(newId, model.model);
}

View File

@@ -1,57 +0,0 @@
import { parseTemplate } from "@yaakapp-internal/templates";
import { activeEnvironmentIdAtom } from "../hooks/useActiveEnvironment";
import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { jotaiStore } from "./jotai";
import { invokeCmd } from "./tauri";
export function analyzeTemplate(template: string): "global_secured" | "local_secured" | "insecure" {
let secureTags = 0;
let insecureTags = 0;
let totalTags = 0;
for (const t of parseTemplate(template).tokens) {
if (t.type === "eof") continue;
totalTags++;
if (t.type === "tag" && t.val.type === "fn" && t.val.name === "secure") {
secureTags++;
} else if (t.type === "tag" && t.val.type === "var") {
// Variables are secure
} else if (t.type === "tag" && t.val.type === "bool") {
// Booleans are secure
} else {
insecureTags++;
}
}
if (secureTags === 1 && totalTags === 1) {
return "global_secured";
}
if (insecureTags === 0) {
return "local_secured";
}
return "insecure";
}
export async function convertTemplateToInsecure(template: string) {
if (template === "") {
return "";
}
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom) ?? "n/a";
const environmentId = jotaiStore.get(activeEnvironmentIdAtom) ?? null;
return invokeCmd<string>("cmd_decrypt_template", { template, workspaceId, environmentId });
}
export async function convertTemplateToSecure(template: string): Promise<string> {
if (template === "") {
return "";
}
if (analyzeTemplate(template) === "global_secured") {
return template;
}
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom) ?? "n/a";
const environmentId = jotaiStore.get(activeEnvironmentIdAtom) ?? null;
return invokeCmd<string>("cmd_secure_template", { template, workspaceId, environmentId });
}

View File

@@ -1,16 +0,0 @@
import { showErrorToast } from "./toast";
/**
* Handles a fire-and-forget promise by catching and reporting errors
* via console.error and a toast notification.
*/
export function fireAndForget(promise: Promise<unknown>) {
promise.catch((err: unknown) => {
console.error("Unhandled async error:", err);
showErrorToast({
id: "async-error",
title: "Unexpected Error",
message: err instanceof Error ? err.message : String(err),
});
});
}

View File

@@ -1,45 +0,0 @@
import vkBeautify from "vkbeautify";
import { invokeCmd } from "./tauri";
export async function tryFormatJson(text: string): Promise<string> {
if (text === "") return text;
try {
const result = await invokeCmd<string>("cmd_format_json", { text });
return result;
} catch (err) {
console.warn("Failed to format JSON", err);
}
try {
return JSON.stringify(JSON.parse(text), null, 2);
} catch (err) {
console.log("JSON beautify failed", err);
}
return text;
}
export async function tryFormatGraphql(text: string): Promise<string> {
if (text === "") return text;
try {
return await invokeCmd<string>("cmd_format_graphql", { text });
} catch (err) {
console.warn("Failed to format GraphQL", err);
}
return text;
}
export async function tryFormatXml(text: string): Promise<string> {
if (text === "") return text;
try {
return vkBeautify.xml(text, " ");
} catch (err) {
console.warn("Failed to format XML", err);
}
return text;
}

Some files were not shown because too many files have changed in this diff Show More