Optimize sidebar collapsing

This commit is contained in:
Gregory Schier
2024-12-23 05:05:04 -08:00
parent 61d094d9fd
commit 31f2bff0f6
35 changed files with 402 additions and 238 deletions

View File

@@ -1,18 +1,58 @@
import { useNavigate, useSearch } from '@tanstack/react-router';
import { useCallback, useEffect, useMemo } from 'react';
import { useCookieJars } from './useCookieJars';
import type { CookieJar } from '@yaakapp-internal/models';
import { atom, useAtomValue } from 'jotai/index';
import { useCallback, useEffect } from 'react';
import { jotaiStore } from '../lib/jotai';
import { cookieJarsAtom, useCookieJars } from './useCookieJars';
export const QUERY_COOKIE_JAR_ID = 'cookie_jar_id';
export const activeCookieJarIdAtom = atom<string>();
export const activeCookieJarAtom = atom<CookieJar | null>((get) => {
const activeId = get(activeCookieJarIdAtom);
return get(cookieJarsAtom)?.find((e) => e.id === activeId) ?? null;
});
export function useActiveCookieJar() {
const [activeCookieJarId, setActiveCookieJarId] = useActiveCookieJarId();
const cookieJars = useCookieJars();
const navigate = useNavigate({ from: '/workspaces/$workspaceId' });
const setId = useCallback(
(id: string) =>
navigate({
search: (prev) => ({ ...prev, cookie_jar_id: id }),
}),
[navigate],
);
const cookieJar = useAtomValue(activeCookieJarAtom);
return [cookieJar, setId] as const;
}
const activeCookieJar = useMemo(() => {
return cookieJars?.find((cookieJar) => cookieJar.id === activeCookieJarId) ?? null;
}, [activeCookieJarId, cookieJars]);
function useActiveCookieJarId() {
// NOTE: This query param is accessed from Rust side, so do not change
const { cookie_jar_id: id } = useSearch({ strict: false });
const navigate = useNavigate({ from: '/workspaces/$workspaceId' });
return [activeCookieJar ?? null, setActiveCookieJarId] as const;
const setId = useCallback(
(id: string) =>
navigate({
search: (prev) => ({ ...prev, cookie_jar_id: id }),
}),
[navigate],
);
return [id, setId] as const;
}
export function useSubscribeActiveCookieJar() {
const { cookie_jar_id } = useSearch({ strict: false });
useEffect(
() => jotaiStore.set(activeCookieJarIdAtom, cookie_jar_id ?? undefined),
[cookie_jar_id],
);
}
export function getActiveCookieJar() {
return jotaiStore.get(activeCookieJarAtom);
}
export function useEnsureActiveCookieJar() {
@@ -37,19 +77,3 @@ export function useEnsureActiveCookieJar() {
setActiveCookieJarId(firstJar.id).catch(console.error);
}, [activeCookieJarId, cookieJars, setActiveCookieJarId]);
}
function useActiveCookieJarId() {
// NOTE: This query param is accessed from Rust side, so do not change
const { cookie_jar_id: id } = useSearch({ strict: false });
const navigate = useNavigate({ from: '/workspaces/$workspaceId' });
const setId = useCallback(
(id: string) =>
navigate({
search: (prev) => ({ ...prev, cookie_jar_id: id }),
}),
[navigate],
);
return [id, setId] as const;
}

View File

