From ff111214c90411f5e523d421189088436b7d1fed Mon Sep 17 00:00:00 2001 From: Dustin Loring Date: Fri, 17 Jan 2025 14:49:32 -0500 Subject: [PATCH] feat: auto sync added auto syncing --- app/components/workbench/Workbench.client.tsx | 30 ++++++++++++++++- app/lib/stores/workbench.ts | 33 ++++++++++++++++--- app/types/global.d.ts | 17 ++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 app/types/global.d.ts diff --git a/app/components/workbench/Workbench.client.tsx b/app/components/workbench/Workbench.client.tsx index 8cf479b..04962ad 100644 --- a/app/components/workbench/Workbench.client.tsx +++ b/app/components/workbench/Workbench.client.tsx @@ -3,7 +3,7 @@ import { saveAs } from 'file-saver'; import { motion, type HTMLMotionProps, type Variants } from 'framer-motion'; import JSZip from 'jszip'; import { computed } from 'nanostores'; -import { memo, useCallback, useEffect, useState } from 'react'; +import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { toast } from 'react-toastify'; import { EditorPanel } from './EditorPanel'; import { GitHubPushModal } from './GitHubPushModal'; @@ -66,6 +66,30 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) => const files = useStore(workbenchStore.files); const selectedView = useStore(workbenchStore.currentView); const [showGitHubModal, setShowGitHubModal] = useState(false); + const [showGitHubPushModal, setShowGitHubPushModal] = useState(false); + const [isSyncing, setIsSyncing] = useState(false); + const fileInputRef = useRef(null); + + const handleSyncFiles = useCallback(async () => { + setIsSyncing(true); + + try { + if ('showDirectoryPicker' in window) { + const directoryHandle = await window.showDirectoryPicker(); + await workbenchStore.syncFiles(directoryHandle); + toast.success('Files synced successfully'); + } else { + // Fallback to download as zip + await downloadZip(); + toast.info('Your browser does not support the File System Access API. Files have been downloaded as a zip instead.'); + } + } catch (error) { + console.error('Error syncing files:', error); + toast.error('Failed to sync files'); + } finally { + setIsSyncing(false); + } + }, []); const downloadZip = async () => { const zip = new JSZip(); @@ -157,6 +181,10 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
{selectedView === 'code' && ( <> + + {isSyncing ?
:
} + {isSyncing ? 'Syncing...' : 'Sync Files'} +
Download diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index cb48d2e..0eb1da3 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -256,15 +256,38 @@ export class WorkbenchStore { } async runAction(data: ActionCallbackData) { - const { messageId } = data; + const artifact = this.artifacts.get()[data.messageId]; + if (!artifact) return; + await artifact.runner.runAction(data); + } - const artifact = this.#getArtifact(messageId); + async syncFiles(targetHandle: FileSystemDirectoryHandle) { + const files = this.files.get(); + const syncedFiles = []; - if (!artifact) { - unreachable('Artifact not found'); + for (const [filePath, dirent] of Object.entries(files)) { + if (dirent?.type === 'file' && !dirent.isBinary) { + const relativePath = filePath.replace(/^\/home\/project\//, ''); + const pathSegments = relativePath.split('/'); + let currentHandle = targetHandle; + + for (let i = 0; i < pathSegments.length - 1; i++) { + currentHandle = await currentHandle.getDirectoryHandle(pathSegments[i], { create: true }); + } + + // create or get the file + const fileHandle = await currentHandle.getFileHandle(pathSegments[pathSegments.length - 1], { create: true }); + + // write the file content + const writable = await fileHandle.createWritable(); + await writable.write(dirent.content); + await writable.close(); + + syncedFiles.push(relativePath); + } } - artifact.runner.runAction(data); + return syncedFiles; } #getArtifact(id: string) { diff --git a/app/types/global.d.ts b/app/types/global.d.ts new file mode 100644 index 0000000..6445f3a --- /dev/null +++ b/app/types/global.d.ts @@ -0,0 +1,17 @@ +interface Window { + showDirectoryPicker(): Promise; +} + +interface FileSystemDirectoryHandle { + getDirectoryHandle(name: string, options?: { create?: boolean }): Promise; + getFileHandle(name: string, options?: { create?: boolean }): Promise; +} + +interface FileSystemFileHandle { + createWritable(): Promise; +} + +interface FileSystemWritableFileStream extends WritableStream { + write(data: string | BufferSource | Blob): Promise; + close(): Promise; +} \ No newline at end of file