@@ -63,6 +63,7 @@ export const CodeTab = memo(forwardRef<CodeTabRef, CodeTabProps>(({ projectId, b
63
63
const [ activeEditorFile , setActiveEditorFile ] = useState < EditorFile | null > ( null ) ;
64
64
const [ openedEditorFiles , setOpenedEditorFiles ] = useState < EditorFile [ ] > ( [ ] ) ;
65
65
const [ showLocalUnsavedDialog , setShowLocalUnsavedDialog ] = useState ( false ) ;
66
+ const [ filesToClose , setFilesToClose ] = useState < string [ ] > ( [ ] ) ;
66
67
67
68
// This is a workaround to allow code controls to access the hasUnsavedChanges state
68
69
const [ hasUnsavedChanges , setHasUnsavedChanges ] = useState ( false ) ;
@@ -185,38 +186,64 @@ export const CodeTab = memo(forwardRef<CodeTabRef, CodeTabProps>(({ projectId, b
185
186
}
186
187
} ;
187
188
189
+ const saveFileWithHash = async ( filePath : string , file : EditorFile ) : Promise < EditorFile > => {
190
+ if ( ! branchData ) {
191
+ throw new Error ( 'Branch data not found' ) ;
192
+ }
193
+
194
+ await branchData . codeEditor . writeFile ( filePath , file . content || '' ) ;
195
+
196
+ if ( file . type === 'text' ) {
197
+ const newHash = await hashContent ( file . content ) ;
198
+ return { ...file , originalHash : newHash } ;
199
+ }
200
+
201
+ return file ;
202
+ } ;
203
+
188
204
const handleSaveFile = async ( ) => {
189
205
if ( ! selectedFilePath || ! activeEditorFile ) return ;
190
206
try {
191
- if ( ! branchData ) {
192
- throw new Error ( 'Branch data not found' ) ;
193
- }
194
-
195
- await branchData . codeEditor . writeFile ( selectedFilePath , activeEditorFile . content || '' ) ;
207
+ const updatedFile = await saveFileWithHash ( selectedFilePath , activeEditorFile ) ;
196
208
197
- // Update originalHash to mark file as clean after successful save
198
- if ( activeEditorFile . type === 'text' ) {
199
- const newHash = await hashContent ( activeEditorFile . content ) ;
200
- const updatedFile = { ...activeEditorFile , originalHash : newHash } ;
201
-
202
- // Update in opened files list
203
- const updatedFiles = openedEditorFiles . map ( file =>
204
- pathsEqual ( file . path , selectedFilePath ) ? updatedFile : file
205
- ) ;
206
- setOpenedEditorFiles ( updatedFiles ) ;
207
- setActiveEditorFile ( updatedFile ) ;
208
- }
209
+ // Update in opened files list
210
+ const updatedFiles = openedEditorFiles . map ( file =>
211
+ pathsEqual ( file . path , selectedFilePath ) ? updatedFile : file
212
+ ) ;
213
+ setOpenedEditorFiles ( updatedFiles ) ;
214
+ setActiveEditorFile ( updatedFile ) ;
209
215
} catch ( error ) {
210
216
console . error ( 'Failed to save file:' , error ) ;
211
217
alert ( `Failed to save: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
212
218
}
213
219
} ;
214
220
221
+ const handleSaveAndCloseFiles = async ( ) => {
222
+ try {
223
+ // Save all files in filesToClose
224
+ await Promise . all ( filesToClose . map ( async ( filePath ) => {
225
+ const fileToSave = openedEditorFiles . find ( f => pathsEqual ( f . path , filePath ) ) ;
226
+ if ( ! fileToSave ) return ;
227
+
228
+ await saveFileWithHash ( filePath , fileToSave ) ;
229
+ } ) ) ;
230
+
231
+ // Close the files (no need to update hashes since we're closing them)
232
+ filesToClose . forEach ( filePath => closeFileInternal ( filePath ) ) ;
233
+ setFilesToClose ( [ ] ) ;
234
+ setShowLocalUnsavedDialog ( false ) ;
235
+ } catch ( error ) {
236
+ console . error ( 'Failed to save files:' , error ) ;
237
+ alert ( `Failed to save: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
238
+ }
239
+ } ;
240
+
215
241
const closeLocalFile = useCallback ( ( filePath : string ) => {
216
242
const fileToClose = openedEditorFiles . find ( f => pathsEqual ( f . path , filePath ) ) ;
217
243
if ( fileToClose ) {
218
244
isDirty ( fileToClose ) . then ( dirty => {
219
245
if ( dirty ) {
246
+ setFilesToClose ( [ filePath ] ) ;
220
247
setShowLocalUnsavedDialog ( true ) ;
221
248
return ;
222
249
}
@@ -238,6 +265,7 @@ export const CodeTab = memo(forwardRef<CodeTabRef, CodeTabProps>(({ projectId, b
238
265
// Check if any dirty files remain
239
266
const dirtyFiles = fileStatuses . filter ( status => status . dirty ) ;
240
267
if ( dirtyFiles . length > 0 ) {
268
+ setFilesToClose ( dirtyFiles . map ( status => status . file . path ) ) ;
241
269
setShowLocalUnsavedDialog ( true ) ;
242
270
return ;
243
271
}
@@ -272,22 +300,27 @@ export const CodeTab = memo(forwardRef<CodeTabRef, CodeTabProps>(({ projectId, b
272
300
editorViewsRef . current . delete ( filePath ) ;
273
301
}
274
302
275
- const updatedFiles = openedEditorFiles . filter ( f => ! pathsEqual ( f . path , filePath ) ) ;
276
- setOpenedEditorFiles ( updatedFiles ) ;
303
+ setOpenedEditorFiles ( prev => {
304
+ const updatedFiles = prev . filter ( f => ! pathsEqual ( f . path , filePath ) ) ;
277
305
278
- if ( activeEditorFile && pathsEqual ( activeEditorFile . path , filePath ) ) {
279
- const newActiveFile = updatedFiles . length > 0 ? updatedFiles [ updatedFiles . length - 1 ] || null : null ;
280
- setActiveEditorFile ( newActiveFile ) ;
281
- }
306
+ // Update active file if we're closing it
307
+ if ( activeEditorFile && pathsEqual ( activeEditorFile . path , filePath ) ) {
308
+ const newActiveFile = updatedFiles . length > 0 ? updatedFiles [ updatedFiles . length - 1 ] || null : null ;
309
+ setActiveEditorFile ( newActiveFile ) ;
310
+ }
311
+
312
+ return updatedFiles ;
313
+ } ) ;
282
314
283
315
// Clear selected file path if the closed file was selected
284
316
if ( selectedFilePath && pathsEqual ( selectedFilePath , filePath ) ) {
285
317
setSelectedFilePath ( null ) ;
286
318
}
287
319
} ;
288
320
289
- const discardLocalFileChanges = ( filePath : string ) => {
290
- closeFileInternal ( filePath ) ;
321
+ const discardLocalFileChanges = ( ) => {
322
+ filesToClose . forEach ( filePath => closeFileInternal ( filePath ) ) ;
323
+ setFilesToClose ( [ ] ) ;
291
324
setShowLocalUnsavedDialog ( false ) ;
292
325
} ;
293
326
@@ -396,11 +429,14 @@ export const CodeTab = memo(forwardRef<CodeTabRef, CodeTabProps>(({ projectId, b
396
429
showUnsavedDialog = { showLocalUnsavedDialog }
397
430
navigationTarget = { navigationTarget }
398
431
onSaveFile = { handleSaveFile }
432
+ onSaveAndCloseFiles = { handleSaveAndCloseFiles }
399
433
onUpdateFileContent = { updateLocalFileContent }
400
434
onDiscardChanges = { discardLocalFileChanges }
401
435
onCancelUnsaved = { ( ) => {
436
+ setFilesToClose ( [ ] ) ;
402
437
setShowLocalUnsavedDialog ( false ) ;
403
438
} }
439
+ fileCountToClose = { filesToClose . length }
404
440
/>
405
441
</ div >
406
442
</ div >
0 commit comments