@@ -1,28 +1,41 @@
import { useNavigate, useSearch } from '@tanstack/react-router';
import { useCallback } from 'react';
import { useEnvironments } from './useEnvironments';
export function useActiveEnvironment() {
const [id, setId] = useActiveEnvironmentId();
const { subEnvironments } = useEnvironments();
const environment = subEnvironments.find((w) => w.id === id) ?? null;
return [environment, setId] as const;
}
import type { Environment } from '@yaakapp-internal/models';
import { useAtomValue } from 'jotai';
import { atom } from 'jotai/index';
import { useCallback, useEffect } from 'react';
import { jotaiStore } from '../lib/jotai';
import { environmentsAtom } from './useEnvironments';
export const QUERY_ENVIRONMENT_ID = 'environment_id';
function useActiveEnvironmentId() {
// NOTE: This query param is accessed from Rust side, so do not change
const { environment_id: id} = useSearch({ strict: false });
const navigate = useNavigate({ from: '/workspaces/$workspaceId' });
export const activeEnvironmentIdAtom = atom<string>();
export const activeEnvironmentAtom = atom<Environment | null>((get) => {
const activeEnvironmentId = get(activeEnvironmentIdAtom);
return get(environmentsAtom).find((e) => e.id === activeEnvironmentId) ?? null;
});
export function useActiveEnvironment() {
const navigate = useNavigate({ from: '/workspaces/$workspaceId' });
const setId = useCallback(
(environmentId: string | null) =>
(id: string | null) =>
navigate({
search: (prev) => ({ ...prev, environment_id: environmentId ?? undefined }),
search: (prev) => ({ ...prev, environment_id: id }),
}),
[navigate],
);
return [id, setId] as const;
const environment = useAtomValue(activeEnvironmentAtom);
return [environment, setId] as const;
}
export function getActiveEnvironment() {
return jotaiStore.get(activeEnvironmentAtom);
}
export function useSubscribeActiveEnvironmentId() {
const { environment_id } = useSearch({ strict: false });
useEffect(
() => jotaiStore.set(activeEnvironmentIdAtom, environment_id ?? undefined),
[environment_id],
);
}

View File

@@ -1,7 +1,7 @@
import { useParams } from '@tanstack/react-router';
import { atom, useAtomValue } from 'jotai';
import { useEffect } from 'react';
import {jotaiStore} from "../lib/jotai";
import { jotaiStore } from '../lib/jotai';
export const activeRequestIdAtom = atom<string>();
@@ -11,7 +11,5 @@ export function useActiveRequestId(): string | null {
export function useSubscribeActiveRequestId() {
const { requestId } = useParams({ strict: false });
useEffect(() => {
jotaiStore.set(activeRequestIdAtom, requestId);
}, [requestId]);
useEffect(() => jotaiStore.set(activeRequestIdAtom, requestId), [requestId]);
}

View File

@@ -18,7 +18,7 @@ function useActiveWorkspaceId(): string | null {
}
export function getActiveWorkspaceId() {
return jotaiStore.get(activeWorkspaceIdAtom);
return jotaiStore.get(activeWorkspaceIdAtom) ?? null;
}
export function useSubscribeActiveWorkspaceId() {

View File

@@ -15,9 +15,14 @@ export function useCreateEnvironment() {
const workspace = useActiveWorkspace();
const setEnvironments = useSetAtom(environmentsAtom);
return useFastMutation<Environment | null, unknown, Environment>({
return useFastMutation<Environment | null, unknown, Environment | null>({
toastyError: true,
mutationKey: ['create_environment'],
mutationFn: async (baseEnvironment) => {
if (baseEnvironment == null) {
throw new Error('No base environment passed');
}
const name = await prompt({
id: 'new-environment',
title: 'New Environment',

View File

@@ -1,15 +1,14 @@
import { useFastMutation } from './useFastMutation';
import type { Folder } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { getActiveWorkspaceId } from './useActiveWorkspace';
import { useFastMutation } from './useFastMutation';
import { foldersAtom } from './useFolders';
import { usePrompt } from './usePrompt';
import { updateModelList } from './useSyncModelStores';
export function useCreateFolder() {
const workspace = useActiveWorkspace();
const prompt = usePrompt();
const setFolders = useSetAtom(foldersAtom);
@@ -20,8 +19,8 @@ export function useCreateFolder() {
>({
mutationKey: ['create_folder'],
mutationFn: async (patch) => {
console.log("FOLDER", workspace);
if (workspace === null) {
const workspaceId = getActiveWorkspaceId();
if (workspaceId == null) {
throw new Error("Cannot create folder when there's no active workspace");
}
@@ -40,7 +39,7 @@ export function useCreateFolder() {
}
patch.sortPriority = patch.sortPriority || -Date.now();
return await invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch });
return await invokeCmd('cmd_create_folder', { workspaceId, ...patch });
},
onSuccess: (folder) => {
if (folder == null) return;

View File

@@ -6,7 +6,7 @@ export const environmentsAtom = atom<Environment[]>([]);
export function useEnvironments() {
const allEnvironments = useAtomValue(environmentsAtom);
const baseEnvironment = allEnvironments.find((e) => e.environmentId == null);
const baseEnvironment = allEnvironments.find((e) => e.environmentId == null) ?? null;
const subEnvironments =
allEnvironments.filter((e) => e.environmentId === (baseEnvironment?.id ?? 'n/a')) ?? [];

View File

@@ -32,6 +32,8 @@ export function useFastMutation<TData = unknown, TError = unknown, TVariables =
if (toastyError) {
toast.show({
id: 'error-' + mutationKey.join('.'),
color: 'danger',
timeout: 8000,
message: String(e),
});
}

View File

@@ -68,7 +68,7 @@ export function useImportData() {
await navigate({
to: '/workspaces/$workspaceId',
params: { workspaceId: importedWorkspace.id },
search: { environmentId },
search: { environment_id: environmentId },
});
}

View File

@@ -1,9 +1,15 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { KeyValue } from '@yaakapp-internal/models';
import { useAtomValue } from 'jotai';
import { atom } from 'jotai/index';
import { useCallback, useMemo } from 'react';
import { buildKeyValueKey, getKeyValue, setKeyValue } from '../lib/keyValueStore';
import { jotaiStore } from '../lib/jotai';
import { buildKeyValueKey, extractKeyValueOrFallback, setKeyValue } from '../lib/keyValueStore';
const DEFAULT_NAMESPACE = 'global';
export const keyValuesAtom = atom<KeyValue[]>([]);
export function keyValueQueryKey({
namespace = DEFAULT_NAMESPACE,
key,
@@ -23,44 +29,60 @@ export function useKeyValue<T extends object | boolean | number | string | null>
key: string | string[];
fallback: T;
}) {
const query = useQuery<T>({
queryKey: keyValueQueryKey({ namespace, key }),
queryFn: async () => getKeyValue({ namespace, key, fallback }),
refetchOnWindowFocus: false,
});
const keyValues = useAtomValue(keyValuesAtom);
const keyValue =
keyValues?.find((kv) => buildKeyValueKey(kv.key) === buildKeyValueKey(key)) ?? null;
const value = extractKeyValueOrFallback(keyValue, fallback);
const isLoading = keyValues == null;
const mutate = useMutation<void, unknown, T>({
const { mutateAsync } = useMutation<void, unknown, T>({
mutationKey: ['set_key_value', namespace, key],
mutationFn: (value) => setKeyValue<T>({ namespace, key, value }),
});
const set = useCallback(
async (value: ((v: T) => T) | T) => {
if (typeof value === 'function') {
await getKeyValue({ namespace, key, fallback }).then((kv) => {
const newV = value(kv);
if (newV === kv) return;
return mutate.mutateAsync(newV);
});
async (valueOrUpdate: ((v: T) => T) | T) => {
if (typeof valueOrUpdate === 'function') {
const newV = valueOrUpdate(value);
if (newV === value) return;
await mutateAsync(newV);
} else {
// TODO: Make this only update if the value is different. I tried this but it seems query.data
// is stale.
await mutate.mutateAsync(value);
await mutateAsync(valueOrUpdate);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[typeof key === 'string' ? key : key.join('::'), namespace],
[typeof key === 'string' ? key : key.join('::'), namespace, value],
);
const reset = useCallback(async () => mutate.mutateAsync(fallback), [mutate, fallback]);
const reset = useCallback(async () => mutateAsync(fallback), [fallback, mutateAsync]);
return useMemo(
() => ({
value: query.data,
isLoading: query.isLoading,
value,
isLoading,
set,
reset,
}),
[query.data, query.isLoading, reset, set],
[isLoading, reset, set, value],
);
}
export function getKeyValue<T extends object | boolean | number | string | null>({
namespace,
key,
fallback,
}: {
namespace?: 'global' | 'no_sync' | 'license';
key: string | string[];
fallback: T;
}) {
const keyValues = jotaiStore.get(keyValuesAtom);
const keyValue =
keyValues?.find(
(kv) => kv.namespace === namespace && buildKeyValueKey(kv.key) === buildKeyValueKey(key),
) ?? null;
const value = extractKeyValueOrFallback(keyValue, fallback);
return value;
}

View File

@@ -21,7 +21,7 @@ export function useOpenWorkspace() {
const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? undefined;
const requestId = (await getRecentRequests(workspaceId))[0] ?? undefined;
const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? undefined;
const search = { environmentId, cookieJarId };
const search = { environment_id: environmentId, cookie_jar_id: cookieJarId };
if (inNewWindow) {
const location = router.buildLocation({

View File

@@ -1,6 +1,7 @@
import { useEffect, useMemo } from 'react';
import { jotaiStore } from '../lib/jotai';
import { getKeyValue } from '../lib/keyValueStore';
import { useActiveRequestId } from './useActiveRequestId';
import { activeRequestIdAtom } from './useActiveRequestId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useKeyValue } from './useKeyValue';
import { useRequests } from './useRequests';
@@ -12,30 +13,38 @@ const fallback: string[] = [];
export function useRecentRequests() {
const requests = useRequests();
const activeWorkspace = useActiveWorkspace();
const activeRequestId = useActiveRequestId();
const kv = useKeyValue<string[]>({
const { set: setRecentRequests, value: recentRequests } = useKeyValue<string[]>({
key: kvKey(activeWorkspace?.id ?? 'n/a'),
namespace,
fallback,
});
// Set history when active request changes
useEffect(() => {
kv.set((currentHistory) => {
if (activeRequestId === null) return currentHistory;
const withoutCurrentRequest = currentHistory.filter((id) => id !== activeRequestId);
return [activeRequestId, ...withoutCurrentRequest];
}).catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeRequestId]);
const onlyValidIds = useMemo(
() => kv.value?.filter((id) => requests.some((r) => r.id === id)) ?? [],
[kv.value, requests],
() => recentRequests?.filter((id) => requests.some((r) => r.id === id)) ?? [],
[recentRequests, requests],
);
return onlyValidIds;
return [onlyValidIds, setRecentRequests] as const;
}
export function useSubscribeRecentRequests() {
const [recentRequests, setRecentRequests] = useRecentRequests();
useEffect(() => {
return jotaiStore.sub(activeRequestIdAtom, () => {
const activeRequestId = jotaiStore.get(activeRequestIdAtom) ?? null;
if (recentRequests[0] === activeRequestId) {
// Nothing to do
return;
}
setRecentRequests((currentHistory) => {
if (activeRequestId === null) return currentHistory;
const withoutCurrentRequest = currentHistory.filter((id) => id !== activeRequestId);
return [activeRequestId, ...withoutCurrentRequest];
}).catch(console.error);
});
}, [recentRequests, setRecentRequests]);
}
export async function getRecentRequests(workspaceId: string) {

View File

@@ -1,16 +1,14 @@
import { useFastMutation } from './useFastMutation';
import type { HttpResponse } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useActiveCookieJar } from './useActiveCookieJar';
import { useActiveEnvironment } from './useActiveEnvironment';
import { getActiveCookieJar } from './useActiveCookieJar';
import { getActiveEnvironment } from './useActiveEnvironment';
import { useAlert } from './useAlert';
import { useFastMutation } from './useFastMutation';
export function useSendAnyHttpRequest() {
const alert = useAlert();
const [environment] = useActiveEnvironment();
const [activeCookieJar] = useActiveCookieJar();
return useFastMutation<HttpResponse | null, string, string | null>({
mutationKey: ['send_any_request'],
mutationFn: async (id) => {
@@ -21,8 +19,8 @@ export function useSendAnyHttpRequest() {
return invokeCmd('cmd_send_http_request', {
request,
environmentId: environment?.id,
cookieJarId: activeCookieJar?.id,
environmentId: getActiveEnvironment()?.id,
cookieJarId: getActiveCookieJar()?.id,
});
},
onSettled: () => trackEvent('http_request', 'send'),

View File

@@ -0,0 +1,45 @@
import { useCallback, useEffect, useState } from 'react';
import { jotaiStore } from '../lib/jotai';
import { setKeyValue } from '../lib/keyValueStore';
import { getActiveWorkspaceId } from './useActiveWorkspace';
import { getKeyValue, keyValuesAtom } from './useKeyValue';
function kvKey(workspaceId: string | null) {
return ['sidebar_collapsed', workspaceId ?? 'n/a'];
}
export function useSidebarItemCollapsed(itemId: string) {
const [isCollapsed, setIsCollapsed] = useState<boolean>(
getSidebarCollapsedMap()[itemId] === true,
);
useEffect(
() =>
jotaiStore.sub(keyValuesAtom, () => {
setIsCollapsed(getSidebarCollapsedMap()[itemId] === true);
}),
[itemId],
);
const toggle = useCallback(() => {
setKeyValue({
key: kvKey(getActiveWorkspaceId()),
namespace: 'no_sync',
value: { ...getSidebarCollapsedMap(), [itemId]: !isCollapsed },
}).catch(console.error);
}, [isCollapsed, itemId]);
return [isCollapsed, toggle] as const;
}
export function getSidebarCollapsedMap() {
const activeWorkspaceId = getActiveWorkspaceId();
if (activeWorkspaceId == null) return {};
const value = getKeyValue<Record<string, boolean>>({
key: kvKey(activeWorkspaceId),
fallback: {},
namespace: 'no_sync',
});
return value;
}

View File

@@ -1,8 +1,8 @@
import { useQueryClient } from '@tanstack/react-query';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import type { AnyModel } from '@yaakapp-internal/models';
import type { AnyModel, KeyValue } from '@yaakapp-internal/models';
import { jotaiStore } from '../lib/jotai';
import { extractKeyValue } from '../lib/keyValueStore';
import { buildKeyValueKey } from '../lib/keyValueStore';
import { modelsEq } from '../lib/model_util';
import { useActiveWorkspace } from './useActiveWorkspace';
import { cookieJarsAtom } from './useCookieJars';
@@ -13,7 +13,7 @@ import { grpcEventsQueryKey } from './useGrpcEvents';
import { grpcRequestsAtom } from './useGrpcRequests';
import { httpRequestsAtom } from './useHttpRequests';
import { httpResponsesAtom } from './useHttpResponses';
import { keyValueQueryKey } from './useKeyValue';
import { keyValueQueryKey, keyValuesAtom } from './useKeyValue';
import { useListenToTauriEvent } from './useListenToTauriEvent';
import { pluginsAtom } from './usePlugins';
import { useRequestUpdateKey } from './useRequestUpdateKey';
@@ -71,14 +71,11 @@ export function useSyncModelStores() {
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));
} else if (queryKey != null) {
// TODO: Convert all models to use Jotai
queryClient.setQueryData(queryKey, (current: unknown) => {
if (model.model === 'key_value') {
// Special-case for KeyValue
return extractKeyValue(model);
}
if (Array.isArray(current)) {
return updateModelList(model)(current);
}
@@ -111,7 +108,7 @@ export function useSyncModelStores() {
} else if (model.model === 'grpc_event') {
queryClient.setQueryData(grpcEventsQueryKey(model), removeModelById(model));
} else if (model.model === 'key_value') {
queryClient.setQueryData(keyValueQueryKey(model), undefined);
queryClient.setQueryData(keyValueQueryKey(model), removeModelByKeyValue(model));
} else if (model.model === 'cookie_jar') {
jotaiStore.set(cookieJarsAtom, removeModelById(model));
}
@@ -136,6 +133,18 @@ export function removeModelById<T extends { id: string }>(model: T) {
return (entries: T[] | undefined) => entries?.filter((e) => e.id !== model.id) ?? [];
}
export function removeModelByKeyValue(model: KeyValue) {
return (entries: KeyValue[] | undefined) =>
entries?.filter(
(e) =>
!(
e.namespace === model.namespace &&
buildKeyValueKey(e.key) === buildKeyValueKey(model.key) &&
e.value == model.value
),
) ?? [];
}
const shouldIgnoreModel = (payload: AnyModel, windowLabel: string) => {
if (windowLabel === getCurrentWebviewWindow().label) {
// Never ignore same-window updates

View File

@@ -1,7 +1,7 @@
import { useSetAtom } from 'jotai/index';
import { useEffect } from 'react';
import { jotaiStore } from '../lib/jotai';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { activeWorkspaceIdAtom, getActiveWorkspaceId } from './useActiveWorkspace';
import { cookieJarsAtom } from './useCookieJars';
import { environmentsAtom } from './useEnvironments';
import { foldersAtom } from './useFolders';
@@ -9,36 +9,28 @@ import { grpcConnectionsAtom } from './useGrpcConnections';
import { grpcRequestsAtom } from './useGrpcRequests';
import { httpRequestsAtom } from './useHttpRequests';
import { httpResponsesAtom } from './useHttpResponses';
import { keyValuesAtom } from './useKeyValue';
export function useSyncWorkspaceChildModels() {
const setCookieJars = useSetAtom(cookieJarsAtom);
const setFolders = useSetAtom(foldersAtom);
const setHttpRequests = useSetAtom(httpRequestsAtom);
const setHttpResponses = useSetAtom(httpResponsesAtom);
const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
const setEnvironments = useSetAtom(environmentsAtom);
const workspace = useActiveWorkspace();
const workspaceId = workspace?.id;
useEffect(() => {
if (workspaceId == null) {
return;
}
(async function () {
console.log('Syncing model stores', { workspaceId });
// Set the things we need first, first
setHttpRequests(await invokeCmd('cmd_list_http_requests', { workspaceId }));
setGrpcRequests(await invokeCmd('cmd_list_grpc_requests', { workspaceId }));
setFolders(await invokeCmd('cmd_list_folders', { workspaceId }));
// Then, set the rest
setCookieJars(await invokeCmd('cmd_list_cookie_jars', { workspaceId }));
setHttpResponses(await invokeCmd('cmd_list_http_responses', { workspaceId }));
setGrpcConnections(await invokeCmd('cmd_list_grpc_connections', { workspaceId }));
setEnvironments(await invokeCmd('cmd_list_environments', { workspaceId }));
})().catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workspaceId]);
jotaiStore.sub(activeWorkspaceIdAtom, sync);
sync().catch(console.error);
}, []);
}
async function sync() {
const workspaceId = getActiveWorkspaceId();
const args = { workspaceId };
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));
jotaiStore.set(environmentsAtom, await invokeCmd('cmd_list_environments', args));
}

View File

@@ -1,14 +1,23 @@
import { useQuery } from '@tanstack/react-query';
import type { GetTemplateFunctionsResponse } from '@yaakapp-internal/plugin';
import type { GetTemplateFunctionsResponse, TemplateFunction } from '@yaakapp-internal/plugin';
import { atom, useAtomValue } from 'jotai';
import { useSetAtom } from 'jotai/index';
import { useState } from 'react';
import { invokeCmd } from '../lib/tauri';
import { usePluginsKey } from './usePlugins';
const templateFunctionsAtom = atom<TemplateFunction[]>([]);
export function useTemplateFunctions() {
return useAtomValue(templateFunctionsAtom);
}
export function useSubscribeTemplateFunctions() {
const pluginsKey = usePluginsKey();
const [numFns, setNumFns] = useState<number>(0);
const setAtom = useSetAtom(templateFunctionsAtom);
const result = useQuery({
useQuery({
queryKey: ['template_functions', pluginsKey],
// Fetch periodically until functions are returned
// NOTE: visibilitychange (refetchOnWindowFocus) does not work on Windows, so we'll rely on this logic
@@ -19,9 +28,9 @@ export function useTemplateFunctions() {
queryFn: async () => {
const result = await invokeCmd<GetTemplateFunctionsResponse[]>('cmd_template_functions');
setNumFns(result.length);
return result;
const functions = result.flatMap((r) => r.functions) ?? [];
setAtom(functions);
return functions;
},
});
return result.data?.flatMap((r) => r.functions) ?? [];
}