mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:13:51 +01:00
Fix workspace creation, reveal sync dir, and don't update timestamps on sync/import
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
"opener:allow-open-url",
|
||||
"opener:allow-open-path",
|
||||
"opener:allow-default-urls",
|
||||
"opener:allow-reveal-item-in-dir",
|
||||
"core:webview:allow-set-webview-zoom",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-internal-toggle-maximize",
|
||||
|
||||
2
src-tauri/gen/schemas/capabilities.json
generated
2
src-tauri/gen/schemas/capabilities.json
generated
@@ -1 +1 @@
|
||||
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-dir","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"opener:allow-open-url","opener:allow-open-path","opener:allow-default-urls","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","yaak-license:default","yaak-sync:default"]}}
|
||||
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-dir","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"opener:allow-open-url","opener:allow-open-path","opener:allow-default-urls","opener:allow-reveal-item-in-dir","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","yaak-license:default","yaak-sync:default"]}}
|
||||
@@ -9,7 +9,7 @@ use crate::models::{
|
||||
WorkspaceMetaIden,
|
||||
};
|
||||
use crate::plugin::SqliteConnection;
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use log::{debug, error, info, warn};
|
||||
use nanoid::nanoid;
|
||||
use rusqlite::OptionalExtension;
|
||||
@@ -278,8 +278,8 @@ pub async fn upsert_workspace<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
timestamp_for_upsert(update_source, workspace.created_at).into(),
|
||||
timestamp_for_upsert(update_source, workspace.updated_at).into(),
|
||||
trimmed_name.into(),
|
||||
workspace.description.into(),
|
||||
workspace.setting_follow_redirects.into(),
|
||||
@@ -297,6 +297,7 @@ pub async fn upsert_workspace<R: Runtime>(
|
||||
WorkspaceIden::SettingRequestTimeout,
|
||||
WorkspaceIden::SettingValidateCertificates,
|
||||
])
|
||||
.values([(WorkspaceIden::UpdatedAt, CurrentTimestamp.into())])
|
||||
.to_owned(),
|
||||
)
|
||||
.returning_all()
|
||||
@@ -333,8 +334,8 @@ pub async fn upsert_workspace_meta<R: Runtime>(
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
workspace_meta.workspace_id.into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
timestamp_for_upsert(update_source, workspace_meta.created_at).into(),
|
||||
timestamp_for_upsert(update_source, workspace_meta.updated_at).into(),
|
||||
workspace_meta.setting_sync_dir.into(),
|
||||
])
|
||||
.on_conflict(
|
||||
@@ -500,8 +501,8 @@ pub async fn upsert_grpc_request<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
timestamp_for_upsert(update_source, request.created_at).into(),
|
||||
timestamp_for_upsert(update_source, request.updated_at).into(),
|
||||
trimmed_name.into(),
|
||||
request.description.into(),
|
||||
request.workspace_id.into(),
|
||||
@@ -612,8 +613,8 @@ pub async fn upsert_grpc_connection<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
timestamp_for_upsert(update_source, connection.created_at).into(),
|
||||
timestamp_for_upsert(update_source, connection.updated_at).into(),
|
||||
connection.workspace_id.as_str().into(),
|
||||
connection.request_id.as_str().into(),
|
||||
connection.service.as_str().into(),
|
||||
@@ -771,8 +772,8 @@ pub async fn upsert_grpc_event<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
timestamp_for_upsert(update_source, event.created_at).into(),
|
||||
timestamp_for_upsert(update_source, event.updated_at).into(),
|
||||
event.workspace_id.as_str().into(),
|
||||
event.request_id.as_str().into(),
|
||||
event.connection_id.as_str().into(),
|
||||
@@ -859,8 +860,8 @@ pub async fn upsert_cookie_jar<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
timestamp_for_upsert(update_source, cookie_jar.created_at).into(),
|
||||
timestamp_for_upsert(update_source, cookie_jar.updated_at).into(),
|
||||
cookie_jar.workspace_id.as_str().into(),
|
||||
trimmed_name.into(),
|
||||
serde_json::to_string(&cookie_jar.cookies)?.into(),
|
||||
@@ -1064,8 +1065,8 @@ pub async fn upsert_environment<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
timestamp_for_upsert(update_source, environment.created_at).into(),
|
||||
timestamp_for_upsert(update_source, environment.updated_at).into(),
|
||||
environment.environment_id.into(),
|
||||
environment.workspace_id.into(),
|
||||
trimmed_name.into(),
|
||||
@@ -1174,8 +1175,8 @@ pub async fn upsert_plugin<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
timestamp_for_upsert(update_source, plugin.created_at).into(),
|
||||
timestamp_for_upsert(update_source, plugin.updated_at).into(),
|
||||
plugin.checked_at.into(),
|
||||
plugin.directory.into(),
|
||||
plugin.url.into(),
|
||||
@@ -1276,15 +1277,15 @@ pub async fn delete_folder<R: Runtime>(
|
||||
|
||||
pub async fn upsert_folder<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
r: Folder,
|
||||
folder: Folder,
|
||||
|
||||
update_source: &UpdateSource,
|
||||
) -> Result<Folder> {
|
||||
let id = match r.id.as_str() {
|
||||
let id = match folder.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeFolder),
|
||||
_ => r.id.to_string(),
|
||||
_ => folder.id.to_string(),
|
||||
};
|
||||
let trimmed_name = r.name.trim();
|
||||
let trimmed_name = folder.name.trim();
|
||||
|
||||
let dbm = &*window.app_handle().state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
@@ -1303,13 +1304,13 @@ pub async fn upsert_folder<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
r.workspace_id.as_str().into(),
|
||||
r.folder_id.as_ref().map(|s| s.as_str()).into(),
|
||||
timestamp_for_upsert(update_source, folder.created_at).into(),
|
||||
timestamp_for_upsert(update_source, folder.updated_at).into(),
|
||||
folder.workspace_id.as_str().into(),
|
||||
folder.folder_id.as_ref().map(|s| s.as_str()).into(),
|
||||
trimmed_name.into(),
|
||||
r.description.into(),
|
||||
r.sort_priority.into(),
|
||||
folder.description.into(),
|
||||
folder.sort_priority.into(),
|
||||
])
|
||||
.on_conflict(
|
||||
OnConflict::column(GrpcEventIden::Id)
|
||||
@@ -1419,14 +1420,14 @@ pub async fn duplicate_folder<R: Runtime>(
|
||||
|
||||
pub async fn upsert_http_request<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
r: HttpRequest,
|
||||
request: HttpRequest,
|
||||
update_source: &UpdateSource,
|
||||
) -> Result<HttpRequest> {
|
||||
let id = match r.id.as_str() {
|
||||
let id = match request.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypeHttpRequest),
|
||||
_ => r.id.to_string(),
|
||||
_ => request.id.to_string(),
|
||||
};
|
||||
let trimmed_name = r.name.trim();
|
||||
let trimmed_name = request.name.trim();
|
||||
|
||||
let dbm = &*window.app_handle().state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
@@ -1453,21 +1454,21 @@ pub async fn upsert_http_request<R: Runtime>(
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
r.workspace_id.into(),
|
||||
r.folder_id.as_ref().map(|s| s.as_str()).into(),
|
||||
timestamp_for_upsert(update_source, request.created_at).into(),
|
||||
timestamp_for_upsert(update_source, request.updated_at).into(),
|
||||
request.workspace_id.into(),
|
||||
request.folder_id.as_ref().map(|s| s.as_str()).into(),
|
||||
trimmed_name.into(),
|
||||
r.description.into(),
|
||||
r.url.into(),
|
||||
serde_json::to_string(&r.url_parameters)?.into(),
|
||||
r.method.into(),
|
||||
serde_json::to_string(&r.body)?.into(),
|
||||
r.body_type.as_ref().map(|s| s.as_str()).into(),
|
||||
serde_json::to_string(&r.authentication)?.into(),
|
||||
r.authentication_type.as_ref().map(|s| s.as_str()).into(),
|
||||
serde_json::to_string(&r.headers)?.into(),
|
||||
r.sort_priority.into(),
|
||||
request.description.into(),
|
||||
request.url.into(),
|
||||
serde_json::to_string(&request.url_parameters)?.into(),
|
||||
request.method.into(),
|
||||
serde_json::to_string(&request.body)?.into(),
|
||||
request.body_type.as_ref().map(|s| s.as_str()).into(),
|
||||
serde_json::to_string(&request.authentication)?.into(),
|
||||
request.authentication_type.as_ref().map(|s| s.as_str()).into(),
|
||||
serde_json::to_string(&request.headers)?.into(),
|
||||
request.sort_priority.into(),
|
||||
])
|
||||
.on_conflict(
|
||||
OnConflict::column(GrpcEventIden::Id)
|
||||
@@ -2167,7 +2168,7 @@ pub async fn get_workspace_export_resources<R: Runtime>(
|
||||
let mut data = WorkspaceExport {
|
||||
yaak_version: mgr.package_info().version.clone().to_string(),
|
||||
yaak_schema: 2,
|
||||
timestamp: chrono::Utc::now().naive_utc(),
|
||||
timestamp: Utc::now().naive_utc(),
|
||||
resources: BatchUpsertResult {
|
||||
workspaces: Vec::new(),
|
||||
environments: Vec::new(),
|
||||
@@ -2197,3 +2198,21 @@ pub async fn get_workspace_export_resources<R: Runtime>(
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
// Generate the created_at or updated_at timestamps for an upsert operation, depending on the ID
|
||||
// provided.
|
||||
fn timestamp_for_upsert(update_source: &UpdateSource, dt: NaiveDateTime) -> NaiveDateTime {
|
||||
match update_source {
|
||||
// Sync and import operations always preserve timestamps
|
||||
UpdateSource::Sync | UpdateSource::Import => {
|
||||
if dt.and_utc().timestamp() == 0 {
|
||||
// Sometimes data won't have timestamps (partial data)
|
||||
Utc::now().naive_utc()
|
||||
} else {
|
||||
dt
|
||||
}
|
||||
},
|
||||
// Other sources will always update to the latest time
|
||||
_ => Utc::now().naive_utc(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::error::Result;
|
||||
use crate::sync::{
|
||||
apply_sync_ops, apply_sync_state_ops, compute_sync_ops, get_db_candidates, get_fs_candidates,
|
||||
SyncOp,
|
||||
FsCandidate, SyncOp,
|
||||
};
|
||||
use crate::watch::{watch_directory, WatchEvent};
|
||||
use chrono::Utc;
|
||||
@@ -23,16 +23,18 @@ pub async fn calculate<R: Runtime>(
|
||||
let fs_candidates = get_fs_candidates(sync_dir)
|
||||
.await?
|
||||
.into_iter()
|
||||
// Strip out any non-workspace candidates
|
||||
// Only keep items in the same workspace
|
||||
.filter(|fs| fs.model.workspace_id() == workspace_id)
|
||||
.collect();
|
||||
.collect::<Vec<FsCandidate>>();
|
||||
// println!("\ndb_candidates: \n{}\n", serde_json::to_string_pretty(&db_candidates)?);
|
||||
// println!("\nfs_candidates: \n{}\n", serde_json::to_string_pretty(&fs_candidates)?);
|
||||
Ok(compute_sync_ops(db_candidates, fs_candidates))
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn calculate_fs(dir: &Path) -> Result<Vec<SyncOp>> {
|
||||
let db_candidates = Vec::new();
|
||||
let fs_candidates = get_fs_candidates(Path::new(&dir)).await?;
|
||||
let fs_candidates = get_fs_candidates(dir).await?;
|
||||
Ok(compute_sync_ops(db_candidates, fs_candidates))
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ impl Display for SyncOp {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum DbCandidate {
|
||||
Added(SyncModel),
|
||||
Modified(SyncModel, SyncState),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Folder, Workspace } from '@yaakapp-internal/models';
|
||||
import type { Folder } from '@yaakapp-internal/models';
|
||||
import { applySync, calculateSync } from '@yaakapp-internal/sync';
|
||||
import { Banner } from '../components/core/Banner';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
@@ -10,21 +10,8 @@ import { showConfirm } from '../lib/confirm';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export const createWorkspace = createFastMutation<Workspace, void, Partial<Workspace>>({
|
||||
mutationKey: ['create_workspace'],
|
||||
mutationFn: (patch) => invokeCmd<Workspace>('cmd_update_workspace', { workspace: patch }),
|
||||
onSuccess: async (workspace) => {
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: workspace.id },
|
||||
});
|
||||
},
|
||||
onSettled: () => trackEvent('workspace', 'create'),
|
||||
});
|
||||
|
||||
export const createFolder = createFastMutation<
|
||||
Folder | null,
|
||||
void,
|
||||
@@ -66,8 +53,10 @@ export const syncWorkspace = createFastMutation<
|
||||
mutationFn: async ({ workspaceId, syncDir }) => {
|
||||
const ops = (await calculateSync(workspaceId, syncDir)) ?? [];
|
||||
if (ops.length === 0) {
|
||||
console.log('Nothing to sync', workspaceId, syncDir, ops);
|
||||
return;
|
||||
}
|
||||
console.log('syncing workspace', workspaceId, syncDir, ops);
|
||||
|
||||
const dbOps = ops.filter((o) => o.type.startsWith('db'));
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { showSimpleAlert } from '../lib/alert';
|
||||
import { router } from '../lib/router';
|
||||
|
||||
export const openWorkspace = createFastMutation({
|
||||
export const openWorkspaceFromSyncDir = createFastMutation({
|
||||
mutationKey: [],
|
||||
mutationFn: async () => {
|
||||
const dir = await open({
|
||||
42
src-web/commands/switchWorkspace.tsx
Normal file
42
src-web/commands/switchWorkspace.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { getRecentCookieJars } from '../hooks/useRecentCookieJars';
|
||||
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { getRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export const switchWorkspace = createFastMutation({
|
||||
mutationKey: ['open_workspace'],
|
||||
mutationFn: async ({
|
||||
workspaceId,
|
||||
inNewWindow,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
inNewWindow: boolean;
|
||||
}) => {
|
||||
const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? undefined;
|
||||
const requestId = (await getRecentRequests(workspaceId))[0] ?? undefined;
|
||||
const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? undefined;
|
||||
const search = {
|
||||
environment_id: environmentId,
|
||||
cookie_jar_id: cookieJarId,
|
||||
request_id: requestId,
|
||||
};
|
||||
|
||||
if (inNewWindow) {
|
||||
const location = router.buildLocation({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId },
|
||||
search,
|
||||
});
|
||||
await invokeCmd<void>('cmd_new_main_window', { url: location.href });
|
||||
return;
|
||||
}
|
||||
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId },
|
||||
search,
|
||||
});
|
||||
},
|
||||
});
|
||||
19
src-web/commands/upsertWorkspace.ts
Normal file
19
src-web/commands/upsertWorkspace.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export const upsertWorkspace = createFastMutation<
|
||||
Workspace,
|
||||
void,
|
||||
Workspace | Partial<Omit<Workspace, 'id'>>
|
||||
>({
|
||||
mutationKey: ['upsert_workspace'],
|
||||
mutationFn: (workspace) => invokeCmd<Workspace>('cmd_update_workspace', { workspace }),
|
||||
onSuccess: async (workspace) => {
|
||||
const isNew = workspace.createdAt == workspace.updatedAt;
|
||||
|
||||
if (isNew) trackEvent('workspace', 'create');
|
||||
else trackEvent('workspace', 'update');
|
||||
},
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import { invokeCmd } from '../lib/tauri';
|
||||
export const upsertWorkspaceMeta = createFastMutation<
|
||||
WorkspaceMeta,
|
||||
unknown,
|
||||
Partial<WorkspaceMeta>
|
||||
WorkspaceMeta | (Partial<Omit<WorkspaceMeta, 'id'>> & { workspaceId: string })
|
||||
>({
|
||||
mutationKey: ['update_workspace_meta'],
|
||||
mutationFn: async (patch) => {
|
||||
|
||||
@@ -2,6 +2,8 @@ import classNames from 'classnames';
|
||||
import { fuzzyFilter } from 'fuzzbunny';
|
||||
import type { KeyboardEvent, ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createFolder } from '../commands/commands';
|
||||
import { switchWorkspace } from '../commands/switchWorkspace';
|
||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
@@ -16,7 +18,6 @@ import type { HotkeyAction } from '../hooks/useHotKey';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useHttpRequestActions } from '../hooks/useHttpRequestActions';
|
||||
import { useOpenSettings } from '../hooks/useOpenSettings';
|
||||
import { useSwitchWorkspace } from '../hooks/useSwitchWorkspace';
|
||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
@@ -38,7 +39,6 @@ import { Icon } from './core/Icon';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||
import { createFolder } from '../commands/commands';
|
||||
|
||||
interface CommandPaletteGroup {
|
||||
key: string;
|
||||
@@ -71,7 +71,6 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
const [, setSidebarHidden] = useSidebarHidden();
|
||||
const { baseEnvironment } = useEnvironments();
|
||||
const { mutate: openSettings } = useOpenSettings();
|
||||
const { mutate: switchWorkspace } = useSwitchWorkspace();
|
||||
const { mutate: createHttpRequest } = useCreateHttpRequest();
|
||||
const { mutate: createGrpcRequest } = useCreateGrpcRequest();
|
||||
const { mutate: createEnvironment } = useCreateEnvironment();
|
||||
@@ -315,7 +314,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
workspaceGroup.items.push({
|
||||
key: `switch-workspace-${w.id}`,
|
||||
label: w.name,
|
||||
onSelect: () => switchWorkspace({ workspaceId: w.id, inNewWindow: false }),
|
||||
onSelect: () => switchWorkspace.mutate({ workspaceId: w.id, inNewWindow: false }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,7 +326,6 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
activeEnvironment?.id,
|
||||
setActiveEnvironmentId,
|
||||
sortedWorkspaces,
|
||||
switchWorkspace,
|
||||
]);
|
||||
|
||||
const allItems = useMemo(() => groups.flatMap((g) => g.items), [groups]);
|
||||
|
||||
@@ -76,7 +76,7 @@ export const CookieDropdown = memo(function CookieDropdown() {
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
variant: 'danger',
|
||||
color: 'danger',
|
||||
onSelect: () => deleteCookieJar.mutateAsync(),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { createWorkspace } from '../commands/commands';
|
||||
import { upsertWorkspace } from '../commands/upsertWorkspace';
|
||||
import { upsertWorkspaceMeta } from '../commands/upsertWorkspaceMeta';
|
||||
import { router } from '../lib/router';
|
||||
import { getWorkspaceMeta } from '../lib/store';
|
||||
import { Button } from './core/Button';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { VStack } from './core/Stacks';
|
||||
@@ -26,7 +29,20 @@ export function CreateWorkspaceDialog({ hide }: Props) {
|
||||
e.preventDefault();
|
||||
const { enabled, value } = settingSyncDir ?? {};
|
||||
if (enabled && !value) return;
|
||||
await createWorkspace.mutateAsync({ name, settingSyncDir: value });
|
||||
const workspace = await upsertWorkspace.mutateAsync({ name });
|
||||
if (workspace == null) return;
|
||||
|
||||
// Do getWorkspaceMeta instead of naively creating one because it might have
|
||||
// been created already when the store refreshes the workspace meta after
|
||||
const workspaceMeta = await getWorkspaceMeta(workspace.id);
|
||||
upsertWorkspaceMeta.mutate({ ...workspaceMeta, settingSyncDir: value });
|
||||
|
||||
// Navigate to workspace
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: workspace.id },
|
||||
});
|
||||
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -278,7 +278,7 @@ function SidebarButton({
|
||||
},
|
||||
{
|
||||
key: 'delete-environment',
|
||||
variant: 'danger',
|
||||
color: 'danger',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" size="sm" />,
|
||||
onSelect: () => deleteEnvironment.mutate(),
|
||||
|
||||
@@ -79,7 +79,7 @@ export function GraphQLEditor({ request, onChange, baseRequest, ...extraEditorPr
|
||||
label: 'Clear',
|
||||
onSelect: clear,
|
||||
hidden: !schema,
|
||||
variant: 'danger',
|
||||
color: 'danger',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
},
|
||||
{ type: 'separator', label: 'Setting' },
|
||||
|
||||
@@ -156,7 +156,9 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
||||
) : (
|
||||
<KeyValueRows>
|
||||
{Object.entries(activeEvent.metadata).map(([key, value]) => (
|
||||
<KeyValueRow key={key} label={key} value={value} />
|
||||
<KeyValueRow key={key} label={key}>
|
||||
{value}
|
||||
</KeyValueRow>
|
||||
))}
|
||||
</KeyValueRows>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,9 @@ export function ResponseHeaders({ response }: Props) {
|
||||
<div className="overflow-auto h-full pb-4">
|
||||
<KeyValueRows>
|
||||
{response.headers.map((h, i) => (
|
||||
<KeyValueRow labelColor="primary" key={i} label={h.name} value={h.value} />
|
||||
<KeyValueRow labelColor="primary" key={i} label={h.name}>
|
||||
{h.value}
|
||||
</KeyValueRow>
|
||||
))}
|
||||
</KeyValueRows>
|
||||
</div>
|
||||
|
||||
@@ -11,8 +11,12 @@ export function ResponseInfo({ response }: Props) {
|
||||
return (
|
||||
<div className="overflow-auto h-full pb-4">
|
||||
<KeyValueRows>
|
||||
<KeyValueRow labelColor="info" label="Version" value={response.version} />
|
||||
<KeyValueRow labelColor="info" label="Remote Address" value={response.remoteAddr} />
|
||||
<KeyValueRow labelColor="info" label="Version">
|
||||
{response.version}
|
||||
</KeyValueRow>
|
||||
<KeyValueRow labelColor="info" label="Remote Address">
|
||||
{response.remoteAddr}
|
||||
</KeyValueRow>
|
||||
<KeyValueRow
|
||||
labelColor="info"
|
||||
label={
|
||||
@@ -27,12 +31,13 @@ export function ResponseInfo({ response }: Props) {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
value={
|
||||
>
|
||||
{
|
||||
<div className="flex">
|
||||
<span className="select-text cursor-text">{response.url}</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</KeyValueRow>
|
||||
</KeyValueRows>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||
import React from 'react';
|
||||
import { upsertWorkspace } from '../../commands/upsertWorkspace';
|
||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||
import { useAppInfo } from '../../hooks/useAppInfo';
|
||||
import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||
import { useUpdateWorkspace } from '../../hooks/useUpdateWorkspace';
|
||||
import { revealInFinderText } from '../../lib/reveal';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { Heading } from '../core/Heading';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
@@ -16,7 +18,6 @@ import { VStack } from '../core/Stacks';
|
||||
|
||||
export function SettingsGeneral() {
|
||||
const workspace = useActiveWorkspace();
|
||||
const updateWorkspace = useUpdateWorkspace(workspace?.id ?? null);
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const appInfo = useAppInfo();
|
||||
@@ -103,7 +104,9 @@ export function SettingsGeneral() {
|
||||
labelPosition="left"
|
||||
defaultValue={`${workspace.settingRequestTimeout}`}
|
||||
validate={(value) => parseInt(value) >= 0}
|
||||
onChange={(v) => updateWorkspace.mutate({ settingRequestTimeout: parseInt(v) || 0 })}
|
||||
onChange={(v) =>
|
||||
upsertWorkspace.mutate({ ...workspace, settingRequestTimeout: parseInt(v) || 0 })
|
||||
}
|
||||
type="number"
|
||||
/>
|
||||
|
||||
@@ -112,7 +115,7 @@ export function SettingsGeneral() {
|
||||
title="Validate TLS Certificates"
|
||||
event="validate-certs"
|
||||
onChange={(settingValidateCertificates) =>
|
||||
updateWorkspace.mutate({ settingValidateCertificates })
|
||||
upsertWorkspace.mutate({ ...workspace, settingValidateCertificates })
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -120,7 +123,12 @@ export function SettingsGeneral() {
|
||||
checked={workspace.settingFollowRedirects}
|
||||
title="Follow Redirects"
|
||||
event="follow-redirects"
|
||||
onChange={(settingFollowRedirects) => updateWorkspace.mutate({ settingFollowRedirects })}
|
||||
onChange={(settingFollowRedirects) =>
|
||||
upsertWorkspace.mutate({
|
||||
...workspace,
|
||||
settingFollowRedirects,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</VStack>
|
||||
|
||||
@@ -128,9 +136,33 @@ export function SettingsGeneral() {
|
||||
|
||||
<Heading size={2}>App Info</Heading>
|
||||
<KeyValueRows>
|
||||
<KeyValueRow label="Version" value={appInfo.version} />
|
||||
<KeyValueRow label="Data Directory" value={appInfo.appDataDir} />
|
||||
<KeyValueRow label="Logs Directory" value={appInfo.appLogDir} />
|
||||
<KeyValueRow label="Version">{appInfo.version}</KeyValueRow>
|
||||
<KeyValueRow
|
||||
label="Data Directory"
|
||||
rightSlot={
|
||||
<IconButton
|
||||
title={revealInFinderText}
|
||||
icon="folder_open"
|
||||
size="2xs"
|
||||
onClick={() => revealItemInDir(appInfo.appDataDir)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{appInfo.appDataDir}
|
||||
</KeyValueRow>
|
||||
<KeyValueRow
|
||||
label="Logs Directory"
|
||||
rightSlot={
|
||||
<IconButton
|
||||
title={revealInFinderText}
|
||||
icon="folder_open"
|
||||
size="2xs"
|
||||
onClick={() => revealItemInDir(appInfo.appLogDir)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{appInfo.appLogDir}
|
||||
</KeyValueRow>
|
||||
</KeyValueRows>
|
||||
</VStack>
|
||||
);
|
||||
|
||||
@@ -72,7 +72,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
{
|
||||
key: 'delete-folder',
|
||||
label: 'Delete',
|
||||
variant: 'danger',
|
||||
color: 'danger',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: () => deleteFolder.mutate(),
|
||||
},
|
||||
@@ -132,7 +132,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
},
|
||||
{
|
||||
key: 'delete-request',
|
||||
variant: 'danger',
|
||||
color: 'danger',
|
||||
label: 'Delete',
|
||||
hotKeyAction: 'http_request.delete',
|
||||
hotKeyLabelOnly: true,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { useState } from 'react';
|
||||
import { useSwitchWorkspace } from '../hooks/useSwitchWorkspace';
|
||||
import { switchWorkspace } from '../commands/switchWorkspace';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { Button } from './core/Button';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import { Icon } from './core/Icon';
|
||||
@@ -15,7 +15,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
||||
const switchWorkspace = useSwitchWorkspace();
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const [remember, setRemember] = useState<boolean>(false);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import {openWorkspace} from "../commands/openWorkspace";
|
||||
import { openWorkspaceFromSyncDir } from '../commands/openWorkspaceFromSyncDir';
|
||||
import { switchWorkspace } from '../commands/switchWorkspace';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
|
||||
import { useSwitchWorkspace } from '../hooks/useSwitchWorkspace';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { settingsAtom } from '../hooks/useSettings';
|
||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { revealInFinderText } from '../lib/reveal';
|
||||
import { getWorkspace } from '../lib/store';
|
||||
import type { ButtonProps } from './core/Button';
|
||||
import { Button } from './core/Button';
|
||||
@@ -25,12 +29,10 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
...buttonProps
|
||||
}: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const workspace = useActiveWorkspace();
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const workspaceMeta = useWorkspaceMeta();
|
||||
const { mutate: deleteSendHistory } = useDeleteSendHistory();
|
||||
const settings = useSettings();
|
||||
const switchWorkspace = useSwitchWorkspace();
|
||||
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
|
||||
|
||||
const orderedWorkspaces = useMemo(
|
||||
() => [...workspaces].sort((a, b) => (a.name.localeCompare(b.name) > 0 ? 1 : -1)),
|
||||
@@ -45,7 +47,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
key: w.id,
|
||||
label: w.name,
|
||||
value: w.id,
|
||||
leftSlot: w.id === activeWorkspace?.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
leftSlot: w.id === workspace?.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
}));
|
||||
|
||||
const extraItems: DropdownItem[] = [
|
||||
@@ -60,14 +62,25 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
title: 'Workspace Settings',
|
||||
size: 'md',
|
||||
render: ({ hide }) => (
|
||||
<WorkspaceSettingsDialog workspaceId={activeWorkspace?.id ?? null} hide={hide} />
|
||||
<WorkspaceSettingsDialog workspaceId={workspace?.id ?? null} hide={hide} />
|
||||
),
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'reveal-workspace-sync-dir',
|
||||
label: revealInFinderText,
|
||||
hidden: workspaceMeta == null || workspaceMeta.settingSyncDir == null,
|
||||
leftSlot: <Icon icon="folder_open" />,
|
||||
onSelect: async () => {
|
||||
if (workspaceMeta?.settingSyncDir == null) return;
|
||||
await revealItemInDir(workspaceMeta.settingSyncDir);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete-responses',
|
||||
label: 'Clear Send History',
|
||||
color: 'warning',
|
||||
leftSlot: <Icon icon="history" />,
|
||||
onSelect: deleteSendHistory,
|
||||
},
|
||||
@@ -82,52 +95,50 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
key: 'open-workspace',
|
||||
label: 'Open Workspace',
|
||||
leftSlot: <Icon icon="folder" />,
|
||||
onSelect: openWorkspace.mutate,
|
||||
onSelect: openWorkspaceFromSyncDir.mutate,
|
||||
},
|
||||
];
|
||||
|
||||
return { workspaceItems, extraItems };
|
||||
}, [orderedWorkspaces, activeWorkspace?.id, deleteSendHistory, createWorkspace]);
|
||||
}, [orderedWorkspaces, deleteSendHistory, createWorkspace, workspaceMeta, workspace?.id]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
async (workspaceId: string | null) => {
|
||||
if (workspaceId == null) return;
|
||||
const handleChangeWorkspace = useCallback(async (workspaceId: string | null) => {
|
||||
if (workspaceId == null) return;
|
||||
|
||||
if (typeof openWorkspaceNewWindow === 'boolean') {
|
||||
switchWorkspace.mutate({ workspaceId, inNewWindow: openWorkspaceNewWindow });
|
||||
return;
|
||||
}
|
||||
const settings = jotaiStore.get(settingsAtom);
|
||||
if (typeof settings.openWorkspaceNewWindow === 'boolean') {
|
||||
switchWorkspace.mutate({ workspaceId, inNewWindow: settings.openWorkspaceNewWindow });
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = await getWorkspace(workspaceId);
|
||||
if (workspace == null) return;
|
||||
const workspace = await getWorkspace(workspaceId);
|
||||
if (workspace == null) return;
|
||||
|
||||
showDialog({
|
||||
id: 'switch-workspace',
|
||||
size: 'sm',
|
||||
title: 'Switch Workspace',
|
||||
render: ({ hide }) => <SwitchWorkspaceDialog workspace={workspace} hide={hide} />,
|
||||
});
|
||||
},
|
||||
[switchWorkspace, openWorkspaceNewWindow],
|
||||
);
|
||||
showDialog({
|
||||
id: 'switch-workspace',
|
||||
size: 'sm',
|
||||
title: 'Switch Workspace',
|
||||
render: ({ hide }) => <SwitchWorkspaceDialog workspace={workspace} hide={hide} />,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<RadioDropdown
|
||||
items={workspaceItems}
|
||||
extraItems={extraItems}
|
||||
onChange={handleChange}
|
||||
value={activeWorkspace?.id ?? null}
|
||||
onChange={handleChangeWorkspace}
|
||||
value={workspace?.id ?? null}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
className={classNames(
|
||||
className,
|
||||
'text !px-2 truncate',
|
||||
activeWorkspace === null && 'italic opacity-disabled',
|
||||
workspace === null && 'italic opacity-disabled',
|
||||
)}
|
||||
{...buttonProps}
|
||||
>
|
||||
{activeWorkspace?.name ?? 'Workspace'}
|
||||
{workspace?.name ?? 'Workspace'}
|
||||
</Button>
|
||||
</RadioDropdown>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { upsertWorkspace } from '../commands/upsertWorkspace';
|
||||
import { upsertWorkspaceMeta } from '../commands/upsertWorkspaceMeta';
|
||||
import { useDeleteActiveWorkspace } from '../hooks/useDeleteActiveWorkspace';
|
||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { Banner } from './core/Banner';
|
||||
@@ -21,7 +21,6 @@ export function WorkspaceSettingsDialog({ workspaceId, hide }: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const workspace = workspaces.find((w) => w.id === workspaceId);
|
||||
const workspaceMeta = useWorkspaceMeta();
|
||||
const { mutate: updateWorkspace } = useUpdateWorkspace(workspaceId ?? null);
|
||||
const { mutateAsync: deleteActiveWorkspace } = useDeleteActiveWorkspace();
|
||||
|
||||
if (workspace == null) {
|
||||
@@ -44,7 +43,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide }: Props) {
|
||||
<Input
|
||||
label="Workspace Name"
|
||||
defaultValue={workspace.name}
|
||||
onChange={(name) => updateWorkspace({ name })}
|
||||
onChange={(name) => upsertWorkspace.mutate({ ...workspace, name })}
|
||||
stateKey={`name.${workspace.id}`}
|
||||
/>
|
||||
|
||||
@@ -54,7 +53,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide }: Props) {
|
||||
className="min-h-[10rem] max-h-[25rem] border border-border px-2"
|
||||
defaultValue={workspace.description}
|
||||
stateKey={`description.${workspace.id}`}
|
||||
onChange={(description) => updateWorkspace({ description })}
|
||||
onChange={(description) => upsertWorkspace.mutate({ ...workspace, description })}
|
||||
heightMode="auto"
|
||||
/>
|
||||
|
||||
@@ -62,7 +61,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide }: Props) {
|
||||
<SyncToFilesystemSetting
|
||||
value={workspaceMeta.settingSyncDir}
|
||||
onChange={({ value: settingSyncDir }) => {
|
||||
upsertWorkspaceMeta.mutate({ settingSyncDir });
|
||||
upsertWorkspaceMeta.mutate({ ...workspaceMeta, settingSyncDir });
|
||||
}}
|
||||
/>
|
||||
<Separator />
|
||||
|
||||
@@ -20,7 +20,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {useClickAway, useKey, useWindowSize} from 'react-use';
|
||||
import { useClickAway, useKey, useWindowSize } from 'react-use';
|
||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||
import { useHotKey } from '../../hooks/useHotKey';
|
||||
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
||||
@@ -45,7 +45,7 @@ export type DropdownItemDefault = {
|
||||
keepOpen?: boolean;
|
||||
hotKeyAction?: HotkeyAction;
|
||||
hotKeyLabelOnly?: boolean;
|
||||
variant?: 'default' | 'danger' | 'notify';
|
||||
color?: 'default' | 'danger' | 'info' | 'warning' | 'notice';
|
||||
disabled?: boolean;
|
||||
hidden?: boolean;
|
||||
leftSlot?: ReactNode;
|
||||
@@ -558,8 +558,10 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
'h-xs', // More compact
|
||||
'min-w-[8rem] outline-none px-2 mx-1.5 flex whitespace-nowrap',
|
||||
'focus:bg-surface-highlight focus:text rounded',
|
||||
item.variant === 'danger' && '!text-danger',
|
||||
item.variant === 'notify' && '!text-primary',
|
||||
item.color === 'danger' && '!text-danger',
|
||||
item.color === 'warning' && '!text-warning',
|
||||
item.color === 'notice' && '!text-notice',
|
||||
item.color === 'info' && '!text-info',
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -42,9 +42,10 @@ const icons = {
|
||||
filter: lucide.FilterIcon,
|
||||
flask: lucide.FlaskConicalIcon,
|
||||
folder: lucide.FolderIcon,
|
||||
folder_sync: lucide.FolderSyncIcon,
|
||||
folder_input: lucide.FolderInputIcon,
|
||||
folder_open: lucide.FolderOpenIcon,
|
||||
folder_output: lucide.FolderOutputIcon,
|
||||
folder_sync: lucide.FolderSyncIcon,
|
||||
git_branch: lucide.GitBranchIcon,
|
||||
git_commit: lucide.GitCommitIcon,
|
||||
git_commit_vertical: lucide.GitCommitVerticalIcon,
|
||||
|
||||
@@ -22,14 +22,18 @@ export function KeyValueRows({ children }: Props) {
|
||||
|
||||
interface KeyValueRowProps {
|
||||
label: ReactNode;
|
||||
value: ReactNode;
|
||||
children: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
leftSlot?: ReactNode;
|
||||
labelClassName?: string;
|
||||
labelColor?: 'secondary' | 'primary' | 'info';
|
||||
}
|
||||
|
||||
export function KeyValueRow({
|
||||
label,
|
||||
value,
|
||||
children,
|
||||
rightSlot,
|
||||
leftSlot,
|
||||
labelColor = 'secondary',
|
||||
labelClassName,
|
||||
}: KeyValueRowProps) {
|
||||
@@ -47,7 +51,11 @@ export function KeyValueRow({
|
||||
<span className="select-text cursor-text">{label}</span>
|
||||
</td>
|
||||
<td className="select-none py-0.5 break-all align-top max-w-[15rem]">
|
||||
<div className="select-text cursor-text max-h-[5rem] overflow-y-auto">{value}</div>
|
||||
<div className="select-text cursor-text max-h-[5rem] overflow-y-auto grid grid-cols-[auto_minmax(0,1fr)_auto]">
|
||||
{leftSlot ?? <span aria-hidden />}
|
||||
{children}
|
||||
{rightSlot ? <div className="ml-1.5">{rightSlot}</div> : <span aria-hidden />}
|
||||
</div>
|
||||
</td>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -344,7 +344,7 @@ function PairEditorRow({
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
onSelect: handleDelete,
|
||||
variant: 'danger',
|
||||
color: 'danger',
|
||||
},
|
||||
],
|
||||
[handleDelete],
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { getRecentCookieJars } from './useRecentCookieJars';
|
||||
import { getRecentEnvironments } from './useRecentEnvironments';
|
||||
import { getRecentRequests } from './useRecentRequests';
|
||||
|
||||
export function useSwitchWorkspace() {
|
||||
return useFastMutation({
|
||||
mutationKey: ['open_workspace'],
|
||||
mutationFn: async ({
|
||||
workspaceId,
|
||||
inNewWindow,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
inNewWindow: boolean;
|
||||
}) => {
|
||||
const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? undefined;
|
||||
const requestId = (await getRecentRequests(workspaceId))[0] ?? undefined;
|
||||
const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? undefined;
|
||||
const search = {
|
||||
environment_id: environmentId,
|
||||
cookie_jar_id: cookieJarId,
|
||||
request_id: requestId,
|
||||
};
|
||||
|
||||
if (inNewWindow) {
|
||||
const location = router.buildLocation({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId },
|
||||
search,
|
||||
});
|
||||
await invokeCmd('cmd_new_main_window', { url: location.href });
|
||||
return;
|
||||
}
|
||||
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId },
|
||||
search,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
||||
import { useEffect } from 'react';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { getWorkspaceMeta } from '../lib/store';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { activeWorkspaceIdAtom, getActiveWorkspaceId } from './useActiveWorkspace';
|
||||
import { cookieJarsAtom } from './useCookieJars';
|
||||
@@ -26,10 +26,9 @@ async function sync() {
|
||||
jotaiStore.set(keyValuesAtom, await invokeCmd('cmd_list_key_values'));
|
||||
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
if (workspaceId == null) return;
|
||||
|
||||
const args = { workspaceId };
|
||||
if (workspaceId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the things we need first, first
|
||||
jotaiStore.set(httpRequestsAtom, await invokeCmd('cmd_list_http_requests', args));
|
||||
@@ -43,5 +42,5 @@ async function sync() {
|
||||
jotaiStore.set(environmentsAtom, await invokeCmd('cmd_list_environments', args));
|
||||
|
||||
// Single models
|
||||
jotaiStore.set(workspaceMetaAtom, await invokeCmd<WorkspaceMeta>('cmd_get_workspace_meta', args));
|
||||
jotaiStore.set(workspaceMetaAtom, await getWorkspaceMeta(workspaceId));
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { getWorkspace } from '../lib/store';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useUpdateWorkspace(id: string | null) {
|
||||
return useFastMutation<Workspace, unknown, Partial<Workspace> | ((w: Workspace) => Workspace)>({
|
||||
mutationKey: ['update_workspace', id],
|
||||
mutationFn: async (v) => {
|
||||
const workspace = await getWorkspace(id);
|
||||
if (workspace == null) {
|
||||
throw new Error("Can't update a null workspace");
|
||||
}
|
||||
|
||||
const newWorkspace = typeof v === 'function' ? v(workspace) : { ...workspace, ...v };
|
||||
return invokeCmd('cmd_update_workspace', { workspace: newWorkspace });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,13 +1,8 @@
|
||||
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
|
||||
export const workspaceMetaAtom = atom<WorkspaceMeta>();
|
||||
export const workspaceMetaAtom = atom<WorkspaceMeta | null>(null);
|
||||
|
||||
export function useWorkspaceMeta() {
|
||||
const workspaceMeta = useAtomValue(workspaceMetaAtom);
|
||||
if (!workspaceMeta) {
|
||||
throw new Error('WorkspaceMeta not found');
|
||||
}
|
||||
|
||||
return workspaceMeta;
|
||||
return useAtomValue(workspaceMetaAtom);
|
||||
}
|
||||
|
||||
9
src-web/lib/reveal.ts
Normal file
9
src-web/lib/reveal.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
|
||||
const os = type();
|
||||
export const revealInFinderText =
|
||||
os === 'macos'
|
||||
? 'Reveal in Finder'
|
||||
: os === 'windows'
|
||||
? 'Show in Explorer'
|
||||
: 'Show in File Manager';
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
Plugin,
|
||||
Settings,
|
||||
Workspace,
|
||||
WorkspaceMeta,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { invokeCmd } from './tauri';
|
||||
|
||||
@@ -59,6 +60,10 @@ export async function getWorkspace(id: string | null): Promise<Workspace | null>
|
||||
return workspace;
|
||||
}
|
||||
|
||||
export async function getWorkspaceMeta(workspaceId: string) {
|
||||
return invokeCmd<WorkspaceMeta>('cmd_get_workspace_meta', { workspaceId });
|
||||
}
|
||||
|
||||
export async function listWorkspaces(): Promise<Workspace[]> {
|
||||
const workspaces: Workspace[] = (await invokeCmd('cmd_list_workspaces')) ?? [];
|
||||
return workspaces;
|
||||
|
||||
Reference in New Issue
Block a user