Filesystem Sync (#142)

This commit is contained in:
Gregory Schier
2025-01-03 20:41:00 -08:00
committed by GitHub
parent 6ad27c4458
commit 31440eea76
159 changed files with 4296 additions and 1016 deletions

View File

@@ -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';

View 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]);
}

View File

@@ -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[]),
];

View File

@@ -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) {

View File

@@ -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'),
});
}

View File

@@ -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 },
});
},
});
}

View 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]);
}

View File

@@ -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);
}

View File

@@ -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({

View File

@@ -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;
}

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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);
}}
>

View File

@@ -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) {

View File

@@ -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 });
});
}, []);

View File

@@ -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;
};
}

View File

@@ -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));

View File

@@ -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';

View File

@@ -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]);

View File

@@ -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);