Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 53 additions & 9 deletions components/object/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export function ObjectList({
const [showDeleted, setShowDeleted] = useLocalStorage("object-list-show-deleted", false)
const [loading, setLoading] = React.useState(false)
const [listError, setListError] = React.useState<ObjectListError | null>(null)
const [loadMoreError, setLoadMoreError] = React.useState(false)
const [data, setData] = React.useState<ObjectRow[]>([])
const [nextToken, setNextToken] = React.useState<string | undefined>()
const [showScrollShortcuts, setShowScrollShortcuts] = React.useState(false)
Expand Down Expand Up @@ -218,6 +219,7 @@ export function ObjectList({
loadingRef.current = true
setLoading(true)
if (!shouldAppend) setListError(null)
setLoadMoreError(false)
try {
const response = await listObject(bucket, prefix || undefined, resolvedPageSize, token, {
includeDeleted: showDeleted,
Expand Down Expand Up @@ -265,7 +267,6 @@ export function ObjectList({
}
} catch (error) {
console.error("Failed to fetch objects:", error)
message.error((error as Error)?.message ?? t("Failed to load objects"))
if (
shouldApplyObjectListResponse({
requestId,
Expand All @@ -274,8 +275,11 @@ export function ObjectList({
activeScope: activeScopeRef.current,
})
) {
setNextToken(undefined)
if (!shouldAppend) {
if (shouldAppend) {
setLoadMoreError(true)
} else {
message.error((error as Error)?.message ?? t("Failed to load objects"))
setNextToken(undefined)
setData([])
setListError(isAccessDeniedError(error) ? "accessDenied" : "loadFailed")
}
Expand Down Expand Up @@ -502,7 +506,14 @@ export function ObjectList({
},
{
id: "lastModified",
header: () => t("Last Modified"),
header: () => (
<span className="flex flex-col items-start">
<span>{t("Last Modified")}</span>
{nextToken ? (
<span className="text-xs font-normal text-muted-foreground">{t("Loaded objects only")}</span>
) : null}
</span>
),
accessorFn: (row) => (row.type === "prefix" || !row.LastModified ? "" : new Date(row.LastModified).getTime()),
cell: ({ row }) => (row.original.LastModified ? formatDateTime(row.original.LastModified) : "-"),
},
Expand Down Expand Up @@ -567,6 +578,7 @@ export function ObjectList({
prefix,
bucketVersioningState,
openDeleteDialog,
nextToken,
],
)

Expand Down Expand Up @@ -808,6 +820,20 @@ export function ObjectList({
) : null}
</>
) : null}
{data.length > 0 ? (
<Button
type="button"
variant="outline"
onClick={loadNextBatch}
aria-disabled={!nextToken || loading}
className="aria-disabled:pointer-events-none aria-disabled:opacity-50"
>
{loading ? <Spinner className="size-4" aria-hidden /> : null}
<span>
{loading ? t("Loading more objects") : nextToken ? t("Load next objects") : t("All objects loaded")}
</span>
</Button>
) : null}
<Button variant="outline" onClick={() => (onRefresh ? onRefresh() : resetAndFetchObjects())}>
<RiRefreshLine className="size-4" aria-hidden />
<span>{t("Refresh")}</span>
Expand Down Expand Up @@ -862,16 +888,34 @@ export function ObjectList({
</div>
) : null}

{nextToken ? (
<div ref={loadMoreRef} className="flex min-h-10 items-center justify-center text-sm text-muted-foreground">
{loading && data.length > 0 ? (
{data.length > 0 ? (
<div
ref={loadMoreRef}
role={loadMoreError ? "alert" : undefined}
className="flex min-h-10 flex-wrap items-center justify-center gap-3 text-sm text-muted-foreground"
>
{loadMoreError ? (
<span className="text-destructive">
{t("Failed to load more objects. Loaded objects remain available.")}
</span>
) : loading && nextToken ? (
<span className="inline-flex items-center gap-2">
<Spinner className="size-4" />
{t("Loading more objects")}
</span>
) : (
) : nextToken ? (
<span>{t("Scroll to load more objects")}</span>
)}
) : null}
<Button
type="button"
variant="outline"
size="sm"
onClick={loadNextBatch}
aria-disabled={!nextToken || loading}
className="aria-disabled:pointer-events-none aria-disabled:opacity-50"
>
{loadMoreError ? t("Retry") : nextToken ? t("Load next objects") : t("All objects loaded")}
</Button>
</div>
) : null}

Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/ar-MA.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "الكائنات المحملة فقط",
"Load next objects": "تحميل الكائنات التالية",
"All objects loaded": "تم تحميل جميع الكائنات",
"Failed to load more objects. Loaded objects remain available.": "فشل تحميل المزيد من الكائنات. تظل الكائنات المحملة متاحة.",
"Retry": "إعادة المحاولة",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Nur geladene Objekte",
"Load next objects": "Nächste Objekte laden",
"All objects loaded": "Alle Objekte geladen",
"Failed to load more objects. Loaded objects remain available.": "Weitere Objekte konnten nicht geladen werden. Geladene Objekte bleiben verfügbar.",
"Retry": "Erneut versuchen",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Loaded objects only",
"Load next objects": "Load next objects",
"All objects loaded": "All objects loaded",
"Failed to load more objects. Loaded objects remain available.": "Failed to load more objects. Loaded objects remain available.",
"Retry": "Retry",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Solo objetos cargados",
"Load next objects": "Cargar los siguientes objetos",
"All objects loaded": "Todos los objetos cargados",
"Failed to load more objects. Loaded objects remain available.": "No se pudieron cargar más objetos. Los objetos cargados siguen disponibles.",
"Retry": "Reintentar",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Objets chargés uniquement",
"Load next objects": "Charger les objets suivants",
"All objects loaded": "Tous les objets sont chargés",
"Failed to load more objects. Loaded objects remain available.": "Impossible de charger d’autres objets. Les objets chargés restent disponibles.",
"Retry": "Réessayer",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/id-ID.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Hanya objek yang dimuat",
"Load next objects": "Muat objek berikutnya",
"All objects loaded": "Semua objek telah dimuat",
"Failed to load more objects. Loaded objects remain available.": "Gagal memuat lebih banyak objek. Objek yang telah dimuat tetap tersedia.",
"Retry": "Coba lagi",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/it-IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Solo oggetti caricati",
"Load next objects": "Carica gli oggetti successivi",
"All objects loaded": "Tutti gli oggetti sono stati caricati",
"Failed to load more objects. Loaded objects remain available.": "Impossibile caricare altri oggetti. Gli oggetti caricati restano disponibili.",
"Retry": "Riprova",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "読み込み済みオブジェクトのみ",
"Load next objects": "次のオブジェクトを読み込む",
"All objects loaded": "すべてのオブジェクトを読み込みました",
"Failed to load more objects. Loaded objects remain available.": "追加のオブジェクトを読み込めませんでした。読み込み済みのオブジェクトは引き続き利用できます。",
"Retry": "再試行",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/ko-KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "로드된 객체만",
"Load next objects": "다음 객체 로드",
"All objects loaded": "모든 객체 로드됨",
"Failed to load more objects. Loaded objects remain available.": "추가 객체를 로드하지 못했습니다. 로드된 객체는 계속 사용할 수 있습니다.",
"Retry": "다시 시도",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Apenas objetos carregados",
"Load next objects": "Carregar próximos objetos",
"All objects loaded": "Todos os objetos carregados",
"Failed to load more objects. Loaded objects remain available.": "Falha ao carregar mais objetos. Os objetos carregados continuam disponíveis.",
"Retry": "Tentar novamente",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/ru-RU.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Фильтровать загруженные объекты",
"Loaded {count} objects": "Загружено {count} объектов",
"Filtering and sorting apply to loaded objects": "Фильтрация и сортировка применяются к загруженным объектам",
"Loaded objects only": "Только загруженные объекты",
"Load next objects": "Загрузить следующие объекты",
"All objects loaded": "Все объекты загружены",
"Failed to load more objects. Loaded objects remain available.": "Не удалось загрузить дополнительные объекты. Загруженные объекты остаются доступными.",
"Retry": "Повторить",
"Loading more objects": "Загрузить больше объектов",
"Scroll to load more objects": "Прокрутить для загрузки объектов",
"Back to top": "Перейти вверх",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/tr-TR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Yalnızca yüklenen nesneler",
"Load next objects": "Sonraki nesneleri yükle",
"All objects loaded": "Tüm nesneler yüklendi",
"Failed to load more objects. Loaded objects remain available.": "Daha fazla nesne yüklenemedi. Yüklenen nesneler kullanılabilir durumda kalır.",
"Retry": "Yeniden dene",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/vi-VN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "Filter loaded objects",
"Loaded {count} objects": "Loaded {count} objects",
"Filtering and sorting apply to loaded objects": "Filtering and sorting apply to loaded objects",
"Loaded objects only": "Chỉ các đối tượng đã tải",
"Load next objects": "Tải các đối tượng tiếp theo",
"All objects loaded": "Đã tải tất cả đối tượng",
"Failed to load more objects. Loaded objects remain available.": "Không thể tải thêm đối tượng. Các đối tượng đã tải vẫn khả dụng.",
"Retry": "Thử lại",
"Loading more objects": "Loading more objects",
"Scroll to load more objects": "Scroll to load more objects",
"Back to top": "Back to top",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@
"Filter loaded objects": "筛选已加载对象",
"Loaded {count} objects": "已加载 {count} 个对象",
"Filtering and sorting apply to loaded objects": "筛选和排序作用于已加载对象",
"Loaded objects only": "仅已加载对象",
"Load next objects": "加载下一批对象",
"All objects loaded": "已加载所有对象",
"Failed to load more objects. Loaded objects remain available.": "无法加载更多对象。已加载的对象仍然可用。",
"Retry": "重试",
"Loading more objects": "正在加载更多对象",
"Scroll to load more objects": "滚动以加载更多对象",
"Back to top": "回到顶部",
Expand Down
12 changes: 12 additions & 0 deletions tests/lib/object-list-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ test("object list lazy loads additional object batches instead of showing a pagi
assert.equal(source.includes('t("Next Page")'), false)
})

test("last modified sorting discloses partial results and keeps continuation visible", () => {
const source = fs.readFileSync("components/object/list.tsx", "utf8")

assert.match(source, /\{nextToken \? \(\s+<span[^>]*>\{t\("Loaded objects only"\)\}/)
assert.match(source, /\{data\.length > 0 \? \(\s+<Button[\s\S]*?onClick=\{loadNextBatch\}/)
assert.equal(source.includes('t("Load next objects")'), true)
assert.equal(source.includes('t("All objects loaded")'), true)
assert.match(source, /if \(shouldAppend\) \{\s+setLoadMoreError\(true\)\s+\} else \{\s+message\.error/)
assert.equal(source.includes('role={loadMoreError ? "alert" : undefined}'), true)
assert.match(source, /onClick=\{loadNextBatch\}[\s\S]*?\{loadMoreError[\s\S]*?t\("Retry"\)/)
})

test("object list distinguishes filtered-empty states from an empty bucket", () => {
const source = fs.readFileSync("components/object/list.tsx", "utf8")

Expand Down