Compare commits

..

53 Commits

Author SHA1 Message Date
Gregory Schier
d2000c86d8 Ignore routes file from fmt 2026-03-13 13:25:38 -07:00
Gregory Schier
7577846369 Fix lint warnings: redundant type and floating promises
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:23:08 -07:00
Gregory Schier
903f57a415 Align branch with main: switch to vite-plus and reformat
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:20:06 -07:00
Gregory Schier
d0f1708017 Replace void with fireAndForget in proxy main.tsx
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:04:09 -07:00
Gregory Schier
ee69db0f12 Align lint fixes with main and resolve merge conflicts
- Convert biome-ignore to oxlint-disable-next-line across client app
- Fix no-base-to-string with type narrowing instead of suppressions
- Fix no-floating-promises with fireAndForget() in proxy app
- Fix restrict-template-expressions with String() wrapping
- Resolve leftover merge conflict markers in manager.rs
- Remove duplicate cmd_plugin_init_errors from lib.rs
- Add graphql as explicit dependency in yaak-client

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:02:29 -07:00
Gregory Schier
7314aedc71 Merge main into proxy branch (formatting and docs)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:09:59 -07:00
Gregory Schier
3c4035097a FormattedError 2026-03-12 19:48:43 -07:00
Gregory Schier
77ec87ea17 Stacks and InlineCode 2026-03-12 19:45:19 -07:00
Gregory Schier
4d792c7f9f Move inlinecode to shared ui package 2026-03-12 19:32:04 -07:00
Gregory Schier
d253d1605c Move banner to ui package 2026-03-12 19:18:13 -07:00
Gregory Schier
bc8a449b8a Get pane ready 2026-03-12 18:56:20 -07:00
Gregory Schier
7fbce4e808 Counts to the right 2026-03-12 18:55:03 -07:00
Gregory Schier
f91f40e3a1 Update Sidebar.tsx 2026-03-12 15:43:52 -07:00
Gregory Schier
024b0a3cd3 Fix sidebar counts 2026-03-12 15:40:45 -07:00
Gregory Schier
9e0a708011 Font size and other fixes 2026-03-12 15:35:55 -07:00
Gregory Schier
d8ce5c9d1a Fix styles 2026-03-12 15:19:02 -07:00
Gregory Schier
f7ff964fe5 Floating sidebar refactor 2026-03-12 15:12:49 -07:00
Gregory Schier
cc504e0a1c Move portal to shared ui lib 2026-03-12 14:28:15 -07:00
Gregory Schier
47f0daabff Shared sidebar layout 2026-03-12 14:19:29 -07:00
Gregory Schier
87e60372fe Fix sidebar width 2026-03-12 14:02:32 -07:00
Gregory Schier
7e7faa69df Move split layout 2026-03-12 14:00:29 -07:00
Gregory Schier
0b7705d915 More tweaking 2026-03-12 08:59:02 -07:00
Gregory Schier
5e3ef70d93 Refactor proxy codebase 2026-03-12 08:31:05 -07:00
Gregory Schier
4968237ece Use native TLS when certificate validation is disabled for legacy server compatibility
When "Validate TLS certificates" is disabled, use the OS native TLS stack
(Secure Transport/SChannel/OpenSSL) instead of rustls. This adds support for
TLS 1.0+ connections to legacy servers like IBM WebSphere, which rustls cannot
handle since it only implements TLS 1.2+.

Ref: https://yaak.app/feedback/posts/tls-handshake-eof-when-connecting-to-private-ibm-websphere-endpoint-works-when-s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:32:45 -07:00
Gregory Schier
568a1b80ed Some fixes 2026-03-11 16:00:24 -07:00
Gregory Schier
d4a6735881 Sidebar filtering 2026-03-11 15:55:25 -07:00
Gregory Schier
0c52fd03e2 Shared Table component 2026-03-11 15:51:57 -07:00
Gregory Schier
3e7d04b2f3 Fix theme 2026-03-11 15:44:32 -07:00
Gregory Schier
6600116b1a Merge branch 'main' into wip/yaak-proxy-foundation
# Conflicts:
#	apps/yaak-client/components/JsonBodyEditor.tsx
#	apps/yaak-client/lib/jsonComments.ts
#	package-lock.json
#	packages/theme/src/window.ts
#	packages/ui/src/components/HeaderSize.tsx
2026-03-11 15:36:57 -07:00
Gregory Schier
7be53ca330 WIP: Add yaak-mac-window to proxy app
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:35:54 -07:00
Gregory Schier
f51f72a332 Show proxy status in UI 2026-03-11 15:09:21 -07:00
Gregory Schier
90365f0723 Create Sidebar.tsx 2026-03-09 09:51:37 -07:00
Gregory Schier
e87c3291e7 Add sidebar to proxy app 2026-03-09 09:33:35 -07:00
Gregory Schier
a0442fb42b lint 2026-03-08 22:33:47 -07:00
Gregory Schier
12ece44197 Move Tree component to @yaakapp-internal/ui package
Decouple Tree from client app's hotkey system by adding
getSelectedItems() to TreeHandle and having callers register
hotkeys externally. Extract shared action callbacks to eliminate
duplication between hotkey and context menu handlers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:32:49 -07:00
Gregory Schier
4c041e68a9 Consolidate RPC commands into unified execute_action dispatcher
Replace individual RPC commands (proxy_start, proxy_stop) with a single
execute_action(ActionInvocation) handler. This simplifies the RPC interface
and enables action chaining through events for workflows like duplicate-then-navigate.
2026-03-08 19:04:31 -07:00
Gregory Schier
6534421733 Start extracting Tree component 2026-03-08 16:37:25 -07:00
Gregory Schier
6e11894f79 Lint stuff 2026-03-08 15:50:13 -07:00
Gregory Schier
96a22c68f2 Model store hooked up 2026-03-08 15:42:18 -07:00
Gregory Schier
0a616eb5e2 Got models and event system working 2026-03-08 15:18:31 -07:00
Gregory Schier
7382287bef Initial DB implementation 2026-03-08 14:39:00 -07:00
Gregory Schier
a5433fbc74 Remove unused yaak-proxy-models crate
Replaced by a purpose-built ProxyEntry model to be added to
yaak-proxy-lib, which better handles multi-protocol capture
(HTTP, gRPC, GraphQL, WebSocket) without REST-specific assumptions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 11:38:16 -07:00
Gregory Schier
6f8c4c06bb Add transport-agnostic RPC layer for proxy app
Introduces yaak-rpc (shared RPC infrastructure) and yaak-proxy-lib
(proxy app logic decoupled from any frontend). A single Tauri command
dispatches all RPC calls through a typed router, with TypeScript types
auto-generated via ts-rs and a generic rpc() function for type-safe
calls from the frontend.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 11:27:51 -07:00
Gregory Schier
4c37e62146 Start extracting DBContext 2026-03-08 08:56:08 -07:00
Gregory Schier
cf28229f5f New yaak-databases crate for shared core logic 2026-03-08 08:10:37 -07:00
Gregory Schier
3586c8fe24 Move Icon and LoadingIcon to shared package 2026-03-07 08:00:14 -08:00
Gregory Schier
d99898f39b Move some more stuff over 2026-03-07 07:44:50 -08:00
Gregory Schier
ff6686f982 HeaderSize as shared component 2026-03-07 07:32:58 -08:00
Gregory Schier
6f9e4ada15 Shared window crate 2026-03-07 06:50:11 -08:00
Gregory Schier
fd100330a6 Extract shared UI and theme packages 2026-03-06 10:30:31 -08:00
Gregory Schier
6915778c06 Refactor desktop app into separate client and proxy apps 2026-03-06 09:23:19 -08:00
Gregory Schier
e26705f016 Use separated client/proxy dev ports across worktrees 2026-03-06 09:20:49 -08:00
Gregory Schier
32f22aad67 Add initial yaak-proxy crate 2026-03-06 06:58:45 -08:00
735 changed files with 7588 additions and 2830 deletions

View File

@@ -8,7 +8,7 @@ Make Yaak runnable as a standalone CLI without Tauri as a dependency. The core R
```
crates/ # Core crates - should NOT depend on Tauri
crates-tauri/ # Tauri-specific crates (yaak-app, yaak-tauri-utils, etc.)
crates-tauri/ # Tauri-specific crates (yaak-app-client, yaak-tauri-utils, etc.)
crates-cli/ # CLI crate (yaak-cli)
```
@@ -16,7 +16,7 @@ crates-cli/ # CLI crate (yaak-cli)
### 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-cli/yaak-cli/` for the standalone CLI
@@ -50,14 +50,14 @@ crates-cli/ # CLI crate (yaak-cli)
3. Move extension traits (e.g., `SomethingManagerExt`) to yaak-app or yaak-tauri-utils
4. Initialize managers in yaak-app's `.setup()` block
5. Remove `tauri` from Cargo.toml dependencies
6. Update `crates-tauri/yaak-app/capabilities/default.json` to remove the plugin permission
6. Update `crates-tauri/yaak-app-client/capabilities/default.json` to remove the plugin permission
7. Replace `tauri::async_runtime::block_on` with `tokio::runtime::Handle::current().block_on()`
## Key Files
- `crates-tauri/yaak-app/src/lib.rs` - Main Tauri app, setup block initializes managers
- `crates-tauri/yaak-app/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/lib.rs` - Main Tauri app, setup block initializes managers
- `crates-tauri/yaak-app-client/src/commands.rs` - Migrated Tauri commands
- `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/yaak-models/src/lib.rs` - Contains `init_standalone()` for CLI usage
@@ -79,5 +79,5 @@ e718a5f1 Refactor models_ext to use init_standalone from yaak-models
## Testing
- Run `cargo check -p <crate>` to verify a crate builds without Tauri
- Run `npm run app-dev` to test the Tauri app still works
- Run `npm run client:dev` to test the Tauri app still works
- Run `cargo run -p yaak-cli -- --help` to test the CLI

4
.gitattributes vendored
View File

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

View File

