diff --git a/apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx b/apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx index 297c04c053..8c6f1c0986 100644 --- a/apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx +++ b/apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useEditorEngine } from '@/components/store/editor'; import { DefaultSettings } from '@onlook/constants'; import { type FolderNode } from '@onlook/models'; @@ -206,14 +208,41 @@ const Folder = observer(() => { )} - + + + + + + +

Create a folder

+
+
+
+ + + + + + +

Refresh Images

+
+
+
diff --git a/apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx b/apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx index 99cc35f262..aefc0ed153 100644 --- a/apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx +++ b/apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx @@ -388,20 +388,19 @@ export const FolderProvider = observer(({ children }: FolderProviderProps) => { // Get all folder paths const getImagesInFolder = useCallback((folder: FolderNode) => { - return editorEngine.activeSandbox.files.filter(image => { + // Use the image manager's scanned image paths instead of sandbox files + return editorEngine.image.imagePaths.filter(image => { if (image.startsWith(folder.fullPath)) { // Check if this is a direct child (not in a subdirectory) const relativePath = image.slice(folder.fullPath.length); // Remove leading slash if present const cleanRelativePath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath; // Only include if it's a direct child (no additional path separators) - if (!cleanRelativePath.includes('/')) { - return isImageFile(image); - } + return !cleanRelativePath.includes('/') && cleanRelativePath.length > 0; } return false; }); - }, [editorEngine.activeSandbox.files]); + }, [editorEngine.image.imagePaths]); // Check if any operation is loading const isOperating = diff --git a/apps/web/client/src/components/store/editor/image/index.ts b/apps/web/client/src/components/store/editor/image/index.ts index 1f42d83c85..c10ddc4ec5 100644 --- a/apps/web/client/src/components/store/editor/image/index.ts +++ b/apps/web/client/src/components/store/editor/image/index.ts @@ -1,5 +1,5 @@ import { DefaultSettings } from '@onlook/constants'; -import type { ActionTarget, ImageContentData, InsertImageAction } from '@onlook/models'; +import { LeftPanelTabValue, type ActionTarget, type ImageContentData, type InsertImageAction } from '@onlook/models'; import { convertToBase64, generateNewFolderPath, getBaseName, getMimeType, isImageFile, stripImageFolderPrefix } from '@onlook/utility'; import { makeAutoObservable, reaction } from 'mobx'; import type { EditorEngine } from '../engine'; @@ -18,10 +18,18 @@ export class ImageManager { init() { this.indexingReactionDisposer = reaction( - () => this.editorEngine.activeSandbox.isIndexing, - async (isIndexingFiles) => { - if (!isIndexingFiles) { - await this.scanImages(); + () => { + return { + isIndexing: this.editorEngine.activeSandbox.isIndexing, + isIndexed: this.editorEngine.activeSandbox.isIndexed, + }; + }, + (sandboxStatus) => { + if (this.editorEngine.state.leftPanelTab !== LeftPanelTabValue.IMAGES) { + return; + } + if (sandboxStatus.isIndexed && !sandboxStatus.isIndexing) { + this.scanImages(); } }, ); @@ -183,16 +191,13 @@ export class ImageManager { } async scanImages() { - if (this._isScanning) { - return; - } - - this._isScanning = true; - try { - const files = await this.editorEngine.activeSandbox.listFilesRecursively( - DefaultSettings.IMAGE_FOLDER, - ); + if (this._isScanning) { + return; + } + this._isScanning = true; + + const files = this.editorEngine.activeSandbox.files; if (!files) { console.error('No files found in image folder'); return; @@ -201,7 +206,7 @@ export class ImageManager { this._imagePaths = []; return; } - this._imagePaths = files.filter((file: string) => isImageFile(file)); + this._imagePaths = files.filter((file: string) => file.startsWith(DefaultSettings.IMAGE_FOLDER) && isImageFile(file)); } catch (error) { console.error('Error scanning images:', error); this._imagePaths = []; diff --git a/apps/web/client/src/components/store/editor/sandbox/index.ts b/apps/web/client/src/components/store/editor/sandbox/index.ts index 6966f873f7..b9d1734c69 100644 --- a/apps/web/client/src/components/store/editor/sandbox/index.ts +++ b/apps/web/client/src/components/store/editor/sandbox/index.ts @@ -549,42 +549,20 @@ export class SandboxManager { async handleFileChangedEvent(normalizedPath: string) { const cachedFile = this.fileSync.readCache(normalizedPath); - if (isImageFile(normalizedPath)) { - if (!cachedFile || cachedFile.content === null) { - // If the file was not cached, we need to write an empty file - this.fileSync.writeEmptyFile(normalizedPath, 'binary'); - } else { - // If the file was already cached, we need to read the remote file and update the cache - const remoteFile = await this.readRemoteFile(normalizedPath); - if (!remoteFile || remoteFile.content === null) { - console.error(`File content for ${normalizedPath} not found in remote`); - return; - } - this.fileSync.updateCache(remoteFile); - } - } else { - // If the file is not an image, we need to read the remote file and update the cache - const remoteFile = await this.readRemoteFile(normalizedPath); - if (!remoteFile || remoteFile.content === null) { - console.error(`File content for ${normalizedPath} not found in remote`); - return; - } - if (remoteFile.type === 'text') { - // If the file is a text file, we need to process it for mapping - this.fileSync.updateCache({ - type: 'text', - path: normalizedPath, - content: remoteFile.content, - }); - if (remoteFile.content !== cachedFile?.content) { - await this.processFileForMapping(remoteFile); - } - } else { - this.fileSync.updateCache({ - type: 'binary', - path: normalizedPath, - content: remoteFile.content, - }); + // Always read the remote file and update the cache, regardless of file type + const remoteFile = await this.readRemoteFile(normalizedPath); + if (!remoteFile) { + console.error(`File content for ${normalizedPath} not found in remote`); + return; + } + + // Always update the cache with the fresh remote file content + this.fileSync.updateCache(remoteFile); + + // For text files, also process for mapping if content has changed + if (remoteFile.type === 'text' && this.isJsxFile(normalizedPath)) { + if (remoteFile.content !== cachedFile?.content) { + await this.processFileForMapping(remoteFile); } } } @@ -678,6 +656,12 @@ export class SandboxManager { }, }); + // Read and cache the copied file + const copiedFile = await this.readRemoteFile(normalizedTargetPath); + if (copiedFile) { + this.fileSync.updateCache(copiedFile); + } + return true; } catch (error) { console.error(`Error copying ${path} to ${targetPath}:`, error); @@ -744,6 +728,7 @@ export class SandboxManager { }, }); + // Note: Cache update handled by file watcher rename event return true; } catch (error) { console.error(`Error renaming file ${oldPath} to ${newPath}:`, error);