Filesystem Sync (#142)

This commit is contained in:
Gregory Schier
2025-01-03 20:41:00 -08:00
committed by GitHub
parent 6ad27c4458
commit 31440eea76
159 changed files with 4296 additions and 1016 deletions

View File

@@ -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"