Compare commits

...

7 Commits

Author SHA1 Message Date
Gregory Schier
b0b282535f Cargo fmt 2026-05-08 12:03:34 -07:00
Gregory Schier
19ed8c2f0d Clean up update downloads after install
Removes the update downloads cache directory after a successful integrated update install so cached artifacts do not accumulate.\n\nFeedback: https://yaak.app/feedback/posts/updates-cache-directory-cleanup
2026-05-08 12:00:25 -07:00
Gregory Schier
d7e67cf13c Add live git status indicators (#458) 2026-05-08 11:25:39 -07:00
Gregory Schier
1b154ba550 Fix workspace content row sizing 2026-05-08 08:47:34 -07:00
Gregory Schier
10559c8f4f Split codebase (#455) 2026-05-07 15:50:10 -07:00
dependabot[bot]
d2dc719cc6 Bump ip-address and express-rate-limit (#453)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-07 08:35:14 -07:00
Gregory Schier
50f33b45b9 Add domain filter to cookie template function (#452) 2026-05-07 07:06:21 -07:00
762 changed files with 9702 additions and 4074 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,8 @@ 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"
projectPath: ./crates-tauri/yaak-app-client
args: "${{ matrix.args }} --config ./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 +172,9 @@ 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"}}}}'
Push-Location crates-tauri/yaak-app-client
npx tauri bundle ${{ matrix.args }} --bundles nsis --config ./tauri.release.conf.json --config '{"bundle":{"createUpdaterArtifacts":true,"windows":{"nsis":{"installMode":"perMachine"}}}}'
Pop-Location
$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

3
.oxfmtignore Normal file
View File

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

View File

@@ -1,4 +1,8 @@
{
"printWidth": 100,
"ignorePatterns": ["**/bindings/**", "crates/yaak-templates/pkg/**", "src-web/routeTree.gen.ts"]
"ignorePatterns": [
"**/bindings/**",
"crates/yaak-templates/pkg/**",
"apps/yaak-client/routeTree.gen.ts"
]
}

