mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-06-28 04:46:20 +02:00
2024.5.0 (#39)
This commit is contained in:
@@ -32,10 +32,10 @@ export function Confirm({ onHide, onResult, confirmText, variant = 'confirm' }:
|
||||
|
||||
return (
|
||||
<HStack space={2} justifyContent="start" className="mt-2 mb-4 flex-row-reverse">
|
||||
<Button className="focus" color={colors[variant]} onClick={handleSuccess}>
|
||||
<Button color={colors[variant]} onClick={handleSuccess}>
|
||||
{confirmText ?? confirmButtonTexts[variant]}
|
||||
</Button>
|
||||
<Button className="focus" color="gray" onClick={handleHide}>
|
||||
<Button onClick={handleHide} variant="border">
|
||||
Cancel
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
@@ -52,10 +52,10 @@ export function Prompt({
|
||||
onChange={setValue}
|
||||
/>
|
||||
<HStack space={2} justifyContent="end">
|
||||
<Button className="focus" color="gray" onClick={onHide}>
|
||||
<Button onClick={onHide} variant="border">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" className="focus" color="primary">
|
||||
<Button type="submit" color="primary">
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</HStack>
|
||||
|
||||
@@ -16,5 +16,5 @@ export function useAppInfo() {
|
||||
const metadata = await invoke('cmd_metadata');
|
||||
return metadata as AppInfo;
|
||||
},
|
||||
});
|
||||
}).data;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { QUERY_ENVIRONMENT_ID } from './useActiveEnvironmentId';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useActiveRequestId } from './useActiveRequestId';
|
||||
import type { Environment } from '../lib/models';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { Environment } from '../lib/models';
|
||||
import { QUERY_ENVIRONMENT_ID } from './useActiveEnvironmentId';
|
||||
import { useActiveRequestId } from './useActiveRequestId';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
|
||||
export type RouteParamsWorkspace = {
|
||||
workspaceId: string;
|
||||
@@ -18,6 +18,9 @@ export const routePaths = {
|
||||
workspaces() {
|
||||
return '/workspaces';
|
||||
},
|
||||
workspaceSettings({ workspaceId } = { workspaceId: ':workspaceId' } as RouteParamsWorkspace) {
|
||||
return `/workspaces/${workspaceId}/settings`;
|
||||
},
|
||||
workspace(
|
||||
{ workspaceId, environmentId } = {
|
||||
workspaceId: ':workspaceId',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { readText, writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useToast } from '../components/ToastContext';
|
||||
import { useWindowFocus } from './useWindowFocus';
|
||||
import { createGlobalState } from 'react-use';
|
||||
|
||||
@@ -8,6 +9,7 @@ const useClipboardTextState = createGlobalState<string>('');
|
||||
export function useClipboardText() {
|
||||
const [value, setValue] = useClipboardTextState();
|
||||
const focused = useWindowFocus();
|
||||
const toast = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
readText().then(setValue);
|
||||
@@ -16,9 +18,14 @@ export function useClipboardText() {
|
||||
const setText = useCallback(
|
||||
(text: string) => {
|
||||
writeText(text).catch(console.error);
|
||||
toast.show({
|
||||
id: 'copied',
|
||||
variant: 'copied',
|
||||
message: 'Copied to clipboard',
|
||||
});
|
||||
setValue(text);
|
||||
},
|
||||
[setValue],
|
||||
[setValue, toast],
|
||||
);
|
||||
|
||||
return [value, setText] as const;
|
||||
|
||||
@@ -8,7 +8,7 @@ export function useCommandPalette() {
|
||||
const appInfo = useAppInfo();
|
||||
useHotKey('command_palette.toggle', () => {
|
||||
// Disabled in production for now
|
||||
if (!appInfo.data?.isDev) {
|
||||
if (!appInfo?.isDev) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||
import { useState } from 'react';
|
||||
import { useToast } from '../components/ToastContext';
|
||||
import { useClipboardText } from './useClipboardText';
|
||||
|
||||
export function useCopyAsCurl(requestId: string) {
|
||||
const [checked, setChecked] = useState<boolean>(false);
|
||||
const toast = useToast();
|
||||
return [
|
||||
checked,
|
||||
async () => {
|
||||
const cmd: string = await invoke('cmd_request_to_curl', { requestId });
|
||||
await writeText(cmd);
|
||||
setChecked(true);
|
||||
setTimeout(() => setChecked(false), 800);
|
||||
toast.show({
|
||||
variant: 'copied',
|
||||
message: 'Curl copied to clipboard',
|
||||
});
|
||||
return cmd;
|
||||
},
|
||||
] as const;
|
||||
const [, copy] = useClipboardText();
|
||||
return async () => {
|
||||
const cmd: string = await invoke('cmd_request_to_curl', { requestId });
|
||||
copy(cmd);
|
||||
return cmd;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,6 @@ export function useFilterResponse({
|
||||
|
||||
return (await invoke('cmd_filter_response', { responseId, filter })) as string | null;
|
||||
},
|
||||
}).data ?? null
|
||||
}).data ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ export type HotkeyAction =
|
||||
| 'sidebar.focus'
|
||||
| 'sidebar.toggle'
|
||||
| 'urlBar.focus'
|
||||
| 'command_palette.toggle';
|
||||
| 'command_palette.toggle'
|
||||
| 'app.zoom_in'
|
||||
| 'app.zoom_out'
|
||||
| 'app.zoom_reset';
|
||||
|
||||
const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'environmentEditor.toggle': ['CmdCtrl+Shift+e'],
|
||||
@@ -37,6 +40,9 @@ const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'sidebar.toggle': ['CmdCtrl+b'],
|
||||
'urlBar.focus': ['CmdCtrl+l'],
|
||||
'command_palette.toggle': ['CmdCtrl+k'],
|
||||
'app.zoom_in': ['CmdCtrl+='],
|
||||
'app.zoom_out': ['CmdCtrl+-'],
|
||||
'app.zoom_reset': ['CmdCtrl+0'],
|
||||
};
|
||||
|
||||
const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||
@@ -54,6 +60,9 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||
'sidebar.toggle': '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',
|
||||
};
|
||||
|
||||
export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[];
|
||||
@@ -114,6 +123,7 @@ export function useHotKey(
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
callbackRef.current(e);
|
||||
currentKeys.current.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useWindowSize } from 'react-use';
|
||||
import { useDebouncedValue } from './useDebouncedValue';
|
||||
|
||||
export function useIsFullscreen() {
|
||||
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
||||
const windowSize = useWindowSize();
|
||||
const debouncedWindowWidth = useDebouncedValue(windowSize.width);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
// Fullscreen state isn't updated right after resize event on Mac (needs to wait for animation) so
|
||||
// we'll poll for 10 seconds to see if it changes. Hopefully Tauri eventually adds a way to listen
|
||||
// for this.
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
const newIsFullscreen = await getCurrent().isFullscreen();
|
||||
if (newIsFullscreen !== isFullscreen) {
|
||||
setIsFullscreen(newIsFullscreen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [windowSize, isFullscreen]);
|
||||
// NOTE: Fullscreen state isn't updated right after resize event on Mac (needs to wait for animation) so
|
||||
// we'll wait for a bit using the debounced window size. Hopefully Tauri eventually adds a way to listen
|
||||
// for fullscreen change events.
|
||||
|
||||
return isFullscreen;
|
||||
return (
|
||||
useQuery({
|
||||
queryKey: ['is_fullscreen', debouncedWindowWidth],
|
||||
queryFn: async () => {
|
||||
return getCurrent().isFullscreen();
|
||||
},
|
||||
}).data ?? false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { buildKeyValueKey, getKeyValue, setKeyValue } from '../lib/keyValueStore';
|
||||
|
||||
const DEFAULT_NAMESPACE = 'app';
|
||||
const DEFAULT_NAMESPACE = 'global';
|
||||
|
||||
export function keyValueQueryKey({
|
||||
namespace = DEFAULT_NAMESPACE,
|
||||
@@ -20,7 +20,7 @@ export function useKeyValue<T extends Object | null>({
|
||||
key,
|
||||
fallback,
|
||||
}: {
|
||||
namespace?: 'app' | 'no_sync' | 'global';
|
||||
namespace?: 'global' | 'no_sync';
|
||||
key: string | string[];
|
||||
fallback: T;
|
||||
}) {
|
||||
@@ -51,7 +51,8 @@ export function useKeyValue<T extends Object | null>({
|
||||
await mutate.mutateAsync(value);
|
||||
}
|
||||
},
|
||||
[fallback, key, mutate, namespace],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[fallback, key, namespace],
|
||||
);
|
||||
|
||||
const reset = useCallback(async () => mutate.mutateAsync(fallback), [mutate, fallback]);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { Button } from '../components/core/Button';
|
||||
import { useToast } from '../components/ToastContext';
|
||||
import { useListenToTauriEvent } from './useListenToTauriEvent';
|
||||
import { Button } from '../components/core/Button';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
export function useNotificationToast() {
|
||||
const toast = useToast();
|
||||
@@ -31,7 +31,7 @@ export function useNotificationToast() {
|
||||
actionLabel && actionUrl ? (
|
||||
<Button
|
||||
size="xs"
|
||||
color="gray"
|
||||
color="secondary"
|
||||
className="mr-auto min-w-[5rem]"
|
||||
onClick={() => {
|
||||
toast.hide(payload.id);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { GrpcConnection, GrpcRequest } from '../lib/models';
|
||||
import { useGrpcConnections } from './useGrpcConnections';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
import { useLatestGrpcConnection } from './useLatestGrpcConnection';
|
||||
|
||||
export function usePinnedGrpcConnection(activeRequest: GrpcRequest) {
|
||||
const latestConnection = useLatestGrpcConnection(activeRequest.id);
|
||||
const { set: setPinnedConnectionId, value: pinnedConnectionId } = useKeyValue<string | null>({
|
||||
// Key on latest connection instead of activeRequest because connections change out of band of active request
|
||||
key: ['pinned_grpc_connection_id', latestConnection?.id ?? 'n/a'],
|
||||
fallback: null,
|
||||
namespace: 'global',
|
||||
});
|
||||
const connections = useGrpcConnections(activeRequest.id);
|
||||
const activeConnection: GrpcConnection | null =
|
||||
connections.find((r) => r.id === pinnedConnectionId) ?? latestConnection;
|
||||
|
||||
return { activeConnection, setPinnedConnectionId, pinnedConnectionId, connections } as const;
|
||||
}
|
||||
@@ -1,28 +1,19 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
import type { HttpRequest, HttpResponse } from '../lib/models';
|
||||
import { useHttpResponses } from './useHttpResponses';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
import { useLatestHttpResponse } from './useLatestHttpResponse';
|
||||
|
||||
const usePinnedResponseIdState = createGlobalState<string | null>(null);
|
||||
|
||||
export function usePinnedHttpResponse(activeRequest: HttpRequest) {
|
||||
const [pinnedResponseId, setPinnedResponseId] = usePinnedResponseIdState();
|
||||
const latestResponse = useLatestHttpResponse(activeRequest.id);
|
||||
const { set: setPinnedResponseId, value: pinnedResponseId } = useKeyValue<string | null>({
|
||||
// Key on latest response instead of activeRequest because responses change out of band of active request
|
||||
key: ['pinned_http_response_id', latestResponse?.id ?? 'n/a'],
|
||||
fallback: null,
|
||||
namespace: 'global',
|
||||
});
|
||||
const responses = useHttpResponses(activeRequest.id);
|
||||
const activeResponse: HttpResponse | null = pinnedResponseId
|
||||
? responses.find((r) => r.id === pinnedResponseId) ?? null
|
||||
: latestResponse ?? null;
|
||||
const activeResponse: HttpResponse | null =
|
||||
responses.find((r) => r.id === pinnedResponseId) ?? latestResponse;
|
||||
|
||||
// Unset pinned response when a new one comes in
|
||||
useEffect(() => setPinnedResponseId(null), [responses.length, setPinnedResponseId]);
|
||||
|
||||
const setPinnedResponse = useCallback(
|
||||
(r: HttpResponse) => {
|
||||
setPinnedResponseId(r.id);
|
||||
},
|
||||
[setPinnedResponseId],
|
||||
);
|
||||
|
||||
return { activeResponse, setPinnedResponse, pinnedResponseId, responses } as const;
|
||||
return { activeResponse, setPinnedResponseId, pinnedResponseId, responses } as const;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
getCSSAppearance,
|
||||
getWindowAppearance,
|
||||
subscribeToWindowAppearanceChange,
|
||||
} from '../lib/theme/appearance';
|
||||
import { type Appearance } from '../lib/theme/window';
|
||||
|
||||
export function usePreferredAppearance() {
|
||||
const [preferredAppearance, setPreferredAppearance] = useState<Appearance>(getCSSAppearance());
|
||||
|
||||
useEffect(() => {
|
||||
getWindowAppearance().then(setPreferredAppearance);
|
||||
return subscribeToWindowAppearanceChange(setPreferredAppearance);
|
||||
}, []);
|
||||
|
||||
return preferredAppearance;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { usePreferredAppearance } from './usePreferredAppearance';
|
||||
import { useSettings } from './useSettings';
|
||||
|
||||
export function useResolvedAppearance() {
|
||||
const preferredAppearance = usePreferredAppearance();
|
||||
|
||||
const settings = useSettings();
|
||||
const appearance =
|
||||
settings == null || settings?.appearance === 'system'
|
||||
? preferredAppearance
|
||||
: settings.appearance;
|
||||
|
||||
return appearance;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { isThemeDark } from '../lib/theme/window';
|
||||
import { useResolvedAppearance } from './useResolvedAppearance';
|
||||
import { useSettings } from './useSettings';
|
||||
import { useThemes } from './useThemes';
|
||||
|
||||
export function useResolvedTheme() {
|
||||
const appearance = useResolvedAppearance();
|
||||
const settings = useSettings();
|
||||
const { themes, fallback } = useThemes();
|
||||
|
||||
const darkThemes = themes.filter((t) => isThemeDark(t));
|
||||
const lightThemes = themes.filter((t) => !isThemeDark(t));
|
||||
|
||||
const dark = darkThemes.find((t) => t.id === settings?.themeDark) ?? fallback.dark;
|
||||
const light = lightThemes.find((t) => t.id === settings?.themeLight) ?? fallback.light;
|
||||
|
||||
const active = appearance === 'dark' ? dark : light;
|
||||
|
||||
return { dark, light, active };
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { Appearance } from '../lib/theme/window';
|
||||
import {
|
||||
setAppearanceOnDocument,
|
||||
getPreferredAppearance,
|
||||
subscribeToPreferredAppearanceChange,
|
||||
} from '../lib/theme/window';
|
||||
import { useSettings } from './useSettings';
|
||||
|
||||
export function useSyncAppearance() {
|
||||
const [preferredAppearance, setPreferredAppearance] = useState<Appearance>(
|
||||
getPreferredAppearance(),
|
||||
);
|
||||
|
||||
const settings = useSettings();
|
||||
|
||||
// Set appearance when preferred theme changes
|
||||
useEffect(() => {
|
||||
return subscribeToPreferredAppearanceChange(setPreferredAppearance);
|
||||
}, []);
|
||||
|
||||
const appearance =
|
||||
settings == null || settings?.appearance === 'system'
|
||||
? preferredAppearance
|
||||
: settings.appearance;
|
||||
|
||||
useEffect(() => {
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
setAppearanceOnDocument(settings.appearance as Appearance);
|
||||
}, [appearance, settings]);
|
||||
|
||||
return { appearance };
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { useEffect } from 'react';
|
||||
import type { YaakTheme } from '../lib/theme/window';
|
||||
import { addThemeStylesToDocument, setThemeOnDocument } from '../lib/theme/window';
|
||||
import { useResolvedTheme } from './useResolvedTheme';
|
||||
|
||||
export function useSyncThemeToDocument() {
|
||||
const theme = useResolvedTheme();
|
||||
|
||||
useEffect(() => {
|
||||
setThemeOnDocument(theme.active);
|
||||
emitBgChange(theme.active);
|
||||
}, [theme.active]);
|
||||
|
||||
useEffect(() => {
|
||||
addThemeStylesToDocument(theme.active);
|
||||
}, [theme.active]);
|
||||
}
|
||||
|
||||
function emitBgChange(t: YaakTheme) {
|
||||
if (t.background == null) return;
|
||||
emit('yaak_bg_changed', t.background.hex()).catch(console.error);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// import { useEffect } from 'react';
|
||||
// import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
// import { useActiveEnvironment } from './useActiveEnvironment';
|
||||
// import { useActiveRequest } from './useActiveRequest';
|
||||
// import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
|
||||
export function useSyncWindowTitle() {
|
||||
// const activeRequest = useActiveRequest();
|
||||
// const activeWorkspace = useActiveWorkspace();
|
||||
// const activeEnvironment = useActiveEnvironment();
|
||||
// useEffect(() => {
|
||||
// let newTitle = activeWorkspace ? activeWorkspace.name : 'Yaak';
|
||||
// if (activeEnvironment) {
|
||||
// newTitle += ` [${activeEnvironment.name}]`;
|
||||
// }
|
||||
// if (activeRequest) {
|
||||
// newTitle += ` – ${fallbackRequestName(activeRequest)}`;
|
||||
// }
|
||||
//
|
||||
// // TODO: This resets the stoplight position so we can't use it yet
|
||||
// // getCurrent().setTitle(newTitle).catch(console.error);
|
||||
// }, [activeEnvironment, activeRequest, activeWorkspace]);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
||||
import { useEffect } from 'react';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||
import { useActiveRequest } from './useActiveRequest';
|
||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
import { useOsInfo } from './useOsInfo';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
|
||||
export function useSyncWorkspaceRequestTitle() {
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
const osInfo = useOsInfo();
|
||||
|
||||
useEffect(() => {
|
||||
if (osInfo?.osType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newTitle = activeWorkspace ? activeWorkspace.name : 'Yaak';
|
||||
if (activeEnvironment) {
|
||||
newTitle += ` [${activeEnvironment.name}]`;
|
||||
}
|
||||
if (activeRequest) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
newTitle += ` – ${fallbackRequestName(activeRequest)}`;
|
||||
}
|
||||
|
||||
// TODO: This resets the stoplight position so we can't use it on macOS yet. So we send
|
||||
// a custom command instead
|
||||
if (osInfo?.osType !== 'macos') {
|
||||
getCurrent().setTitle(newTitle).catch(console.error);
|
||||
} else {
|
||||
emit('yaak_title_changed', newTitle).catch(console.error);
|
||||
}
|
||||
}, [activeEnvironment, activeRequest, activeWorkspace, osInfo?.osType]);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defaultDarkTheme, defaultLightTheme, yaakThemes } from '../lib/theme/themes';
|
||||
|
||||
export function useThemes() {
|
||||
const dark = defaultDarkTheme;
|
||||
const light = defaultLightTheme;
|
||||
|
||||
const otherThemes = yaakThemes
|
||||
.filter((t) => t.id !== dark.id && t.id !== light.id)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const themes = [dark, light, ...otherThemes];
|
||||
return { themes, fallback: { dark, light } };
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { Settings } from '../lib/models';
|
||||
import { settingsQueryKey } from './useSettings';
|
||||
import { useSettings } from './useSettings';
|
||||
|
||||
export function useUpdateSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
const settings = useSettings();
|
||||
|
||||
return useMutation<void, unknown, Settings>({
|
||||
mutationFn: async (settings) => {
|
||||
await invoke('cmd_update_settings', { settings });
|
||||
},
|
||||
onMutate: async (settings) => {
|
||||
queryClient.setQueryData<Settings[]>(settingsQueryKey(), [settings]);
|
||||
return useMutation<void, unknown, Partial<Settings>>({
|
||||
mutationFn: async (patch) => {
|
||||
if (settings == null) return;
|
||||
const newSettings: Settings = { ...settings, ...patch };
|
||||
await invoke('cmd_update_settings', { settings: newSettings });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSettings } from './useSettings';
|
||||
import { useUpdateSettings } from './useUpdateSettings';
|
||||
|
||||
export function useZoom() {
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
const zoomIn = useCallback(() => {
|
||||
if (!settings) return;
|
||||
updateSettings.mutate({
|
||||
interfaceScale: Math.min(1.8, settings.interfaceScale * 1.1),
|
||||
});
|
||||
}, [settings, updateSettings]);
|
||||
|
||||
const zoomOut = useCallback(() => {
|
||||
if (!settings) return;
|
||||
updateSettings.mutate({
|
||||
interfaceScale: Math.max(0.4, settings.interfaceScale * 0.9),
|
||||
});
|
||||
}, [settings, updateSettings]);
|
||||
|
||||
const zoomReset = useCallback(() => {
|
||||
updateSettings.mutate({ ...settings, interfaceScale: 1 });
|
||||
}, [settings, updateSettings]);
|
||||
|
||||
return { zoomIn, zoomOut, zoomReset };
|
||||
}
|
||||
Reference in New Issue
Block a user