@internetarchive/collection-browser 4.3.2-alpha-webdev7939.0 → 4.3.2-alpha-webdev7939.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -421,7 +421,7 @@ export class CollectionBrowser
421
421
  const model = this.dataSource.getTileModelAt(index);
422
422
  /**
423
423
  * If we encounter a model we don't have yet and we're not in the middle of an
424
- * automated scroll, fetch the page and just return undefined.
424
+ * automated scroll, schedule a fetch for the missing page and return undefined.
425
425
  * The datasource will be updated once the page is loaded and the cell will be rendered.
426
426
  *
427
427
  * We disable it during the automated scroll since we don't want to fetch pages for intervening cells the
@@ -429,11 +429,61 @@ export class CollectionBrowser
429
429
  */
430
430
  if (!model && !this.isScrollingToCell && this.dataSource.queryInitialized) {
431
431
  const pageNumber = Math.floor(index / this.pageSize) + 1;
432
- this.dataSource.fetchPage(pageNumber);
432
+ this.scheduleDeferredPageFetch(pageNumber);
433
433
  }
434
434
  return model;
435
435
  }
436
436
 
437
+ /**
438
+ * Debounce delay for page fetches initiated by new cells becoming visible.
439
+ * Tuned so quick scrolling through unloaded regions doesn't send rapid-fire
440
+ * search requests for every page we pass through, but to still feel responsive
441
+ * when the scroll ends.
442
+ */
443
+ private static readonly DEFERRED_FETCH_DELAY_MS = 150;
444
+
445
+ private deferredFetchTimer = 0;
446
+
447
+ /**
448
+ * Schedules a fetch for the given page, debounced to ensure we don't
449
+ * rapid-fire fetches while scrolling through pages quickly.
450
+ *
451
+ * If there's no pending fetch timer yet, it will fire a fetch immediately.
452
+ * Otherwise, it will reset any existing timer. In either case, a deferred
453
+ * fetch for the visible pages is scheduled after a brief delay to account
454
+ * for whatever pages we land on after scrolling.
455
+ */
456
+ private scheduleDeferredPageFetch(pageNumber: number): void {
457
+ if (!this.deferredFetchTimer) {
458
+ this.dataSource.fetchPage(pageNumber);
459
+ } else {
460
+ window.clearTimeout(this.deferredFetchTimer);
461
+ }
462
+
463
+ this.deferredFetchTimer = window.setTimeout(() => {
464
+ this.deferredFetchTimer = 0;
465
+ this.fetchVisiblePages();
466
+ }, CollectionBrowser.DEFERRED_FETCH_DELAY_MS);
467
+ }
468
+
469
+ /**
470
+ * Fetch each currently-visible page whose first cell still has no
471
+ * loaded model.
472
+ */
473
+ private fetchVisiblePages(): void {
474
+ const visibleIndices = this.infiniteScroller?.getVisibleCellIndices() ?? [];
475
+ const visiblePages = new Set(
476
+ visibleIndices.map(i => Math.floor(i / this.pageSize) + 1),
477
+ );
478
+
479
+ for (const page of visiblePages) {
480
+ const firstCellOfPage = (page - 1) * this.pageSize;
481
+ if (!this.dataSource.getTileModelAt(firstCellOfPage)) {
482
+ this.dataSource.fetchPage(page);
483
+ }
484
+ }
485
+ }
486
+
437
487
  // this is the total number of tiles we expect if
438
488
  // the data returned is a full page worth
439
489
  // this is useful for putting in placeholders for the expected number of tiles
@@ -1891,6 +1941,11 @@ export class CollectionBrowser
1891
1941
  window.removeEventListener('popstate', this.boundNavigationHandler);
1892
1942
  }
1893
1943
 
1944
+ if (this.deferredFetchTimer) {
1945
+ window.clearTimeout(this.deferredFetchTimer);
1946
+ this.deferredFetchTimer = 0;
1947
+ }
1948
+
1894
1949
  this.leftColIntersectionObserver?.disconnect();
1895
1950
  this.facetsIntersectionObserver?.disconnect();
1896
1951
  window.removeEventListener('resize', this.updateLeftColumnHeight);
