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);