mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix(files): show the loading skeleton instead of "No files yet" before the first listing
This commit is contained in:
@@ -51,7 +51,7 @@ const SHARED = node('e:f-team', 'Team', null, { isShared: true, accountId: 'e',
|
||||
|
||||
type Overrides = Partial<React.ComponentProps<typeof FileBrowser>>;
|
||||
|
||||
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();
|
||||
|
||||
@@ -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 <ListSkeleton />;
|
||||
if (accountPickerMode) {
|
||||
return accountFolders && accountFolders.length > 0 && onSelectAccount ? (
|
||||
<div className="p-4">
|
||||
@@ -903,6 +903,8 @@ export function FileBrowser({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Until the first listing is in, an empty list means "not loaded yet".
|
||||
if ((isLoading || !hasListed) && resources.length === 0) return <ListSkeleton />;
|
||||
if (showEmpty && searchQuery) return <NoSearchResults query={searchQuery} onClear={() => setSearchQuery("")} />;
|
||||
if (showEmpty) {
|
||||
return (
|
||||
|
||||
@@ -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<FileState>((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<FileState>((set, get) => ({
|
||||
currentParentId: null,
|
||||
resources: [],
|
||||
allFolders: [],
|
||||
hasListed: false,
|
||||
selectedResources: new Set<string>(),
|
||||
error: null,
|
||||
isLoading: false,
|
||||
@@ -579,13 +583,14 @@ export const useFileStore = create<FileState>((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: [],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user