diff --git a/components/files/__tests__/file-browser-shared-folders.test.tsx b/components/files/__tests__/file-browser-shared-folders.test.tsx index ffa80d2fe..d37cc1757 100644 --- a/components/files/__tests__/file-browser-shared-folders.test.tsx +++ b/components/files/__tests__/file-browser-shared-folders.test.tsx @@ -51,7 +51,7 @@ const SHARED = node('e:f-team', 'Team', null, { isShared: true, accountId: 'e', type Overrides = Partial>; -function renderBrowser(opts: { resources?: FileResource[]; allFolders?: FileResource[]; props?: Overrides } = {}) { +function renderBrowser(opts: { resources?: FileResource[]; allFolders?: FileResource[]; props?: Overrides; hasListed?: boolean } = {}) { const client = { listAllFileNodesAcrossAccounts: vi.fn(async () => [...OWN, SHARED]), } as unknown as IJMAPClient; @@ -62,6 +62,7 @@ function renderBrowser(opts: { resources?: FileResource[]; allFolders?: FileReso allFolders: opts.allFolders ?? OWN.filter((n) => n.blobId == null).map(resource), currentParentId: null, pathStack: [{ id: null, name: '' }], + hasListed: opts.hasListed ?? true, }); }); const handlers = { @@ -213,6 +214,12 @@ describe('FileBrowser', () => { expect(names).toEqual(['c.txt', 'b.txt', 'a.txt']); }); + it('shows the loading skeleton, not the empty state, before the first listing', () => { + renderBrowser({ resources: [], allFolders: [], hasListed: false }); + expect(screen.queryByText('empty_state_title')).toBeNull(); + expect(screen.queryByText('empty_folder_title')).toBeNull(); + }); + it('shows an empty state with upload and new folder in an empty folder', () => { renderBrowser({ resources: [], allFolders: [], props: { currentPath: '/Documents' } }); expect(screen.getByText('empty_folder_title')).toBeInTheDocument(); diff --git a/components/files/file-browser.tsx b/components/files/file-browser.tsx index 38fec0400..f245ed5e2 100644 --- a/components/files/file-browser.tsx +++ b/components/files/file-browser.tsx @@ -187,6 +187,7 @@ export function FileBrowser({ const sharedRoots = useFileStore((s) => s.sharedRoots); const loadSharedRoots = useFileStore((s) => s.loadSharedRoots); const allFolders = useFileStore((s) => s.allFolders); + const hasListed = useFileStore((s) => s.hasListed); const currentParentId = useFileStore((s) => s.currentParentId); const pathStack = useFileStore((s) => s.pathStack); useEffect(() => { @@ -873,7 +874,6 @@ export function FileBrowser({ const showEmpty = !isLoading && !error && displayResources.length === 0 && sharedAtRoot.length === 0; const listing = (() => { - if (isLoading && resources.length === 0) return ; if (accountPickerMode) { return accountFolders && accountFolders.length > 0 && onSelectAccount ? (
@@ -903,6 +903,8 @@ export function FileBrowser({
); } + // Until the first listing is in, an empty list means "not loaded yet". + if ((isLoading || !hasListed) && resources.length === 0) return ; if (showEmpty && searchQuery) return setSearchQuery("")} />; if (showEmpty) { return ( diff --git a/stores/file-store.ts b/stores/file-store.ts index f9f54c87e..5a0c56c0e 100644 --- a/stores/file-store.ts +++ b/stores/file-store.ts @@ -80,6 +80,8 @@ interface FileState { * instead of fetching again, and is current after every change. */ allFolders: FileResource[]; + /** False until the first listing for the attached account has finished. */ + hasListed: boolean; // Actions initClient: (client: IJMAPClient, accountId?: string | null) => void; @@ -296,6 +298,7 @@ export const useFileStore = create((set, get) => ({ lastAction: null, sharedRoots: [], allFolders: [], + hasListed: false, favorites: (() => { try { return JSON.parse(localStorage.getItem('files-favorites') || '[]'); } catch { return []; } })(), @@ -319,6 +322,7 @@ export const useFileStore = create((set, get) => ({ currentParentId: null, resources: [], allFolders: [], + hasListed: false, selectedResources: new Set(), error: null, isLoading: false, @@ -579,13 +583,14 @@ export const useFileStore = create((set, get) => ({ const prunedRecent = recentFiles.filter(r => existingIds.has(r.id)); if (prunedRecent.length !== recentFiles.length) { try { localStorage.setItem('files-recent-files', JSON.stringify(prunedRecent)); } catch { /* ignore */ } - set({ resources, allFolders, recentFiles: prunedRecent, isLoading: false }); + set({ resources, allFolders, recentFiles: prunedRecent, isLoading: false, hasListed: true }); } else { - set({ resources, allFolders, isLoading: false }); + set({ resources, allFolders, isLoading: false, hasListed: true }); } } catch (error) { set({ error: error instanceof Error ? error.message : 'Failed to list directory', + hasListed: true, isLoading: false, resources: [], });