mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-15 05:33:29 +01:00
Request history navigator
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Yaak App</title>
|
||||
<!-- <script src="http://localhost:8097"></script>-->
|
||||
<script src="http://localhost:8097"></script>
|
||||
<style>
|
||||
body {
|
||||
background-color: white;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createBrowserRouter, Navigate, Outlet, RouterProvider } from 'react-router-dom';
|
||||
import { routePaths } from '../hooks/useRoutes';
|
||||
import { routePaths } from '../hooks/useAppRoutes';
|
||||
import { GlobalHooks } from './GlobalHooks';
|
||||
import RouteError from './RouteError';
|
||||
import { TauriListeners } from './TauriListeners';
|
||||
import Workspace from './Workspace';
|
||||
import Workspaces from './Workspaces';
|
||||
|
||||
@@ -9,12 +9,7 @@ const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
errorElement: <RouteError />,
|
||||
element: (
|
||||
<>
|
||||
<Outlet />
|
||||
<TauriListeners />
|
||||
</>
|
||||
),
|
||||
element: <Layout />,
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
@@ -42,3 +37,12 @@ const router = createBrowserRouter([
|
||||
export function AppRouter() {
|
||||
return <RouterProvider router={router} />;
|
||||
}
|
||||
|
||||
function Layout() {
|
||||
return (
|
||||
<>
|
||||
<Outlet />
|
||||
<GlobalHooks />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||
import type { HttpRequest, HttpResponse, Model, Workspace } from '../lib/models';
|
||||
import { modelsEq } from '../lib/models';
|
||||
|
||||
export function TauriListeners() {
|
||||
export function GlobalHooks() {
|
||||
const queryClient = useQueryClient();
|
||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||
|
||||
106
src-web/components/RecentRequestsDropdown.tsx
Normal file
106
src-web/components/RecentRequestsDropdown.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useKeyPressEvent } from 'react-use';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { Button } from './core/Button';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import type { DropdownItem, DropdownRef } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
|
||||
export function RecentRequestsDropdown() {
|
||||
const dropdownRef = useRef<DropdownRef>(null);
|
||||
|
||||
useKeyPressEvent('Control', undefined, () => {
|
||||
// Key up
|
||||
dropdownRef.current?.select?.();
|
||||
});
|
||||
|
||||
useKeyPressEvent('Tab', (e) => {
|
||||
if (!e.ctrlKey) return;
|
||||
if (!dropdownRef.current?.isOpen) {
|
||||
// Set to 1 because the first item is the active request
|
||||
dropdownRef.current?.open(e.shiftKey ? -1 : 1);
|
||||
}
|
||||
|
||||
if (e.shiftKey) {
|
||||
dropdownRef.current?.prev?.();
|
||||
} else {
|
||||
dropdownRef.current?.next?.();
|
||||
}
|
||||
});
|
||||
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const recentRequestIds = useRecentRequests();
|
||||
const requests = useRequests();
|
||||
const routes = useAppRoutes();
|
||||
const deleteRequest = useDeleteRequest(activeRequest?.id ?? null);
|
||||
const duplicateRequest = useDuplicateRequest({
|
||||
id: activeRequest?.id ?? null,
|
||||
navigateAfter: true,
|
||||
});
|
||||
|
||||
const items = useMemo<DropdownItem[]>(() => {
|
||||
if (activeWorkspaceId === null) return [];
|
||||
|
||||
const recentRequestItems: DropdownItem[] = [];
|
||||
for (const id of recentRequestIds) {
|
||||
const request = requests.find((r) => r.id === id);
|
||||
if (request === undefined) continue;
|
||||
|
||||
recentRequestItems.push({
|
||||
label: request.name,
|
||||
leftSlot: <CountBadge className="!mx-0" count={recentRequestItems.length + 1} />,
|
||||
onSelect: () => {
|
||||
routes.navigate('request', {
|
||||
requestId: request.id,
|
||||
workspaceId: activeWorkspaceId,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Show max 30 items
|
||||
const fixedItems: DropdownItem[] = [
|
||||
// {
|
||||
// label: 'Duplicate',
|
||||
// onSelect: duplicateRequest.mutate,
|
||||
// leftSlot: <Icon icon="copy" />,
|
||||
// rightSlot: <HotKey modifier="Meta" keyName="D" />,
|
||||
// },
|
||||
// {
|
||||
// label: 'Delete',
|
||||
// onSelect: deleteRequest.mutate,
|
||||
// variant: 'danger',
|
||||
// leftSlot: <Icon icon="trash" />,
|
||||
// },
|
||||
];
|
||||
|
||||
// No recent requests to show
|
||||
if (recentRequestItems.length === 0) {
|
||||
return fixedItems;
|
||||
}
|
||||
|
||||
return [
|
||||
// ...fixedItems,
|
||||
// { type: 'separator', label: 'Recent Requests' },
|
||||
...recentRequestItems.slice(0, 20),
|
||||
];
|
||||
}, [activeWorkspaceId, recentRequestIds, requests, routes]);
|
||||
|
||||
return (
|
||||
<Dropdown ref={dropdownRef} items={items}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="pointer-events-auto flex-[2] text-center text-gray-800 text-sm truncate pointer-events-none"
|
||||
>
|
||||
{activeRequest?.name}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export function RequestActionsDropdown({ requestId, children }: Props) {
|
||||
{
|
||||
label: 'Delete',
|
||||
onSelect: deleteRequest.mutate,
|
||||
variant: 'danger',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
},
|
||||
{ type: 'separator', label: 'Yaak Settings' },
|
||||
|
||||
@@ -162,7 +162,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
label="Request"
|
||||
onChangeValue={setActiveTab}
|
||||
tabs={tabs}
|
||||
tabListClassName="mt-2"
|
||||
tabListClassName="mt-2 !mb-1.5"
|
||||
>
|
||||
<TabContent value="auth">
|
||||
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? (
|
||||
|
||||
@@ -103,7 +103,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
<HStack
|
||||
alignItems="center"
|
||||
className={classnames(
|
||||
'italic text-gray-700 text-sm w-full flex-shrink-0',
|
||||
'text-gray-700 text-sm w-full flex-shrink-0',
|
||||
// Remove a bit of space because the tabs have lots too
|
||||
'-mb-1.5',
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import classnames from 'classnames';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
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';
|
||||
@@ -25,7 +25,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
|
||||
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
||||
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
||||
const prompt = usePrompt();
|
||||
const routes = useRoutes();
|
||||
const routes = useAppRoutes();
|
||||
|
||||
const items: DropdownItem[] = useMemo(() => {
|
||||
const workspaceItems = workspaces.map((w) => ({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { memo } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { RecentRequestsDropdown } from './RecentRequestsDropdown';
|
||||
import { RequestActionsDropdown } from './RequestActionsDropdown';
|
||||
import { SidebarActions } from './SidebarActions';
|
||||
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
|
||||
@@ -24,8 +25,8 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
||||
<SidebarActions />
|
||||
<WorkspaceActionsDropdown className="pointer-events-auto" />
|
||||
</HStack>
|
||||
<div className="flex-[2] text-center text-gray-800 text-sm truncate pointer-events-none">
|
||||
{activeRequest?.name}
|
||||
<div>
|
||||
<RecentRequestsDropdown />
|
||||
</div>
|
||||
<div className="flex-1 flex justify-end -mr-2 pointer-events-none">
|
||||
{activeRequest && (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { useRoutes } from '../hooks/useRoutes';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { Heading } from './core/Heading';
|
||||
|
||||
export default function Workspaces() {
|
||||
const routes = useRoutes();
|
||||
const routes = useAppRoutes();
|
||||
const workspaces = useWorkspaces();
|
||||
const workspace = workspaces[0];
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import classnames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
count: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function CountBadge({ count }: Props) {
|
||||
export function CountBadge({ count, className }: Props) {
|
||||
if (count === 0) return null;
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
aria-hidden
|
||||
className="opacity-70 border border-highlight text-3xs rounded mb-0.5 px-1 ml-1 h-4 font-mono"
|
||||
>
|
||||
{count}
|
||||
</div>
|
||||
</>
|
||||
<div
|
||||
aria-hidden
|
||||
className={classnames(
|
||||
className,
|
||||
'opacity-70 border border-highlight text-3xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
|
||||
)}
|
||||
>
|
||||
{count}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import type { CSSProperties, HTMLAttributes, MouseEvent, ReactElement, ReactNode
|
||||
import React, {
|
||||
Children,
|
||||
cloneElement,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
@@ -39,21 +41,50 @@ export interface DropdownProps {
|
||||
items: DropdownItem[];
|
||||
}
|
||||
|
||||
export function Dropdown({ children, items }: DropdownProps) {
|
||||
export interface DropdownRef {
|
||||
isOpen: boolean;
|
||||
open: (activeIndex?: number) => void;
|
||||
close?: () => void;
|
||||
next?: () => void;
|
||||
prev?: () => void;
|
||||
select?: () => void;
|
||||
}
|
||||
|
||||
export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown(
|
||||
{ children, items }: DropdownProps,
|
||||
ref,
|
||||
) {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
const [defaultSelectedIndex, setDefaultSelectedIndex] = useState<number>();
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const menuRef = useRef<Omit<DropdownRef, 'open'>>(null);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
...menuRef.current,
|
||||
isOpen: open,
|
||||
open: (activeIndex?: number) => {
|
||||
if (activeIndex === undefined) {
|
||||
setDefaultSelectedIndex(undefined);
|
||||
} else {
|
||||
setDefaultSelectedIndex(activeIndex >= 0 ? activeIndex : items.length + activeIndex);
|
||||
}
|
||||
setOpen(true);
|
||||
},
|
||||
}));
|
||||
|
||||
const child = useMemo(() => {
|
||||
const existingChild = Children.only(children);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const props: any = {
|
||||
...existingChild.props,
|
||||
ref,
|
||||
ref: buttonRef,
|
||||
'aria-haspopup': 'true',
|
||||
onClick:
|
||||
existingChild.props?.onClick ??
|
||||
((e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDefaultSelectedIndex(undefined);
|
||||
setOpen((o) => !o);
|
||||
}),
|
||||
};
|
||||
@@ -62,37 +93,48 @@ export function Dropdown({ children, items }: DropdownProps) {
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
ref.current?.focus();
|
||||
buttonRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current?.setAttribute('aria-expanded', open.toString());
|
||||
buttonRef.current?.setAttribute('aria-expanded', open.toString());
|
||||
}, [open]);
|
||||
|
||||
const triggerRect = useMemo(() => {
|
||||
if (!open) return null;
|
||||
return ref.current?.getBoundingClientRect();
|
||||
return buttonRef.current?.getBoundingClientRect();
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{child}
|
||||
{open && triggerRect && (
|
||||
<Menu items={items} triggerRect={triggerRect} onClose={handleClose} />
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
defaultSelectedIndex={defaultSelectedIndex}
|
||||
items={items}
|
||||
triggerRect={triggerRect}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
interface MenuProps {
|
||||
className?: string;
|
||||
defaultSelectedIndex?: number;
|
||||
items: DropdownProps['items'];
|
||||
triggerRect: DOMRect;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen'>, MenuProps>(function Menu(
|
||||
{ className, items, onClose, triggerRect, defaultSelectedIndex }: MenuProps,
|
||||
ref,
|
||||
) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(defaultSelectedIndex ?? null);
|
||||
const [menuStyles, setMenuStyles] = useState<CSSProperties>({});
|
||||
|
||||
// Calculate the max height so we can scroll
|
||||
@@ -119,8 +161,7 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
onClose();
|
||||
});
|
||||
|
||||
useKeyPressEvent('ArrowUp', (e) => {
|
||||
e.preventDefault();
|
||||
const handlePrev = useCallback(() => {
|
||||
setSelectedIndex((currIndex) => {
|
||||
let nextIndex = (currIndex ?? 0) - 1;
|
||||
const maxTries = items.length;
|
||||
@@ -135,10 +176,9 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
}
|
||||
return nextIndex;
|
||||
});
|
||||
});
|
||||
}, [items]);
|
||||
|
||||
useKeyPressEvent('ArrowDown', (e) => {
|
||||
e.preventDefault();
|
||||
const handleNext = useCallback(() => {
|
||||
setSelectedIndex((currIndex) => {
|
||||
let nextIndex = (currIndex ?? -1) + 1;
|
||||
const maxTries = items.length;
|
||||
@@ -153,8 +193,44 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
}
|
||||
return nextIndex;
|
||||
});
|
||||
}, [items]);
|
||||
|
||||
useKeyPressEvent('ArrowUp', (e) => {
|
||||
e.preventDefault();
|
||||
handlePrev();
|
||||
});
|
||||
|
||||
useKeyPressEvent('ArrowDown', (e) => {
|
||||
e.preventDefault();
|
||||
handleNext();
|
||||
});
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(i: DropdownItem) => {
|
||||
onClose();
|
||||
setSelectedIndex(null);
|
||||
if (i.type !== 'separator') {
|
||||
i.onSelect?.();
|
||||
}
|
||||
},
|
||||
[onClose],
|
||||
);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
close: onClose,
|
||||
prev: handlePrev,
|
||||
next: handleNext,
|
||||
select: () => {
|
||||
const item = items[selectedIndex ?? -1] ?? null;
|
||||
if (!item) return;
|
||||
handleSelect(item);
|
||||
},
|
||||
}),
|
||||
[handleNext, handlePrev, handleSelect, items, onClose, selectedIndex],
|
||||
);
|
||||
|
||||
const { containerStyles, triangleStyles } = useMemo<{
|
||||
containerStyles: CSSProperties;
|
||||
triangleStyles: CSSProperties;
|
||||
@@ -173,17 +249,6 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
return { containerStyles, triangleStyles };
|
||||
}, [triggerRect]);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(i: DropdownItem) => {
|
||||
onClose();
|
||||
setSelectedIndex(null);
|
||||
if (i.type !== 'separator') {
|
||||
i.onSelect?.();
|
||||
}
|
||||
},
|
||||
[onClose],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
(i: DropdownItem) => {
|
||||
const index = items.findIndex((item) => item === i) ?? null;
|
||||
@@ -192,8 +257,6 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
[items],
|
||||
);
|
||||
|
||||
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
|
||||
|
||||
return (
|
||||
<Portal name="dropdown">
|
||||
<FocusTrap>
|
||||
@@ -251,7 +314,7 @@ function Menu({ className, items, onClose, triggerRect }: MenuProps) {
|
||||
</FocusTrap>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
interface MenuItemProps {
|
||||
className?: string;
|
||||
@@ -293,7 +356,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{item.leftSlot && <div className="w-6">{item.leftSlot}</div>}
|
||||
{item.leftSlot && <div className="w-6 flex justify-start">{item.leftSlot}</div>}
|
||||
<div>{item.label}</div>
|
||||
{item.rightSlot && <div className="ml-auto pl-3">{item.rightSlot}</div>}
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import type { RouteParamsRequest } from './useRoutes';
|
||||
import type { RouteParamsRequest } from './useAppRoutes';
|
||||
|
||||
export function useActiveRequestId(): string | null {
|
||||
const { requestId } = useParams<RouteParamsRequest>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import type { RouteParamsWorkspace } from './useRoutes';
|
||||
import type { RouteParamsWorkspace } from './useAppRoutes';
|
||||
|
||||
export function useActiveWorkspaceId(): string | null {
|
||||
const { workspaceId } = useParams<RouteParamsWorkspace>();
|
||||
|
||||
@@ -26,7 +26,7 @@ export const routePaths = {
|
||||
},
|
||||
};
|
||||
|
||||
export function useRoutes() {
|
||||
export function useAppRoutes() {
|
||||
const navigate = useNavigate();
|
||||
return useMemo(
|
||||
() => ({
|
||||
@@ -2,12 +2,12 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { requestsQueryKey, useRequests } from './useRequests';
|
||||
import { useRoutes } from './useRoutes';
|
||||
|
||||
export function useCreateRequest({ navigateAfter }: { navigateAfter: boolean }) {
|
||||
const workspaceId = useActiveWorkspaceId();
|
||||
const routes = useRoutes();
|
||||
const routes = useAppRoutes();
|
||||
const requests = useRequests();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { Workspace } from '../lib/models';
|
||||
import { useRoutes } from './useRoutes';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { workspacesQueryKey } from './useWorkspaces';
|
||||
|
||||
export function useCreateWorkspace({ navigateAfter }: { navigateAfter: boolean }) {
|
||||
const routes = useRoutes();
|
||||
const routes = useAppRoutes();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<Workspace, unknown, Pick<Workspace, 'name'>>({
|
||||
mutationFn: (patch) => {
|
||||
|
||||
@@ -3,15 +3,15 @@ import { invoke } from '@tauri-apps/api';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import type { Workspace } from '../lib/models';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { useConfirm } from './useConfirm';
|
||||
import { requestsQueryKey } from './useRequests';
|
||||
import { useRoutes } from './useRoutes';
|
||||
import { workspacesQueryKey } from './useWorkspaces';
|
||||
|
||||
export function useDeleteWorkspace(workspace: Workspace | null) {
|
||||
const queryClient = useQueryClient();
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
const routes = useRoutes();
|
||||
const routes = useAppRoutes();
|
||||
const confirm = useConfirm();
|
||||
|
||||
return useMutation<Workspace | null, string>({
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { requestsQueryKey } from './useRequests';
|
||||
import { useRoutes } from './useRoutes';
|
||||
|
||||
export function useDuplicateRequest({
|
||||
id,
|
||||
@@ -13,7 +13,7 @@ export function useDuplicateRequest({
|
||||
navigateAfter: boolean;
|
||||
}) {
|
||||
const workspaceId = useActiveWorkspaceId();
|
||||
const routes = useRoutes();
|
||||
const routes = useAppRoutes();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<HttpRequest, string>({
|
||||
mutationFn: async () => {
|
||||
|
||||
31
src-web/hooks/useRecentRequests.ts
Normal file
31
src-web/hooks/useRecentRequests.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useEffect } from 'react';
|
||||
import { createGlobalState, useEffectOnce, useLocalStorage } from 'react-use';
|
||||
import { useActiveRequestId } from './useActiveRequestId';
|
||||
|
||||
const useHistoryState = createGlobalState<string[]>([]);
|
||||
|
||||
export function useRecentRequests() {
|
||||
const [history, setHistory] = useHistoryState();
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const [lsState, setLSState] = useLocalStorage<string[]>('recent_requests', []);
|
||||
|
||||
useEffect(() => {
|
||||
setLSState(history);
|
||||
}, [history, setLSState]);
|
||||
|
||||
useEffectOnce(() => {
|
||||
if (lsState) {
|
||||
setHistory(lsState);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setHistory((h: string[]) => {
|
||||
if (activeRequestId === null) return h;
|
||||
const withoutCurrentRequest = h.filter((id) => id !== activeRequestId);
|
||||
return [activeRequestId, ...withoutCurrentRequest];
|
||||
});
|
||||
}, [activeRequestId, setHistory]);
|
||||
|
||||
return history;
|
||||
}
|
||||
Reference in New Issue
Block a user