mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-01 15:03:11 +02:00
Ability to open workspace from directory, WorkspaceMeta, and many sync improvements
This commit is contained in:
@@ -16,7 +16,7 @@ import type { HotkeyAction } from '../hooks/useHotKey';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useHttpRequestActions } from '../hooks/useHttpRequestActions';
|
||||
import { useOpenSettings } from '../hooks/useOpenSettings';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { useSwitchWorkspace } from '../hooks/useSwitchWorkspace';
|
||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
@@ -26,7 +26,6 @@ import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { createFolder } from '../lib/commands';
|
||||
import { showDialog, toggleDialog } from '../lib/dialog';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { router } from '../lib/router';
|
||||
@@ -39,6 +38,7 @@ import { Icon } from './core/Icon';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||
import { createFolder } from '../commands/commands';
|
||||
|
||||
interface CommandPaletteGroup {
|
||||
key: string;
|
||||
@@ -71,7 +71,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
const [, setSidebarHidden] = useSidebarHidden();
|
||||
const { baseEnvironment } = useEnvironments();
|
||||
const { mutate: openSettings } = useOpenSettings();
|
||||
const { mutate: openWorkspace } = useOpenWorkspace();
|
||||
const { mutate: switchWorkspace } = useSwitchWorkspace();
|
||||
const { mutate: createHttpRequest } = useCreateHttpRequest();
|
||||
const { mutate: createGrpcRequest } = useCreateGrpcRequest();
|
||||
const { mutate: createEnvironment } = useCreateEnvironment();
|
||||
@@ -315,7 +315,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
workspaceGroup.items.push({
|
||||
key: `switch-workspace-${w.id}`,
|
||||
label: w.name,
|
||||
onSelect: () => openWorkspace({ workspaceId: w.id, inNewWindow: false }),
|
||||
onSelect: () => switchWorkspace({ workspaceId: w.id, inNewWindow: false }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
activeEnvironment?.id,
|
||||
setActiveEnvironmentId,
|
||||
sortedWorkspaces,
|
||||
openWorkspace,
|
||||
switchWorkspace,
|
||||
]);
|
||||
|
||||
const allItems = useMemo(() => groups.flatMap((g) => g.items), [groups]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { createWorkspace } from '../lib/commands';
|
||||
import { createWorkspace } from '../commands/commands';
|
||||
import { Button } from './core/Button';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { VStack } from './core/Stacks';
|
||||
@@ -32,7 +32,11 @@ export function CreateWorkspaceDialog({ hide }: Props) {
|
||||
>
|
||||
<PlainInput require label="Workspace Name" defaultValue={name} onChange={setName} />
|
||||
|
||||
<SyncToFilesystemSetting onChange={setSettingSyncDir} value={settingSyncDir.value} />
|
||||
<SyncToFilesystemSetting
|
||||
onChange={setSettingSyncDir}
|
||||
value={settingSyncDir.value}
|
||||
allowNonEmptyDirectory // Will do initial import when the workspace is created
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugins';
|
||||
import { useWatchWorkspace } from '@yaakapp-internal/sync';
|
||||
import type { ShowToastRequest } from '@yaakapp/api';
|
||||
import { useActiveWorkspace, useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
|
||||
import { useGenerateThemeCss } from '../hooks/useGenerateThemeCss';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||
import { useSyncModelStores } from '../hooks/useSyncModelStores';
|
||||
import { useSyncWorkspace } from '../hooks/useSyncWorkspace';
|
||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||
import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions';
|
||||
@@ -31,12 +29,6 @@ export function GlobalHooks() {
|
||||
useNotificationToast();
|
||||
useActiveWorkspaceChangedToast();
|
||||
|
||||
// Trigger workspace sync operation when workspace files change
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const { debouncedSync } = useSyncWorkspace(activeWorkspace, { debounceMillis: 1000 });
|
||||
useListenToTauriEvent('upserted_model', debouncedSync);
|
||||
useWatchWorkspace(activeWorkspace, debouncedSync);
|
||||
|
||||
// Listen for toasts
|
||||
useListenToTauriEvent<ShowToastRequest>('show_toast', (event) => {
|
||||
showToast({ ...event.payload });
|
||||
|
||||
@@ -29,7 +29,7 @@ export function SelectFile({
|
||||
}: Props) {
|
||||
const handleClick = async () => {
|
||||
const filePath = await open({
|
||||
title: 'Select File',
|
||||
title: directory ? 'Select Folder' : 'Select File',
|
||||
multiple: false,
|
||||
directory,
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ export function SettingsGeneral() {
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const appInfo = useAppInfo();
|
||||
const checkForUpdates = useCheckForUpdates();
|
||||
const checkForUpdates = useCheckForUpdates();
|
||||
|
||||
if (settings == null || workspace == null) {
|
||||
return null;
|
||||
@@ -53,12 +53,12 @@ export function SettingsGeneral() {
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
name="openWorkspace"
|
||||
label="Open Workspace"
|
||||
name="switchWorkspaceBehavior"
|
||||
label="Switch Workspace Behavior"
|
||||
labelPosition="left"
|
||||
labelClassName="w-[12rem]"
|
||||
size="sm"
|
||||
event="workspace-open"
|
||||
event="workspace-switch-behavior"
|
||||
value={
|
||||
settings.openWorkspaceNewWindow === true
|
||||
? 'new'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { useSwitchWorkspace } from '../hooks/useSwitchWorkspace';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
@@ -14,8 +14,8 @@ interface Props {
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
export function OpenWorkspaceDialog({ hide, workspace }: Props) {
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
export function SwitchWorkspaceDialog({ hide, workspace }: Props) {
|
||||
const switchWorkspace = useSwitchWorkspace();
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
const [remember, setRemember] = useState<boolean>(false);
|
||||
@@ -31,7 +31,7 @@ export function OpenWorkspaceDialog({ hide, workspace }: Props) {
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
hide();
|
||||
openWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: false });
|
||||
switchWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: false });
|
||||
if (remember) {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: false });
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export function OpenWorkspaceDialog({ hide, workspace }: Props) {
|
||||
rightSlot={<Icon icon="external_link" />}
|
||||
onClick={() => {
|
||||
hide();
|
||||
openWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true });
|
||||
switchWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true });
|
||||
if (remember) {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: true });
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { readDir } from '@tauri-apps/plugin-fs';
|
||||
import { useState } from 'react';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import { VStack } from './core/Stacks';
|
||||
@@ -6,10 +7,16 @@ import { SelectFile } from './SelectFile';
|
||||
export interface SyncToFilesystemSettingProps {
|
||||
onChange: (args: { value: string | null; enabled: boolean }) => void;
|
||||
value: string | null;
|
||||
allowNonEmptyDirectory?: boolean;
|
||||
}
|
||||
|
||||
export function SyncToFilesystemSetting({ onChange, value }: SyncToFilesystemSettingProps) {
|
||||
export function SyncToFilesystemSetting({
|
||||
onChange,
|
||||
value,
|
||||
allowNonEmptyDirectory,
|
||||
}: SyncToFilesystemSettingProps) {
|
||||
const [useSyncDir, setUseSyncDir] = useState<boolean>(!!value);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<VStack space={1.5} className="w-full">
|
||||
@@ -26,19 +33,28 @@ export function SyncToFilesystemSetting({ onChange, value }: SyncToFilesystemSet
|
||||
}}
|
||||
title="Sync to a filesystem directory"
|
||||
/>
|
||||
{error && <div className="text-danger">{error}</div>}
|
||||
{useSyncDir && (
|
||||
<>
|
||||
<SelectFile
|
||||
directory
|
||||
size="xs"
|
||||
noun="Directory"
|
||||
filePath={value}
|
||||
onChange={({ filePath }) => {
|
||||
if (filePath == null) setUseSyncDir(false);
|
||||
onChange({ value: filePath, enabled: useSyncDir });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<SelectFile
|
||||
directory
|
||||
size="xs"
|
||||
noun="Directory"
|
||||
filePath={value}
|
||||
onChange={async ({ filePath }) => {
|
||||
setError(null);
|
||||
if (filePath == null) {
|
||||
setUseSyncDir(false);
|
||||
} else {
|
||||
const files = await readDir(filePath);
|
||||
if (files.length > 0 && !allowNonEmptyDirectory) {
|
||||
setError('Directory must be empty');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onChange({ value: filePath, enabled: useSyncDir });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VStack>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import {openWorkspace} from "../commands/openWorkspace";
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { useSwitchWorkspace } from '../hooks/useSwitchWorkspace';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
@@ -14,7 +15,7 @@ import type { DropdownItem } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import type { RadioDropdownItem } from './core/RadioDropdown';
|
||||
import { RadioDropdown } from './core/RadioDropdown';
|
||||
import { OpenWorkspaceDialog } from './OpenWorkspaceDialog';
|
||||
import { SwitchWorkspaceDialog } from './SwitchWorkspaceDialog';
|
||||
import { WorkspaceSettingsDialog } from './WorkspaceSettingsDialog';
|
||||
|
||||
type Props = Pick<ButtonProps, 'className' | 'justify' | 'forDropdown' | 'leftSlot'>;
|
||||
@@ -28,7 +29,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const { mutate: deleteSendHistory } = useDeleteSendHistory();
|
||||
const settings = useSettings();
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
const switchWorkspace = useSwitchWorkspace();
|
||||
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
|
||||
|
||||
const orderedWorkspaces = useMemo(
|
||||
@@ -77,6 +78,12 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: createWorkspace,
|
||||
},
|
||||
{
|
||||
key: 'open-workspace',
|
||||
label: 'Open Workspace',
|
||||
leftSlot: <Icon icon="folder" />,
|
||||
onSelect: openWorkspace.mutate,
|
||||
},
|
||||
];
|
||||
|
||||
return { workspaceItems, extraItems };
|
||||
@@ -87,7 +94,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
if (workspaceId == null) return;
|
||||
|
||||
if (typeof openWorkspaceNewWindow === 'boolean') {
|
||||
openWorkspace.mutate({ workspaceId, inNewWindow: openWorkspaceNewWindow });
|
||||
switchWorkspace.mutate({ workspaceId, inNewWindow: openWorkspaceNewWindow });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,13 +102,13 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
if (workspace == null) return;
|
||||
|
||||
showDialog({
|
||||
id: 'open-workspace',
|
||||
id: 'switch-workspace',
|
||||
size: 'sm',
|
||||
title: 'Open Workspace',
|
||||
render: ({ hide }) => <OpenWorkspaceDialog workspace={workspace} hide={hide} />,
|
||||
title: 'Switch Workspace',
|
||||
render: ({ hide }) => <SwitchWorkspaceDialog workspace={workspace} hide={hide} />,
|
||||
});
|
||||
},
|
||||
[openWorkspace, openWorkspaceNewWindow],
|
||||
[switchWorkspace, openWorkspaceNewWindow],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { upsertWorkspaceMeta } from '../commands/upsertWorkspaceMeta';
|
||||
import { useDeleteActiveWorkspace } from '../hooks/useDeleteActiveWorkspace';
|
||||
import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace';
|
||||
import { useWorkspaceMeta } from '../hooks/useWorkspaceMeta';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { Input } from './core/Input';
|
||||
import { Separator } from './core/Separator';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
import { SyncToFilesystemSetting } from './SyncToFilesystemSetting';
|
||||
@@ -15,10 +20,17 @@ interface Props {
|
||||
export function WorkspaceSettingsDialog({ workspaceId, hide }: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const workspace = workspaces.find((w) => w.id === workspaceId);
|
||||
const workspaceMeta = useWorkspaceMeta();
|
||||
const { mutate: updateWorkspace } = useUpdateWorkspace(workspaceId ?? null);
|
||||
const { mutateAsync: deleteActiveWorkspace } = useDeleteActiveWorkspace();
|
||||
|
||||
if (workspace == null) return null;
|
||||
if (workspaceMeta == null)
|
||||
return (
|
||||
<Banner color="danger">
|
||||
<InlineCode>WorkspaceMeta</InlineCode> not found for workspace
|
||||
</Banner>
|
||||
);
|
||||
|
||||
return (
|
||||
<VStack space={3} alignItems="start" className="pb-3 h-full">
|
||||
@@ -39,21 +51,24 @@ export function WorkspaceSettingsDialog({ workspaceId, hide }: Props) {
|
||||
heightMode="auto"
|
||||
/>
|
||||
|
||||
<VStack space={3} className="mt-3" alignItems="start">
|
||||
<VStack space={6} className="mt-3 w-full" alignItems="start">
|
||||
<SyncToFilesystemSetting
|
||||
value={workspace.settingSyncDir}
|
||||
value={workspaceMeta.settingSyncDir}
|
||||
onChange={({ value: settingSyncDir }) => {
|
||||
updateWorkspace({ settingSyncDir });
|
||||
upsertWorkspaceMeta.mutate({ settingSyncDir });
|
||||
}}
|
||||
/>
|
||||
<Separator />
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await deleteActiveWorkspace();
|
||||
hide();
|
||||
const workspace = await deleteActiveWorkspace();
|
||||
if (workspace) {
|
||||
hide(); // Only hide if actually deleted workspace
|
||||
}
|
||||
}}
|
||||
color="danger"
|
||||
variant="border"
|
||||
size="sm"
|
||||
size="xs"
|
||||
>
|
||||
Delete Workspace
|
||||
</Button>
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface DialogProps {
|
||||
children: ReactNode;
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
disableBackdropClose?: boolean;
|
||||
title?: ReactNode;
|
||||
description?: ReactNode;
|
||||
className?: string;
|
||||
@@ -27,6 +28,7 @@ export function Dialog({
|
||||
size = 'full',
|
||||
open,
|
||||
onClose,
|
||||
disableBackdropClose,
|
||||
title,
|
||||
description,
|
||||
hideX,
|
||||
@@ -51,7 +53,7 @@ export function Dialog({
|
||||
);
|
||||
|
||||
return (
|
||||
<Overlay open={open} onClose={onClose} portalName="dialog">
|
||||
<Overlay open={open} onClose={disableBackdropClose ? undefined : onClose} portalName="dialog">
|
||||
<div
|
||||
role="dialog"
|
||||
className={classNames(
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
|
||||
export function Separator({ className, dashed, orientation = 'horizontal', children }: Props) {
|
||||
return (
|
||||
<div role="separator" className={classNames(className, 'flex items-center')}>
|
||||
<div role="separator" className={classNames(className, 'flex items-center w-full')}>
|
||||
{children && (
|
||||
<div className="text-sm text-text-subtlest mr-2 whitespace-nowrap">{children}</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user