mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-22 01:19:13 +01:00
Filesystem Sync (#142)
This commit is contained in:
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useCommands } from '../hooks/useCommands';
|
||||
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
|
||||
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
|
||||
@@ -53,21 +54,22 @@ type CommandPaletteItem = {
|
||||
|
||||
const MAX_PER_GROUP = 8;
|
||||
|
||||
export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
const [command, setCommand] = useDebouncedState<string>('', 150);
|
||||
const [selectedItemKey, setSelectedItemKey] = useState<string | null>(null);
|
||||
const [activeEnvironment, setActiveEnvironmentId] = useActiveEnvironment();
|
||||
const httpRequestActions = useHttpRequestActions();
|
||||
const workspaces = useWorkspaces();
|
||||
const { subEnvironments } = useEnvironments();
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const recentEnvironments = useRecentEnvironments();
|
||||
const recentWorkspaces = useRecentWorkspaces();
|
||||
const requests = useRequests();
|
||||
const activeRequest = useActiveRequest();
|
||||
const [recentRequests] = useRecentRequests();
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const createHttpRequest = useCreateHttpRequest();
|
||||
const { createFolder } = useCommands();
|
||||
const [activeCookieJar] = useActiveCookieJar();
|
||||
const createGrpcRequest = useCreateGrpcRequest();
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
@@ -91,13 +93,18 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
{
|
||||
key: 'app.create',
|
||||
label: 'Create Workspace',
|
||||
onSelect: createWorkspace.mutate,
|
||||
onSelect: createWorkspace,
|
||||
},
|
||||
{
|
||||
key: 'http_request.create',
|
||||
label: 'Create HTTP Request',
|
||||
onSelect: () => createHttpRequest.mutate({}),
|
||||
},
|
||||
{
|
||||
key: 'folder.create',
|
||||
label: 'Create Folder',
|
||||
onSelect: () => createFolder.mutate({}),
|
||||
},
|
||||
{
|
||||
key: 'cookies.show',
|
||||
label: 'Show Cookies',
|
||||
@@ -183,9 +190,10 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
activeRequest,
|
||||
baseEnvironment,
|
||||
createEnvironment,
|
||||
createFolder,
|
||||
createGrpcRequest,
|
||||
createHttpRequest,
|
||||
createWorkspace.mutate,
|
||||
createWorkspace,
|
||||
deleteRequest.mutate,
|
||||
dialog,
|
||||
httpRequestActions,
|
||||
@@ -230,9 +238,14 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
}, [subEnvironments, recentEnvironments]);
|
||||
|
||||
const sortedWorkspaces = useMemo(() => {
|
||||
const r = [...workspaces].sort((a, b) => {
|
||||
const aRecentIndex = recentWorkspaces.indexOf(a.id);
|
||||
const bRecentIndex = recentWorkspaces.indexOf(b.id);
|
||||
if (recentWorkspaces == null) {
|
||||
// Should never happen
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
const r = [...workspaces].sort((a, b) => {
|
||||
const aRecentIndex = recentWorkspaces?.indexOf(a.id);
|
||||
const bRecentIndex = recentWorkspaces?.indexOf(b.id);
|
||||
|
||||
if (aRecentIndex >= 0 && bRecentIndex >= 0) {
|
||||
return aRecentIndex - bRecentIndex;
|
||||
@@ -309,7 +322,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
for (const w of sortedWorkspaces) {
|
||||
workspaceGroup.items.push({
|
||||
key: `switch-workspace-${w.id}`,
|
||||
label: w.id + ' - ' + w.name,
|
||||
label: w.name,
|
||||
onSelect: () => openWorkspace.mutate({ workspaceId: w.id, inNewWindow: false }),
|
||||
});
|
||||
}
|
||||
54
src-web/components/CreateWorkspaceDialog.tsx
Normal file
54
src-web/components/CreateWorkspaceDialog.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useState } from 'react';
|
||||
import { useCommands } from '../hooks/useCommands';
|
||||
import { Button } from './core/Button';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
import { SelectFile } from './SelectFile';
|
||||
|
||||
interface Props {
|
||||
hide: () => void;
|
||||
}
|
||||
|
||||
export function CreateWorkspaceDialog({ hide }: Props) {
|
||||
const [name, setName] = useState<string>('');
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const [settingSyncDir, setSettingSyncDir] = useState<string | null>(null);
|
||||
const { createWorkspace } = useCommands();
|
||||
|
||||
return (
|
||||
<VStack
|
||||
as="form"
|
||||
space={3}
|
||||
alignItems="start"
|
||||
className="pb-3 max-h-[50vh]"
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
await createWorkspace.mutateAsync({ name, description, settingSyncDir });
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<PlainInput label="Workspace Name" defaultValue={name} onChange={setName} />
|
||||
|
||||
<MarkdownEditor
|
||||
name="workspace-description"
|
||||
placeholder="Workspace description"
|
||||
className="min-h-[10rem] max-h-[25rem] border border-border px-2"
|
||||
defaultValue={description}
|
||||
stateKey={null}
|
||||
onChange={setDescription}
|
||||
heightMode="auto"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<SelectFile
|
||||
directory
|
||||
noun="Sync Directory"
|
||||
filePath={settingSyncDir}
|
||||
onChange={({ filePath }) => setSettingSyncDir(filePath)}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit">Create Workspace</Button>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugin';
|
||||
import { useEnsureActiveCookieJar, useSubscribeActiveCookieJarId } from '../hooks/useActiveCookieJar';
|
||||
import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugins';
|
||||
import {
|
||||
useEnsureActiveCookieJar,
|
||||
useSubscribeActiveCookieJarId,
|
||||
} from '../hooks/useActiveCookieJar';
|
||||
import { useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
@@ -13,10 +16,10 @@ import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import {useRecentCookieJars, useSubscribeRecentCookieJars} from '../hooks/useRecentCookieJars';
|
||||
import {useRecentEnvironments, useSubscribeRecentEnvironments} from '../hooks/useRecentEnvironments';
|
||||
import { useSubscribeRecentCookieJars } from '../hooks/useRecentCookieJars';
|
||||
import { useSubscribeRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useSubscribeRecentRequests } from '../hooks/useRecentRequests';
|
||||
import {useRecentWorkspaces, useSubscribeRecentWorkspaces} from '../hooks/useRecentWorkspaces';
|
||||
import { useSubscribeRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||
import { useSyncModelStores } from '../hooks/useSyncModelStores';
|
||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||
@@ -42,10 +45,6 @@ export function GlobalHooks() {
|
||||
useSubscribeRecentEnvironments();
|
||||
useSubscribeRecentCookieJars();
|
||||
|
||||
// Include here so they always update, even if no component references them
|
||||
useRecentWorkspaces();
|
||||
useRecentEnvironments();
|
||||
useRecentCookieJars();
|
||||
useSyncWorkspaceChildModels();
|
||||
useSubscribeTemplateFunctions();
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ export function GrpcConnectionLayout({ style }: Props) {
|
||||
) : messages.length >= 0 ? (
|
||||
<GrpcConnectionMessagesPane activeRequest={activeRequest} methodType={methodType} />
|
||||
) : (
|
||||
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.focus', 'urlBar.focus']} />
|
||||
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.focus', 'url_bar.focus']} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -64,7 +64,7 @@ export function MarkdownEditor({
|
||||
defaultValue.length === 0 ? (
|
||||
<p className="text-text-subtle">No description</p>
|
||||
) : (
|
||||
<Prose className="max-w-xl">
|
||||
<Prose className="max-w-xl overflow-y-auto max-h-full">
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
|
||||
@@ -64,17 +64,17 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
|
||||
</InlineCode>
|
||||
</>
|
||||
),
|
||||
action: (
|
||||
action: ({ hide }) => (
|
||||
<Button
|
||||
size="xs"
|
||||
color="secondary"
|
||||
className="mr-auto min-w-[5rem]"
|
||||
onClick={async () => {
|
||||
toast.hide('workspace-moved');
|
||||
await navigate({
|
||||
to: '/workspaces/$workspaceId',
|
||||
params: { workspaceId: selectedWorkspaceId },
|
||||
});
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
Switch to Workspace
|
||||
|
||||
@@ -12,7 +12,7 @@ export function RedirectToLatestWorkspace() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaces.length === 0) {
|
||||
if (workspaces.length === 0 || recentWorkspaces == null) {
|
||||
console.log('No workspaces found to redirect to. Skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export const ResponsePane = memo(function ResponsePane({
|
||||
>
|
||||
{activeResponse == null ? (
|
||||
<HotKeyList
|
||||
hotkeys={['http_request.send', 'http_request.create', 'sidebar.focus', 'urlBar.focus']}
|
||||
hotkeys={['http_request.send', 'http_request.create', 'sidebar.focus', 'url_bar.focus']}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button } from './core/Button';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { HStack } from './core/Stacks';
|
||||
|
||||
type Props = ButtonProps & {
|
||||
type Props = Omit<ButtonProps, 'type'> & {
|
||||
onChange: (value: { filePath: string | null; contentType: string | null }) => void;
|
||||
filePath: string | null;
|
||||
directory?: boolean;
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
TemplateFunctionHttpRequestArg,
|
||||
TemplateFunctionSelectArg,
|
||||
TemplateFunctionTextArg,
|
||||
} from '@yaakapp-internal/plugin';
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import type { FnArg, Tokens } from '@yaakapp-internal/templates';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { ShowToastRequest } from '@yaakapp-internal/plugin';
|
||||
import type { ShowToastRequest } from '@yaakapp-internal/plugins';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import React, {type ReactNode, useContext, useMemo, useRef, useState} from 'react';
|
||||
import React, { type ReactNode, useContext, useMemo, useRef, useState } from 'react';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { generateId } from '../lib/generateId';
|
||||
import {Toast, type ToastProps} from './core/Toast';
|
||||
import { Toast, type ToastProps } from './core/Toast';
|
||||
import { Portal } from './Portal';
|
||||
import { ToastContext } from './ToastContext';
|
||||
|
||||
@@ -29,7 +29,6 @@ export interface Actions {
|
||||
hide: (id: string) => void;
|
||||
}
|
||||
|
||||
|
||||
export const ToastProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [toasts, setToasts] = useState<ToastState['toasts']>([]);
|
||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
@@ -49,7 +49,7 @@ export const UrlBar = memo(function UrlBar({
|
||||
const inputRef = useRef<EditorView>(null);
|
||||
const [isFocused, setIsFocused] = useState<boolean>(false);
|
||||
|
||||
useHotKey('urlBar.focus', () => {
|
||||
useHotKey('url_bar.focus', () => {
|
||||
const head = inputRef.current?.state.doc.length ?? 0;
|
||||
inputRef.current?.dispatch({
|
||||
selection: { anchor: 0, head },
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
import { applySync, calculateSync } from '@yaakapp-internal/sync';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useConfirm } from '../hooks/useConfirm';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
|
||||
import { useDialog } from '../hooks/useDialog';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useToast } from '../hooks/useToast';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { getWorkspace } from '../lib/store';
|
||||
import type { ButtonProps } from './core/Button';
|
||||
import { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { RadioDropdownItem } from './core/RadioDropdown';
|
||||
import { RadioDropdown } from './core/RadioDropdown';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { OpenWorkspaceDialog } from './OpenWorkspaceDialog';
|
||||
import { WorkspaceSettingsDialog } from './WorkpaceSettingsDialog';
|
||||
import { WorkspaceSettingsDialog } from './WorkspaceSettingsDialog';
|
||||
|
||||
type Props = Pick<ButtonProps, 'className' | 'justify' | 'forDropdown' | 'leftSlot'>;
|
||||
|
||||
@@ -25,10 +32,11 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
}: Props) {
|
||||
const workspaces = useWorkspaces();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
||||
const { mutate: createWorkspace } = useCreateWorkspace();
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const { mutate: deleteSendHistory } = useDeleteSendHistory();
|
||||
const dialog = useDialog();
|
||||
const confirm = useConfirm();
|
||||
const toast = useToast();
|
||||
const settings = useSettings();
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
|
||||
@@ -46,7 +54,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
key: w.id,
|
||||
label: w.name,
|
||||
value: w.id,
|
||||
leftSlot: w.id === activeWorkspaceId ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
leftSlot: w.id === activeWorkspace?.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
||||
}));
|
||||
|
||||
const extraItems: DropdownItem[] = [
|
||||
@@ -54,6 +62,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
key: 'workspace-settings',
|
||||
label: 'Workspace Settings',
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
hotKeyAction: 'workspace_settings.show',
|
||||
onSelect: async () => {
|
||||
dialog.show({
|
||||
id: 'workspace-settings',
|
||||
@@ -63,6 +72,96 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'sync',
|
||||
label: 'Sync Workspace',
|
||||
leftSlot: <Icon icon="folder_sync" />,
|
||||
hidden: !activeWorkspace?.settingSyncDir,
|
||||
onSelect: async () => {
|
||||
if (activeWorkspace == null) return;
|
||||
|
||||
const ops = await calculateSync(activeWorkspace);
|
||||
if (ops.length === 0) {
|
||||
toast.show({
|
||||
id: 'no-sync-changes',
|
||||
message: 'No changes detected for sync',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const dbChanges = ops.filter((o) => o.type.startsWith('db'));
|
||||
|
||||
if (dbChanges.length === 0) {
|
||||
await applySync(activeWorkspace, ops);
|
||||
toast.show({
|
||||
id: 'applied-sync-changes',
|
||||
message: `Applied ${pluralizeCount('change', ops.length)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await confirm({
|
||||
id: 'commit-sync',
|
||||
title: 'Filesystem Changes Detected',
|
||||
confirmText: 'Apply Changes',
|
||||
description: (
|
||||
<VStack space={3}>
|
||||
<p>
|
||||
Some files in the directory have changed. Do you want to apply the updates to your
|
||||
workspace?
|
||||
</p>
|
||||
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="py-1 text-left">Name</th>
|
||||
<th className="py-1 text-right pl-4">Operation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{dbChanges.map((op, i) => {
|
||||
let name = '';
|
||||
let label = '';
|
||||
let color = '';
|
||||
|
||||
if (op.type === 'dbCreate') {
|
||||
label = 'create';
|
||||
name = fallbackRequestName(op.fs.model);
|
||||
color = 'text-success';
|
||||
} else if (op.type === 'dbUpdate') {
|
||||
label = 'update';
|
||||
name = fallbackRequestName(op.fs.model);
|
||||
color = 'text-info';
|
||||
} else if (op.type === 'dbDelete') {
|
||||
label = 'delete';
|
||||
name = fallbackRequestName(op.model);
|
||||
color = 'text-danger';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={i} className="text-text">
|
||||
<td className="py-1">{name}</td>
|
||||
<td className="py-1 pl-4 text-right">
|
||||
<InlineCode className={color}>{label}</InlineCode>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</VStack>
|
||||
),
|
||||
});
|
||||
if (confirmed) {
|
||||
await applySync(activeWorkspace, ops);
|
||||
toast.show({
|
||||
id: 'applied-confirmed-sync-changes',
|
||||
message: `Applied ${pluralizeCount('change', ops.length)}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete-responses',
|
||||
label: 'Clear Send History',
|
||||
@@ -80,12 +179,13 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
|
||||
return { workspaceItems, extraItems };
|
||||
}, [
|
||||
activeWorkspace?.id,
|
||||
activeWorkspaceId,
|
||||
createWorkspace,
|
||||
deleteSendHistory,
|
||||
dialog,
|
||||
orderedWorkspaces,
|
||||
activeWorkspace,
|
||||
deleteSendHistory,
|
||||
createWorkspace,
|
||||
dialog,
|
||||
confirm,
|
||||
toast,
|
||||
]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
@@ -115,7 +215,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
items={workspaceItems}
|
||||
extraItems={extraItems}
|
||||
onChange={handleChange}
|
||||
value={activeWorkspaceId}
|
||||
value={activeWorkspace?.id ?? null}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -5,15 +5,16 @@ import { Button } from './core/Button';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
import { SelectFile } from './SelectFile';
|
||||
|
||||
interface Props {
|
||||
workspaceId: string | null;
|
||||
}
|
||||
|
||||
export function WorkspaceSettingsDialog({ workspaceId }: Props) {
|
||||
const updateWorkspace = useUpdateWorkspace(workspaceId ?? null);
|
||||
const workspaces = useWorkspaces();
|
||||
const workspace = workspaces.find((w) => w.id === workspaceId);
|
||||
const { mutate: updateWorkspace } = useUpdateWorkspace(workspaceId ?? null);
|
||||
const { mutate: deleteWorkspace } = useDeleteWorkspace();
|
||||
|
||||
if (workspace == null) return null;
|
||||
@@ -23,21 +24,27 @@ export function WorkspaceSettingsDialog({ workspaceId }: Props) {
|
||||
<PlainInput
|
||||
label="Workspace Name"
|
||||
defaultValue={workspace.name}
|
||||
onChange={(name) => updateWorkspace.mutate({ name })}
|
||||
onChange={(name) => updateWorkspace({ name })}
|
||||
/>
|
||||
|
||||
<MarkdownEditor
|
||||
name="workspace-description"
|
||||
placeholder="Workspace description"
|
||||
className="min-h-[10rem] border border-border px-2"
|
||||
className="min-h-[10rem] max-h-[25rem] border border-border px-2"
|
||||
defaultValue={workspace.description}
|
||||
stateKey={`description.${workspace.id}`}
|
||||
onChange={(description) => updateWorkspace.mutate({ description })}
|
||||
onChange={(description) => updateWorkspace({ description })}
|
||||
heightMode="auto"
|
||||
/>
|
||||
|
||||
<VStack space={3} className="mt-3" alignItems="start">
|
||||
<Button onClick={() => deleteWorkspace()} color="danger" variant="border">
|
||||
<SelectFile
|
||||
directory
|
||||
noun="Sync Directory"
|
||||
filePath={workspace.settingSyncDir}
|
||||
onChange={({ filePath: settingSyncDir }) => updateWorkspace({ settingSyncDir })}
|
||||
/>
|
||||
<Button onClick={() => deleteWorkspace()} color="danger" variant="border" size="sm">
|
||||
Delete Workspace
|
||||
</Button>
|
||||
</VStack>
|
||||
@@ -4,7 +4,7 @@ import type { EditorStateConfig, Extension } from '@codemirror/state';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
|
||||
import type { EnvironmentVariable } from '@yaakapp-internal/models';
|
||||
import type { TemplateFunction } from '@yaakapp-internal/plugin';
|
||||
import type { TemplateFunction } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { EditorView } from 'codemirror';
|
||||
import type { MutableRefObject, ReactNode } from 'react';
|
||||
|
||||
@@ -41,6 +41,8 @@ const icons = {
|
||||
file_code: lucide.FileCodeIcon,
|
||||
filter: lucide.FilterIcon,
|
||||
flask: lucide.FlaskConicalIcon,
|
||||
folder: lucide.FolderIcon,
|
||||
folder_sync: lucide.FolderSyncIcon,
|
||||
folder_input: lucide.FolderInputIcon,
|
||||
folder_output: lucide.FolderOutputIcon,
|
||||
git_branch: lucide.GitBranchIcon,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ShowToastRequest } from '@yaakapp-internal/plugin';
|
||||
import type { ShowToastRequest } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -15,7 +15,7 @@ export interface ToastProps {
|
||||
onClose: () => void;
|
||||
className?: string;
|
||||
timeout: number | null;
|
||||
action?: ReactNode;
|
||||
action?: (args: { hide: () => void }) => ReactNode;
|
||||
icon?: ShowToastRequest['icon'];
|
||||
color?: ShowToastRequest['color'];
|
||||
}
|
||||
@@ -59,15 +59,15 @@ export function Toast({ children, open, onClose, timeout, action, icon, color }:
|
||||
`x-theme-toast x-theme-toast--${color}`,
|
||||
'pointer-events-auto overflow-hidden',
|
||||
'relative pointer-events-auto bg-surface text-text rounded-lg',
|
||||
'border border-border shadow-lg max-w-[30rem]',
|
||||
'border border-border shadow-lg w-[25rem]',
|
||||
'grid grid-cols-[1fr_auto]',
|
||||
)}
|
||||
>
|
||||
<div className="px-3 py-3 flex items-center gap-2">
|
||||
{toastIcon && <Icon icon={toastIcon} className="text-text-subtle" />}
|
||||
<VStack space={2}>
|
||||
<div className="px-3 py-3 flex items-start gap-2 w-full">
|
||||
{toastIcon && <Icon icon={toastIcon} className="mt-1 text-text-subtle" />}
|
||||
<VStack space={2} className="w-full">
|
||||
<div>{children}</div>
|
||||
{action}
|
||||
{action?.({ hide: onClose })}
|
||||
</VStack>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user