feat(breadcrumb): build breadcrumb in 1 API call

add /api/folders/{id}/ancestors

    this API to iterate parent up to the drive root or the shared folder
    this will help UI to build the breadcrumb in 1 API call
    and to identify the root element (is it a drive users has access to or
    a shared folder ?)

    ui: now only 1 API call is now required to build the breadcrumb
This commit is contained in:
Edouard Vanbelle
2026-07-26 21:31:04 +02:00
parent 0efbf0ff85
commit 3b31b8911b
33 changed files with 1342 additions and 221 deletions
@@ -504,6 +504,33 @@
const SKELETON = [0, 1, 2, 3, 4, 5];
// ── Delayed-skeleton reveal ──────────────────────────────────────────
// Fast fetches (< 150 ms) don't render the skeleton bars — the flash
// is worse UX than briefly-empty content. The skeleton appears only
// when a load is genuinely slow. Ed's 2026-07-26 report: navigating
// from an empty folder to its parent showed "6 blank elements" (the
// skeleton) for the ~25 ms fetch window because stale-while-revalidate
// at the /files layer has no previous content to keep on screen here.
//
// Pairs with the empty-state gate below (`!loading && isEmpty`) so
// the pre-fix "Folder is empty" flash during the delay window
// doesn't come back — during load, neither skeleton nor empty state
// renders; the container just holds empty until content or the
// 150 ms timer elapses.
let renderSkeleton = $state(false);
$effect(() => {
if (loading && items.length === 0) {
const timer = setTimeout(() => {
renderSkeleton = true;
}, 150);
return () => {
clearTimeout(timer);
renderSkeleton = false;
};
}
renderSkeleton = false;
});
// ── Group-by / direction ──────────────────────────────────────────────────
const activeGroup = $derived(groupBys?.find((g) => g.key === groupBy));
@@ -1319,9 +1346,15 @@
{#if error}
<EmptyState icon="exclamation-circle" title={error} error />
{:else if loading && isEmpty}
{:else if renderSkeleton}
<!-- Only renders after the 150 ms delay elapses AND we're still
loading with no items — fast loads skip this entirely. -->
<SkeletonList count={SKELETON.length} />
{:else if isEmpty}
{:else if isEmpty && !loading}
<!-- Empty state gates on `!loading` (not just `isEmpty`) so
mid-load empty-content windows don't flash the "Folder is
empty" banner. Renders only when the fetch has definitively
completed with zero items. -->
<EmptyState
icon={emptyIcon}
title={emptyText ?? t('common.empty', 'Nothing here yet.')}