@@ -3,38 +3,87 @@ import { DefaultSettings } from '@onlook/models/constants';
3
3
import { promises as fs , readFileSync } from 'fs' ;
4
4
import mime from 'mime-lite' ;
5
5
import path from 'path' ;
6
+ import { detectRouterType } from '../pages' ;
7
+
8
+ const SUPPORTED_IMAGE_EXTENSIONS = [ '.jpg' , '.jpeg' , '.png' , '.gif' , '.webp' , '.svg' , '.ico' ] ;
9
+ const MAX_FILENAME_LENGTH = 255 ;
10
+ const VALID_FILENAME_REGEX = / ^ [ a - z A - Z 0 - 9 - _ . ] + $ / ;
11
+
12
+ async function getImageFolderPath ( projectRoot : string , folder ?: string ) : Promise < string > {
13
+ if ( folder ) {
14
+ return path . join ( projectRoot , folder ) ;
15
+ }
16
+
17
+ const routerType = await detectRouterType ( projectRoot ) ;
18
+ return routerType ?. basePath
19
+ ? routerType . basePath
20
+ : path . join ( projectRoot , DefaultSettings . IMAGE_FOLDER ) ;
21
+ }
22
+
23
+ // Helper function to validate and process image file
24
+ function processImageFile ( filePath : string , folder : string ) : ImageContentData {
25
+ const image = readFileSync ( filePath , { encoding : 'base64' } ) ;
26
+ const mimeType = mime . getType ( filePath ) || 'application/octet-stream' ;
27
+
28
+ return {
29
+ fileName : path . basename ( filePath ) ,
30
+ content : `data:${ mimeType } ;base64,${ image } ` ,
31
+ mimeType,
32
+ folder,
33
+ } ;
34
+ }
6
35
7
36
async function scanImagesDirectory ( projectRoot : string ) : Promise < ImageContentData [ ] > {
8
- const imagesPath = path . join ( projectRoot , DefaultSettings . IMAGE_FOLDER ) ;
9
37
const images : ImageContentData [ ] = [ ] ;
10
38
39
+ const publicImagesPath = path . join ( projectRoot , DefaultSettings . IMAGE_FOLDER ) ;
11
40
try {
12
- const entries = await fs . readdir ( imagesPath , { withFileTypes : true } ) ;
13
-
14
- for ( const entry of entries ) {
15
- if ( entry . isFile ( ) ) {
16
- const extension = path . extname ( entry . name ) . toLowerCase ( ) ;
17
- // Common image extensions
18
- if (
19
- [ '.jpg' , '.jpeg' , '.png' , '.gif' , '.webp' , '.svg' , '.ico' ] . includes ( extension )
20
- ) {
21
- const imagePath = path . join ( imagesPath , entry . name ) ;
22
- const image = readFileSync ( imagePath , { encoding : 'base64' } ) ;
23
- const mimeType = mime . getType ( imagePath ) || 'application/octet-stream' ;
24
- images . push ( {
25
- fileName : entry . name ,
26
- content : `data:${ mimeType } ;base64,${ image } ` ,
27
- mimeType,
28
- } ) ;
29
- }
41
+ const publicEntries = await fs . readdir ( publicImagesPath , { withFileTypes : true } ) ;
42
+ for ( const entry of publicEntries ) {
43
+ if (
44
+ entry . isFile ( ) &&
45
+ SUPPORTED_IMAGE_EXTENSIONS . includes ( path . extname ( entry . name ) . toLowerCase ( ) )
46
+ ) {
47
+ const imagePath = path . join ( publicImagesPath , entry . name ) ;
48
+ images . push ( processImageFile ( imagePath , DefaultSettings . IMAGE_FOLDER ) ) ;
30
49
}
31
50
}
51
+ } catch ( error ) {
52
+ console . error ( 'Error scanning public images directory:' , error ) ;
53
+ }
32
54
33
- return images ;
55
+ // Scan app directory images
56
+ const appDir = path . join ( projectRoot , 'app' ) ;
57
+ try {
58
+ const appImages = await findImagesInDirectory ( appDir ) ;
59
+ for ( const imagePath of appImages ) {
60
+ images . push ( processImageFile ( imagePath , 'app' ) ) ;
61
+ }
34
62
} catch ( error ) {
35
- console . error ( 'Error scanning images directory:' , error ) ;
36
- return [ ] ;
63
+ console . error ( 'Error scanning app directory images:' , error ) ;
37
64
}
65
+
66
+ return images ;
67
+ }
68
+
69
+ async function findImagesInDirectory ( dirPath : string ) : Promise < string [ ] > {
70
+ const imageFiles : string [ ] = [ ] ;
71
+ const entries = await fs . readdir ( dirPath , { withFileTypes : true } ) ;
72
+
73
+ for ( const entry of entries ) {
74
+ const fullPath = path . join ( dirPath , entry . name ) ;
75
+
76
+ if ( entry . isDirectory ( ) && ! entry . name . startsWith ( '.' ) && entry . name !== 'node_modules' ) {
77
+ imageFiles . push ( ...( await findImagesInDirectory ( fullPath ) ) ) ;
78
+ } else if (
79
+ entry . isFile ( ) &&
80
+ SUPPORTED_IMAGE_EXTENSIONS . includes ( path . extname ( entry . name ) . toLowerCase ( ) )
81
+ ) {
82
+ imageFiles . push ( fullPath ) ;
83
+ }
84
+ }
85
+
86
+ return imageFiles ;
38
87
}
39
88
40
89
export async function scanNextJsImages ( projectRoot : string ) : Promise < ImageContentData [ ] > {
@@ -72,16 +121,15 @@ async function getUniqueFileName(imageFolder: string, fileName: string): Promise
72
121
}
73
122
74
123
export async function saveImageToProject (
75
- projectFolder : string ,
76
- content : string ,
77
- fileName : string ,
124
+ projectRoot : string ,
125
+ image : ImageContentData ,
78
126
) : Promise < string > {
79
127
try {
80
- const imageFolder = path . join ( projectFolder , DefaultSettings . IMAGE_FOLDER ) ;
81
- const uniqueFileName = await getUniqueFileName ( imageFolder , fileName ) ;
128
+ const imageFolder = await getImageFolderPath ( projectRoot , image . folder ) ;
129
+ const uniqueFileName = await getUniqueFileName ( imageFolder , image . fileName ) ;
82
130
const imagePath = path . join ( imageFolder , uniqueFileName ) ;
83
131
84
- const buffer = Buffer . from ( content , 'base64' ) ;
132
+ const buffer = Buffer . from ( image . content , 'base64' ) ;
85
133
await fs . writeFile ( imagePath , buffer ) ;
86
134
return imagePath ;
87
135
} catch ( error ) {
@@ -92,11 +140,11 @@ export async function saveImageToProject(
92
140
93
141
export async function deleteImageFromProject (
94
142
projectRoot : string ,
95
- imageName : string ,
143
+ image : ImageContentData ,
96
144
) : Promise < string > {
97
145
try {
98
- const imageFolder = path . join ( projectRoot , DefaultSettings . IMAGE_FOLDER ) ;
99
- const imagePath = path . join ( imageFolder , imageName ) ;
146
+ const imageFolder = await getImageFolderPath ( projectRoot , image . folder ) ;
147
+ const imagePath = path . join ( imageFolder , image . fileName ) ;
100
148
await fs . unlink ( imagePath ) ;
101
149
return imagePath ;
102
150
} catch ( error ) {
@@ -107,32 +155,28 @@ export async function deleteImageFromProject(
107
155
108
156
export async function renameImageInProject (
109
157
projectRoot : string ,
110
- imageName : string ,
158
+ image : ImageContentData ,
111
159
newName : string ,
112
160
) : Promise < string > {
113
- if ( ! imageName || ! newName ) {
161
+ if ( ! image . fileName || ! newName ) {
114
162
throw new Error ( 'Image name and new name are required' ) ;
115
163
}
116
164
117
- const imageFolder = path . join ( projectRoot , DefaultSettings . IMAGE_FOLDER ) ;
118
- const oldImagePath = path . join ( imageFolder , imageName ) ;
165
+ const imageFolder = await getImageFolderPath ( projectRoot , image . folder ) ;
166
+ const oldImagePath = path . join ( imageFolder , image . fileName ) ;
119
167
const newImagePath = path . join ( imageFolder , newName ) ;
120
168
121
169
try {
122
170
await validateRename ( oldImagePath , newImagePath ) ;
123
171
await fs . rename ( oldImagePath , newImagePath ) ;
124
-
125
- await updateImageReferences ( projectRoot , imageName , newName ) ;
172
+ await updateImageReferences ( projectRoot , image . fileName , newName ) ;
126
173
return newImagePath ;
127
174
} catch ( error ) {
128
175
console . error ( 'Error renaming image:' , error ) ;
129
176
throw error ;
130
177
}
131
178
}
132
179
133
- const MAX_FILENAME_LENGTH = 255 ;
134
- const VALID_FILENAME_REGEX = / ^ [ a - z A - Z 0 - 9 - _ . ] + $ / ;
135
-
136
180
async function validateRename ( oldImagePath : string , newImagePath : string ) : Promise < void > {
137
181
try {
138
182
await fs . access ( oldImagePath ) ;
0 commit comments