mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
feat: auto sync
added auto syncing
This commit is contained in:
parent
f0d2faae6e
commit
ff111214c9
@ -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<HTMLInputElement>(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) =>
|
||||
<div className="ml-auto" />
|
||||
{selectedView === 'code' && (
|
||||
<>
|
||||
<PanelHeaderButton className="mr-1 text-sm" onClick={handleSyncFiles} disabled={isSyncing}>
|
||||
{isSyncing ? <div className="i-ph:spinner" /> : <div className="i-ph:cloud-arrow-down" />}
|
||||
{isSyncing ? 'Syncing...' : 'Sync Files'}
|
||||
</PanelHeaderButton>
|
||||
<PanelHeaderButton className="mr-1 text-sm" onClick={downloadZip}>
|
||||
<div className="i-ph:download-bold" />
|
||||
Download
|
||||
|
||||
@ -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) {
|
||||
|
||||
17
app/types/global.d.ts
vendored
Normal file
17
app/types/global.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
interface Window {
|
||||
showDirectoryPicker(): Promise<FileSystemDirectoryHandle>;
|
||||
}
|
||||
|
||||
interface FileSystemDirectoryHandle {
|
||||
getDirectoryHandle(name: string, options?: { create?: boolean }): Promise<FileSystemDirectoryHandle>;
|
||||
getFileHandle(name: string, options?: { create?: boolean }): Promise<FileSystemFileHandle>;
|
||||
}
|
||||
|
||||
interface FileSystemFileHandle {
|
||||
createWritable(): Promise<FileSystemWritableFileStream>;
|
||||
}
|
||||
|
||||
interface FileSystemWritableFileStream extends WritableStream {
|
||||
write(data: string | BufferSource | Blob): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user