@@ -137,6 +137,9 @@ const MIME_TYPE_LABELS: Record<string, string> = {
137137 'text/markdown' : 'Markdown' ,
138138}
139139
140+ const EMPTY_WORKSPACE_FILES : WorkspaceFileRecord [ ] = [ ]
141+ const EMPTY_WORKSPACE_FILE_FOLDERS : WorkspaceFileFolderApi [ ] = [ ]
142+
140143const fileRowId = ( id : string ) => `file:${ id } `
141144const folderRowId = ( id : string ) => `folder:${ id } `
142145const parseRowId = ( rowId : string ) : { kind : 'file' | 'folder' ; id : string } => {
@@ -186,8 +189,9 @@ export function Files() {
186189 }
187190 } , [ permissionConfig . hideFilesTab , router , workspaceId ] )
188191
189- const { data : files = [ ] , isLoading, error } = useWorkspaceFiles ( workspaceId )
190- const { data : folders = [ ] , isLoading : foldersLoading } = useWorkspaceFileFolders ( workspaceId )
192+ const { data : files = EMPTY_WORKSPACE_FILES , isLoading, error } = useWorkspaceFiles ( workspaceId )
193+ const { data : folders = EMPTY_WORKSPACE_FILE_FOLDERS , isLoading : foldersLoading } =
194+ useWorkspaceFileFolders ( workspaceId )
191195 const { data : members } = useWorkspaceMembersQuery ( workspaceId )
192196 const uploadFile = useUploadWorkspaceFile ( )
193197 const deleteFile = useDeleteWorkspaceFile ( )
@@ -493,15 +497,17 @@ export function Files() {
493497 const visibleRowIds = useMemo ( ( ) => rows . map ( ( row ) => row . id ) , [ rows ] )
494498
495499 const prevVisibleRowIdsRef = useRef ( visibleRowIds )
496- if ( prevVisibleRowIdsRef . current !== visibleRowIds ) {
500+ useEffect ( ( ) => {
501+ if ( prevVisibleRowIdsRef . current === visibleRowIds ) return
497502 prevVisibleRowIdsRef . current = visibleRowIds
498503 lastSelectedIndexRef . current = - 1
499504 const visible = new Set ( visibleRowIds )
500505 setSelectedRowIds ( ( prev ) => {
506+ if ( prev . size === 0 ) return prev
501507 const next = new Set ( Array . from ( prev ) . filter ( ( id ) => visible . has ( id ) ) )
502508 return next . size === prev . size ? prev : next
503509 } )
504- }
510+ } , [ visibleRowIds ] )
505511
506512 const isAllSelected =
507513 visibleRowIds . length > 0 && visibleRowIds . every ( ( id ) => selectedRowIds . has ( id ) )
@@ -813,7 +819,6 @@ export function Files() {
813819 } )
814820 . catch ( ( error ) => {
815821 logger . error ( 'Failed to move items via drag and drop:' , error )
816- toast . error ( toError ( error ) . message )
817822 } )
818823 } ,
819824 onDragEnd : ( ) => {
@@ -931,23 +936,26 @@ export function Files() {
931936 isDirtyRef . current = isDirty
932937 const saveStatusRef = useRef ( saveStatus )
933938 saveStatusRef . current = saveStatus
939+ const pendingFileNavigationUrlRef = useRef < string | null > ( null )
934940
935941 const handleSave = useCallback ( async ( ) => {
936942 if ( ! saveRef . current || ! isDirtyRef . current || saveStatusRef . current === 'saving' ) return
937943 await saveRef . current ( )
938944 } , [ ] )
939945
940- const handleBackAttempt = useCallback ( ( ) => {
941- const backUrl = currentFolderId
942- ? `/workspace/${ workspaceId } /files?folderId=${ currentFolderId } `
943- : `/workspace/${ workspaceId } /files`
944- if ( isDirtyRef . current ) {
945- setShowUnsavedChangesAlert ( true )
946- } else {
946+ const handleNavigateFromFileDetail = useCallback (
947+ ( url : string ) => {
948+ if ( isDirtyRef . current ) {
949+ pendingFileNavigationUrlRef . current = url
950+ setShowUnsavedChangesAlert ( true )
951+ return
952+ }
953+
947954 setPreviewMode ( 'editor' )
948- router . push ( backUrl )
949- }
950- } , [ router , workspaceId , currentFolderId ] )
955+ router . push ( url )
956+ } ,
957+ [ router ]
958+ )
951959
952960 const handleStartHeaderRename = useCallback ( ( ) => {
953961 const file = selectedFileRef . current
@@ -997,57 +1005,80 @@ export function Files() {
9971005 window . location . href = `/api/workspaces/${ workspaceId } /files/download?${ query . toString ( ) } `
9981006 } , [ selectedFileIds , selectedFolderIds , files , handleDownload , workspaceId ] )
9991007
1000- const fileDetailBreadcrumbs = useMemo (
1001- ( ) =>
1002- selectedFile
1003- ? [
1004- { label : 'Files' , onClick : handleBackAttempt } ,
1005- {
1006- label : selectedFile . name ,
1007- editing : headerRename . editingId
1008- ? {
1009- isEditing : true ,
1010- value : headerRename . editValue ,
1011- onChange : headerRename . setEditValue ,
1012- onSubmit : headerRename . submitRename ,
1013- onCancel : headerRename . cancelRename ,
1014- }
1015- : undefined ,
1016- dropdownItems : [
1017- { label : 'Download' , icon : Download , onClick : handleDownloadSelected } ,
1018- ...( canEdit
1019- ? [
1020- { label : 'Rename' , icon : Pencil , onClick : handleStartHeaderRename } ,
1021- { label : 'Delete' , icon : Trash2 , onClick : handleDeleteSelected } ,
1022- ]
1023- : [ ] ) ,
1024- ] ,
1025- } ,
1026- ]
1027- : [ ] ,
1028- [
1029- selectedFile ,
1030- canEdit ,
1031- handleBackAttempt ,
1032- headerRename . editingId ,
1033- headerRename . editValue ,
1034- handleStartHeaderRename ,
1035- handleDownloadSelected ,
1036- handleDeleteSelected ,
1008+ const fileDetailBreadcrumbs = useMemo ( ( ) => {
1009+ if ( ! selectedFile ) return [ ]
1010+
1011+ const folderBreadcrumbs : BreadcrumbItem [ ] = [ ]
1012+ const visitedFolderIds = new Set < string > ( )
1013+ let folderId = selectedFile . folderId
1014+
1015+ while ( folderId && ! visitedFolderIds . has ( folderId ) ) {
1016+ visitedFolderIds . add ( folderId )
1017+ const folder = folderById . get ( folderId )
1018+ if ( ! folder ) break
1019+
1020+ folderBreadcrumbs . unshift ( {
1021+ label : folder . name ,
1022+ onClick : ( ) =>
1023+ handleNavigateFromFileDetail ( `/workspace/${ workspaceId } /files?folderId=${ folder . id } ` ) ,
1024+ } )
1025+ folderId = folder . parentId
1026+ }
1027+
1028+ return [
1029+ {
1030+ label : 'Files' ,
1031+ onClick : ( ) => handleNavigateFromFileDetail ( `/workspace/${ workspaceId } /files` ) ,
1032+ } ,
1033+ ...folderBreadcrumbs ,
1034+ {
1035+ label : selectedFile . name ,
1036+ editing : headerRename . editingId
1037+ ? {
1038+ isEditing : true ,
1039+ value : headerRename . editValue ,
1040+ onChange : headerRename . setEditValue ,
1041+ onSubmit : headerRename . submitRename ,
1042+ onCancel : headerRename . cancelRename ,
1043+ }
1044+ : undefined ,
1045+ dropdownItems : [
1046+ { label : 'Download' , icon : Download , onClick : handleDownloadSelected } ,
1047+ ...( canEdit
1048+ ? [
1049+ { label : 'Rename' , icon : Pencil , onClick : handleStartHeaderRename } ,
1050+ { label : 'Delete' , icon : Trash2 , onClick : handleDeleteSelected } ,
1051+ ]
1052+ : [ ] ) ,
1053+ ] ,
1054+ } ,
10371055 ]
1038- )
1056+ } , [
1057+ selectedFile ,
1058+ folderById ,
1059+ handleNavigateFromFileDetail ,
1060+ workspaceId ,
1061+ canEdit ,
1062+ headerRename . editingId ,
1063+ headerRename . editValue ,
1064+ handleStartHeaderRename ,
1065+ handleDownloadSelected ,
1066+ handleDeleteSelected ,
1067+ ] )
10391068
10401069 const handleDiscardChanges = ( ) => {
10411070 setShowUnsavedChangesAlert ( false )
10421071 setIsDirty ( false )
10431072 setSaveStatus ( 'idle' )
10441073 setPreviewMode ( 'editor' )
10451074 const folderId = selectedFileRef . current ?. folderId
1046- router . push (
1047- folderId
1075+ const targetUrl =
1076+ pendingFileNavigationUrlRef . current ??
1077+ ( folderId
10481078 ? `/workspace/${ workspaceId } /files?folderId=${ folderId } `
1049- : `/workspace/${ workspaceId } /files`
1050- )
1079+ : `/workspace/${ workspaceId } /files` )
1080+ pendingFileNavigationUrlRef . current = null
1081+ router . push ( targetUrl )
10511082 }
10521083
10531084 const creatingFileRef = useRef ( creatingFile )
@@ -1213,7 +1244,6 @@ export function Files() {
12131244 closeContextMenu ( )
12141245 } catch ( error ) {
12151246 logger . error ( 'Failed to move items:' , error )
1216- toast . error ( toError ( error ) . message )
12171247 }
12181248 } ,
12191249 [ workspaceId , selectedFileIds , selectedFolderIds , closeContextMenu ]
@@ -1240,7 +1270,8 @@ export function Files() {
12401270 } , [ canEdit , uploading , closeListContextMenu ] )
12411271
12421272 const prevFileIdRef = useRef ( fileIdFromRoute )
1243- if ( fileIdFromRoute !== prevFileIdRef . current ) {
1273+ useEffect ( ( ) => {
1274+ if ( fileIdFromRoute === prevFileIdRef . current ) return
12441275 prevFileIdRef . current = fileIdFromRoute
12451276 const isJustCreated =
12461277 isNewFile || ( fileIdFromRoute != null && justCreatedFileIdRef . current === fileIdFromRoute )
@@ -1255,10 +1286,8 @@ export function Files() {
12551286 : null
12561287 return file && isPreviewable ( file ) ? 'preview' : 'editor'
12571288 } ) ( )
1258- if ( nextMode !== previewMode ) {
1259- setPreviewMode ( nextMode )
1260- }
1261- }
1289+ setPreviewMode ( ( current ) => ( nextMode === current ? current : nextMode ) )
1290+ } , [ fileIdFromRoute , isNewFile ] )
12621291
12631292 useEffect ( ( ) => {
12641293 if ( isNewFile && fileIdFromRoute ) {
0 commit comments