Start on plugin ctx API (#64)

This commit is contained in:
Gregory Schier
2024-08-14 06:42:54 -07:00
committed by GitHub
parent e47a2c5fab
commit 12f4c2c668
106 changed files with 1086 additions and 1219 deletions

View File

@@ -4,9 +4,8 @@ import type { KeyboardEvent, ReactNode } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
@@ -56,8 +55,8 @@ const MAX_PER_GROUP = 8;
export function CommandPalette({ onClose }: { onClose: () => void }) {
const [command, setCommand] = useDebouncedState<string>('', 150);
const [selectedItemKey, setSelectedItemKey] = useState<string | null>(null);
const [activeEnvironment, setActiveEnvironmentId] = useActiveEnvironment();
const routes = useAppRoutes();
const activeEnvironmentId = useActiveEnvironmentId();
const workspaces = useWorkspaces();
const environments = useEnvironments();
const recentEnvironments = useRecentEnvironments();
@@ -68,12 +67,11 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
const openWorkspace = useOpenWorkspace();
const createWorkspace = useCreateWorkspace();
const createHttpRequest = useCreateHttpRequest();
const { activeCookieJar } = useActiveCookieJar();
const [activeCookieJar] = useActiveCookieJar();
const createGrpcRequest = useCreateGrpcRequest();
const createEnvironment = useCreateEnvironment();
const dialog = useDialog();
const workspaceId = useActiveWorkspaceId();
const activeEnvironment = useActiveEnvironment();
const workspace = useActiveWorkspace();
const sendRequest = useSendAnyHttpRequest();
const renameRequest = useRenameRequest(activeRequest?.id ?? null);
const deleteRequest = useDeleteRequest(activeRequest?.id ?? null);
@@ -86,9 +84,9 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
label: 'Open Settings',
action: 'settings.show',
onSelect: async () => {
if (workspaceId == null) return;
if (workspace == null) return;
await invokeCmd('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId }),
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
label: 'settings',
title: 'Yaak Settings',
});
@@ -190,7 +188,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
routes.paths,
sendRequest,
setSidebarHidden,
workspaceId,
workspace,
]);
const sortedRequests = useMemo(() => {
@@ -271,7 +269,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
return routes.navigate('request', {
workspaceId: r.workspaceId,
requestId: r.id,
environmentId: activeEnvironmentId ?? undefined,
environmentId: activeEnvironment?.id,
});
},
});
@@ -290,7 +288,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
environmentGroup.items.push({
key: `switch-environment-${e.id}`,
label: e.name,
onSelect: () => routes.setEnvironment(e),
onSelect: () => setActiveEnvironmentId(e.id),
});
}
@@ -313,9 +311,9 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
workspaceCommands,
sortedRequests,
routes,
activeEnvironmentId,
activeEnvironment,
sortedEnvironments,
activeEnvironment?.id,
setActiveEnvironmentId,
sortedWorkspaces,
openWorkspace,
]);

View File

