mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-22 08:38:29 +02:00
Remove base env, fix hotkeys, and QoL improvements
This commit is contained in:
@@ -2,6 +2,7 @@ import classNames from 'classnames';
|
|||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
|
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||||
import { useEnvironments } from '../hooks/useEnvironments';
|
import { useEnvironments } from '../hooks/useEnvironments';
|
||||||
import { useHotkey } from '../hooks/useHotkey';
|
import { useHotkey } from '../hooks/useHotkey';
|
||||||
import type { ButtonProps } from './core/Button';
|
import type { ButtonProps } from './core/Button';
|
||||||
@@ -22,6 +23,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const environments = useEnvironments();
|
const environments = useEnvironments();
|
||||||
const activeEnvironment = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
|
const createEnvironment = useCreateEnvironment();
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
@@ -55,15 +57,25 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
...((environments.length > 0
|
...((environments.length > 0
|
||||||
? [{ type: 'separator', label: 'Environments' }]
|
? [{ type: 'separator', label: 'Environments' }]
|
||||||
: []) as DropdownItem[]),
|
: []) as DropdownItem[]),
|
||||||
{
|
environments.length
|
||||||
key: 'edit',
|
? {
|
||||||
label: 'Manage Environments',
|
key: 'edit',
|
||||||
hotkeyAction: 'environmentEditor.toggle',
|
label: 'Manage Environments',
|
||||||
leftSlot: <Icon icon="gear" />,
|
hotkeyAction: 'environmentEditor.toggle',
|
||||||
onSelect: showEnvironmentDialog,
|
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 (
|
return (
|
||||||
|
|||||||
@@ -58,23 +58,15 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
|
|||||||
{showSidebar && (
|
{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">
|
<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">
|
<div className="min-w-0 h-full w-full overflow-y-scroll">
|
||||||
<SidebarButton
|
{environments.map((e) => (
|
||||||
active={selectedEnvironment == null}
|
<SidebarButton
|
||||||
onClick={() => setSelectedEnvironmentId(null)}
|
key={e.id}
|
||||||
>
|
active={selectedEnvironment?.id === e.id}
|
||||||
Base Environment
|
onClick={() => setSelectedEnvironmentId(e.id)}
|
||||||
</SidebarButton>
|
>
|
||||||
<div className="ml-3 pl-2 border-l border-highlight">
|
{e.name}
|
||||||
{environments.map((e) => (
|
</SidebarButton>
|
||||||
<SidebarButton
|
))}
|
||||||
key={e.id}
|
|
||||||
active={selectedEnvironment?.id === e.id}
|
|
||||||
onClick={() => setSelectedEnvironmentId(e.id)}
|
|
||||||
>
|
|
||||||
{e.name}
|
|
||||||
</SidebarButton>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -204,11 +196,6 @@ const EnvironmentEditor = function ({
|
|||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
{environment == null && (
|
|
||||||
<span className="text-sm italic text-gray-500">
|
|
||||||
Base variables available at all times
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</HStack>
|
</HStack>
|
||||||
<PairEditor
|
<PairEditor
|
||||||
nameAutocomplete={nameAutocomplete}
|
nameAutocomplete={nameAutocomplete}
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import { useExportData } from '../hooks/useExportData';
|
|||||||
import { useImportData } from '../hooks/useImportData';
|
import { useImportData } from '../hooks/useImportData';
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
import { useUpdateMode } from '../hooks/useUpdateMode';
|
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 { Dropdown } from './core/Dropdown';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
|
import { VStack } from './core/Stacks';
|
||||||
|
import { useDialog } from './DialogContext';
|
||||||
|
|
||||||
export function SettingsDropdown() {
|
export function SettingsDropdown() {
|
||||||
const importData = useImportData();
|
const importData = useImportData();
|
||||||
@@ -17,6 +20,7 @@ export function SettingsDropdown() {
|
|||||||
const appVersion = useAppVersion();
|
const appVersion = useAppVersion();
|
||||||
const [updateMode, setUpdateMode] = useUpdateMode();
|
const [updateMode, setUpdateMode] = useUpdateMode();
|
||||||
const dropdownRef = useRef<DropdownRef>(null);
|
const dropdownRef = useRef<DropdownRef>(null);
|
||||||
|
const dialog = useDialog();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -26,7 +30,29 @@ export function SettingsDropdown() {
|
|||||||
key: 'import-data',
|
key: 'import-data',
|
||||||
label: 'Import',
|
label: 'Import',
|
||||||
leftSlot: <Icon icon="download" />,
|
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',
|
key: 'export-data',
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useCreateFolder } from '../hooks/useCreateFolder';
|
import { useCreateFolder } from '../hooks/useCreateFolder';
|
||||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
|
||||||
import { useHotkey } from '../hooks/useHotkey';
|
import { useHotkey } from '../hooks/useHotkey';
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
@@ -12,8 +10,6 @@ import { HStack } from './core/Stacks';
|
|||||||
export const SidebarActions = memo(function SidebarActions() {
|
export const SidebarActions = memo(function SidebarActions() {
|
||||||
const createRequest = useCreateRequest();
|
const createRequest = useCreateRequest();
|
||||||
const createFolder = useCreateFolder();
|
const createFolder = useCreateFolder();
|
||||||
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
|
|
||||||
const prompt = usePrompt();
|
|
||||||
const { hidden, toggle } = useSidebarHidden();
|
const { hidden, toggle } = useSidebarHidden();
|
||||||
|
|
||||||
useHotkey('request.create', () => createRequest.mutate({}));
|
useHotkey('request.create', () => createRequest.mutate({}));
|
||||||
@@ -41,19 +37,6 @@ export const SidebarActions = memo(function SidebarActions() {
|
|||||||
label: 'New Folder',
|
label: 'New Folder',
|
||||||
onSelect: () => createFolder.mutate({}),
|
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" />
|
<IconButton size="sm" icon="plusCircle" title="Add Resource" />
|
||||||
|
|||||||
@@ -41,9 +41,13 @@ export default function Workspace() {
|
|||||||
// float/un-float sidebar on window resize
|
// float/un-float sidebar on window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
|
const shouldHide = windowSize.width <= WINDOW_FLOATING_SIDEBAR_WIDTH;
|
||||||
if (shouldHide) setFloating(true);
|
if (shouldHide && !floating) {
|
||||||
else if (!shouldHide) setFloating(false);
|
setFloating(true);
|
||||||
}, [windowSize.width]);
|
hide();
|
||||||
|
} else if (!shouldHide && floating) {
|
||||||
|
setFloating(false);
|
||||||
|
}
|
||||||
|
}, [floating, hide, windowSize.width]);
|
||||||
|
|
||||||
const unsub = () => {
|
const unsub = () => {
|
||||||
if (moveState.current !== null) {
|
if (moveState.current !== null) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import classNames from 'classnames';
|
|||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
|
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||||
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
|
||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||||
@@ -28,6 +29,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
||||||
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
||||||
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
||||||
|
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
@@ -122,6 +124,21 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
onSelect: deleteWorkspace.mutate,
|
onSelect: deleteWorkspace.mutate,
|
||||||
variant: 'danger',
|
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,
|
activeWorkspace?.name,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes, ReactNode } from 'react';
|
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 type { HotkeyAction } from '../../hooks/useHotkey';
|
||||||
import { useFormattedHotkey, useHotkey } from '../../hooks/useHotkey';
|
import { useFormattedHotkey, useHotkey } from '../../hooks/useHotkey';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
@@ -31,8 +31,7 @@ export type ButtonProps = HTMLAttributes<HTMLButtonElement> & {
|
|||||||
hotkeyAction?: HotkeyAction;
|
hotkeyAction?: HotkeyAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||||
const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
||||||
{
|
{
|
||||||
isLoading,
|
isLoading,
|
||||||
className,
|
className,
|
||||||
@@ -114,5 +113,3 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Button = memo(_Button);
|
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ const FormRow = memo(function FormRow({
|
|||||||
size="sm"
|
size="sm"
|
||||||
title="Delete header"
|
title="Delete header"
|
||||||
onClick={!isLast ? handleDelete : undefined}
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function useCreateFolder() {
|
|||||||
throw new Error("Cannot create folder when there's no active workspace");
|
throw new Error("Cannot create folder when there's no active workspace");
|
||||||
}
|
}
|
||||||
patch.name = patch.name || 'New Folder';
|
patch.name = patch.name || 'New Folder';
|
||||||
patch.sortPriority = patch.sortPriority || Date.now();
|
patch.sortPriority = patch.sortPriority || -Date.now();
|
||||||
return invoke('create_folder', { workspaceId, ...patch });
|
return invoke('create_folder', { workspaceId, ...patch });
|
||||||
},
|
},
|
||||||
onSettled: () => trackEvent('folder', 'create'),
|
onSettled: () => trackEvent('folder', 'create'),
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ import { invoke } from '@tauri-apps/api';
|
|||||||
import { trackEvent } from '../lib/analytics';
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||||
|
import { useActiveRequest } from './useActiveRequest';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { useAppRoutes } from './useAppRoutes';
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
import { requestsQueryKey, useRequests } from './useRequests';
|
import { requestsQueryKey } from './useRequests';
|
||||||
|
|
||||||
export function useCreateRequest() {
|
export function useCreateRequest() {
|
||||||
const workspaceId = useActiveWorkspaceId();
|
const workspaceId = useActiveWorkspaceId();
|
||||||
const activeEnvironmentId = useActiveEnvironmentId();
|
const activeEnvironmentId = useActiveEnvironmentId();
|
||||||
|
const activeRequest = useActiveRequest();
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
const requests = useRequests();
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<
|
return useMutation<
|
||||||
@@ -23,7 +24,8 @@ export function useCreateRequest() {
|
|||||||
if (workspaceId === null) {
|
if (workspaceId === null) {
|
||||||
throw new Error("Cannot create request when there's no active workspace");
|
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 });
|
return invoke('create_request', { workspaceId, name: '', ...patch });
|
||||||
},
|
},
|
||||||
onSettled: () => trackEvent('http_request', 'create'),
|
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));
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user