Better tauri listeners and stuff

This commit is contained in:
Gregory Schier
2023-03-30 09:05:54 -07:00
parent d2e0717d91
commit bb41f0e4fe
23 changed files with 305 additions and 794 deletions

View File

@@ -1,5 +1,3 @@
import { useRef } from 'react';
import { useMount } from 'react-use';
import { Button } from '../components/core/Button';
import { HStack } from '../components/core/Stacks';

View File

@@ -14,7 +14,7 @@ export function useDeleteRequest(id: string | null) {
},
onSuccess: async () => {
if (workspaceId === null || id === null) return;
await queryClient.invalidateQueries(requestsQueryKey(workspaceId));
await queryClient.invalidateQueries(requestsQueryKey({ workspaceId }));
},
});
}

View File

@@ -11,7 +11,7 @@ export function useDeleteResponses(requestId?: string) {
},
onSuccess: async () => {
if (!requestId) return;
await queryClient.invalidateQueries(responsesQueryKey(requestId));
await queryClient.invalidateQueries(responsesQueryKey({ requestId }));
},
});
}

View File

@@ -3,7 +3,7 @@ import { invoke } from '@tauri-apps/api';
import type { HttpRequest } from '../lib/models';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function requestsQueryKey(workspaceId: string) {
export function requestsQueryKey({ workspaceId }: { workspaceId: string }) {
return ['http_requests', { workspaceId }];
}
@@ -12,7 +12,7 @@ export function useRequests() {
return (
useQuery({
enabled: workspaceId != null,
queryKey: requestsQueryKey(workspaceId ?? 'n/a'),
queryKey: requestsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('requests', { workspaceId })) as HttpRequest[];

View File

@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { HttpResponse } from '../lib/models';
export function responsesQueryKey(requestId: string) {
export function responsesQueryKey({ requestId }: { requestId: string }) {
return ['http_responses', { requestId }];
}
@@ -11,7 +11,7 @@ export function useResponses(requestId: string | null) {
useQuery<HttpResponse[]>({
enabled: requestId !== null,
initialData: [],
queryKey: responsesQueryKey(requestId ?? 'n/a'),
queryKey: responsesQueryKey({ requestId: requestId ?? 'n/a' }),
queryFn: async () => {
return (await invoke('responses', {
requestId,

View File

@@ -1,4 +1,5 @@
import { useCallback } from 'react';
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
import { useKeyValue } from './useKeyValue';
const START_WIDTH = 200;
@@ -15,6 +16,7 @@ export interface SidebarDisplay {
export function useSidebarDisplay() {
const display = useKeyValue<SidebarDisplay>({
namespace: NAMESPACE_NO_SYNC,
key: sidebarDisplayKey,
defaultValue: sidebarDisplayDefaultValue,
});

View File

@@ -1,70 +1,136 @@
import { useQueryClient } from '@tanstack/react-query';
import { listen } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api';
import type { EventCallback } from '@tauri-apps/api/event';
import { appWindow } from '@tauri-apps/api/window';
import { useEffect } from 'react';
import { matchPath } from 'react-router-dom';
import { useEffectOnce } from 'react-use';
import { DEFAULT_FONT_SIZE } from '../lib/constants';
import { debounce } from '../lib/debounce';
import type { HttpRequest } from '../lib/models';
import { extractKeyValue, NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
import type { HttpRequest, HttpResponse, KeyValue, Model, Workspace } from '../lib/models';
import { modelsEq } from '../lib/models';
import { keyValueQueryKey } from './useKeyValue';
import { requestsQueryKey } from './useRequests';
import { useRequestUpdateKey } from './useRequestUpdateKey';
import { responsesQueryKey } from './useResponses';
import { routePaths } from './useRoutes';
import { useSidebarDisplay } from './useSidebarDisplay';
import { workspacesQueryKey } from './useWorkspaces';
const unsubFns: (() => void)[] = [];
export const UPDATE_DEBOUNCE_MILLIS = 1000;
export const UPDATE_DEBOUNCE_MILLIS = 100;
export function useTauriListeners() {
const sidebarDisplay = useSidebarDisplay();
const queryClient = useQueryClient();
const { wasUpdatedExternally } = useRequestUpdateKey(null);
useEffect(() => {
useEffectOnce(() => {
let unmounted = false;
appWindow
.listen('toggle_sidebar', async () => {
sidebarDisplay.toggle();
})
.then((unsub) => {
// eslint-disable-next-line @typescript-eslint/ban-types
function listen<T>(event: string, fn: EventCallback<T>) {
appWindow.listen(event, fn).then((unsub) => {
if (unmounted) unsub();
else unsubFns.push(unsub);
});
}
listen('refresh', () => {
location.reload();
}).then((unsub) => {
if (unmounted) unsub();
else unsubFns.push(unsub);
function listenDebounced<T>(event: string, fn: EventCallback<T>) {
listen(event, debounce(fn, UPDATE_DEBOUNCE_MILLIS));
}
listen('toggle_sidebar', sidebarDisplay.toggle);
listen('refresh', () => location.reload());
listenDebounced('updated_key_value', ({ payload: keyValue }: { payload: KeyValue }) => {
if (keyValue.namespace !== NAMESPACE_NO_SYNC) {
queryClient.setQueryData(keyValueQueryKey(keyValue), extractKeyValue(keyValue));
}
});
appWindow
.listen(
'updated_request',
debounce(({ payload: request }: { payload: HttpRequest }) => {
queryClient.setQueryData(
requestsQueryKey(request.workspaceId),
(requests: HttpRequest[] = []) => {
const newRequests = [];
let found = false;
for (const r of requests) {
if (r.id === request.id) {
found = true;
newRequests.push(request);
} else {
newRequests.push(r);
}
}
if (!found) {
newRequests.push(request);
}
setTimeout(() => wasUpdatedExternally(request.id), 50);
return newRequests;
},
);
}, UPDATE_DEBOUNCE_MILLIS),
)
.then((unsub) => {
if (unmounted) unsub();
else unsubFns.push(unsub);
});
listenDebounced('updated_model', ({ payload }: { payload: Model }) => {
const queryKey =
payload.model === 'http_request'
? requestsQueryKey(payload)
: payload.model === 'http_response'
? responsesQueryKey(payload)
: payload.model === 'workspace'
? workspacesQueryKey(payload)
: payload.model === 'key_value'
? keyValueQueryKey(payload)
: null;
if (queryKey === null) {
throw new Error('Unrecognized updated model ' + payload.model);
}
const skipSync = payload.model === 'key_value' && payload.namespace === NAMESPACE_NO_SYNC;
if (!skipSync) {
queryClient.setQueryData(queryKey, (values: Model[] = []) => {
const newValues = [];
let found = false;
for (const v of values) {
if (modelsEq(v, payload)) {
found = true;
newValues.push(payload);
} else {
newValues.push(v);
}
}
// Doesn't exist already, so add it to the list
if (!found) newValues.push(payload);
if (payload.model === 'http_request') {
setTimeout(() => wasUpdatedExternally(payload.id), 50);
}
return newValues;
});
}
});
listen('deleted_model', ({ payload: model }: { payload: Model }) => {
function removeById<T extends { id: string }>(model: T) {
return (entries: T[] | undefined) => entries?.filter((e) => e.id !== model.id);
}
if (model.model === 'workspace') {
queryClient.setQueryData(workspacesQueryKey(), removeById<Workspace>(model));
} else if (model.model === 'http_request') {
queryClient.setQueryData(requestsQueryKey(model), removeById<HttpRequest>(model));
} else if (model.model === 'http_response') {
queryClient.setQueryData(responsesQueryKey(model), removeById<HttpResponse>(model));
} else if (model.model === 'key_value') {
queryClient.setQueryData(keyValueQueryKey(model), undefined);
}
});
listen('send_request', async () => {
const params = matchPath(routePaths.request(), window.location.pathname);
const requestId = params?.params.requestId;
if (typeof requestId !== 'string') {
return;
}
await invoke('send_request', { requestId });
});
listen('zoom', ({ payload: zoomDelta }: { payload: number }) => {
const fontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize);
let newFontSize;
if (zoomDelta === 0) {
newFontSize = DEFAULT_FONT_SIZE;
} else if (zoomDelta > 0) {
newFontSize = Math.min(fontSize * 1.1, DEFAULT_FONT_SIZE * 5);
} else if (zoomDelta < 0) {
newFontSize = Math.max(fontSize * 0.9, DEFAULT_FONT_SIZE * 0.4);
}
document.documentElement.style.fontSize = `${newFontSize}px`;
});
return () => {
unmounted = true;
@@ -72,5 +138,5 @@ export function useTauriListeners() {
unsub();
}
};
}, []);
});
}

View File

@@ -1,16 +0,0 @@
import { useState } from 'react';
export function useUniqueKey(len = 10): { key: string; regenerate: () => void } {
const [key, setKey] = useState<string>(() => generate(len));
return { key, wasUpdatedExternally: () => setKey(generate(len)) };
}
const CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
function generate(len: number): string {
const chars = [];
for (let i = 0; i < len; i++) {
chars.push(CHARS[Math.floor(Math.random() * CHARS.length)]);
}
return chars.join('');
}

View File

@@ -19,10 +19,8 @@ export function useUpdateAnyRequest() {
onMutate: async ({ id, update }) => {
const request = await getRequest(id);
if (request === null) return;
queryClient.setQueryData(
requestsQueryKey(request?.workspaceId),
(requests: HttpRequest[] | undefined) =>
requests?.map((r) => (r.id === request.id ? update(r) : r)),
queryClient.setQueryData(requestsQueryKey(request), (requests: HttpRequest[] | undefined) =>
requests?.map((r) => (r.id === request.id ? update(r) : r)),
);
},
});

View File

@@ -1,6 +1,5 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import { appWindow } from '@tauri-apps/api/window';
import type { HttpRequest } from '../lib/models';
import { getRequest } from '../lib/store';
import { requestsQueryKey } from './useRequests';
@@ -21,14 +20,9 @@ export function useUpdateRequest(id: string | null) {
const request = await getRequest(id);
if (request === null) return;
// Sync updatedBy so that the UI doesn't think the update is coming from elsewhere
request.updatedBy = appWindow.label;
const newRequest = typeof v === 'function' ? v(request) : { ...request, ...v };
queryClient.setQueryData(
requestsQueryKey(request?.workspaceId),
(requests: HttpRequest[] | undefined) =>
requests?.map((r) => (r.id === newRequest.id ? newRequest : r)),
queryClient.setQueryData(requestsQueryKey(request), (requests: HttpRequest[] | undefined) =>
requests?.map((r) => (r.id === newRequest.id ? newRequest : r)),
);
},
});

View File

@@ -1,8 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { Workspace } from '../lib/models';
import { useQuery } from '@tanstack/react-query';
export function workspacesQueryKey() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/ban-types
export function workspacesQueryKey(_?: {}) {
return ['workspaces'];
}