mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-01 15:03:11 +02:00
Generalized frontend model store (#193)
This commit is contained in:
@@ -1,24 +1,25 @@
|
||||
import type { Folder } from '@yaakapp-internal/models';
|
||||
import { createWorkspaceModel } from '@yaakapp-internal/models';
|
||||
import { applySync, calculateSync } from '@yaakapp-internal/sync';
|
||||
import { Banner } from '../components/core/Banner';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { VStack } from '../components/core/Stacks';
|
||||
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { showConfirm } from '../lib/confirm';
|
||||
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
|
||||
|
||||
export const createFolder = createFastMutation<
|
||||
Folder | null,
|
||||
void,
|
||||
void,
|
||||
Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>
|
||||
>({
|
||||
mutationKey: ['create_folder'],
|
||||
mutationFn: async (patch) => {
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (workspaceId == null) {
|
||||
throw new Error("Cannot create folder when there's no active workspace");
|
||||
}
|
||||
@@ -33,13 +34,13 @@ export const createFolder = createFastMutation<
|
||||
confirmText: 'Create',
|
||||
placeholder: 'Name',
|
||||
});
|
||||
if (name == null) return null;
|
||||
if (name == null) throw new Error('No name provided to create folder');
|
||||
|
||||
patch.name = name;
|
||||
}
|
||||
|
||||
patch.sortPriority = patch.sortPriority || -Date.now();
|
||||
return invokeCmd<Folder>('cmd_update_folder', { folder: { workspaceId, ...patch } });
|
||||
await createWorkspaceModel({ model: 'folder', workspaceId, ...patch });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { WebsocketConnection } from '@yaakapp-internal/models';
|
||||
import { deleteWebsocketConnection as cmdDeleteWebsocketConnection } from '@yaakapp-internal/ws';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
|
||||
export const deleteWebsocketConnection = createFastMutation({
|
||||
mutationKey: ['delete_websocket_connection'],
|
||||
mutationFn: async function (connection: WebsocketConnection) {
|
||||
return cmdDeleteWebsocketConnection(connection.id);
|
||||
},
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { deleteWebsocketRequest as cmdDeleteWebsocketRequest } from '@yaakapp-internal/ws';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { showConfirmDelete } from '../lib/confirm';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
|
||||
export const deleteWebsocketRequest = createFastMutation({
|
||||
mutationKey: ['delete_websocket_request'],
|
||||
mutationFn: async (request: WebsocketRequest) => {
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'delete-websocket-request',
|
||||
title: 'Delete WebSocket Request',
|
||||
description: (
|
||||
<>
|
||||
Permanently delete <InlineCode>{resolvedModelName(request)}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cmdDeleteWebsocketRequest(request.id);
|
||||
},
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import { duplicateWebsocketRequest as cmdDuplicateWebsocketRequest } from '@yaakapp-internal/ws';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { router } from '../lib/router';
|
||||
|
||||
export const duplicateWebsocketRequest = createFastMutation({
|
||||
mutationKey: ['delete_websocket_connection'],
|
||||
mutationFn: async function (requestId: string) {
|
||||
return cmdDuplicateWebsocketRequest(requestId);
|
||||
},
|
||||
onSuccess: async (request) => {
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: request.workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,13 +1,14 @@
|
||||
import { SettingsTab } from '../components/Settings/SettingsTab';
|
||||
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export const openSettings = createFastMutation<void, string, SettingsTab | null>({
|
||||
mutationKey: ['open_settings'],
|
||||
mutationFn: async function (tab) {
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (workspaceId == null) return;
|
||||
|
||||
const location = router.buildLocation({
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { WorkspaceSettingsDialog } from '../components/WorkspaceSettingsDialog';
|
||||
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
|
||||
export const openWorkspaceSettings = createFastMutation<void, string, { openSyncMenu?: boolean }>({
|
||||
mutationKey: ['open_workspace_settings'],
|
||||
async mutationFn({ openSyncMenu }) {
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
showDialog({
|
||||
id: 'workspace-settings',
|
||||
title: 'Workspace Settings',
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { upsertWebsocketRequest as cmdUpsertWebsocketRequest } from '@yaakapp-internal/ws';
|
||||
import { differenceInMilliseconds } from 'date-fns';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { router } from '../lib/router';
|
||||
|
||||
export const upsertWebsocketRequest = createFastMutation<
|
||||
WebsocketRequest,
|
||||
void,
|
||||
Parameters<typeof cmdUpsertWebsocketRequest>[0]
|
||||
>({
|
||||
mutationKey: ['upsert_websocket_request'],
|
||||
mutationFn: (request) => cmdUpsertWebsocketRequest(request),
|
||||
onSuccess: async (request) => {
|
||||
const isNew = differenceInMilliseconds(new Date(), request.createdAt + 'Z') < 100;
|
||||
|
||||
if (isNew) {
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: request.workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export const upsertWorkspace = createFastMutation<
|
||||
Workspace,
|
||||
void,
|
||||
Workspace | Partial<Omit<Workspace, 'id'>>
|
||||
>({
|
||||
mutationKey: ['upsert_workspace'],
|
||||
mutationFn: (workspace) => invokeCmd<Workspace>('cmd_update_workspace', { workspace }),
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { workspaceMetaAtom } from '../hooks/useWorkspaceMeta';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export const upsertWorkspaceMeta = createFastMutation<
|
||||
WorkspaceMeta,
|
||||
unknown,
|
||||
WorkspaceMeta | (Partial<Omit<WorkspaceMeta, 'id'>> & { workspaceId: string })
|
||||
>({
|
||||
mutationKey: ['update_workspace_meta'],
|
||||
mutationFn: async (patch) => {
|
||||
const workspaceMeta = jotaiStore.get(workspaceMetaAtom);
|
||||
return invokeCmd<WorkspaceMeta>('cmd_update_workspace_meta', {
|
||||
workspaceMeta: { ...workspaceMeta, ...patch },
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { fuzzyFilter } from 'fuzzbunny';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import type { KeyboardEvent, ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createFolder } from '../commands/commands';
|
||||
@@ -8,26 +10,25 @@ import { switchWorkspace } from '../commands/switchWorkspace';
|
||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useAllRequests } from '../hooks/useAllRequests';
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
|
||||
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDebouncedState } from '../hooks/useDebouncedState';
|
||||
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||
import type { HotkeyAction } from '../hooks/useHotKey';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useHttpRequestActions } from '../hooks/useHttpRequestActions';
|
||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { useRenameRequest } from '../hooks/useRenameRequest';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||
import { showDialog, toggleDialog } from '../lib/dialog';
|
||||
import { renameModelWithPrompt } from '../lib/renameModelWithPrompt';
|
||||
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
|
||||
import { router } from '../lib/router';
|
||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||
@@ -60,23 +61,20 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
const [selectedItemKey, setSelectedItemKey] = useState<string | null>(null);
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
const httpRequestActions = useHttpRequestActions();
|
||||
const workspaces = useWorkspaces();
|
||||
const { subEnvironments } = useEnvironments();
|
||||
const workspaces = useAtomValue(workspacesAtom);
|
||||
const { baseEnvironment, subEnvironments } = useEnvironmentsBreakdown();
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const recentEnvironments = useRecentEnvironments();
|
||||
const recentWorkspaces = useRecentWorkspaces();
|
||||
const requests = useRequests();
|
||||
const requests = useAllRequests();
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeCookieJar = useActiveCookieJar();
|
||||
const [recentRequests] = useRecentRequests();
|
||||
const [, setSidebarHidden] = useSidebarHidden();
|
||||
const { baseEnvironment } = useEnvironments();
|
||||
const { mutate: createHttpRequest } = useCreateHttpRequest();
|
||||
const { mutate: createGrpcRequest } = useCreateGrpcRequest();
|
||||
const { mutate: createEnvironment } = useCreateEnvironment();
|
||||
const { mutate: sendRequest } = useSendAnyHttpRequest();
|
||||
const { mutate: renameRequest } = useRenameRequest(activeRequest?.id ?? null);
|
||||
const { mutate: deleteRequest } = useDeleteAnyRequest();
|
||||
|
||||
const workspaceCommands = useMemo<CommandPaletteItem[]>(() => {
|
||||
const commands: CommandPaletteItem[] = [
|
||||
@@ -166,13 +164,13 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
commands.push({
|
||||
key: 'http_request.rename',
|
||||
label: 'Rename Request',
|
||||
onSelect: renameRequest,
|
||||
onSelect: () => renameModelWithPrompt(activeRequest),
|
||||
});
|
||||
|
||||
commands.push({
|
||||
key: 'http_request.delete',
|
||||
key: 'sidebar.delete_selected_item',
|
||||
label: 'Delete Request',
|
||||
onSelect: () => deleteRequest(activeRequest.id),
|
||||
onSelect: () => deleteModelWithConfirm(activeRequest),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,9 +188,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
createGrpcRequest,
|
||||
createHttpRequest,
|
||||
createWorkspace,
|
||||
deleteRequest,
|
||||
httpRequestActions,
|
||||
renameRequest,
|
||||
sendRequest,
|
||||
setSidebarHidden,
|
||||
]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Cookie } from '@yaakapp-internal/models';
|
||||
import { useCookieJars } from '../hooks/useCookieJars';
|
||||
import { useUpdateCookieJar } from '../hooks/useUpdateCookieJar';
|
||||
import type { Cookie} from '@yaakapp-internal/models';
|
||||
import { cookieJarsAtom, patchModel } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { cookieDomain } from '../lib/model_util';
|
||||
import { Banner } from './core/Banner';
|
||||
import { IconButton } from './core/IconButton';
|
||||
@@ -11,8 +11,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const CookieDialog = function ({ cookieJarId }: Props) {
|
||||
const updateCookieJar = useUpdateCookieJar(cookieJarId ?? null);
|
||||
const cookieJars = useCookieJars();
|
||||
const cookieJars = useAtomValue(cookieJarsAtom);
|
||||
const cookieJar = cookieJars?.find((c) => c.id === cookieJarId);
|
||||
|
||||
if (cookieJar == null) {
|
||||
@@ -39,7 +38,7 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{cookieJar?.cookies.map((c: Cookie) => (
|
||||
<tr key={c.domain + c.raw_cookie}>
|
||||
<tr key={c.domain + c.raw_cookie + c.path + c.expires}>
|
||||
<td className="py-2 select-text cursor-text font-mono font-semibold max-w-0">
|
||||
{cookieDomain(c)}
|
||||
</td>
|
||||
@@ -53,12 +52,11 @@ export const CookieDialog = function ({ cookieJarId }: Props) {
|
||||
iconSize="sm"
|
||||
title="Delete"
|
||||
className="ml-auto"
|
||||
onClick={async () => {
|
||||
await updateCookieJar.mutateAsync({
|
||||
...cookieJar,
|
||||
onClick={() =>
|
||||
patchModel(cookieJar, {
|
||||
cookies: cookieJar.cookies.filter((c2: Cookie) => c2 !== c),
|
||||
});
|
||||
}}
|
||||
})
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { cookieJarsAtom, patchModel } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { cookieJarsAtom } from '../hooks/useCookieJars';
|
||||
import { useCreateCookieJar } from '../hooks/useCreateCookieJar';
|
||||
import { useDeleteCookieJar } from '../hooks/useDeleteCookieJar';
|
||||
import { useUpdateCookieJar } from '../hooks/useUpdateCookieJar';
|
||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||
@@ -16,8 +15,6 @@ import { InlineCode } from './core/InlineCode';
|
||||
|
||||
export const CookieDropdown = memo(function CookieDropdown() {
|
||||
const activeCookieJar = useActiveCookieJar();
|
||||
const updateCookieJar = useUpdateCookieJar(activeCookieJar?.id ?? null);
|
||||
const deleteCookieJar = useDeleteCookieJar(activeCookieJar ?? null);
|
||||
const createCookieJar = useCreateCookieJar();
|
||||
const cookieJars = useAtomValue(cookieJarsAtom);
|
||||
|
||||
@@ -67,7 +64,7 @@ export const CookieDropdown = memo(function CookieDropdown() {
|
||||
defaultValue: activeCookieJar?.name,
|
||||
});
|
||||
if (name == null) return;
|
||||
updateCookieJar.mutate({ name });
|
||||
await patchModel(activeCookieJar, { name });
|
||||
},
|
||||
},
|
||||
...(((cookieJars ?? []).length > 1 // Never delete the last one
|
||||
@@ -76,7 +73,9 @@ export const CookieDropdown = memo(function CookieDropdown() {
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
color: 'danger',
|
||||
onSelect: deleteCookieJar.mutate,
|
||||
onSelect: async () => {
|
||||
await deleteModelWithConfirm(activeCookieJar);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []) as DropdownItem[]),
|
||||
@@ -90,7 +89,7 @@ export const CookieDropdown = memo(function CookieDropdown() {
|
||||
onSelect: () => createCookieJar.mutate(),
|
||||
},
|
||||
];
|
||||
}, [activeCookieJar, cookieJars, createCookieJar, deleteCookieJar, updateCookieJar]);
|
||||
}, [activeCookieJar, cookieJars, createCookieJar]);
|
||||
|
||||
return (
|
||||
<Dropdown items={items}>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useGitInit } from '@yaakapp-internal/git';
|
||||
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
||||
import { createGlobalModel, patchModel } from '@yaakapp-internal/models';
|
||||
import { useState } from 'react';
|
||||
import { upsertWorkspace } from '../commands/upsertWorkspace';
|
||||
import { upsertWorkspaceMeta } from '../commands/upsertWorkspaceMeta';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { showErrorToast } from '../lib/toast';
|
||||
@@ -31,16 +30,15 @@ export function CreateWorkspaceDialog({ hide }: Props) {
|
||||
className="pb-3 max-h-[50vh]"
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
const workspace = await upsertWorkspace.mutateAsync({ name });
|
||||
if (workspace == null) return;
|
||||
const workspaceId = await createGlobalModel({ model: 'workspace', name });
|
||||
if (workspaceId == null) return;
|
||||
|
||||
// Do getWorkspaceMeta instead of naively creating one because it might have
|
||||
// been created already when the store refreshes the workspace meta after
|
||||
const workspaceMeta = await invokeCmd<WorkspaceMeta>('cmd_get_workspace_meta', {
|
||||
workspaceId: workspace.id,
|
||||
workspaceId,
|
||||
});
|
||||
await upsertWorkspaceMeta.mutateAsync({
|
||||
...workspaceMeta,
|
||||
await patchModel(workspaceMeta, {
|
||||
settingSyncDir: syncConfig.filePath,
|
||||
});
|
||||
|
||||
@@ -53,7 +51,7 @@ export function CreateWorkspaceDialog({ hide }: Props) {
|
||||
// Navigate to workspace
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: workspace.id },
|
||||
params: { workspaceId },
|
||||
});
|
||||
|
||||
hide();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Folder, HttpRequest } from '@yaakapp-internal/models';
|
||||
import { foldersAtom, httpRequestsAtom } from '@yaakapp-internal/models';
|
||||
import type {
|
||||
FormInput,
|
||||
FormInputCheckbox,
|
||||
@@ -10,10 +11,9 @@ import type {
|
||||
JsonPrimitive,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import { useCallback } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useFolders } from '../hooks/useFolders';
|
||||
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||
import { capitalize } from '../lib/capitalize';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { Banner } from './core/Banner';
|
||||
@@ -354,8 +354,8 @@ function HttpRequestArg({
|
||||
value: string;
|
||||
onChange: (v: string) => void;
|
||||
}) {
|
||||
const folders = useFolders();
|
||||
const httpRequests = useHttpRequests();
|
||||
const folders = useAtomValue(foldersAtom);
|
||||
const httpRequests = useAtomValue(httpRequestsAtom);
|
||||
const activeRequest = useActiveRequest();
|
||||
return (
|
||||
<Select
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||
import { toggleDialog } from '../lib/dialog';
|
||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||
import type { ButtonProps } from './core/Button';
|
||||
@@ -19,7 +19,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
className,
|
||||
...buttonProps
|
||||
}: Props) {
|
||||
const { subEnvironments, baseEnvironment } = useEnvironments();
|
||||
const { subEnvironments, baseEnvironment } = useEnvironmentsBreakdown();
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
|
||||
const showEnvironmentDialog = useCallback(() => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { Environment } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment';
|
||||
import { useEnvironments } from '../hooks/useEnvironments';
|
||||
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
@@ -29,8 +29,7 @@ interface Props {
|
||||
|
||||
export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
const { baseEnvironment, subEnvironments, allEnvironments } = useEnvironments();
|
||||
|
||||
const { baseEnvironment, subEnvironments, allEnvironments } = useEnvironmentsBreakdown();
|
||||
const [selectedEnvironmentId, setSelectedEnvironmentId] = useState<string | null>(
|
||||
initialEnvironment?.id ?? null,
|
||||
);
|
||||
@@ -42,9 +41,8 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
||||
|
||||
const handleCreateEnvironment = async () => {
|
||||
if (baseEnvironment == null) return;
|
||||
const e = await createEnvironment.mutateAsync(baseEnvironment);
|
||||
if (e == null) return;
|
||||
setSelectedEnvironmentId(e.id);
|
||||
const id = await createEnvironment.mutateAsync(baseEnvironment);
|
||||
setSelectedEnvironmentId(id);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -127,11 +125,10 @@ const EnvironmentEditor = function ({
|
||||
key: 'environmentValueVisibility',
|
||||
fallback: true,
|
||||
});
|
||||
const { allEnvironments } = useEnvironments();
|
||||
const updateEnvironment = useUpdateEnvironment(activeEnvironment?.id ?? null);
|
||||
const { allEnvironments } = useEnvironmentsBreakdown();
|
||||
const handleChange = useCallback<PairEditorProps['onChange']>(
|
||||
(variables) => updateEnvironment.mutate({ variables }),
|
||||
[updateEnvironment],
|
||||
(variables) => patchModel(activeEnvironment, { variables }),
|
||||
[activeEnvironment],
|
||||
);
|
||||
|
||||
// Gather a list of env names from other environments, to help the user get them aligned
|
||||
@@ -217,8 +214,6 @@ function SidebarButton({
|
||||
rightSlot?: ReactNode;
|
||||
environment: Environment | null;
|
||||
}) {
|
||||
const updateEnvironment = useUpdateEnvironment(environment?.id ?? null);
|
||||
const deleteEnvironment = useDeleteEnvironment(environment);
|
||||
const [showContextMenu, setShowContextMenu] = useState<{
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -277,17 +272,16 @@ function SidebarButton({
|
||||
defaultValue: environment.name,
|
||||
});
|
||||
if (name == null) return;
|
||||
updateEnvironment.mutate({ name });
|
||||
await patchModel(environment, { name });
|
||||
},
|
||||
},
|
||||
{
|
||||
color: 'danger',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" size="sm" />,
|
||||
onSelect: () => {
|
||||
deleteEnvironment.mutate(undefined, {
|
||||
onSuccess: onDelete,
|
||||
});
|
||||
onSelect: async () => {
|
||||
await deleteModelWithConfirm(environment);
|
||||
onDelete?.();
|
||||
},
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { save } from '@tauri-apps/plugin-dialog';
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import type { Workspace} from '@yaakapp-internal/models';
|
||||
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import slugify from 'slugify';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { Banner } from './core/Banner';
|
||||
@@ -17,8 +18,8 @@ interface Props {
|
||||
}
|
||||
|
||||
export function ExportDataDialog({ onHide, onSuccess }: Props) {
|
||||
const allWorkspaces = useWorkspaces();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const allWorkspaces = useAtomValue(workspacesAtom);
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
if (activeWorkspace == null || allWorkspaces.length === 0) return null;
|
||||
|
||||
return (
|
||||
@@ -40,7 +41,7 @@ function ExportDataDialogContent({
|
||||
allWorkspaces: Workspace[];
|
||||
activeWorkspace: Workspace;
|
||||
}) {
|
||||
const [includeEnvironments, setIncludeEnvironments] = useState<boolean>(true);
|
||||
const [includeEnvironments, setIncludeEnvironments] = useState<boolean>(false);
|
||||
const [selectedWorkspaces, setSelectedWorkspaces] = useState<Record<string, boolean>>({
|
||||
[activeWorkspace.id]: true,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useFolders } from '../hooks/useFolders';
|
||||
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
|
||||
import { foldersAtom, patchModel } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import { Input } from './core/Input';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
@@ -9,8 +9,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export function FolderSettingsDialog({ folderId }: Props) {
|
||||
const { mutate: updateFolder } = useUpdateAnyFolder();
|
||||
const folders = useFolders();
|
||||
const folders = useAtomValue(foldersAtom);
|
||||
const folder = folders.find((f) => f.id === folderId);
|
||||
|
||||
if (folder == null) return null;
|
||||
@@ -20,10 +19,7 @@ export function FolderSettingsDialog({ folderId }: Props) {
|
||||
<Input
|
||||
label="Folder Name"
|
||||
defaultValue={folder.name}
|
||||
onChange={(name) => {
|
||||
if (folderId == null) return;
|
||||
updateFolder({ id: folderId, update: (folder) => ({ ...folder, name }) });
|
||||
}}
|
||||
onChange={(name) => patchModel(folder, { name })}
|
||||
stateKey={`name.${folder.id}`}
|
||||
/>
|
||||
|
||||
@@ -33,13 +29,7 @@ export function FolderSettingsDialog({ folderId }: Props) {
|
||||
className="min-h-[10rem] border border-border px-2"
|
||||
defaultValue={folder.description}
|
||||
stateKey={`description.${folder.id}`}
|
||||
onChange={(description) => {
|
||||
if (folderId == null) return;
|
||||
updateFolder({
|
||||
id: folderId,
|
||||
update: (folder) => ({ ...folder, description }),
|
||||
});
|
||||
}}
|
||||
onChange={(description) => patchModel(folder, { description })}
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useGit } from '@yaakapp-internal/git';
|
||||
import type { WorkspaceMeta } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from '../hooks/useActiveWorkspace';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
||||
import { sync } from '../init/sync';
|
||||
import { showConfirm, showConfirmDelete } from '../lib/confirm';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
@@ -22,7 +22,7 @@ import { HistoryDialog } from './git/HistoryDialog';
|
||||
import { GitCommitDialog } from './GitCommitDialog';
|
||||
|
||||
export function GitDropdown() {
|
||||
const workspaceMeta = useWorkspaceMeta();
|
||||
const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom);
|
||||
if (workspaceMeta == null) return null;
|
||||
|
||||
if (workspaceMeta.settingSyncDir == null) {
|
||||
@@ -33,7 +33,7 @@ export function GitDropdown() {
|
||||
}
|
||||
|
||||
function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
||||
const workspace = useActiveWorkspace();
|
||||
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||
const [
|
||||
{ status, log },
|
||||
{ branch, deleteBranch, fetchAll, mergeBranch, push, pull, checkout, init },
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useSubscribeHttpAuthentication } from '../hooks/useHttpAuthentication';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||
import { useSyncModelStores } from '../hooks/useSyncModelStores';
|
||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||
import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions';
|
||||
@@ -17,7 +16,6 @@ import { showPrompt } from '../lib/prompt';
|
||||
import { showToast } from '../lib/toast';
|
||||
|
||||
export function GlobalHooks() {
|
||||
useSyncModelStores();
|
||||
useSyncZoomSetting();
|
||||
useSyncFontSizeSetting();
|
||||
useGenerateThemeCss();
|
||||
|
||||
@@ -3,9 +3,10 @@ import { updateSchema } from 'cm6-graphql';
|
||||
import type { EditorView } from 'codemirror';
|
||||
|
||||
import { formatSdl } from 'format-graphql';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
|
||||
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
@@ -30,19 +31,20 @@ export function GraphQLEditor({ request, onChange, baseRequest, ...extraEditorPr
|
||||
const { schema, isLoading, error, refetch, clear } = useIntrospectGraphQL(baseRequest, {
|
||||
disabled: autoIntrospectDisabled?.[baseRequest.id],
|
||||
});
|
||||
const [currentBody, setCurrentBody] = useState<{ query: string; variables: string | undefined }>(
|
||||
() => {
|
||||
// Migrate text bodies to GraphQL format
|
||||
// NOTE: This is how GraphQL used to be stored
|
||||
if ('text' in request.body) {
|
||||
const b = tryParseJson(request.body.text, {});
|
||||
const variables = JSON.stringify(b.variables || undefined, null, 2);
|
||||
return { query: b.query ?? '', variables };
|
||||
}
|
||||
const [currentBody, setCurrentBody] = useStateWithDeps<{
|
||||
query: string;
|
||||
variables: string | undefined;
|
||||
}>(() => {
|
||||
// Migrate text bodies to GraphQL format
|
||||
// NOTE: This is how GraphQL used to be stored
|
||||
if ('text' in request.body) {
|
||||
const b = tryParseJson(request.body.text, {});
|
||||
const variables = JSON.stringify(b.variables || undefined, null, 2);
|
||||
return { query: b.query ?? '', variables };
|
||||
}
|
||||
|
||||
return { query: request.body.query ?? '', variables: request.body.variables ?? '' };
|
||||
},
|
||||
);
|
||||
return { query: request.body.query ?? '', variables: request.body.variables ?? '' };
|
||||
}, [extraEditorProps.forceUpdateKey]);
|
||||
|
||||
const handleChangeQuery = (query: string) => {
|
||||
const newBody = { query, variables: currentBody.variables || undefined };
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useGrpc } from '../hooks/useGrpc';
|
||||
import { useGrpcConnections } from '../hooks/useGrpcConnections';
|
||||
import { useGrpcEvents } from '../hooks/useGrpcEvents';
|
||||
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { activeGrpcConnectionAtom, useGrpcEvents } from '../hooks/usePinnedGrpcConnection';
|
||||
import { Banner } from './core/Banner';
|
||||
import { HotKeyList } from './core/HotKeyList';
|
||||
import { SplitLayout } from './core/SplitLayout';
|
||||
import { GrpcConnectionMessagesPane } from './GrpcConnectionMessagesPane';
|
||||
import { GrpcConnectionSetupPane } from './GrpcConnectionSetupPane';
|
||||
import { GrpcRequestPane } from './GrpcRequestPane';
|
||||
import { GrpcResponsePane } from './GrpcResponsePane';
|
||||
|
||||
interface Props {
|
||||
style: CSSProperties;
|
||||
@@ -21,10 +21,8 @@ const emptyArray: string[] = [];
|
||||
|
||||
export function GrpcConnectionLayout({ style }: Props) {
|
||||
const activeRequest = useActiveRequest('grpc_request');
|
||||
const updateRequest = useUpdateAnyGrpcRequest();
|
||||
const connections = useGrpcConnections().filter((c) => c.requestId === activeRequest?.id);
|
||||
const activeConnection = connections[0] ?? null;
|
||||
const messages = useGrpcEvents(activeConnection?.id ?? null);
|
||||
const activeConnection = useAtomValue(activeGrpcConnectionAtom);
|
||||
const grpcEvents = useGrpcEvents(activeConnection?.id ?? null);
|
||||
const protoFilesKv = useGrpcProtoFiles(activeRequest?.id ?? null);
|
||||
const protoFiles = protoFilesKv.value ?? emptyArray;
|
||||
const grpc = useGrpc(activeRequest, activeConnection, protoFiles);
|
||||
@@ -34,25 +32,21 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
if (services == null || activeRequest == null) return;
|
||||
const s = services.find((s) => s.name === activeRequest.service);
|
||||
if (s == null) {
|
||||
updateRequest.mutate({
|
||||
id: activeRequest.id,
|
||||
update: {
|
||||
service: services[0]?.name ?? null,
|
||||
method: services[0]?.methods[0]?.name ?? null,
|
||||
},
|
||||
});
|
||||
patchModel(activeRequest, {
|
||||
service: services[0]?.name ?? null,
|
||||
method: services[0]?.methods[0]?.name ?? null,
|
||||
}).catch(console.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const m = s.methods.find((m) => m.name === activeRequest.method);
|
||||
if (m == null) {
|
||||
updateRequest.mutate({
|
||||
id: activeRequest.id,
|
||||
update: { method: s.methods[0]?.name ?? null },
|
||||
});
|
||||
patchModel(activeRequest, {
|
||||
method: s.methods[0]?.name ?? null,
|
||||
}).catch(console.error);
|
||||
return;
|
||||
}
|
||||
}, [activeRequest, services, updateRequest]);
|
||||
}, [activeRequest, services]);
|
||||
|
||||
const activeMethod = useMemo(() => {
|
||||
if (services == null || activeRequest == null) return null;
|
||||
@@ -87,7 +81,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
className="p-3 gap-1.5"
|
||||
style={style}
|
||||
firstSlot={({ style }) => (
|
||||
<GrpcConnectionSetupPane
|
||||
<GrpcRequestPane
|
||||
style={style}
|
||||
activeRequest={activeRequest}
|
||||
protoFiles={protoFiles}
|
||||
@@ -117,8 +111,8 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
<Banner color="danger" className="m-2">
|
||||
{grpc.go.error}
|
||||
</Banner>
|
||||
) : messages.length >= 0 ? (
|
||||
<GrpcConnectionMessagesPane activeRequest={activeRequest} methodType={methodType} />
|
||||
) : grpcEvents.length >= 0 ? (
|
||||
<GrpcResponsePane activeRequest={activeRequest} methodType={methodType} />
|
||||
) : (
|
||||
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.focus', 'url_bar.focus']} />
|
||||
)}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Editor } from './core/Editor/Editor';
|
||||
import { FormattedError } from './core/FormattedError';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { GrpcProtoSelection } from './GrpcProtoSelection';
|
||||
import { GrpcProtoSelectionDialog } from './GrpcProtoSelectionDialog';
|
||||
|
||||
type Props = Pick<EditorProps, 'heightMode' | 'onChange' | 'className' | 'forceUpdateKey'> & {
|
||||
services: ReflectResponseService[] | null;
|
||||
@@ -143,13 +143,7 @@ export function GrpcEditor({
|
||||
title: 'Configure Schema',
|
||||
size: 'md',
|
||||
id: 'reflection-failed',
|
||||
render: ({ hide }) => {
|
||||
return (
|
||||
<VStack space={6} className="pb-5">
|
||||
<GrpcProtoSelection onDone={hide} requestId={request.id} />
|
||||
</VStack>
|
||||
);
|
||||
},
|
||||
render: ({ hide }) => <GrpcProtoSelectionDialog onDone={hide} />,
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -167,14 +161,7 @@ export function GrpcEditor({
|
||||
</Button>
|
||||
</div>,
|
||||
],
|
||||
[
|
||||
protoFiles.length,
|
||||
reflectionError,
|
||||
reflectionLoading,
|
||||
reflectionUnavailable,
|
||||
request.id,
|
||||
services,
|
||||
],
|
||||
[protoFiles.length, reflectionError, reflectionLoading, reflectionUnavailable, services],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import type { GrpcRequest } from '@yaakapp-internal/models';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useGrpc } from '../hooks/useGrpc';
|
||||
import { useGrpcProtoFiles } from '../hooks/useGrpcProtoFiles';
|
||||
import { useGrpcRequest } from '../hooks/useGrpcRequest';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
@@ -11,13 +12,18 @@ import { Link } from './core/Link';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
|
||||
interface Props {
|
||||
requestId: string;
|
||||
onDone: () => void;
|
||||
}
|
||||
|
||||
export function GrpcProtoSelection({ requestId }: Props) {
|
||||
const request = useGrpcRequest(requestId);
|
||||
const protoFilesKv = useGrpcProtoFiles(requestId);
|
||||
export function GrpcProtoSelectionDialog(props: Props) {
|
||||
const request = useActiveRequest();
|
||||
if (request?.model !== 'grpc_request') return null;
|
||||
|
||||
return GrpcProtoSelectionDialogWithRequest({ ...props, request });
|
||||
}
|
||||
|
||||
function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: GrpcRequest }) {
|
||||
const protoFilesKv = useGrpcProtoFiles(request.id);
|
||||
const protoFiles = protoFilesKv.value ?? [];
|
||||
const grpc = useGrpc(request, null, protoFiles);
|
||||
const services = grpc.reflect.data;
|
||||
@@ -34,7 +40,7 @@ export function GrpcProtoSelection({ requestId }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack className="flex-col-reverse" space={3}>
|
||||
<VStack className="flex-col-reverse mb-3" space={3}>
|
||||
{/* Buttons on top so they get focus first */}
|
||||
<HStack space={2} justifyContent="start" className="flex-row-reverse">
|
||||
<Button
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GrpcMetadataEntry, GrpcRequest } from '@yaakapp-internal/models';
|
||||
import { type GrpcMetadataEntry, type GrpcRequest, patchModel } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
@@ -7,7 +7,6 @@ import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { Button } from './core/Button';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
@@ -51,7 +50,7 @@ const TAB_METADATA = 'metadata';
|
||||
const TAB_AUTH = 'auth';
|
||||
const TAB_DESCRIPTION = 'description';
|
||||
|
||||
export function GrpcConnectionSetupPane({
|
||||
export function GrpcRequestPane({
|
||||
style,
|
||||
services,
|
||||
methodType,
|
||||
@@ -65,28 +64,25 @@ export function GrpcConnectionSetupPane({
|
||||
onCancel,
|
||||
onSend,
|
||||
}: Props) {
|
||||
const updateRequest = useUpdateAnyGrpcRequest();
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
|
||||
namespace: 'no_sync',
|
||||
key: 'grpcRequestActiveTabs',
|
||||
fallback: {},
|
||||
});
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
const forceUpdateKey = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
|
||||
const urlContainerEl = useRef<HTMLDivElement>(null);
|
||||
const { width: paneWidth } = useContainerSize(urlContainerEl);
|
||||
|
||||
const handleChangeUrl = useCallback(
|
||||
(url: string) => updateRequest.mutateAsync({ id: activeRequest.id, update: { url } }),
|
||||
[activeRequest.id, updateRequest],
|
||||
(url: string) => patchModel(activeRequest, { url }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const handleChangeMessage = useCallback(
|
||||
(message: string) => {
|
||||
return updateRequest.mutateAsync({ id: activeRequest.id, update: { message } });
|
||||
},
|
||||
[activeRequest.id, updateRequest],
|
||||
(message: string) => patchModel(activeRequest, { message }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const select = useMemo(() => {
|
||||
@@ -105,15 +101,12 @@ export function GrpcConnectionSetupPane({
|
||||
async (v: string) => {
|
||||
const [serviceName, methodName] = v.split('/', 2);
|
||||
if (serviceName == null || methodName == null) throw new Error('Should never happen');
|
||||
await updateRequest.mutateAsync({
|
||||
id: activeRequest.id,
|
||||
update: {
|
||||
service: serviceName,
|
||||
method: methodName,
|
||||
},
|
||||
await patchModel(activeRequest, {
|
||||
service: serviceName,
|
||||
method: methodName,
|
||||
});
|
||||
},
|
||||
[activeRequest.id, updateRequest],
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const handleConnect = useCallback(async () => {
|
||||
@@ -151,16 +144,16 @@ export function GrpcConnectionSetupPane({
|
||||
{ type: 'separator' },
|
||||
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
||||
],
|
||||
onChange: (authenticationType) => {
|
||||
onChange: async (authenticationType) => {
|
||||
let authentication: GrpcRequest['authentication'] = activeRequest.authentication;
|
||||
if (activeRequest.authenticationType !== authenticationType) {
|
||||
authentication = {
|
||||
// Reset auth if changing types
|
||||
};
|
||||
}
|
||||
updateRequest.mutate({
|
||||
id: activeRequest.id,
|
||||
update: { authenticationType, authentication },
|
||||
await patchModel(activeRequest, {
|
||||
authenticationType,
|
||||
authentication,
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -172,14 +165,7 @@ export function GrpcConnectionSetupPane({
|
||||
rightSlot: activeRequest.description && <CountBadge count={true} />,
|
||||
},
|
||||
],
|
||||
[
|
||||
activeRequest.authentication,
|
||||
activeRequest.authenticationType,
|
||||
activeRequest.description,
|
||||
activeRequest.id,
|
||||
authentication,
|
||||
updateRequest,
|
||||
],
|
||||
[activeRequest, authentication],
|
||||
);
|
||||
|
||||
const activeTab = activeTabs?.[activeRequest.id];
|
||||
@@ -191,15 +177,13 @@ export function GrpcConnectionSetupPane({
|
||||
);
|
||||
|
||||
const handleMetadataChange = useCallback(
|
||||
(metadata: GrpcMetadataEntry[]) =>
|
||||
updateRequest.mutate({ id: activeRequest.id, update: { metadata } }),
|
||||
[activeRequest.id, updateRequest],
|
||||
(metadata: GrpcMetadataEntry[]) => patchModel(activeRequest, { metadata }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const handleDescriptionChange = useCallback(
|
||||
(description: string) =>
|
||||
updateRequest.mutate({ id: activeRequest.id, update: { description } }),
|
||||
[activeRequest.id, updateRequest],
|
||||
(description: string) => patchModel(activeRequest, { description }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -209,7 +193,7 @@ export function GrpcConnectionSetupPane({
|
||||
className={classNames(
|
||||
'grid grid-cols-[minmax(0,1fr)_auto] gap-1.5',
|
||||
paneWidth === 0 && 'opacity-0',
|
||||
paneWidth < 400 && '!grid-cols-1',
|
||||
paneWidth > 0 && paneWidth < 400 && '!grid-cols-1',
|
||||
)}
|
||||
>
|
||||
<UrlBar
|
||||
@@ -346,7 +330,7 @@ export function GrpcConnectionSetupPane({
|
||||
className="font-sans !text-xl !px-0"
|
||||
containerClassName="border-0"
|
||||
placeholder={resolvedModelName(activeRequest)}
|
||||
onChange={(name) => updateRequest.mutate({ id: activeRequest.id, update: { name } })}
|
||||
onChange={(name) => patchModel(activeRequest, { name })}
|
||||
/>
|
||||
<MarkdownEditor
|
||||
name="request-description"
|
||||
@@ -1,11 +1,17 @@
|
||||
import type { GrpcEvent, GrpcRequest } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { format } from 'date-fns';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useSetAtom } from 'jotai/index';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCopy } from '../hooks/useCopy';
|
||||
import { useGrpcEvents } from '../hooks/useGrpcEvents';
|
||||
import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection';
|
||||
import {
|
||||
activeGrpcConnectionAtom,
|
||||
activeGrpcConnections,
|
||||
pinnedGrpcConnectionIdAtom,
|
||||
useGrpcEvents,
|
||||
} from '../hooks/usePinnedGrpcConnection';
|
||||
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
||||
import { AutoScroller } from './core/AutoScroller';
|
||||
import { Banner } from './core/Banner';
|
||||
@@ -34,13 +40,14 @@ interface Props {
|
||||
| 'no-method';
|
||||
}
|
||||
|
||||
export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) {
|
||||
export function GrpcResponsePane({ style, methodType, activeRequest }: Props) {
|
||||
const [activeEventId, setActiveEventId] = useState<string | null>(null);
|
||||
const [showLarge, setShowLarge] = useStateWithDeps<boolean>(false, [activeRequest.id]);
|
||||
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
||||
const { activeConnection, connections, setPinnedConnectionId } =
|
||||
usePinnedGrpcConnection(activeRequest);
|
||||
const connections = useAtomValue(activeGrpcConnections);
|
||||
const activeConnection = useAtomValue(activeGrpcConnectionAtom);
|
||||
const events = useGrpcEvents(activeConnection?.id ?? null);
|
||||
const setPinnedGrpcConnectionId = useSetAtom(pinnedGrpcConnectionIdAtom);
|
||||
const copy = useCopy();
|
||||
|
||||
const activeEvent = useMemo(
|
||||
@@ -78,7 +85,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
||||
<RecentGrpcConnectionsDropdown
|
||||
connections={connections}
|
||||
activeConnection={activeConnection}
|
||||
onPinnedConnectionId={setPinnedConnectionId}
|
||||
onPinnedConnectionId={setPinnedGrpcConnectionId}
|
||||
/>
|
||||
</div>
|
||||
</HStack>
|
||||
@@ -113,7 +120,7 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
|
||||
<div className="pb-3 px-2">
|
||||
<Separator />
|
||||
</div>
|
||||
<div className="pl-2 overflow-y-auto">
|
||||
<div className="h-full pl-2 overflow-y-auto grid grid-rows-[auto_minmax(0,1fr)] ">
|
||||
{activeEvent.eventType === 'client_message' ||
|
||||
activeEvent.eventType === 'server_message' ? (
|
||||
<>
|
||||
@@ -1,8 +1,9 @@
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
|
||||
import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants';
|
||||
import { WindowControls } from './WindowControls';
|
||||
@@ -23,7 +24,7 @@ export function HeaderSize({
|
||||
children,
|
||||
}: HeaderSizeProps) {
|
||||
const osInfo = useOsInfo();
|
||||
const settings = useSettings();
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const stoplightsVisible = useStoplightsVisible();
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import React, { useCallback } from 'react';
|
||||
import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest';
|
||||
import { useHttpAuthenticationConfig } from '../hooks/useHttpAuthenticationConfig';
|
||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
@@ -18,8 +16,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export function HttpAuthenticationEditor({ request }: Props) {
|
||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
||||
const authConfig = useHttpAuthenticationConfig(
|
||||
request.authenticationType,
|
||||
request.authentication,
|
||||
@@ -27,22 +23,8 @@ export function HttpAuthenticationEditor({ request }: Props) {
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(authentication: Record<string, boolean>) => {
|
||||
if (request.model === 'http_request') {
|
||||
updateHttpRequest.mutate({
|
||||
id: request.id,
|
||||
update: (r) => ({ ...r, authentication }),
|
||||
});
|
||||
} else if (request.model === 'websocket_request') {
|
||||
upsertWebsocketRequest.mutate({ ...request, authentication });
|
||||
} else {
|
||||
updateGrpcRequest.mutate({
|
||||
id: request.id,
|
||||
update: (r) => ({ ...r, authentication }),
|
||||
});
|
||||
}
|
||||
},
|
||||
[request, updateGrpcRequest, updateHttpRequest],
|
||||
(authentication: Record<string, boolean>) => patchModel(request, { authentication }),
|
||||
[request],
|
||||
);
|
||||
|
||||
if (authConfig.data == null) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
@@ -6,16 +7,14 @@ import type { CSSProperties } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||
import { grpcRequestsAtom } from '../hooks/useGrpcRequests';
|
||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||
import { httpRequestsAtom } from '../hooks/useHttpRequests';
|
||||
import { useImportCurl } from '../hooks/useImportCurl';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { deepEqualAtom } from '../lib/atoms';
|
||||
import { languageFromContentType } from '../lib/contentType';
|
||||
import { generateId } from '../lib/generateId';
|
||||
@@ -67,7 +66,7 @@ const TAB_DESCRIPTION = 'description';
|
||||
|
||||
const nonActiveRequestUrlsAtom = atom((get) => {
|
||||
const activeRequestId = get(activeRequestIdAtom);
|
||||
const requests = [...get(httpRequestsAtom), ...get(grpcRequestsAtom)];
|
||||
const requests = get(allRequestsAtom);
|
||||
return requests
|
||||
.filter((r) => r.id !== activeRequestId)
|
||||
.map((r): GenericCompletionOption => ({ type: 'constant', label: r.url }));
|
||||
@@ -77,20 +76,19 @@ const memoNotActiveRequestUrlsAtom = deepEqualAtom(nonActiveRequestUrlsAtom);
|
||||
|
||||
export function HttpRequestPane({ style, fullHeight, className, activeRequest }: Props) {
|
||||
const activeRequestId = activeRequest.id;
|
||||
const { mutateAsync: updateRequestAsync, mutate: updateRequest } = useUpdateAnyHttpRequest();
|
||||
const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
|
||||
namespace: 'no_sync',
|
||||
key: 'httpRequestActiveTabs',
|
||||
fallback: {},
|
||||
});
|
||||
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
const forceUpdateKey = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
|
||||
const contentType = getContentTypeFromHeaders(activeRequest.headers);
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
|
||||
const handleContentTypeChange = useCallback(
|
||||
async (contentType: string | null) => {
|
||||
async (contentType: string | null, patch: Partial<Omit<HttpRequest, 'headers'>> = {}) => {
|
||||
if (activeRequest == null) {
|
||||
console.error('Failed to get active request to update', activeRequest);
|
||||
return;
|
||||
@@ -106,12 +104,12 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
id: generateId(),
|
||||
});
|
||||
}
|
||||
await updateRequestAsync({ id: activeRequest.id, update: { headers } });
|
||||
await patchModel(activeRequest, { ...patch, headers });
|
||||
|
||||
// Force update header editor so any changed headers are reflected
|
||||
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
|
||||
},
|
||||
[activeRequest, updateRequestAsync],
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const { urlParameterPairs, urlParametersKey } = useMemo(() => {
|
||||
@@ -203,10 +201,10 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
showMethodToast(patch.method);
|
||||
}
|
||||
|
||||
await updateRequestAsync({ id: activeRequestId, update: patch });
|
||||
|
||||
if (newContentType !== undefined) {
|
||||
await handleContentTypeChange(newContentType);
|
||||
await handleContentTypeChange(newContentType, patch);
|
||||
} else {
|
||||
await patchModel(activeRequest, patch);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -242,10 +240,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
// Reset auth if changing types
|
||||
};
|
||||
}
|
||||
updateRequest({
|
||||
id: activeRequestId,
|
||||
update: { authenticationType, authentication },
|
||||
});
|
||||
await patchModel(activeRequest, { authenticationType, authentication });
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -254,36 +249,23 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
label: 'Info',
|
||||
},
|
||||
],
|
||||
[
|
||||
activeRequest.authentication,
|
||||
activeRequest.authenticationType,
|
||||
activeRequest.bodyType,
|
||||
activeRequest.headers,
|
||||
activeRequest.method,
|
||||
activeRequestId,
|
||||
authentication,
|
||||
handleContentTypeChange,
|
||||
numParams,
|
||||
updateRequest,
|
||||
updateRequestAsync,
|
||||
urlParameterPairs.length,
|
||||
],
|
||||
[activeRequest, authentication, handleContentTypeChange, numParams, urlParameterPairs.length],
|
||||
);
|
||||
|
||||
const { mutate: sendRequest } = useSendAnyHttpRequest();
|
||||
const { activeResponse } = usePinnedHttpResponse(activeRequestId);
|
||||
const { mutate: cancelResponse } = useCancelHttpResponse(activeResponse?.id ?? null);
|
||||
const { updateKey } = useRequestUpdateKey(activeRequestId);
|
||||
const updateKey = useRequestUpdateKey(activeRequestId);
|
||||
const { mutate: importCurl } = useImportCurl();
|
||||
|
||||
const handleBodyChange = useCallback(
|
||||
(body: HttpRequest['body']) => updateRequest({ id: activeRequestId, update: { body } }),
|
||||
[activeRequestId, updateRequest],
|
||||
(body: HttpRequest['body']) => patchModel(activeRequest, { body }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const handleBodyTextChange = useCallback(
|
||||
(text: string) => updateRequest({ id: activeRequestId, update: { body: { text } } }),
|
||||
[activeRequestId, updateRequest],
|
||||
(text: string) => patchModel(activeRequest, { body: { text } }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const activeTab = activeTabs?.[activeRequestId];
|
||||
@@ -315,15 +297,15 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
);
|
||||
|
||||
const handlePaste = useCallback(
|
||||
(e: ClipboardEvent, text: string) => {
|
||||
async (e: ClipboardEvent, text: string) => {
|
||||
if (text.startsWith('curl ')) {
|
||||
importCurl({ overwriteRequestId: activeRequestId, command: text });
|
||||
} else {
|
||||
const data = prepareImportQuerystring(text);
|
||||
if (data != null) {
|
||||
const patch = prepareImportQuerystring(text);
|
||||
if (patch != null) {
|
||||
e.preventDefault(); // Prevent input onChange
|
||||
|
||||
updateRequest({ id: activeRequestId, update: data });
|
||||
await patchModel(activeRequest, patch);
|
||||
focusParamsTab();
|
||||
|
||||
// Wait for request to update, then refresh the UI
|
||||
@@ -336,12 +318,12 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
}
|
||||
},
|
||||
[
|
||||
activeRequest,
|
||||
activeRequestId,
|
||||
focusParamsTab,
|
||||
forceParamsRefresh,
|
||||
forceUrlRefresh,
|
||||
importCurl,
|
||||
updateRequest,
|
||||
],
|
||||
);
|
||||
const handleSend = useCallback(
|
||||
@@ -350,13 +332,13 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
);
|
||||
|
||||
const handleMethodChange = useCallback(
|
||||
(method: string) => updateRequest({ id: activeRequestId, update: { method } }),
|
||||
[activeRequestId, updateRequest],
|
||||
(method: string) => patchModel(activeRequest, { method }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const handleUrlChange = useCallback(
|
||||
(url: string) => updateRequest({ id: activeRequestId, update: { url } }),
|
||||
[activeRequestId, updateRequest],
|
||||
(url: string) => patchModel(activeRequest, { url }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -397,7 +379,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
forceUpdateKey={`${forceUpdateHeaderEditorKey}::${forceUpdateKey}`}
|
||||
headers={activeRequest.headers}
|
||||
stateKey={`headers.${activeRequest.id}`}
|
||||
onChange={(headers) => updateRequest({ id: activeRequestId, update: { headers } })}
|
||||
onChange={(headers) => patchModel(activeRequest, { headers })}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_PARAMS}>
|
||||
@@ -405,9 +387,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
stateKey={`params.${activeRequest.id}`}
|
||||
forceUpdateKey={forceUpdateKey + urlParametersKey}
|
||||
pairs={urlParameterPairs}
|
||||
onChange={(urlParameters) =>
|
||||
updateRequest({ id: activeRequestId, update: { urlParameters } })
|
||||
}
|
||||
onChange={(urlParameters) => patchModel(activeRequest, { urlParameters })}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_BODY}>
|
||||
@@ -459,7 +439,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
requestId={activeRequest.id}
|
||||
contentType={contentType}
|
||||
body={activeRequest.body}
|
||||
onChange={(body) => updateRequest({ id: activeRequestId, update: { body } })}
|
||||
onChange={(body) => patchModel(activeRequest, { body })}
|
||||
onChangeContentType={handleContentTypeChange}
|
||||
/>
|
||||
) : typeof activeRequest.bodyType === 'string' ? (
|
||||
@@ -488,7 +468,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
className="font-sans !text-xl !px-0"
|
||||
containerClassName="border-0"
|
||||
placeholder={resolvedModelName(activeRequest)}
|
||||
onChange={(name) => updateRequest({ id: activeRequestId, update: { name } })}
|
||||
onChange={(name) => patchModel(activeRequest, { name })}
|
||||
/>
|
||||
<MarkdownEditor
|
||||
name="request-description"
|
||||
@@ -496,9 +476,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
defaultValue={activeRequest.description}
|
||||
stateKey={`description.${activeRequest.id}`}
|
||||
forceUpdateKey={updateKey}
|
||||
onChange={(description) =>
|
||||
updateRequest({ id: activeRequestId, update: { description } })
|
||||
}
|
||||
onChange={(description) => patchModel(activeRequest, { description })}
|
||||
/>
|
||||
</div>
|
||||
</TabContent>
|
||||
|
||||
@@ -14,7 +14,7 @@ const details: Record<
|
||||
commercial_use: null,
|
||||
invalid_license: { label: 'License Error', color: 'danger' },
|
||||
personal_use: { label: 'Personal Use', color: 'notice' },
|
||||
trialing: { label: 'Personal Use', color: 'notice' },
|
||||
trialing: { label: 'Personal Use', color: 'info' },
|
||||
};
|
||||
|
||||
export function LicenseBadge() {
|
||||
@@ -23,12 +23,7 @@ export function LicenseBadge() {
|
||||
|
||||
if (check.error) {
|
||||
return (
|
||||
<LicenseBadgeButton
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
openSettings.mutate(SettingsTab.License);
|
||||
}}
|
||||
>
|
||||
<LicenseBadgeButton color="danger" onClick={() => openSettings.mutate(SettingsTab.License)}>
|
||||
License Error
|
||||
</LicenseBadgeButton>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import type {
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest} from '@yaakapp-internal/models';
|
||||
import {
|
||||
patchModel,
|
||||
workspacesAtom,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useState } from 'react';
|
||||
import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest';
|
||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { router } from '../lib/router';
|
||||
import { showToast } from '../lib/toast';
|
||||
@@ -19,9 +23,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
||||
const workspaces = useAtomValue(workspacesAtom);
|
||||
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(activeWorkspaceId);
|
||||
|
||||
return (
|
||||
@@ -40,18 +42,12 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
|
||||
color="primary"
|
||||
disabled={selectedWorkspaceId === activeWorkspaceId}
|
||||
onClick={async () => {
|
||||
const update = {
|
||||
const patch = {
|
||||
workspaceId: selectedWorkspaceId,
|
||||
folderId: null,
|
||||
};
|
||||
|
||||
if (request.model === 'http_request') {
|
||||
await updateHttpRequest.mutateAsync({ id: request.id, update });
|
||||
} else if (request.model === 'grpc_request') {
|
||||
await updateGrpcRequest.mutateAsync({ id: request.id, update });
|
||||
} else if (request.model === 'websocket_request') {
|
||||
await upsertWebsocketRequest.mutateAsync({ ...request, ...update });
|
||||
}
|
||||
await patchModel(request, patch);
|
||||
|
||||
// Hide after a moment, to give time for request to disappear
|
||||
setTimeout(onDone, 100);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { formatDistanceToNowStrict } from 'date-fns';
|
||||
import { useDeleteGrpcConnection } from '../hooks/useDeleteGrpcConnection';
|
||||
import { useDeleteGrpcConnections } from '../hooks/useDeleteGrpcConnections';
|
||||
import type { GrpcConnection } from '@yaakapp-internal/models';
|
||||
import { deleteModel } from '@yaakapp-internal/models';
|
||||
import { formatDistanceToNowStrict } from 'date-fns';
|
||||
import { useDeleteGrpcConnections } from '../hooks/useDeleteGrpcConnections';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
@@ -19,7 +19,6 @@ export function RecentGrpcConnectionsDropdown({
|
||||
connections,
|
||||
onPinnedConnectionId,
|
||||
}: Props) {
|
||||
const deleteConnection = useDeleteGrpcConnection(activeConnection?.id ?? null);
|
||||
const deleteAllConnections = useDeleteGrpcConnections(activeConnection?.requestId);
|
||||
const latestConnectionId = connections[0]?.id ?? 'n/a';
|
||||
|
||||
@@ -28,7 +27,7 @@ export function RecentGrpcConnectionsDropdown({
|
||||
items={[
|
||||
{
|
||||
label: 'Clear Connection',
|
||||
onSelect: deleteConnection.mutate,
|
||||
onSelect: () => deleteModel(activeConnection),
|
||||
disabled: connections.length === 0,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||
import { deleteModel } from '@yaakapp-internal/models';
|
||||
import { useCopyHttpResponse } from '../hooks/useCopyHttpResponse';
|
||||
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
|
||||
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
|
||||
import { useSaveResponse } from '../hooks/useSaveResponse';
|
||||
import { pluralize } from '../lib/pluralize';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { HttpStatusTag } from './core/HttpStatusTag';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { HttpStatusTag } from './core/HttpStatusTag';
|
||||
|
||||
interface Props {
|
||||
responses: HttpResponse[];
|
||||
@@ -22,7 +22,6 @@ export const RecentHttpResponsesDropdown = function ResponsePane({
|
||||
responses,
|
||||
onPinnedResponseId,
|
||||
}: Props) {
|
||||
const deleteResponse = useDeleteHttpResponse(activeResponse?.id ?? null);
|
||||
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
||||
const latestResponseId = responses[0]?.id ?? 'n/a';
|
||||
const saveResponse = useSaveResponse(activeResponse);
|
||||
@@ -48,7 +47,7 @@ export const RecentHttpResponsesDropdown = function ResponsePane({
|
||||
{
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: deleteResponse.mutate,
|
||||
onSelect: () => deleteModel(activeResponse),
|
||||
},
|
||||
{
|
||||
label: 'Unpin Response',
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import classNames from 'classnames';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useKeyboardEvent } from '../hooks/useKeyboardEvent';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { requestsAtom } from '../hooks/useRequests';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import {allRequestsAtom} from "../hooks/useAllRequests";
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { router } from '../lib/router';
|
||||
import { Button } from './core/Button';
|
||||
import type { DropdownItem, DropdownRef } from './core/Dropdown';
|
||||
@@ -47,10 +47,10 @@ export function RecentRequestsDropdown({ className }: Props) {
|
||||
});
|
||||
|
||||
const items = useMemo(() => {
|
||||
const activeWorkspaceId = getActiveWorkspaceId();
|
||||
const activeWorkspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (activeWorkspaceId === null) return [];
|
||||
|
||||
const requests = jotaiStore.get(requestsAtom);
|
||||
const requests = jotaiStore.get(allRequestsAtom);
|
||||
const recentRequestItems: DropdownItem[] = [];
|
||||
for (const id of recentRequestIds) {
|
||||
const request = requests.find((r) => r.id === id);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { WebsocketConnection } from '@yaakapp-internal/models';
|
||||
import { deleteModel, getModel } from '@yaakapp-internal/models';
|
||||
import { formatDistanceToNowStrict } from 'date-fns';
|
||||
import { deleteWebsocketConnection } from '../commands/deleteWebsocketConnection';
|
||||
import { deleteWebsocketConnections } from '../commands/deleteWebsocketConnections';
|
||||
import { websocketRequestsAtom } from '../hooks/useWebsocketRequests';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
@@ -28,15 +26,13 @@ export function RecentWebsocketConnectionsDropdown({
|
||||
items={[
|
||||
{
|
||||
label: 'Clear Connection',
|
||||
onSelect: () => deleteWebsocketConnection.mutate(activeConnection),
|
||||
onSelect: () => deleteModel(activeConnection),
|
||||
disabled: connections.length === 0,
|
||||
},
|
||||
{
|
||||
label: `Clear ${pluralizeCount('Connection', connections.length)}`,
|
||||
onSelect: () => {
|
||||
const request = jotaiStore
|
||||
.get(websocketRequestsAtom)
|
||||
.find((r) => r.id === activeConnection.requestId);
|
||||
const request = getModel('websocket_request', activeConnection.requestId);
|
||||
if (request != null) {
|
||||
deleteWebsocketConnections.mutate(request);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useEffect } from 'react';
|
||||
import { getRecentCookieJars } from '../hooks/useRecentCookieJars';
|
||||
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { getRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { router } from '../lib/router';
|
||||
|
||||
export function RedirectToLatestWorkspace() {
|
||||
const workspaces = useWorkspaces();
|
||||
const workspaces = useAtomValue(workspacesAtom);
|
||||
const recentWorkspaces = useRecentWorkspaces();
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaces.length === 0 || recentWorkspaces == null) {
|
||||
console.log('No workspaces found to redirect to. Skipping.');
|
||||
console.log('No workspaces found to redirect to. Skipping.', {
|
||||
workspaces,
|
||||
recentWorkspaces,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { EditorKeymap } from '@yaakapp-internal/models';
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React from 'react';
|
||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
|
||||
import { useResolvedTheme } from '../../hooks/useResolvedTheme';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import { getThemes } from '../../lib/theme/themes';
|
||||
import { isThemeDark } from '../../lib/theme/window';
|
||||
@@ -62,9 +62,8 @@ const icons: IconProps['icon'][] = [
|
||||
const { themes } = getThemes();
|
||||
|
||||
export function SettingsAppearance() {
|
||||
const workspace = useActiveWorkspace();
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const appearance = useResolvedAppearance();
|
||||
const activeTheme = useResolvedTheme();
|
||||
|
||||
@@ -93,18 +92,20 @@ export function SettingsAppearance() {
|
||||
name="interfaceFontSize"
|
||||
label="Font Size"
|
||||
labelPosition="left"
|
||||
defaultValue="15"
|
||||
value={`${settings.interfaceFontSize}`}
|
||||
options={fontSizeOptions}
|
||||
onChange={(v) => updateSettings.mutate({ interfaceFontSize: parseInt(v) })}
|
||||
onChange={(v) => patchModel(settings, { interfaceFontSize: parseInt(v) })}
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
name="editorFontSize"
|
||||
label="Editor Font Size"
|
||||
labelPosition="left"
|
||||
defaultValue="13"
|
||||
value={`${settings.editorFontSize}`}
|
||||
options={fontSizeOptions}
|
||||
onChange={(v) => updateSettings.mutate({ editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
|
||||
onChange={(v) => patchModel(settings, { editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
@@ -113,12 +114,12 @@ export function SettingsAppearance() {
|
||||
labelPosition="left"
|
||||
value={`${settings.editorKeymap}`}
|
||||
options={keymaps}
|
||||
onChange={(v) => updateSettings.mutate({ editorKeymap: v })}
|
||||
onChange={(v) => patchModel(settings, { editorKeymap: v })}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={settings.editorSoftWrap}
|
||||
title="Wrap Editor Lines"
|
||||
onChange={(editorSoftWrap) => updateSettings.mutate({ editorSoftWrap })}
|
||||
onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })}
|
||||
/>
|
||||
|
||||
<Separator className="my-4" />
|
||||
@@ -129,7 +130,7 @@ export function SettingsAppearance() {
|
||||
labelPosition="top"
|
||||
size="sm"
|
||||
value={settings.appearance}
|
||||
onChange={(appearance) => updateSettings.mutate({ appearance })}
|
||||
onChange={(appearance) => patchModel(settings, { appearance })}
|
||||
options={[
|
||||
{ label: 'Automatic', value: 'system' },
|
||||
{ label: 'Light', value: 'light' },
|
||||
@@ -147,7 +148,7 @@ export function SettingsAppearance() {
|
||||
className="flex-1"
|
||||
value={activeTheme.light.id}
|
||||
options={lightThemes}
|
||||
onChange={(themeLight) => updateSettings.mutate({ ...settings, themeLight })}
|
||||
onChange={(themeLight) => patchModel(settings, { themeLight })}
|
||||
/>
|
||||
)}
|
||||
{(settings.appearance === 'system' || settings.appearance === 'dark') && (
|
||||
@@ -160,7 +161,7 @@ export function SettingsAppearance() {
|
||||
size="sm"
|
||||
value={activeTheme.dark.id}
|
||||
options={darkThemes}
|
||||
onChange={(themeDark) => updateSettings.mutate({ ...settings, themeDark })}
|
||||
onChange={(themeDark) => patchModel(settings, { themeDark })}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import React from 'react';
|
||||
import { upsertWorkspace } from '../../commands/upsertWorkspace';
|
||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { useAppInfo } from '../../hooks/useAppInfo';
|
||||
import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||
import { revealInFinderText } from '../../lib/reveal';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { Heading } from '../core/Heading';
|
||||
@@ -17,9 +16,8 @@ import { Separator } from '../core/Separator';
|
||||
import { VStack } from '../core/Stacks';
|
||||
|
||||
export function SettingsGeneral() {
|
||||
const workspace = useActiveWorkspace();
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const appInfo = useAppInfo();
|
||||
const checkForUpdates = useCheckForUpdates();
|
||||
|
||||
@@ -37,7 +35,7 @@ export function SettingsGeneral() {
|
||||
labelClassName="w-[14rem]"
|
||||
size="sm"
|
||||
value={settings.updateChannel}
|
||||
onChange={(updateChannel) => updateSettings.mutate({ updateChannel })}
|
||||
onChange={(updateChannel) => patchModel(settings, { updateChannel })}
|
||||
options={[
|
||||
{ label: 'Stable (less frequent)', value: 'stable' },
|
||||
{ label: 'Beta (more frequent)', value: 'beta' },
|
||||
@@ -65,10 +63,10 @@ export function SettingsGeneral() {
|
||||
? 'current'
|
||||
: 'ask'
|
||||
}
|
||||
onChange={(v) => {
|
||||
if (v === 'current') updateSettings.mutate({ openWorkspaceNewWindow: false });
|
||||
else if (v === 'new') updateSettings.mutate({ openWorkspaceNewWindow: true });
|
||||
else updateSettings.mutate({ openWorkspaceNewWindow: null });
|
||||
onChange={async (v) => {
|
||||
if (v === 'current') await patchModel(settings, { openWorkspaceNewWindow: false });
|
||||
else if (v === 'new') await patchModel(settings, { openWorkspaceNewWindow: true });
|
||||
else await patchModel(settings, { openWorkspaceNewWindow: null });
|
||||
}}
|
||||
options={[
|
||||
{ label: 'Always Ask', value: 'ask' },
|
||||
@@ -96,9 +94,7 @@ export function SettingsGeneral() {
|
||||
labelPosition="left"
|
||||
defaultValue={`${workspace.settingRequestTimeout}`}
|
||||
validate={(value) => parseInt(value) >= 0}
|
||||
onChange={(v) =>
|
||||
upsertWorkspace.mutate({ ...workspace, settingRequestTimeout: parseInt(v) || 0 })
|
||||
}
|
||||
onChange={(v) => patchModel(workspace, { settingRequestTimeout: parseInt(v) || 0 })}
|
||||
type="number"
|
||||
/>
|
||||
|
||||
@@ -106,7 +102,7 @@ export function SettingsGeneral() {
|
||||
checked={workspace.settingValidateCertificates}
|
||||
title="Validate TLS Certificates"
|
||||
onChange={(settingValidateCertificates) =>
|
||||
upsertWorkspace.mutate({ ...workspace, settingValidateCertificates })
|
||||
patchModel(workspace, { settingValidateCertificates })
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -114,8 +110,7 @@ export function SettingsGeneral() {
|
||||
checked={workspace.settingFollowRedirects}
|
||||
title="Follow Redirects"
|
||||
onChange={(settingFollowRedirects) =>
|
||||
upsertWorkspace.mutate({
|
||||
...workspace,
|
||||
patchModel(workspace, {
|
||||
settingFollowRedirects,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ export function SettingsLicense() {
|
||||
className="max-w-sm"
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
await activate.mutateAsync({ licenseKey: key });
|
||||
toggleActivateFormVisible();
|
||||
activate.mutate({ licenseKey: key });
|
||||
}}
|
||||
>
|
||||
<PlainInput
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import type { Plugin } from '@yaakapp-internal/models';
|
||||
import type { Plugin} from '@yaakapp-internal/models';
|
||||
import { pluginsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React from 'react';
|
||||
import { useInstallPlugin } from '../../hooks/useInstallPlugin';
|
||||
import { usePluginInfo } from '../../hooks/usePluginInfo';
|
||||
import { usePlugins, useRefreshPlugins } from '../../hooks/usePlugins';
|
||||
import { useRefreshPlugins } from '../../hooks/usePlugins';
|
||||
import { useUninstallPlugin } from '../../hooks/useUninstallPlugin';
|
||||
import { Button } from '../core/Button';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
@@ -14,7 +16,7 @@ import { SelectFile } from '../SelectFile';
|
||||
|
||||
export function SettingsPlugins() {
|
||||
const [directory, setDirectory] = React.useState<string | null>(null);
|
||||
const plugins = usePlugins();
|
||||
const plugins = useAtomValue(pluginsAtom);
|
||||
const createPlugin = useInstallPlugin();
|
||||
const refreshPlugins = useRefreshPlugins();
|
||||
return (
|
||||
@@ -61,12 +63,7 @@ export function SettingsPlugins() {
|
||||
/>
|
||||
<HStack>
|
||||
{directory && (
|
||||
<Button
|
||||
size="xs"
|
||||
type="submit"
|
||||
color="primary"
|
||||
className="ml-auto"
|
||||
>
|
||||
<Button size="xs" type="submit" color="primary" className="ml-auto">
|
||||
Add Plugin
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React from 'react';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { PlainInput } from '../core/PlainInput';
|
||||
import { Select } from '../core/Select';
|
||||
@@ -8,8 +8,7 @@ import { Separator } from '../core/Separator';
|
||||
import { HStack, VStack } from '../core/Stacks';
|
||||
|
||||
export function SettingsProxy() {
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
|
||||
return (
|
||||
<VStack space={1.5} className="mb-4">
|
||||
@@ -19,11 +18,11 @@ export function SettingsProxy() {
|
||||
hideLabel
|
||||
size="sm"
|
||||
value={settings.proxy?.type ?? 'automatic'}
|
||||
onChange={(v) => {
|
||||
onChange={async (v) => {
|
||||
if (v === 'automatic') {
|
||||
updateSettings.mutate({ proxy: undefined });
|
||||
await patchModel(settings, { proxy: undefined });
|
||||
} else if (v === 'enabled') {
|
||||
updateSettings.mutate({
|
||||
await patchModel(settings, {
|
||||
proxy: {
|
||||
type: 'enabled',
|
||||
http: '',
|
||||
@@ -32,7 +31,7 @@ export function SettingsProxy() {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
updateSettings.mutate({ proxy: { type: 'disabled' } });
|
||||
await patchModel(settings, { proxy: { type: 'disabled' } });
|
||||
}
|
||||
}}
|
||||
options={[
|
||||
@@ -49,10 +48,10 @@ export function SettingsProxy() {
|
||||
label="HTTP"
|
||||
placeholder="localhost:9090"
|
||||
defaultValue={settings.proxy?.http}
|
||||
onChange={(http) => {
|
||||
onChange={async (http) => {
|
||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||
const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null;
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
<PlainInput
|
||||
@@ -60,10 +59,10 @@ export function SettingsProxy() {
|
||||
label="HTTPS"
|
||||
placeholder="localhost:9090"
|
||||
defaultValue={settings.proxy?.https}
|
||||
onChange={(https) => {
|
||||
onChange={async (https) => {
|
||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||
const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null;
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
@@ -71,11 +70,11 @@ export function SettingsProxy() {
|
||||
<Checkbox
|
||||
checked={settings.proxy.auth != null}
|
||||
title="Enable authentication"
|
||||
onChange={(enabled) => {
|
||||
onChange={async (enabled) => {
|
||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||
const auth = enabled ? { user: '', password: '' } : null;
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -87,13 +86,13 @@ export function SettingsProxy() {
|
||||
label="User"
|
||||
placeholder="myUser"
|
||||
defaultValue={settings.proxy.auth.user}
|
||||
onChange={(user) => {
|
||||
onChange={async (user) => {
|
||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||
const password =
|
||||
settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.password ?? '') : '';
|
||||
const auth = { user, password };
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
<PlainInput
|
||||
@@ -102,13 +101,13 @@ export function SettingsProxy() {
|
||||
type="password"
|
||||
placeholder="s3cretPassw0rd"
|
||||
defaultValue={settings.proxy.auth.password}
|
||||
onChange={(password) => {
|
||||
onChange={async (password) => {
|
||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||
const user =
|
||||
settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.user ?? '') : '';
|
||||
const auth = { user, password };
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
await patchModel(settings, { proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
import { switchWorkspace } from '../commands/switchWorkspace';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
||||
import { Button } from './core/Button';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import { Icon } from './core/Icon';
|
||||
@@ -15,8 +15,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const [remember, setRemember] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
@@ -28,11 +27,11 @@ export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
||||
<Button
|
||||
className="focus"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
hide();
|
||||
switchWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: false });
|
||||
if (remember) {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: false });
|
||||
await patchModel(settings, { openWorkspaceNewWindow: false });
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -42,11 +41,11 @@ export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
||||
className="focus"
|
||||
color="secondary"
|
||||
rightSlot={<Icon icon="external_link" />}
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
hide();
|
||||
switchWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true });
|
||||
if (remember) {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: true });
|
||||
await patchModel(settings, { openWorkspaceNewWindow: true });
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import { closeWebsocket, connectWebsocket, sendWebsocket } from '@yaakapp-internal/ws';
|
||||
import classNames from 'classnames';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest';
|
||||
import { getActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { getActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||
@@ -13,11 +13,10 @@ import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||
import { activeWebsocketConnectionAtom } from '../hooks/usePinnedWebsocketConnection';
|
||||
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
|
||||
import { requestsAtom } from '../hooks/useRequests';
|
||||
import {allRequestsAtom} from "../hooks/useAllRequests";
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { useLatestWebsocketConnection } from '../hooks/useWebsocketConnections';
|
||||
import { deepEqualAtom } from '../lib/atoms';
|
||||
import { languageFromContentType } from '../lib/contentType';
|
||||
import { generateId } from '../lib/generateId';
|
||||
@@ -52,10 +51,11 @@ const TAB_DESCRIPTION = 'description';
|
||||
|
||||
const nonActiveRequestUrlsAtom = atom((get) => {
|
||||
const activeRequestId = get(activeRequestIdAtom);
|
||||
const requests = get(requestsAtom);
|
||||
return requests
|
||||
const requests = get(allRequestsAtom);
|
||||
const urls = requests
|
||||
.filter((r) => r.id !== activeRequestId)
|
||||
.map((r): GenericCompletionOption => ({ type: 'constant', label: r.url }));
|
||||
return urls;
|
||||
});
|
||||
|
||||
const memoNotActiveRequestUrlsAtom = deepEqualAtom(nonActiveRequestUrlsAtom);
|
||||
@@ -67,7 +67,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
key: 'websocketRequestActiveTabs',
|
||||
fallback: {},
|
||||
});
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
const forceUpdateKey = useRequestUpdateKey(activeRequest.id);
|
||||
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
|
||||
@@ -89,17 +89,6 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
}, [activeRequest.url, activeRequest.urlParameters]);
|
||||
|
||||
const tabs = useMemo<TabItem[]>(() => {
|
||||
// const options: Omit<RadioDropdownProps<WebsocketMessageType>, 'children'> = {
|
||||
// value: activeRequest.messageType ?? 'text',
|
||||
// items: [
|
||||
// { label: 'Text', value: 'text' },
|
||||
// { label: 'Binary', value: 'binary' },
|
||||
// ],
|
||||
// onChange: async (messageType) => {
|
||||
// if (messageType === activeRequest.messageType) return;
|
||||
// upsertWebsocketRequest.mutate({ ...activeRequest, messageType });
|
||||
// },
|
||||
// };
|
||||
return [
|
||||
{
|
||||
value: TAB_MESSAGE,
|
||||
@@ -136,8 +125,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
// Reset auth if changing types
|
||||
};
|
||||
}
|
||||
upsertWebsocketRequest.mutate({
|
||||
...activeRequest,
|
||||
await patchModel(activeRequest, {
|
||||
authenticationType,
|
||||
authentication,
|
||||
});
|
||||
@@ -153,9 +141,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
|
||||
const { activeResponse } = usePinnedHttpResponse(activeRequestId);
|
||||
const { mutate: cancelResponse } = useCancelHttpResponse(activeResponse?.id ?? null);
|
||||
const { mutate: updateRequest } = useUpdateAnyHttpRequest();
|
||||
const { updateKey } = useRequestUpdateKey(activeRequestId);
|
||||
const connection = useLatestWebsocketConnection(activeRequestId);
|
||||
const connection = useAtomValue(activeWebsocketConnectionAtom);
|
||||
|
||||
const activeTab = activeTabs?.[activeRequestId];
|
||||
const setActiveTab = useCallback(
|
||||
@@ -207,17 +193,17 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
}, [connection]);
|
||||
|
||||
const handleUrlChange = useCallback(
|
||||
(url: string) => upsertWebsocketRequest.mutate({ ...activeRequest, url }),
|
||||
(url: string) => patchModel(activeRequest, { url }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
const handlePaste = useCallback(
|
||||
(e: ClipboardEvent, text: string) => {
|
||||
const data = prepareImportQuerystring(text);
|
||||
if (data != null) {
|
||||
async (e: ClipboardEvent, text: string) => {
|
||||
const patch = prepareImportQuerystring(text);
|
||||
if (patch != null) {
|
||||
e.preventDefault(); // Prevent input onChange
|
||||
|
||||
updateRequest({ id: activeRequestId, update: data });
|
||||
await patchModel(activeRequest, patch);
|
||||
focusParamsTab();
|
||||
|
||||
// Wait for request to update, then refresh the UI
|
||||
@@ -228,7 +214,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
[activeRequestId, focusParamsTab, forceParamsRefresh, forceUrlRefresh, updateRequest],
|
||||
[activeRequest, focusParamsTab, forceParamsRefresh, forceUrlRefresh],
|
||||
);
|
||||
|
||||
const messageLanguage = languageFromContentType(null, activeRequest.message);
|
||||
@@ -266,7 +252,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
onSend={isLoading ? handleSend : handleConnect}
|
||||
onCancel={cancelResponse}
|
||||
onUrlChange={handleUrlChange}
|
||||
forceUpdateKey={updateKey}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
isLoading={activeResponse != null && activeResponse.state !== 'closed'}
|
||||
method={null}
|
||||
/>
|
||||
@@ -287,7 +273,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
headers={activeRequest.headers}
|
||||
stateKey={`headers.${activeRequest.id}`}
|
||||
onChange={(headers) => upsertWebsocketRequest.mutate({ ...activeRequest, headers })}
|
||||
onChange={(headers) => patchModel(activeRequest, { headers })}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_PARAMS}>
|
||||
@@ -295,9 +281,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
stateKey={`params.${activeRequest.id}`}
|
||||
forceUpdateKey={forceUpdateKey + urlParametersKey}
|
||||
pairs={urlParameterPairs}
|
||||
onChange={(urlParameters) =>
|
||||
upsertWebsocketRequest.mutate({ ...activeRequest, urlParameters })
|
||||
}
|
||||
onChange={(urlParameters) => patchModel(activeRequest, { urlParameters })}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_MESSAGE}>
|
||||
@@ -309,7 +293,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
defaultValue={activeRequest.message}
|
||||
language={messageLanguage}
|
||||
onChange={(message) => upsertWebsocketRequest.mutate({ ...activeRequest, message })}
|
||||
onChange={(message) => patchModel(activeRequest, { message })}
|
||||
stateKey={`json.${activeRequest.id}`}
|
||||
/>
|
||||
</TabContent>
|
||||
@@ -318,22 +302,20 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
<PlainInput
|
||||
label="Request Name"
|
||||
hideLabel
|
||||
forceUpdateKey={updateKey}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
defaultValue={activeRequest.name}
|
||||
className="font-sans !text-xl !px-0"
|
||||
containerClassName="border-0"
|
||||
placeholder={resolvedModelName(activeRequest)}
|
||||
onChange={(name) => upsertWebsocketRequest.mutate({ ...activeRequest, name })}
|
||||
onChange={(name) => patchModel(activeRequest, { name })}
|
||||
/>
|
||||
<MarkdownEditor
|
||||
name="request-description"
|
||||
placeholder="Request description"
|
||||
defaultValue={activeRequest.description}
|
||||
stateKey={`description.${activeRequest.id}`}
|
||||
forceUpdateKey={updateKey}
|
||||
onChange={(description) =>
|
||||
upsertWebsocketRequest.mutate({ ...activeRequest, description })
|
||||
}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
onChange={(description) => patchModel(activeRequest, { description })}
|
||||
/>
|
||||
</div>
|
||||
</TabContent>
|
||||
|
||||
@@ -2,12 +2,17 @@ import type { WebsocketEvent, WebsocketRequest } from '@yaakapp-internal/models'
|
||||
import classNames from 'classnames';
|
||||
import { format } from 'date-fns';
|
||||
import { hexy } from 'hexy';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useCopy } from '../hooks/useCopy';
|
||||
import { useFormatText } from '../hooks/useFormatText';
|
||||
import { usePinnedWebsocketConnection } from '../hooks/usePinnedWebsocketConnection';
|
||||
import {
|
||||
activeWebsocketConnectionAtom,
|
||||
activeWebsocketConnectionsAtom,
|
||||
setPinnedWebsocketConnectionId,
|
||||
useWebsocketEvents,
|
||||
} from '../hooks/usePinnedWebsocketConnection';
|
||||
import { useStateWithDeps } from '../hooks/useStateWithDeps';
|
||||
import { useWebsocketEvents } from '../hooks/useWebsocketEvents';
|
||||
import { languageFromContentType } from '../lib/contentType';
|
||||
import { AutoScroller } from './core/AutoScroller';
|
||||
import { Banner } from './core/Banner';
|
||||
@@ -33,10 +38,9 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
||||
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
||||
const [hexDumps, setHexDumps] = useState<Record<string, boolean>>({});
|
||||
|
||||
const { activeConnection, connections, setPinnedConnectionId } =
|
||||
usePinnedWebsocketConnection(activeRequest);
|
||||
const activeConnection = useAtomValue(activeWebsocketConnectionAtom);
|
||||
const connections = useAtomValue(activeWebsocketConnectionsAtom);
|
||||
|
||||
// const isLoading = activeConnection !== null && activeConnection.state !== 'closed';
|
||||
const events = useWebsocketEvents(activeConnection?.id ?? null);
|
||||
|
||||
const activeEvent = useMemo(
|
||||
@@ -82,7 +86,7 @@ export function WebsocketResponsePane({ activeRequest }: Props) {
|
||||
<RecentWebsocketConnectionsDropdown
|
||||
connections={connections}
|
||||
activeConnection={activeConnection}
|
||||
onPinnedConnectionId={setPinnedConnectionId}
|
||||
onPinnedConnectionId={setPinnedWebsocketConnectionId}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import * as m from 'motion/react-m';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import {duplicateWebsocketRequest} from "../commands/duplicateWebsocketRequest";
|
||||
import {
|
||||
useEnsureActiveCookieJar,
|
||||
useSubscribeActiveCookieJarId,
|
||||
} from '../hooks/useActiveCookieJar';
|
||||
import { useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment';
|
||||
import { getActiveRequest, useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { activeRequestAtom } from '../hooks/useActiveRequest';
|
||||
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
|
||||
import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest';
|
||||
import { activeWorkspaceAtom } from '../hooks/useActiveWorkspace';
|
||||
import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useImportData } from '../hooks/useImportData';
|
||||
@@ -25,7 +24,8 @@ import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
|
||||
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { duplicateRequestAndNavigate } from '../lib/deleteRequestAndNavigate';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { HotKeyList } from './core/HotKeyList';
|
||||
@@ -51,7 +51,7 @@ export function Workspace() {
|
||||
// First, subscribe to some things applicable to workspaces
|
||||
useGlobalWorkspaceHooks();
|
||||
|
||||
const workspaces = useWorkspaces();
|
||||
const workspaces = useAtomValue(workspacesAtom);
|
||||
const { setWidth, width, resetWidth } = useSidebarWidth();
|
||||
const [sidebarHidden, setSidebarHidden] = useSidebarHidden();
|
||||
const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden();
|
||||
@@ -181,8 +181,8 @@ export function Workspace() {
|
||||
}
|
||||
|
||||
function WorkspaceBody() {
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeRequest = useAtomValue(activeRequestAtom);
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const importData = useImportData();
|
||||
|
||||
if (activeWorkspace == null) {
|
||||
@@ -242,29 +242,7 @@ function useGlobalWorkspaceHooks() {
|
||||
const toggleCommandPalette = useToggleCommandPalette();
|
||||
useHotKey('command_palette.toggle', toggleCommandPalette);
|
||||
|
||||
const activeRequest = useActiveRequest();
|
||||
const duplicateHttpRequest = useDuplicateHttpRequest({
|
||||
id: activeRequest?.id ?? null,
|
||||
navigateAfter: true,
|
||||
});
|
||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({
|
||||
id: activeRequest?.id ?? null,
|
||||
navigateAfter: true,
|
||||
});
|
||||
|
||||
useHotKey('http_request.duplicate', async () => {
|
||||
const activeRequest = getActiveRequest();
|
||||
if (activeRequest == null) {
|
||||
// Nothing
|
||||
} else if (activeRequest.model === 'http_request') {
|
||||
await duplicateHttpRequest.mutateAsync();
|
||||
} else if (activeRequest.model === 'grpc_request') {
|
||||
await duplicateGrpcRequest.mutateAsync();
|
||||
} else if (activeRequest.model === 'websocket_request') {
|
||||
await duplicateWebsocketRequest.mutateAsync(activeRequest.id);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
throw new Error('Failed to duplicate invalid request model: ' + (activeRequest as any).model);
|
||||
}
|
||||
});
|
||||
useHotKey('http_request.duplicate', () =>
|
||||
duplicateRequestAndNavigate(jotaiStore.get(activeRequestAtom)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import {open} from "@tauri-apps/plugin-dialog";
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||
import { getModel, settingsAtom, workspacesAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { openWorkspaceFromSyncDir } from '../commands/openWorkspaceFromSyncDir';
|
||||
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
||||
import { switchWorkspace } from '../commands/switchWorkspace';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from '../hooks/useActiveWorkspace';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
|
||||
import { settingsAtom } from '../hooks/useSettings';
|
||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
||||
import { getWorkspace, useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { revealInFinderText } from '../lib/reveal';
|
||||
@@ -28,10 +27,10 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
className,
|
||||
...buttonProps
|
||||
}: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const workspace = useActiveWorkspace();
|
||||
const workspaces = useAtomValue(workspacesAtom);
|
||||
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const workspaceMeta = useWorkspaceMeta();
|
||||
const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom);
|
||||
const { mutate: deleteSendHistory } = useDeleteSendHistory();
|
||||
|
||||
const { workspaceItems, extraItems } = useMemo<{
|
||||
@@ -92,7 +91,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
return { workspaceItems, extraItems };
|
||||
}, [workspaces, workspaceMeta, deleteSendHistory, createWorkspace, workspace?.id]);
|
||||
|
||||
const handleChangeWorkspace = useCallback(async (workspaceId: string | null) => {
|
||||
const handleSwitchWorkspace = useCallback(async (workspaceId: string | null) => {
|
||||
if (workspaceId == null) return;
|
||||
|
||||
const settings = jotaiStore.get(settingsAtom);
|
||||
@@ -101,7 +100,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = getWorkspace(workspaceId);
|
||||
const workspace = getModel('workspace', workspaceId);
|
||||
if (workspace == null) return;
|
||||
|
||||
showDialog({
|
||||
@@ -116,7 +115,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
<RadioDropdown
|
||||
items={workspaceItems}
|
||||
extraItems={extraItems}
|
||||
onChange={handleChangeWorkspace}
|
||||
onChange={handleSwitchWorkspace}
|
||||
value={workspace?.id ?? null}
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { upsertWorkspace } from '../commands/upsertWorkspace';
|
||||
import { upsertWorkspaceMeta } from '../commands/upsertWorkspaceMeta';
|
||||
import { useDeleteActiveWorkspace } from '../hooks/useDeleteActiveWorkspace';
|
||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { patchModel, workspaceMetasAtom, workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||
import { router } from '../lib/router';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
@@ -19,10 +18,10 @@ interface Props {
|
||||
}
|
||||
|
||||
export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const workspace = workspaces.find((w) => w.id === workspaceId);
|
||||
const workspaceMeta = useWorkspaceMeta();
|
||||
const { mutateAsync: deleteActiveWorkspace } = useDeleteActiveWorkspace();
|
||||
const workspace = useAtomValue(workspacesAtom).find((w) => w.id === workspaceId);
|
||||
const workspaceMeta = useAtomValue(workspaceMetasAtom).find(
|
||||
(wm) => wm.workspaceId === workspaceId,
|
||||
);
|
||||
|
||||
if (workspace == null) {
|
||||
return (
|
||||
@@ -45,7 +44,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Pro
|
||||
required
|
||||
label="Name"
|
||||
defaultValue={workspace.name}
|
||||
onChange={(name) => upsertWorkspace.mutate({ ...workspace, name })}
|
||||
onChange={(name) => patchModel(workspace, { name })}
|
||||
stateKey={`name.${workspace.id}`}
|
||||
/>
|
||||
|
||||
@@ -55,7 +54,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Pro
|
||||
className="min-h-[10rem] max-h-[25rem] border border-border px-2"
|
||||
defaultValue={workspace.description}
|
||||
stateKey={`description.${workspace.id}`}
|
||||
onChange={(description) => upsertWorkspace.mutate({ ...workspace, description })}
|
||||
onChange={(description) => patchModel(workspace, { description })}
|
||||
heightMode="auto"
|
||||
/>
|
||||
|
||||
@@ -64,16 +63,15 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, openSyncMenu }: Pro
|
||||
value={{ filePath: workspaceMeta.settingSyncDir }}
|
||||
forceOpen={openSyncMenu}
|
||||
onCreateNewWorkspace={hide}
|
||||
onChange={({ filePath }) => {
|
||||
upsertWorkspaceMeta.mutate({ ...workspaceMeta, settingSyncDir: filePath });
|
||||
}}
|
||||
onChange={({ filePath }) => patchModel(workspaceMeta, { settingSyncDir: filePath })}
|
||||
/>
|
||||
<Separator />
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const workspace = await deleteActiveWorkspace();
|
||||
if (workspace) {
|
||||
const didDelete = await deleteModelWithConfirm(workspace);
|
||||
if (didDelete) {
|
||||
hide(); // Only hide if actually deleted workspace
|
||||
await router.navigate({ to: '/workspaces' });
|
||||
}
|
||||
}}
|
||||
color="danger"
|
||||
|
||||
@@ -6,10 +6,12 @@ import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/vie
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||
import type { EditorKeymap, EnvironmentVariable } from '@yaakapp-internal/models';
|
||||
import type {EditorKeymap, EnvironmentVariable} from '@yaakapp-internal/models';
|
||||
import { settingsAtom} from '@yaakapp-internal/models';
|
||||
import type { EditorLanguage, TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { EditorView } from 'codemirror';
|
||||
import {useAtomValue} from "jotai";
|
||||
import { md5 } from 'js-md5';
|
||||
import type { MutableRefObject, ReactNode } from 'react';
|
||||
import {
|
||||
@@ -26,7 +28,6 @@ import {
|
||||
import { useActiveEnvironmentVariables } from '../../../hooks/useActiveEnvironmentVariables';
|
||||
import { parseTemplate } from '../../../hooks/useParseTemplate';
|
||||
import { useRequestEditor } from '../../../hooks/useRequestEditor';
|
||||
import { useSettings } from '../../../hooks/useSettings';
|
||||
import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplateFunctions';
|
||||
import { showDialog } from '../../../lib/dialog';
|
||||
import { tryFormatJson, tryFormatXml } from '../../../lib/formatters';
|
||||
@@ -125,7 +126,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
}: EditorProps,
|
||||
ref,
|
||||
) {
|
||||
const settings = useSettings();
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
|
||||
const allEnvironmentVariables = useActiveEnvironmentVariables();
|
||||
const environmentVariables = autocompleteVariables ? allEnvironmentVariables : emptyVariables;
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface SelectProps<T extends string> {
|
||||
leftSlot?: ReactNode;
|
||||
options: RadioDropdownItem<T>[];
|
||||
onChange: (value: T) => void;
|
||||
defaultValue?: T;
|
||||
size?: ButtonProps['size'];
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
@@ -36,6 +37,7 @@ export function Select<T extends string>({
|
||||
leftSlot,
|
||||
onChange,
|
||||
className,
|
||||
defaultValue,
|
||||
size = 'md',
|
||||
}: SelectProps<T>) {
|
||||
const osInfo = useOsInfo();
|
||||
@@ -83,7 +85,9 @@ export function Select<T extends string>({
|
||||
onChange={(e) => handleChange(e.target.value as T)}
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
className={classNames('pr-7 w-full outline-none bg-transparent disabled:opacity-disabled')}
|
||||
className={classNames(
|
||||
'pr-7 w-full outline-none bg-transparent disabled:opacity-disabled',
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{isInvalidSelection && <option value={'__NONE__'}>-- Select an Option --</option>}
|
||||
@@ -92,6 +96,7 @@ export function Select<T extends string>({
|
||||
return (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
{o.value === defaultValue && ' (default)'}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode } from 'react';
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { useContainerSize } from '../../hooks/useContainerQuery';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import { ResizeHandle } from '../ResizeHandle';
|
||||
@@ -42,7 +43,7 @@ export function SplitLayout({
|
||||
minWidthPx = 10,
|
||||
}: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const [widthRaw, setWidth] = useLocalStorage<number>(
|
||||
`${name}_width::${activeWorkspace?.id ?? 'n/a'}`,
|
||||
);
|
||||
|
||||
@@ -5,29 +5,26 @@ import type {
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { getAnyModel, patchModelById } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useKey, useKeyPressEvent } from 'react-use';
|
||||
import { upsertWebsocketRequest } from '../../commands/upsertWebsocketRequest';
|
||||
import { getActiveRequest } from '../../hooks/useActiveRequest';
|
||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||
import { activeRequestIdAtom } from '../../hooks/useActiveRequestId';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
||||
import { useDeleteAnyRequest } from '../../hooks/useDeleteAnyRequest';
|
||||
import { useHotKey } from '../../hooks/useHotKey';
|
||||
import { useSidebarHidden } from '../../hooks/useSidebarHidden';
|
||||
import { getSidebarCollapsedMap } from '../../hooks/useSidebarItemCollapsed';
|
||||
import { useUpdateAnyFolder } from '../../hooks/useUpdateAnyFolder';
|
||||
import { useUpdateAnyGrpcRequest } from '../../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../../hooks/useUpdateAnyHttpRequest';
|
||||
import { getWebsocketRequest } from '../../hooks/useWebsocketRequests';
|
||||
import { deleteModelWithConfirm } from '../../lib/deleteModelWithConfirm';
|
||||
import { jotaiStore } from '../../lib/jotai';
|
||||
import { router } from '../../lib/router';
|
||||
import { setWorkspaceSearchParams } from '../../lib/setWorkspaceSearchParams';
|
||||
import { ContextMenu } from '../core/Dropdown';
|
||||
import { GitDropdown } from '../GitDropdown';
|
||||
import { sidebarSelectedIdAtom, sidebarTreeAtom } from './SidebarAtoms';
|
||||
import type { SidebarItemProps } from './SidebarItem';
|
||||
import { SidebarItems } from './SidebarItems';
|
||||
import { GitDropdown } from '../GitDropdown';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -49,13 +46,10 @@ export interface SidebarTreeNode {
|
||||
export function Sidebar({ className }: Props) {
|
||||
const [hidden, setHidden] = useSidebarHidden();
|
||||
const sidebarRef = useRef<HTMLElement>(null);
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const [hasFocus, setHasFocus] = useState<boolean>(false);
|
||||
const [selectedId, setSelectedId] = useAtom(sidebarSelectedIdAtom);
|
||||
const [selectedTree, setSelectedTree] = useState<SidebarTreeNode | null>(null);
|
||||
const { mutateAsync: updateAnyHttpRequest } = useUpdateAnyHttpRequest();
|
||||
const { mutateAsync: updateAnyGrpcRequest } = useUpdateAnyGrpcRequest();
|
||||
const { mutateAsync: updateAnyFolder } = useUpdateAnyFolder();
|
||||
const [draggingId, setDraggingId] = useState<string | null>(null);
|
||||
const [hoveredTree, setHoveredTree] = useState<SidebarTreeNode | null>(null);
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||
@@ -72,11 +66,11 @@ export function Sidebar({ className }: Props) {
|
||||
noFocusSidebar?: boolean;
|
||||
} = {},
|
||||
) => {
|
||||
const activeRequest = getActiveRequest();
|
||||
const activeRequestId = jotaiStore.get(activeRequestIdAtom);
|
||||
const { forced, noFocusSidebar } = args;
|
||||
const tree = forced?.tree ?? treeParentMap[activeRequest?.id ?? 'n/a'] ?? null;
|
||||
const tree = forced?.tree ?? treeParentMap[activeRequestId ?? 'n/a'] ?? null;
|
||||
const children = tree?.children ?? [];
|
||||
const id = forced?.id ?? children.find((m) => m.id === activeRequest?.id)?.id ?? null;
|
||||
const id = forced?.id ?? children.find((m) => m.id === activeRequestId)?.id ?? null;
|
||||
|
||||
setHasFocus(true);
|
||||
setSelectedId(id);
|
||||
@@ -129,14 +123,14 @@ export function Sidebar({ className }: Props) {
|
||||
}, [focusActiveRequest, hasFocus]);
|
||||
|
||||
const handleBlur = useCallback(() => setHasFocus(false), [setHasFocus]);
|
||||
const deleteRequest = useDeleteAnyRequest();
|
||||
|
||||
useHotKey(
|
||||
'http_request.delete',
|
||||
'sidebar.delete_selected_item',
|
||||
async () => {
|
||||
// Delete only works if a request is focused
|
||||
if (selectedId == null) return;
|
||||
deleteRequest.mutate(selectedId);
|
||||
const request = getAnyModel(selectedId ?? 'n/a');
|
||||
if (request != null) {
|
||||
await deleteModelWithConfirm(request);
|
||||
}
|
||||
},
|
||||
{ enable: hasFocus },
|
||||
);
|
||||
@@ -178,7 +172,8 @@ export function Sidebar({ className }: Props) {
|
||||
if (!hasFocus) return;
|
||||
e.preventDefault();
|
||||
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
||||
const newSelectable = selectableRequests[i - 1];
|
||||
const newI = i <= 0 ? selectableRequests.length - 1 : i - 1;
|
||||
const newSelectable = selectableRequests[newI];
|
||||
if (newSelectable == null) {
|
||||
return;
|
||||
}
|
||||
@@ -196,7 +191,8 @@ export function Sidebar({ className }: Props) {
|
||||
if (!hasFocus) return;
|
||||
e.preventDefault();
|
||||
const i = selectableRequests.findIndex((r) => r.id === selectedId);
|
||||
const newSelectable = selectableRequests[i + 1];
|
||||
const newI = i >= selectableRequests.length - 1 ? 0 : i + 1;
|
||||
const newSelectable = selectableRequests[newI];
|
||||
if (newSelectable == null) {
|
||||
return;
|
||||
}
|
||||
@@ -277,52 +273,16 @@ export function Sidebar({ className }: Props) {
|
||||
await Promise.all(
|
||||
newChildren.map((child, i) => {
|
||||
const sortPriority = i * 1000;
|
||||
if (child.model === 'folder') {
|
||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
||||
return updateAnyFolder({ id: child.id, update: updateFolder });
|
||||
} else if (child.model === 'grpc_request') {
|
||||
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
||||
return updateAnyGrpcRequest({ id: child.id, update: updateRequest });
|
||||
} else if (child.model === 'http_request') {
|
||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
||||
return updateAnyHttpRequest({ id: child.id, update: updateRequest });
|
||||
} else if (child.model === 'websocket_request') {
|
||||
const request = getWebsocketRequest(child.id);
|
||||
return upsertWebsocketRequest.mutateAsync({ ...request, sortPriority, folderId });
|
||||
} else {
|
||||
throw new Error('Invalid model to update: ' + child.model);
|
||||
}
|
||||
return patchModelById(child.model, child.id, { sortPriority, folderId });
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const sortPriority = afterPriority - (afterPriority - beforePriority) / 2;
|
||||
if (child.model === 'folder') {
|
||||
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
|
||||
await updateAnyFolder({ id: child.id, update: updateFolder });
|
||||
} else if (child.model === 'grpc_request') {
|
||||
const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId });
|
||||
await updateAnyGrpcRequest({ id: child.id, update: updateRequest });
|
||||
} else if (child.model === 'http_request') {
|
||||
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
|
||||
await updateAnyHttpRequest({ id: child.id, update: updateRequest });
|
||||
} else if (child.model === 'websocket_request') {
|
||||
const request = getWebsocketRequest(child.id);
|
||||
return upsertWebsocketRequest.mutateAsync({ ...request, sortPriority, folderId });
|
||||
} else {
|
||||
throw new Error('Invalid model to update: ' + child.model);
|
||||
}
|
||||
return patchModelById(child.model, child.id, { sortPriority, folderId });
|
||||
}
|
||||
setDraggingId(null);
|
||||
},
|
||||
[
|
||||
handleClearSelected,
|
||||
hoveredTree,
|
||||
hoveredIndex,
|
||||
treeParentMap,
|
||||
updateAnyFolder,
|
||||
updateAnyGrpcRequest,
|
||||
updateAnyHttpRequest,
|
||||
],
|
||||
[handleClearSelected, hoveredTree, hoveredIndex, treeParentMap],
|
||||
);
|
||||
|
||||
const [showMainContextMenu, setShowMainContextMenu] = useState<{
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import type { Folder, GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import {
|
||||
type Folder,
|
||||
foldersAtom,
|
||||
type GrpcRequest,
|
||||
type HttpRequest,
|
||||
type WebsocketRequest,
|
||||
} from '@yaakapp-internal/models';
|
||||
|
||||
// This is an atom so we can use it in the child items to avoid re-rendering the entire list
|
||||
import { atom } from 'jotai';
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { foldersAtom } from '../../hooks/useFolders';
|
||||
import { requestsAtom } from '../../hooks/useRequests';
|
||||
import { allRequestsAtom } from '../../hooks/useAllRequests';
|
||||
import { deepEqualAtom } from '../../lib/atoms';
|
||||
import { resolvedModelName } from '../../lib/resolvedModelName';
|
||||
import type { SidebarTreeNode } from './Sidebar';
|
||||
@@ -12,7 +17,7 @@ import type { SidebarTreeNode } from './Sidebar';
|
||||
export const sidebarSelectedIdAtom = atom<string | null>(null);
|
||||
|
||||
const allPotentialChildrenAtom = atom((get) => {
|
||||
const requests = get(requestsAtom);
|
||||
const requests = get(allRequestsAtom);
|
||||
const folders = get(foldersAtom);
|
||||
return [...requests, ...folders].map((v) => ({
|
||||
id: v.id,
|
||||
|
||||
@@ -4,26 +4,22 @@ import type {
|
||||
HttpResponse,
|
||||
WebsocketConnection,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { foldersAtom, patchModelById } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { upsertWebsocketRequest } from '../../commands/upsertWebsocketRequest';
|
||||
import { activeRequestAtom } from '../../hooks/useActiveRequest';
|
||||
import { foldersAtom } from '../../hooks/useFolders';
|
||||
import { requestsAtom } from '../../hooks/useRequests';
|
||||
import {allRequestsAtom} from "../../hooks/useAllRequests";
|
||||
import { useScrollIntoView } from '../../hooks/useScrollIntoView';
|
||||
import { useSidebarItemCollapsed } from '../../hooks/useSidebarItemCollapsed';
|
||||
import { useUpdateAnyGrpcRequest } from '../../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../../hooks/useUpdateAnyHttpRequest';
|
||||
import { getWebsocketRequest } from '../../hooks/useWebsocketRequests';
|
||||
import { jotaiStore } from '../../lib/jotai';
|
||||
import { HttpMethodTag } from '../core/HttpMethodTag';
|
||||
import { HttpStatusTag } from '../core/HttpStatusTag';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { LoadingIcon } from '../core/LoadingIcon';
|
||||
import { HttpStatusTag } from '../core/HttpStatusTag';
|
||||
import type { SidebarTreeNode } from './Sidebar';
|
||||
import { sidebarSelectedIdAtom } from './SidebarAtoms';
|
||||
import { SidebarItemContextMenu } from './SidebarItemContextMenu';
|
||||
@@ -110,8 +106,6 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
|
||||
connectDrag(connectDrop(ref));
|
||||
|
||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
|
||||
const [selected, setSelected] = useState<boolean>(
|
||||
@@ -137,26 +131,12 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
|
||||
const handleSubmitNameEdit = useCallback(
|
||||
async (el: HTMLInputElement) => {
|
||||
if (itemModel === 'http_request') {
|
||||
await updateHttpRequest.mutateAsync({
|
||||
id: itemId,
|
||||
update: (r) => ({ ...r, name: el.value }),
|
||||
});
|
||||
} else if (itemModel === 'grpc_request') {
|
||||
await updateGrpcRequest.mutateAsync({
|
||||
id: itemId,
|
||||
update: (r) => ({ ...r, name: el.value }),
|
||||
});
|
||||
} else if (itemModel === 'websocket_request') {
|
||||
const request = getWebsocketRequest(itemId);
|
||||
if (request == null) return;
|
||||
await upsertWebsocketRequest.mutateAsync({ ...request, name: el.value });
|
||||
}
|
||||
await patchModelById(itemModel, itemId, { name: el.value });
|
||||
|
||||
// Slight delay for the model to propagate to the local store
|
||||
setTimeout(() => setEditing(false));
|
||||
},
|
||||
[itemId, itemModel, updateGrpcRequest, updateHttpRequest],
|
||||
[itemId, itemModel],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback((el: HTMLInputElement | null) => {
|
||||
@@ -199,8 +179,11 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(async () => {
|
||||
if (itemModel === 'folder') toggleCollapsed();
|
||||
else onSelect(itemId);
|
||||
if (itemModel === 'folder') {
|
||||
toggleCollapsed();
|
||||
} else {
|
||||
onSelect(itemId);
|
||||
}
|
||||
}, [itemModel, toggleCollapsed, onSelect, itemId]);
|
||||
const [showContextMenu, setShowContextMenu] = useState<{
|
||||
x: number;
|
||||
@@ -220,7 +203,7 @@ export const SidebarItem = memo(function SidebarItem({
|
||||
if (itemModel === 'folder') {
|
||||
return get(foldersAtom).find((v) => v.id === itemId);
|
||||
} else {
|
||||
return get(requestsAtom).find((v) => v.id === itemId);
|
||||
return get(allRequestsAtom).find((v) => v.id === itemId);
|
||||
}
|
||||
});
|
||||
}, [itemId, itemModel]);
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import {
|
||||
deleteModelById,
|
||||
duplicateModelById,
|
||||
getModel,
|
||||
workspacesAtom,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useMemo } from 'react';
|
||||
import { duplicateWebsocketRequest } from '../../commands/duplicateWebsocketRequest';
|
||||
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
||||
import { useDeleteAnyRequest } from '../../hooks/useDeleteAnyRequest';
|
||||
import { useDeleteFolder } from '../../hooks/useDeleteFolder';
|
||||
import { useDuplicateFolder } from '../../hooks/useDuplicateFolder';
|
||||
import { useDuplicateGrpcRequest } from '../../hooks/useDuplicateGrpcRequest';
|
||||
import { useDuplicateHttpRequest } from '../../hooks/useDuplicateHttpRequest';
|
||||
import { useHttpRequestActions } from '../../hooks/useHttpRequestActions';
|
||||
import { getHttpRequest } from '../../hooks/useHttpRequests';
|
||||
import { useMoveToWorkspace } from '../../hooks/useMoveToWorkspace';
|
||||
import { useRenameRequest } from '../../hooks/useRenameRequest';
|
||||
import { useSendAnyHttpRequest } from '../../hooks/useSendAnyHttpRequest';
|
||||
import { useSendManyRequests } from '../../hooks/useSendManyRequests';
|
||||
import { useWorkspaces } from '../../hooks/useWorkspaces';
|
||||
import { deleteModelWithConfirm } from '../../lib/deleteModelWithConfirm';
|
||||
import { duplicateRequestAndNavigate } from '../../lib/deleteRequestAndNavigate';
|
||||
|
||||
import { showDialog } from '../../lib/dialog';
|
||||
import { renameModelWithPrompt } from '../../lib/renameModelWithPrompt';
|
||||
import type { DropdownItem } from '../core/Dropdown';
|
||||
import { ContextMenu } from '../core/Dropdown';
|
||||
import { Icon } from '../core/Icon';
|
||||
@@ -29,15 +30,9 @@ interface Props {
|
||||
|
||||
export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
const sendManyRequests = useSendManyRequests();
|
||||
const duplicateFolder = useDuplicateFolder(child.id);
|
||||
const deleteFolder = useDeleteFolder(child.id);
|
||||
const httpRequestActions = useHttpRequestActions();
|
||||
const sendRequest = useSendAnyHttpRequest();
|
||||
const workspaces = useWorkspaces();
|
||||
const deleteRequest = useDeleteAnyRequest();
|
||||
const renameRequest = useRenameRequest(child.id);
|
||||
const duplicateHttpRequest = useDuplicateHttpRequest({ id: child.id, navigateAfter: true });
|
||||
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: child.id, navigateAfter: true });
|
||||
const workspaces = useAtomValue(workspacesAtom);
|
||||
const moveToWorkspace = useMoveToWorkspace(child.id);
|
||||
const createDropdownItems = useCreateDropdownItems({
|
||||
folderId: child.model === 'folder' ? child.id : null,
|
||||
@@ -65,13 +60,15 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
{
|
||||
label: 'Duplicate',
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
onSelect: () => duplicateFolder.mutate(),
|
||||
onSelect: () => duplicateModelById(child.model, child.id),
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
color: 'danger',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: () => deleteFolder.mutate(),
|
||||
onSelect: async () => {
|
||||
await deleteModelWithConfirm(getModel(child.model, child.id));
|
||||
},
|
||||
},
|
||||
{ type: 'separator' },
|
||||
...createDropdownItems,
|
||||
@@ -92,7 +89,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
leftSlot: <Icon icon={(a.icon as any) ?? 'empty'} />,
|
||||
onSelect: async () => {
|
||||
const request = getHttpRequest(child.id);
|
||||
const request = getModel('http_request', child.id);
|
||||
if (request != null) await a.call(request);
|
||||
},
|
||||
})),
|
||||
@@ -104,23 +101,25 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
{
|
||||
label: 'Rename',
|
||||
leftSlot: <Icon icon="pencil" />,
|
||||
onSelect: renameRequest.mutate,
|
||||
onSelect: async () => {
|
||||
const request = getModel(
|
||||
['http_request', 'grpc_request', 'websocket_request'],
|
||||
child.id,
|
||||
);
|
||||
await renameModelWithPrompt(request);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Duplicate',
|
||||
hotKeyAction: 'http_request.duplicate',
|
||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
onSelect: () => {
|
||||
if (child.model === 'http_request') {
|
||||
duplicateHttpRequest.mutate();
|
||||
} else if (child.model === 'grpc_request') {
|
||||
duplicateGrpcRequest.mutate();
|
||||
} else if (child.model === 'websocket_request') {
|
||||
duplicateWebsocketRequest.mutate(child.id);
|
||||
} else {
|
||||
throw new Error('Cannot duplicate invalid model: ' + child.model);
|
||||
}
|
||||
onSelect: async () => {
|
||||
const request = getModel(
|
||||
['http_request', 'grpc_request', 'websocket_request'],
|
||||
child.id,
|
||||
);
|
||||
await duplicateRequestAndNavigate(request);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -132,10 +131,10 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
{
|
||||
color: 'danger',
|
||||
label: 'Delete',
|
||||
hotKeyAction: 'http_request.delete',
|
||||
hotKeyAction: 'sidebar.delete_selected_item',
|
||||
hotKeyLabelOnly: true,
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: () => deleteRequest.mutate(child.id),
|
||||
onSelect: async () => deleteModelById(child.model, child.id),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -144,14 +143,8 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
child.id,
|
||||
child.model,
|
||||
createDropdownItems,
|
||||
deleteFolder,
|
||||
deleteRequest,
|
||||
duplicateFolder,
|
||||
duplicateGrpcRequest,
|
||||
duplicateHttpRequest,
|
||||
httpRequestActions,
|
||||
moveToWorkspace.mutate,
|
||||
renameRequest.mutate,
|
||||
sendManyRequests,
|
||||
sendRequest,
|
||||
workspaces.length,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {
|
||||
grpcConnectionsAtom,
|
||||
httpResponsesAtom,
|
||||
websocketConnectionsAtom,
|
||||
} from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import React, { Fragment, memo } from 'react';
|
||||
import { useGrpcConnections } from '../../hooks/useGrpcConnections';
|
||||
import { useHttpResponses } from '../../hooks/useHttpResponses';
|
||||
import { useWebsocketConnections } from '../../hooks/useWebsocketConnections';
|
||||
import { VStack } from '../core/Stacks';
|
||||
import { DropMarker } from '../DropMarker';
|
||||
import type { SidebarTreeNode } from './Sidebar';
|
||||
@@ -33,9 +36,9 @@ export const SidebarItems = memo(function SidebarItems({
|
||||
handleMove,
|
||||
handleDragStart,
|
||||
}: SidebarItemsProps) {
|
||||
const httpResponses = useHttpResponses();
|
||||
const grpcConnections = useGrpcConnections();
|
||||
const websocketConnections = useWebsocketConnections();
|
||||
const httpResponses = useAtomValue(httpResponsesAtom);
|
||||
const grpcConnections = useAtomValue(grpcConnectionsAtom);
|
||||
const websocketConnections = useAtomValue(websocketConnectionsAtom);
|
||||
|
||||
return (
|
||||
<VStack
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Listen for settings changes, the re-compute theme
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import type { ModelPayload, Settings } from '@yaakapp-internal/models';
|
||||
import { invokeCmd } from './lib/tauri';
|
||||
import type { ModelPayload } from '@yaakapp-internal/models';
|
||||
import { getSettings } from './lib/settings';
|
||||
|
||||
function setFontSizeOnDocument(fontSize: number) {
|
||||
document.documentElement.style.fontSize = `${fontSize}px`;
|
||||
@@ -12,6 +12,6 @@ listen<ModelPayload>('upserted_model', async (event) => {
|
||||
setFontSizeOnDocument(event.payload.model.interfaceFontSize);
|
||||
}).catch(console.error);
|
||||
|
||||
invokeCmd<Settings>('cmd_get_settings').then((settings) =>
|
||||
getSettings().then((settings) =>
|
||||
setFontSizeOnDocument(settings.interfaceFontSize),
|
||||
);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useSearch } from '@tanstack/react-router';
|
||||
import type { CookieJar } from '@yaakapp-internal/models';
|
||||
import { cookieJarsAtom } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai/index';
|
||||
import { useEffect } from 'react';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||
import { cookieJarsAtom, useCookieJars } from './useCookieJars';
|
||||
|
||||
export const QUERY_COOKIE_JAR_ID = 'cookie_jar_id';
|
||||
|
||||
export const activeCookieJarAtom = atom<CookieJar | null>(null);
|
||||
|
||||
@@ -18,6 +16,7 @@ export function useSubscribeActiveCookieJarId() {
|
||||
const search = useSearch({ strict: false });
|
||||
const cookieJarId = search.cookie_jar_id;
|
||||
const cookieJars = useAtomValue(cookieJarsAtom);
|
||||
|
||||
useEffect(() => {
|
||||
if (search == null) return; // Happens during Vite hot reload
|
||||
const activeCookieJar = cookieJars?.find((j) => j.id == cookieJarId) ?? null;
|
||||
@@ -30,7 +29,7 @@ export function getActiveCookieJar() {
|
||||
}
|
||||
|
||||
export function useEnsureActiveCookieJar() {
|
||||
const cookieJars = useCookieJars();
|
||||
const cookieJars = useAtomValue(cookieJarsAtom);
|
||||
const { cookie_jar_id: activeCookieJarId } = useSearch({ from: '/workspaces/$workspaceId/' });
|
||||
|
||||
// Set the active cookie jar to the first one, if none set
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useSearch } from '@tanstack/react-router';
|
||||
import type { Environment } from '@yaakapp-internal/models';
|
||||
import { environmentsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { atom } from 'jotai/index';
|
||||
import { useEffect } from 'react';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { environmentsAtom } from './useEnvironments';
|
||||
|
||||
export const QUERY_ENVIRONMENT_ID = 'environment_id';
|
||||
|
||||
export const activeEnvironmentIdAtom = atom<string>();
|
||||
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import type { EnvironmentVariable } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
import { activeEnvironmentAtom } from './useActiveEnvironment';
|
||||
import { environmentsBreakdownAtom } from './useEnvironments';
|
||||
|
||||
const activeEnvironmentVariablesAtom = atom((get) => {
|
||||
const { baseEnvironment } = get(environmentsBreakdownAtom);
|
||||
const activeEnvironment = get(activeEnvironmentAtom);
|
||||
|
||||
const varMap: Record<string, EnvironmentVariable> = {};
|
||||
const allVariables = [
|
||||
...(baseEnvironment?.variables ?? []),
|
||||
...(activeEnvironment?.variables ?? []),
|
||||
];
|
||||
|
||||
for (const v of allVariables) {
|
||||
if (!v.enabled || !v.name) continue;
|
||||
varMap[v.name] = v;
|
||||
}
|
||||
|
||||
return Object.values(varMap);
|
||||
});
|
||||
import { useEnvironmentsBreakdown } from './useEnvironmentsBreakdown';
|
||||
|
||||
export function useActiveEnvironmentVariables() {
|
||||
return useAtomValue(activeEnvironmentVariablesAtom);
|
||||
const { baseEnvironment } = useEnvironmentsBreakdown();
|
||||
const activeEnvironment = useAtomValue(activeEnvironmentAtom);
|
||||
return useMemo(() => {
|
||||
const varMap: Record<string, EnvironmentVariable> = {};
|
||||
const allVariables = [
|
||||
...(baseEnvironment?.variables ?? []),
|
||||
...(activeEnvironment?.variables ?? []),
|
||||
];
|
||||
|
||||
for (const v of allVariables) {
|
||||
if (!v.enabled || !v.name) continue;
|
||||
varMap[v.name] = v;
|
||||
}
|
||||
|
||||
return Object.values(varMap);
|
||||
}, [activeEnvironment, baseEnvironment]);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { activeRequestIdAtom } from './useActiveRequestId';
|
||||
import { requestsAtom } from './useRequests';
|
||||
import { allRequestsAtom } from './useAllRequests';
|
||||
|
||||
export const activeRequestAtom = atom((get) => {
|
||||
const activeRequestId = get(activeRequestIdAtom);
|
||||
const requests = get(allRequestsAtom);
|
||||
return requests.find((r) => r.id === activeRequestId) ?? null;
|
||||
});
|
||||
|
||||
interface TypeMap {
|
||||
http_request: HttpRequest;
|
||||
@@ -10,16 +15,6 @@ interface TypeMap {
|
||||
websocket_request: WebsocketRequest;
|
||||
}
|
||||
|
||||
export const activeRequestAtom = atom((get) => {
|
||||
const activeRequestId = get(activeRequestIdAtom);
|
||||
const requests = get(requestsAtom);
|
||||
return requests.find((r) => r.id === activeRequestId) ?? null;
|
||||
});
|
||||
|
||||
export function getActiveRequest() {
|
||||
return jotaiStore.get(activeRequestAtom);
|
||||
}
|
||||
|
||||
export function useActiveRequest<T extends keyof TypeMap>(
|
||||
model?: T | undefined,
|
||||
): TypeMap[T] | null {
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
import {useParams} from '@tanstack/react-router';
|
||||
import type {Workspace} from '@yaakapp-internal/models';
|
||||
import {atom, useAtomValue} from 'jotai/index';
|
||||
import {useEffect} from 'react';
|
||||
import {jotaiStore} from '../lib/jotai';
|
||||
import {workspacesAtom} from './useWorkspaces';
|
||||
import { useParams } from '@tanstack/react-router';
|
||||
import { workspaceMetasAtom, workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { atom } from 'jotai/index';
|
||||
import { useEffect } from 'react';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
|
||||
export const activeWorkspaceIdAtom = atom<string>();
|
||||
export const activeWorkspaceIdAtom = atom<string | null>(null);
|
||||
|
||||
export const activeWorkspaceAtom = atom<Workspace | null>((get) => {
|
||||
const activeWorkspaceId = get(activeWorkspaceIdAtom);
|
||||
const workspaces = get(workspacesAtom);
|
||||
return workspaces.find((w) => w.id === activeWorkspaceId) ?? null;
|
||||
export const activeWorkspaceAtom = atom((get) => {
|
||||
const activeWorkspaceId = get(activeWorkspaceIdAtom);
|
||||
const workspaces = get(workspacesAtom);
|
||||
return workspaces.find((w) => w.id === activeWorkspaceId) ?? null;
|
||||
});
|
||||
|
||||
export function useActiveWorkspace(): Workspace | null {
|
||||
return useAtomValue(activeWorkspaceAtom);
|
||||
}
|
||||
|
||||
export function getActiveWorkspaceId() {
|
||||
return jotaiStore.get(activeWorkspaceIdAtom) ?? null;
|
||||
}
|
||||
|
||||
export function getActiveWorkspace() {
|
||||
return jotaiStore.get(activeWorkspaceAtom) ?? null;
|
||||
}
|
||||
export const activeWorkspaceMetaAtom = atom((get) => {
|
||||
const activeWorkspaceId = get(activeWorkspaceIdAtom);
|
||||
const workspaceMetas = get(workspaceMetasAtom);
|
||||
return workspaceMetas.find((m) => m.workspaceId === activeWorkspaceId) ?? null;
|
||||
});
|
||||
|
||||
export function useSubscribeActiveWorkspaceId() {
|
||||
const {workspaceId} = useParams({strict: false});
|
||||
useEffect(() => jotaiStore.set(activeWorkspaceIdAtom, workspaceId), [workspaceId]);
|
||||
const { workspaceId } = useParams({ strict: false });
|
||||
useEffect(() => jotaiStore.set(activeWorkspaceIdAtom, workspaceId ?? null), [workspaceId]);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
import { showToast } from '../lib/toast';
|
||||
import { activeWorkspaceAtom } from './useActiveWorkspace';
|
||||
|
||||
export function useActiveWorkspaceChangedToast() {
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const [id, setId] = useState<string | null>(activeWorkspace?.id ?? null);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
14
src-web/hooks/useAllRequests.ts
Normal file
14
src-web/hooks/useAllRequests.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
grpcRequestsAtom,
|
||||
httpRequestsAtom,
|
||||
websocketRequestsAtom,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai/index';
|
||||
|
||||
export const allRequestsAtom = atom(function (get) {
|
||||
return [...get(httpRequestsAtom), ...get(grpcRequestsAtom), ...get(websocketRequestsAtom)];
|
||||
});
|
||||
|
||||
export function useAllRequests() {
|
||||
return useAtomValue(allRequestsAtom);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { CookieJar } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
|
||||
export const cookieJarsAtom = atom<CookieJar[] | undefined>();
|
||||
|
||||
export const sortedCookieJars = atom((get) => {
|
||||
return get(cookieJarsAtom)?.sort((a, b) => a.name.localeCompare(b.name));
|
||||
});
|
||||
|
||||
export function useCookieJars() {
|
||||
return useAtomValue(sortedCookieJars);
|
||||
}
|
||||
|
||||
export function getCookieJar(id: string | null) {
|
||||
return jotaiStore.get(cookieJarsAtom)?.find((e) => e.id === id) ?? null;
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
import type { CookieJar } from '@yaakapp-internal/models';
|
||||
import { createWorkspaceModel } from '@yaakapp-internal/models';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveWorkspaceId } from './useActiveWorkspace';
|
||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useCreateCookieJar() {
|
||||
return useFastMutation<CookieJar | null>({
|
||||
return useFastMutation({
|
||||
mutationKey: ['create_cookie_jar'],
|
||||
mutationFn: async () => {
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (workspaceId == null) {
|
||||
throw new Error("Cannot create cookie jar when there's no active workspace");
|
||||
}
|
||||
|
||||
const name = await showPrompt({
|
||||
id: 'new-cookie-jar',
|
||||
title: 'New CookieJar',
|
||||
@@ -22,7 +24,10 @@ export function useCreateCookieJar() {
|
||||
});
|
||||
if (name == null) return null;
|
||||
|
||||
return invokeCmd('cmd_create_cookie_jar', { workspaceId, name });
|
||||
return createWorkspaceModel({ model: 'cookie_jar', workspaceId, name });
|
||||
},
|
||||
onSuccess: async (cookieJarId) => {
|
||||
setWorkspaceSearchParams({ cookie_jar_id: cookieJarId });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { createWorkspaceModel } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
import { createFolder } from '../commands/commands';
|
||||
import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest';
|
||||
import type { DropdownItem } from '../components/core/Dropdown';
|
||||
import { Icon } from '../components/core/Icon';
|
||||
import { generateId } from '../lib/generateId';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { BODY_TYPE_GRAPHQL } from '../lib/model_util';
|
||||
import { getActiveRequest } from './useActiveRequest';
|
||||
import { getActiveWorkspace } from './useActiveWorkspace';
|
||||
import { useCreateGrpcRequest } from './useCreateGrpcRequest';
|
||||
import { activeRequestAtom } from './useActiveRequest';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
import { useCreateHttpRequest } from './useCreateHttpRequest';
|
||||
|
||||
export function useCreateDropdownItems({
|
||||
@@ -20,14 +21,13 @@ export function useCreateDropdownItems({
|
||||
folderId?: string | null | 'active-folder';
|
||||
} = {}): DropdownItem[] {
|
||||
const { mutate: createHttpRequest } = useCreateHttpRequest();
|
||||
const { mutate: createGrpcRequest } = useCreateGrpcRequest();
|
||||
const activeWorkspace = getActiveWorkspace();
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
|
||||
const items = useMemo((): DropdownItem[] => {
|
||||
const activeRequest = getActiveRequest();
|
||||
const activeRequest = jotaiStore.get(activeRequestAtom);
|
||||
const folderId =
|
||||
(folderIdOption === 'active-folder' ? activeRequest?.folderId : folderIdOption) ?? null;
|
||||
if (activeWorkspace == null) return [];
|
||||
if (workspaceId == null) return [];
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -51,13 +51,12 @@ export function useCreateDropdownItems({
|
||||
{
|
||||
label: 'gRPC',
|
||||
leftSlot: hideIcons ? undefined : <Icon icon="plus" />,
|
||||
onSelect: () => createGrpcRequest({ folderId }),
|
||||
onSelect: () => createWorkspaceModel({ model: 'grpc_request', workspaceId, folderId }),
|
||||
},
|
||||
{
|
||||
label: 'WebSocket',
|
||||
leftSlot: hideIcons ? undefined : <Icon icon="plus" />,
|
||||
onSelect: () =>
|
||||
upsertWebsocketRequest.mutate({ folderId, workspaceId: activeWorkspace.id }),
|
||||
onSelect: () => createWorkspaceModel({ model: 'websocket_request', workspaceId, folderId }),
|
||||
},
|
||||
...((hideFolder
|
||||
? []
|
||||
@@ -70,14 +69,7 @@ export function useCreateDropdownItems({
|
||||
},
|
||||
]) as DropdownItem[]),
|
||||
];
|
||||
}, [
|
||||
activeWorkspace,
|
||||
createGrpcRequest,
|
||||
createHttpRequest,
|
||||
folderIdOption,
|
||||
hideFolder,
|
||||
hideIcons,
|
||||
]);
|
||||
}, [createHttpRequest, folderIdOption, hideFolder, hideIcons, workspaceId]);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import type { Environment } from '@yaakapp-internal/models';
|
||||
import { createWorkspaceModel } from '@yaakapp-internal/models';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveWorkspaceId } from './useActiveWorkspace';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useCreateEnvironment() {
|
||||
return useFastMutation<Environment | null, unknown, Environment | null>({
|
||||
return useFastMutation<string, unknown, Environment | null>({
|
||||
mutationKey: ['create_environment'],
|
||||
mutationFn: async (baseEnvironment) => {
|
||||
if (baseEnvironment == null) {
|
||||
throw new Error('No base environment passed');
|
||||
}
|
||||
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (workspaceId == null) {
|
||||
throw new Error('Cannot create environment when no active workspace');
|
||||
}
|
||||
|
||||
const name = await showPrompt({
|
||||
id: 'new-environment',
|
||||
title: 'New Environment',
|
||||
@@ -23,18 +28,18 @@ export function useCreateEnvironment() {
|
||||
defaultValue: 'My Environment',
|
||||
confirmText: 'Create',
|
||||
});
|
||||
if (name == null) return null;
|
||||
if (name == null) throw new Error('No name provided to create environment');
|
||||
|
||||
return invokeCmd('cmd_create_environment', {
|
||||
return createWorkspaceModel({
|
||||
model: 'environment',
|
||||
name,
|
||||
variables: [],
|
||||
workspaceId,
|
||||
environmentId: baseEnvironment.id,
|
||||
});
|
||||
},
|
||||
onSuccess: async (environment) => {
|
||||
if (environment == null) return;
|
||||
setWorkspaceSearchParams({ environment_id: environment.id });
|
||||
onSuccess: async (environmentId) => {
|
||||
setWorkspaceSearchParams({ environment_id: environmentId });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import type { GrpcRequest } from '@yaakapp-internal/models';
|
||||
import { createWorkspaceModel } from '@yaakapp-internal/models';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveRequest } from './useActiveRequest';
|
||||
import { activeWorkspaceAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { router } from '../lib/router';
|
||||
import { activeRequestAtom } from './useActiveRequest';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useCreateGrpcRequest() {
|
||||
return useFastMutation<
|
||||
GrpcRequest,
|
||||
string,
|
||||
unknown,
|
||||
Partial<Pick<GrpcRequest, 'name' | 'sortPriority' | 'folderId'>>
|
||||
>({
|
||||
mutationKey: ['create_grpc_request'],
|
||||
mutationFn: async (patch) => {
|
||||
const workspace = jotaiStore.get(activeWorkspaceAtom);
|
||||
if (workspace === null) {
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (workspaceId === null) {
|
||||
throw new Error("Cannot create grpc request when there's no active workspace");
|
||||
}
|
||||
const activeRequest = getActiveRequest();
|
||||
const activeRequest = jotaiStore.get(activeRequestAtom);
|
||||
if (patch.sortPriority === undefined) {
|
||||
if (activeRequest != null) {
|
||||
// Place above currently active request
|
||||
@@ -29,17 +29,16 @@ export function useCreateGrpcRequest() {
|
||||
}
|
||||
}
|
||||
patch.folderId = patch.folderId || activeRequest?.folderId;
|
||||
return invokeCmd<GrpcRequest>('cmd_create_grpc_request', {
|
||||
workspaceId: workspace.id,
|
||||
name: '',
|
||||
...patch,
|
||||
});
|
||||
return createWorkspaceModel({ model: 'grpc_request', workspaceId, ...patch });
|
||||
},
|
||||
onSuccess: async (request) => {
|
||||
onSuccess: async (requestId) => {
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (workspaceId == null) return;
|
||||
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: request.workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
||||
params: { workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: requestId }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { createWorkspaceModel } from '@yaakapp-internal/models';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveRequest } from './useActiveRequest';
|
||||
import { getActiveWorkspaceId } from './useActiveWorkspace';
|
||||
import { activeRequestAtom } from './useActiveRequest';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useCreateHttpRequest() {
|
||||
return useFastMutation<HttpRequest, unknown, Partial<HttpRequest>>({
|
||||
return useFastMutation<string, unknown, Partial<HttpRequest>>({
|
||||
mutationKey: ['create_http_request'],
|
||||
mutationFn: async (patch = {}) => {
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (workspaceId == null) {
|
||||
throw new Error("Cannot create request when there's no active workspace");
|
||||
}
|
||||
|
||||
const activeRequest = getActiveRequest();
|
||||
const activeRequest = jotaiStore.get(activeRequestAtom);
|
||||
if (patch.sortPriority === undefined) {
|
||||
if (activeRequest != null) {
|
||||
// Place above currently active request
|
||||
@@ -25,15 +26,15 @@ export function useCreateHttpRequest() {
|
||||
}
|
||||
}
|
||||
patch.folderId = patch.folderId || activeRequest?.folderId;
|
||||
return invokeCmd<HttpRequest>('cmd_upsert_http_request', {
|
||||
request: { workspaceId, ...patch },
|
||||
});
|
||||
return createWorkspaceModel({ model: 'http_request', workspaceId, ...patch });
|
||||
},
|
||||
onSuccess: async (request) => {
|
||||
onSuccess: async (requestId) => {
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
if (workspaceId == null) return;
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: request.workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
||||
params: { workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: requestId }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { showConfirmDelete } from '../lib/confirm';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveWorkspace } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteActiveWorkspace() {
|
||||
return useFastMutation<Workspace | null, string>({
|
||||
mutationKey: ['delete_workspace'],
|
||||
mutationFn: async () => {
|
||||
const workspace = getActiveWorkspace();
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'delete-workspace',
|
||||
title: 'Delete Workspace',
|
||||
description: (
|
||||
<>
|
||||
Permanently delete <InlineCode>{workspace?.name}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
if (!confirmed) return null;
|
||||
return invokeCmd('cmd_delete_workspace', { workspaceId: workspace?.id });
|
||||
},
|
||||
onSuccess: async (workspace) => {
|
||||
if (workspace === null) return;
|
||||
await router.navigate({ to: '/workspaces' });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { GrpcRequest } from '@yaakapp-internal/models';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { showConfirmDelete } from '../lib/confirm';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteAnyGrpcRequest() {
|
||||
return useFastMutation<GrpcRequest | null, string, GrpcRequest>({
|
||||
mutationKey: ['delete_any_grpc_request'],
|
||||
mutationFn: async (request) => {
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'delete-grpc-request',
|
||||
title: 'Delete Request',
|
||||
description: (
|
||||
<>
|
||||
Permanently delete <InlineCode>{resolvedModelName(request)}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return null;
|
||||
}
|
||||
return invokeCmd('cmd_delete_grpc_request', { requestId: request.id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { showConfirmDelete } from '../lib/confirm';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteAnyHttpRequest() {
|
||||
return useFastMutation<HttpRequest | null, string, HttpRequest>({
|
||||
mutationKey: ['delete_any_http_request'],
|
||||
mutationFn: async (request) => {
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'delete-request',
|
||||
title: 'Delete Request',
|
||||
description: (
|
||||
<>
|
||||
Permanently delete <InlineCode>{resolvedModelName(request)}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return null;
|
||||
}
|
||||
return invokeCmd<HttpRequest>('cmd_delete_http_request', { requestId: request.id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { deleteWebsocketRequest } from '../commands/deleteWebsocketRequest';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { useDeleteAnyGrpcRequest } from './useDeleteAnyGrpcRequest';
|
||||
import { useDeleteAnyHttpRequest } from './useDeleteAnyHttpRequest';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { requestsAtom } from './useRequests';
|
||||
|
||||
export function useDeleteAnyRequest() {
|
||||
const deleteAnyHttpRequest = useDeleteAnyHttpRequest();
|
||||
const deleteAnyGrpcRequest = useDeleteAnyGrpcRequest();
|
||||
|
||||
return useFastMutation<void, string, string>({
|
||||
mutationKey: ['delete_request'],
|
||||
mutationFn: async (id) => {
|
||||
if (id == null) return;
|
||||
const request = jotaiStore.get(requestsAtom).find((r) => r.id === id);
|
||||
|
||||
if (request?.model === 'websocket_request') {
|
||||
deleteWebsocketRequest.mutate(request);
|
||||
} else if (request?.model === 'http_request') {
|
||||
deleteAnyHttpRequest.mutate(request);
|
||||
} else if (request?.model === 'grpc_request') {
|
||||
deleteAnyGrpcRequest.mutate(request);
|
||||
} else {
|
||||
console.log('Failed to delete request', id, request);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { CookieJar } from '@yaakapp-internal/models';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { showConfirmDelete } from '../lib/confirm';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteCookieJar(cookieJar: CookieJar | null) {
|
||||
return useFastMutation<CookieJar | null, string>({
|
||||
mutationKey: ['delete_cookie_jar', cookieJar?.id],
|
||||
mutationFn: async () => {
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'delete-cookie-jar',
|
||||
title: 'Delete CookieJar',
|
||||
description: (
|
||||
<>
|
||||
Permanently delete <InlineCode>{cookieJar?.name}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
if (!confirmed) return null;
|
||||
return invokeCmd('cmd_delete_cookie_jar', { cookieJarId: cookieJar?.id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { Environment } from '@yaakapp-internal/models';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { showConfirmDelete } from '../lib/confirm';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteEnvironment(environment: Environment | null) {
|
||||
return useFastMutation<Environment | null, string>({
|
||||
mutationKey: ['delete_environment', environment?.id],
|
||||
mutationFn: async () => {
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'delete-environment',
|
||||
title: 'Delete Environment',
|
||||
description: (
|
||||
<>
|
||||
Permanently delete <InlineCode>{environment?.name}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
if (!confirmed) return null;
|
||||
return invokeCmd('cmd_delete_environment', { environmentId: environment?.id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { Folder } from '@yaakapp-internal/models';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { showConfirmDelete } from '../lib/confirm';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { getFolder } from './useFolders';
|
||||
|
||||
export function useDeleteFolder(id: string | null) {
|
||||
return useFastMutation<Folder | null, string>({
|
||||
mutationKey: ['delete_folder', id],
|
||||
mutationFn: async () => {
|
||||
const folder = getFolder(id);
|
||||
const confirmed = await showConfirmDelete({
|
||||
id: 'delete-folder',
|
||||
title: 'Delete Folder',
|
||||
description: (
|
||||
<>
|
||||
Permanently delete <InlineCode>{folder?.name}</InlineCode> and everything in it?
|
||||
</>
|
||||
),
|
||||
});
|
||||
if (!confirmed) return null;
|
||||
return invokeCmd('cmd_delete_folder', { folderId: id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { GrpcConnection } from '@yaakapp-internal/models';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteGrpcConnection(id: string | null) {
|
||||
return useFastMutation<GrpcConnection>({
|
||||
mutationKey: ['delete_grpc_connection', id],
|
||||
mutationFn: async () => {
|
||||
return await invokeCmd('cmd_delete_grpc_connection', { id: id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,18 +1,12 @@
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { grpcConnectionsAtom } from './useGrpcConnections';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteGrpcConnections(requestId?: string) {
|
||||
const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
|
||||
return useFastMutation({
|
||||
mutationKey: ['delete_grpc_connections', requestId],
|
||||
mutationFn: async () => {
|
||||
if (requestId === undefined) return;
|
||||
await invokeCmd('cmd_delete_all_grpc_connections', { requestId });
|
||||
},
|
||||
onSuccess: () => {
|
||||
setGrpcConnections((all) => all.filter((r) => r.requestId !== requestId));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteHttpResponse(id: string | null) {
|
||||
return useFastMutation<HttpResponse>({
|
||||
mutationKey: ['delete_http_response', id],
|
||||
mutationFn: async () => {
|
||||
return await invokeCmd('cmd_delete_http_response', { id: id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,18 +1,12 @@
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { httpResponsesAtom } from './useHttpResponses';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDeleteHttpResponses(requestId?: string) {
|
||||
const setHttpResponses = useSetAtom(httpResponsesAtom);
|
||||
return useFastMutation({
|
||||
mutationKey: ['delete_http_responses', requestId],
|
||||
mutationFn: async () => {
|
||||
if (requestId === undefined) return;
|
||||
await invokeCmd('cmd_delete_all_http_responses', { requestId });
|
||||
},
|
||||
onSuccess: () => {
|
||||
setHttpResponses((all) => all.filter((r) => r.requestId !== requestId));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { useSetAtom } from 'jotai/index';
|
||||
import {
|
||||
grpcConnectionsAtom,
|
||||
httpResponsesAtom,
|
||||
websocketConnectionsAtom,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import { showAlert } from '../lib/alert';
|
||||
import { showConfirmDelete } from '../lib/confirm';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveWorkspaceId } from './useActiveWorkspace';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { useGrpcConnections } from './useGrpcConnections';
|
||||
import { httpResponsesAtom, useHttpResponses } from './useHttpResponses';
|
||||
import { useWebsocketConnections } from './useWebsocketConnections';
|
||||
|
||||
export function useDeleteSendHistory() {
|
||||
const setHttpResponses = useSetAtom(httpResponsesAtom);
|
||||
const httpResponses = useHttpResponses();
|
||||
const grpcConnections = useGrpcConnections();
|
||||
const websocketConnections = useWebsocketConnections();
|
||||
const httpResponses = useAtomValue(httpResponsesAtom);
|
||||
const grpcConnections = useAtomValue(grpcConnectionsAtom);
|
||||
const websocketConnections = useAtomValue(websocketConnectionsAtom);
|
||||
|
||||
const labels = [
|
||||
httpResponses.length > 0 ? pluralizeCount('Http Response', httpResponses.length) : null,
|
||||
grpcConnections.length > 0 ? pluralizeCount('Grpc Connection', grpcConnections.length) : null,
|
||||
@@ -41,14 +44,9 @@ export function useDeleteSendHistory() {
|
||||
});
|
||||
if (!confirmed) return false;
|
||||
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
await invokeCmd('cmd_delete_send_history', { workspaceId });
|
||||
return true;
|
||||
},
|
||||
onSuccess: async (confirmed) => {
|
||||
if (!confirmed) return;
|
||||
const activeWorkspaceId = getActiveWorkspaceId();
|
||||
setHttpResponses((all) => all.filter((r) => r.workspaceId !== activeWorkspaceId));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export function useDuplicateFolder(id: string) {
|
||||
return useFastMutation<void, string>({
|
||||
mutationKey: ['duplicate_folder', id],
|
||||
mutationFn: () => invokeCmd('cmd_duplicate_folder', { id }),
|
||||
});
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { GrpcRequest } from '@yaakapp-internal/models';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { getGrpcProtoFiles, setGrpcProtoFiles } from './useGrpcProtoFiles';
|
||||
|
||||
export function useDuplicateGrpcRequest({
|
||||
id,
|
||||
navigateAfter,
|
||||
}: {
|
||||
id: string | null;
|
||||
navigateAfter: boolean;
|
||||
}) {
|
||||
return useFastMutation<GrpcRequest, string>({
|
||||
mutationKey: ['duplicate_grpc_request', id],
|
||||
mutationFn: async () => {
|
||||
if (id === null) throw new Error("Can't duplicate a null grpc request");
|
||||
return invokeCmd('cmd_duplicate_grpc_request', { id });
|
||||
},
|
||||
onSuccess: async (request) => {
|
||||
if (id == null) return;
|
||||
|
||||
// Also copy proto files to new request
|
||||
const protoFiles = await getGrpcProtoFiles(id);
|
||||
await setGrpcProtoFiles(request.id, protoFiles);
|
||||
|
||||
if (navigateAfter) {
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: request.workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { router } from '../lib/router';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
|
||||
export function useDuplicateHttpRequest({
|
||||
id,
|
||||
navigateAfter,
|
||||
}: {
|
||||
id: string | null;
|
||||
navigateAfter: boolean;
|
||||
}) {
|
||||
return useFastMutation<HttpRequest, string>({
|
||||
mutationKey: ['duplicate_http_request', id],
|
||||
mutationFn: async () => {
|
||||
if (id === null) throw new Error("Can't duplicate a null request");
|
||||
return invokeCmd('cmd_duplicate_http_request', { id });
|
||||
},
|
||||
onSuccess: async (request) => {
|
||||
if (navigateAfter) {
|
||||
await router.navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: request.workspaceId },
|
||||
search: (prev) => ({ ...prev, request_id: request.id }),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { Environment } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { atom } from 'jotai/index';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
|
||||
export const environmentsAtom = atom<Environment[]>([]);
|
||||
|
||||
export const sortedEnvironmentsAtom = atom((get) =>
|
||||
get(environmentsAtom).sort((a, b) => a.name.localeCompare(b.name)),
|
||||
);
|
||||
|
||||
export const environmentsBreakdownAtom = atom<{
|
||||
baseEnvironment: Environment | null;
|
||||
allEnvironments: Environment[];
|
||||
subEnvironments: Environment[];
|
||||
}>((get) => {
|
||||
const allEnvironments = get(sortedEnvironmentsAtom);
|
||||
const baseEnvironment = allEnvironments.find((e) => e.environmentId == null) ?? null;
|
||||
const subEnvironments =
|
||||
allEnvironments.filter((e) => e.environmentId === (baseEnvironment?.id ?? 'n/a')) ?? [];
|
||||
return { baseEnvironment, subEnvironments, allEnvironments } as const;
|
||||
});
|
||||
|
||||
export function useEnvironments() {
|
||||
return useAtomValue(environmentsBreakdownAtom);
|
||||
}
|
||||
|
||||
export function getEnvironment(id: string | null) {
|
||||
return jotaiStore.get(environmentsAtom).find((e) => e.id === id) ?? null;
|
||||
}
|
||||
13
src-web/hooks/useEnvironmentsBreakdown.ts
Normal file
13
src-web/hooks/useEnvironmentsBreakdown.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { environmentsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai/index';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function useEnvironmentsBreakdown() {
|
||||
const allEnvironments = useAtomValue(environmentsAtom);
|
||||
return useMemo(() => {
|
||||
const baseEnvironment = allEnvironments.find((e) => e.environmentId == null) ?? null;
|
||||
const subEnvironments =
|
||||
allEnvironments.filter((e) => e.environmentId === (baseEnvironment?.id ?? 'n/a')) ?? [];
|
||||
return { allEnvironments, baseEnvironment, subEnvironments };
|
||||
}, [allEnvironments]);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { ExportDataDialog } from '../components/ExportDataDialog';
|
||||
import { showAlert } from '../lib/alert';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { showToast } from '../lib/toast';
|
||||
import { getActiveWorkspace } from './useActiveWorkspace';
|
||||
import { activeWorkspaceAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { workspacesAtom } from './useWorkspaces';
|
||||
|
||||
export function useExportData() {
|
||||
return useFastMutation({
|
||||
@@ -14,7 +14,7 @@ export function useExportData() {
|
||||
showAlert({ id: 'export-failed', title: 'Export Failed', body: err });
|
||||
},
|
||||
mutationFn: async () => {
|
||||
const activeWorkspace = getActiveWorkspace();
|
||||
const activeWorkspace = jotaiStore.get(activeWorkspaceAtom);
|
||||
const workspaces = jotaiStore.get(workspacesAtom);
|
||||
|
||||
if (activeWorkspace == null || workspaces.length === 0) return;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { activeWorkspaceAtom } from './useActiveWorkspace';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
|
||||
export function useFloatingSidebarHidden() {
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspace = useAtomValue(activeWorkspaceAtom);
|
||||
const { set, value } = useKeyValue<boolean>({
|
||||
namespace: 'no_sync',
|
||||
key: ['floating_sidebar_hidden', activeWorkspace?.id ?? 'n/a'],
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { Folder } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { atom } from 'jotai/index';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
|
||||
export const foldersAtom = atom<Folder[]>([]);
|
||||
|
||||
export function useFolders() {
|
||||
return useAtomValue(foldersAtom);
|
||||
}
|
||||
|
||||
export function getFolder(id: string | null) {
|
||||
return jotaiStore.get(foldersAtom).find((v) => v.id === id) ?? null;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { GrpcConnection } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai/index';
|
||||
|
||||
export const grpcConnectionsAtom = atom<GrpcConnection[]>([]);
|
||||
|
||||
export function useGrpcConnections() {
|
||||
return useAtomValue(grpcConnectionsAtom);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { GrpcEvent } from '@yaakapp-internal/models';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export function grpcEventsQueryKey({ connectionId }: { connectionId: string }) {
|
||||
return ['grpc_events', { connectionId }];
|
||||
}
|
||||
|
||||
export function useGrpcEvents(connectionId: string | null) {
|
||||
return (
|
||||
useQuery<GrpcEvent[]>({
|
||||
enabled: connectionId !== null,
|
||||
initialData: [],
|
||||
queryKey: grpcEventsQueryKey({ connectionId: connectionId ?? 'n/a' }),
|
||||
queryFn: async () => {
|
||||
return (await invokeCmd('cmd_list_grpc_events', {
|
||||
connectionId,
|
||||
limit: 200,
|
||||
})) as GrpcEvent[];
|
||||
},
|
||||
}).data ?? []
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getKeyValue, setKeyValue } from '../lib/keyValueStore';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
|
||||
export function protoFilesArgs(requestId: string | null) {
|
||||
@@ -11,11 +10,3 @@ export function protoFilesArgs(requestId: string | null) {
|
||||
export function useGrpcProtoFiles(activeRequestId: string | null) {
|
||||
return useKeyValue<string[]>({ ...protoFilesArgs(activeRequestId), fallback: [] });
|
||||
}
|
||||
|
||||
export async function getGrpcProtoFiles(requestId: string) {
|
||||
return getKeyValue<string[]>({ ...protoFilesArgs(requestId), fallback: [] });
|
||||
}
|
||||
|
||||
export async function setGrpcProtoFiles(requestId: string, protoFiles: string[]) {
|
||||
return setKeyValue<string[]>({ ...protoFilesArgs(requestId), value: protoFiles });
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { GrpcRequest } from '@yaakapp-internal/models';
|
||||
import { useGrpcRequests } from './useGrpcRequests';
|
||||
|
||||
export function useGrpcRequest(id: string | null): GrpcRequest | null {
|
||||
const requests = useGrpcRequests();
|
||||
return requests.find((r) => r.id === id) ?? null;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { GrpcRequest } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
|
||||
export const grpcRequestsAtom = atom<GrpcRequest[]>([]);
|
||||
|
||||
export function useGrpcRequests() {
|
||||
return useAtomValue(grpcRequestsAtom);
|
||||
}
|
||||
|
||||
export function getGrpcRequest(id: string) {
|
||||
return jotaiStore.get(grpcRequestsAtom).find((r) => r.id === id) ?? null;
|
||||
}
|
||||
@@ -15,13 +15,13 @@ export type HotkeyAction =
|
||||
| 'grpc_request.send'
|
||||
| 'hotkeys.showHelp'
|
||||
| 'http_request.create'
|
||||
| 'http_request.delete'
|
||||
| 'http_request.duplicate'
|
||||
| 'http_request.send'
|
||||
| 'request_switcher.next'
|
||||
| 'request_switcher.prev'
|
||||
| 'request_switcher.toggle'
|
||||
| 'settings.show'
|
||||
| 'sidebar.delete_selected_item'
|
||||
| 'sidebar.focus'
|
||||
| 'url_bar.focus'
|
||||
| 'workspace_settings.show';
|
||||
@@ -35,13 +35,13 @@ const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'grpc_request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
|
||||
'hotkeys.showHelp': ['CmdCtrl+Shift+/', 'CmdCtrl+Shift+?'], // when shift is pressed, it might be a question mark
|
||||
'http_request.create': ['CmdCtrl+n'],
|
||||
'http_request.delete': ['Backspace'],
|
||||
'http_request.duplicate': ['CmdCtrl+d'],
|
||||
'http_request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
|
||||
'request_switcher.next': ['Control+Shift+Tab'],
|
||||
'request_switcher.prev': ['Control+Tab'],
|
||||
'request_switcher.toggle': ['CmdCtrl+p'],
|
||||
'settings.show': ['CmdCtrl+,'],
|
||||
'sidebar.delete_selected_item': ['Backspace'],
|
||||
'sidebar.focus': ['CmdCtrl+b'],
|
||||
'url_bar.focus': ['CmdCtrl+l'],
|
||||
'workspace_settings.show': ['CmdCtrl+;'],
|
||||
@@ -56,13 +56,13 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||
'grpc_request.send': 'Send Message',
|
||||
'hotkeys.showHelp': 'Show Keyboard Shortcuts',
|
||||
'http_request.create': 'New Request',
|
||||
'http_request.delete': 'Delete Request',
|
||||
'http_request.duplicate': 'Duplicate Request',
|
||||
'http_request.send': 'Send Request',
|
||||
'request_switcher.next': 'Go To Previous Request',
|
||||
'request_switcher.prev': 'Go To Next Request',
|
||||
'request_switcher.toggle': 'Toggle Request Switcher',
|
||||
'settings.show': 'Open Settings',
|
||||
'sidebar.delete_selected_item': 'Delete Request',
|
||||
'sidebar.focus': 'Focus or Toggle Sidebar',
|
||||
'url_bar.focus': 'Focus URL',
|
||||
'workspace_settings.show': 'Open Workspace Settings',
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { httpResponsesAtom } from '@yaakapp-internal/models';
|
||||
import type { GetHttpAuthenticationConfigResponse, JsonPrimitive } from '@yaakapp-internal/plugins';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { md5 } from 'js-md5';
|
||||
import { useState } from 'react';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useHttpResponses } from './useHttpResponses';
|
||||
|
||||
export function useHttpAuthenticationConfig(
|
||||
authName: string | null,
|
||||
values: Record<string, JsonPrimitive>,
|
||||
requestId: string,
|
||||
) {
|
||||
const responses = useHttpResponses();
|
||||
const responses = useAtomValue(httpResponsesAtom);
|
||||
const [forceRefreshCounter, setForceRefreshCounter] = useState<number>(0);
|
||||
|
||||
// Some auth handlers like OAuth 2.0 show the current token after a successful request. To
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import { useHttpRequests } from './useHttpRequests';
|
||||
|
||||
export function useHttpRequest(id: string | null): HttpRequest | null {
|
||||
const requests = useHttpRequests();
|
||||
return requests.find((r) => r.id === id) ?? null;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import type {HttpRequest} from '@yaakapp-internal/models';
|
||||
import {atom, useAtomValue} from 'jotai';
|
||||
import {jotaiStore} from "../lib/jotai";
|
||||
|
||||
export const httpRequestsAtom = atom<HttpRequest[]>([]);
|
||||
|
||||
export function useHttpRequests() {
|
||||
return useAtomValue(httpRequestsAtom);
|
||||
}
|
||||
|
||||
export function getHttpRequest(id: string) {
|
||||
return jotaiStore.get(httpRequestsAtom).find(r => r.id === id) ?? null;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { atom } from 'jotai/index';
|
||||
|
||||
export const httpResponsesAtom = atom<HttpResponse[]>([]);
|
||||
|
||||
export function useHttpResponses() {
|
||||
return useAtomValue(httpResponsesAtom);
|
||||
}
|
||||
@@ -1,17 +1,13 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import type { HttpRequest} from '@yaakapp-internal/models';
|
||||
import { createWorkspaceModel, patchModelById } from '@yaakapp-internal/models';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { getActiveWorkspaceId } from './useActiveWorkspace';
|
||||
import { useCreateHttpRequest } from './useCreateHttpRequest';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { useRequestUpdateKey } from './useRequestUpdateKey';
|
||||
import { showToast } from '../lib/toast';
|
||||
import { useUpdateAnyHttpRequest } from './useUpdateAnyHttpRequest';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
import { useFastMutation } from './useFastMutation';
|
||||
import { wasUpdatedExternally } from './useRequestUpdateKey';
|
||||
|
||||
export function useImportCurl() {
|
||||
const updateRequest = useUpdateAnyHttpRequest();
|
||||
const createRequest = useCreateHttpRequest();
|
||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||
|
||||
return useFastMutation({
|
||||
mutationKey: ['import_curl'],
|
||||
mutationFn: async ({
|
||||
@@ -21,8 +17,8 @@ export function useImportCurl() {
|
||||
overwriteRequestId?: string;
|
||||
command: string;
|
||||
}) => {
|
||||
const workspaceId = getActiveWorkspaceId();
|
||||
const request: HttpRequest = await invokeCmd('cmd_curl_to_request', {
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
const importedRequest: HttpRequest = await invokeCmd('cmd_curl_to_request', {
|
||||
command,
|
||||
workspaceId,
|
||||
});
|
||||
@@ -30,21 +26,18 @@ export function useImportCurl() {
|
||||
let verb;
|
||||
if (overwriteRequestId == null) {
|
||||
verb = 'Created';
|
||||
await createRequest.mutateAsync(request);
|
||||
await createWorkspaceModel(importedRequest);
|
||||
} else {
|
||||
verb = 'Updated';
|
||||
await updateRequest.mutateAsync({
|
||||
id: overwriteRequestId,
|
||||
update: (r: HttpRequest) => ({
|
||||
...request,
|
||||
id: r.id,
|
||||
createdAt: r.createdAt,
|
||||
workspaceId: r.workspaceId,
|
||||
folderId: r.folderId,
|
||||
name: r.name,
|
||||
sortPriority: r.sortPriority,
|
||||
}),
|
||||
});
|
||||
await patchModelById(importedRequest.model, overwriteRequestId, (r: HttpRequest) => ({
|
||||
...importedRequest,
|
||||
id: r.id,
|
||||
createdAt: r.createdAt,
|
||||
workspaceId: r.workspaceId,
|
||||
folderId: r.folderId,
|
||||
name: r.name,
|
||||
sortPriority: r.sortPriority,
|
||||
}));
|
||||
|
||||
setTimeout(() => wasUpdatedExternally(overwriteRequestId), 100);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user