mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-18 23:16:59 +01:00
Typesafe routing and CM line height issue
This commit is contained in:
@@ -313,14 +313,13 @@ async fn delete_request(
|
||||
app_handle: AppHandle<Wry>,
|
||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||
request_id: &str,
|
||||
) -> Result<models::HttpRequest, String> {
|
||||
) -> Result<(), String> {
|
||||
let pool = &*db_instance.lock().await;
|
||||
let req = models::delete_request(request_id, pool)
|
||||
.await
|
||||
.expect("Failed to delete request");
|
||||
app_handle.emit_all("deleted_request", request_id).unwrap();
|
||||
|
||||
Ok(req)
|
||||
app_handle.emit_all("deleted_model", req).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -357,12 +356,15 @@ async fn responses(
|
||||
#[tauri::command]
|
||||
async fn delete_response(
|
||||
id: &str,
|
||||
app_handle: AppHandle<Wry>,
|
||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||
) -> Result<(), String> {
|
||||
let pool = &*db_instance.lock().await;
|
||||
models::delete_response(id, pool)
|
||||
let response = models::delete_response(id, pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.expect("Failed to delete response");
|
||||
app_handle.emit_all("deleted_model", response).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -394,6 +396,20 @@ async fn workspaces(
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn delete_workspace(
|
||||
app_handle: AppHandle<Wry>,
|
||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||
id: &str,
|
||||
) -> Result<(), String> {
|
||||
let pool = &*db_instance.lock().await;
|
||||
let workspace = models::delete_workspace(id, pool)
|
||||
.await
|
||||
.expect("Failed to delete workspace");
|
||||
app_handle.emit_all("deleted_model", workspace).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
@@ -511,6 +527,7 @@ fn main() {
|
||||
send_request,
|
||||
create_request,
|
||||
create_workspace,
|
||||
delete_workspace,
|
||||
update_request,
|
||||
delete_request,
|
||||
responses,
|
||||
|
||||
@@ -142,6 +142,22 @@ pub async fn get_workspace(id: &str, pool: &Pool<Sqlite>) -> Result<Workspace, s
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_workspace(id: &str, pool: &Pool<Sqlite>) -> Result<Workspace, sqlx::Error> {
|
||||
let workspace = get_workspace(id, pool)
|
||||
.await
|
||||
.expect("Failed to get request to delete");
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM http_requests
|
||||
WHERE id = ?
|
||||
"#,
|
||||
id,
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
Ok(workspace)
|
||||
}
|
||||
|
||||
pub async fn create_workspace(
|
||||
name: &str,
|
||||
description: &str,
|
||||
@@ -395,7 +411,11 @@ pub async fn find_responses(
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_response(id: &str, pool: &Pool<Sqlite>) -> Result<(), sqlx::Error> {
|
||||
pub async fn delete_response(id: &str, pool: &Pool<Sqlite>) -> Result<HttpResponse, sqlx::Error> {
|
||||
let resp = get_response(id, pool)
|
||||
.await
|
||||
.expect("Failed to get response to delete");
|
||||
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM http_responses
|
||||
@@ -406,7 +426,7 @@ pub async fn delete_response(id: &str, pool: &Pool<Sqlite>) -> Result<(), sqlx::
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn delete_all_responses(
|
||||
|
||||
@@ -10,12 +10,13 @@ import { matchPath } from 'react-router-dom';
|
||||
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
||||
import { requestsQueryKey } from '../hooks/useRequests';
|
||||
import { responsesQueryKey } from '../hooks/useResponses';
|
||||
import { routePaths } from '../hooks/useRoutes';
|
||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||
import { DEFAULT_FONT_SIZE } from '../lib/constants';
|
||||
import { extractKeyValue } from '../lib/keyValueStore';
|
||||
import type { HttpRequest, HttpResponse, KeyValue, Workspace } from '../lib/models';
|
||||
import { convertDates } from '../lib/models';
|
||||
import { AppRouter, WORKSPACE_REQUEST_PATH } from './AppRouter';
|
||||
import { AppRouter } from './AppRouter';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -45,12 +46,6 @@ await listen('updated_request', ({ payload: request }: { payload: HttpRequest })
|
||||
);
|
||||
});
|
||||
|
||||
await listen('deleted_request', ({ payload: request }: { payload: HttpRequest }) => {
|
||||
queryClient.setQueryData(requestsQueryKey(request.workspaceId), (requests: HttpRequest[] = []) =>
|
||||
requests.filter((r) => r.id !== request.id),
|
||||
);
|
||||
});
|
||||
|
||||
await listen('updated_response', ({ payload: response }: { payload: HttpResponse }) => {
|
||||
queryClient.setQueryData(
|
||||
responsesQueryKey(response.requestId),
|
||||
@@ -92,8 +87,27 @@ await listen('updated_workspace', ({ payload: workspace }: { payload: Workspace
|
||||
});
|
||||
});
|
||||
|
||||
await listen(
|
||||
'deleted_model',
|
||||
({ payload: model }: { payload: Workspace | HttpRequest | HttpResponse | KeyValue }) => {
|
||||
function removeById<T extends { id: string }>(model: T) {
|
||||
return (entries: T[] | undefined) => entries?.filter((e) => e.id !== model.id);
|
||||
}
|
||||
|
||||
if (model.model === 'workspace') {
|
||||
queryClient.setQueryData(workspacesQueryKey(), removeById<Workspace>(model));
|
||||
} else if (model.model === 'http_request') {
|
||||
queryClient.setQueryData(requestsQueryKey(model.workspaceId), removeById<HttpRequest>(model));
|
||||
} else if (model.model === 'http_response') {
|
||||
queryClient.setQueryData(responsesQueryKey(model.requestId), removeById<HttpResponse>(model));
|
||||
} else if (model.model === 'key_value') {
|
||||
queryClient.setQueryData(keyValueQueryKey(model), undefined);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await listen('send_request', async () => {
|
||||
const params = matchPath(WORKSPACE_REQUEST_PATH, window.location.pathname);
|
||||
const params = matchPath(routePaths.request(), window.location.pathname);
|
||||
const requestId = params?.params.requestId;
|
||||
if (typeof requestId !== 'string') {
|
||||
return;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom';
|
||||
import { routePaths } from '../hooks/useRoutes';
|
||||
|
||||
const Workspaces = lazy(() => import('./Workspaces'));
|
||||
const Workspace = lazy(() => import('./Workspace'));
|
||||
const RouteError = lazy(() => import('./RouteError'));
|
||||
|
||||
export const WORKSPACE_PATH = '/workspaces/:workspaceId';
|
||||
export const WORKSPACE_REQUEST_PATH = '/workspaces/:workspaceId/requests/:requestId';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
@@ -15,14 +13,21 @@ const router = createBrowserRouter([
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
element: <Navigate to={routePaths.workspaces()} replace={true} />,
|
||||
},
|
||||
{
|
||||
path: routePaths.workspaces(),
|
||||
element: <Workspaces />,
|
||||
},
|
||||
{
|
||||
path: WORKSPACE_PATH,
|
||||
path: routePaths.workspace({ workspaceId: ':workspaceId' }),
|
||||
element: <Workspace />,
|
||||
},
|
||||
{
|
||||
path: WORKSPACE_REQUEST_PATH,
|
||||
path: routePaths.request({
|
||||
workspaceId: ':workspaceId',
|
||||
requestId: ':requestId',
|
||||
}),
|
||||
element: <Workspace />,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -2,7 +2,7 @@ import classnames from 'classnames';
|
||||
import { memo, useEffect, useMemo, useState } from 'react';
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useDeleteResponses } from '../hooks/useDeleteResponses';
|
||||
import { useDeleteResponse } from '../hooks/useResponseDelete';
|
||||
import { useDeleteResponse } from '../hooks/useDeleteResponse';
|
||||
import { useResponses } from '../hooks/useResponses';
|
||||
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
@@ -21,18 +21,18 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ResponsePane = memo(function ResponsePane({ className }: Props) {
|
||||
const [activeResponseId, setActiveResponseId] = useState<string | null>(null);
|
||||
const [pinnedResponseId, setPinnedResponseId] = useState<string | null>(null);
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const responses = useResponses(activeRequestId);
|
||||
const activeResponse: HttpResponse | null = activeResponseId
|
||||
? responses.find((r) => r.id === activeResponseId) ?? null
|
||||
const activeResponse: HttpResponse | null = pinnedResponseId
|
||||
? responses.find((r) => r.id === pinnedResponseId) ?? null
|
||||
: responses[responses.length - 1] ?? null;
|
||||
const [viewMode, toggleViewMode] = useResponseViewMode(activeResponse?.requestId);
|
||||
const deleteResponse = useDeleteResponse(activeResponse);
|
||||
const deleteResponse = useDeleteResponse(activeResponse?.id ?? null);
|
||||
const deleteAllResponses = useDeleteResponses(activeResponse?.requestId);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveResponseId(null);
|
||||
setPinnedResponseId(null);
|
||||
}, [responses.length]);
|
||||
|
||||
const contentType = useMemo(
|
||||
@@ -92,7 +92,7 @@ export const ResponsePane = memo(function ResponsePane({ className }: Props) {
|
||||
...responses.slice(0, 10).map((r) => ({
|
||||
label: r.status + ' - ' + r.elapsed + ' ms',
|
||||
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
|
||||
onSelect: () => setActiveResponseId(r.id),
|
||||
onSelect: () => setPinnedResponseId(r.id),
|
||||
})),
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={classnames(className, 'w-full flex items-center')}>
|
||||
<form onSubmit={handleSubmit} className={className}>
|
||||
<Input
|
||||
key={requestId}
|
||||
hideLabel
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import classnames from 'classnames';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
||||
import { useRoutes } from '../hooks/useRoutes';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
@@ -15,11 +16,12 @@ type Props = {
|
||||
};
|
||||
|
||||
export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }: Props) {
|
||||
const navigate = useNavigate();
|
||||
const routes = useRoutes();
|
||||
const workspaces = useWorkspaces();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
|
||||
const deleteWorkspace = useDeleteWorkspace(activeWorkspaceId);
|
||||
|
||||
const items: DropdownItem[] = useMemo(() => {
|
||||
const workspaceItems = workspaces.map((w) => ({
|
||||
@@ -27,7 +29,7 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
|
||||
leftSlot: activeWorkspaceId === w.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
onSelect: () => {
|
||||
if (w.id === activeWorkspaceId) return;
|
||||
navigate(`/workspaces/${w.id}`);
|
||||
routes.navigate('workspace', { workspaceId: w.id });
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -36,10 +38,14 @@ export const WorkspaceDropdown = memo(function WorkspaceDropdown({ className }:
|
||||
'-----',
|
||||
{
|
||||
label: 'New Workspace',
|
||||
value: 'new',
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: () => createWorkspace.mutate({ name: 'New Workspace' }),
|
||||
},
|
||||
{
|
||||
label: 'Delete Workspace',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: () => deleteWorkspace.mutate(),
|
||||
},
|
||||
];
|
||||
}, [workspaces, activeWorkspaceId]);
|
||||
|
||||
|
||||
@@ -20,10 +20,9 @@ export type DropdownItem =
|
||||
export interface DropdownProps {
|
||||
children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
|
||||
items: DropdownItem[];
|
||||
ignoreClick?: boolean;
|
||||
}
|
||||
|
||||
export function Dropdown({ children, items, ignoreClick }: DropdownProps) {
|
||||
export function Dropdown({ children, items }: DropdownProps) {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
const child = useMemo(() => {
|
||||
@@ -36,7 +35,6 @@ export function Dropdown({ children, items, ignoreClick }: DropdownProps) {
|
||||
onClick:
|
||||
existingChild.props?.onClick ??
|
||||
((e: MouseEvent<HTMLButtonElement>) => {
|
||||
console.log('CLICK INSIDE');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setOpen((o) => !o);
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
@apply text-placeholder;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
/* Inherit line-height from outside */
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
/* Don't show selection on blurred input */
|
||||
.cm-selectionBackground {
|
||||
@apply bg-transparent;
|
||||
@@ -54,17 +59,15 @@
|
||||
|
||||
&.cm-singleline {
|
||||
.cm-editor {
|
||||
@apply h-full w-full;
|
||||
@apply w-full h-auto;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
@apply font-mono flex text-[0.8rem];
|
||||
align-items: center !important;
|
||||
overflow: hidden !important;
|
||||
@apply font-mono text-[0.8rem] overflow-hidden;
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
@apply px-0;
|
||||
@apply px-2 overflow-hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,11 +99,12 @@
|
||||
}
|
||||
|
||||
.cm-editor .cm-gutterElement {
|
||||
@apply flex items-center;
|
||||
transition: color var(--transition-duration);
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon {
|
||||
@apply pt-[0.3em] pl-[0.4em] px-[0.4em] h-4 cursor-pointer rounded;
|
||||
@apply pt-[0.25em] pl-[0.4em] px-[0.4em] h-4 cursor-pointer rounded;
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon::after {
|
||||
@@ -109,7 +113,7 @@
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon[data-open] {
|
||||
@apply pt-[0.4em] pl-[0.3em];
|
||||
@apply pt-[0.38em] pl-[0.3em];
|
||||
}
|
||||
|
||||
.cm-editor .fold-gutter-icon[data-open]::after {
|
||||
|
||||
@@ -46,7 +46,7 @@ export function Input({
|
||||
const id = `input-${name}`;
|
||||
const inputClassName = classnames(
|
||||
className,
|
||||
'!bg-transparent pl-3 pr-2 min-w-0 h-full w-full focus:outline-none placeholder:text-placeholder',
|
||||
'!bg-transparent min-w-0 h-full w-full focus:outline-none placeholder:text-placeholder',
|
||||
!!leftSlot && '!pl-0.5',
|
||||
!!rightSlot && '!pr-0.5',
|
||||
);
|
||||
@@ -81,8 +81,8 @@ export function Input({
|
||||
'relative w-full rounded-md text-gray-900',
|
||||
'border border-gray-200 focus-within:border-focus',
|
||||
!isValid && '!border-invalid',
|
||||
size === 'md' && 'h-md',
|
||||
size === 'sm' && 'h-sm',
|
||||
size === 'md' && 'h-md leading-md',
|
||||
size === 'sm' && 'h-sm leading-sm',
|
||||
)}
|
||||
>
|
||||
{leftSlot}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import type { RouteParamsRequest } from './useRoutes';
|
||||
|
||||
export function useActiveRequestId(): string | null {
|
||||
const { requestId } = useParams<{ requestId?: string }>();
|
||||
const { requestId } = useParams<RouteParamsRequest>();
|
||||
return requestId ?? null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import type { RouteParamsWorkspace } from './useRoutes';
|
||||
|
||||
export function useActiveWorkspaceId(): string | null {
|
||||
const { workspaceId } = useParams<{ workspaceId?: string }>();
|
||||
const { workspaceId } = useParams<RouteParamsWorkspace>();
|
||||
return workspaceId ?? null;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { Workspace } from '../lib/models';
|
||||
import { useRoutes } from './useRoutes';
|
||||
|
||||
export function useCreateWorkspace({ navigateAfter }: { navigateAfter: boolean }) {
|
||||
const navigate = useNavigate();
|
||||
const routes = useRoutes();
|
||||
return useMutation<string, unknown, Pick<Workspace, 'name'>>({
|
||||
mutationFn: (patch) => {
|
||||
return invoke('create_workspace', patch);
|
||||
},
|
||||
onSuccess: async (workspaceId) => {
|
||||
if (navigateAfter) {
|
||||
navigate(`/workspaces/${workspaceId}`);
|
||||
routes.navigate('workspace', { workspaceId });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
11
src-web/hooks/useDeleteResponse.ts
Normal file
11
src-web/hooks/useDeleteResponse.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
|
||||
export function useDeleteResponse(id: string | null) {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
if (id === null) return;
|
||||
await invoke('delete_response', { id: id });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -9,9 +9,9 @@ export function useDeleteResponses(requestId?: string) {
|
||||
if (!requestId) return;
|
||||
await invoke('delete_all_responses', { requestId });
|
||||
},
|
||||
onSuccess: () => {
|
||||
onSuccess: async () => {
|
||||
if (!requestId) return;
|
||||
queryClient.setQueryData(responsesQueryKey(requestId), []);
|
||||
await queryClient.invalidateQueries(responsesQueryKey(requestId));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
25
src-web/hooks/useDeleteWorkspace.ts
Normal file
25
src-web/hooks/useDeleteWorkspace.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useRoutes } from './useRoutes';
|
||||
import { workspacesQueryKey } from './useWorkspaces';
|
||||
|
||||
export function useDeleteWorkspace(id: string | null) {
|
||||
const queryClient = useQueryClient();
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const routes = useRoutes();
|
||||
return useMutation<void, string>({
|
||||
mutationFn: async () => {
|
||||
if (id === null) return;
|
||||
await invoke('delete_workspace', { id });
|
||||
},
|
||||
onSuccess: async () => {
|
||||
if (id === null) return;
|
||||
await queryClient.invalidateQueries(workspacesQueryKey());
|
||||
if (id === activeWorkspaceId) {
|
||||
routes.navigate('workspace', { workspaceId: id });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { responsesQueryKey } from './useResponses';
|
||||
|
||||
export function useDeleteResponse(response: HttpResponse | null) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
if (response === null) return;
|
||||
await invoke('delete_response', { id: response.id });
|
||||
},
|
||||
onSuccess: () => {
|
||||
if (response === null) return;
|
||||
queryClient.setQueryData(
|
||||
responsesQueryKey(response.requestId),
|
||||
(responses: HttpResponse[] = []) => responses.filter((r) => r.id !== response.id),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
39
src-web/hooks/useRoutes.ts
Normal file
39
src-web/hooks/useRoutes.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export type RouteParamsWorkspace = {
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
export type RouteParamsRequest = RouteParamsWorkspace & {
|
||||
requestId: string;
|
||||
};
|
||||
|
||||
export const routePaths = {
|
||||
workspaces() {
|
||||
return '/workspaces';
|
||||
},
|
||||
workspace({ workspaceId } = { workspaceId: ':workspaceId' } as RouteParamsWorkspace) {
|
||||
return `/workspaces/${workspaceId}`;
|
||||
},
|
||||
request(
|
||||
{ workspaceId, requestId } = {
|
||||
workspaceId: ':workspaceId',
|
||||
requestId: ':requestId',
|
||||
} as RouteParamsRequest,
|
||||
) {
|
||||
return `${this.workspace({ workspaceId })}/requests/${requestId}`;
|
||||
},
|
||||
};
|
||||
|
||||
export function useRoutes() {
|
||||
return {
|
||||
navigate<T extends keyof typeof routePaths>(
|
||||
path: T,
|
||||
params: Parameters<(typeof routePaths)[T]>[0],
|
||||
) {
|
||||
// Not sure how to make TS work here, but it's good from the
|
||||
// outside caller perspective.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
routePaths[path](params as any);
|
||||
},
|
||||
paths: routePaths,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
export interface BaseModel {
|
||||
readonly id: string;
|
||||
readonly workspaceId: string;
|
||||
readonly createdAt: Date;
|
||||
readonly updatedAt: Date;
|
||||
}
|
||||
@@ -18,6 +17,7 @@ export interface HttpHeader {
|
||||
}
|
||||
|
||||
export interface HttpRequest extends BaseModel {
|
||||
readonly workspaceId: string;
|
||||
readonly model: 'http_request';
|
||||
sortPriority: number;
|
||||
name: string;
|
||||
@@ -36,6 +36,7 @@ export interface KeyValue extends Omit<BaseModel, 'id'> {
|
||||
}
|
||||
|
||||
export interface HttpResponse extends BaseModel {
|
||||
readonly workspaceId: string;
|
||||
readonly model: 'http_response';
|
||||
readonly requestId: string;
|
||||
readonly body: string;
|
||||
|
||||
@@ -13,7 +13,11 @@ module.exports = {
|
||||
height: {
|
||||
'sm': '2rem',
|
||||
'md': '2.5rem',
|
||||
}
|
||||
},
|
||||
lineHeight: {
|
||||
'sm': '2rem',
|
||||
'md': '2.5rem',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
"mono": ["JetBrains Mono", "Menlo", "monospace"],
|
||||
|
||||
Reference in New Issue
Block a user