Better opening workspaces and redirect workspace to recent request

This commit is contained in:
Gregory Schier
2023-04-11 11:11:36 -07:00
parent deeefdcfbf
commit a38691ed53
11 changed files with 146 additions and 70 deletions

View File

@@ -1,5 +1,7 @@
import { createBrowserRouter, Navigate, Outlet, RouterProvider } from 'react-router-dom';
import { routePaths } from '../hooks/useAppRoutes';
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
import { useRecentRequests } from '../hooks/useRecentRequests';
import { useRequests } from '../hooks/useRequests';
import { GlobalHooks } from './GlobalHooks';
import RouteError from './RouteError';
import Workspace from './Workspace';
@@ -21,7 +23,7 @@ const router = createBrowserRouter([
},
{
path: routePaths.workspace({ workspaceId: ':workspaceId' }),
element: <Workspace />,
element: <WorkspaceOrRedirect />,
},
{
path: routePaths.request({
@@ -38,6 +40,23 @@ export function AppRouter() {
return <RouterProvider router={router} />;
}
function WorkspaceOrRedirect() {
const recentRequests = useRecentRequests();
const requests = useRequests();
const request = requests.find((r) => r.id === recentRequests[0]);
const routes = useAppRoutes();
if (request === undefined) {
return <Workspace />;
}
return (
<Navigate
to={routes.paths.request({ workspaceId: request.workspaceId, requestId: request.id })}
/>
);
}
function Layout() {
return (
<>

View File

@@ -57,16 +57,21 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
(requestId: string) => {
const index = requests.findIndex((r) => r.id === requestId);
const request = requests[index];
if (!request || request.id === activeRequestId) return;
if (!request) return;
routes.navigate('request', { requestId, workspaceId: request.workspaceId });
setSelectedIndex(index);
focusActiveRequest(index);
},
[activeRequestId, focusActiveRequest, requests, routes],
[focusActiveRequest, requests, routes],
);
const handleFocus = useCallback(() => focusActiveRequest(), [focusActiveRequest]);
const handleFocus = useCallback(() => {
if (hasFocus) return;
focusActiveRequest(selectedIndex ?? 0);
}, [focusActiveRequest, hasFocus, selectedIndex]);
const handleBlur = useCallback(() => setHasFocus(false), []);
const handleDeleteKey = useCallback(
(e: KeyboardEvent) => {
if (!hasFocus) return;
@@ -85,11 +90,11 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
useTauriEvent(
'focus_sidebar',
() => {
if (hidden) return;
if (hidden || hasFocus) return;
// Select 0 index on focus if none selected
focusActiveRequest(selectedIndex ?? 0);
},
[focusActiveRequest, hidden],
[focusActiveRequest, hidden, activeRequestId],
);
useKeyPressEvent('Enter', (e) => {

View File

@@ -1,3 +1,4 @@
import { invoke } from '@tauri-apps/api';
import classnames from 'classnames';
import { memo, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
@@ -12,6 +13,8 @@ import type { DropdownItem } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { InlineCode } from './core/InlineCode';
import { HStack } from './core/Stacks';
import { useDialog } from './DialogContext';
type Props = {
className?: string;
@@ -24,6 +27,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
const dialog = useDialog();
const prompt = usePrompt();
const routes = useAppRoutes();
@@ -31,10 +35,46 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
const workspaceItems = workspaces.map((w) => ({
key: w.id,
label: w.name,
leftSlot: activeWorkspaceId === w.id ? <Icon icon="check" /> : <Icon icon="empty" />,
onSelect: () => {
if (w.id === activeWorkspaceId) return;
routes.navigate('workspace', { workspaceId: w.id });
onSelect: async () => {
dialog.show({
id: 'open-workspace',
size: 'sm',
title: 'Open Workspace',
description: (
<>
Where would you like to open <InlineCode>{w.name}</InlineCode>?
</>
),
render: ({ hide }) => {
return (
<HStack space={2} justifyContent="end" className="mt-6">
<Button
className="focus"
color="gray"
rightSlot={<Icon icon="openNewWindow" />}
onClick={async () => {
hide();
await invoke('new_window', {
url: routes.paths.workspace({ workspaceId: w.id }),
});
}}
>
New Window
</Button>
<Button
className="focus"
color="gray"
onClick={() => {
hide();
routes.navigate('workspace', { workspaceId: w.id });
}}
>
This Window
</Button>
</HStack>
);
},
});
},
}));

View File

@@ -1,7 +1,6 @@
import classnames from 'classnames';
import type { HTMLAttributes } from 'react';
import type { HTMLAttributes, ReactNode } from 'react';
import { forwardRef, memo, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { Icon } from './Icon';
const colorStyles = {
@@ -15,8 +14,7 @@ const colorStyles = {
danger: 'bg-red-400 text-white enabled:hocus:bg-red-500 ring-red-500/50',
};
export type ButtonProps = HTMLAttributes<HTMLElement> & {
to?: string;
export type ButtonProps = HTMLAttributes<HTMLButtonElement> & {
color?: keyof typeof colorStyles;
isLoading?: boolean;
size?: 'sm' | 'md' | 'xs';
@@ -25,12 +23,12 @@ export type ButtonProps = HTMLAttributes<HTMLElement> & {
forDropdown?: boolean;
disabled?: boolean;
title?: string;
rightSlot?: ReactNode;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _Button = forwardRef<any, ButtonProps>(function Button(
const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
{
to,
isLoading,
className,
children,
@@ -39,6 +37,7 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
type = 'button',
justify = 'center',
size = 'md',
rightSlot,
disabled,
...props
}: ButtonProps,
@@ -48,7 +47,7 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
() =>
classnames(
className,
'outline-none whitespace-nowrap',
'flex-shrink-0 outline-none whitespace-nowrap',
'focus-visible-or-class:ring',
'rounded-md flex items-center',
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
@@ -62,22 +61,14 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
[className, disabled, color, justify, size],
);
if (typeof to === 'string') {
return (
<Link ref={ref} to={to} className={classes} {...props}>
{children}
{forDropdown && <Icon icon="chevronDown" className="ml-1 -mr-1" />}
</Link>
);
} else {
return (
<button ref={ref} type={type} className={classes} disabled={disabled} {...props}>
{isLoading && <Icon icon="update" size={size} className="animate-spin mr-1" />}
{children}
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
</button>
);
}
return (
<button ref={ref} type={type} className={classes} disabled={disabled} {...props}>
{isLoading && <Icon icon="update" size={size} className="animate-spin mr-1" />}
{children}
{rightSlot && <div className="ml-1">{rightSlot}</div>}
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
</button>
);
});
export const Button = memo(_Button);

View File

@@ -62,6 +62,13 @@ export function Dialog({
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
)}
>
<Heading className="text-xl font-semibold w-full" id={titleId}>
{title}
</Heading>
{description && <p id={descriptionId}>{description}</p>}
<div className="mt-4">{children}</div>
{/*Put close at the end so that it's the last thing to be tabbed to*/}
{!hideX && (
<IconButton
onClick={onClose}
@@ -72,11 +79,6 @@ export function Dialog({
className="ml-auto absolute right-1 top-1"
/>
)}
<Heading className="text-xl font-semibold w-full" id={titleId}>
{title}
</Heading>
{description && <p id={descriptionId}>{description}</p>}
<div className="mt-4">{children}</div>
</motion.div>
</div>
</div>

View File

@@ -15,6 +15,7 @@ import React, {
} from 'react';
import { useKey, useKeyPressEvent } from 'react-use';
import { Portal } from '../Portal';
import { Button } from './Button';
import { Separator } from './Separator';
import { VStack } from './Stacks';
@@ -346,16 +347,18 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
if (item.type === 'separator') return <Separator className="my-1.5" />;
return (
<button
<Button
ref={initRef}
size="xs"
tabIndex={-1}
onMouseEnter={(e) => e.currentTarget.focus()}
onMouseLeave={(e) => e.currentTarget.blur()}
onFocus={handleFocus}
onClick={handleClick}
justify="start"
className={classnames(
className,
'min-w-[8rem] outline-none px-2 mx-1.5 h-7 flex items-center text-sm text-gray-700 whitespace-nowrap',
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm text-gray-700 whitespace-nowrap',
'focus:bg-highlight focus:text-gray-900 rounded',
item.variant === 'danger' && 'text-red-600',
)}
@@ -371,6 +374,6 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
{item.label}
</div>
{item.rightSlot && <div className="ml-auto pl-3">{item.rightSlot}</div>}
</button>
</Button>
);
}

View File

@@ -22,6 +22,7 @@ import {
MagicWandIcon,
MagnifyingGlassIcon,
MoonIcon,
OpenInNewWindowIcon,
PaperPlaneIcon,
Pencil2Icon,
PlusCircledIcon,
@@ -66,6 +67,7 @@ const icons = {
magicWand: MagicWandIcon,
magnifyingGlass: MagnifyingGlassIcon,
moon: MoonIcon,
openNewWindow: OpenInNewWindowIcon,
paperPlane: PaperPlaneIcon,
pencil: Pencil2Icon,
plus: PlusIcon,