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