154
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",
@@ -6807,6 +6860,7 @@ version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
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",
@@ -10256,6 +10310,7 @@ dependencies = [
"log 0.4.29",
"md5 0.8.0",
"mime_guess",
"notify",
"openssl-sys",
"pretty_graphql",
"r2d2",
@@ -10303,9 +10358,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 +10446,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 +10537,7 @@ dependencies = [
"hyper-util",
"log 0.4.29",
"mime_guess",
"native-tls",
"regex 1.11.1",
"reqwest",
"serde",
@@ -10516,6 +10607,7 @@ dependencies = [
"thiserror 2.0.17",
"ts-rs",
"yaak-core",
"yaak-database",
]
[[package]]
@@ -10547,6 +10639,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 +10750,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 +10792,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-family";
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

@@ -1,6 +1,8 @@
import type { Extension } from "@codemirror/state";
import { Compartment } from "@codemirror/state";
import { debounce } from "@yaakapp-internal/lib";
import { gitMutations } from "@yaakapp-internal/git";
import type { GitStatus } from "@yaakapp-internal/git";
import type {
AnyModel,
Folder,
@@ -23,13 +25,18 @@ import {
} from "@yaakapp-internal/models";
import classNames from "classnames";
import { atom, useAtomValue } from "jotai";
import { atomFamily } from "jotai-family";
import { selectAtom } from "jotai/utils";
import { memo, useCallback, useEffect, useMemo, useRef } from "react";
import { moveToWorkspace } from "../commands/moveToWorkspace";
import { openFolderSettings } from "../commands/openFolderSettings";
import { activeFolderIdAtom } from "../hooks/useActiveFolderId";
import { activeRequestIdAtom } from "../hooks/useActiveRequestId";
import { activeWorkspaceAtom, activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import {
activeWorkspaceAtom,
activeWorkspaceIdAtom,
activeWorkspaceMetaAtom,
} from "../hooks/useActiveWorkspace";
import { allRequestsAtom } from "../hooks/useAllRequests";
import { getCreateDropdownItems } from "../hooks/useCreateDropdownItems";
import { getFolderActions } from "../hooks/useFolderActions";
@@ -42,31 +49,46 @@ import { sendAnyHttpRequest } from "../hooks/useSendAnyHttpRequest";
import { useSidebarHidden } from "../hooks/useSidebarHidden";
import { getWebsocketRequestActions } from "../hooks/useWebsocketRequestActions";
import { deepEqualAtom } from "../lib/atoms";
import { showConfirm } from "../lib/confirm";
import { deleteModelWithConfirm } from "../lib/deleteModelWithConfirm";
import { fireAndForget } from "../lib/fireAndForget";
import { showDialog } from "../lib/dialog";
import {
gitWorktreeStatusByModelIdAtom,
gitWorktreeStatusFamily,
} from "../lib/gitWorktreeStatus";
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";
import { gitCallbacks } from "./git/callbacks";
import { FileHistoryDialog } from "./git/FileHistoryDialog";
import { sync } from "../init/sync";
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 {
@@ -230,95 +252,129 @@ 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.mutateAsync(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) => {
@@ -335,6 +391,8 @@ function Sidebar({ className }: { className?: string }) {
}
const workspaces = jotaiStore.get(workspacesAtom);
const syncDir = jotaiStore.get(activeWorkspaceMetaAtom)?.settingSyncDir;
const gitItems = getGitContextMenuItems({ items, syncDir });
const onlyHttpRequests = items.every((i) => i.model === "http_request");
const requestItems = items.filter(
(i) =>
@@ -356,7 +414,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()
@@ -418,24 +476,24 @@ function Sidebar({ className }: { className?: string }) {
...initialItems,
{
type: "separator",
hidden: initialItems.filter((v) => !v.hidden).length === 0,
hidden: initialItems.filter((v) => !v.hidden).length === 0 || gitItems.length === 0,
},
...gitItems,
{ type: "separator", hidden: gitItems.length === 0 },
{
label: "Rename",
leftSlot: <Icon icon="pencil" />,
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 +504,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 +512,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 +615,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 +662,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"
@@ -602,6 +681,73 @@ function Sidebar({ className }: { className?: string }) {
export default Sidebar;
function getGitContextMenuItems({
items,
syncDir,
}: {
items: SidebarModel[];
syncDir: string | null | undefined;
}): DropdownItem[] {
if (syncDir == null) return [];
const gitStatusEntries = items.flatMap((item) => {
const status = jotaiStore.get(gitWorktreeStatusFamily(item.id));
return status == null || status.status === "current" ? [] : [status];
});
const historyItem = items.length === 1 ? items[0] : null;
const historyPath =
historyItem == null
? null
: (jotaiStore.get(gitWorktreeStatusFamily(historyItem.id))?.relaPath ??
syncPathForModel(historyItem));
return [
{
label: "View History",
leftSlot: <Icon icon="history" />,
hidden: historyPath == null,
onSelect: () => {
if (historyPath == null) return;
showDialog({
id: "git-history",
size: "lg",
title: "File History",
noPadding: true,
noScroll: true,
render: () => <FileHistoryDialog dir={syncDir} relaPath={historyPath} />,
});
},
},
{
label: "Restore Changes",
leftSlot: <Icon icon="rotate_ccw" />,
hidden: gitStatusEntries.length === 0,
async onSelect() {
const confirmed = await showConfirm({
id: "git-restore-sidebar-items",
title: "Restore Changes",
description:
gitStatusEntries.length === 1
? "This will discard uncommitted changes for the selected item."
: `This will discard uncommitted changes for ${gitStatusEntries.length} selected items.`,
confirmText: "Restore",
color: "danger",
});
if (!confirmed) return;
await gitMutations(syncDir, gitCallbacks(syncDir)).restore.mutateAsync({
relaPaths: gitStatusEntries.map((entry) => entry.relaPath),
});
await sync({ force: true });
},
},
];
}
function syncPathForModel(item: SidebarModel) {
return `yaak.${item.id}.yaml`;
}
const activeIdAtom = atom<string | null>((get) => {
return get(activeRequestIdAtom) || get(activeFolderIdAtom);
});
@@ -731,6 +877,64 @@ const sidebarTreeAtom = atom<[TreeNode<SidebarModel>, FieldDef[]] | null>((get)
return [root, fields] as const;
});
const sidebarGitStatusByModelIdAtom = atom<Record<string, GitStatus>>((get) => {
const allModels = get(memoAllPotentialChildrenAtom);
const activeWorkspace = get(activeWorkspaceAtom);
const gitStatusByModelId = get(gitWorktreeStatusByModelIdAtom);
const childrenMap: Record<string, Exclude<SidebarModel, Workspace>[]> = {};
const statusByModelId: Record<string, GitStatus> = {};
for (const item of allModels) {
if ("folderId" in item && item.folderId == null) {
childrenMap[item.workspaceId] = childrenMap[item.workspaceId] ?? [];
childrenMap[item.workspaceId]?.push(item);
} else if ("folderId" in item && item.folderId != null) {
childrenMap[item.folderId] = childrenMap[item.folderId] ?? [];
childrenMap[item.folderId]?.push(item);
}
}
const visit = (item: SidebarModel): GitStatus | null => {
const statuses: GitStatus[] = [];
const directStatus = gitStatusByModelId[item.id]?.status;
if (directStatus != null && directStatus !== "current") {
statuses.push(directStatus);
}
for (const child of childrenMap[item.id] ?? []) {
const childStatus = visit(child);
if (childStatus != null) statuses.push(childStatus);
}
const status = summarizeGitStatuses(statuses);
if (status != null) {
statusByModelId[item.id] = status;
}
return status;
};
if (activeWorkspace != null) {
visit(activeWorkspace);
}
return statusByModelId;
});
const sidebarGitStatusFamily = atomFamily(
(modelId: string) =>
selectAtom(sidebarGitStatusByModelIdAtom, (statusByModelId) => statusByModelId[modelId] ?? null),
Object.is,
);
function summarizeGitStatuses(statuses: GitStatus[]): GitStatus | null {
if (statuses.length === 0) return null;
const firstStatus = statuses[0];
if (firstStatus != null && statuses.every((status) => status === firstStatus)) {
return firstStatus;
}
return "modified";
}
function getItemKey(item: SidebarModel) {
const responses = jotaiStore.get(httpResponsesAtom);
const latestResponse = responses.find((r) => r.requestId === item.id) ?? null;
@@ -777,6 +981,7 @@ const SidebarInnerItem = memo(function SidebarInnerItem({
treeId: string;
item: SidebarModel;
}) {
const gitStatus = useAtomValue(sidebarGitStatusFamily(item.id));
const response = useAtomValue(
useMemo(
() =>
@@ -795,7 +1000,16 @@ const SidebarInnerItem = memo(function SidebarInnerItem({
return (
<div className="flex items-center gap-2 min-w-0 h-full w-full text-left">
<div className="truncate">{resolvedModelName(item)}</div>
<div
className={classNames(
"truncate",
gitStatus === "modified" && "text-info",
gitStatus === "untracked" && "text-success",
gitStatus === "removed" && "text-danger",
)}
>
{resolvedModelName(item)}
</div>
{response != null && (
<div className="ml-auto">
{response.state !== "closed" ? (

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