atrokhym commited on
Commit
809b54e
·
1 Parent(s): 0741610

upload new files

Browse files
app/components/workbench/Workbench.client.tsx CHANGED
@@ -57,6 +57,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
57
  renderLogger.trace('Workbench');
58
 
59
  const [isSyncing, setIsSyncing] = useState(false);
 
60
 
61
  const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
62
  const showWorkbench = useStore(workbenchStore.showWorkbench);
@@ -119,6 +120,60 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
119
  }
120
  }, []);
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  return (
123
  chatStarted && (
124
  <motion.div
@@ -158,6 +213,10 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
158
  {isSyncing ? <div className="i-ph:spinner" /> : <div className="i-ph:cloud-arrow-down" />}
159
  {isSyncing ? 'Syncing...' : 'Sync Files'}
160
  </PanelHeaderButton>
 
 
 
 
161
  <PanelHeaderButton
162
  className="mr-1 text-sm"
163
  onClick={() => {
 
57
  renderLogger.trace('Workbench');
58
 
59
  const [isSyncing, setIsSyncing] = useState(false);
60
+ const [isUploading, setIsUploading] = useState(false);
61
 
62
  const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
63
  const showWorkbench = useStore(workbenchStore.showWorkbench);
 
120
  }
121
  }, []);
122
 
123
+ const handleUploadFiles = useCallback(async () => {
124
+ setIsUploading(true);
125
+
126
+ try {
127
+ // const directoryHandle = await window.showDirectoryPicker();
128
+
129
+ // // First upload new files
130
+ // await workbenchStore.uploadFilesFromDisk(directoryHandle);
131
+
132
+ // // Get current files state
133
+ // const currentFiles = workbenchStore.files.get();
134
+
135
+ // // Create new modifications map with all files as "new"
136
+ // const newModifications = new Map();
137
+ // Object.entries(currentFiles).forEach(([path, file]) => {
138
+ // if (file.type === 'file') {
139
+ // newModifications.set(path, file.content);
140
+ // }
141
+ // });
142
+
143
+ // // Update workbench state
144
+ // await workbenchStore.refreshFiles();
145
+ // workbenchStore.resetAllFileModifications();
146
+
147
+ // toast.success('Files uploaded successfully');
148
+ // } catch (error) {
149
+ // toast.error('Failed to upload files');
150
+ // }
151
+ await handleUploadFilesFunc();
152
+ }
153
+
154
+ finally {
155
+ setIsUploading(false);
156
+ }
157
+ }, []);
158
+
159
+ async function handleUploadFilesFunc() {
160
+ try {
161
+ // First clean all statuses
162
+ await workbenchStore.saveAllFiles();
163
+ await workbenchStore.resetAllFileModifications();
164
+ await workbenchStore.refreshFiles();
165
+
166
+ // Now upload new files
167
+ const directoryHandle = await window.showDirectoryPicker();
168
+ await workbenchStore.uploadFilesFromDisk(directoryHandle);
169
+
170
+ toast.success('Files uploaded successfully');
171
+ } catch (error) {
172
+ console.error('Upload files error:', error);
173
+ toast.error('Failed to upload files');
174
+ }
175
+ }
176
+
177
  return (
178
  chatStarted && (
179
  <motion.div
 
213
  {isSyncing ? <div className="i-ph:spinner" /> : <div className="i-ph:cloud-arrow-down" />}
214
  {isSyncing ? 'Syncing...' : 'Sync Files'}
215
  </PanelHeaderButton>
216
+ <PanelHeaderButton className="mr-1 text-sm" onClick={handleUploadFiles} disabled={isSyncing}>
217
+ {isSyncing ? <div className="i-ph:spinner" /> : <div className="i-ph:cloud-arrow-up" />}
218
+ {isSyncing ? 'Uploading...' : 'Upload Files'}
219
+ </PanelHeaderButton>
220
  <PanelHeaderButton
221
  className="mr-1 text-sm"
222
  onClick={() => {
app/lib/stores/files.ts CHANGED
@@ -80,6 +80,10 @@ export class FilesStore {
80
  this.#modifiedFiles.clear();
81
  }
82
 
 
 
 
 
83
  async saveFile(filePath: string, content: string) {
84
  const webcontainer = await this.#webcontainer;
85
 
 
80
  this.#modifiedFiles.clear();
81
  }
82
 
83
+ markFileAsNew(filePath: string) {
84
+ this.#modifiedFiles.set(filePath, '');
85
+ }
86
+
87
  async saveFile(filePath: string, content: string) {
88
  const webcontainer = await this.#webcontainer;
89
 
app/lib/stores/workbench.ts CHANGED
@@ -32,6 +32,7 @@ export type WorkbenchViewType = 'code' | 'preview';
32
  export class WorkbenchStore {
33
  #previewsStore = new PreviewsStore(webcontainer);
34
  #filesStore = new FilesStore(webcontainer);
 
35
  #editorStore = new EditorStore(this.#filesStore);
36
  #terminalStore = new TerminalStore(webcontainer);
37
 
@@ -43,7 +44,7 @@ export class WorkbenchStore {
43
  modifiedFiles = new Set<string>();
44
  artifactIdList: string[] = [];
45
  #boltTerminal: { terminal: ITerminal; process: WebContainerProcess } | undefined;
46
- #globalExecutionQueue=Promise.resolve();
47
  constructor() {
48
  if (import.meta.hot) {
49
  import.meta.hot.data.artifacts = this.artifacts;
@@ -54,7 +55,7 @@ export class WorkbenchStore {
54
  }
55
 
56
  addToExecutionQueue(callback: () => Promise<void>) {
57
- this.#globalExecutionQueue=this.#globalExecutionQueue.then(()=>callback())
58
  }
59
 
60
  get previews() {
@@ -277,11 +278,11 @@ export class WorkbenchStore {
277
  }
278
 
279
  runAction(data: ActionCallbackData, isStreaming: boolean = false) {
280
- if(isStreaming) {
281
  this._runAction(data, isStreaming)
282
  }
283
- else{
284
- this.addToExecutionQueue(()=>this._runAction(data, isStreaming))
285
  }
286
  }
287
  async _runAction(data: ActionCallbackData, isStreaming: boolean = false) {
@@ -381,6 +382,61 @@ export class WorkbenchStore {
381
  return syncedFiles;
382
  }
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  async pushToGitHub(repoName: string, githubUsername: string, ghToken: string) {
385
 
386
  try {
@@ -486,6 +542,21 @@ export class WorkbenchStore {
486
  console.error('Error pushing to GitHub:', error instanceof Error ? error.message : String(error));
487
  }
488
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  }
490
 
491
  export const workbenchStore = new WorkbenchStore();
 
32
  export class WorkbenchStore {
33
  #previewsStore = new PreviewsStore(webcontainer);
34
  #filesStore = new FilesStore(webcontainer);
35
+
36
  #editorStore = new EditorStore(this.#filesStore);
37
  #terminalStore = new TerminalStore(webcontainer);
38
 
 
44
  modifiedFiles = new Set<string>();
45
  artifactIdList: string[] = [];
46
  #boltTerminal: { terminal: ITerminal; process: WebContainerProcess } | undefined;
47
+ #globalExecutionQueue = Promise.resolve();
48
  constructor() {
49
  if (import.meta.hot) {
50
  import.meta.hot.data.artifacts = this.artifacts;
 
55
  }
56
 
57
  addToExecutionQueue(callback: () => Promise<void>) {
58
+ this.#globalExecutionQueue = this.#globalExecutionQueue.then(() => callback())
59
  }
60
 
61
  get previews() {
 
278
  }
279
 
280
  runAction(data: ActionCallbackData, isStreaming: boolean = false) {
281
+ if (isStreaming) {
282
  this._runAction(data, isStreaming)
283
  }
284
+ else {
285
+ this.addToExecutionQueue(() => this._runAction(data, isStreaming))
286
  }
287
  }
288
  async _runAction(data: ActionCallbackData, isStreaming: boolean = false) {
 
382
  return syncedFiles;
383
  }
384
 
385
+ async uploadFilesFromDisk(sourceHandle: FileSystemDirectoryHandle) {
386
+ const loadedFiles = [];
387
+ const wc = await webcontainer;
388
+ const newFiles = {};
389
+
390
+ const processDirectory = async (handle: FileSystemDirectoryHandle, currentPath: string = '') => {
391
+ const entries = await Array.fromAsync(handle.values());
392
+
393
+ for (const entry of entries) {
394
+ const entryPath = currentPath ? `${currentPath}/${entry.name}` : entry.name;
395
+ const fullPath = `/${entryPath}`;
396
+
397
+ if (entry.kind === 'directory') {
398
+ await wc.fs.mkdir(fullPath, { recursive: true });
399
+ const subDirHandle = await handle.getDirectoryHandle(entry.name);
400
+ await processDirectory(subDirHandle, entryPath);
401
+ } else {
402
+ const file = await entry.getFile();
403
+ const content = await file.text();
404
+
405
+ // Write to WebContainer
406
+ await wc.fs.writeFile(fullPath, content);
407
+
408
+ // Mark file as new
409
+ this.#filesStore.markFileAsNew(fullPath);
410
+
411
+ // Update the files store with the current content
412
+ this.files.setKey(fullPath, { type: 'file', content, isBinary: false });
413
+
414
+ // Collect for editor store with actual content
415
+ newFiles[fullPath] = { type: 'file', content, isBinary: false };
416
+ loadedFiles.push(entryPath);
417
+ }
418
+ }
419
+ }
420
+
421
+ await processDirectory(sourceHandle);
422
+
423
+ return loadedFiles;
424
+ }
425
+
426
+ async refreshFiles() {
427
+ // Clear old state
428
+ this.modifiedFiles = new Set<string>();
429
+ this.artifactIdList = [];
430
+
431
+ // Reset stores
432
+ this.#filesStore = new FilesStore(webcontainer);
433
+ this.#editorStore = new EditorStore(this.#filesStore);
434
+
435
+ // Update UI state
436
+ this.currentView.set('code');
437
+ this.unsavedFiles.set(new Set<string>());
438
+ }
439
+
440
  async pushToGitHub(repoName: string, githubUsername: string, ghToken: string) {
441
 
442
  try {
 
542
  console.error('Error pushing to GitHub:', error instanceof Error ? error.message : String(error));
543
  }
544
  }
545
+
546
+ async markFileAsModified(filePath: string) {
547
+ const file = this.#filesStore.getFile(filePath);
548
+ if (file?.type === 'file') {
549
+ // First collect all original content
550
+ const originalContent = file.content;
551
+ console.log(`Processing ${filePath}:`, originalContent);
552
+
553
+ // Then save modifications
554
+ await this.saveFile(filePath, originalContent);
555
+ }
556
+ }
557
+
558
+
559
+
560
  }
561
 
562
  export const workbenchStore = new WorkbenchStore();