@@ -2143,7 +2198,13 @@ export class CollectionBrowser
2143
2198
  private visibleCellsChanged(
2144
2199
  e: CustomEvent<{ visibleCellIndices: number[] }>,
2145
2200
  ) {
2146
- if (this.isScrollingToCell) return;
2201
+ if (this.isScrollingToCell) {
2202
+ // eslint-disable-next-line no-console
2203
+ console.log(
2204
+ `[CB visibleCellsChanged] BAIL isScrollingToCell=true visible=[${e.detail.visibleCellIndices.join(',')}]`,
2205
+ );
2206
+ return;
2207
+ }
2147
2208
  const { visibleCellIndices } = e.detail;
2148
2209
  if (visibleCellIndices.length === 0) return;
2149
2210
 
@@ -2155,6 +2216,20 @@ export class CollectionBrowser
2155
2216
  const lastVisibleCellIndex = visibleCellIndices[lastIndexWithinCurrentPage];
2156
2217
  const lastVisibleCellPage =
2157
2218
  Math.floor(lastVisibleCellIndex / this.pageSize) + 1;
2219
+
2220
+ // Detect out-of-order Set: compare the indices we got to a sorted copy.
2221
+ const sorted = [...visibleCellIndices].sort((a, b) => a - b);
2222
+ const isSorted = sorted.every((v, i) => v === visibleCellIndices[i]);
2223
+ const minIdx = sorted[0];
2224
+ const maxIdx = sorted[sorted.length - 1];
2225
+ const first10 = visibleCellIndices.slice(0, 10).join(',');
2226
+ const last10 = visibleCellIndices.slice(-10).join(',');
2227
+
2228
+ // eslint-disable-next-line no-console
2229
+ console.log(
2230
+ `[CB visibleCellsChanged] computedPage=${lastVisibleCellPage} lastIdx=${lastVisibleCellIndex} sampledAt=${lastIndexWithinCurrentPage} len=${visibleCellIndices.length} sorted=${isSorted} min=${minIdx} max=${maxIdx} scrollY=${window.scrollY} first10=[${first10}] last10=[${last10}]`,
2231
+ );
2232
+
2158
2233
  if (this.currentPage !== lastVisibleCellPage) {
2159
2234
  this.currentPage = lastVisibleCellPage;
2160
2235
  }
@@ -2345,6 +2420,10 @@ export class CollectionBrowser
2345
2420
 
2346
2421
  private async scrollToPage(pageNumber: number): Promise<void> {
2347
2422
  const cellIndexToScrollTo = this.pageSize * (pageNumber - 1);
2423
+ // eslint-disable-next-line no-console
2424
+ console.log(
2425
+ `[CB scrollToPage] ENTRY page=${pageNumber} targetCell=${cellIndexToScrollTo} pagesToRender=${this.pagesToRender} estimatedTileCount=${this.estimatedTileCount} itemCount=${this.infiniteScroller?.itemCount} scrollY=${window.scrollY}`,
2426
+ );
2348
2427
 
2349
2428
  // Wait for the infinite scroller be rendered before proceeding
2350
2429
  let waitAttempts = 0;
@@ -2352,7 +2431,13 @@ export class CollectionBrowser
2352
2431
  await this.updateComplete;
2353
2432
  waitAttempts++;
2354
2433
  }
2355
- if (!this.infiniteScroller) return;
2434
+ if (!this.infiniteScroller) {
2435
+ // eslint-disable-next-line no-console
2436
+ console.log(
2437
+ `[CB scrollToPage] NO_SCROLLER after wait waitAttempts=${waitAttempts}`,
2438
+ );
2439
+ return;
2440
+ }
2356
2441
 
2357
2442
  // The scroller have its default `itemCount=0`, so propagate our estimated
2358
2443
  // tile count before jumping to the desired page.
@@ -2368,8 +2453,15 @@ export class CollectionBrowser
2368
2453
  });
2369
2454
 
2370
2455
  this.isScrollingToCell = true;
2371
- await this.infiniteScroller.scrollToCell(cellIndexToScrollTo, true);
2456
+ const scrolled = await this.infiniteScroller.scrollToCell(
2457
+ cellIndexToScrollTo,
2458
+ true,
2459
+ );
2372
2460
  this.isScrollingToCell = false;
2461
+ // eslint-disable-next-line no-console
2462
+ console.log(
2463
+ `[CB scrollToPage] DONE page=${pageNumber} targetCell=${cellIndexToScrollTo} scrolled=${scrolled} scrollY=${window.scrollY}`,
2464
+ );
2373
2465
  this.infiniteScroller.refreshAllVisibleCells();
2374
2466
  }
2375
2467