@@ -125,8 +125,8 @@ jobs:
security list-keychain -d user -s $KEYCHAIN_PATH
# Sign vendored binaries with hardened runtime and their specific entitlements
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/macos/entitlements.yaaknode.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app/vendored/node/yaaknode || true
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-client/macos/entitlements.yaaknode.plist --sign "$APPLE_SIGNING_IDENTITY" crates-tauri/yaak-app-client/vendored/node/yaaknode || true
- uses: tauri-apps/tauri-action@v0
env:
@@ -155,7 +155,7 @@ jobs:
releaseBody: "[Changelog __VERSION__](https://yaak.app/blog/__VERSION__)"
releaseDraft: true
prerelease: true
args: "${{ matrix.args }} --config ./crates-tauri/yaak-app/tauri.release.conf.json"
args: "${{ matrix.args }} --config ./crates-tauri/yaak-app-client/tauri.release.conf.json"
# Build a per-machine NSIS installer for enterprise deployment (PDQ, SCCM, Intune)
- name: Build and upload machine-wide installer (Windows only)
@@ -171,7 +171,7 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
run: |
Get-ChildItem -Recurse -Path target -File -Filter "*.exe.sig" | Remove-Item -Force
npx tauri bundle ${{ matrix.args }} --bundles nsis --config ./crates-tauri/yaak-app/tauri.release.conf.json --config '{"bundle":{"createUpdaterArtifacts":true,"windows":{"nsis":{"installMode":"perMachine"}}}}'
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"}}}}'
$setup = Get-ChildItem -Recurse -Path target -Filter "*setup*.exe" | Select-Object -First 1
$setupSig = "$($setup.FullName).sig"
$dest = $setup.FullName -replace '-setup\.exe$', '-setup-machine.exe'

View File

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

3
.gitignore vendored
View File

@@ -39,7 +39,8 @@ codebook.toml
target
# Per-worktree Tauri config (generated by post-checkout hook)
crates-tauri/yaak-app/tauri.worktree.conf.json
crates-tauri/yaak-app-client/tauri.worktree.conf.json
crates-tauri/yaak-app-proxy/tauri.worktree.conf.json
# Tauri auto-generated permission files
**/permissions/autogenerated

View File

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

157
Cargo.lock generated
View File

