mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-10 03:03:37 +02:00
Merge remote-tracking branch 'origin/main' into cli-command-architecture
This commit is contained in:
@@ -3,23 +3,30 @@ import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-intern
|
|||||||
import { MoveToWorkspaceDialog } from '../components/MoveToWorkspaceDialog';
|
import { MoveToWorkspaceDialog } from '../components/MoveToWorkspaceDialog';
|
||||||
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||||
import { createFastMutation } from '../hooks/useFastMutation';
|
import { createFastMutation } from '../hooks/useFastMutation';
|
||||||
|
import { pluralizeCount } from '../lib/pluralize';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
import { jotaiStore } from '../lib/jotai';
|
import { jotaiStore } from '../lib/jotai';
|
||||||
|
|
||||||
export const moveToWorkspace = createFastMutation({
|
export const moveToWorkspace = createFastMutation({
|
||||||
mutationKey: ['move_workspace'],
|
mutationKey: ['move_workspace'],
|
||||||
mutationFn: async (request: HttpRequest | GrpcRequest | WebsocketRequest) => {
|
mutationFn: async (requests: (HttpRequest | GrpcRequest | WebsocketRequest)[]) => {
|
||||||
const activeWorkspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
const activeWorkspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||||
if (activeWorkspaceId == null) return;
|
if (activeWorkspaceId == null) return;
|
||||||
|
if (requests.length === 0) return;
|
||||||
|
|
||||||
|
const title =
|
||||||
|
requests.length === 1
|
||||||
|
? 'Move Request'
|
||||||
|
: `Move ${pluralizeCount('Request', requests.length)}`;
|
||||||
|
|
||||||
showDialog({
|
showDialog({
|
||||||
id: 'change-workspace',
|
id: 'change-workspace',
|
||||||
title: 'Move Workspace',
|
title,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
render: ({ hide }) => (
|
render: ({ hide }) => (
|
||||||
<MoveToWorkspaceDialog
|
<MoveToWorkspaceDialog
|
||||||
onDone={hide}
|
onDone={hide}
|
||||||
request={request}
|
requests={requests}
|
||||||
activeWorkspaceId={activeWorkspaceId}
|
activeWorkspaceId={activeWorkspaceId}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-intern
|
|||||||
import { patchModel, workspacesAtom } from '@yaakapp-internal/models';
|
import { patchModel, workspacesAtom } from '@yaakapp-internal/models';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { pluralizeCount } from '../lib/pluralize';
|
||||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||||
import { router } from '../lib/router';
|
import { router } from '../lib/router';
|
||||||
import { showToast } from '../lib/toast';
|
import { showToast } from '../lib/toast';
|
||||||
@@ -12,18 +13,21 @@ import { VStack } from './core/Stacks';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
activeWorkspaceId: string;
|
activeWorkspaceId: string;
|
||||||
request: HttpRequest | GrpcRequest | WebsocketRequest;
|
requests: (HttpRequest | GrpcRequest | WebsocketRequest)[];
|
||||||
onDone: () => void;
|
onDone: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Props) {
|
export function MoveToWorkspaceDialog({ onDone, requests, activeWorkspaceId }: Props) {
|
||||||
const workspaces = useAtomValue(workspacesAtom);
|
const workspaces = useAtomValue(workspacesAtom);
|
||||||
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(activeWorkspaceId);
|
const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(activeWorkspaceId);
|
||||||
|
|
||||||
|
const targetWorkspace = workspaces.find((w) => w.id === selectedWorkspaceId);
|
||||||
|
const isSameWorkspace = selectedWorkspaceId === activeWorkspaceId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack space={4} className="mb-4">
|
<VStack space={4} className="mb-4">
|
||||||
<Select
|
<Select
|
||||||
label="New Workspace"
|
label="Target Workspace"
|
||||||
name="workspace"
|
name="workspace"
|
||||||
value={selectedWorkspaceId}
|
value={selectedWorkspaceId}
|
||||||
onChange={setSelectedWorkspaceId}
|
onChange={setSelectedWorkspaceId}
|
||||||
@@ -34,27 +38,31 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={selectedWorkspaceId === activeWorkspaceId}
|
disabled={isSameWorkspace}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const patch = {
|
const patch = {
|
||||||
workspaceId: selectedWorkspaceId,
|
workspaceId: selectedWorkspaceId,
|
||||||
folderId: null,
|
folderId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
await patchModel(request, patch);
|
await Promise.all(requests.map((r) => patchModel(r, patch)));
|
||||||
|
|
||||||
// Hide after a moment, to give time for request to disappear
|
// Hide after a moment, to give time for requests to disappear
|
||||||
setTimeout(onDone, 100);
|
setTimeout(onDone, 100);
|
||||||
showToast({
|
showToast({
|
||||||
id: 'workspace-moved',
|
id: 'workspace-moved',
|
||||||
message: (
|
message:
|
||||||
<>
|
requests.length === 1 && requests[0] != null ? (
|
||||||
<InlineCode>{resolvedModelName(request)}</InlineCode> moved to{' '}
|
<>
|
||||||
<InlineCode>
|
<InlineCode>{resolvedModelName(requests[0])}</InlineCode> moved to{' '}
|
||||||
{workspaces.find((w) => w.id === selectedWorkspaceId)?.name ?? 'unknown'}
|
<InlineCode>{targetWorkspace?.name ?? 'unknown'}</InlineCode>
|
||||||
</InlineCode>
|
</>
|
||||||
</>
|
) : (
|
||||||
),
|
<>
|
||||||
|
{pluralizeCount('request', requests.length)} moved to{' '}
|
||||||
|
<InlineCode>{targetWorkspace?.name ?? 'unknown'}</InlineCode>
|
||||||
|
</>
|
||||||
|
),
|
||||||
action: ({ hide }) => (
|
action: ({ hide }) => (
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
@@ -74,7 +82,7 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Move
|
{requests.length === 1 ? 'Move' : `Move ${pluralizeCount('Request', requests.length)}`}
|
||||||
</Button>
|
</Button>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ function Sidebar({ className }: { className?: string }) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
'sidebar.selected.duplicate': {
|
'sidebar.selected.duplicate': {
|
||||||
|
// Higher priority so this takes precedence over model.duplicate (same Meta+d binding)
|
||||||
priority: 10,
|
priority: 10,
|
||||||
enable,
|
enable,
|
||||||
cb: async (items: SidebarModel[]) => {
|
cb: async (items: SidebarModel[]) => {
|
||||||
@@ -290,6 +291,18 @@ function Sidebar({ className }: { className?: string }) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'sidebar.selected.move': {
|
||||||
|
enable,
|
||||||
|
cb: async (items: SidebarModel[]) => {
|
||||||
|
const requests = items.filter(
|
||||||
|
(i): i is HttpRequest | GrpcRequest | WebsocketRequest =>
|
||||||
|
i.model === 'http_request' || i.model === 'grpc_request' || i.model === 'websocket_request'
|
||||||
|
);
|
||||||
|
if (requests.length > 0) {
|
||||||
|
moveToWorkspace.mutate(requests);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
'request.send': {
|
'request.send': {
|
||||||
enable,
|
enable,
|
||||||
cb: async (items: SidebarModel[]) => {
|
cb: async (items: SidebarModel[]) => {
|
||||||
@@ -320,6 +333,10 @@ function Sidebar({ className }: { className?: string }) {
|
|||||||
|
|
||||||
const workspaces = jotaiStore.get(workspacesAtom);
|
const workspaces = jotaiStore.get(workspacesAtom);
|
||||||
const onlyHttpRequests = items.every((i) => i.model === 'http_request');
|
const onlyHttpRequests = items.every((i) => i.model === 'http_request');
|
||||||
|
const requestItems = items.filter(
|
||||||
|
(i) =>
|
||||||
|
i.model === 'http_request' || i.model === 'grpc_request' || i.model === 'websocket_request',
|
||||||
|
);
|
||||||
|
|
||||||
const initialItems: ContextMenuProps['items'] = [
|
const initialItems: ContextMenuProps['items'] = [
|
||||||
{
|
{
|
||||||
@@ -416,16 +433,13 @@ function Sidebar({ className }: { className?: string }) {
|
|||||||
onSelect: () => actions['sidebar.selected.duplicate'].cb(items),
|
onSelect: () => actions['sidebar.selected.duplicate'].cb(items),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Move',
|
label: items.length <= 1 ? 'Move' : `Move ${requestItems.length} Requests`,
|
||||||
|
hotKeyAction: 'sidebar.selected.move',
|
||||||
|
hotKeyLabelOnly: true,
|
||||||
leftSlot: <Icon icon="arrow_right_circle" />,
|
leftSlot: <Icon icon="arrow_right_circle" />,
|
||||||
hidden:
|
hidden: workspaces.length <= 1 || requestItems.length === 0 || requestItems.length !== items.length,
|
||||||
workspaces.length <= 1 ||
|
|
||||||
items.length > 1 ||
|
|
||||||
child.model === 'folder' ||
|
|
||||||
child.model === 'workspace',
|
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
if (child.model === 'folder' || child.model === 'workspace') return;
|
actions['sidebar.selected.move'].cb(items);
|
||||||
moveToWorkspace.mutate(child);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type HotkeyAction =
|
|||||||
| 'sidebar.filter'
|
| 'sidebar.filter'
|
||||||
| 'sidebar.selected.delete'
|
| 'sidebar.selected.delete'
|
||||||
| 'sidebar.selected.duplicate'
|
| 'sidebar.selected.duplicate'
|
||||||
|
| 'sidebar.selected.move'
|
||||||
| 'sidebar.selected.rename'
|
| 'sidebar.selected.rename'
|
||||||
| 'sidebar.expand_all'
|
| 'sidebar.expand_all'
|
||||||
| 'sidebar.collapse_all'
|
| 'sidebar.collapse_all'
|
||||||
@@ -58,6 +59,7 @@ const defaultHotkeysMac: Record<HotkeyAction, string[]> = {
|
|||||||
'sidebar.collapse_all': ['Meta+Shift+Minus'],
|
'sidebar.collapse_all': ['Meta+Shift+Minus'],
|
||||||
'sidebar.selected.delete': ['Delete', 'Meta+Backspace'],
|
'sidebar.selected.delete': ['Delete', 'Meta+Backspace'],
|
||||||
'sidebar.selected.duplicate': ['Meta+d'],
|
'sidebar.selected.duplicate': ['Meta+d'],
|
||||||
|
'sidebar.selected.move': [],
|
||||||
'sidebar.selected.rename': ['Enter'],
|
'sidebar.selected.rename': ['Enter'],
|
||||||
'sidebar.focus': ['Meta+b'],
|
'sidebar.focus': ['Meta+b'],
|
||||||
'sidebar.context_menu': ['Control+Enter'],
|
'sidebar.context_menu': ['Control+Enter'],
|
||||||
@@ -87,6 +89,7 @@ const defaultHotkeysOther: Record<HotkeyAction, string[]> = {
|
|||||||
'sidebar.collapse_all': ['Control+Shift+Minus'],
|
'sidebar.collapse_all': ['Control+Shift+Minus'],
|
||||||
'sidebar.selected.delete': ['Delete', 'Control+Backspace'],
|
'sidebar.selected.delete': ['Delete', 'Control+Backspace'],
|
||||||
'sidebar.selected.duplicate': ['Control+d'],
|
'sidebar.selected.duplicate': ['Control+d'],
|
||||||
|
'sidebar.selected.move': [],
|
||||||
'sidebar.selected.rename': ['Enter'],
|
'sidebar.selected.rename': ['Enter'],
|
||||||
'sidebar.focus': ['Control+b'],
|
'sidebar.focus': ['Control+b'],
|
||||||
'sidebar.context_menu': ['Alt+Insert'],
|
'sidebar.context_menu': ['Alt+Insert'],
|
||||||
@@ -141,6 +144,7 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
|||||||
'sidebar.collapse_all': 'Collapse All Folders',
|
'sidebar.collapse_all': 'Collapse All Folders',
|
||||||
'sidebar.selected.delete': 'Delete Selected Sidebar Item',
|
'sidebar.selected.delete': 'Delete Selected Sidebar Item',
|
||||||
'sidebar.selected.duplicate': 'Duplicate Selected Sidebar Item',
|
'sidebar.selected.duplicate': 'Duplicate Selected Sidebar Item',
|
||||||
|
'sidebar.selected.move': 'Move Selected to Workspace',
|
||||||
'sidebar.selected.rename': 'Rename Selected Sidebar Item',
|
'sidebar.selected.rename': 'Rename Selected Sidebar Item',
|
||||||
'sidebar.focus': 'Focus or Toggle Sidebar',
|
'sidebar.focus': 'Focus or Toggle Sidebar',
|
||||||
'sidebar.context_menu': 'Show Context Menu',
|
'sidebar.context_menu': 'Show Context Menu',
|
||||||
|
|||||||
Reference in New Issue
Block a user