Generalized frontend model store (#193)

This commit is contained in:
Gregory Schier
2025-03-31 11:56:17 -07:00
committed by GitHub
parent ce885c3551
commit f1757ae427
201 changed files with 2185 additions and 2865 deletions

View File

@@ -0,0 +1,25 @@
import { atom } from 'jotai/index';
import { getKeyValue, setKeyValue } from '../keyValueStore';
export function atomWithKVStorage<T extends object | boolean | number | string | null>(
key: string,
fallback: T,
namespace = 'global',
) {
const baseAtom = atom<T>(fallback);
baseAtom.onMount = (setValue) => {
setValue(getKeyValue<T>({ namespace, key, fallback }));
};
const derivedAtom = atom<T, [T | ((prev: T) => T)], void>(
(get) => get(baseAtom),
(get, set, update) => {
const nextValue = typeof update === 'function' ? update(get(baseAtom)) : update;
set(baseAtom, nextValue);
setKeyValue({ namespace, key, value: nextValue }).catch(console.error);
},
);
return derivedAtom;
}

View File

@@ -0,0 +1,29 @@
import type { AnyModel } from '@yaakapp-internal/models';
import { deleteModel, modelTypeLabel } from '@yaakapp-internal/models';
import { InlineCode } from '../components/core/InlineCode';
import { showConfirmDelete } from './confirm';
import { resolvedModelName } from './resolvedModelName';
export async function deleteModelWithConfirm(model: AnyModel | null): Promise<boolean> {
if (model == null ) {
console.warn('Tried to delete null model');
return false;
}
const confirmed = await showConfirmDelete({
id: 'delete-model-' + model.model,
title: 'Delete ' + modelTypeLabel(model),
description: (
<>
Permanently delete <InlineCode>{resolvedModelName(model)}</InlineCode>?
</>
),
});
if (!confirmed) {
return false;
}
await deleteModel(model);
return true;
}

View File

@@ -0,0 +1,23 @@
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
import { duplicateModel } from '@yaakapp-internal/models';
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
import { jotaiStore } from './jotai';
import { router } from './router';
export async function duplicateRequestAndNavigate(
model: HttpRequest | GrpcRequest | WebsocketRequest | null,
) {
if (model == null ){
throw new Error('Cannot duplicate null request');
}
const newId = await duplicateModel(model);
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
if (workspaceId == null) return;
await router.navigate({
to: '/workspaces/$workspaceId',
params: { workspaceId },
search: (prev) => ({ ...prev, request_id: newId }),
});
}

View File

@@ -1,37 +1,42 @@
import type { KeyValue } from '@yaakapp-internal/models';
import { invokeCmd } from './tauri';
import { createGlobalModel, keyValuesAtom, patchModel } from '@yaakapp-internal/models';
import { jotaiStore } from './jotai';
export async function setKeyValue<T>({
namespace = 'global',
key,
value,
key: keyOrKeys,
value: rawValue,
}: {
namespace?: string;
key: string | string[];
value: T;
}): Promise<void> {
await invokeCmd('cmd_set_key_value', {
namespace,
key: buildKeyValueKey(key),
value: JSON.stringify(value),
});
const kv = getKeyValueRaw({ namespace, key: keyOrKeys });
const key = buildKeyValueKey(keyOrKeys);
const value = JSON.stringify(rawValue);
if (kv) {
await patchModel(kv, { namespace, key, value });
} else {
await createGlobalModel({ model: 'key_value', namespace, key, value });
}
}
export async function getKeyValueRaw({
export function getKeyValueRaw({
namespace = 'global',
key,
key: keyOrKeys,
}: {
namespace?: string;
key: string | string[];
}) {
const kv = (await invokeCmd('cmd_get_key_value', {
namespace,
key: buildKeyValueKey(key),
})) as KeyValue | null;
return kv;
const key = buildKeyValueKey(keyOrKeys);
const kv = jotaiStore
.get(keyValuesAtom)
.find((kv) => kv.namespace === namespace && kv?.key === key);
return kv ?? null;
}
export async function getKeyValue<T>({
export function getKeyValue<T>({
namespace = 'global',
key,
fallback,
@@ -40,7 +45,7 @@ export async function getKeyValue<T>({
key: string | string[];
fallback: T;
}) {
const kv = await getKeyValueRaw({ namespace, key });
const kv = getKeyValueRaw({ namespace, key });
return extractKeyValueOrFallback(kv, fallback);
}

View File

@@ -0,0 +1,31 @@
import type { AnyModel} from '@yaakapp-internal/models';
import { patchModel } from '@yaakapp-internal/models';
import { InlineCode } from '../components/core/InlineCode';
import { showPrompt } from './prompt';
export async function renameModelWithPrompt(model: Extract<AnyModel, { name: string }> | null) {
if (model == null) {
throw new Error('Tried to rename null model');
}
const name = await showPrompt({
id: 'rename-request',
title: 'Rename Request',
description:
model.name === '' ? (
'Enter a new name'
) : (
<>
Enter a new name for <InlineCode>{model.name}</InlineCode>
</>
),
label: 'Name',
placeholder: 'New Name',
defaultValue: model.name,
confirmText: 'Save',
});
if (name == null) return;
await patchModel(model, { name });
}

View File

@@ -1,5 +1,5 @@
import type { AnyModel } from '@yaakapp-internal/models';
import { foldersAtom } from '../hooks/useFolders';
import type { AnyModel} from '@yaakapp-internal/models';
import { foldersAtom } from '@yaakapp-internal/models';
import { jotaiStore } from './jotai';
export function resolvedModelName(r: AnyModel | null): string {

6
src-web/lib/settings.ts Normal file
View File

@@ -0,0 +1,6 @@
import { invoke } from '@tauri-apps/api/core';
import type { Settings } from '@yaakapp-internal/models';
export function getSettings(): Promise<Settings> {
return invoke<Settings>('plugin:yaak-models|get_settings');
}

View File

@@ -5,53 +5,24 @@ type TauriCmd =
| 'cmd_call_http_authentication_action'
| 'cmd_call_http_request_action'
| 'cmd_check_for_updates'
| 'cmd_create_cookie_jar'
| 'cmd_create_environment'
| 'cmd_create_grpc_request'
| 'cmd_curl_to_request'
| 'cmd_delete_all_grpc_connections'
| 'cmd_delete_all_http_responses'
| 'cmd_delete_cookie_jar'
| 'cmd_delete_environment'
| 'cmd_delete_folder'
| 'cmd_delete_grpc_connection'
| 'cmd_delete_grpc_request'
| 'cmd_delete_http_request'
| 'cmd_delete_http_response'
| 'cmd_delete_send_history'
| 'cmd_delete_workspace'
| 'cmd_dismiss_notification'
| 'cmd_duplicate_folder'
| 'cmd_duplicate_grpc_request'
| 'cmd_duplicate_http_request'
| 'cmd_export_data'
| 'cmd_filter_response'
| 'cmd_format_json'
| 'cmd_get_environment'
| 'cmd_get_folder'
| 'cmd_get_http_authentication_config'
| 'cmd_get_http_authentication_summaries'
| 'cmd_get_key_value'
| 'cmd_get_settings'
| 'cmd_get_sse_events'
| 'cmd_get_workspace'
| 'cmd_get_workspace_meta'
| 'cmd_grpc_go'
| 'cmd_grpc_reflect'
| 'cmd_http_request_actions'
| 'cmd_import_data'
| 'cmd_install_plugin'
| 'cmd_list_cookie_jars'
| 'cmd_list_environments'
| 'cmd_list_folders'
| 'cmd_list_grpc_connections'
| 'cmd_list_grpc_events'
| 'cmd_list_grpc_requests'
| 'cmd_list_http_requests'
| 'cmd_list_http_responses'
| 'cmd_list_key_values'
| 'cmd_list_plugins'
| 'cmd_list_workspaces'
| 'cmd_metadata'
| 'cmd_new_child_window'
| 'cmd_new_main_window'
@@ -62,19 +33,9 @@ type TauriCmd =
| 'cmd_save_response'
| 'cmd_send_ephemeral_request'
| 'cmd_send_http_request'
| 'cmd_set_key_value'
| 'cmd_set_update_mode'
| 'cmd_template_functions'
| 'cmd_template_tokens_to_string'
| 'cmd_uninstall_plugin'
| 'cmd_update_cookie_jar'
| 'cmd_update_environment'
| 'cmd_update_folder'
| 'cmd_update_grpc_request'
| 'cmd_upsert_http_request'
| 'cmd_update_settings'
| 'cmd_update_workspace'
| 'cmd_update_workspace_meta';
| 'cmd_uninstall_plugin';
export async function invokeCmd<T>(cmd: TauriCmd, args?: InvokeArgs): Promise<T> {
// console.log('RUN COMMAND', cmd, args);