@@ -12,7 +12,7 @@ interface Props {
export const CookieDialog = function ({ cookieJarId }: Props) {
const updateCookieJar = useUpdateCookieJar(cookieJarId ?? null);
const cookieJars = useCookieJars();
const cookieJars = useCookieJars().data ?? [];
const cookieJar = cookieJars.find((c) => c.id === cookieJarId);
if (cookieJar == null) {

View File

@@ -12,8 +12,8 @@ import { InlineCode } from './core/InlineCode';
import { useDialog } from './DialogContext';
export function CookieDropdown() {
const cookieJars = useCookieJars();
const { activeCookieJar, setActiveCookieJarId } = useActiveCookieJar();
const cookieJars = useCookieJars().data ?? [];
const [activeCookieJar, setActiveCookieJarId] = useActiveCookieJar();
const updateCookieJar = useUpdateCookieJar(activeCookieJar?.id ?? null);
const deleteCookieJar = useDeleteCookieJar(activeCookieJar ?? null);
const createCookieJar = useCreateCookieJar();

View File

@@ -1,7 +1,6 @@
import classNames from 'classnames';
import { memo, useCallback, useMemo } from 'react';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useEnvironments } from '../hooks/useEnvironments';
import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
@@ -20,9 +19,8 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
...buttonProps
}: Props) {
const environments = useEnvironments();
const activeEnvironment = useActiveEnvironment();
const [activeEnvironment, setActiveEnvironmentId] = useActiveEnvironment();
const dialog = useDialog();
const routes = useAppRoutes();
const showEnvironmentDialog = useCallback(() => {
dialog.toggle({
@@ -43,9 +41,9 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
leftSlot: e.id === activeEnvironment?.id ? <Icon icon="check" /> : <Icon icon="empty" />,
onSelect: async () => {
if (e.id !== activeEnvironment?.id) {
routes.setEnvironment(e);
setActiveEnvironmentId(e.id);
} else {
routes.setEnvironment(null);
setActiveEnvironmentId(null);
}
},
}),
@@ -62,7 +60,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
onSelect: showEnvironmentDialog,
},
],
[activeEnvironment?.id, environments, routes, showEnvironmentDialog],
[activeEnvironment?.id, environments, setActiveEnvironmentId, showEnvironmentDialog],
);
return (

View File

@@ -2,7 +2,8 @@ import { useQueryClient } from '@tanstack/react-query';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import type { Model } from '@yaakapp/api';
import { useEffect } from 'react';
import { useAtiveWorkspaceChangedToast } from '../hooks/useAtiveWorkspaceChangedToast';
import { useEnsureActiveCookieJar, useMigrateActiveCookieJarId } from '../hooks/useActiveCookieJar';
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
import { useCopy } from '../hooks/useCopy';
import { environmentsQueryKey } from '../hooks/useEnvironments';
@@ -16,6 +17,7 @@ import { httpResponsesQueryKey } from '../hooks/useHttpResponses';
import { keyValueQueryKey } from '../hooks/useKeyValue';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { useNotificationToast } from '../hooks/useNotificationToast';
import { useRecentCookieJars } from '../hooks/useRecentCookieJars';
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
import { useRecentRequests } from '../hooks/useRecentRequests';
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
@@ -25,6 +27,7 @@ import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument';
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
import { workspacesQueryKey } from '../hooks/useWorkspaces';
import { useZoom } from '../hooks/useZoom';
import { extractKeyValue } from '../lib/keyValueStore';
import { modelsEq } from '../lib/models';
import { catppuccinMacchiato } from '../lib/theme/themes/catppuccin';
import { githubLight } from '../lib/theme/themes/github';
@@ -38,12 +41,17 @@ export function GlobalHooks() {
// Include here so they always update, even if no component references them
useRecentWorkspaces();
useRecentEnvironments();
useRecentCookieJars();
useRecentRequests();
// Other useful things
useSyncThemeToDocument();
useNotificationToast();
useAtiveWorkspaceChangedToast();
useActiveWorkspaceChangedToast();
useEnsureActiveCookieJar();
// TODO: Remove in future version
useMigrateActiveCookieJarId();
const toggleCommandPalette = useToggleCommandPalette();
useHotKey('command_palette.toggle', toggleCommandPalette);
@@ -96,21 +104,28 @@ export function GlobalHooks() {
model.model,
);
if (shouldIgnoreModel(model)) return;
if (shouldIgnoreModel(model, windowLabel)) return;
queryClient.setQueryData<Model[]>(queryKey, (values = []) => {
const index = values.findIndex((v) => modelsEq(v, model)) ?? -1;
if (index >= 0) {
return [...values.slice(0, index), model, ...values.slice(index + 1)];
} else {
return pushToFront ? [model, ...(values ?? [])] : [...(values ?? []), model];
queryClient.setQueryData(queryKey, (current: unknown) => {
if (model.model === 'key_value') {
// Special-case for KeyValue
return extractKeyValue(model);
}
if (Array.isArray(current)) {
const index = current.findIndex((v) => modelsEq(v, model)) ?? -1;
if (index >= 0) {
return [...current.slice(0, index), model, ...current.slice(index + 1)];
} else {
return pushToFront ? [model, ...(current ?? [])] : [...(current ?? []), model];
}
}
});
});
useListenToTauriEvent<ModelPayload>('deleted_model', ({ payload }) => {
const { model } = payload;
if (shouldIgnoreModel(model)) return;
const { model, windowLabel } = payload;
if (shouldIgnoreModel(model, windowLabel)) return;
if (model.model === 'workspace') {
queryClient.setQueryData(workspacesQueryKey(), removeById(model));
@@ -181,7 +196,11 @@ function removeById<T extends { id: string }>(model: T) {
return (entries: T[] | undefined) => entries?.filter((e) => e.id !== model.id);
}
const shouldIgnoreModel = (payload: Model) => {
const shouldIgnoreModel = (payload: Model, windowLabel: string) => {
if (windowLabel === getCurrentWebviewWindow().label) {
// Never ignore same-window updates
return false;
}
if (payload.model === 'key_value') {
return payload.namespace === 'no_sync';
}

View File

@@ -52,12 +52,12 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
if (request.model === 'http_request') {
await updateHttpRequest.mutateAsync(args);
queryClient.invalidateQueries({
await queryClient.invalidateQueries({
queryKey: httpRequestsQueryKey({ workspaceId: activeWorkspaceId }),
});
} else if (request.model === 'grpc_request') {
await updateGrpcRequest.mutateAsync(args);
queryClient.invalidateQueries({
await queryClient.invalidateQueries({
queryKey: grpcRequestsQueryKey({ workspaceId: activeWorkspaceId }),
});
}

View File

@@ -3,7 +3,7 @@ import { useMemo, useRef } from 'react';
import { useKeyPressEvent } from 'react-use';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useHotKey } from '../hooks/useHotKey';
import { useRecentRequests } from '../hooks/useRecentRequests';
@@ -18,8 +18,8 @@ import { HttpMethodTag } from './core/HttpMethodTag';
export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'className'>) {
const dropdownRef = useRef<DropdownRef>(null);
const activeRequest = useActiveRequest();
const activeWorkspaceId = useActiveWorkspaceId();
const activeEnvironment = useActiveEnvironment();
const activeWorkspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const routes = useAppRoutes();
const allRecentRequestIds = useRecentRequests();
const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]);
@@ -42,7 +42,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
});
const items = useMemo<DropdownItem[]>(() => {
if (activeWorkspaceId === null) return [];
if (activeWorkspace === null) return [];
const recentRequestItems: DropdownItem[] = [];
for (const id of recentRequestIds) {
@@ -58,7 +58,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
routes.navigate('request', {
requestId: request.id,
environmentId: activeEnvironment?.id,
workspaceId: activeWorkspaceId,
workspaceId: activeWorkspace.id,
});
},
});
@@ -76,7 +76,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
}
return recentRequestItems.slice(0, 20);
}, [activeWorkspaceId, activeEnvironment?.id, recentRequestIds, requests, routes]);
}, [activeWorkspace, activeEnvironment?.id, recentRequestIds, requests, routes]);
return (
<Dropdown ref={dropdownRef} items={items}>

View File

@@ -1,6 +1,6 @@
import { open } from '@tauri-apps/plugin-shell';
import { useRef } from 'react';
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppInfo } from '../hooks/useAppInfo';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCheckForUpdates } from '../hooks/useCheckForUpdates';
@@ -23,12 +23,12 @@ export function SettingsDropdown() {
const dialog = useDialog();
const checkForUpdates = useCheckForUpdates();
const routes = useAppRoutes();
const workspaceId = useActiveWorkspaceId();
const workspace = useActiveWorkspace();
const showSettings = async () => {
if (!workspaceId) return;
if (!workspace) return;
await invokeCmd('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId }),
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
label: 'settings',
title: 'Yaak Settings',
});

View File

@@ -1,11 +1,12 @@
import type { Folder, GrpcRequest, HttpRequest, Workspace } from '@yaakapp/api';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import React, { Fragment, useCallback, useMemo, useRef, useState } from 'react';
import type { XYCoord } from 'react-dnd';
import { useDrag, useDrop } from 'react-dnd';
import { useKey, useKeyPressEvent } from 'react-use';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes';
@@ -32,7 +33,6 @@ import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import type { Folder, GrpcRequest, HttpRequest, Workspace } from '@yaakapp/api';
import { isResponseLoading } from '../lib/models';
import type { DropdownItem } from './core/Dropdown';
import { ContextMenu } from './core/Dropdown';
@@ -61,7 +61,7 @@ export function Sidebar({ className }: Props) {
const [hidden, setHidden] = useSidebarHidden();
const sidebarRef = useRef<HTMLLIElement>(null);
const activeRequest = useActiveRequest();
const activeEnvironmentId = useActiveEnvironmentId();
const [activeEnvironment] = useActiveEnvironment();
const folders = useFolders();
const requests = useRequests();
const activeWorkspace = useActiveWorkspace();
@@ -207,14 +207,14 @@ export function Sidebar({ className }: Props) {
routes.navigate('request', {
requestId: id,
workspaceId: item.workspaceId,
environmentId: activeEnvironmentId ?? undefined,
environmentId: activeEnvironment?.id,
});
setSelectedId(id);
setSelectedTree(tree);
if (!opts.noFocus) focusActiveRequest({ forced: { id, tree } });
}
},
[treeParentMap, collapsed, routes, activeEnvironmentId, focusActiveRequest],
[treeParentMap, collapsed, routes, activeEnvironment, focusActiveRequest],
);
const handleClearSelected = useCallback(() => {
@@ -260,7 +260,7 @@ export function Sidebar({ className }: Props) {
routes.navigate('request', {
requestId: selected.id,
workspaceId: activeWorkspace?.id,
environmentId: activeEnvironmentId ?? undefined,
environmentId: activeEnvironment?.id,
});
});

View File

@@ -5,7 +5,6 @@ import { useCallback, useMemo, useRef, useState } from 'react';
import { useWindowSize } from 'react-use';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
import { useImportData } from '../hooks/useImportData';
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
@@ -16,7 +15,6 @@ import { useWorkspaces } from '../hooks/useWorkspaces';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { HotKeyList } from './core/HotKeyList';
import { InlineCode } from './core/InlineCode';
import { FeedbackLink } from './core/Link';
import { HStack } from './core/Stacks';
import { CreateDropdown } from './CreateDropdown';
@@ -38,7 +36,6 @@ export default function Workspace() {
useSyncWorkspaceRequestTitle();
const workspaces = useWorkspaces();
const activeWorkspace = useActiveWorkspace();
const activeWorkspaceId = useActiveWorkspaceId();
const { setWidth, width, resetWidth } = useSidebarWidth();
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
@@ -176,9 +173,8 @@ export default function Workspace() {
{activeWorkspace == null ? (
<div className="m-auto">
<Banner color="warning" className="max-w-[30rem]">
The active workspace{' '}
<InlineCode className="text-warning">{activeWorkspaceId}</InlineCode> was not found.
Select a workspace from the header menu or report this bug to <FeedbackLink />
The active workspace was not found. Select a workspace from the header menu or report
this bug to <FeedbackLink />
</Banner>
</div>
) : activeRequest == null ? (

View File

@@ -87,7 +87,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
ref,
) {
const s = useSettings();
const e = useActiveEnvironment();
const [e] = useActiveEnvironment();
const w = useActiveWorkspace();
const environment = autocompleteVariables ? e : null;
const workspace = autocompleteVariables ? w : null;

View File

@@ -9,7 +9,6 @@ class PlaceholderWidget extends WidgetType {
readonly exists: boolean,
readonly type: 'function' | 'variable' = 'variable',
) {
console.log('PLACEHOLDER', { name, value });
super();
}

View File

@@ -3,7 +3,7 @@ import classNames from 'classnames';
import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useLocalStorage } from 'react-use';
import { useActiveWorkspaceId } from '../../hooks/useActiveWorkspaceId';
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
import { clamp } from '../../lib/clamp';
import { ResizeHandle } from '../ResizeHandle';
@@ -42,10 +42,13 @@ export function SplitLayout({
minWidthPx = 10,
}: Props) {
const containerRef = useRef<HTMLDivElement>(null);
const activeWorkspace = useActiveWorkspace();
const [verticalBasedOnSize, setVerticalBasedOnSize] = useState<boolean>(false);
const [widthRaw, setWidth] = useLocalStorage<number>(`${name}_width::${useActiveWorkspaceId()}`);
const [widthRaw, setWidth] = useLocalStorage<number>(
`${name}_width::${activeWorkspace?.id ?? 'n/a'}`,
);
const [heightRaw, setHeight] = useLocalStorage<number>(
`${name}_height::${useActiveWorkspaceId()}`,
`${name}_height::${activeWorkspace?.id ?? 'n/a'}`,
);
const width = widthRaw ?? defaultRatio;
let height = heightRaw ?? defaultRatio;

View File

@@ -1,36 +1,87 @@
import { useEffect, useMemo } from 'react';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useCallback, useEffect, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useCookieJars } from './useCookieJars';
import { useKeyValue } from './useKeyValue';
export const QUERY_COOKIE_JAR_ID = 'cookie_jar_id';
export function useActiveCookieJar() {
const workspaceId = useActiveWorkspaceId();
const [activeCookieJarId, setActiveCookieJarId] = useActiveCookieJarId();
const cookieJars = useCookieJars();
const activeCookieJar = useMemo(() => {
if (cookieJars.data == null) return undefined;
return cookieJars.data.find((cookieJar) => cookieJar.id === activeCookieJarId) ?? null;
}, [activeCookieJarId, cookieJars.data]);
return [activeCookieJar ?? null, setActiveCookieJarId] as const;
}
export function useEnsureActiveCookieJar() {
const cookieJars = useCookieJars();
const [activeCookieJarId, setActiveCookieJarId] = useActiveCookieJarId();
useEffect(() => {
if (cookieJars.data == null) return;
if (cookieJars.data.find((j) => j.id === activeCookieJarId)) {
return; // There's an active jar
}
const firstJar = cookieJars.data[0];
if (firstJar == null) {
console.log("Workspace doesn't have any cookie jars to activate");
return;
}
// There's no active jar, so set it to the first one
console.log('Setting active cookie jar to', firstJar.id);
setActiveCookieJarId(firstJar.id);
}, [activeCookieJarId, cookieJars, cookieJars.data, setActiveCookieJarId]);
}
export function useMigrateActiveCookieJarId() {
const workspace = useActiveWorkspace();
const [, setActiveCookieJarId] = useActiveCookieJarId();
const {
set: setActiveCookieJarId,
value: activeCookieJarId,
isLoading: isLoadingActiveCookieJarId,
set: setLegacyActiveCookieJarId,
value: legacyActiveCookieJarId,
isLoading: isLoadingLegacyActiveCookieJarId,
} = useKeyValue<string | null>({
namespace: 'global',
key: ['activeCookieJar', workspaceId ?? 'n/a'],
key: ['activeCookieJar', workspace?.id ?? 'n/a'],
fallback: null,
});
const activeCookieJar = useMemo(
() => cookieJars.find((cookieJar) => cookieJar.id === activeCookieJarId),
[activeCookieJarId, cookieJars],
useEffect(() => {
if (legacyActiveCookieJarId == null || isLoadingLegacyActiveCookieJarId) return;
console.log('Migrating active cookie jar ID to query param', legacyActiveCookieJarId);
setActiveCookieJarId(legacyActiveCookieJarId);
setLegacyActiveCookieJarId(null).catch(console.error);
}, [
workspace,
isLoadingLegacyActiveCookieJarId,
legacyActiveCookieJarId,
setActiveCookieJarId,
setLegacyActiveCookieJarId,
]);
}
function useActiveCookieJarId() {
// NOTE: This query param is accessed from Rust side, so do not change
const [params, setParams] = useSearchParams();
const id = params.get(QUERY_COOKIE_JAR_ID);
const setId = useCallback(
(id: string) => {
setParams((p) => {
const existing = Object.fromEntries(p);
return { ...existing, [QUERY_COOKIE_JAR_ID]: id };
});
},
[setParams],
);
// TODO: Make this not be called so many times (move to GlobalHooks?)
useEffect(() => {
if (!isLoadingActiveCookieJarId && activeCookieJar == null && cookieJars.length > 0) {
setActiveCookieJarId(cookieJars[0]?.id ?? null).catch(console.error);
}
}, [activeCookieJar, cookieJars, isLoadingActiveCookieJarId, setActiveCookieJarId]);
return {
activeCookieJar: activeCookieJar ?? null,
setActiveCookieJarId: setActiveCookieJarId,
};
return [id, setId] as const;
}

View File

@@ -1,10 +1,38 @@
import { useMemo } from 'react';
import type { Environment } from '@yaakapp/api';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useCallback, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useEnvironments } from './useEnvironments';
export function useActiveEnvironment(): Environment | null {
const id = useActiveEnvironmentId();
export function useActiveEnvironment() {
const [id, setId] = useActiveEnvironmentId();
const environments = useEnvironments();
return useMemo(() => environments.find((w) => w.id === id) ?? null, [environments, id]);
const environment = useMemo(
() => environments.find((w) => w.id === id) ?? null,
[environments, id],
);
return [environment, setId] as const;
}
export const QUERY_ENVIRONMENT_ID = 'environment_id';
export function useActiveEnvironmentId() {
// NOTE: This query param is accessed from Rust side, so do not change
const [params, setParams] = useSearchParams();
const id = params.get(QUERY_ENVIRONMENT_ID);
const setId = useCallback(
(id: string | null) => {
setParams((p) => {
const existing = Object.fromEntries(p);
if (id == null) {
delete existing[QUERY_ENVIRONMENT_ID];
} else {
existing[QUERY_ENVIRONMENT_ID] = id;
}
return existing;
});
},
[setParams],
);
return [id, setId] as const;
}

View File

@@ -1,13 +0,0 @@
import { useSearchParams } from 'react-router-dom';
export const QUERY_ENVIRONMENT_ID = 'environment_id';
export function useActiveEnvironmentId(): string | null {
const [params] = useSearchParams();
const environmentId = params.get(QUERY_ENVIRONMENT_ID);
if (environmentId == null) {
return null;
}
return environmentId;
}

View File

@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import type { Workspace } from '@yaakapp/api';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useParams } from 'react-router-dom';
import type { RouteParamsWorkspace } from './useAppRoutes';
import { useWorkspaces } from './useWorkspaces';
export function useActiveWorkspace(): Workspace | null {
@@ -11,3 +12,8 @@ export function useActiveWorkspace(): Workspace | null {
[workspaces, workspaceId],
);
}
function useActiveWorkspaceId(): string | null {
const { workspaceId } = useParams<RouteParamsWorkspace>();
return workspaceId ?? null;
}

View File

@@ -3,7 +3,7 @@ import { InlineCode } from '../components/core/InlineCode';
import { useToast } from '../components/ToastContext';
import { useActiveWorkspace } from './useActiveWorkspace';
export function useAtiveWorkspaceChangedToast() {
export function useActiveWorkspaceChangedToast() {
const toast = useToast();
const activeWorkspace = useActiveWorkspace();
const [id, setId] = useState<string | null>(activeWorkspace?.id ?? null);

View File

@@ -1,7 +0,0 @@
import { useParams } from 'react-router-dom';
import type { RouteParamsWorkspace } from './useAppRoutes';
export function useActiveWorkspaceId(): string | null {
const { workspaceId } = useParams<RouteParamsWorkspace>();
return workspaceId ?? null;
}

View File

@@ -1,13 +1,15 @@
import type { Environment } from '@yaakapp/api';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import type { Environment } from '@yaakapp/api';
import { QUERY_ENVIRONMENT_ID } from './useActiveEnvironmentId';
import { QUERY_COOKIE_JAR_ID } from './useActiveCookieJar';
import { QUERY_ENVIRONMENT_ID } from './useActiveEnvironment';
import { useActiveRequestId } from './useActiveRequestId';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
export type RouteParamsWorkspace = {
workspaceId: string;
environmentId?: string;
cookieJarId?: string;
};
export type RouteParamsRequest = RouteParamsWorkspace & {
@@ -22,34 +24,35 @@ export const routePaths = {
return `/workspaces/${workspaceId}/settings`;
},
workspace(
{ workspaceId, environmentId } = {
{ workspaceId, environmentId, cookieJarId } = {
workspaceId: ':workspaceId',
environmentId: ':environmentId',
cookieJarId: ':cookieJarId',
} as RouteParamsWorkspace,
) {
let path = `/workspaces/${workspaceId}`;
if (environmentId != null) {
path += `?${QUERY_ENVIRONMENT_ID}=${encodeURIComponent(environmentId)}`;
}
return path;
const path = `/workspaces/${workspaceId}`;
const params = new URLSearchParams();
if (environmentId != null) params.set(QUERY_ENVIRONMENT_ID, environmentId);
if (cookieJarId != null) params.set(QUERY_COOKIE_JAR_ID, cookieJarId);
return `${path}?${params}`;
},
request(
{ workspaceId, environmentId, requestId } = {
{ workspaceId, environmentId, requestId, cookieJarId } = {
workspaceId: ':workspaceId',
environmentId: ':environmentId',
requestId: ':requestId',
} as RouteParamsRequest,
) {
let path = `/workspaces/${workspaceId}/requests/${requestId}`;
if (environmentId != null) {
path += `?${QUERY_ENVIRONMENT_ID}=${encodeURIComponent(environmentId)}`;
}
return path;
const path = `/workspaces/${workspaceId}/requests/${requestId}`;
const params = new URLSearchParams();
if (environmentId != null) params.set(QUERY_ENVIRONMENT_ID, environmentId);
if (cookieJarId != null) params.set(QUERY_COOKIE_JAR_ID, cookieJarId);
return `${path}?${params}`;
},
};
export function useAppRoutes() {
const activeWorkspaceId = useActiveWorkspaceId();
const activeWorkspace = useActiveWorkspace();
const requestId = useActiveRequestId();
const nav = useNavigate();
@@ -66,22 +69,22 @@ export function useAppRoutes() {
const setEnvironment = useCallback(
(environment: Environment | null) => {
if (activeWorkspaceId == null) {
if (activeWorkspace == null) {
navigate('workspaces');
} else if (requestId == null) {
navigate('workspace', {
workspaceId: activeWorkspaceId,
workspaceId: activeWorkspace.id,
environmentId: environment == null ? undefined : environment.id,
});
} else {
navigate('request', {
workspaceId: activeWorkspaceId,
workspaceId: activeWorkspace.id,
environmentId: environment == null ? undefined : environment.id,
requestId,
});
}
},
[navigate, activeWorkspaceId, requestId],
[navigate, activeWorkspace, requestId],
);
return {

View File

@@ -1,22 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import type { CookieJar } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
export function cookieJarsQueryKey({ workspaceId }: { workspaceId: string }) {
return ['cookie_jars', { workspaceId }];
}
export function useCookieJars() {
const workspaceId = useActiveWorkspaceId();
return (
useQuery({
enabled: workspaceId != null,
queryKey: cookieJarsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invokeCmd('cmd_list_cookie_jars', { workspaceId })) as CookieJar[];
},
}).data ?? []
);
const workspace = useActiveWorkspace();
return useQuery({
enabled: workspace != null,
queryKey: cookieJarsQueryKey({ workspaceId: workspace?.id ?? 'n/a' }),
queryFn: async () => {
if (workspace == null) return [];
return (await invokeCmd('cmd_list_cookie_jars', {
workspaceId: workspace.id,
})) as CookieJar[];
},
});
}

View File

@@ -1,15 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useCopy } from './useCopy';
export function useCopyAsCurl(requestId: string) {
const copy = useCopy();
const environmentId = useActiveEnvironmentId();
const [environment] = useActiveEnvironment();
return useMutation({
mutationKey: ['copy_as_curl', requestId],
mutationFn: async () => {
const cmd: string = await invokeCmd('cmd_request_to_curl', { requestId, environmentId });
const cmd: string = await invokeCmd('cmd_request_to_curl', {
requestId,
environmentId: environment?.id,
});
copy(cmd);
return cmd;
},

View File

@@ -1,20 +1,18 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import type { CookieJar } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { cookieJarsQueryKey } from './useCookieJars';
import { useActiveWorkspace } from './useActiveWorkspace';
import { usePrompt } from './usePrompt';
export function useCreateCookieJar() {
const workspaceId = useActiveWorkspaceId();
const queryClient = useQueryClient();
const workspace = useActiveWorkspace();
const prompt = usePrompt();
return useMutation<CookieJar>({
mutationKey: ['create_cookie_jar'],
mutationFn: async () => {
if (workspaceId === null) {
if (workspace === null) {
throw new Error("Cannot create cookie jar when there's no active workspace");
}
const name = await prompt({
@@ -25,14 +23,8 @@ export function useCreateCookieJar() {
label: 'Name',
defaultValue: 'My Jar',
});
return invokeCmd('cmd_create_cookie_jar', { workspaceId, name });
return invokeCmd('cmd_create_cookie_jar', { workspaceId: workspace.id, name });
},
onSettled: () => trackEvent('cookie_jar', 'create'),
onSuccess: async (cookieJar) => {
queryClient.setQueryData<CookieJar[]>(
cookieJarsQueryKey({ workspaceId: cookieJar.workspaceId }),
(items) => [...(items ?? []), cookieJar],
);
},
});
}

View File

@@ -1,17 +1,15 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
import { environmentsQueryKey } from './useEnvironments';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
import { usePrompt } from './usePrompt';
export function useCreateEnvironment() {
const routes = useAppRoutes();
const [, setActiveEnvironmentId] = useActiveEnvironment();
const prompt = usePrompt();
const workspaceId = useActiveWorkspaceId();
const queryClient = useQueryClient();
const workspace = useActiveWorkspace();
return useMutation<Environment, unknown, void>({
mutationKey: ['create_environment'],
@@ -25,16 +23,16 @@ export function useCreateEnvironment() {
placeholder: 'My Environment',
defaultValue: 'My Environment',
});
return invokeCmd('cmd_create_environment', { name, variables: [], workspaceId });
return invokeCmd('cmd_create_environment', {
name,
variables: [],
workspaceId: workspace?.id,
});
},
onSettled: () => trackEvent('environment', 'create'),
onSuccess: async (environment) => {
if (workspaceId == null) return;
routes.setEnvironment(environment);
queryClient.setQueryData<Environment[]>(
environmentsQueryKey({ workspaceId }),
(environments) => [...(environments ?? []), environment],
);
if (workspace == null) return;
setActiveEnvironmentId(environment.id);
},
});
}

View File

@@ -1,22 +1,20 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { foldersQueryKey } from './useFolders';
import { useActiveWorkspace } from './useActiveWorkspace';
import { usePrompt } from './usePrompt';
export function useCreateFolder() {
const workspaceId = useActiveWorkspaceId();
const workspace = useActiveWorkspace();
const activeRequest = useActiveRequest();
const queryClient = useQueryClient();
const prompt = usePrompt();
return useMutation<Folder, unknown, Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>>({
mutationKey: ['create_folder'],
mutationFn: async (patch) => {
if (workspaceId === null) {
if (workspace === null) {
throw new Error("Cannot create folder when there's no active workspace");
}
patch.name =
@@ -32,13 +30,8 @@ export function useCreateFolder() {
}));
patch.sortPriority = patch.sortPriority || -Date.now();
patch.folderId = patch.folderId || activeRequest?.folderId;
return invokeCmd('cmd_create_folder', { workspaceId, ...patch });
return invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch });
},
onSettled: () => trackEvent('folder', 'create'),
onSuccess: async (request) => {
await queryClient.invalidateQueries({
queryKey: foldersQueryKey({ workspaceId: request.workspaceId }),
});
},
});
}

View File

@@ -1,15 +1,15 @@
import { useMutation } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import type { GrpcRequest } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
export function useCreateGrpcRequest() {
const workspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
const workspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const activeRequest = useActiveRequest();
const routes = useAppRoutes();
@@ -20,12 +20,12 @@ export function useCreateGrpcRequest() {
>({
mutationKey: ['create_grpc_request'],
mutationFn: (patch) => {
if (workspaceId === null) {
if (workspace === null) {
throw new Error("Cannot create grpc request when there's no active workspace");
}
if (patch.sortPriority === undefined) {
if (activeRequest != null) {
// Place above currently-active request
// Place above currently active request
patch.sortPriority = activeRequest.sortPriority + 0.0001;
} else {
// Place at the very top
@@ -33,14 +33,18 @@ export function useCreateGrpcRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
return invokeCmd('cmd_create_grpc_request', { workspaceId, name: '', ...patch });
return invokeCmd('cmd_create_grpc_request', {
workspaceId: workspace.id,
name: '',
...patch,
});
},
onSettled: () => trackEvent('grpc_request', 'create'),
onSuccess: async (request) => {
routes.navigate('request', {
workspaceId: request.workspaceId,
requestId: request.id,
environmentId: activeEnvironmentId ?? undefined,
environmentId: activeEnvironment?.id,
});
},
});

View File

@@ -1,22 +1,22 @@
import { useMutation } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import type { HttpRequest } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
export function useCreateHttpRequest() {
const workspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
const workspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const activeRequest = useActiveRequest();
const routes = useAppRoutes();
return useMutation<HttpRequest, unknown, Partial<HttpRequest>>({
mutationKey: ['create_http_request'],
mutationFn: (patch = {}) => {
if (workspaceId === null) {
if (workspace === null) {
throw new Error("Cannot create request when there's no active workspace");
}
if (patch.sortPriority === undefined) {
@@ -29,14 +29,16 @@ export function useCreateHttpRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
return invokeCmd('cmd_create_http_request', { request: { workspaceId, ...patch } });
return invokeCmd('cmd_create_http_request', {
request: { workspaceId: workspace.id, ...patch },
});
},
onSettled: () => trackEvent('http_request', 'create'),
onSuccess: async (request) => {
routes.navigate('request', {
workspaceId: request.workspaceId,
requestId: request.id,
environmentId: activeEnvironmentId ?? undefined,
environmentId: activeEnvironment?.id,
});
},
});

View File

@@ -1,15 +1,13 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp/api';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import type { GrpcRequest } from '@yaakapp/api';
import { getGrpcRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { grpcRequestsQueryKey } from './useGrpcRequests';
export function useDeleteAnyGrpcRequest() {
const queryClient = useQueryClient();
const confirm = useConfirm();
return useMutation<GrpcRequest | null, string, string>({
@@ -32,13 +30,5 @@ export function useDeleteAnyGrpcRequest() {
return invokeCmd('cmd_delete_grpc_request', { requestId: id });
},
onSettled: () => trackEvent('grpc_request', 'delete'),
onSuccess: async (request) => {
if (request === null) return;
const { workspaceId, id: requestId } = request;
queryClient.setQueryData<GrpcRequest[]>(grpcRequestsQueryKey({ workspaceId }), (requests) =>
(requests ?? []).filter((r) => r.id !== requestId),
);
},
});
}

View File

@@ -1,16 +1,13 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp/api';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import type { HttpRequest } from '@yaakapp/api';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { httpRequestsQueryKey } from './useHttpRequests';
import { httpResponsesQueryKey } from './useHttpResponses';
export function useDeleteAnyHttpRequest() {
const queryClient = useQueryClient();
const confirm = useConfirm();
return useMutation<HttpRequest | null, string, string>({
@@ -33,15 +30,5 @@ export function useDeleteAnyHttpRequest() {
return invokeCmd('cmd_delete_http_request', { requestId: id });
},
onSettled: () => trackEvent('http_request', 'delete'),
onSuccess: async (request) => {
// Was it cancelled?
if (request === null) return;
const { workspaceId, id: requestId } = request;
queryClient.setQueryData(httpResponsesQueryKey({ requestId }), []); // Responses were deleted
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey({ workspaceId }), (requests) =>
(requests ?? []).filter((r) => r.id !== requestId),
);
},
});
}

View File

@@ -1,13 +1,11 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import type { CookieJar } from '../lib/models';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { cookieJarsQueryKey } from './useCookieJars';
export function useDeleteCookieJar(cookieJar: CookieJar | null) {
const queryClient = useQueryClient();
const confirm = useConfirm();
return useMutation<CookieJar | null, string>({
@@ -27,13 +25,5 @@ export function useDeleteCookieJar(cookieJar: CookieJar | null) {
return invokeCmd('cmd_delete_cookie_jar', { cookieJarId: cookieJar?.id });
},
onSettled: () => trackEvent('cookie_jar', 'delete'),
onSuccess: async (cookieJar) => {
if (cookieJar === null) return;
const { id: cookieJarId, workspaceId } = cookieJar;
queryClient.setQueryData<CookieJar[]>(cookieJarsQueryKey({ workspaceId }), (cookieJars) =>
cookieJars?.filter((e) => e.id !== cookieJarId),
);
},
});
}

View File

@@ -1,13 +1,11 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp/api';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import type { Environment, Workspace } from '@yaakapp/api';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { environmentsQueryKey } from './useEnvironments';
export function useDeleteEnvironment(environment: Environment | null) {
const queryClient = useQueryClient();
const confirm = useConfirm();
return useMutation<Environment | null, string>({
@@ -27,13 +25,5 @@ export function useDeleteEnvironment(environment: Environment | null) {
return invokeCmd('cmd_delete_environment', { environmentId: environment?.id });
},
onSettled: () => trackEvent('environment', 'delete'),
onSuccess: async (environment) => {
if (environment === null) return;
const { id: environmentId, workspaceId } = environment;
queryClient.setQueryData<Workspace[]>(environmentsQueryKey({ workspaceId }), (environments) =>
environments?.filter((e) => e.id !== environmentId),
);
},
});
}

View File

@@ -1,15 +1,12 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp/api';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import type { Folder } from '@yaakapp/api';
import { getFolder } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { foldersQueryKey } from './useFolders';
import { httpRequestsQueryKey } from './useHttpRequests';
export function useDeleteFolder(id: string | null) {
const queryClient = useQueryClient();
const confirm = useConfirm();
return useMutation<Folder | null, string>({
@@ -30,15 +27,5 @@ export function useDeleteFolder(id: string | null) {
return invokeCmd('cmd_delete_folder', { folderId: id });
},
onSettled: () => trackEvent('folder', 'delete'),
onSuccess: async (folder) => {
// Was it cancelled?
if (folder === null) return;
const { workspaceId } = folder;
// Nesting makes it hard to clean things up, so just clear everything that could have been deleted
await queryClient.invalidateQueries({ queryKey: httpRequestsQueryKey({ workspaceId }) });
await queryClient.invalidateQueries({ queryKey: foldersQueryKey({ workspaceId }) });
},
});
}

View File

@@ -1,22 +1,14 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import { useMutation } from '@tanstack/react-query';
import type { GrpcConnection } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { grpcConnectionsQueryKey } from './useGrpcConnections';
export function useDeleteGrpcConnection(id: string | null) {
const queryClient = useQueryClient();
return useMutation<GrpcConnection>({
mutationKey: ['delete_grpc_connection', id],
mutationFn: async () => {
return await invokeCmd('cmd_delete_grpc_connection', { id: id });
},
onSettled: () => trackEvent('grpc_connection', 'delete'),
onSuccess: ({ requestId, id: connectionId }) => {
queryClient.setQueryData<GrpcConnection[]>(
grpcConnectionsQueryKey({ requestId }),
(connections) => (connections ?? []).filter((c) => c.id !== connectionId),
);
},
});
}

View File

@@ -1,10 +1,8 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { grpcConnectionsQueryKey } from './useGrpcConnections';
export function useDeleteGrpcConnections(requestId?: string) {
const queryClient = useQueryClient();
return useMutation({
mutationKey: ['delete_grpc_connections', requestId],
mutationFn: async () => {
@@ -12,9 +10,5 @@ export function useDeleteGrpcConnections(requestId?: string) {
await invokeCmd('cmd_delete_all_grpc_connections', { requestId });
},
onSettled: () => trackEvent('grpc_connection', 'delete_many'),
onSuccess: async () => {
if (requestId === undefined) return;
queryClient.setQueryData(grpcConnectionsQueryKey({ requestId }), []);
},
});
}

View File

@@ -1,21 +1,14 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import { useMutation } from '@tanstack/react-query';
import type { HttpResponse } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { httpResponsesQueryKey } from './useHttpResponses';
export function useDeleteHttpResponse(id: string | null) {
const queryClient = useQueryClient();
return useMutation<HttpResponse>({
mutationKey: ['delete_http_response', id],
mutationFn: async () => {
return await invokeCmd('cmd_delete_http_response', { id: id });
},
onSettled: () => trackEvent('http_response', 'delete'),
onSuccess: ({ requestId, id: responseId }) => {
queryClient.setQueryData<HttpResponse[]>(httpResponsesQueryKey({ requestId }), (responses) =>
(responses ?? []).filter((response) => response.id !== responseId),
);
},
});
}

View File

@@ -1,10 +1,8 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { httpResponsesQueryKey } from './useHttpResponses';
export function useDeleteHttpResponses(requestId?: string) {
const queryClient = useQueryClient();
return useMutation({
mutationKey: ['delete_http_responses', requestId],
mutationFn: async () => {
@@ -12,9 +10,5 @@ export function useDeleteHttpResponses(requestId?: string) {
await invokeCmd('cmd_delete_all_http_responses', { requestId });
},
onSettled: () => trackEvent('http_response', 'delete_many'),
onSuccess: async () => {
if (requestId === undefined) return;
queryClient.setQueryData(httpResponsesQueryKey({ requestId }), []);
},
});
}

View File

@@ -1,17 +1,14 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { Workspace } from '@yaakapp/api';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import type { Workspace } from '@yaakapp/api';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
import { useConfirm } from './useConfirm';
import { httpRequestsQueryKey } from './useHttpRequests';
import { workspacesQueryKey } from './useWorkspaces';
export function useDeleteWorkspace(workspace: Workspace | null) {
const queryClient = useQueryClient();
const activeWorkspaceId = useActiveWorkspaceId();
const activeWorkspace = useActiveWorkspace();
const routes = useAppRoutes();
const confirm = useConfirm();
@@ -36,16 +33,9 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
if (workspace === null) return;
const { id: workspaceId } = workspace;
queryClient.setQueryData<Workspace[]>(workspacesQueryKey({}), (workspaces) =>
workspaces?.filter((workspace) => workspace.id !== workspaceId),
);
if (workspaceId === activeWorkspaceId) {
if (workspaceId === activeWorkspace?.id) {
routes.navigate('workspaces');
}
// Also clean up other things that may have been deleted
queryClient.setQueryData(httpRequestsQueryKey({ workspaceId }), []);
await queryClient.invalidateQueries({ queryKey: httpRequestsQueryKey({ workspaceId }) });
},
});
}

View File

@@ -1,10 +1,10 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { setKeyValue } from '../lib/keyValueStore';
import type { GrpcRequest } from '@yaakapp/api';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
import { protoFilesArgs, useGrpcProtoFiles } from './useGrpcProtoFiles';
@@ -15,8 +15,8 @@ export function useDuplicateGrpcRequest({
id: string | null;
navigateAfter: boolean;
}) {
const activeWorkspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
const activeWorkspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const routes = useAppRoutes();
const protoFiles = useGrpcProtoFiles(id);
return useMutation<GrpcRequest, string>({
@@ -30,11 +30,11 @@ export function useDuplicateGrpcRequest({
// Also copy proto files to new request
await setKeyValue({ ...protoFilesArgs(request.id), value: protoFiles.value ?? [] });
if (navigateAfter && activeWorkspaceId !== null) {
if (navigateAfter && activeWorkspace !== null) {
routes.navigate('request', {
workspaceId: activeWorkspaceId,
workspaceId: activeWorkspace.id,
requestId: request.id,
environmentId: activeEnvironmentId ?? undefined,
environmentId: activeEnvironment?.id,
});
}
},

View File

@@ -1,9 +1,9 @@
import { useMutation } from '@tanstack/react-query';
import { trackEvent } from '../lib/analytics';
import type { HttpRequest } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
export function useDuplicateHttpRequest({
@@ -13,8 +13,8 @@ export function useDuplicateHttpRequest({
id: string | null;
navigateAfter: boolean;
}) {
const activeWorkspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
const activeWorkspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const routes = useAppRoutes();
return useMutation<HttpRequest, string>({
mutationKey: ['duplicate_http_request', id],
@@ -24,11 +24,11 @@ export function useDuplicateHttpRequest({
},
onSettled: () => trackEvent('http_request', 'duplicate'),
onSuccess: async (request) => {
if (navigateAfter && activeWorkspaceId !== null) {
if (navigateAfter && activeWorkspace !== null) {
routes.navigate('request', {
workspaceId: activeWorkspaceId,
workspaceId: activeWorkspace.id,
requestId: request.id,
environmentId: activeEnvironmentId ?? undefined,
environmentId: activeEnvironment?.id,
});
}
},

View File

@@ -1,21 +1,23 @@
import { useQuery } from '@tanstack/react-query';
import type { Environment } from '@yaakapp/api';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
export function environmentsQueryKey({ workspaceId }: { workspaceId: string }) {
return ['environments', { workspaceId }];
}
export function useEnvironments() {
const workspaceId = useActiveWorkspaceId();
const workspace = useActiveWorkspace();
return (
useQuery({
enabled: workspaceId != null,
queryKey: environmentsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
enabled: workspace != null,
queryKey: environmentsQueryKey({ workspaceId: workspace?.id ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invokeCmd('cmd_list_environments', { workspaceId })) as Environment[];
if (workspace == null) return [];
return (await invokeCmd('cmd_list_environments', {
workspaceId: workspace.id,
})) as Environment[];
},
}).data ?? []
);

View File

@@ -1,11 +1,11 @@
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useKeyValue } from './useKeyValue';
export function useFloatingSidebarHidden() {
const activeWorkspaceId = useActiveWorkspaceId();
const activeWorkspace = useActiveWorkspace();
const { set, value } = useKeyValue<boolean>({
namespace: 'no_sync',
key: ['floating_sidebar_hidden', activeWorkspaceId ?? 'n/a'],
key: ['floating_sidebar_hidden', activeWorkspace?.id ?? 'n/a'],
fallback: false,
});

View File

@@ -1,21 +1,21 @@
import { useQuery } from '@tanstack/react-query';
import type { Folder } from '@yaakapp/api';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
export function foldersQueryKey({ workspaceId }: { workspaceId: string }) {
return ['folders', { workspaceId }];
}
export function useFolders() {
const workspaceId = useActiveWorkspaceId();
const workspace = useActiveWorkspace();
return (
useQuery({
enabled: workspaceId != null,
queryKey: foldersQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
enabled: workspace != null,
queryKey: foldersQueryKey({ workspaceId: workspace?.id ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invokeCmd('cmd_list_folders', { workspaceId })) as Folder[];
if (workspace == null) return [];
return (await invokeCmd('cmd_list_folders', { workspaceId: workspace.id })) as Folder[];
},
}).data ?? []
);

View File

@@ -1,10 +1,10 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { emit } from '@tauri-apps/api/event';
import type { GrpcConnection, GrpcRequest } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { minPromiseMillis } from '../lib/minPromiseMillis';
import type { GrpcConnection, GrpcRequest } from '@yaakapp/api';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useDebouncedValue } from './useDebouncedValue';
export interface ReflectResponseService {
@@ -18,12 +18,12 @@ export function useGrpc(
protoFiles: string[],
) {
const requestId = req?.id ?? 'n/a';
const environmentId = useActiveEnvironmentId();
const [environment] = useActiveEnvironment();
const go = useMutation<void, string>({
mutationKey: ['grpc_go', conn?.id],
mutationFn: async () =>
await invokeCmd('cmd_grpc_go', { requestId, environmentId, protoFiles }),
await invokeCmd('cmd_grpc_go', { requestId, environmentId: environment?.id, protoFiles }),
onSettled: () => trackEvent('grpc_request', 'send'),
});

View File

@@ -1,21 +1,23 @@
import { useQuery } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp/api';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
export function grpcRequestsQueryKey({ workspaceId }: { workspaceId: string }) {
return ['grpc_requests', { workspaceId }];
}
export function useGrpcRequests() {
const workspaceId = useActiveWorkspaceId();
const workspace = useActiveWorkspace();
return (
useQuery({
enabled: workspaceId != null,
queryKey: grpcRequestsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
enabled: workspace != null,
queryKey: grpcRequestsQueryKey({ workspaceId: workspace?.id ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invokeCmd('cmd_list_grpc_requests', { workspaceId })) as GrpcRequest[];
if (workspace == null) return [];
return (await invokeCmd('cmd_list_grpc_requests', {
workspaceId: workspace.id,
})) as GrpcRequest[];
},
}).data ?? []
);

View File

@@ -1,21 +1,23 @@
import { useQuery } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp/api';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
export function httpRequestsQueryKey({ workspaceId }: { workspaceId: string }) {
return ['http_requests', { workspaceId }];
}
export function useHttpRequests() {
const workspaceId = useActiveWorkspaceId();
const workspace = useActiveWorkspace();
return (
useQuery({
enabled: workspaceId != null,
queryKey: httpRequestsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
enabled: workspace != null,
queryKey: httpRequestsQueryKey({ workspaceId: workspace?.id ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invokeCmd('cmd_list_http_requests', { workspaceId })) as HttpRequest[];
if (workspace == null) return [];
return (await invokeCmd('cmd_list_http_requests', {
workspaceId: workspace.id,
})) as HttpRequest[];
},
}).data ?? []
);

View File

@@ -1,13 +1,13 @@
import { useMutation } from '@tanstack/react-query';
import { useToast } from '../components/ToastContext';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useCreateHttpRequest } from './useCreateHttpRequest';
import { useRequestUpdateKey } from './useRequestUpdateKey';
import { useUpdateAnyHttpRequest } from './useUpdateAnyHttpRequest';
export function useImportCurl() {
const workspaceId = useActiveWorkspaceId();
const workspace = useActiveWorkspace();
const updateRequest = useUpdateAnyHttpRequest();
const createRequest = useCreateHttpRequest();
const { wasUpdatedExternally } = useRequestUpdateKey(null);
@@ -24,7 +24,7 @@ export function useImportCurl() {
}) => {
const request: Record<string, unknown> = await invokeCmd('cmd_curl_to_request', {
command,
workspaceId,
workspaceId: workspace?.id,
});
delete request.id;

View File

@@ -1,13 +1,13 @@
import { useMutation } from '@tanstack/react-query';
import type { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '@yaakapp/api';
import { Button } from '../components/core/Button';
import { FormattedError } from '../components/core/FormattedError';
import { VStack } from '../components/core/Stacks';
import { useDialog } from '../components/DialogContext';
import { ImportDataDialog } from '../components/ImportDataDialog';
import type { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '@yaakapp/api';
import { count } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert';
import { useAppRoutes } from './useAppRoutes';
@@ -15,7 +15,7 @@ export function useImportData() {
const routes = useAppRoutes();
const dialog = useDialog();
const alert = useAlert();
const activeWorkspaceId = useActiveWorkspaceId();
const activeWorkspace = useActiveWorkspace();
const importData = async (filePath: string): Promise<boolean> => {
const imported: {
@@ -26,7 +26,7 @@ export function useImportData() {
grpcRequests: GrpcRequest[];
} = await invokeCmd('cmd_import_data', {
filePath,
workspaceId: activeWorkspaceId,
workspaceId: activeWorkspace?.id,
});
const importedWorkspace = imported.workspaces[0];

View File

@@ -1,11 +1,11 @@
import type { HttpRequest } from '@yaakapp/api';
import type { IntrospectionQuery } from 'graphql';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { buildClientSchema, getIntrospectionQuery } from '../components/core/Editor';
import { minPromiseMillis } from '../lib/minPromiseMillis';
import type { HttpRequest } from '@yaakapp/api';
import { getResponseBodyText } from '../lib/responseBody';
import { sendEphemeralRequest } from '../lib/sendEphemeralRequest';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useDebouncedValue } from './useDebouncedValue';
import { useKeyValue } from './useKeyValue';
@@ -18,7 +18,7 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
// Debounce the request because it can change rapidly and we don't
// want to send so too many requests.
const request = useDebouncedValue(baseRequest);
const activeEnvironmentId = useActiveEnvironmentId();
const [activeEnvironment] = useActiveEnvironment();
const [refetchKey, setRefetchKey] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string>();
@@ -40,7 +40,10 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
bodyType: 'application/json',
body: { text: introspectionRequestBody },
};
const response = await minPromiseMillis(sendEphemeralRequest(args, activeEnvironmentId), 700);
const response = await minPromiseMillis(
sendEphemeralRequest(args, activeEnvironment?.id ?? null),
700,
);
if (response.error) {
throw new Error(response.error);
@@ -72,7 +75,7 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
runIntrospection(); // Run immediately
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [request.id, request.url, request.method, refetchKey, activeEnvironmentId]);
}, [request.id, request.url, request.method, refetchKey, activeEnvironment?.id]);
const refetch = useCallback(() => {
setRefetchKey((k) => k + 1);

View File

@@ -1,4 +1,4 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';
import { buildKeyValueKey, getKeyValue, setKeyValue } from '../lib/keyValueStore';
@@ -24,7 +24,6 @@ export function useKeyValue<T extends Object | null>({
key: string | string[];
fallback: T;
}) {
const queryClient = useQueryClient();
const query = useQuery<T>({
queryKey: keyValueQueryKey({ namespace, key }),
queryFn: async () => getKeyValue({ namespace, key, fallback }),
@@ -34,8 +33,6 @@ export function useKeyValue<T extends Object | null>({
const mutate = useMutation<void, unknown, T>({
mutationKey: ['set_key_value', namespace, key],
mutationFn: (value) => setKeyValue<T>({ namespace, key, value }),
// k/v should be as fast as possible, so optimistically update the cache
onMutate: (value) => queryClient.setQueryData<T>(keyValueQueryKey({ namespace, key }), value),
});
const set = useCallback(

View File

@@ -2,19 +2,19 @@ import { useMutation } from '@tanstack/react-query';
import React from 'react';
import { useDialog } from '../components/DialogContext';
import { MoveToWorkspaceDialog } from '../components/MoveToWorkspaceDialog';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useRequests } from './useRequests';
export function useMoveToWorkspace(id: string) {
const dialog = useDialog();
const requests = useRequests();
const request = requests.find((r) => r.id === id);
const activeWorkspaceId = useActiveWorkspaceId();
const activeWorkspace = useActiveWorkspace();
return useMutation<void, unknown>({
mutationKey: ['move_workspace', id],
mutationFn: async () => {
if (request == null || activeWorkspaceId == null) return;
if (request == null || activeWorkspace == null) return;
dialog.show({
id: 'change-workspace',
@@ -24,7 +24,7 @@ export function useMoveToWorkspace(id: string) {
<MoveToWorkspaceDialog
onDone={hide}
request={request}
activeWorkspaceId={activeWorkspaceId}
activeWorkspaceId={activeWorkspace.id}
/>
),
});

View File

@@ -1,6 +1,7 @@
import { useMutation } from '@tanstack/react-query';
import { invokeCmd } from '../lib/tauri';
import { useAppRoutes } from './useAppRoutes';
import { getRecentCookieJars } from './useRecentCookieJars';
import { getRecentEnvironments } from './useRecentEnvironments';
import { getRecentRequests } from './useRecentRequests';
@@ -16,29 +17,21 @@ export function useOpenWorkspace() {
workspaceId: string;
inNewWindow: boolean;
}) => {
const environmentId = (await getRecentEnvironments(workspaceId))[0];
const requestId = (await getRecentRequests(workspaceId))[0];
const cookieJarId = (await getRecentCookieJars(workspaceId))[0];
const baseArgs = { workspaceId, environmentId, cookieJarId } as const;
if (inNewWindow) {
const environmentId = (await getRecentEnvironments(workspaceId))[0];
const requestId = (await getRecentRequests(workspaceId))[0];
const path =
requestId != null
? routes.paths.request({
workspaceId,
environmentId,
requestId,
})
: routes.paths.workspace({ workspaceId, environmentId });
? routes.paths.request({ ...baseArgs, requestId })
: routes.paths.workspace({ ...baseArgs });
await invokeCmd('cmd_new_window', { url: path });
} else {
const environmentId = (await getRecentEnvironments(workspaceId))[0];
const requestId = (await getRecentRequests(workspaceId))[0];
if (requestId != null) {
routes.navigate('request', {
workspaceId: workspaceId,
environmentId,
requestId,
});
routes.navigate('request', { ...baseArgs, requestId });
} else {
routes.navigate('workspace', { workspaceId, environmentId });
routes.navigate('workspace', { ...baseArgs });
}
}
},

View File

@@ -1,10 +1,10 @@
import { useEffect, useState } from 'react';
import type { Appearance } from '../lib/theme/appearance';
import {
getCSSAppearance,
getWindowAppearance,
subscribeToWindowAppearanceChange,
} from '../lib/theme/appearance';
import { type Appearance } from '../lib/theme/window';
export function usePreferredAppearance() {
const [preferredAppearance, setPreferredAppearance] = useState<Appearance>(getCSSAppearance());

View File

@@ -0,0 +1,47 @@
import { useEffect, useMemo } from 'react';
import { getKeyValue } from '../lib/keyValueStore';
import { useActiveCookieJar } from './useActiveCookieJar';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useCookieJars } from './useCookieJars';
import { useKeyValue } from './useKeyValue';
const kvKey = (workspaceId: string) => 'recent_cookie_jars::' + workspaceId;
const namespace = 'global';
const fallback: string[] = [];
export function useRecentCookieJars() {
const cookieJars = useCookieJars();
const activeWorkspace = useActiveWorkspace();
const [activeCookieJar] = useActiveCookieJar();
const activeCookieJarId = activeCookieJar?.id ?? null;
const kv = useKeyValue<string[]>({
key: kvKey(activeWorkspace?.id ?? 'n/a'),
namespace,
fallback,
});
// Set history when active request changes
useEffect(() => {
kv.set((currentHistory: string[]) => {
if (activeCookieJarId === null) return currentHistory;
const withoutCurrent = currentHistory.filter((id) => id !== activeCookieJarId);
return [activeCookieJarId, ...withoutCurrent];
}).catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeCookieJarId]);
const onlyValidIds = useMemo(
() => kv.value?.filter((id) => cookieJars.data?.some((e) => e.id === id)) ?? [],
[kv.value, cookieJars],
);
return onlyValidIds;
}
export async function getRecentCookieJars(workspaceId: string) {
return getKeyValue<string[]>({
namespace,
key: kvKey(workspaceId),
fallback,
});
}

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo } from 'react';
import { getKeyValue } from '../lib/keyValueStore';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useEnvironments } from './useEnvironments';
import { useKeyValue } from './useKeyValue';
@@ -11,10 +11,10 @@ const fallback: string[] = [];
export function useRecentEnvironments() {
const environments = useEnvironments();
const activeWorkspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
const activeWorkspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const kv = useKeyValue<string[]>({
key: kvKey(activeWorkspaceId ?? 'n/a'),
key: kvKey(activeWorkspace?.id ?? 'n/a'),
namespace,
fallback,
});
@@ -22,12 +22,12 @@ export function useRecentEnvironments() {
// Set history when active request changes
useEffect(() => {
kv.set((currentHistory: string[]) => {
if (activeEnvironmentId === null) return currentHistory;
const withoutCurrentEnvironment = currentHistory.filter((id) => id !== activeEnvironmentId);
return [activeEnvironmentId, ...withoutCurrentEnvironment];
if (activeEnvironment === null) return currentHistory;
const withoutCurrentEnvironment = currentHistory.filter((id) => id !== activeEnvironment.id);
return [activeEnvironment.id, ...withoutCurrentEnvironment];
}).catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeEnvironmentId]);
}, [activeEnvironment?.id]);
const onlyValidIds = useMemo(
() => kv.value?.filter((id) => environments.some((e) => e.id === id)) ?? [],

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo } from 'react';
import { getKeyValue } from '../lib/keyValueStore';
import { useActiveRequestId } from './useActiveRequestId';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useKeyValue } from './useKeyValue';
import { useRequests } from './useRequests';
@@ -11,11 +11,11 @@ const fallback: string[] = [];
export function useRecentRequests() {
const requests = useRequests();
const activeWorkspaceId = useActiveWorkspaceId();
const activeWorkspace = useActiveWorkspace();
const activeRequestId = useActiveRequestId();
const kv = useKeyValue<string[]>({
key: kvKey(activeWorkspaceId ?? 'n/a'),
key: kvKey(activeWorkspace?.id ?? 'n/a'),
namespace,
fallback,
});

View File

@@ -1,6 +1,6 @@
import { useEffect, useMemo } from 'react';
import { getKeyValue } from '../lib/keyValueStore';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useKeyValue } from './useKeyValue';
import { useWorkspaces } from './useWorkspaces';
@@ -10,7 +10,7 @@ const fallback: string[] = [];
export function useRecentWorkspaces() {
const workspaces = useWorkspaces();
const activeWorkspaceId = useActiveWorkspaceId();
const activeWorkspace = useActiveWorkspace();
const kv = useKeyValue<string[]>({
key: kvKey(),
namespace,
@@ -20,12 +20,12 @@ export function useRecentWorkspaces() {
// Set history when active request changes
useEffect(() => {
kv.set((currentHistory: string[]) => {
if (activeWorkspaceId === null) return currentHistory;
const withoutCurrent = currentHistory.filter((id) => id !== activeWorkspaceId);
return [activeWorkspaceId, ...withoutCurrent];
if (activeWorkspace === null) return currentHistory;
const withoutCurrent = currentHistory.filter((id) => id !== activeWorkspace.id);
return [activeWorkspace.id, ...withoutCurrent];
}).catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeWorkspaceId]);
}, [activeWorkspace]);
const onlyValidIds = useMemo(
() => kv.value?.filter((id) => workspaces.some((w) => w.id === id)) ?? [],

View File

@@ -1,18 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import { save } from '@tauri-apps/plugin-dialog';
import slugify from 'slugify';
import { trackEvent } from '../lib/analytics';
import type { HttpResponse } from '@yaakapp/api';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveCookieJar } from './useActiveCookieJar';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useAlert } from './useAlert';
import { useHttpRequests } from './useHttpRequests';
export function useSendAnyHttpRequest(options: { download?: boolean } = {}) {
const environment = useActiveEnvironment();
export function useSendAnyHttpRequest() {
const [environment] = useActiveEnvironment();
const alert = useAlert();
const { activeCookieJar } = useActiveCookieJar();
const [activeCookieJar] = useActiveCookieJar();
const requests = useHttpRequests();
return useMutation<HttpResponse | null, string, string | null>({
mutationKey: ['send_any_request'],
@@ -22,21 +20,9 @@ export function useSendAnyHttpRequest(options: { download?: boolean } = {}) {
return null;
}
let downloadDir: string | null = null;
if (options.download) {
downloadDir = await save({
title: 'Select Download Destination',
defaultPath: slugify(request.name, { lower: true, trim: true, strict: true }),
});
if (downloadDir == null) {
return null;
}
}
return invokeCmd('cmd_send_http_request', {
request,
environmentId: environment?.id,
downloadDir: downloadDir,
cookieJarId: activeCookieJar?.id,
});
},

View File

@@ -1,11 +1,11 @@
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useKeyValue } from './useKeyValue';
export function useSidebarHidden() {
const activeWorkspaceId = useActiveWorkspaceId();
const activeWorkspace = useActiveWorkspace();
const { set, value } = useKeyValue<boolean>({
namespace: 'no_sync',
key: ['sidebar_hidden', activeWorkspaceId ?? 'n/a'],
key: ['sidebar_hidden', activeWorkspace?.id ?? 'n/a'],
fallback: false,
});

View File

@@ -1,10 +1,13 @@
import { useCallback, useMemo } from 'react';
import { useLocalStorage } from 'react-use';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useActiveWorkspace } from './useActiveWorkspace';
export function useSidebarWidth() {
const activeWorkspaceId = useActiveWorkspaceId();
const [width, setWidth] = useLocalStorage<number>(`sidebar_width::${activeWorkspaceId}`, 250);
const activeWorkspace = useActiveWorkspace();
const [width, setWidth] = useLocalStorage<number>(
`sidebar_width::${activeWorkspace?.id ?? 'n/a'}`,
250,
);
const resetWidth = useCallback(() => setWidth(250), [setWidth]);
return useMemo(() => ({ width, setWidth, resetWidth }), [width, setWidth, resetWidth]);
}

View File

@@ -18,6 +18,6 @@ export function useSyncThemeToDocument() {
}
function emitBgChange(t: YaakTheme) {
if (t.background == null) return;
emit('yaak_bg_changed', t.background.hex()).catch(console.error);
if (t.surface == null) return;
emit('yaak_bg_changed', t.surface.hexNoAlpha()).catch(console.error);
}

View File

@@ -11,7 +11,7 @@ import { emit } from '@tauri-apps/api/event';
export function useSyncWorkspaceRequestTitle() {
const activeRequest = useActiveRequest();
const activeWorkspace = useActiveWorkspace();
const activeEnvironment = useActiveEnvironment();
const [activeEnvironment] = useActiveEnvironment();
const osInfo = useOsInfo();
const appInfo = useAppInfo();

View File

@@ -1,12 +1,9 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp/api';
import { getFolder } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { foldersQueryKey } from './useFolders';
export function useUpdateAnyFolder() {
const queryClient = useQueryClient();
return useMutation<void, unknown, { id: string; update: (r: Folder) => Folder }>({
mutationKey: ['update_any_folder'],
mutationFn: async ({ id, update }) => {
@@ -17,12 +14,5 @@ export function useUpdateAnyFolder() {
await invokeCmd('cmd_update_folder', { folder: update(folder) });
},
onMutate: async ({ id, update }) => {
const folder = await getFolder(id);
if (folder === null) return;
queryClient.setQueryData<Folder[]>(foldersQueryKey(folder), (folders) =>
(folders ?? []).map((f) => (f.id === folder.id ? update(f) : f)),
);
},
});
}

View File

@@ -1,12 +1,9 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp/api';
import { getGrpcRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { grpcRequestsQueryKey } from './useGrpcRequests';
export function useUpdateAnyGrpcRequest() {
const queryClient = useQueryClient();
return useMutation<
void,
unknown,
@@ -23,14 +20,5 @@ export function useUpdateAnyGrpcRequest() {
typeof update === 'function' ? update(request) : { ...request, ...update };
await invokeCmd('cmd_update_grpc_request', { request: patchedRequest });
},
onMutate: async ({ id, update }) => {
const request = await getGrpcRequest(id);
if (request === null) return;
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
queryClient.setQueryData<GrpcRequest[]>(grpcRequestsQueryKey(request), (requests) =>
(requests ?? []).map((r) => (r.id === patchedRequest.id ? patchedRequest : r)),
);
},
});
}

View File

@@ -1,12 +1,9 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp/api';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { httpRequestsQueryKey } from './useHttpRequests';
export function useUpdateAnyHttpRequest() {
const queryClient = useQueryClient();
return useMutation<
void,
unknown,
@@ -23,14 +20,5 @@ export function useUpdateAnyHttpRequest() {
typeof update === 'function' ? update(request) : { ...request, ...update };
await invokeCmd('cmd_update_http_request', { request: patchedRequest });
},
onMutate: async ({ id, update }) => {
const request = await getHttpRequest(id);
if (request === null) return;
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey(request), (requests) =>
(requests ?? []).map((r) => (r.id === patchedRequest.id ? patchedRequest : r)),
);
},
});
}

View File

@@ -1,11 +1,9 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { CookieJar } from '../lib/models';
import { getCookieJar } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { cookieJarsQueryKey } from './useCookieJars';
export function useUpdateCookieJar(id: string | null) {
const queryClient = useQueryClient();
return useMutation<void, unknown, Partial<CookieJar> | ((j: CookieJar) => CookieJar)>({
mutationKey: ['update_cookie_jar', id],
mutationFn: async (v) => {
@@ -15,17 +13,7 @@ export function useUpdateCookieJar(id: string | null) {
}
const newCookieJar = typeof v === 'function' ? v(cookieJar) : { ...cookieJar, ...v };
console.log('NEW COOKIE JAR', newCookieJar.cookies.length);
await invokeCmd('cmd_update_cookie_jar', { cookieJar: newCookieJar });
},
onMutate: async (v) => {
const cookieJar = await getCookieJar(id);
if (cookieJar === null) return;
const newCookieJar = typeof v === 'function' ? v(cookieJar) : { ...cookieJar, ...v };
queryClient.setQueryData<CookieJar[]>(cookieJarsQueryKey(cookieJar), (cookieJars) =>
(cookieJars ?? []).map((j) => (j.id === newCookieJar.id ? newCookieJar : j)),
);
},
});
}

View File

@@ -1,11 +1,9 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp/api';
import { getEnvironment } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { environmentsQueryKey } from './useEnvironments';
export function useUpdateEnvironment(id: string | null) {
const queryClient = useQueryClient();
return useMutation<void, unknown, Partial<Environment> | ((r: Environment) => Environment)>({
mutationKey: ['update_environment', id],
mutationFn: async (v) => {
@@ -17,14 +15,5 @@ export function useUpdateEnvironment(id: string | null) {
const newEnvironment = typeof v === 'function' ? v(environment) : { ...environment, ...v };
await invokeCmd('cmd_update_environment', { environment: newEnvironment });
},
onMutate: async (v) => {
const environment = await getEnvironment(id);
if (environment === null) return;
const newEnvironment = typeof v === 'function' ? v(environment) : { ...environment, ...v };
queryClient.setQueryData<Environment[]>(environmentsQueryKey(environment), (environments) =>
(environments ?? []).map((r) => (r.id === newEnvironment.id ? newEnvironment : r)),
);
},
});
}

View File

@@ -1,11 +1,9 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { Workspace } from '@yaakapp/api';
import { getWorkspace } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { workspacesQueryKey } from './useWorkspaces';
export function useUpdateWorkspace(id: string | null) {
const queryClient = useQueryClient();
return useMutation<void, unknown, Partial<Workspace> | ((w: Workspace) => Workspace)>({
mutationKey: ['update_workspace', id],
mutationFn: async (v) => {
@@ -17,14 +15,5 @@ export function useUpdateWorkspace(id: string | null) {
const newWorkspace = typeof v === 'function' ? v(workspace) : { ...workspace, ...v };
await invokeCmd('cmd_update_workspace', { workspace: newWorkspace });
},
onMutate: async (v) => {
const workspace = await getWorkspace(id);
if (workspace === null) return;
const newWorkspace = typeof v === 'function' ? v(workspace) : { ...workspace, ...v };
queryClient.setQueryData<Workspace[]>(workspacesQueryKey(workspace), (workspaces) =>
(workspaces ?? []).map((w) => (w.id === newWorkspace.id ? newWorkspace : w)),
);
},
});
}

View File

@@ -17,6 +17,20 @@ export async function setKeyValue<T>({
});
}
export async function getKeyValueRaw({
namespace = 'global',
key,
}: {
namespace?: string;
key: string | string[];
}) {
const kv = (await invokeCmd('cmd_get_key_value', {
namespace,
key: buildKeyValueKey(key),
})) as KeyValue | null;
return kv;
}
export async function getKeyValue<T>({
namespace = 'global',
key,
@@ -26,14 +40,11 @@ export async function getKeyValue<T>({
key: string | string[];
fallback: T;
}) {
const kv = (await invokeCmd('cmd_get_key_value', {
namespace,
key: buildKeyValueKey(key),
})) as KeyValue | null;
const kv = await getKeyValueRaw({ namespace, key });
return extractKeyValueOrFallback(kv, fallback);
}
function extractKeyValue<T>(kv: KeyValue | null): T | undefined {
export function extractKeyValue<T>(kv: KeyValue | null): T | undefined {
if (kv === null) return undefined;
try {
return JSON.parse(kv.value) as T;

View File

@@ -36,6 +36,9 @@ export function isResponseLoading(response: HttpResponse | GrpcConnection): bool
}
export function modelsEq(a: Model, b: Model) {
if (a.model != b.model) {
return false;
}
if (a.model === 'key_value' && b.model === 'key_value') {
return a.key === b.key && a.namespace === b.namespace;
}

View File

@@ -1,6 +1,6 @@
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
type Appearance = 'light' | 'dark';
export type Appearance = 'light' | 'dark';
export function getCSSAppearance(): Appearance {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';

View File

@@ -310,7 +310,6 @@ export function completeTheme(theme: YaakTheme): YaakTheme {
theme.border = theme.border ?? theme.surface?.lift(0.12);
theme.borderSubtle = theme.borderSubtle ?? theme.border?.lower(0.08);
console.log('HELLO', { theme });
theme.text = theme.text ?? theme.border?.lift(1).lower(0.2);
theme.textSubtle = theme.textSubtle ?? theme.text?.lower(0.3);

View File

@@ -104,6 +104,15 @@ export class YaakColor {
return rgbaToHex(r, g, b, a);
}
hexNoAlpha(): string {
const h = this.hue;
const s = this.saturation;
const l = this.lightness;
const [r, g, b] = parseColor(`hsl(${h},${s}%,${l}%)`).rgb;
return rgbaToHexNoAlpha(r, g, b);
}
private _lighten(mod: number): YaakColor {
const c = this.clone();
c.lightness = this.lightness + (100 - this.lightness) * mod;
@@ -125,6 +134,14 @@ function rgbaToHex(r: number, g: number, b: number, a: number): string {
return '#' + [toHex(r), toHex(g), toHex(b), toHex(a * 255)].join('').toUpperCase();
}
function rgbaToHexNoAlpha(r: number, g: number, b: number): string {
const toHex = (n: number): string => {
const hex = Number(Math.round(n)).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
return '#' + [toHex(r), toHex(g), toHex(b)].join('').toUpperCase();
}
function hexToRgba(hex: string): [number, number, number, number] {
const fromHex = (h: string): number => {
if (h === '') return 255;