Remove base env, fix hotkeys, and QoL improvements

This commit is contained in:
Gregory Schier
2024-01-07 22:24:19 -08:00
parent cb9960bbc8
commit 6f15d1352b
10 changed files with 90 additions and 67 deletions

View File

@@ -2,6 +2,7 @@ import classNames from 'classnames';
import { memo, useCallback, useMemo } from 'react';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
import { useEnvironments } from '../hooks/useEnvironments';
import { useHotkey } from '../hooks/useHotkey';
import type { ButtonProps } from './core/Button';
@@ -22,6 +23,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
}: Props) {
const environments = useEnvironments();
const activeEnvironment = useActiveEnvironment();
const createEnvironment = useCreateEnvironment();
const dialog = useDialog();
const routes = useAppRoutes();
@@ -55,15 +57,25 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
...((environments.length > 0
? [{ type: 'separator', label: 'Environments' }]
: []) as DropdownItem[]),
{
key: 'edit',
label: 'Manage Environments',
hotkeyAction: 'environmentEditor.toggle',
leftSlot: <Icon icon="gear" />,
onSelect: showEnvironmentDialog,
},
environments.length
? {
key: 'edit',
label: 'Manage Environments',
hotkeyAction: 'environmentEditor.toggle',
leftSlot: <Icon icon="gear" />,
onSelect: showEnvironmentDialog,
}
: {
key: 'new',
label: 'New Environment',
leftSlot: <Icon icon="plus" />,
onSelect: async () => {
await createEnvironment.mutateAsync();
showEnvironmentDialog();
},
},
],
[activeEnvironment, environments, routes, showEnvironmentDialog],
[activeEnvironment?.id, createEnvironment, environments, routes, showEnvironmentDialog],
);
return (

View File

@@ -58,23 +58,15 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
{showSidebar && (
<aside className="grid grid-rows-[minmax(0,1fr)_auto] gap-y-0.5 h-full max-w-[250px] pr-3 border-r border-gray-100 -ml-2">
<div className="min-w-0 h-full w-full overflow-y-scroll">
<SidebarButton
active={selectedEnvironment == null}
onClick={() => setSelectedEnvironmentId(null)}
>
Base Environment
</SidebarButton>
<div className="ml-3 pl-2 border-l border-highlight">
{environments.map((e) => (
<SidebarButton
key={e.id}
active={selectedEnvironment?.id === e.id}
onClick={() => setSelectedEnvironmentId(e.id)}
>
{e.name}
</SidebarButton>
))}
</div>
{environments.map((e) => (
<SidebarButton
key={e.id}
active={selectedEnvironment?.id === e.id}
onClick={() => setSelectedEnvironmentId(e.id)}
>
{e.name}
</SidebarButton>
))}
</div>
<Button
size="sm"
@@ -204,11 +196,6 @@ const EnvironmentEditor = function ({
/>
</Dropdown>
)}
{environment == null && (
<span className="text-sm italic text-gray-500">
Base variables available at all times
</span>
)}
</HStack>
<PairEditor
nameAutocomplete={nameAutocomplete}

View File

@@ -5,10 +5,13 @@ import { useExportData } from '../hooks/useExportData';
import { useImportData } from '../hooks/useImportData';
import { useTheme } from '../hooks/useTheme';
import { useUpdateMode } from '../hooks/useUpdateMode';
import type { DropdownProps, DropdownRef } from './core/Dropdown';
import { Button } from './core/Button';
import type { DropdownRef } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { VStack } from './core/Stacks';
import { useDialog } from './DialogContext';
export function SettingsDropdown() {
const importData = useImportData();
@@ -17,6 +20,7 @@ export function SettingsDropdown() {
const appVersion = useAppVersion();
const [updateMode, setUpdateMode] = useUpdateMode();
const dropdownRef = useRef<DropdownRef>(null);
const dialog = useDialog();
return (
<Dropdown
@@ -26,7 +30,29 @@ export function SettingsDropdown() {
key: 'import-data',
label: 'Import',
leftSlot: <Icon icon="download" />,
onSelect: () => importData.mutate(),
onSelect: () => {
dialog.show({
title: 'Import Data',
size: 'sm',
render: ({ hide }) => {
return (
<VStack space={3}>
<p>Insomnia or Postman Collection v2/v2.1 formats are supported</p>
<Button
size="sm"
color="primary"
onClick={async () => {
await importData.mutateAsync();
hide();
}}
>
Select File
</Button>
</VStack>
);
},
});
},
},
{
key: 'export-data',

View File

@@ -1,9 +1,7 @@
import { memo } from 'react';
import { useCreateFolder } from '../hooks/useCreateFolder';
import { useCreateRequest } from '../hooks/useCreateRequest';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useHotkey } from '../hooks/useHotkey';
import { usePrompt } from '../hooks/usePrompt';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { Dropdown } from './core/Dropdown';
import { IconButton } from './core/IconButton';
@@ -12,8 +10,6 @@ import { HStack } from './core/Stacks';
export const SidebarActions = memo(function SidebarActions() {
const createRequest = useCreateRequest();
const createFolder = useCreateFolder();
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
const prompt = usePrompt();
const { hidden, toggle } = useSidebarHidden();
useHotkey('request.create', () => createRequest.mutate({}));
@@ -41,19 +37,6 @@ export const SidebarActions = memo(function SidebarActions() {
label: 'New Folder',
onSelect: () => createFolder.mutate({}),
},
{
key: 'create-workspace',
label: 'New Workspace',
onSelect: async () => {
const name = await prompt({
name: 'name',
label: 'Name',
defaultValue: 'My Workspace',
title: 'New Workspace',
});
createWorkspace.mutate({ name });
},
},
]}
>
<IconButton size="sm" icon="plusCircle" title="Add Resource" />

View File

@@ -41,9 +41,13 @@ export default function Workspace() {
// float/un-float sidebar on window resize
useEffect(() => {
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
if (shouldHide) setFloating(true);
else if (!shouldHide) setFloating(false);
}, [windowSize.width]);
if (shouldHide && !floating) {
setFloating(true);
hide();
} else if (!shouldHide && floating) {
setFloating(false);
}
}, [floating, hide, windowSize.width]);
const unsub = () => {
if (moveState.current !== null) {

View File

@@ -3,6 +3,7 @@ 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 { getRecentEnvironments } from '../hooks/useRecentEnvironments';
@@ -28,6 +29,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
const activeWorkspaceId = activeWorkspace?.id ?? null;
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
const dialog = useDialog();
const prompt = usePrompt();
const routes = useAppRoutes();
@@ -122,6 +124,21 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
onSelect: deleteWorkspace.mutate,
variant: 'danger',
},
{ type: 'separator' },
{
key: 'create-workspace',
label: 'New Workspace',
leftSlot: <Icon icon="plus" />,
onSelect: async () => {
const name = await prompt({
name: 'name',
label: 'Name',
defaultValue: 'My Workspace',
title: 'New Workspace',
});
createWorkspace.mutate({ name });
},
},
];
}, [
activeWorkspace?.name,

View File

@@ -1,6 +1,6 @@
import classNames from 'classnames';
import type { HTMLAttributes, ReactNode } from 'react';
import { forwardRef, memo, useImperativeHandle, useMemo, useRef } from 'react';
import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
import type { HotkeyAction } from '../../hooks/useHotkey';
import { useFormattedHotkey, useHotkey } from '../../hooks/useHotkey';
import { Icon } from './Icon';
@@ -31,8 +31,7 @@ export type ButtonProps = HTMLAttributes<HTMLButtonElement> & {
hotkeyAction?: HotkeyAction;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
{
isLoading,
className,
@@ -114,5 +113,3 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
</button>
);
});
export const Button = memo(_Button);

View File

@@ -426,7 +426,7 @@ const FormRow = memo(function FormRow({
size="sm"
title="Delete header"
onClick={!isLast ? handleDelete : undefined}
className="ml-0.5 group-hover:!opacity-100 focus-visible:!opacity-100"
className="ml-0.5 opacity-0 group-hover:!opacity-100 focus-visible:!opacity-100"
/>
</div>
);

View File

@@ -15,7 +15,7 @@ export function useCreateFolder() {
throw new Error("Cannot create folder when there's no active workspace");
}
patch.name = patch.name || 'New Folder';
patch.sortPriority = patch.sortPriority || Date.now();
patch.sortPriority = patch.sortPriority || -Date.now();
return invoke('create_folder', { workspaceId, ...patch });
},
onSettled: () => trackEvent('folder', 'create'),

View File

@@ -3,15 +3,16 @@ import { invoke } from '@tauri-apps/api';
import { trackEvent } from '../lib/analytics';
import type { HttpRequest } from '../lib/models';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
import { requestsQueryKey, useRequests } from './useRequests';
import { requestsQueryKey } from './useRequests';
export function useCreateRequest() {
const workspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
const activeRequest = useActiveRequest();
const routes = useAppRoutes();
const requests = useRequests();
const queryClient = useQueryClient();
return useMutation<
@@ -23,7 +24,8 @@ export function useCreateRequest() {
if (workspaceId === null) {
throw new Error("Cannot create request when there's no active workspace");
}
patch.sortPriority = patch.sortPriority || maxSortPriority(requests) + 1000;
patch.sortPriority = patch.sortPriority || -Date.now();
patch.folderId = patch.folderId || activeRequest?.folderId;
return invoke('create_request', { workspaceId, name: '', ...patch });
},
onSettled: () => trackEvent('http_request', 'create'),
@@ -40,8 +42,3 @@ export function useCreateRequest() {
},
});
}
function maxSortPriority(requests: HttpRequest[]) {
if (requests.length === 0) return 1000;
return Math.max(...requests.map((r) => r.sortPriority));
}