mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-12 02:26:30 -05:00
Rename workspace
This commit is contained in:
@@ -506,6 +506,16 @@
|
||||
},
|
||||
"query": "\n INSERT INTO key_values (namespace, key, value)\n VALUES (?, ?, ?) ON CONFLICT DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n value = excluded.value\n "
|
||||
},
|
||||
"e0f41023d877d94b7609ce910a71bd89c4827a558654b8ae14d85e6ba86990cf": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "\n UPDATE workspaces SET (name, updated_at) =\n (?, CURRENT_TIMESTAMP) WHERE id = ?;\n "
|
||||
},
|
||||
"e3ade0a69348d512e47e964bded9d7d890b92fdc1e01c6c22fa5e91f943639f2": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
||||
@@ -362,6 +362,21 @@ async fn duplicate_request(
|
||||
emit_and_return(&window, "updated_model", request)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn update_workspace(
|
||||
workspace: models::Workspace,
|
||||
window: Window<Wry>,
|
||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||
) -> Result<models::Workspace, String> {
|
||||
let pool = &*db_instance.lock().await;
|
||||
|
||||
let updated_workspace = models::update_workspace(workspace, pool)
|
||||
.await
|
||||
.expect("Failed to update request");
|
||||
|
||||
emit_and_return(&window, "updated_model", updated_workspace)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn update_request(
|
||||
request: models::HttpRequest,
|
||||
@@ -436,6 +451,17 @@ async fn get_request(
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_workspace(
|
||||
id: &str,
|
||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||
) -> Result<models::Workspace, String> {
|
||||
let pool = &*db_instance.lock().await;
|
||||
models::get_workspace(id, pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn responses(
|
||||
request_id: &str,
|
||||
@@ -546,8 +572,10 @@ fn main() {
|
||||
send_ephemeral_request,
|
||||
duplicate_request,
|
||||
create_request,
|
||||
get_workspace,
|
||||
create_workspace,
|
||||
delete_workspace,
|
||||
update_workspace,
|
||||
update_request,
|
||||
delete_request,
|
||||
responses,
|
||||
|
||||
@@ -423,6 +423,24 @@ pub async fn update_response_if_id(
|
||||
return update_response(response, pool).await;
|
||||
}
|
||||
|
||||
pub async fn update_workspace(
|
||||
workspace: Workspace,
|
||||
pool: &Pool<Sqlite>,
|
||||
) -> Result<Workspace, sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE workspaces SET (name, updated_at) =
|
||||
(?, CURRENT_TIMESTAMP) WHERE id = ?;
|
||||
"#,
|
||||
workspace.name,
|
||||
workspace.id,
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.expect("Failed to update workspace");
|
||||
get_workspace(&workspace.id, pool).await
|
||||
}
|
||||
|
||||
pub async fn update_response(
|
||||
response: HttpResponse,
|
||||
pool: &Pool<Sqlite>,
|
||||
|
||||
@@ -45,13 +45,20 @@ export const DialogProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<DialogContext.Provider value={state}>
|
||||
{children}
|
||||
{dialogs.map(({ id, render, ...props }) => (
|
||||
<Dialog open key={id} onClose={() => actions.hide(id)} {...props}>
|
||||
{render({ hide: () => actions.hide(id) })}
|
||||
</Dialog>
|
||||
{dialogs.map((props: DialogEntry) => (
|
||||
<DialogInstance key={props.id} {...props} />
|
||||
))}
|
||||
</DialogContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
function DialogInstance({ id, render, ...props }: DialogEntry) {
|
||||
const { actions } = useContext(DialogContext);
|
||||
return (
|
||||
<Dialog open onClose={() => actions.hide(id)} {...props}>
|
||||
{render({ hide: () => actions.hide(id) })}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export const useDialog = () => useContext(DialogContext).actions;
|
||||
|
||||
@@ -111,7 +111,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
{activeResponse && (
|
||||
<HStack alignItems="center" className="w-full">
|
||||
<div className="whitespace-nowrap px-3">
|
||||
<StatusTag response={activeResponse} />
|
||||
<StatusTag showReason response={activeResponse} />
|
||||
{activeResponse.elapsed > 0 && <> • {activeResponse.elapsed}ms</>}
|
||||
{activeResponse.body.length > 0 && (
|
||||
<> • {(activeResponse.body.length / 1000).toFixed(1)} KB</>
|
||||
@@ -165,7 +165,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
</TabContent>
|
||||
<TabContent value="body">
|
||||
{!activeResponse.body ? (
|
||||
<EmptyStateText>No Response</EmptyStateText>
|
||||
<EmptyStateText>Empty Body</EmptyStateText>
|
||||
) : viewMode === 'pretty' && contentType.includes('html') ? (
|
||||
<Webview
|
||||
body={activeResponse.body}
|
||||
|
||||
@@ -3,12 +3,15 @@ import { memo, useMemo } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useRoutes } from '../hooks/useRoutes';
|
||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
@@ -19,7 +22,9 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
||||
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
|
||||
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
||||
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
||||
const prompt = usePrompt();
|
||||
const routes = useRoutes();
|
||||
|
||||
const items: DropdownItem[] = useMemo(() => {
|
||||
@@ -32,31 +37,59 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
|
||||
},
|
||||
}));
|
||||
|
||||
const activeWorkspaceItems: DropdownItem[] =
|
||||
workspaces.length <= 1
|
||||
? []
|
||||
: [
|
||||
...workspaceItems,
|
||||
{
|
||||
type: 'separator',
|
||||
label: activeWorkspace?.name,
|
||||
},
|
||||
];
|
||||
|
||||
return [
|
||||
...workspaceItems,
|
||||
...activeWorkspaceItems,
|
||||
{
|
||||
type: 'separator',
|
||||
label: 'Actions',
|
||||
label: 'Rename',
|
||||
leftSlot: <Icon icon="pencil" />,
|
||||
onSelect: async () => {
|
||||
const name = await prompt({
|
||||
title: 'Rename Workspace',
|
||||
description: (
|
||||
<>
|
||||
Enter a new name for <InlineCode>{activeWorkspace?.name}</InlineCode>
|
||||
</>
|
||||
),
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
defaultValue: activeWorkspace?.name,
|
||||
});
|
||||
updateWorkspace.mutate({ name });
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'New Workspace',
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: () => createWorkspace.mutate({ name: 'New Workspace' }),
|
||||
},
|
||||
{
|
||||
label: 'Delete Workspace',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: deleteWorkspace.mutate,
|
||||
variant: 'danger',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Create Workspace',
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: () => createWorkspace.mutate({ name: 'Workspace' }),
|
||||
},
|
||||
];
|
||||
}, [
|
||||
workspaces,
|
||||
deleteWorkspace.mutate,
|
||||
activeWorkspaceId,
|
||||
routes,
|
||||
createWorkspace,
|
||||
confirm,
|
||||
prompt,
|
||||
activeWorkspace?.name,
|
||||
deleteWorkspace,
|
||||
updateWorkspace,
|
||||
createWorkspace,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -36,6 +36,7 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
|
||||
children,
|
||||
forDropdown,
|
||||
color,
|
||||
type = 'button',
|
||||
justify = 'center',
|
||||
size = 'md',
|
||||
...props
|
||||
@@ -68,7 +69,7 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<button ref={ref} className={classes} {...props}>
|
||||
<button ref={ref} type={type} className={classes} {...props}>
|
||||
{isLoading && <Icon icon="update" size={size} className="animate-spin mr-1" />}
|
||||
{children}
|
||||
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
|
||||
|
||||
@@ -76,7 +76,7 @@ export function Dialog({
|
||||
{title}
|
||||
</Heading>
|
||||
{description && <p id={descriptionId}>{description}</p>}
|
||||
<div className="mt-6">{children}</div>
|
||||
<div className="mt-4">{children}</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,15 @@ import classnames from 'classnames';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { CSSProperties, HTMLAttributes, MouseEvent, ReactElement, ReactNode } from 'react';
|
||||
import { Children, cloneElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, {
|
||||
Children,
|
||||
cloneElement,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useKeyPressEvent } from 'react-use';
|
||||
import { Portal } from '../Portal';
|
||||
import { Separator } from './Separator';
|
||||
@@ -17,6 +25,7 @@ export type DropdownItem =
|
||||
| {
|
||||
type?: 'default';
|
||||
label: string;
|
||||
variant?: 'danger';
|
||||
disabled?: boolean;
|
||||
hidden?: boolean;
|
||||
leftSlot?: ReactNode;
|
||||
@@ -94,6 +103,17 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
setMenuStyles({ maxHeight: windowBox.height - menuBox.top - 5 });
|
||||
}, []);
|
||||
|
||||
// Close menu on space bar
|
||||
const handleMenuKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
[onClose],
|
||||
);
|
||||
|
||||
useKeyPressEvent('Escape', (e) => {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
@@ -181,6 +201,7 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
<div tabIndex={-1} aria-hidden className="fixed inset-0" onClick={onClose} />
|
||||
<motion.div
|
||||
tabIndex={0}
|
||||
onKeyDown={handleMenuKeyDown}
|
||||
initial={{ opacity: 0, y: -5, scale: 0.98 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
role="menu"
|
||||
@@ -268,6 +289,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
className,
|
||||
'min-w-[8rem] outline-none px-2 mx-1.5 h-7 flex items-center text-sm text-gray-700 whitespace-nowrap',
|
||||
'focus:bg-highlight focus:text-gray-900 rounded',
|
||||
item.variant === 'danger' && 'text-red-600',
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
MagnifyingGlassIcon,
|
||||
MoonIcon,
|
||||
PaperPlaneIcon,
|
||||
Pencil2Icon,
|
||||
PlusCircledIcon,
|
||||
PlusIcon,
|
||||
QuestionMarkIcon,
|
||||
@@ -65,6 +66,7 @@ const icons = {
|
||||
magnifyingGlass: MagnifyingGlassIcon,
|
||||
moon: MoonIcon,
|
||||
paperPlane: PaperPlaneIcon,
|
||||
pencil: Pencil2Icon,
|
||||
plus: PlusIcon,
|
||||
plusCircle: PlusCircledIcon,
|
||||
question: QuestionMarkIcon,
|
||||
|
||||
@@ -2,11 +2,12 @@ import classnames from 'classnames';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
|
||||
interface Props {
|
||||
response: Pick<HttpResponse, 'status' | 'error'>;
|
||||
response: Pick<HttpResponse, 'status' | 'statusReason' | 'error'>;
|
||||
className?: string;
|
||||
showReason?: boolean;
|
||||
}
|
||||
|
||||
export function StatusTag({ response, className }: Props) {
|
||||
export function StatusTag({ response, className, showReason }: Props) {
|
||||
const { status, error } = response;
|
||||
const label = error ? 'ERR' : status;
|
||||
return (
|
||||
@@ -22,7 +23,7 @@ export function StatusTag({ response, className }: Props) {
|
||||
status >= 500 && 'text-red-600',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{label} {showReason && response.statusReason && response.statusReason}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,17 +73,17 @@ export function Tabs({
|
||||
aria-label={label}
|
||||
className={classnames(
|
||||
tabListClassName,
|
||||
'flex items-center overflow-x-auto hide-scrollbars mt-1 mb-2',
|
||||
'flex items-center overflow-x-auto overflow-y-visible hide-scrollbars mt-1 mb-2',
|
||||
// Give space for button focus states within overflow boundary.
|
||||
'px-2 -mx-2',
|
||||
'-mx-5 pl-3 py-1',
|
||||
)}
|
||||
>
|
||||
<HStack space={1} className="flex-shrink-0">
|
||||
<HStack space={2} className="flex-shrink-0">
|
||||
{tabs.map((t) => {
|
||||
const isActive = t.value === value;
|
||||
const btnClassName = classnames(
|
||||
isActive ? '' : 'text-gray-600 hover:text-gray-800',
|
||||
'!px-0 mr-4 ml-[1px]',
|
||||
'!px-2 ml-[1px]',
|
||||
);
|
||||
|
||||
if ('options' in t) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from '../components/core/Button';
|
||||
import { HStack } from '../components/core/Stacks';
|
||||
|
||||
export interface ConfirmProps {
|
||||
hide: () => void;
|
||||
onHide: () => void;
|
||||
onResult: (result: boolean) => void;
|
||||
variant?: 'delete' | 'confirm';
|
||||
}
|
||||
@@ -18,7 +18,7 @@ const confirmButtonTexts: Record<NonNullable<ConfirmProps['variant']>, string> =
|
||||
confirm: 'Confirm',
|
||||
};
|
||||
|
||||
export function Confirm({ hide, onResult, variant = 'confirm' }: ConfirmProps) {
|
||||
export function Confirm({ onHide, onResult, variant = 'confirm' }: ConfirmProps) {
|
||||
const focusRef = (el: HTMLButtonElement | null) => {
|
||||
setTimeout(() => {
|
||||
el?.focus();
|
||||
@@ -27,16 +27,16 @@ export function Confirm({ hide, onResult, variant = 'confirm' }: ConfirmProps) {
|
||||
|
||||
const handleHide = () => {
|
||||
onResult(false);
|
||||
hide();
|
||||
onHide();
|
||||
};
|
||||
|
||||
const handleSuccess = () => {
|
||||
onResult(true);
|
||||
hide();
|
||||
onHide();
|
||||
};
|
||||
|
||||
return (
|
||||
<HStack space={2} justifyContent="end">
|
||||
<HStack space={2} justifyContent="end" className="mt-6">
|
||||
<Button className="focus" color="gray" onClick={handleHide}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
48
src-web/hooks/Prompt.tsx
Normal file
48
src-web/hooks/Prompt.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { FormEvent } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from '../components/core/Button';
|
||||
import type { InputProps } from '../components/core/Input';
|
||||
import { Input } from '../components/core/Input';
|
||||
import { HStack, VStack } from '../components/core/Stacks';
|
||||
|
||||
export interface PromptProps {
|
||||
onHide: () => void;
|
||||
onResult: (value: string) => void;
|
||||
label: InputProps['label'];
|
||||
name: InputProps['name'];
|
||||
defaultValue: InputProps['defaultValue'];
|
||||
}
|
||||
|
||||
export function Prompt({ onHide, label, name, defaultValue, onResult }: PromptProps) {
|
||||
const [value, setValue] = useState<string>(defaultValue ?? '');
|
||||
const handleSubmit = useCallback(
|
||||
(e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
onHide();
|
||||
onResult(value);
|
||||
},
|
||||
[onHide, onResult, value],
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<VStack space={6}>
|
||||
<Input
|
||||
hideLabel
|
||||
label={label}
|
||||
name={name}
|
||||
defaultValue={defaultValue}
|
||||
onChange={setValue}
|
||||
/>
|
||||
<HStack space={2} justifyContent="end">
|
||||
<Button className="focus" color="gray" onClick={onHide}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" className="focus" color="primary">
|
||||
Save
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export function useConfirm() {
|
||||
description,
|
||||
hideX: true,
|
||||
size: 'sm',
|
||||
render: ({ hide }) => Confirm({ hide, variant, onResult }),
|
||||
render: ({ hide }) => Confirm({ onHide: hide, variant, onResult }),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export function useDeleteRequest(id: string | null) {
|
||||
variant: 'delete',
|
||||
description: (
|
||||
<>
|
||||
Are you sure you want to delete <InlineCode>{request?.name}</InlineCode>?
|
||||
Permanently delete <InlineCode>{request?.name}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
|
||||
variant: 'delete',
|
||||
description: (
|
||||
<>
|
||||
Are you sure you want to delete <InlineCode>{workspace?.name}</InlineCode>?
|
||||
Permanently delete <InlineCode>{workspace?.name}</InlineCode>?
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
30
src-web/hooks/usePrompt.ts
Normal file
30
src-web/hooks/usePrompt.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { DialogProps } from '../components/core/Dialog';
|
||||
import { useDialog } from '../components/DialogContext';
|
||||
import type { PromptProps } from './Prompt';
|
||||
import { Prompt } from './Prompt';
|
||||
|
||||
export function usePrompt() {
|
||||
const dialog = useDialog();
|
||||
return ({
|
||||
title,
|
||||
description,
|
||||
name,
|
||||
label,
|
||||
defaultValue,
|
||||
}: {
|
||||
title: DialogProps['title'];
|
||||
description?: DialogProps['description'];
|
||||
name: PromptProps['name'];
|
||||
label: PromptProps['label'];
|
||||
defaultValue: PromptProps['defaultValue'];
|
||||
}) =>
|
||||
new Promise((onResult: PromptProps['onResult']) => {
|
||||
dialog.show({
|
||||
title,
|
||||
description,
|
||||
hideX: true,
|
||||
size: 'sm',
|
||||
render: ({ hide }) => Prompt({ onHide: hide, onResult, name, label, defaultValue }),
|
||||
});
|
||||
});
|
||||
}
|
||||
29
src-web/hooks/useUpdateWorkspace.ts
Normal file
29
src-web/hooks/useUpdateWorkspace.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { Workspace } from '../lib/models';
|
||||
import { getWorkspace } from '../lib/store';
|
||||
import { workspacesQueryKey } from './useWorkspaces';
|
||||
|
||||
export function useUpdateWorkspace(id: string | null) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<void, unknown, Partial<Workspace> | ((w: Workspace) => Workspace)>({
|
||||
mutationFn: async (v) => {
|
||||
const workspace = await getWorkspace(id);
|
||||
if (workspace == null) {
|
||||
throw new Error("Can't update a null workspace");
|
||||
}
|
||||
|
||||
const newWorkspace = typeof v === 'function' ? v(workspace) : { ...workspace, ...v };
|
||||
await invoke('update_workspace', { workspace: newWorkspace });
|
||||
},
|
||||
onMutate: async (v) => {
|
||||
const workspace = await getWorkspace(id);
|
||||
if (workspace === null) return;
|
||||
|
||||
const newWorkspace = typeof v === 'function' ? v(workspace) : { ...workspace, ...v };
|
||||
queryClient.setQueryData<Workspace[]>(workspacesQueryKey(workspace), (workspaces) =>
|
||||
(workspaces ?? []).map((w) => (w.id === newWorkspace.id ? newWorkspace : w)),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { HttpRequest } from './models';
|
||||
import type { HttpRequest, Workspace } from './models';
|
||||
|
||||
export async function getRequest(id: string | null): Promise<HttpRequest | null> {
|
||||
if (id === null) return null;
|
||||
@@ -9,3 +9,12 @@ export async function getRequest(id: string | null): Promise<HttpRequest | null>
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
export async function getWorkspace(id: string | null): Promise<Workspace | null> {
|
||||
if (id === null) return null;
|
||||
const workspace: Workspace = (await invoke('get_workspace', { id })) ?? null;
|
||||
if (workspace == null) {
|
||||
return null;
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user