mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 22:40:26 +01:00
A bunch of tweaks
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -6,7 +6,6 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { AppRouter } from './AppRouter';
|
||||
import { DialogProvider } from './DialogContext';
|
||||
import { TauriListeners } from './TauriListeners';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
logger: undefined,
|
||||
@@ -28,7 +27,6 @@ export function App() {
|
||||
<DialogProvider>
|
||||
<Suspense>
|
||||
<AppRouter />
|
||||
<TauriListeners />
|
||||
{/*<ReactQueryDevtools initialIsOpen={false} />*/}
|
||||
</Suspense>
|
||||
</DialogProvider>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom';
|
||||
import { createBrowserRouter, Navigate, Outlet, RouterProvider } from 'react-router-dom';
|
||||
import { routePaths } from '../hooks/useRoutes';
|
||||
import RouteError from './RouteError';
|
||||
import { TauriListeners } from './TauriListeners';
|
||||
import Workspace from './Workspace';
|
||||
import Workspaces from './Workspaces';
|
||||
|
||||
@@ -8,6 +9,12 @@ const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
errorElement: <RouteError />,
|
||||
element: (
|
||||
<>
|
||||
<Outlet />
|
||||
<TauriListeners />
|
||||
</>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
|
||||
@@ -24,7 +24,7 @@ export function RequestActionsDropdown({ requestId, children }: Props) {
|
||||
label: 'Duplicate',
|
||||
onSelect: duplicateRequest.mutate,
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
rightSlot: <HotKey>⌘D</HotKey>,
|
||||
rightSlot: <HotKey modifier="Meta" keyName="D" />,
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
const activeResponse: HttpResponse | null = pinnedResponseId
|
||||
? responses.find((r) => r.id === pinnedResponseId) ?? null
|
||||
: responses[responses.length - 1] ?? null;
|
||||
const [viewMode, toggleViewMode] = useResponseViewMode(activeResponse?.requestId);
|
||||
const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId);
|
||||
const deleteResponse = useDeleteResponse(activeResponse?.id ?? null);
|
||||
const deleteAllResponses = useDeleteResponses(activeResponse?.requestId);
|
||||
const [activeTab, setActiveTab] = useActiveTab();
|
||||
@@ -62,7 +62,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
label: 'Preview',
|
||||
options: {
|
||||
value: viewMode,
|
||||
onChange: toggleViewMode,
|
||||
onChange: setViewMode,
|
||||
items: [
|
||||
{ label: 'Pretty', value: 'pretty' },
|
||||
{ label: 'Raw', value: 'raw' },
|
||||
@@ -81,7 +81,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
value: 'headers',
|
||||
},
|
||||
],
|
||||
[activeResponse?.headers, toggleViewMode, viewMode],
|
||||
[activeResponse?.headers, setViewMode, viewMode],
|
||||
);
|
||||
|
||||
// Don't render until we know the view mode
|
||||
|
||||
@@ -3,7 +3,8 @@ import type { ForwardedRef, KeyboardEvent } from 'react';
|
||||
import React, { forwardRef, Fragment, memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useDeleteRequest } from '../hooks/useDeleteRequest';
|
||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
@@ -11,7 +12,6 @@ import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
|
||||
import { useUpdateRequest } from '../hooks/useUpdateRequest';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { isResponseLoading } from '../lib/models';
|
||||
import { Button } from './core/Button';
|
||||
import { Icon } from './core/Icon';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { StatusTag } from './core/StatusTag';
|
||||
@@ -28,7 +28,6 @@ enum ItemTypes {
|
||||
export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||
const unorderedRequests = useRequests();
|
||||
const activeRequest = useActiveRequest();
|
||||
const requests = useMemo(
|
||||
() => [...unorderedRequests].sort((a, b) => a.sortPriority - b.sortPriority),
|
||||
[unorderedRequests],
|
||||
@@ -45,20 +44,14 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
className="relative py-3 overflow-y-auto overflow-x-visible"
|
||||
draggable={false}
|
||||
>
|
||||
<SidebarItems activeRequestId={activeRequest?.id} requests={requests} />
|
||||
<SidebarItems requests={requests} />
|
||||
</VStack>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
function SidebarItems({
|
||||
requests,
|
||||
activeRequestId,
|
||||
}: {
|
||||
requests: HttpRequest[];
|
||||
activeRequestId?: string;
|
||||
}) {
|
||||
function SidebarItems({ requests }: { requests: HttpRequest[] }) {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||
const updateRequest = useUpdateAnyRequest();
|
||||
|
||||
@@ -112,7 +105,6 @@ function SidebarItems({
|
||||
requestId={r.id}
|
||||
requestName={r.name}
|
||||
workspaceId={r.workspaceId}
|
||||
active={r.id === activeRequestId}
|
||||
onMove={handleMove}
|
||||
onEnd={handleEnd}
|
||||
/>
|
||||
@@ -128,17 +120,18 @@ type SidebarItemProps = {
|
||||
requestId: string;
|
||||
requestName: string;
|
||||
workspaceId: string;
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
const _SidebarItem = forwardRef(function SidebarItem(
|
||||
{ className, requestName, requestId, workspaceId, active }: SidebarItemProps,
|
||||
{ className, requestName, requestId, workspaceId }: SidebarItemProps,
|
||||
ref: ForwardedRef<HTMLLIElement>,
|
||||
) {
|
||||
const latestResponse = useLatestResponse(requestId);
|
||||
const updateRequest = useUpdateRequest(requestId);
|
||||
const deleteRequest = useDeleteRequest(requestId);
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const isActive = activeRequestId === requestId;
|
||||
|
||||
const handleSubmitNameEdit = useCallback(
|
||||
async (el: HTMLInputElement) => {
|
||||
@@ -156,16 +149,16 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLElement>) => {
|
||||
// Hitting enter on active request during keyboard nav will start edit
|
||||
if (active && e.key === 'Enter') {
|
||||
if (isActive && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
setEditing(true);
|
||||
}
|
||||
if (active && (e.key === 'Backspace' || e.key === 'Delete')) {
|
||||
if (isActive && (e.key === 'Backspace' || e.key === 'Delete')) {
|
||||
e.preventDefault();
|
||||
deleteRequest.mutate();
|
||||
}
|
||||
},
|
||||
[active, deleteRequest],
|
||||
[isActive, deleteRequest],
|
||||
);
|
||||
|
||||
const handleInputKeyDown = useCallback(
|
||||
@@ -185,21 +178,29 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
[handleSubmitNameEdit],
|
||||
);
|
||||
|
||||
const handleStartEditing = useCallback(() => setEditing(true), [setEditing]);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
handleSubmitNameEdit(e.currentTarget).catch(console.error);
|
||||
},
|
||||
[handleSubmitNameEdit],
|
||||
);
|
||||
|
||||
return (
|
||||
<li ref={ref} className={classnames(className, 'block group/item px-2 pb-0.5')}>
|
||||
<div className="relative">
|
||||
<Button
|
||||
<NavLink
|
||||
tabIndex={0}
|
||||
color="custom"
|
||||
size="xs"
|
||||
to={`/workspaces/${workspaceId}/requests/${requestId}`}
|
||||
draggable={false} // Item should drag, not the link
|
||||
onDoubleClick={() => setEditing(true)}
|
||||
justify="start"
|
||||
onDoubleClick={handleStartEditing}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={classnames(
|
||||
'flex items-center text-sm h-xs px-2 rounded-md',
|
||||
editing && 'ring-1 focus-within:ring-focus',
|
||||
active
|
||||
isActive
|
||||
? 'bg-highlight text-gray-900'
|
||||
: 'text-gray-600 group-hover/item:text-gray-800 active:bg-highlightSecondary',
|
||||
)}
|
||||
@@ -209,7 +210,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
ref={handleFocus}
|
||||
defaultValue={requestName}
|
||||
className="bg-transparent outline-none w-full"
|
||||
onBlur={(e) => handleSubmitNameEdit(e.currentTarget)}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
) : (
|
||||
@@ -226,7 +227,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</NavLink>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
@@ -248,7 +249,6 @@ const DraggableSidebarItem = memo(function DraggableSidebarItem({
|
||||
requestName,
|
||||
requestId,
|
||||
workspaceId,
|
||||
active,
|
||||
onMove,
|
||||
onEnd,
|
||||
}: DraggableSidebarItemProps) {
|
||||
@@ -290,7 +290,6 @@ const DraggableSidebarItem = memo(function DraggableSidebarItem({
|
||||
requestName={requestName}
|
||||
requestId={requestId}
|
||||
workspaceId={workspaceId}
|
||||
active={active}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useTauriEvent } from '../hooks/useTauriEvent';
|
||||
import { IconButton } from './core/IconButton';
|
||||
|
||||
export const SidebarActions = memo(function SidebarDisplayToggle() {
|
||||
export const SidebarActions = memo(function SidebarActions() {
|
||||
const { hidden, toggle } = useSidebarHidden();
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const createRequest = useCreateRequest({ navigateAfter: true });
|
||||
const duplicateRequest = useDuplicateRequest({ id: activeRequestId, navigateAfter: true });
|
||||
|
||||
const handleCreateRequest = useCallback(() => {
|
||||
createRequest.mutate({});
|
||||
}, [createRequest]);
|
||||
|
||||
useTauriEvent('new_request', () => {
|
||||
createRequest.mutate({});
|
||||
});
|
||||
// TODO: Put this somewhere better
|
||||
useTauriEvent('duplicate_request', () => {
|
||||
duplicateRequest.mutate();
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
|
||||
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
||||
import { requestsQueryKey } from '../hooks/useRequests';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
@@ -15,6 +17,14 @@ export function TauriListeners() {
|
||||
const queryClient = useQueryClient();
|
||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const duplicateRequest = useDuplicateRequest({ id: activeRequestId, navigateAfter: true });
|
||||
|
||||
// TODO: Put this somewhere better
|
||||
useTauriEvent('duplicate_request', () => {
|
||||
duplicateRequest.mutate();
|
||||
});
|
||||
|
||||
useTauriEvent<Model>('created_model', ({ payload, windowLabel }) => {
|
||||
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
||||
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import classnames from 'classnames';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
export function HotKey({ children }: HTMLAttributes<HTMLSpanElement>) {
|
||||
interface Props {
|
||||
modifier: 'Meta' | 'Control' | 'Shift';
|
||||
keyName: string;
|
||||
}
|
||||
|
||||
const keys: Record<Props['modifier'], string> = {
|
||||
Control: '⌃',
|
||||
Meta: '⌘',
|
||||
Shift: '⇧',
|
||||
};
|
||||
|
||||
export function HotKey({ modifier, keyName }: Props) {
|
||||
return (
|
||||
<span
|
||||
className={classnames(
|
||||
'bg-highlightSecondary bg-opacity-20 px-1.5 py-0.5 rounded text-sm',
|
||||
'font-mono text-gray-500 tracking-widest',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<span className={classnames('text-sm text-gray-600')}>
|
||||
{keys[modifier]}
|
||||
{keyName}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classnames from 'classnames';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { forwardRef, memo, useCallback } from 'react';
|
||||
import { forwardRef, useCallback } from 'react';
|
||||
import { useTimedBoolean } from '../../hooks/useTimedBoolean';
|
||||
import type { ButtonProps } from './Button';
|
||||
import { Button } from './Button';
|
||||
@@ -15,7 +15,7 @@ type Props = IconProps &
|
||||
title: string;
|
||||
};
|
||||
|
||||
const _IconButton = forwardRef<HTMLButtonElement, Props>(function IconButton(
|
||||
export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButton(
|
||||
{
|
||||
showConfirm,
|
||||
icon,
|
||||
@@ -69,5 +69,3 @@ const _IconButton = forwardRef<HTMLButtonElement, Props>(function IconButton(
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
export const IconButton = memo(_IconButton);
|
||||
|
||||
@@ -3,16 +3,12 @@ import { invoke } from '@tauri-apps/api';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import type { HttpRequest } from '../lib/models';
|
||||
import { getRequest } from '../lib/store';
|
||||
import { useActiveRequestId } from './useActiveRequestId';
|
||||
import { useConfirm } from './useConfirm';
|
||||
import { requestsQueryKey } from './useRequests';
|
||||
import { responsesQueryKey } from './useResponses';
|
||||
import { useRoutes } from './useRoutes';
|
||||
|
||||
export function useDeleteRequest(id: string | null) {
|
||||
const queryClient = useQueryClient();
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const routes = useRoutes();
|
||||
const confirm = useConfirm();
|
||||
|
||||
return useMutation<HttpRequest | null, string>({
|
||||
@@ -39,9 +35,6 @@ export function useDeleteRequest(id: string | null) {
|
||||
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey({ workspaceId }), (requests) =>
|
||||
(requests ?? []).filter((r) => r.id !== requestId),
|
||||
);
|
||||
if (activeRequestId === requestId) {
|
||||
routes.navigate('workspace', { workspaceId });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
|
||||
export function useResponseViewMode(requestId?: string): [string | undefined, () => void] {
|
||||
const v = useKeyValue<string>({
|
||||
namespace: 'app',
|
||||
key: ['response_view_mode', requestId ?? 'n/a'],
|
||||
defaultValue: 'pretty',
|
||||
});
|
||||
|
||||
const toggle = () => {
|
||||
v.set(v.value === 'pretty' ? 'raw' : 'pretty');
|
||||
};
|
||||
|
||||
return [v.value, toggle];
|
||||
export function useResponseViewMode(
|
||||
requestId?: string,
|
||||
): [string | undefined, (m: 'pretty' | 'raw') => void] {
|
||||
const [value, setValue] = useLocalStorage<'pretty' | 'raw'>(
|
||||
`response_view_mode::${requestId}`,
|
||||
'pretty',
|
||||
);
|
||||
return [value, setValue];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export type RouteParamsWorkspace = {
|
||||
@@ -27,17 +28,20 @@ export const routePaths = {
|
||||
|
||||
export function useRoutes() {
|
||||
const navigate = useNavigate();
|
||||
return {
|
||||
navigate<T extends keyof typeof routePaths>(
|
||||
path: T,
|
||||
...params: Parameters<(typeof routePaths)[T]>
|
||||
) {
|
||||
// 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
|
||||
const resolvedPath = routePaths[path](...(params as any));
|
||||
navigate(resolvedPath);
|
||||
},
|
||||
paths: routePaths,
|
||||
};
|
||||
return useMemo(
|
||||
() => ({
|
||||
navigate<T extends keyof typeof routePaths>(
|
||||
path: T,
|
||||
...params: Parameters<(typeof routePaths)[T]>
|
||||
) {
|
||||
// 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
|
||||
const resolvedPath = routePaths[path](...(params as any));
|
||||
navigate(resolvedPath);
|
||||
},
|
||||
paths: routePaths,
|
||||
}),
|
||||
[navigate],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ export function useSidebarWidth() {
|
||||
return useKeyValue<number>({
|
||||
namespace: NAMESPACE_NO_SYNC,
|
||||
key: 'sidebar_width',
|
||||
defaultValue: 200,
|
||||
defaultValue: 220,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,20 +33,24 @@
|
||||
}
|
||||
|
||||
/* Style the scrollbars */
|
||||
::-webkit-scrollbar-corner,
|
||||
::-webkit-scrollbar {
|
||||
@apply w-1.5 h-1.5;
|
||||
}
|
||||
* {
|
||||
::-webkit-scrollbar-corner,
|
||||
::-webkit-scrollbar {
|
||||
@apply w-1.5 h-1.5;
|
||||
}
|
||||
|
||||
.scrollbar-track,
|
||||
::-webkit-scrollbar-corner,
|
||||
::-webkit-scrollbar {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
.scrollbar-track,
|
||||
::-webkit-scrollbar-corner,
|
||||
::-webkit-scrollbar {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
.scrollbar-thumb,
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-500/30 hover:bg-gray-500/50 rounded-full;
|
||||
&:hover {
|
||||
&.scrollbar-thumb,
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-500/30 hover:bg-gray-500/50 rounded-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iframe {
|
||||
|
||||
Reference in New Issue
Block a user