@@ -488,6 +488,28 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "axum"
version = "0.7.9"
@@ -2209,6 +2231,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "fsevent-sys"
version = "4.1.0"
@@ -5132,6 +5160,16 @@ dependencies = [
"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]]
name = "percent-encoding"
version = "2.3.2"
@@ -5984,6 +6022,19 @@ dependencies = [
"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]]
name = "redox_syscall"
version = "0.5.12"
@@ -6735,6 +6786,8 @@ version = "0.23.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7"
dependencies = [
"aws-lc-rs",
"log 0.4.29",
"once_cell",
"ring",
"rustls-pki-types",
@@ -6803,10 +6856,11 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.10"
version = "0.103.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -10245,7 +10299,7 @@ dependencies = [
]
[[package]]
name = "yaak-app"
name = "yaak-app-client"
version = "0.0.0"
dependencies = [
"charset",
@@ -10303,9 +10357,25 @@ dependencies = [
"yaak-tauri-utils",
"yaak-templates",
"yaak-tls",
"yaak-window",
"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]]
name = "yaak-cli"
version = "0.1.0"
@@ -10375,6 +10445,25 @@ dependencies = [
"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]]
name = "yaak-fonts"
version = "0.1.0"
@@ -10447,6 +10536,7 @@ dependencies = [
"hyper-util",
"log 0.4.29",
"mime_guess",
"native-tls",
"regex 1.11.1",
"reqwest",
"serde",
@@ -10516,6 +10606,7 @@ dependencies = [
"thiserror 2.0.17",
"ts-rs",
"yaak-core",
"yaak-database",
]
[[package]]
@@ -10547,6 +10638,52 @@ dependencies = [
"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]]
name = "yaak-sse"
version = "0.1.0"
@@ -10612,6 +10749,17 @@ dependencies = [
"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]]
name = "yaak-ws"
version = "0.1.0"
@@ -10643,6 +10791,9 @@ name = "yasna"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
dependencies = [
"time",
]
[[package]]
name = "yoke"

View File

@@ -2,6 +2,9 @@
resolver = "2"
members = [
"crates/yaak",
# Common/foundation crates
"crates/common/yaak-database",
"crates/common/yaak-rpc",
# Shared crates (no Tauri dependency)
"crates/yaak-core",
"crates/yaak-common",
@@ -17,14 +20,19 @@ members = [
"crates/yaak-tls",
"crates/yaak-ws",
"crates/yaak-api",
"crates/yaak-proxy",
# Proxy-specific crates
"crates-proxy/yaak-proxy-lib",
# CLI crates
"crates-cli/yaak-cli",
# Tauri-specific crates
"crates-tauri/yaak-app",
"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]
@@ -47,6 +55,10 @@ thiserror = "2.0.17"
tokio = "1.48.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
yaak-core = { path = "crates/yaak-core" }
yaak = { path = "crates/yaak" }
@@ -63,12 +75,17 @@ yaak-templates = { path = "crates/yaak-templates" }
yaak-tls = { path = "crates/yaak-tls" }
yaak-ws = { path = "crates/yaak-ws" }
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
yaak-fonts = { path = "crates-tauri/yaak-fonts" }
yaak-license = { path = "crates-tauri/yaak-license" }
yaak-mac-window = { path = "crates-tauri/yaak-mac-window" }
yaak-tauri-utils = { path = "crates-tauri/yaak-tauri-utils" }
yaak-window = { path = "crates-tauri/yaak-window" }
[profile.release]
strip = false

View File

@@ -1,6 +1,6 @@
<p align="center">
<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/icons/icon.png">
<img width="200px" src="https://github.com/mountain-loop/yaak/raw/main/crates-tauri/yaak-app-client/icons/icon.png">
</a>
</p>

View File

@@ -1,9 +1,9 @@
import { createWorkspaceModel, type Folder, modelTypeLabel } from "@yaakapp-internal/models";
import { applySync, calculateSync } from "@yaakapp-internal/sync";
import { Banner } from "../components/core/Banner";
import { Button } from "../components/core/Button";
import { InlineCode } from "../components/core/InlineCode";
import {
Banner,
InlineCode,
Table,
TableBody,
TableCell,
@@ -11,7 +11,7 @@ import {
TableHeaderCell,
TableRow,
TruncatedWideTableCell,
} from "../components/core/Table";
} from "@yaakapp-internal/ui";
import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { createFastMutation } from "../hooks/useFastMutation";
import { showDialog } from "../lib/dialog";

View File

@@ -1,10 +1,8 @@
import type { HttpRequest } from "@yaakapp-internal/models";
import { Banner, HStack, InlineCode, VStack } from "@yaakapp-internal/ui";
import mime from "mime";
import { useKeyValue } from "../hooks/useKeyValue";
import { Banner } from "./core/Banner";
import { Button } from "./core/Button";
import { InlineCode } from "./core/InlineCode";
import { HStack, VStack } from "./core/Stacks";
import { SelectFile } from "./SelectFile";
type Props = {

View File

@@ -1,15 +1,14 @@
import { open } from "@tauri-apps/plugin-dialog";
import { gitClone } from "@yaakapp-internal/git";
import { Banner, VStack } from "@yaakapp-internal/ui";
import { useState } from "react";
import { openWorkspaceFromSyncDir } from "../commands/openWorkspaceFromSyncDir";
import { appInfo } from "../lib/appInfo";
import { showErrorToast } from "../lib/toast";
import { Banner } from "./core/Banner";
import { Button } from "./core/Button";
import { Checkbox } from "./core/Checkbox";
import { IconButton } from "./core/IconButton";
import { PlainInput } from "./core/PlainInput";
import { VStack } from "./core/Stacks";
import { promptCredentials } from "./git/credentials";
interface Props {

View File

@@ -1,4 +1,5 @@
import { workspacesAtom } from "@yaakapp-internal/models";
import { Heading, Icon, useDebouncedState } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { fuzzyFilter } from "fuzzbunny";
import { useAtomValue } from "jotai";
@@ -21,7 +22,6 @@ import { useActiveRequest } from "../hooks/useActiveRequest";
import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { useAllRequests } from "../hooks/useAllRequests";
import { useCreateWorkspace } from "../hooks/useCreateWorkspace";
import { useDebouncedState } from "../hooks/useDebouncedState";
import { useEnvironmentsBreakdown } from "../hooks/useEnvironmentsBreakdown";
import { useGrpcRequestActions } from "../hooks/useGrpcRequestActions";
import type { HotkeyAction } from "../hooks/useHotKey";
@@ -47,10 +47,8 @@ import { router } from "../lib/router";
import { setWorkspaceSearchParams } from "../lib/setWorkspaceSearchParams";
import { CookieDialog } from "./CookieDialog";
import { Button } from "./core/Button";
import { Heading } from "./core/Heading";
import { Hotkey } from "./core/Hotkey";
import { HttpMethodTag } from "./core/HttpMethodTag";
import { Icon } from "./core/Icon";
import { PlainInput } from "./core/PlainInput";
interface CommandPaletteGroup {

View File

@@ -1,14 +1,12 @@
import type { HttpRequest } from "@yaakapp-internal/models";
import { patchModel } from "@yaakapp-internal/models";
import { Banner, HStack, InlineCode } from "@yaakapp-internal/ui";
import type { ReactNode } from "react";
import { useToggle } from "../hooks/useToggle";
import { showConfirm } from "../lib/confirm";
import { Banner } from "./core/Banner";
import { Button } from "./core/Button";
import { InlineCode } from "./core/InlineCode";
import { Link } from "./core/Link";
import { SizeTag } from "./core/SizeTag";
import { HStack } from "./core/Stacks";
interface Props {
children: ReactNode;

View File

@@ -1,4 +1,5 @@
import type { HttpResponse } from "@yaakapp-internal/models";
import { Banner, HStack, InlineCode } from "@yaakapp-internal/ui";
import { type ReactNode, useMemo } from "react";
import { useSaveResponse } from "../hooks/useSaveResponse";
import { useToggle } from "../hooks/useToggle";
@@ -6,11 +7,8 @@ import { isProbablyTextContentType } from "../lib/contentType";
import { getContentTypeFromHeaders } from "../lib/model_util";
import { getResponseBodyText } from "../lib/responseBody";
import { CopyButton } from "./CopyButton";
import { Banner } from "./core/Banner";
import { Button } from "./core/Button";
import { InlineCode } from "./core/InlineCode";
import { SizeTag } from "./core/SizeTag";
import { HStack } from "./core/Stacks";
interface Props {
children: ReactNode;

View File

@@ -1,15 +1,13 @@
import type { HttpResponse } from "@yaakapp-internal/models";
import { Banner, HStack, InlineCode } from "@yaakapp-internal/ui";
import { type ReactNode, useMemo } from "react";
import { getRequestBodyText as getHttpResponseRequestBodyText } from "../hooks/useHttpRequestBody";
import { useToggle } from "../hooks/useToggle";
import { isProbablyTextContentType } from "../lib/contentType";
import { getContentTypeFromHeaders } from "../lib/model_util";
import { CopyButton } from "./CopyButton";
import { Banner } from "./core/Banner";
import { Button } from "./core/Button";
import { InlineCode } from "./core/InlineCode";
import { SizeTag } from "./core/SizeTag";
import { HStack } from "./core/Stacks";
interface Props {
children: ReactNode;

View File

@@ -2,9 +2,8 @@ import type { Cookie } from "@yaakapp-internal/models";
import { cookieJarsAtom, patchModel } from "@yaakapp-internal/models";
import { useAtomValue } from "jotai";
import { cookieDomain } from "../lib/model_util";
import { Banner } from "./core/Banner";
import { Banner, InlineCode } from "@yaakapp-internal/ui";
import { IconButton } from "./core/IconButton";
import { InlineCode } from "./core/InlineCode";
interface Props {
cookieJarId: string | null;

View File

@@ -9,9 +9,8 @@ import { showPrompt } from "../lib/prompt";
import { setWorkspaceSearchParams } from "../lib/setWorkspaceSearchParams";
import { CookieDialog } from "./CookieDialog";
import { Dropdown, type DropdownItem } from "./core/Dropdown";
import { Icon } from "./core/Icon";
import { Icon, InlineCode } from "@yaakapp-internal/ui";
import { IconButton } from "./core/IconButton";
import { InlineCode } from "./core/InlineCode";
export const CookieDropdown = memo(function CookieDropdown() {
const activeCookieJar = useActiveCookieJar();

View File

@@ -1,4 +1,4 @@
import { useTimedBoolean } from "../hooks/useTimedBoolean";
import { useTimedBoolean } from "@yaakapp-internal/ui";
import { copyToClipboard } from "../lib/copy";
import { showToast } from "../lib/toast";
import type { ButtonProps } from "./core/Button";

View File

@@ -1,8 +1,6 @@
import { useTimedBoolean } from "../hooks/useTimedBoolean";
import { IconButton, type IconButtonProps, useTimedBoolean } from "@yaakapp-internal/ui";
import { copyToClipboard } from "../lib/copy";
import { showToast } from "../lib/toast";
import type { IconButtonProps } from "./core/IconButton";
import { IconButton } from "./core/IconButton";
interface Props extends Omit<IconButtonProps, "onClick" | "icon"> {
text: string | (() => Promise<string | null>);

View File

@@ -1,6 +1,7 @@
import { gitMutations } from "@yaakapp-internal/git";
import type { WorkspaceMeta } from "@yaakapp-internal/models";
import { createGlobalModel, updateModel } from "@yaakapp-internal/models";
import { VStack } from "@yaakapp-internal/ui";
import { useState } from "react";
import { router } from "../lib/router";
import { setupOrConfigureEncryption } from "../lib/setupOrConfigureEncryption";
@@ -10,7 +11,6 @@ import { Button } from "./core/Button";
import { Checkbox } from "./core/Checkbox";
import { Label } from "./core/Label";
import { PlainInput } from "./core/PlainInput";
import { VStack } from "./core/Stacks";
import { EncryptionHelp } from "./EncryptionHelp";
import { gitCallbacks } from "./git/callbacks";
import { SyncToFilesystemSetting } from "./SyncToFilesystemSetting";

View File

@@ -1,13 +1,21 @@
import type { DnsOverride, Workspace } from "@yaakapp-internal/models";
import { patchModel } from "@yaakapp-internal/models";
import { useCallback, useId, useMemo } from "react";
import { fireAndForget } from "../lib/fireAndForget";
import {
HStack,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
VStack,
} from "@yaakapp-internal/ui";
import { useCallback, useId, useMemo } from "react";
import { Button } from "./core/Button";
import { Checkbox } from "./core/Checkbox";
import { IconButton } from "./core/IconButton";
import { PlainInput } from "./core/PlainInput";
import { HStack, VStack } from "./core/Stacks";
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "./core/Table";
interface Props {
workspace: Workspace;

View File

@@ -11,6 +11,7 @@ import type {
FormInputText,
JsonPrimitive,
} from "@yaakapp-internal/plugins";
import { Banner, VStack } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import { useCallback, useEffect, useMemo } from "react";
@@ -19,7 +20,6 @@ import { useRandomKey } from "../hooks/useRandomKey";
import { capitalize } from "../lib/capitalize";
import { showDialog } from "../lib/dialog";
import { resolvedModelName } from "../lib/resolvedModelName";
import { Banner } from "./core/Banner";
import { Checkbox } from "./core/Checkbox";
import { DetailsBanner } from "./core/DetailsBanner";
import { Editor } from "./core/Editor/LazyEditor";
@@ -31,7 +31,6 @@ import type { Pair } from "./core/PairEditor";
import { PairEditor } from "./core/PairEditor";
import { PlainInput } from "./core/PlainInput";
import { Select } from "./core/Select";
import { VStack } from "./core/Stacks";
import { Markdown } from "./Markdown";
import { SelectFile } from "./SelectFile";

View File

@@ -1,4 +1,4 @@
import { VStack } from "./core/Stacks";
import { VStack } from "@yaakapp-internal/ui";
export function EncryptionHelp() {
return (

View File

@@ -8,7 +8,7 @@ import type { ButtonProps } from "./core/Button";
import { Button } from "./core/Button";
import type { DropdownItem } from "./core/Dropdown";
import { Dropdown } from "./core/Dropdown";
import { Icon } from "./core/Icon";
import { Icon } from "@yaakapp-internal/ui";
import { EnvironmentColorIndicator } from "./EnvironmentColorIndicator";
type Props = {

View File

@@ -1,6 +1,6 @@
import { useState } from "react";
import { ColorIndicator } from "./ColorIndicator";
import { Banner } from "./core/Banner";
import { Banner } from "@yaakapp-internal/ui";
import { Button } from "./core/Button";
import { ColorPickerWithThemeColors } from "./core/ColorPicker";

View File

@@ -1,34 +1,38 @@
import type { Environment, Workspace } from "@yaakapp-internal/models";
import { duplicateModel, patchModel } from "@yaakapp-internal/models";
import type { TreeHandle, TreeNode, TreeProps } from "@yaakapp-internal/ui";
import { Banner, Icon, InlineCode, SplitLayout, Tree } from "@yaakapp-internal/ui";
import { atom, useAtomValue } from "jotai";
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
import { atomFamily } from "jotai/utils";
import { useCallback, useLayoutEffect, useRef, useState } from "react";
import { createSubEnvironmentAndActivate } from "../commands/createEnvironment";
import { activeWorkspaceAtom, activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import {
environmentsBreakdownAtom,
useEnvironmentsBreakdown,
} from "../hooks/useEnvironmentsBreakdown";
import { useHotKey } from "../hooks/useHotKey";
import { atomWithKVStorage } from "../lib/atoms/atomWithKVStorage";
import { deleteModelWithConfirm } from "../lib/deleteModelWithConfirm";
import { fireAndForget } from "../lib/fireAndForget";
import { jotaiStore } from "../lib/jotai";
import { isBaseEnvironment, isSubEnvironment } from "../lib/model_util";
import { resolvedModelName } from "../lib/resolvedModelName";
import { showColorPicker } from "../lib/showColorPicker";
import { Banner } from "./core/Banner";
import type { ContextMenuProps, DropdownItem } from "./core/Dropdown";
import { Icon } from "./core/Icon";
import { ContextMenu } from "./core/Dropdown";
import { IconButton } from "./core/IconButton";
import { IconTooltip } from "./core/IconTooltip";
import { InlineCode } from "./core/InlineCode";
import type { PairEditorHandle } from "./core/PairEditor";
import { SplitLayout } from "./core/SplitLayout";
import type { TreeNode } from "./core/tree/common";
import type { TreeHandle, TreeProps } from "./core/tree/Tree";
import { Tree } from "./core/tree/Tree";
import { EnvironmentColorIndicator } from "./EnvironmentColorIndicator";
import { EnvironmentEditor } from "./EnvironmentEditor";
import { EnvironmentSharableTooltip } from "./EnvironmentSharableTooltip";
const collapsedFamily = atomFamily((treeId: string) => {
const key = ["env_collapsed", treeId ?? "n/a"];
return atomWithKVStorage<Record<string, boolean>>(key, {});
});
interface Props {
initialEnvironmentId: string | null;
setRef?: (ref: PairEditorHandle | null) => void;
@@ -49,7 +53,7 @@ export function EnvironmentEditDialog({ initialEnvironmentId, setRef }: Props) {
return (
<SplitLayout
name="env_editor"
storageKey="env_editor"
defaultRatio={0.75}
layout="horizontal"
className="gap-0"
@@ -113,7 +117,7 @@ function EnvironmentEditDialogSidebar({
const treeRef = useRef<TreeHandle>(null);
const { baseEnvironment, baseEnvironments } = useEnvironmentsBreakdown();
// oxlint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react-hooks/exhaustive-deps -- none
useLayoutEffect(() => {
if (selectedEnvironmentId == null) return;
treeRef.current?.selectItem(selectedEnvironmentId);
@@ -130,44 +134,60 @@ function EnvironmentEditDialogSidebar({
[baseEnvironment?.id, selectedEnvironmentId, setSelectedEnvironmentId],
);
const actions = useMemo(() => {
const enable = () => treeRef.current?.hasFocus() ?? false;
const treeHasFocus = useCallback(() => treeRef.current?.hasFocus() ?? false, []);
const actions = {
"sidebar.selected.rename": {
enable,
allowDefault: true,
priority: 100,
cb: async (items: TreeModel[]) => {
const item = items[0];
if (items.length === 1 && item != null) {
treeRef.current?.renameItem(item.id);
}
},
},
"sidebar.selected.delete": {
priority: 100,
enable,
cb: (items: TreeModel[]) => deleteModelWithConfirm(items),
},
"sidebar.selected.duplicate": {
priority: 100,
enable,
cb: async (items: TreeModel[]) => {
if (items.length === 1 && items[0]) {
const item = items[0];
const newId = await duplicateModel(item);
setSelectedEnvironmentId(newId);
} else {
await Promise.all(items.map(duplicateModel));
}
},
},
} as const;
return actions;
}, [setSelectedEnvironmentId]);
const getSelectedTreeModels = useCallback(
() => treeRef.current?.getSelectedItems() as TreeModel[] | undefined,
[],
);
const hotkeys = useMemo<TreeProps<TreeModel>["hotkeys"]>(() => ({ actions }), [actions]);
const handleRenameSelected = useCallback(() => {
const items = getSelectedTreeModels();
if (items?.length === 1 && items[0] != null) {
treeRef.current?.renameItem(items[0].id);
}
}, [getSelectedTreeModels]);
const handleDeleteSelected = useCallback(
(items: TreeModel[]) => deleteModelWithConfirm(items),
[],
);
const handleDuplicateSelected = useCallback(
async (items: TreeModel[]) => {
if (items.length === 1 && items[0]) {
const newId = await duplicateModel(items[0]);
setSelectedEnvironmentId(newId);
} else {
await Promise.all(items.map(duplicateModel));
}
},
[setSelectedEnvironmentId],
);
useHotKey("sidebar.selected.rename", handleRenameSelected, {
enable: treeHasFocus,
allowDefault: true,
priority: 100,
});
useHotKey(
"sidebar.selected.delete",
useCallback(() => {
const items = getSelectedTreeModels();
if (items) {
fireAndForget(handleDeleteSelected(items));
}
}, [getSelectedTreeModels, handleDeleteSelected]),
{ enable: treeHasFocus, priority: 100 },
);
useHotKey(
"sidebar.selected.duplicate",
useCallback(async () => {
const items = getSelectedTreeModels();
if (items) await handleDuplicateSelected(items);
}, [getSelectedTreeModels, handleDuplicateSelected]),
{ enable: treeHasFocus, priority: 100 },
);
const getContextMenu = useCallback(
(items: TreeModel[]): ContextMenuProps["items"] => {
@@ -196,12 +216,10 @@ function EnvironmentEditDialogSidebar({
hidden: isBaseEnvironment(environment) || !singleEnvironment,
hotKeyAction: "sidebar.selected.rename",
hotKeyLabelOnly: true,
onSelect: async () => {
onSelect: () => {
// Not sure why this is needed, but without it the
// edit input blurs immediately after opening.
requestAnimationFrame(() => {
fireAndForget(actions["sidebar.selected.rename"].cb(items));
});
requestAnimationFrame(() => handleRenameSelected());
},
},
{
@@ -210,7 +228,7 @@ function EnvironmentEditDialogSidebar({
hidden: isBaseEnvironment(environment),
hotKeyAction: "sidebar.selected.duplicate",
hotKeyLabelOnly: true,
onSelect: () => actions["sidebar.selected.duplicate"].cb(items),
onSelect: () => handleDuplicateSelected(items),
},
{
label: environment.color ? "Change Color" : "Assign Color",
@@ -246,7 +264,12 @@ function EnvironmentEditDialogSidebar({
return menuItems;
},
[actions, baseEnvironments.length, handleDeleteEnvironment],
[
baseEnvironments.length,
handleDeleteEnvironment,
handleDuplicateSelected,
handleRenameSelected,
],
);
const handleDragEnd = useCallback(async function handleDragEnd({
@@ -293,6 +316,13 @@ function EnvironmentEditDialogSidebar({
[setSelectedEnvironmentId],
);
const renderContextMenuFn = useCallback<NonNullable<TreeProps<TreeModel>["renderContextMenu"]>>(
({ items, position, onClose }) => (
<ContextMenu items={items as DropdownItem[]} triggerPosition={position} onClose={onClose} />
),
[],
);
const tree = useAtomValue(treeAtom);
return (
<aside className="x-theme-sidebar h-full w-full min-w-0 grid overflow-y-auto border-r border-border-subtle ">
@@ -301,10 +331,11 @@ function EnvironmentEditDialogSidebar({
<Tree
ref={treeRef}
treeId={treeId}
collapsedAtom={collapsedFamily(treeId)}
className="px-2 pb-10"
hotkeys={hotkeys}
root={tree}
getContextMenu={getContextMenu}
renderContextMenu={renderContextMenuFn}
onDragEnd={handleDragEnd}
getItemKey={(i) => `${i.id}::${i.name}`}
ItemLeftSlotInner={ItemLeftSlotInner}

View File

@@ -1,6 +1,7 @@
import type { Environment } from "@yaakapp-internal/models";
import { patchModel } from "@yaakapp-internal/models";
import type { GenericCompletionOption } from "@yaakapp-internal/plugins";
import { Heading } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useCallback, useMemo } from "react";
import { useEnvironmentsBreakdown } from "../hooks/useEnvironmentsBreakdown";
@@ -15,7 +16,6 @@ import {
} from "../lib/setupOrConfigureEncryption";
import { DismissibleBanner } from "./core/DismissibleBanner";
import type { GenericCompletionConfig } from "./core/Editor/genericCompletion";
import { Heading } from "./core/Heading";
import type { PairEditorHandle, PairWithId } from "./core/PairEditor";
import { ensurePairId } from "./core/PairEditor.util";
import { PairOrBulkEditor } from "./core/PairOrBulkEditor";

View File

@@ -1,9 +1,7 @@
import { Banner, Button, InlineCode } from "@yaakapp-internal/ui";
import type { ErrorInfo, ReactNode } from "react";
import { Component, useEffect } from "react";
import { showDialog } from "../lib/dialog";
import { Banner } from "./core/Banner";
import { Button } from "./core/Button";
import { InlineCode } from "./core/InlineCode";
import RouteError from "./RouteError";
interface ErrorBoundaryProps {

View File

@@ -1,6 +1,7 @@
import { save } from "@tauri-apps/plugin-dialog";
import type { Workspace } from "@yaakapp-internal/models";
import { workspacesAtom } from "@yaakapp-internal/models";
import { HStack, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai";
import { useCallback, useMemo, useState } from "react";
import slugify from "slugify";
@@ -11,7 +12,6 @@ import { Button } from "./core/Button";
import { Checkbox } from "./core/Checkbox";
import { DetailsBanner } from "./core/DetailsBanner";
import { Link } from "./core/Link";
import { HStack, VStack } from "./core/Stacks";
interface Props {
onHide: () => void;

View File

@@ -1,5 +1,6 @@
import type { Folder, GrpcRequest, HttpRequest, WebsocketRequest } from "@yaakapp-internal/models";
import { foldersAtom } from "@yaakapp-internal/models";
import { Heading, HStack, Icon, LoadingIcon } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import type { CSSProperties, ReactNode } from "react";
@@ -8,20 +9,15 @@ import { allRequestsAtom } from "../hooks/useAllRequests";
import { useFolderActions } from "../hooks/useFolderActions";
import { useLatestHttpResponse } from "../hooks/useLatestHttpResponse";
import { sendAnyHttpRequest } from "../hooks/useSendAnyHttpRequest";
import { fireAndForget } from "../lib/fireAndForget";
import { showDialog } from "../lib/dialog";
import { resolvedModelName } from "../lib/resolvedModelName";
import { router } from "../lib/router";
import { Button } from "./core/Button";
import { Heading } from "./core/Heading";
import { HttpResponseDurationTag } from "./core/HttpResponseDurationTag";
import { HttpStatusTag } from "./core/HttpStatusTag";
import { Icon } from "./core/Icon";
import { IconButton } from "./core/IconButton";
import { LoadingIcon } from "./core/LoadingIcon";
import { Separator } from "./core/Separator";
import { SizeTag } from "./core/SizeTag";
import { HStack } from "./core/Stacks";
import { HttpResponsePane } from "./HttpResponsePane";
interface Props {
@@ -46,7 +42,7 @@ export function FolderLayout({ folder, style }: Props) {
}, [folder.id, folders, requests]);
const handleSendAll = useCallback(() => {
if (sendAllAction) fireAndForget(sendAllAction.call(folder));
void sendAllAction?.call(folder);
}, [sendAllAction, folder]);
return (

View File

@@ -1,4 +1,5 @@
import { createWorkspaceModel, foldersAtom, patchModel } from "@yaakapp-internal/models";
import { HStack, Icon, InlineCode, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai";
import { Fragment, useMemo } from "react";
import { useAuthTab } from "../hooks/useAuthTab";
@@ -11,11 +12,8 @@ import { hideDialog } from "../lib/dialog";
import { CopyIconButton } from "./CopyIconButton";
import { Button } from "./core/Button";
import { CountBadge } from "./core/CountBadge";
import { Icon } from "./core/Icon";
import { InlineCode } from "./core/InlineCode";
import { Input } from "./core/Input";
import { Link } from "./core/Link";
import { HStack, VStack } from "./core/Stacks";
import type { TabItem } from "./core/Tabs/Tabs";
import { TabContent, Tabs } from "./core/Tabs/Tabs";
import { EmptyStateText } from "./EmptyStateText";

View File

@@ -7,10 +7,10 @@ import { useActiveRequest } from "../hooks/useActiveRequest";
import { useGrpc } from "../hooks/useGrpc";
import { useGrpcProtoFiles } from "../hooks/useGrpcProtoFiles";
import { activeGrpcConnectionAtom, useGrpcEvents } from "../hooks/usePinnedGrpcConnection";
import { Banner, SplitLayout } from "@yaakapp-internal/ui";
import { activeWorkspaceAtom } from "../hooks/useActiveWorkspace";
import { workspaceLayoutAtom } from "../lib/atoms";
import { Banner } from "./core/Banner";
import { HotkeyList } from "./core/HotkeyList";
import { SplitLayout } from "./core/SplitLayout";
import { GrpcRequestPane } from "./GrpcRequestPane";
import { GrpcResponsePane } from "./GrpcResponsePane";
@@ -22,6 +22,8 @@ const emptyArray: string[] = [];
export function GrpcConnectionLayout({ style }: Props) {
const workspaceLayout = useAtomValue(workspaceLayoutAtom);
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
const wsId = activeWorkspace?.id ?? "n/a";
const activeRequest = useActiveRequest("grpc_request");
const activeConnection = useAtomValue(activeGrpcConnectionAtom);
const grpcEvents = useGrpcEvents(activeConnection?.id ?? null);
@@ -79,7 +81,7 @@ export function GrpcConnectionLayout({ style }: Props) {
return (
<SplitLayout
name="grpc_layout"
storageKey={`grpc_layout::${wsId}`}
className="p-3 gap-1.5"
style={style}
layout={workspaceLayout}

View File

@@ -1,7 +1,8 @@
import { jsoncLanguage } from "@shopify/lang-jsonc";
import { linter } from "@codemirror/lint";
import type { EditorView } from "@codemirror/view";
import { jsoncLanguage } from "@shopify/lang-jsonc";
import type { GrpcRequest } from "@yaakapp-internal/models";
import { FormattedError, InlineCode, VStack } from "@yaakapp-internal/ui";
import classNames from "classnames";
import {
handleRefresh,
@@ -18,9 +19,6 @@ import { pluralizeCount } from "../lib/pluralize";
import { Button } from "./core/Button";
import type { EditorProps } from "./core/Editor/Editor";
import { Editor } from "./core/Editor/LazyEditor";
import { FormattedError } from "./core/FormattedError";
import { InlineCode } from "./core/InlineCode";
import { VStack } from "./core/Stacks";
import { GrpcProtoSelectionDialog } from "./GrpcProtoSelectionDialog";
type Props = Pick<EditorProps, "heightMode" | "onChange" | "className" | "forceUpdateKey"> & {

View File

@@ -1,16 +1,13 @@
import { open } from "@tauri-apps/plugin-dialog";
import type { GrpcRequest } from "@yaakapp-internal/models";
import { Banner, HStack, Icon, InlineCode, VStack } from "@yaakapp-internal/ui";
import { useActiveRequest } from "../hooks/useActiveRequest";
import { useGrpc } from "../hooks/useGrpc";
import { useGrpcProtoFiles } from "../hooks/useGrpcProtoFiles";
import { pluralizeCount } from "../lib/pluralize";
import { Banner } from "./core/Banner";
import { Button } from "./core/Button";
import { Icon } from "./core/Icon";
import { IconButton } from "./core/IconButton";
import { InlineCode } from "./core/InlineCode";
import { Link } from "./core/Link";
import { HStack, VStack } from "./core/Stacks";
interface Props {
onDone: () => void;
@@ -30,7 +27,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
const services = grpc.reflect.data;
const serverReflection = protoFiles.length === 0 && services != null;
let reflectError = grpc.reflect.error ?? null;
const reflectionUnimplemented = `${reflectError}`.match(/unimplemented/i);
const reflectionUnimplemented = String(reflectError).match(/unimplemented/i);
if (reflectionUnimplemented) {
reflectError = null;
@@ -143,8 +140,8 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
<tbody className="divide-y divide-surface-highlight">
{protoFiles.map((f, i) => {
const parts = f.split("/");
// oxlint-disable-next-line no-array-index-key -- none
return (
// oxlint-disable-next-line react/no-array-index-key
<tr key={f + i} className="group">
<td>
<Icon icon={f.endsWith(".proto") ? "file_code" : "folder_code"} />

View File

@@ -1,9 +1,9 @@
import { type GrpcRequest, type HttpRequestHeader, patchModel } from "@yaakapp-internal/models";
import { HStack, Icon, useContainerSize, VStack } from "@yaakapp-internal/ui";
import classNames from "classnames";
import type { CSSProperties } from "react";
import { useCallback, useMemo, useRef } from "react";
import { useAuthTab } from "../hooks/useAuthTab";
import { useContainerSize } from "../hooks/useContainerQuery";
import type { ReflectResponseService } from "../hooks/useGrpc";
import { useHeadersTab } from "../hooks/useHeadersTab";
import { useInheritedHeaders } from "../hooks/useInheritedHeaders";
@@ -11,11 +11,9 @@ import { useRequestUpdateKey } from "../hooks/useRequestUpdateKey";
import { resolvedModelName } from "../lib/resolvedModelName";
import { Button } from "./core/Button";
import { CountBadge } from "./core/CountBadge";
import { Icon } from "./core/Icon";
import { IconButton } from "./core/IconButton";
import { PlainInput } from "./core/PlainInput";
import { RadioDropdown } from "./core/RadioDropdown";
import { HStack, VStack } from "./core/Stacks";
import type { TabItem } from "./core/Tabs/Tabs";
import { TabContent, Tabs } from "./core/Tabs/Tabs";
import { GrpcEditor } from "./GrpcEditor";

View File

@@ -1,4 +1,5 @@
import type { GrpcEvent, GrpcRequest } from "@yaakapp-internal/models";
import { HStack, Icon, type IconProps, LoadingIcon, VStack } from "@yaakapp-internal/ui";
import { useAtomValue, useSetAtom } from "jotai";
import type { CSSProperties } from "react";
import { useEffect, useMemo, useState } from "react";
@@ -14,10 +15,7 @@ import { Editor } from "./core/Editor/LazyEditor";
import { EventDetailHeader, EventViewer } from "./core/EventViewer";
import { EventViewerRow } from "./core/EventViewerRow";
import { HotkeyList } from "./core/HotkeyList";
import { Icon, type IconProps } from "./core/Icon";
import { KeyValueRow, KeyValueRows } from "./core/KeyValueRow";
import { LoadingIcon } from "./core/LoadingIcon";
import { HStack, VStack } from "./core/Stacks";
import { EmptyStateText } from "./EmptyStateText";
import { ErrorBoundary } from "./ErrorBoundary";
import { RecentGrpcConnectionsDropdown } from "./RecentGrpcConnectionsDropdown";
@@ -93,7 +91,7 @@ export function GrpcResponsePane({ style, methodType, activeRequest }: Props) {
getEventKey={(event) => event.id}
error={activeConnection.error}
header={header}
splitLayoutName="grpc_events"
splitLayoutStorageKey="grpc_events"
defaultRatio={0.4}
renderRow={({ event, isActive, onClick }) => (
<GrpcEventRow event={event} isActive={isActive} onClick={onClick} />

View File

@@ -1,5 +1,6 @@
import type { HttpRequestHeader } from "@yaakapp-internal/models";
import type { GenericCompletionOption } from "@yaakapp-internal/plugins";
import { HStack } from "@yaakapp-internal/ui";
import { charsets } from "../lib/data/charsets";
import { connections } from "../lib/data/connections";
import { encodings } from "../lib/data/encodings";
@@ -13,7 +14,6 @@ import type { Pair, PairEditorProps } from "./core/PairEditor";
import { PairEditorRow } from "./core/PairEditor";
import { ensurePairId } from "./core/PairEditor.util";
import { PairOrBulkEditor } from "./core/PairOrBulkEditor";
import { HStack } from "./core/Stacks";
type Props = {
forceUpdateKey: string;

View File

@@ -6,6 +6,7 @@ import type {
Workspace,
} from "@yaakapp-internal/models";
import { patchModel } from "@yaakapp-internal/models";
import { HStack, Icon, InlineCode } from "@yaakapp-internal/ui";
import { useCallback } from "react";
import { openFolderSettings } from "../commands/openFolderSettings";
import { openWorkspaceSettings } from "../commands/openWorkspaceSettings";
@@ -14,13 +15,10 @@ import { useInheritedAuthentication } from "../hooks/useInheritedAuthentication"
import { useRenderTemplate } from "../hooks/useRenderTemplate";
import { resolvedModelName } from "../lib/resolvedModelName";
import { Dropdown, type DropdownItem } from "./core/Dropdown";
import { Icon } from "./core/Icon";
import { IconButton } from "./core/IconButton";
import { InlineCode } from "./core/InlineCode";
import { Input, type InputProps } from "./core/Input";
import { Link } from "./core/Link";
import { SegmentedControl } from "./core/SegmentedControl";
import { HStack } from "./core/Stacks";
import { DynamicForm } from "./DynamicForm";
import { EmptyStateText } from "./EmptyStateText";

View File

@@ -1,11 +1,12 @@
import type { HttpRequest } from "@yaakapp-internal/models";
import type { SlotProps } from "@yaakapp-internal/ui";
import { SplitLayout } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import type { CSSProperties } from "react";
import { useCurrentGraphQLSchema } from "../hooks/useIntrospectGraphQL";
import { activeWorkspaceAtom } from "../hooks/useActiveWorkspace";
import { workspaceLayoutAtom } from "../lib/atoms";
import type { SlotProps } from "./core/SplitLayout";
import { SplitLayout } from "./core/SplitLayout";
import { GraphQLDocsExplorer } from "./graphql/GraphQLDocsExplorer";
import { showGraphQLDocExplorerAtom } from "./graphql/graphqlAtoms";
import { HttpRequestPane } from "./HttpRequestPane";
@@ -20,10 +21,12 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
const showGraphQLDocExplorer = useAtomValue(showGraphQLDocExplorerAtom);
const graphQLSchema = useCurrentGraphQLSchema(activeRequest);
const workspaceLayout = useAtomValue(workspaceLayoutAtom);
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
const wsId = activeWorkspace?.id ?? "n/a";
const requestResponseSplit = ({ style }: Pick<SlotProps, "style">) => (
<SplitLayout
name="http_layout"
storageKey={`http_layout::${wsId}`}
className="p-3 gap-1.5"
style={style}
layout={workspaceLayout}
@@ -47,7 +50,7 @@ export function HttpRequestLayout({ activeRequest, style }: Props) {
) {
return (
<SplitLayout
name="graphql_layout"
storageKey={`graphql_layout::${wsId}`}
defaultRatio={1 / 3}
firstSlot={requestResponseSplit}
secondSlot={({ style, orientation }) => (

View File

@@ -38,7 +38,7 @@ import { ConfirmLargeRequestBody } from "./ConfirmLargeRequestBody";
import { CountBadge } from "./core/CountBadge";
import type { GenericCompletionConfig } from "./core/Editor/genericCompletion";
import { Editor } from "./core/Editor/LazyEditor";
import { InlineCode } from "./core/InlineCode";
import { InlineCode } from "@yaakapp-internal/ui";
import type { Pair } from "./core/PairEditor";
import { PlainInput } from "./core/PlainInput";
import type { TabItem, TabsRef } from "./core/Tabs/Tabs";

View File

@@ -1,4 +1,5 @@
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";
@@ -12,17 +13,13 @@ import { getMimeTypeFromContentType } from "../lib/contentType";
import { getContentTypeFromHeaders, getCookieCounts } from "../lib/model_util";
import { ConfirmLargeResponse } from "./ConfirmLargeResponse";
import { ConfirmLargeResponseRequest } from "./ConfirmLargeResponseRequest";
import { Banner } from "./core/Banner";
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 { Icon } from "./core/Icon";
import { LoadingIcon } from "./core/LoadingIcon";
import { PillButton } from "./core/PillButton";
import { SizeTag } from "./core/SizeTag";
import { HStack, VStack } from "./core/Stacks";
import type { TabItem } from "./core/Tabs/Tabs";
import { TabContent, Tabs } from "./core/Tabs/Tabs";
import { Tooltip } from "./core/Tooltip";

View File

@@ -9,7 +9,7 @@ import { Editor } from "./core/Editor/LazyEditor";
import { type EventDetailAction, EventDetailHeader, EventViewer } from "./core/EventViewer";
import { EventViewerRow } from "./core/EventViewerRow";
import { HttpStatusTagRaw } from "./core/HttpStatusTag";
import { Icon, type IconProps } from "./core/Icon";
import { Icon, type IconProps } from "@yaakapp-internal/ui";
import { KeyValueRow, KeyValueRows } from "./core/KeyValueRow";
import type { TimelineViewMode } from "./HttpResponsePane";
@@ -55,7 +55,7 @@ function Inner({ response, viewMode }: Props) {
isLoading={isLoading}
loadingMessage="Loading events..."
emptyMessage="No events recorded"
splitLayoutName="http_response_events"
splitLayoutStorageKey="http_response_events"
defaultRatio={0.25}
renderRow={({ event, isActive, onClick }) => {
const display = getEventDisplay(event.event);

View File

@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
import { useImportCurl } from "../hooks/useImportCurl";
import { useWindowFocus } from "../hooks/useWindowFocus";
import { Button } from "./core/Button";
import { Icon } from "./core/Icon";
import { Icon } from "@yaakapp-internal/ui";
export function ImportCurlButton() {
const focused = useWindowFocus();
@@ -13,11 +13,9 @@ export function ImportCurlButton() {
const importCurl = useImportCurl();
const [isLoading, setIsLoading] = useState(false);
// oxlint-disable-next-line react-hooks/exhaustive-deps
// oxlint-disable-next-line react-hooks/exhaustive-deps -- none
useEffect(() => {
readText()
.then(setClipboardText)
.catch(() => {});
void readText().then(setClipboardText);
}, [focused]);
if (!clipboardText?.trim().startsWith("curl ")) {

View File

@@ -1,7 +1,7 @@
import { VStack } from "@yaakapp-internal/ui";
import { useState } from "react";
import { useLocalStorage } from "react-use";
import { Button } from "./core/Button";
import { VStack } from "./core/Stacks";
import { SelectFile } from "./SelectFile";
interface Props {

View File

@@ -1,17 +1,16 @@
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 { fireAndForget } from "../lib/fireAndForget";
import { useKeyValue } from "../hooks/useKeyValue";
import { fireAndForget } from "../lib/fireAndForget";
import { textLikelyContainsJsonComments } from "../lib/jsonComments";
import { Banner } from "./core/Banner";
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 { Icon } from "./core/Icon";
import { IconButton } from "./core/IconButton";
import { IconTooltip } from "./core/IconTooltip";

View File

@@ -12,7 +12,7 @@ import { jotaiStore } from "../lib/jotai";
import { CargoFeature } from "./CargoFeature";
import type { ButtonProps } from "./core/Button";
import { Dropdown, type DropdownItem } from "./core/Dropdown";
import { Icon } from "./core/Icon";
import { Icon } from "@yaakapp-internal/ui";
import { PillButton } from "./core/PillButton";
const dismissedAtom = atomWithKVStorage<string | null>("dismissed_license_expired", null);

View File

@@ -1,5 +1,6 @@
import type { GrpcRequest, HttpRequest, WebsocketRequest } from "@yaakapp-internal/models";
import { patchModel, workspacesAtom } from "@yaakapp-internal/models";
import { InlineCode, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai";
import { useState } from "react";
import { pluralizeCount } from "../lib/pluralize";
@@ -7,9 +8,7 @@ import { resolvedModelName } from "../lib/resolvedModelName";
import { router } from "../lib/router";
import { showToast } from "../lib/toast";
import { Button } from "./core/Button";
import { InlineCode } from "./core/InlineCode";
import { Select } from "./core/Select";
import { VStack } from "./core/Stacks";
interface Props {
activeWorkspaceId: string;

View File

@@ -1,12 +1,11 @@
import type { GrpcConnection } from "@yaakapp-internal/models";
import { deleteModel } from "@yaakapp-internal/models";
import { HStack, Icon } from "@yaakapp-internal/ui";
import { formatDistanceToNowStrict } from "date-fns";
import { useDeleteGrpcConnections } from "../hooks/useDeleteGrpcConnections";
import { pluralizeCount } from "../lib/pluralize";
import { Dropdown } from "./core/Dropdown";
import { Icon } from "./core/Icon";
import { IconButton } from "./core/IconButton";
import { HStack } from "./core/Stacks";
interface Props {
connections: GrpcConnection[];

View File

@@ -1,14 +1,13 @@
import type { HttpResponse } from "@yaakapp-internal/models";
import { deleteModel } from "@yaakapp-internal/models";
import { HStack, Icon } from "@yaakapp-internal/ui";
import { useCopyHttpResponse } from "../hooks/useCopyHttpResponse";
import { useDeleteHttpResponses } from "../hooks/useDeleteHttpResponses";
import { useSaveResponse } from "../hooks/useSaveResponse";
import { pluralize } from "../lib/pluralize";
import { Dropdown } from "./core/Dropdown";
import { HttpStatusTag } from "./core/HttpStatusTag";
import { Icon } from "./core/Icon";
import { IconButton } from "./core/IconButton";
import { HStack } from "./core/Stacks";
interface Props {
responses: HttpResponse[];

View File

@@ -1,12 +1,11 @@
import type { WebsocketConnection } from "@yaakapp-internal/models";
import { deleteModel, getModel } from "@yaakapp-internal/models";
import { HStack, Icon } from "@yaakapp-internal/ui";
import { formatDistanceToNowStrict } from "date-fns";
import { deleteWebsocketConnections } from "../commands/deleteWebsocketConnections";
import { pluralizeCount } from "../lib/pluralize";
import { Dropdown } from "./core/Dropdown";
import { Icon } from "./core/Icon";
import { IconButton } from "./core/IconButton";
import { HStack } from "./core/Stacks";
interface Props {
connections: WebsocketConnection[];

View File

@@ -2,7 +2,7 @@ import type { HttpResponse } from "@yaakapp-internal/models";
import { lazy, Suspense } from "react";
import { useHttpRequestBody } from "../hooks/useHttpRequestBody";
import { getMimeTypeFromContentType, languageFromContentType } from "../lib/contentType";
import { LoadingIcon } from "./core/LoadingIcon";
import { LoadingIcon } from "@yaakapp-internal/ui";
import { EmptyStateText } from "./EmptyStateText";
import { AudioViewer } from "./responseViewers/AudioViewer";
import { CsvViewer } from "./responseViewers/CsvViewer";

View File

@@ -6,7 +6,7 @@ import { showPrompt } from "../lib/prompt";
import { Button } from "./core/Button";
import type { DropdownItem } from "./core/Dropdown";
import { HttpMethodTag, HttpMethodTagRaw } from "./core/HttpMethodTag";
import { Icon } from "./core/Icon";
import { Icon } from "@yaakapp-internal/ui";
import type { RadioDropdownItem } from "./core/RadioDropdown";
import { RadioDropdown } from "./core/RadioDropdown";

View File

@@ -1,13 +1,10 @@
import { Button } from "./core/Button";
import { Button, FormattedError, Heading, VStack } from "@yaakapp-internal/ui";
import { DetailsBanner } from "./core/DetailsBanner";
import { FormattedError } from "./core/FormattedError";
import { Heading } from "./core/Heading";
import { VStack } from "./core/Stacks";
export default function RouteError({ error }: { error: unknown }) {
console.log("Error", error);
const stringified = JSON.stringify(error);
// oxlint-disable-next-line no-explicit-any
// oxlint-disable-next-line no-explicit-any -- none
const message = (error as any).message ?? stringified;
const stack =
typeof error === "object" && error != null && "stack" in error ? String(error.stack) : null;

View File

@@ -1,5 +1,6 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { open } from "@tauri-apps/plugin-dialog";
import { HStack } from "@yaakapp-internal/ui";
import classNames from "classnames";
import mime from "mime";
import type { ReactNode } from "react";
@@ -9,7 +10,6 @@ import { Button } from "./core/Button";
import { IconButton } from "./core/IconButton";
import { IconTooltip } from "./core/IconTooltip";
import { Label } from "./core/Label";
import { HStack } from "./core/Stacks";
type Props = Omit<ButtonProps, "type"> & {
onChange: (value: { filePath: string | null; contentType: string | null }) => void;

View File

@@ -3,16 +3,14 @@ import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { type } from "@tauri-apps/plugin-os";
import { useLicense } from "@yaakapp-internal/license";
import { pluginsAtom, settingsAtom } from "@yaakapp-internal/models";
import { HeaderSize, HStack, Icon } from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import { useKeyPressEvent } from "react-use";
import { appInfo } from "../../lib/appInfo";
import { capitalize } from "../../lib/capitalize";
import { CountBadge } from "../core/CountBadge";
import { Icon } from "../core/Icon";
import { HStack } from "../core/Stacks";
import { TabContent, type TabItem, Tabs } from "../core/Tabs/Tabs";
import { HeaderSize } from "../HeaderSize";
import { SettingsCertificates } from "./SettingsCertificates";
import { SettingsGeneral } from "./SettingsGeneral";
import { SettingsHotkeys } from "./SettingsHotkeys";
@@ -77,6 +75,10 @@ export default function Settings({ hide }: Props) {
onlyXWindowControl
size="md"
className="x-theme-appHeader bg-surface text-text-subtle flex items-center justify-center border-b border-border-subtle text-sm font-semibold"
osType={type()}
hideWindowControls={settings.hideWindowControls}
useNativeTitlebar={settings.useNativeTitlebar}
interfaceScale={settings.interfaceScale}
>
<HStack
space={2}

View File

@@ -1,17 +1,15 @@
import type { ClientCertificate } from "@yaakapp-internal/models";
import { patchModel, settingsAtom } from "@yaakapp-internal/models";
import { Heading, HStack, InlineCode, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai";
import { useRef } from "react";
import { showConfirmDelete } from "../../lib/confirm";
import { Button } from "../core/Button";
import { Checkbox } from "../core/Checkbox";
import { DetailsBanner } from "../core/DetailsBanner";
import { Heading } from "../core/Heading";
import { IconButton } from "../core/IconButton";
import { InlineCode } from "../core/InlineCode";
import { PlainInput } from "../core/PlainInput";
import { Separator } from "../core/Separator";
import { HStack, VStack } from "../core/Stacks";
import { SelectFile } from "../SelectFile";
function createEmptyCertificate(): ClientCertificate {

View File

@@ -1,5 +1,6 @@
import { revealItemInDir } from "@tauri-apps/plugin-opener";
import { patchModel, settingsAtom } from "@yaakapp-internal/models";
import { Heading, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai";
import { activeWorkspaceAtom } from "../../hooks/useActiveWorkspace";
import { useCheckForUpdates } from "../../hooks/useCheckForUpdates";
@@ -7,13 +8,11 @@ import { appInfo } from "../../lib/appInfo";
import { revealInFinderText } from "../../lib/reveal";
import { CargoFeature } from "../CargoFeature";
import { Checkbox } from "../core/Checkbox";
import { Heading } from "../core/Heading";
import { IconButton } from "../core/IconButton";
import { KeyValueRow, KeyValueRows } from "../core/KeyValueRow";
import { PlainInput } from "../core/PlainInput";
import { Select } from "../core/Select";
import { Separator } from "../core/Separator";
import { VStack } from "../core/Stacks";
export function SettingsGeneral() {
const workspace = useAtomValue(activeWorkspaceAtom);

View File

@@ -1,4 +1,16 @@
import { patchModel, settingsAtom } from "@yaakapp-internal/models";
import {
Heading,
HStack,
Icon,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
VStack,
} from "@yaakapp-internal/ui";
import classNames from "classnames";
import { fuzzyMatch } from "fuzzbunny";
import { useAtomValue } from "jotai";
@@ -16,13 +28,9 @@ import { capitalize } from "../../lib/capitalize";
import { showDialog } from "../../lib/dialog";
import { Button } from "../core/Button";
import { Dropdown, type DropdownItem } from "../core/Dropdown";
import { Heading } from "../core/Heading";
import { HotkeyRaw } from "../core/Hotkey";
import { Icon } from "../core/Icon";
import { IconButton } from "../core/IconButton";
import { PlainInput } from "../core/PlainInput";
import { HStack, VStack } from "../core/Stacks";
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "../core/Table";
const HOLD_KEYS = ["Shift", "Control", "Alt", "Meta"];
const LAYOUT_INSENSITIVE_KEYS = [

View File

@@ -3,21 +3,17 @@ import { useFonts } from "@yaakapp-internal/fonts";
import { useLicense } from "@yaakapp-internal/license";
import type { EditorKeymap, Settings } from "@yaakapp-internal/models";
import { patchModel, settingsAtom } from "@yaakapp-internal/models";
import { clamp, Heading, HStack, Icon, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai";
import { useState } from "react";
import { activeWorkspaceAtom } from "../../hooks/useActiveWorkspace";
import { clamp } from "../../lib/clamp";
import { showConfirm } from "../../lib/confirm";
import { invokeCmd } from "../../lib/tauri";
import { CargoFeature } from "../CargoFeature";
import { Button } from "../core/Button";
import { Checkbox } from "../core/Checkbox";
import { Heading } from "../core/Heading";
import { Icon } from "../core/Icon";
import { Link } from "../core/Link";
import { Select } from "../core/Select";
import { HStack, VStack } from "../core/Stacks";
const NULL_FONT_VALUE = "__NULL_FONT__";

View File

@@ -1,18 +1,16 @@
import { openUrl } from "@tauri-apps/plugin-opener";
import { useLicense } from "@yaakapp-internal/license";
import { Banner, HStack, Icon, VStack } from "@yaakapp-internal/ui";
import { differenceInDays } from "date-fns";
import { formatDate } from "date-fns/format";
import { useState } from "react";
import { useToggle } from "../../hooks/useToggle";
import { pluralizeCount } from "../../lib/pluralize";
import { CargoFeature } from "../CargoFeature";
import { Banner } from "../core/Banner";
import { Button } from "../core/Button";
import { Icon } from "../core/Icon";
import { Link } from "../core/Link";
import { PlainInput } from "../core/PlainInput";
import { Separator } from "../core/Separator";
import { HStack, VStack } from "../core/Stacks";
export function SettingsLicense() {
return (

View File

@@ -9,10 +9,22 @@ import {
searchPlugins,
uninstallPlugin,
} from "@yaakapp-internal/plugins";
import {
HStack,
Icon,
InlineCode,
LoadingIcon,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
useDebouncedValue,
} from "@yaakapp-internal/ui";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import { useState } from "react";
import { useDebouncedValue } from "../../hooks/useDebouncedValue";
import { useInstallPlugin } from "../../hooks/useInstallPlugin";
import { usePluginInfo } from "../../hooks/usePluginInfo";
import { usePluginsKey, useRefreshPlugins } from "../../hooks/usePlugins";
@@ -21,14 +33,9 @@ import { minPromiseMillis } from "../../lib/minPromiseMillis";
import { Button } from "../core/Button";
import { Checkbox } from "../core/Checkbox";
import { CountBadge } from "../core/CountBadge";
import { Icon } from "../core/Icon";
import { IconButton } from "../core/IconButton";
import { InlineCode } from "../core/InlineCode";
import { Link } from "../core/Link";
import { LoadingIcon } from "../core/LoadingIcon";
import { PlainInput } from "../core/PlainInput";
import { HStack } from "../core/Stacks";
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "../core/Table";
import { TabContent, Tabs } from "../core/Tabs/Tabs";
import { EmptyStateText } from "../EmptyStateText";
import { SelectFile } from "../SelectFile";

View File

@@ -1,13 +1,10 @@
import { patchModel, settingsAtom } from "@yaakapp-internal/models";
import { Heading, HStack, InlineCode, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai";
import { Checkbox } from "../core/Checkbox";
import { Heading } from "../core/Heading";
import { InlineCode } from "../core/InlineCode";
import { PlainInput } from "../core/PlainInput";
import { Select } from "../core/Select";
import { Separator } from "../core/Separator";
import { HStack, VStack } from "../core/Stacks";
export function SettingsProxy() {
const settings = useAtomValue(settingsAtom);

View File

@@ -1,18 +1,15 @@
import { patchModel, settingsAtom } from "@yaakapp-internal/models";
import { Heading, HStack, Icon, type IconProps, VStack } from "@yaakapp-internal/ui";
import { useAtomValue } from "jotai";
import { lazy, Suspense } from "react";
import { activeWorkspaceAtom } from "../../hooks/useActiveWorkspace";
import { useResolvedAppearance } from "../../hooks/useResolvedAppearance";
import { useResolvedTheme } from "../../hooks/useResolvedTheme";
import type { ButtonProps } from "../core/Button";
import { Heading } from "../core/Heading";
import type { IconProps } from "../core/Icon";
import { Icon } from "../core/Icon";
import { IconButton } from "../core/IconButton";
import { Link } from "../core/Link";
import type { SelectProps } from "../core/Select";
import { Select } from "../core/Select";
import { HStack, VStack } from "../core/Stacks";
const Editor = lazy(() => import("../core/Editor/Editor").then((m) => ({ default: m.Editor })));

View File

@@ -9,7 +9,7 @@ import { showDialog } from "../lib/dialog";
import { importData } from "../lib/importData";
import type { DropdownRef } from "./core/Dropdown";
import { Dropdown } from "./core/Dropdown";
import { Icon } from "./core/Icon";
import { Icon } from "@yaakapp-internal/ui";
import { IconButton } from "./core/IconButton";
import { KeyboardShortcutsDialog } from "./KeyboardShortcutsDialog";

View File

@@ -23,7 +23,7 @@ import {
} from "@yaakapp-internal/models";
import classNames from "classnames";
import { atom, useAtomValue } from "jotai";
import { selectAtom } from "jotai/utils";
import { atomFamily, selectAtom } from "jotai/utils";
import { memo, useCallback, useEffect, useMemo, useRef } from "react";
import { moveToWorkspace } from "../commands/moveToWorkspace";
import { openFolderSettings } from "../commands/openFolderSettings";
@@ -43,31 +43,37 @@ import { useSidebarHidden } from "../hooks/useSidebarHidden";
import { getWebsocketRequestActions } from "../hooks/useWebsocketRequestActions";
import { deepEqualAtom } from "../lib/atoms";
import { deleteModelWithConfirm } from "../lib/deleteModelWithConfirm";
import { fireAndForget } from "../lib/fireAndForget";
import { jotaiStore } from "../lib/jotai";
import { resolvedModelName } from "../lib/resolvedModelName";
import { isSidebarFocused } from "../lib/scopes";
import { navigateToRequestOrFolderOrWorkspace } from "../lib/setWorkspaceSearchParams";
import type { ContextMenuProps, DropdownItem } from "./core/Dropdown";
import { Dropdown } from "./core/Dropdown";
import { ContextMenu, Dropdown } from "./core/Dropdown";
import type { FieldDef } from "./core/Editor/filter/extension";
import { filter } from "./core/Editor/filter/extension";
import { evaluate, parseQuery } from "./core/Editor/filter/query";
import { HttpMethodTag } from "./core/HttpMethodTag";
import { HttpStatusTag } from "./core/HttpStatusTag";
import { Icon } from "./core/Icon";
import {
Icon,
LoadingIcon,
Tree,
isSelectedFamily,
selectedIdsFamily,
InlineCode,
} from "@yaakapp-internal/ui";
import type { TreeNode, TreeHandle, TreeProps, TreeItemProps } from "@yaakapp-internal/ui";
import { IconButton } from "./core/IconButton";
import { InlineCode } from "./core/InlineCode";
import type { InputHandle } from "./core/Input";
import { Input } from "./core/Input";
import { LoadingIcon } from "./core/LoadingIcon";
import { collapsedFamily, isSelectedFamily, selectedIdsFamily } from "./core/tree/atoms";
import type { TreeNode } from "./core/tree/common";
import type { TreeHandle, TreeProps } from "./core/tree/Tree";
import { Tree } from "./core/tree/Tree";
import type { TreeItemProps } from "./core/tree/TreeItem";
import { atomWithKVStorage } from "../lib/atoms/atomWithKVStorage";
import { GitDropdown } from "./git/GitDropdown";
const collapsedFamily = atomFamily((treeId: string) => {
const key = ["sidebar_collapsed", treeId ?? "n/a"];
return atomWithKVStorage<Record<string, boolean>>(key, {});
});
type SidebarModel = Workspace | Folder | HttpRequest | GrpcRequest | WebsocketRequest;
function isSidebarLeafModel(m: AnyModel): boolean {
const modelMap: Record<Exclude<SidebarModel["model"], "workspace">, null> = {
@@ -230,95 +236,127 @@ function Sidebar({ className }: { className?: string }) {
[],
);
const actions = useMemo(() => {
const enable = () => treeRef.current?.hasFocus() ?? false;
const treeHasFocus = useCallback(() => treeRef.current?.hasFocus() ?? false, []);
const actions = {
"sidebar.context_menu": {
enable,
cb: () => treeRef.current?.showContextMenu(),
},
"sidebar.expand_all": {
enable: isSidebarFocused,
cb: () => {
jotaiStore.set(collapsedFamily(treeId), {});
},
},
"sidebar.collapse_all": {
enable: isSidebarFocused,
cb: () => {
if (tree == null) return;
const getSelectedTreeModels = useCallback(
() => treeRef.current?.getSelectedItems() as SidebarModel[] | undefined,
[],
);
const next = (node: TreeNode<SidebarModel>, collapsed: Record<string, boolean>) => {
let newCollapsed = { ...collapsed };
for (const n of node.children ?? []) {
if (n.item.model !== "folder") continue;
newCollapsed[n.item.id] = true;
newCollapsed = next(n, newCollapsed);
}
return newCollapsed;
};
const collapsed = next(tree, {});
jotaiStore.set(collapsedFamily(treeId), collapsed);
},
},
"sidebar.selected.delete": {
enable,
cb: async (items: SidebarModel[]) => {
await deleteModelWithConfirm(items);
},
},
"sidebar.selected.rename": {
enable,
allowDefault: true,
cb: async (items: SidebarModel[]) => {
const item = items[0];
if (items.length === 1 && item != null) {
treeRef.current?.renameItem(item.id);
}
},
},
"sidebar.selected.duplicate": {
// Higher priority so this takes precedence over model.duplicate (same Meta+d binding)
priority: 10,
enable,
cb: async (items: SidebarModel[]) => {
if (items.length === 1 && items[0]) {
const item = items[0];
const newId = await duplicateModel(item);
navigateToRequestOrFolderOrWorkspace(newId, item.model);
} else {
await Promise.all(items.map(duplicateModel));
}
},
},
"sidebar.selected.move": {
enable,
cb: async (items: SidebarModel[]) => {
const requests = items.filter(
(i): i is HttpRequest | GrpcRequest | WebsocketRequest =>
i.model === "http_request" ||
i.model === "grpc_request" ||
i.model === "websocket_request",
);
if (requests.length > 0) {
moveToWorkspace.mutate(requests);
}
},
},
"request.send": {
enable,
cb: async (items: SidebarModel[]) => {
await Promise.all(
items
.filter((i) => i.model === "http_request")
.map((i) => sendAnyHttpRequest.mutate(i.id)),
);
},
},
} as const;
return actions;
}, [tree, treeId]);
const handleRenameSelected = useCallback((items: SidebarModel[]) => {
if (items.length === 1 && items[0] != null) {
treeRef.current?.renameItem(items[0].id);
}
}, []);
const handleDeleteSelected = useCallback(async (items: SidebarModel[]) => {
await deleteModelWithConfirm(items);
}, []);
const handleDuplicateSelected = useCallback(async (items: SidebarModel[]) => {
if (items.length === 1 && items[0]) {
const newId = await duplicateModel(items[0]);
navigateToRequestOrFolderOrWorkspace(newId, items[0].model);
} else {
await Promise.all(items.map(duplicateModel));
}
}, []);
const handleMoveSelected = useCallback((items: SidebarModel[]) => {
const requests = items.filter(
(i): i is HttpRequest | GrpcRequest | WebsocketRequest =>
i.model === "http_request" || i.model === "grpc_request" || i.model === "websocket_request",
);
if (requests.length > 0) {
moveToWorkspace.mutate(requests);
}
}, []);
const handleSendSelected = useCallback(async (items: SidebarModel[]) => {
await Promise.all(
items.filter((i) => i.model === "http_request").map((i) => sendAnyHttpRequest.mutate(i.id)),
);
}, []);
useHotKey(
"sidebar.context_menu",
useCallback(() => {
treeRef.current?.showContextMenu();
}, []),
{ enable: treeHasFocus },
);
useHotKey(
"sidebar.expand_all",
useCallback(() => {
jotaiStore.set(collapsedFamily(treeId), {});
}, [treeId]),
{ enable: isSidebarFocused },
);
useHotKey(
"sidebar.collapse_all",
useCallback(() => {
if (tree == null) return;
const next = (node: TreeNode<SidebarModel>, collapsed: Record<string, boolean>) => {
let newCollapsed = { ...collapsed };
for (const n of node.children ?? []) {
if (n.item.model !== "folder") continue;
newCollapsed[n.item.id] = true;
newCollapsed = next(n, newCollapsed);
}
return newCollapsed;
};
const collapsed = next(tree, {});
jotaiStore.set(collapsedFamily(treeId), collapsed);
}, [tree, treeId]),
{ enable: isSidebarFocused },
);
useHotKey(
"sidebar.selected.delete",
useCallback(() => {
const items = getSelectedTreeModels();
if (items) void handleDeleteSelected(items);
}, [getSelectedTreeModels, handleDeleteSelected]),
{ enable: treeHasFocus },
);
useHotKey(
"sidebar.selected.rename",
useCallback(() => {
const items = getSelectedTreeModels();
if (items) handleRenameSelected(items);
}, [getSelectedTreeModels, handleRenameSelected]),
{ enable: treeHasFocus, allowDefault: true },
);
useHotKey(
"sidebar.selected.duplicate",
useCallback(async () => {
const items = getSelectedTreeModels();
if (items) await handleDuplicateSelected(items);
}, [getSelectedTreeModels, handleDuplicateSelected]),
{ priority: 10, enable: treeHasFocus },
);
useHotKey(
"sidebar.selected.move",
useCallback(() => {
const items = getSelectedTreeModels();
if (items) handleMoveSelected(items);
}, [getSelectedTreeModels, handleMoveSelected]),
{ enable: treeHasFocus },
);
useHotKey(
"request.send",
useCallback(async () => {
const items = getSelectedTreeModels();
if (items) await handleSendSelected(items);
}, [getSelectedTreeModels, handleSendSelected]),
{ enable: treeHasFocus },
);
const getContextMenu = useCallback<(items: SidebarModel[]) => Promise<DropdownItem[]>>(
async (items) => {
@@ -356,7 +394,7 @@ function Sidebar({ className }: { className?: string }) {
hotKeyLabelOnly: true,
hidden: !onlyHttpRequests,
leftSlot: <Icon icon="send_horizontal" />,
onSelect: () => actions["request.send"].cb(items),
onSelect: () => handleSendSelected(items),
},
...(items.length === 1 && child.model === "http_request"
? await getHttpRequestActions()
@@ -426,16 +464,14 @@ function Sidebar({ className }: { className?: string }) {
hidden: items.length > 1,
hotKeyAction: "sidebar.selected.rename",
hotKeyLabelOnly: true,
onSelect: () => {
treeRef.current?.renameItem(child.id);
},
onSelect: () => handleRenameSelected(items),
},
{
label: "Duplicate",
hotKeyAction: "model.duplicate",
hotKeyLabelOnly: true, // Would trigger for every request (bad)
leftSlot: <Icon icon="copy" />,
onSelect: () => actions["sidebar.selected.duplicate"].cb(items),
onSelect: () => handleDuplicateSelected(items),
},
{
label: items.length <= 1 ? "Move" : `Move ${requestItems.length} Requests`,
@@ -446,9 +482,7 @@ function Sidebar({ className }: { className?: string }) {
workspaces.length <= 1 ||
requestItems.length === 0 ||
requestItems.length !== items.length,
onSelect: () => {
fireAndForget(actions["sidebar.selected.move"].cb(items));
},
onSelect: () => handleMoveSelected(items),
},
{
color: "danger",
@@ -456,16 +490,23 @@ function Sidebar({ className }: { className?: string }) {
hotKeyAction: "sidebar.selected.delete",
hotKeyLabelOnly: true,
leftSlot: <Icon icon="trash" />,
onSelect: () => actions["sidebar.selected.delete"].cb(items),
onSelect: () => handleDeleteSelected(items),
},
...modelCreationItems,
];
return menuItems;
},
[actions],
[],
);
const hotkeys = useMemo<TreeProps<SidebarModel>["hotkeys"]>(() => ({ actions }), [actions]);
const renderContextMenuFn = useCallback<
NonNullable<TreeProps<SidebarModel>["renderContextMenu"]>
>(
({ items, position, onClose }) => (
<ContextMenu items={items as DropdownItem[]} triggerPosition={position} onClose={onClose} />
),
[],
);
// Use a language compartment for the filter so we can reconfigure it when the autocompletion changes
const filterLanguageCompartmentRef = useRef(new Compartment());
@@ -552,14 +593,29 @@ function Sidebar({ className }: { className?: string }) {
{
label: "Expand All Folders",
leftSlot: <Icon icon="chevrons_up_down" />,
onSelect: actions["sidebar.expand_all"].cb,
onSelect: () => jotaiStore.set(collapsedFamily(treeId), {}),
hotKeyAction: "sidebar.expand_all",
hotKeyLabelOnly: true,
},
{
label: "Collapse All Folders",
leftSlot: <Icon icon="chevrons_down_up" />,
onSelect: actions["sidebar.collapse_all"].cb,
onSelect: () => {
if (tree == null) return;
const next = (
node: TreeNode<SidebarModel>,
collapsed: Record<string, boolean>,
) => {
let newCollapsed = { ...collapsed };
for (const n of node.children ?? []) {
if (n.item.model !== "folder") continue;
newCollapsed[n.item.id] = true;
newCollapsed = next(n, newCollapsed);
}
return newCollapsed;
};
jotaiStore.set(collapsedFamily(treeId), next(tree, {}));
},
hotKeyAction: "sidebar.collapse_all",
hotKeyLabelOnly: true,
},
@@ -584,11 +640,12 @@ function Sidebar({ className }: { className?: string }) {
ref={handleTreeRefInit}
root={tree}
treeId={treeId}
hotkeys={hotkeys}
collapsedAtom={collapsedFamily(treeId)}
getItemKey={getItemKey}
ItemInner={SidebarInnerItem}
ItemLeftSlotInner={SidebarLeftSlot}
getContextMenu={getContextMenu}
renderContextMenu={renderContextMenuFn}
onActivate={handleActivate}
getEditOptions={getEditOptions}
className="pl-2 pr-3 pt-2 pb-2"

View File

@@ -1,28 +1,28 @@
import { HStack } from "@yaakapp-internal/ui";
import { useMemo } from "react";
import { useFloatingSidebarHidden } from "../hooks/useFloatingSidebarHidden";
import { useShouldFloatSidebar } from "../hooks/useShouldFloatSidebar";
import { useSidebarHidden } from "../hooks/useSidebarHidden";
import { CreateDropdown } from "./CreateDropdown";
import { IconButton } from "./core/IconButton";
import { HStack } from "./core/Stacks";
export function SidebarActions() {
const floating = useShouldFloatSidebar();
const [normalHidden, setNormalHidden] = useSidebarHidden();
interface Props {
floating?: boolean;
}
export function SidebarActions({ floating = false }: Props) {
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
const [floatingHidden, setFloatingHidden] = useFloatingSidebarHidden();
const hidden = floating ? floatingHidden : normalHidden;
const hidden = floating ? floatingHidden : sidebarHidden;
const setHidden = useMemo(
() => (floating ? setFloatingHidden : setNormalHidden),
[floating, setFloatingHidden, setNormalHidden],
() => (floating ? setFloatingHidden : setSidebarHidden),
[floating, setFloatingHidden, setSidebarHidden],
);
return (
<HStack className="h-full">
<IconButton
onClick={async () => {
// NOTE: We're not using the (h) => !h pattern here because the data
// might be different if another window changed it (out of sync)
await setHidden(!hidden);
}}
className="pointer-events-auto"

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