mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-18 23:43:55 +01:00
Filesystem Sync (#142)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { PromptTextRequest } from '@yaakapp-internal/plugin';
|
||||
import type { PromptTextRequest } from '@yaakapp-internal/plugins';
|
||||
import type { FormEvent, ReactNode } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from '../components/core/Button';
|
||||
|
||||
84
src-web/hooks/useCommands.ts
Normal file
84
src-web/hooks/useCommands.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import type { Folder, Workspace } from '@yaakapp-internal/models';
|
||||
import { useMemo } from 'react';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveWorkspaceId } from './useActiveWorkspace';
|
||||
import { createFastMutation } from './useFastMutation';
|
||||
import { foldersAtom } from './useFolders';
|
||||
import { usePrompt } from './usePrompt';
|
||||
import { updateModelList } from './useSyncModelStores';
|
||||
import { useToast } from './useToast';
|
||||
import { workspacesAtom } from './useWorkspaces';
|
||||
|
||||
function makeCommands({
|
||||
navigate,
|
||||
prompt,
|
||||
}: {
|
||||
navigate: ReturnType<typeof useNavigate>;
|
||||
prompt: ReturnType<typeof usePrompt>;
|
||||
toast: ReturnType<typeof useToast>;
|
||||
}) {
|
||||
return {
|
||||
createWorkspace: createFastMutation<Workspace, void, Partial<Workspace>>({
|
||||
mutationKey: ['create_workspace'],
|
||||
mutationFn: (patch) => invokeCmd<Workspace>('cmd_update_workspace', { workspace: patch }),
|
||||
onSuccess: async (workspace) => {
|
||||
// Optimistic update
|
||||
jotaiStore.set(workspacesAtom, updateModelList(workspace));
|
||||
|
||||
await navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: workspace.id },
|
||||
});
|
||||
},
|
||||
onSettled: () => trackEvent('workspace', 'create'),
|
||||
}),
|
||||
|
||||
createFolder: createFastMutation<
|
||||
Folder | null,
|
||||
void,
|
||||
Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>
|
||||
>({
|
||||
mutationKey: ['create_folder'],
|
||||
mutationFn: async (patch) => {
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
if (workspaceId == null) {
|
||||
throw new Error("Cannot create folder when there's no active workspace");
|
||||
}
|
||||
|
||||
if (!patch.name) {
|
||||
const name = await prompt({
|
||||
id: 'new-folder',
|
||||
label: 'Name',
|
||||
defaultValue: 'Folder',
|
||||
title: 'New Folder',
|
||||
confirmText: 'Create',
|
||||
placeholder: 'Name',
|
||||
});
|
||||
if (name == null) return null;
|
||||
|
||||
patch.name = name;
|
||||
}
|
||||
|
||||
patch.sortPriority = patch.sortPriority || -Date.now();
|
||||
return invokeCmd<Folder>('cmd_update_folder', { folder: { workspaceId, ...patch } });
|
||||
},
|
||||
onSuccess: async (folder) => {
|
||||
if (folder == null) return;
|
||||
|
||||
// Optimistic update
|
||||
jotaiStore.set(foldersAtom, updateModelList(folder));
|
||||
},
|
||||
onSettled: () => trackEvent('folder', 'create'),
|
||||
}),
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function useCommands() {
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
const prompt = usePrompt();
|
||||
return useMemo(() => makeCommands({ navigate, toast, prompt }), [navigate, prompt, toast]);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Icon } from '../components/core/Icon';
|
||||
import { generateId } from '../lib/generateId';
|
||||
import { BODY_TYPE_GRAPHQL } from '../lib/model_util';
|
||||
import { getActiveRequest } from './useActiveRequest';
|
||||
import { useCreateFolder } from './useCreateFolder';
|
||||
import {useCommands} from "./useCommands";
|
||||
import { useCreateGrpcRequest } from './useCreateGrpcRequest';
|
||||
import { useCreateHttpRequest } from './useCreateHttpRequest';
|
||||
|
||||
@@ -19,7 +19,7 @@ export function useCreateDropdownItems({
|
||||
} = {}): () => DropdownItem[] {
|
||||
const { mutate: createHttpRequest } = useCreateHttpRequest();
|
||||
const { mutate: createGrpcRequest } = useCreateGrpcRequest();
|
||||
const { mutate: createFolder } = useCreateFolder();
|
||||
const { createFolder } = useCommands();
|
||||
|
||||
return useCallback((): DropdownItem[] => {
|
||||
const folderId =
|
||||
@@ -62,7 +62,7 @@ export function useCreateDropdownItems({
|
||||
key: 'create-folder',
|
||||
label: 'Folder',
|
||||
leftSlot: hideIcons ? undefined : <Icon icon="plus" />,
|
||||
onSelect: () => createFolder({ folderId }),
|
||||
onSelect: () => createFolder.mutate({ folderId }),
|
||||
},
|
||||
]) as DropdownItem[]),
|
||||
];
|
||||
|
||||
@@ -15,7 +15,6 @@ export function useCreateEnvironment() {
|
||||
const setEnvironments = useSetAtom(environmentsAtom);
|
||||
|
||||
return useFastMutation<Environment | null, unknown, Environment | null>({
|
||||
toastyError: true,
|
||||
mutationKey: ['create_environment'],
|
||||
mutationFn: async (baseEnvironment) => {
|
||||
if (baseEnvironment == null) {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { Folder } from '@yaakapp-internal/models';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveWorkspaceId } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { foldersAtom } from './useFolders';
|
||||
import { usePrompt } from './usePrompt';
|
||||
import { updateModelList } from './useSyncModelStores';
|
||||
|
||||
export function useCreateFolder() {
|
||||
const prompt = usePrompt();
|
||||
const setFolders = useSetAtom(foldersAtom);
|
||||
|
||||
return useFastMutation<
|
||||
Folder | null,
|
||||
unknown,
|
||||
Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>
|
||||
>({
|
||||
mutationKey: ['create_folder'],
|
||||
mutationFn: async (patch) => {
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
if (workspaceId == null) {
|
||||
throw new Error("Cannot create folder when there's no active workspace");
|
||||
}
|
||||
|
||||
if (!patch.name) {
|
||||
const name = await prompt({
|
||||
id: 'new-folder',
|
||||
label: 'Name',
|
||||
defaultValue: 'Folder',
|
||||
title: 'New Folder',
|
||||
confirmText: 'Create',
|
||||
placeholder: 'Name',
|
||||
});
|
||||
if (name == null) return null;
|
||||
|
||||
patch.name = name;
|
||||
}
|
||||
|
||||
patch.sortPriority = patch.sortPriority || -Date.now();
|
||||
return await invokeCmd('cmd_create_folder', { workspaceId, ...patch });
|
||||
},
|
||||
onSuccess: (folder) => {
|
||||
if (folder == null) return;
|
||||
|
||||
// Optimistic update
|
||||
setFolders(updateModelList(folder));
|
||||
},
|
||||
onSettled: () => trackEvent('folder', 'create'),
|
||||
});
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { useSetAtom } from 'jotai/index';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { usePrompt } from './usePrompt';
|
||||
import { updateModelList } from './useSyncModelStores';
|
||||
import { workspacesAtom } from './useWorkspaces';
|
||||
|
||||
export function useCreateWorkspace() {
|
||||
const prompt = usePrompt();
|
||||
const setWorkspaces = useSetAtom(workspacesAtom);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useFastMutation<Workspace | null, void, void>({
|
||||
mutationKey: ['create_workspace'],
|
||||
mutationFn: async () => {
|
||||
const name = await prompt({
|
||||
id: 'new-workspace',
|
||||
label: 'Name',
|
||||
defaultValue: 'My Workspace',
|
||||
title: 'New Workspace',
|
||||
placeholder: 'My Workspace',
|
||||
confirmText: 'Create',
|
||||
});
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
return invokeCmd<Workspace>('cmd_create_workspace', { name });
|
||||
},
|
||||
onSuccess: async (workspace) => {
|
||||
if (workspace == null) return;
|
||||
|
||||
// Optimistic update
|
||||
setWorkspaces(updateModelList(workspace));
|
||||
|
||||
navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: workspace.id },
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
16
src-web/hooks/useCreateWorkspace.tsx
Normal file
16
src-web/hooks/useCreateWorkspace.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useCallback } from 'react';
|
||||
import { CreateWorkspaceDialog } from '../components/CreateWorkspaceDialog';
|
||||
import { useDialog } from './useDialog';
|
||||
|
||||
export function useCreateWorkspace() {
|
||||
const dialog = useDialog();
|
||||
|
||||
return useCallback(() => {
|
||||
dialog.show({
|
||||
id: 'create-workspace',
|
||||
title: 'Create Workspace',
|
||||
size: 'md',
|
||||
render: ({ hide }) => <CreateWorkspaceDialog hide={hide} />,
|
||||
});
|
||||
}, [dialog]);
|
||||
}
|
||||
@@ -1,59 +1,58 @@
|
||||
import type { MutationKey } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
import { useToast } from './useToast';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function useFastMutation<TData = unknown, TError = unknown, TVariables = void>({
|
||||
mutationKey,
|
||||
mutationFn,
|
||||
onSuccess,
|
||||
onError,
|
||||
onSettled,
|
||||
toastyError,
|
||||
}: {
|
||||
interface MutationOptions<TData, TError, TVariables> {
|
||||
mutationKey: MutationKey;
|
||||
mutationFn: (vars: TVariables) => Promise<TData>;
|
||||
onSettled?: () => void;
|
||||
onError?: (err: TError) => void;
|
||||
onSuccess?: (data: TData) => void;
|
||||
toastyError?: boolean;
|
||||
}) {
|
||||
const toast = useToast();
|
||||
|
||||
const mutateAsync = useCallback(
|
||||
async (variables: TVariables) => {
|
||||
try {
|
||||
const data = await mutationFn(variables);
|
||||
onSuccess?.(data);
|
||||
return data;
|
||||
} catch (err: unknown) {
|
||||
const e = err as TError;
|
||||
console.log('Fast mutation error', mutationKey, e);
|
||||
onError?.(e);
|
||||
if (toastyError) {
|
||||
toast.show({
|
||||
id: 'error-' + mutationKey.join('.'),
|
||||
color: 'danger',
|
||||
timeout: 8000,
|
||||
message: String(e),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
onSettled?.();
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
mutationKey,
|
||||
);
|
||||
|
||||
const mutate = useCallback(
|
||||
(variables: TVariables) => {
|
||||
setTimeout(() => mutateAsync(variables));
|
||||
},
|
||||
[mutateAsync],
|
||||
);
|
||||
|
||||
return {
|
||||
mutate,
|
||||
mutateAsync,
|
||||
};
|
||||
}
|
||||
|
||||
type CallbackMutationOptions<TData, TError, TVariables> = Omit<
|
||||
MutationOptions<TData, TError, TVariables>,
|
||||
'mutationKey' | 'mutationFn'
|
||||
>;
|
||||
|
||||
export function createFastMutation<TData = unknown, TError = unknown, TVariables = void>(
|
||||
defaultArgs: MutationOptions<TData, TError, TVariables>,
|
||||
) {
|
||||
const mutateAsync = async (
|
||||
variables: TVariables,
|
||||
args?: CallbackMutationOptions<TData, TError, TVariables>,
|
||||
) => {
|
||||
const { mutationKey, mutationFn, onSuccess, onError, onSettled } = {
|
||||
...defaultArgs,
|
||||
...args,
|
||||
};
|
||||
try {
|
||||
const data = await mutationFn(variables);
|
||||
onSuccess?.(data);
|
||||
return data;
|
||||
} catch (err: unknown) {
|
||||
const e = err as TError;
|
||||
console.log('Fast mutation error', mutationKey, e);
|
||||
onError?.(e);
|
||||
} finally {
|
||||
onSettled?.();
|
||||
}
|
||||
};
|
||||
|
||||
const mutate = (
|
||||
variables: TVariables,
|
||||
args?: CallbackMutationOptions<TData, TError, TVariables>,
|
||||
) => {
|
||||
setTimeout(() => mutateAsync(variables, args));
|
||||
};
|
||||
|
||||
return { mutateAsync, mutate };
|
||||
}
|
||||
|
||||
export function useFastMutation<TData = unknown, TError = unknown, TVariables = void>(
|
||||
defaultArgs: MutationOptions<TData, TError, TVariables>,
|
||||
) {
|
||||
return useMemo(() => {
|
||||
return createFastMutation(defaultArgs);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, defaultArgs.mutationKey);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { FilterResponse } from '@yaakapp-internal/plugin';
|
||||
import type { FilterResponse } from '@yaakapp-internal/plugins';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export function useFilterResponse({
|
||||
|
||||
@@ -7,9 +7,13 @@ import { useOsInfo } from './useOsInfo';
|
||||
const HOLD_KEYS = ['Shift', 'Control', 'Command', 'Alt', 'Meta'];
|
||||
|
||||
export type HotkeyAction =
|
||||
| 'app.zoom_in'
|
||||
| 'app.zoom_out'
|
||||
| 'app.zoom_reset'
|
||||
| 'command_palette.toggle'
|
||||
| 'environmentEditor.toggle'
|
||||
| 'hotkeys.showHelp'
|
||||
| 'grpc_request.send'
|
||||
| 'hotkeys.showHelp'
|
||||
| 'http_request.create'
|
||||
| 'http_request.duplicate'
|
||||
| 'http_request.send'
|
||||
@@ -18,13 +22,14 @@ export type HotkeyAction =
|
||||
| 'request_switcher.toggle'
|
||||
| 'settings.show'
|
||||
| 'sidebar.focus'
|
||||
| 'urlBar.focus'
|
||||
| 'command_palette.toggle'
|
||||
| 'app.zoom_in'
|
||||
| 'app.zoom_out'
|
||||
| 'app.zoom_reset';
|
||||
| 'url_bar.focus'
|
||||
| 'workspace_settings.show';
|
||||
|
||||
const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'app.zoom_in': ['CmdCtrl+='],
|
||||
'app.zoom_out': ['CmdCtrl+-'],
|
||||
'app.zoom_reset': ['CmdCtrl+0'],
|
||||
'command_palette.toggle': ['CmdCtrl+k'],
|
||||
'environmentEditor.toggle': ['CmdCtrl+Shift+e'],
|
||||
'grpc_request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
|
||||
'hotkeys.showHelp': ['CmdCtrl+Shift+/'],
|
||||
@@ -36,14 +41,15 @@ const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'request_switcher.toggle': ['CmdCtrl+p'],
|
||||
'settings.show': ['CmdCtrl+,'],
|
||||
'sidebar.focus': ['CmdCtrl+b'],
|
||||
'urlBar.focus': ['CmdCtrl+l'],
|
||||
'command_palette.toggle': ['CmdCtrl+k'],
|
||||
'app.zoom_in': ['CmdCtrl+='],
|
||||
'app.zoom_out': ['CmdCtrl+-'],
|
||||
'app.zoom_reset': ['CmdCtrl+0'],
|
||||
'url_bar.focus': ['CmdCtrl+l'],
|
||||
'workspace_settings.show': ['CmdCtrl+Shift+,'],
|
||||
};
|
||||
|
||||
const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||
'app.zoom_in': 'Zoom In',
|
||||
'app.zoom_out': 'Zoom Out',
|
||||
'app.zoom_reset': 'Zoom to Actual Size',
|
||||
'command_palette.toggle': 'Toggle Command Palette',
|
||||
'environmentEditor.toggle': 'Edit Environments',
|
||||
'grpc_request.send': 'Send Message',
|
||||
'hotkeys.showHelp': 'Show Keyboard Shortcuts',
|
||||
@@ -55,11 +61,8 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||
'request_switcher.toggle': 'Toggle Request Switcher',
|
||||
'settings.show': 'Open Settings',
|
||||
'sidebar.focus': 'Focus or Toggle Sidebar',
|
||||
'urlBar.focus': 'Focus URL',
|
||||
'command_palette.toggle': 'Toggle Command Palette',
|
||||
'app.zoom_in': 'Zoom In',
|
||||
'app.zoom_out': 'Zoom Out',
|
||||
'app.zoom_reset': 'Zoom to Actual Size',
|
||||
'url_bar.focus': 'Focus URL',
|
||||
'workspace_settings.show': 'Open Workspace Settings',
|
||||
};
|
||||
|
||||
export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[];
|
||||
@@ -165,7 +168,7 @@ export function useHotKeyLabel(action: HotkeyAction): string {
|
||||
|
||||
export function useFormattedHotkey(action: HotkeyAction | null): string[] | null {
|
||||
const osInfo = useOsInfo();
|
||||
const trigger = action != null ? hotkeys[action]?.[0] ?? null : null;
|
||||
const trigger = action != null ? (hotkeys[action]?.[0] ?? null) : null;
|
||||
if (trigger == null || osInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
CallHttpRequestActionRequest,
|
||||
GetHttpRequestActionsResponse,
|
||||
HttpRequestAction,
|
||||
} from '@yaakapp-internal/plugin';
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { usePluginsKey } from './usePlugins';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { buildKeyValueKey, extractKeyValueOrFallback, setKeyValue } from '../lib
|
||||
|
||||
const DEFAULT_NAMESPACE = 'global';
|
||||
|
||||
export const keyValuesAtom = atom<KeyValue[]>([]);
|
||||
export const keyValuesAtom = atom<KeyValue[] | null>(null);
|
||||
|
||||
export function keyValueQueryKey({
|
||||
namespace = DEFAULT_NAMESPACE,
|
||||
@@ -32,7 +32,7 @@ export function useKeyValue<T extends object | boolean | number | string | null>
|
||||
const keyValues = useAtomValue(keyValuesAtom);
|
||||
const keyValue =
|
||||
keyValues?.find((kv) => buildKeyValueKey(kv.key) === buildKeyValueKey(key)) ?? null;
|
||||
const value = extractKeyValueOrFallback(keyValue, fallback);
|
||||
const value = keyValues == null ? null : extractKeyValueOrFallback(keyValue, fallback);
|
||||
const isLoading = keyValues == null;
|
||||
|
||||
const { mutateAsync } = useMutation<void, unknown, T>({
|
||||
@@ -43,7 +43,7 @@ export function useKeyValue<T extends object | boolean | number | string | null>
|
||||
const set = useCallback(
|
||||
async (valueOrUpdate: ((v: T) => T) | T) => {
|
||||
if (typeof valueOrUpdate === 'function') {
|
||||
const newV = valueOrUpdate(value);
|
||||
const newV = valueOrUpdate(value ?? fallback);
|
||||
if (newV === value) return;
|
||||
await mutateAsync(newV);
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { Button } from '../components/core/Button';
|
||||
import { useToast } from './useToast';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useListenToTauriEvent } from './useListenToTauriEvent';
|
||||
import { useToast } from './useToast';
|
||||
|
||||
export function useNotificationToast() {
|
||||
const toast = useToast();
|
||||
@@ -29,14 +29,14 @@ export function useNotificationToast() {
|
||||
message: payload.message,
|
||||
color: 'custom',
|
||||
onClose: () => markRead(payload.id),
|
||||
action:
|
||||
action: ({ hide }) =>
|
||||
actionLabel && actionUrl ? (
|
||||
<Button
|
||||
size="xs"
|
||||
color="secondary"
|
||||
className="mr-auto min-w-[5rem]"
|
||||
onClick={() => {
|
||||
toast.hide(payload.id);
|
||||
hide();
|
||||
return open(actionUrl);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { BootResponse } from '@yaakapp-internal/plugin';
|
||||
import type { BootResponse } from '@yaakapp-internal/plugins';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export function usePluginInfo(id: string) {
|
||||
|
||||
@@ -11,13 +11,17 @@ const fallback: string[] = [];
|
||||
|
||||
export function useRecentWorkspaces() {
|
||||
const workspaces = useWorkspaces();
|
||||
const { value } = useKeyValue<string[]>({ key: kvKey(), namespace, fallback });
|
||||
const { value, isLoading } = useKeyValue<string[]>({ key: kvKey(), namespace, fallback });
|
||||
|
||||
const onlyValidIds = useMemo(
|
||||
() => value?.filter((id) => workspaces.some((w) => w.id === id)) ?? [],
|
||||
[value, workspaces],
|
||||
);
|
||||
|
||||
|
||||
console.log("HELLO", {isLoading, value})
|
||||
if (isLoading) return null;
|
||||
|
||||
return onlyValidIds;
|
||||
}
|
||||
|
||||
@@ -25,15 +29,18 @@ export function useSubscribeRecentWorkspaces() {
|
||||
useEffect(() => {
|
||||
return jotaiStore.sub(activeWorkspaceIdAtom, async () => {
|
||||
const activeWorkspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
console.log("AAA");
|
||||
if (activeWorkspaceId == null) return;
|
||||
|
||||
const key = kvKey();
|
||||
|
||||
const recentIds = await getKeyValue<string[]>({ namespace, key, fallback });
|
||||
console.log("BBB", recentIds, activeWorkspaceId);
|
||||
if (recentIds[0] === activeWorkspaceId) return; // Short-circuit
|
||||
|
||||
const withoutActiveId = recentIds.filter((id) => id !== activeWorkspaceId);
|
||||
const value = [activeWorkspaceId, ...withoutActiveId];
|
||||
console.log("SET ACTIVE WORCENT", value);
|
||||
await setKeyValue({ namespace, key, value });
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import deepEqual from '@gilbarbara/deep-equal';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import type { AnyModel, KeyValue } from '@yaakapp-internal/models';
|
||||
import type { AnyModel, KeyValue, ModelPayload } from '@yaakapp-internal/models';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { buildKeyValueKey } from '../lib/keyValueStore';
|
||||
import { modelsEq } from '../lib/model_util';
|
||||
@@ -21,97 +21,93 @@ import { useRequestUpdateKey } from './useRequestUpdateKey';
|
||||
import { settingsAtom } from './useSettings';
|
||||
import { workspacesAtom } from './useWorkspaces';
|
||||
|
||||
export interface ModelPayload {
|
||||
model: AnyModel;
|
||||
windowLabel: string;
|
||||
}
|
||||
|
||||
export function useSyncModelStores() {
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const queryClient = useQueryClient();
|
||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||
|
||||
useListenToTauriEvent<ModelPayload>('upserted_model', ({ payload }) => {
|
||||
const { model, windowLabel } = payload;
|
||||
const queryKey =
|
||||
model.model === 'grpc_event'
|
||||
? grpcEventsQueryKey(model)
|
||||
: model.model === 'key_value'
|
||||
? keyValueQueryKey(model)
|
||||
payload.model.model === 'grpc_event'
|
||||
? grpcEventsQueryKey(payload.model)
|
||||
: payload.model.model === 'key_value'
|
||||
? keyValueQueryKey(payload.model)
|
||||
: null;
|
||||
|
||||
// TODO: Move this logic to useRequestEditor() hook
|
||||
if (model.model === 'http_request' && windowLabel !== getCurrentWebviewWindow().label) {
|
||||
wasUpdatedExternally(model.id);
|
||||
if (
|
||||
payload.model.model === 'http_request' &&
|
||||
(payload.windowLabel !== getCurrentWebviewWindow().label || payload.updateSource !== 'window')
|
||||
) {
|
||||
wasUpdatedExternally(payload.model.id);
|
||||
}
|
||||
|
||||
// Only sync models that belong to this workspace, if a workspace ID is present
|
||||
if ('workspaceId' in model && model.workspaceId !== activeWorkspace?.id) {
|
||||
if ('workspaceId' in payload.model && payload.model.workspaceId !== activeWorkspace?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldIgnoreModel(model, windowLabel)) return;
|
||||
if (shouldIgnoreModel(payload)) return;
|
||||
|
||||
if (model.model === 'workspace') {
|
||||
jotaiStore.set(workspacesAtom, updateModelList(model));
|
||||
} else if (model.model === 'plugin') {
|
||||
jotaiStore.set(pluginsAtom, updateModelList(model));
|
||||
} else if (model.model === 'http_request') {
|
||||
jotaiStore.set(httpRequestsAtom, updateModelList(model));
|
||||
} else if (model.model === 'folder') {
|
||||
jotaiStore.set(foldersAtom, updateModelList(model));
|
||||
} else if (model.model === 'http_response') {
|
||||
jotaiStore.set(httpResponsesAtom, updateModelList(model));
|
||||
} else if (model.model === 'grpc_request') {
|
||||
jotaiStore.set(grpcRequestsAtom, updateModelList(model));
|
||||
} else if (model.model === 'grpc_connection') {
|
||||
jotaiStore.set(grpcConnectionsAtom, updateModelList(model));
|
||||
} else if (model.model === 'environment') {
|
||||
jotaiStore.set(environmentsAtom, updateModelList(model));
|
||||
} else if (model.model === 'cookie_jar') {
|
||||
jotaiStore.set(cookieJarsAtom, updateModelList(model));
|
||||
} else if (model.model === 'settings') {
|
||||
jotaiStore.set(settingsAtom, model);
|
||||
} else if (model.model === 'key_value') {
|
||||
jotaiStore.set(keyValuesAtom, updateModelList(model));
|
||||
if (payload.model.model === 'workspace') {
|
||||
jotaiStore.set(workspacesAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'plugin') {
|
||||
jotaiStore.set(pluginsAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'http_request') {
|
||||
jotaiStore.set(httpRequestsAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'folder') {
|
||||
jotaiStore.set(foldersAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'http_response') {
|
||||
jotaiStore.set(httpResponsesAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'grpc_request') {
|
||||
jotaiStore.set(grpcRequestsAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'grpc_connection') {
|
||||
jotaiStore.set(grpcConnectionsAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'environment') {
|
||||
jotaiStore.set(environmentsAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'cookie_jar') {
|
||||
jotaiStore.set(cookieJarsAtom, updateModelList(payload.model));
|
||||
} else if (payload.model.model === 'settings') {
|
||||
jotaiStore.set(settingsAtom, payload.model);
|
||||
} else if (payload.model.model === 'key_value') {
|
||||
jotaiStore.set(keyValuesAtom, updateModelList(payload.model));
|
||||
} else if (queryKey != null) {
|
||||
// TODO: Convert all models to use Jotai
|
||||
queryClient.setQueryData(queryKey, (current: unknown) => {
|
||||
if (Array.isArray(current)) {
|
||||
return updateModelList(model)(current);
|
||||
return updateModelList(payload.model)(current);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
useListenToTauriEvent<ModelPayload>('deleted_model', ({ payload }) => {
|
||||
const { model, windowLabel } = payload;
|
||||
if (shouldIgnoreModel(model, windowLabel)) return;
|
||||
if (shouldIgnoreModel(payload)) return;
|
||||
|
||||
console.log('Delete model', payload);
|
||||
|
||||
if (model.model === 'workspace') {
|
||||
jotaiStore.set(workspacesAtom, removeModelById(model));
|
||||
} else if (model.model === 'plugin') {
|
||||
jotaiStore.set(pluginsAtom, removeModelById(model));
|
||||
} else if (model.model === 'http_request') {
|
||||
jotaiStore.set(httpRequestsAtom, removeModelById(model));
|
||||
} else if (model.model === 'http_response') {
|
||||
jotaiStore.set(httpResponsesAtom, removeModelById(model));
|
||||
} else if (model.model === 'folder') {
|
||||
jotaiStore.set(foldersAtom, removeModelById(model));
|
||||
} else if (model.model === 'environment') {
|
||||
jotaiStore.set(environmentsAtom, removeModelById(model));
|
||||
} else if (model.model === 'grpc_request') {
|
||||
jotaiStore.set(grpcRequestsAtom, removeModelById(model));
|
||||
} else if (model.model === 'grpc_connection') {
|
||||
jotaiStore.set(grpcConnectionsAtom, removeModelById(model));
|
||||
} else if (model.model === 'grpc_event') {
|
||||
queryClient.setQueryData(grpcEventsQueryKey(model), removeModelById(model));
|
||||
} else if (model.model === 'key_value') {
|
||||
queryClient.setQueryData(keyValueQueryKey(model), removeModelByKeyValue(model));
|
||||
} else if (model.model === 'cookie_jar') {
|
||||
jotaiStore.set(cookieJarsAtom, removeModelById(model));
|
||||
if (payload.model.model === 'workspace') {
|
||||
jotaiStore.set(workspacesAtom, removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'plugin') {
|
||||
jotaiStore.set(pluginsAtom, removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'http_request') {
|
||||
jotaiStore.set(httpRequestsAtom, removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'http_response') {
|
||||
jotaiStore.set(httpResponsesAtom, removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'folder') {
|
||||
jotaiStore.set(foldersAtom, removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'environment') {
|
||||
jotaiStore.set(environmentsAtom, removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'grpc_request') {
|
||||
jotaiStore.set(grpcRequestsAtom, removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'grpc_connection') {
|
||||
jotaiStore.set(grpcConnectionsAtom, removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'grpc_event') {
|
||||
queryClient.setQueryData(grpcEventsQueryKey(payload.model), removeModelById(payload.model));
|
||||
} else if (payload.model.model === 'key_value') {
|
||||
queryClient.setQueryData(keyValueQueryKey(payload.model), removeModelByKv(payload.model));
|
||||
} else if (payload.model.model === 'cookie_jar') {
|
||||
jotaiStore.set(cookieJarsAtom, removeModelById(payload.model));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -120,7 +116,7 @@ export function updateModelList<T extends AnyModel>(model: T) {
|
||||
// Mark these models as DESC instead of ASC
|
||||
const pushToFront = model.model === 'http_response' || model.model === 'grpc_connection';
|
||||
|
||||
return (current: T[] | undefined): T[] => {
|
||||
return (current: T[] | undefined | null): T[] => {
|
||||
const index = current?.findIndex((v) => modelsEq(v, model)) ?? -1;
|
||||
const existingModel = current?.[index];
|
||||
if (existingModel && deepEqual(existingModel, model)) {
|
||||
@@ -147,7 +143,7 @@ export function removeModelById<T extends { id: string }>(model: T) {
|
||||
};
|
||||
}
|
||||
|
||||
export function removeModelByKeyValue(model: KeyValue) {
|
||||
export function removeModelByKv(model: KeyValue) {
|
||||
return (prevEntries: KeyValue[] | undefined) =>
|
||||
prevEntries?.filter(
|
||||
(e) =>
|
||||
@@ -159,13 +155,20 @@ export function removeModelByKeyValue(model: KeyValue) {
|
||||
) ?? [];
|
||||
}
|
||||
|
||||
const shouldIgnoreModel = (payload: AnyModel, windowLabel: string) => {
|
||||
function shouldIgnoreModel({ model, windowLabel, updateSource }: ModelPayload) {
|
||||
// Never ignore same-window updates
|
||||
if (windowLabel === getCurrentWebviewWindow().label) {
|
||||
// Never ignore same-window updates
|
||||
return false;
|
||||
}
|
||||
if (payload.model === 'key_value') {
|
||||
return payload.namespace === 'no_sync';
|
||||
|
||||
// Never ignore updates from non-user sources
|
||||
if (updateSource !== 'window') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (model.model === 'key_value') {
|
||||
return model.namespace === 'no_sync';
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,16 +19,23 @@ export function useSyncWorkspaceChildModels() {
|
||||
}
|
||||
|
||||
async function sync() {
|
||||
// Doesn't need a workspace ID, so sync it right away
|
||||
jotaiStore.set(keyValuesAtom, await invokeCmd('cmd_list_key_values'));
|
||||
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const args = { workspaceId };
|
||||
if (workspaceId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Syncing model stores', args);
|
||||
|
||||
// Set the things we need first, first
|
||||
jotaiStore.set(httpRequestsAtom, await invokeCmd('cmd_list_http_requests', args));
|
||||
jotaiStore.set(grpcRequestsAtom, await invokeCmd('cmd_list_grpc_requests', args));
|
||||
jotaiStore.set(foldersAtom, await invokeCmd('cmd_list_folders', args));
|
||||
|
||||
// Then, set the rest
|
||||
jotaiStore.set(keyValuesAtom, await invokeCmd('cmd_list_key_values', args));
|
||||
jotaiStore.set(cookieJarsAtom, await invokeCmd('cmd_list_cookie_jars', args));
|
||||
jotaiStore.set(httpResponsesAtom, await invokeCmd('cmd_list_http_responses', args));
|
||||
jotaiStore.set(grpcConnectionsAtom, await invokeCmd('cmd_list_grpc_connections', args));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { GetTemplateFunctionsResponse, TemplateFunction } from '@yaakapp-internal/plugin';
|
||||
import type { GetTemplateFunctionsResponse, TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { useSetAtom } from 'jotai/index';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { CommandPalette } from '../components/CommandPalette';
|
||||
import { CommandPaletteDialog } from '../components/CommandPaletteDialog';
|
||||
import { useDialog } from './useDialog';
|
||||
|
||||
export function useToggleCommandPalette() {
|
||||
@@ -13,7 +13,7 @@ export function useToggleCommandPalette() {
|
||||
vAlign: 'top',
|
||||
noPadding: true,
|
||||
noScroll: true,
|
||||
render: ({ hide }) => <CommandPalette onClose={hide} />,
|
||||
render: ({ hide }) => <CommandPaletteDialog onClose={hide} />,
|
||||
});
|
||||
}, [dialog]);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ export function useUpdateEnvironment(id: string | null) {
|
||||
unknown,
|
||||
Partial<Environment> | ((r: Environment) => Environment)
|
||||
>({
|
||||
toastyError: true,
|
||||
mutationKey: ['update_environment', id],
|
||||
mutationFn: async (v) => {
|
||||
const environment = await getEnvironment(id);
|
||||
|
||||
Reference in New Issue
Block a user