box-content-preview 3.72.0 → 3.73.0

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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![Mergify Status](https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/box/box-content-preview&style=flat)](https://mergify.io)
3
3
  [![Styled With Prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
4
4
  [![build status](https://img.shields.io/travis/box/box-content-preview/master.svg?style=flat-square)](https://travis-ci.org/box/box-content-preview)
5
- [![version](https://img.shields.io/badge/version-v3.72.0-blue.svg?style=flat-square)](https://github.com/box/box-content-preview)
5
+ [![version](https://img.shields.io/badge/version-v3.73.0-blue.svg?style=flat-square)](https://github.com/box/box-content-preview)
6
6
  [![npm version](https://img.shields.io/npm/v/box-ui-elements.svg?style=flat-square)](https://www.npmjs.com/package/box-ui-elements)
7
7
 
8
8
  # [Box Content Preview](https://developer.box.com/docs/box-content-preview)
@@ -22,11 +22,11 @@ We encourage all users to update their applications to utilize modern browsers f
22
22
 
23
23
  ## Current Version
24
24
 
25
- - Version: v3.72.0
25
+ - Version: v3.73.0
26
26
  - Locale: en-US
27
27
 
28
- https://cdn01.boxcdn.net/platform/preview/3.72.0/en-US/preview.js
29
- https://cdn01.boxcdn.net/platform/preview/3.72.0/en-US/preview.css
28
+ https://cdn01.boxcdn.net/platform/preview/3.73.0/en-US/preview.js
29
+ https://cdn01.boxcdn.net/platform/preview/3.73.0/en-US/preview.css
30
30
 
31
31
  ## Supported Locales
32
32
 
@@ -52,10 +52,10 @@ You can self-host the Box Content Preview library or reference the versions avai
52
52
  <title>Box Content Preview Demo</title>
53
53
 
54
54
  <!-- Latest version of Preview SDK for your locale -->
55
- <script src="https://cdn01.boxcdn.net/platform/preview/3.72.0/en-US/preview.js"></script>
55
+ <script src="https://cdn01.boxcdn.net/platform/preview/3.73.0/en-US/preview.js"></script>
56
56
  <link
57
57
  rel="stylesheet"
58
- href="https://cdn01.boxcdn.net/platform/preview/3.72.0/en-US/preview.css"
58
+ href="https://cdn01.boxcdn.net/platform/preview/3.73.0/en-US/preview.css"
59
59
  />
60
60
  </head>
61
61
  <body>
@@ -76,7 +76,7 @@ To self-host the Box Content Preview library, follow these steps:
76
76
 
77
77
  1. Either fork the repo and check out the version you want to host or download the specific version as a zip:
78
78
 
79
- - Check out a specific version with `git checkout v3.72.0`
79
+ - Check out a specific version with `git checkout v3.73.0`
80
80
  - Download a specific version as a zip from https://github.com/box/box-content-preview/releases
81
81
 
82
82
  2. Install dependencies and build the library with `yarn install && yarn build:i18n && yarn build:prod`
package/dist/lib/index.js CHANGED
@@ -15388,7 +15388,7 @@ function util_toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var
15388
15388
  const CLIENT_NAME = "box-content-preview"; // eslint-disable-line no-undef
15389
15389
  const CLIENT_NAME_KEY = 'box_client_name';
15390
15390
  const CLIENT_VERSION_KEY = 'box_client_version';
15391
- const CLIENT_VERSION = "3.72.0"; // eslint-disable-line no-undef
15391
+ const CLIENT_VERSION = "3.73.0"; // eslint-disable-line no-undef
15392
15392
  const HEADER_CLIENT_NAME = 'X-Box-Client-Name';
15393
15393
  const HEADER_CLIENT_VERSION = 'X-Box-Client-Version';
15394
15394
  const PROMISE_MAP = {};
@@ -17083,7 +17083,7 @@ class Browser {
17083
17083
  ;// ./src/lib/Logger.js
17084
17084
  /* eslint-disable no-undef */
17085
17085
  const Logger_CLIENT_NAME = "box-content-preview";
17086
- const Logger_CLIENT_VERSION = "3.72.0";
17086
+ const Logger_CLIENT_VERSION = "3.73.0";
17087
17087
  /* eslint-enable no-undef */
17088
17088
 
17089
17089
  class Logger {
@@ -37237,10 +37237,17 @@ function Thumbnail_toPropertyKey(t) { var i = Thumbnail_toPrimitive(t, "string")
37237
37237
  function Thumbnail_toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
37238
37238
 
37239
37239
 
37240
+
37240
37241
  const CLASS_BOX_PREVIEW_THUMBNAIL_IMAGE = 'bp-thumbnail-image';
37241
37242
  const THUMBNAIL_TOTAL_WIDTH = 150; // 190px sidebar width - 40px margins
37242
37243
  const THUMBNAIL_IMAGE_WIDTH = THUMBNAIL_TOTAL_WIDTH * 2; // Multiplied by a scaling factor so that we render the image at a higher resolution
37243
37244
 
37245
+ function createRejectedTask(message) {
37246
+ return {
37247
+ cancel: (noop_default()),
37248
+ promise: Promise.reject(new Error(message))
37249
+ };
37250
+ }
37244
37251
  class Thumbnail {
37245
37252
  /**
37246
37253
  * [constructor]
@@ -37252,13 +37259,17 @@ class Thumbnail {
37252
37259
  Thumbnail_defineProperty(this, "pdfViewer", void 0);
37253
37260
  /** @property {Object} - Cache for the thumbnail image elements */
37254
37261
  Thumbnail_defineProperty(this, "thumbnailImageCache", void 0);
37262
+ /** @property {Set} - Active PDF.js render tasks */
37263
+ Thumbnail_defineProperty(this, "renderTasks", void 0);
37255
37264
  Thumbnail_defineProperty(this, "preloader", void 0);
37256
37265
  this.preloader = preloader;
37257
37266
  this.createImageEl = this.createImageEl.bind(this);
37258
37267
  this.createThumbnailImage = this.createThumbnailImage.bind(this);
37259
37268
  this.getThumbnailDataURL = this.getThumbnailDataURL.bind(this);
37269
+ this.renderPageImage = this.renderPageImage.bind(this);
37260
37270
  this.pdfViewer = pdfViewer;
37261
37271
  this.thumbnailImageCache = new lib_BoundedCache();
37272
+ this.renderTasks = new Set();
37262
37273
  }
37263
37274
 
37264
37275
  /**
@@ -37267,6 +37278,11 @@ class Thumbnail {
37267
37278
  * @return {void}
37268
37279
  */
37269
37280
  destroy() {
37281
+ if (this.renderTasks) {
37282
+ this.renderTasks.forEach(task => task.cancel());
37283
+ this.renderTasks.clear();
37284
+ this.renderTasks = null;
37285
+ }
37270
37286
  if (this.thumbnailImageCache) {
37271
37287
  this.thumbnailImageCache.destroy();
37272
37288
  this.thumbnailImageCache = null;
@@ -37385,6 +37401,12 @@ class Thumbnail {
37385
37401
  if (!this.thumbnailImageCache) {
37386
37402
  return null;
37387
37403
  }
37404
+ if (!dataUrl) {
37405
+ this.thumbnailImageCache.set(itemIndex, Thumbnail_objectSpread(Thumbnail_objectSpread({}, cacheEntry), {}, {
37406
+ inProgress: false
37407
+ }));
37408
+ return null;
37409
+ }
37388
37410
  const imageEl = this.createImageEl(dataUrl, thumbOptions);
37389
37411
  // Cache this image element for future use
37390
37412
  this.thumbnailImageCache.set(itemIndex, {
@@ -37414,22 +37436,35 @@ class Thumbnail {
37414
37436
  }
37415
37437
 
37416
37438
  /**
37417
- * Given a page number, generates the image data URL for the image of the page
37418
- * @param {number} pageNum - The page number of the document
37419
- * @return {string} The data URL of the page image
37439
+ * Renders a page image without caching it.
37440
+ * @param {number} pageNum - The page number of the document (1 indexed)
37441
+ * @param {Object} thumbOptions - Thumbnail render options
37442
+ * @return {Object} A cancellable render task
37420
37443
  */
37421
- getThumbnailDataURL(pageNum, thumbOptions) {
37422
- if (this.preloader?.preloadedImages?.[pageNum]) {
37423
- return Promise.resolve(this.preloader.preloadedImages[pageNum]);
37424
- }
37444
+ renderPageImage(pageNum, thumbOptions) {
37425
37445
  const pdfDocument = this.pdfViewer?.pdfDocument;
37426
37446
  if (!pdfDocument) {
37427
- return Promise.reject(new Error('PDF document not ready'));
37447
+ return createRejectedTask('PDF document not ready');
37428
37448
  }
37429
- const canvas = document.createElement('canvas');
37430
37449
  const thumbnailImageWidth = thumbOptions && thumbOptions.thumbMaxWidth ? thumbOptions.thumbMaxWidth : THUMBNAIL_IMAGE_WIDTH;
37431
- return pdfDocument.getPage(pageNum).then(page => {
37432
- if (!this.thumbnailImageCache) {
37450
+ if (!isFinite_default()(thumbnailImageWidth) || thumbnailImageWidth <= 0) {
37451
+ return createRejectedTask('Invalid thumbnail render dimensions');
37452
+ }
37453
+ const canvas = document.createElement('canvas');
37454
+ let isCancelled = false;
37455
+ let pdfRenderTask = null;
37456
+ const task = {
37457
+ cancel: () => {
37458
+ if (isCancelled) {
37459
+ return;
37460
+ }
37461
+ isCancelled = true;
37462
+ pdfRenderTask?.cancel();
37463
+ },
37464
+ promise: null
37465
+ };
37466
+ task.promise = pdfDocument.getPage(pageNum).then(page => {
37467
+ if (isCancelled || !this.thumbnailImageCache) {
37433
37468
  return null;
37434
37469
  }
37435
37470
  const rotation = ((this.pdfViewer.pagesRotation || 0) + page.rotate) % 360;
@@ -37437,48 +37472,249 @@ class Thumbnail {
37437
37472
  scale: 1,
37438
37473
  rotation
37439
37474
  });
37440
- if (!viewport || !isFinite_default()(viewport.width) || !isFinite_default()(viewport.height)) {
37475
+ if (!viewport || !isFinite_default()(viewport.width) || viewport.width <= 0 || !isFinite_default()(viewport.height) || viewport.height <= 0) {
37441
37476
  return Promise.reject(new Error('Invalid page viewport'));
37442
37477
  }
37443
37478
  const {
37444
37479
  width,
37445
37480
  height
37446
37481
  } = viewport;
37447
- // Get the current page w:h ratio in case it differs from the first page
37448
- const curPageRatio = width / height;
37449
-
37450
- // Handle the case where the current page's w:h ratio is less than the
37451
- // `pageRatio` which means that this page is probably more portrait than
37452
- // landscape
37453
- if (curPageRatio < this.pageRatio) {
37454
- // Set the canvas height to that of the thumbnail max height
37482
+ const currentPageRatio = width / height;
37483
+
37484
+ // More-portrait pages are constrained by the first page's height budget.
37485
+ if (currentPageRatio < this.pageRatio) {
37455
37486
  canvas.height = Math.ceil(thumbnailImageWidth / this.pageRatio);
37456
- // Find the canvas width based on the current page ratio
37457
- canvas.width = canvas.height * curPageRatio;
37487
+ canvas.width = canvas.height * currentPageRatio;
37458
37488
  } else {
37459
- // In case the current page ratio is same as the first page
37460
- // or in case it's larger (which means that it's wider), keep
37461
- // the width at the max thumbnail width
37462
37489
  canvas.width = thumbnailImageWidth;
37463
- // Find the height based on the current page ratio
37464
- canvas.height = Math.ceil(thumbnailImageWidth / curPageRatio);
37490
+ canvas.height = Math.ceil(thumbnailImageWidth / currentPageRatio);
37465
37491
  }
37466
- // The amount for which to scale down the current page
37467
37492
  const {
37468
37493
  width: canvasWidth
37469
37494
  } = canvas;
37470
37495
  const scale = canvasWidth / width;
37471
- return page.render({
37496
+ pdfRenderTask = page.render({
37472
37497
  canvasContext: canvas.getContext('2d'),
37473
37498
  viewport: page.getViewport({
37474
37499
  scale,
37475
37500
  rotation
37476
37501
  })
37477
- }).promise;
37478
- }).then(() => canvas.toDataURL());
37502
+ });
37503
+ return pdfRenderTask.promise.then(() => {
37504
+ if (isCancelled || !this.thumbnailImageCache) {
37505
+ return null;
37506
+ }
37507
+ return {
37508
+ dataUrl: canvas.toDataURL(),
37509
+ height: canvas.height,
37510
+ width: canvas.width
37511
+ };
37512
+ });
37513
+ }).catch(error => {
37514
+ if (isCancelled) {
37515
+ return null;
37516
+ }
37517
+ throw error;
37518
+ }).finally(() => {
37519
+ canvas.width = 0;
37520
+ canvas.height = 0;
37521
+ this.renderTasks?.delete(task);
37522
+ });
37523
+ this.renderTasks.add(task);
37524
+ return task;
37525
+ }
37526
+
37527
+ /**
37528
+ * Given a page number, generates the image data URL for the image of the page
37529
+ * @param {number} pageNum - The page number of the document
37530
+ * @return {string} The data URL of the page image
37531
+ */
37532
+ getThumbnailDataURL(pageNum, thumbOptions) {
37533
+ if (this.preloader?.preloadedImages?.[pageNum]) {
37534
+ return Promise.resolve(this.preloader.preloadedImages[pageNum]);
37535
+ }
37536
+ return this.renderPageImage(pageNum, thumbOptions).promise.then(result => result?.dataUrl || null);
37479
37537
  }
37480
37538
  }
37481
37539
  /* harmony default export */ const lib_Thumbnail = (Thumbnail);
37540
+ ;// ./src/lib/viewers/gallery/HighResThumbnailStore.ts
37541
+ function HighResThumbnailStore_ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
37542
+ function HighResThumbnailStore_objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? HighResThumbnailStore_ownKeys(Object(t), !0).forEach(function (r) { HighResThumbnailStore_defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : HighResThumbnailStore_ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
37543
+ function HighResThumbnailStore_defineProperty(e, r, t) { return (r = HighResThumbnailStore_toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
37544
+ function HighResThumbnailStore_toPropertyKey(t) { var i = HighResThumbnailStore_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
37545
+ function HighResThumbnailStore_toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
37546
+ class HighResThumbnailStore {
37547
+ constructor({
37548
+ maxBytes,
37549
+ maxConcurrent,
37550
+ maxPages,
37551
+ onChange,
37552
+ render
37553
+ }) {
37554
+ HighResThumbnailStore_defineProperty(this, "activeCount", 0);
37555
+ HighResThumbnailStore_defineProperty(this, "cache", new Map());
37556
+ HighResThumbnailStore_defineProperty(this, "currentBytes", 0);
37557
+ HighResThumbnailStore_defineProperty(this, "failed", new Set());
37558
+ HighResThumbnailStore_defineProperty(this, "inFlight", new Map());
37559
+ HighResThumbnailStore_defineProperty(this, "isDestroyed", false);
37560
+ HighResThumbnailStore_defineProperty(this, "maxBytes", void 0);
37561
+ HighResThumbnailStore_defineProperty(this, "maxConcurrent", void 0);
37562
+ HighResThumbnailStore_defineProperty(this, "maxPages", void 0);
37563
+ HighResThumbnailStore_defineProperty(this, "onChange", void 0);
37564
+ HighResThumbnailStore_defineProperty(this, "queue", []);
37565
+ HighResThumbnailStore_defineProperty(this, "render", void 0);
37566
+ HighResThumbnailStore_defineProperty(this, "retained", new Set());
37567
+ HighResThumbnailStore_defineProperty(this, "targetWidth", 0);
37568
+ this.maxBytes = maxBytes;
37569
+ this.maxConcurrent = maxConcurrent;
37570
+ this.maxPages = maxPages;
37571
+ this.onChange = onChange;
37572
+ this.render = render;
37573
+ }
37574
+ setRetained(pages, width, pageRatio) {
37575
+ if (this.isDestroyed) {
37576
+ return;
37577
+ }
37578
+ const previousWidth = this.targetWidth;
37579
+ this.targetWidth = width;
37580
+ if (previousWidth !== width) {
37581
+ this.failed.clear();
37582
+ }
37583
+ const ratio = Number.isFinite(pageRatio) && pageRatio > 0 ? pageRatio : 1;
37584
+ const estimatedBytes = width * Math.ceil(width / ratio) * 4;
37585
+ const pageLimit = Math.min(this.maxPages, Math.floor(this.maxBytes / estimatedBytes));
37586
+ const retainedPages = Array.from(new Set(pages)).slice(0, pageLimit);
37587
+ const retainedSet = new Set(retainedPages);
37588
+ this.retained = retainedSet;
37589
+ this.failed.forEach(pageNum => {
37590
+ if (!retainedSet.has(pageNum)) {
37591
+ this.failed.delete(pageNum);
37592
+ }
37593
+ });
37594
+ let didEvict = false;
37595
+ this.cache.forEach((entry, pageNum) => {
37596
+ if (!this.retained.has(pageNum) || entry.requestedWidth !== width) {
37597
+ this.cache.delete(pageNum);
37598
+ this.currentBytes -= entry.bytes;
37599
+ didEvict = true;
37600
+ }
37601
+ });
37602
+ this.inFlight.forEach((pending, pageNum) => {
37603
+ if (!this.retained.has(pageNum) || pending.width !== width) {
37604
+ pending.task.cancel();
37605
+ this.inFlight.delete(pageNum);
37606
+ }
37607
+ });
37608
+ this.queue = retainedPages.filter(pageNum => !this.cache.has(pageNum) && !this.inFlight.has(pageNum) && !this.failed.has(pageNum));
37609
+ if (didEvict) {
37610
+ this.emit();
37611
+ }
37612
+ this.pump();
37613
+ }
37614
+ destroy() {
37615
+ if (this.isDestroyed) {
37616
+ return;
37617
+ }
37618
+ this.isDestroyed = true;
37619
+ this.inFlight.forEach(({
37620
+ task
37621
+ }) => task.cancel());
37622
+ this.inFlight.clear();
37623
+ this.cache.clear();
37624
+ this.failed.clear();
37625
+ this.queue = [];
37626
+ this.retained.clear();
37627
+ this.currentBytes = 0;
37628
+ this.onChange = null;
37629
+ this.render = null;
37630
+ }
37631
+ emit() {
37632
+ if (!this.onChange) {
37633
+ return;
37634
+ }
37635
+ const images = {};
37636
+ this.cache.forEach((entry, pageNum) => {
37637
+ images[pageNum] = entry.dataUrl;
37638
+ });
37639
+ this.onChange(images);
37640
+ }
37641
+ pump() {
37642
+ if (this.isDestroyed || !this.render) {
37643
+ return;
37644
+ }
37645
+ while (this.activeCount < this.maxConcurrent && this.queue.length > 0) {
37646
+ const pageNum = this.queue.shift();
37647
+ if (pageNum && this.retained.has(pageNum)) {
37648
+ this.startRender(pageNum);
37649
+ }
37650
+ }
37651
+ }
37652
+ startRender(pageNum) {
37653
+ if (!this.render) {
37654
+ return;
37655
+ }
37656
+ const width = this.targetWidth;
37657
+ let task;
37658
+ try {
37659
+ task = this.render(pageNum, width);
37660
+ } catch {
37661
+ this.failed.add(pageNum);
37662
+ return;
37663
+ }
37664
+ const pending = {
37665
+ task,
37666
+ width
37667
+ };
37668
+ this.activeCount += 1;
37669
+ this.inFlight.set(pageNum, pending);
37670
+ task.promise.then(result => this.handleRenderResult(pageNum, pending, result), () => this.handleRenderFailure(pageNum, pending)).then(() => this.finishRender(), () => this.finishRender());
37671
+ }
37672
+ clearInFlight(pageNum, pending) {
37673
+ if (this.inFlight.get(pageNum) !== pending) {
37674
+ return false;
37675
+ }
37676
+ this.inFlight.delete(pageNum);
37677
+ return true;
37678
+ }
37679
+ finishRender() {
37680
+ this.activeCount -= 1;
37681
+ this.pump();
37682
+ }
37683
+ handleRenderFailure(pageNum, pending) {
37684
+ if (this.clearInFlight(pageNum, pending) && this.isStillWanted(pageNum, pending)) {
37685
+ this.failed.add(pageNum);
37686
+ }
37687
+ }
37688
+ handleRenderResult(pageNum, pending, result) {
37689
+ if (!this.clearInFlight(pageNum, pending) || !result || !this.isStillWanted(pageNum, pending)) {
37690
+ return;
37691
+ }
37692
+ const {
37693
+ height,
37694
+ width
37695
+ } = result;
37696
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
37697
+ this.failed.add(pageNum);
37698
+ return;
37699
+ }
37700
+ const bytes = width * height * 4;
37701
+ const existing = this.cache.get(pageNum);
37702
+ const nextBytes = this.currentBytes - (existing?.bytes || 0) + bytes;
37703
+ if (nextBytes > this.maxBytes) {
37704
+ this.failed.add(pageNum);
37705
+ return;
37706
+ }
37707
+ this.cache.set(pageNum, HighResThumbnailStore_objectSpread(HighResThumbnailStore_objectSpread({}, result), {}, {
37708
+ bytes,
37709
+ requestedWidth: pending.width
37710
+ }));
37711
+ this.currentBytes = nextBytes;
37712
+ this.emit();
37713
+ }
37714
+ isStillWanted(pageNum, pending) {
37715
+ return !this.isDestroyed && this.targetWidth === pending.width && this.retained.has(pageNum);
37716
+ }
37717
+ }
37482
37718
  ;// ./src/lib/viewers/gallery/useGalleryPinch.ts
37483
37719
 
37484
37720
 
@@ -37613,12 +37849,20 @@ function GalleryGrid_toPrimitive(t, r) { if ("object" != typeof t || !t) return
37613
37849
 
37614
37850
 
37615
37851
 
37852
+
37616
37853
  const GALLERY_THUMB_MAX_WIDTH = 440;
37617
37854
  const CONCURRENT_LOADS = 4;
37618
37855
  const SCROLL_THROTTLE_MS = 200;
37619
37856
  // Keep in sync with the 100% grid rule in GalleryGrid.scss
37620
37857
  const GALLERY_TILE_GAP = 16;
37621
37858
  const GALLERY_TILE_MIN_WIDTH = 220;
37859
+ const GALLERY_THUMB_WIDTH_TIERS = [GALLERY_THUMB_MAX_WIDTH, GALLERY_THUMB_MAX_WIDTH * 2, GALLERY_THUMB_MAX_WIDTH * 3];
37860
+ const GALLERY_THUMB_MAX_TIER = GALLERY_THUMB_WIDTH_TIERS[GALLERY_THUMB_WIDTH_TIERS.length - 1];
37861
+ const GALLERY_THUMB_MAX_DPR = 2;
37862
+ const GALLERY_HIGH_RES_MAX_BYTES = 64 * 1024 * 1024;
37863
+ const GALLERY_HIGH_RES_MAX_PAGES = 16;
37864
+ const GALLERY_HIGH_RES_CONCURRENCY = 2;
37865
+ const GALLERY_DEFAULT_PAGE_RATIO = 0.775;
37622
37866
  const GalleryTile = /*#__PURE__*/__WEBPACK_EXTERNAL_MODULE_react_default__.memo(function GalleryTile({
37623
37867
  pageNum,
37624
37868
  isFocused,
@@ -37678,6 +37922,7 @@ function GalleryGrid({
37678
37922
  thumbnail
37679
37923
  }) {
37680
37924
  const [loadedImages, setLoadedImages] = __WEBPACK_EXTERNAL_MODULE_react_useState__({});
37925
+ const [highResImages, setHighResImages] = __WEBPACK_EXTERNAL_MODULE_react_useState__({});
37681
37926
  const [focusedPage, setFocusedPage] = __WEBPACK_EXTERNAL_MODULE_react_useState__(currentPage);
37682
37927
  const [pageRatio, setPageRatio] = __WEBPACK_EXTERNAL_MODULE_react_useState__(null);
37683
37928
  // Topmost visible page — the scroll anchor used to restore the viewed area after a reflow.
@@ -37690,37 +37935,56 @@ function GalleryGrid({
37690
37935
  const isProcessingRef = __WEBPACK_EXTERNAL_MODULE_react_useRef__(false);
37691
37936
  const isMountedRef = __WEBPACK_EXTERNAL_MODULE_react_useRef__(true);
37692
37937
  const inFlightRef = __WEBPACK_EXTERNAL_MODULE_react_useRef__(new Set());
37938
+ const highResStoreRef = __WEBPACK_EXTERNAL_MODULE_react_useRef__(null);
37693
37939
  const byDistanceFromAnchor = (a, b) => Math.abs(a - anchorPageRef.current) - Math.abs(b - anchorPageRef.current);
37940
+ function getNeededThumbWidth() {
37941
+ if (scaleRef.current === 1) {
37942
+ return GALLERY_THUMB_MAX_WIDTH;
37943
+ }
37944
+ const tile = gridRef.current?.querySelector('[data-page]');
37945
+ const neededWidth = (tile?.offsetWidth || 0) * Math.min(window.devicePixelRatio || 1, GALLERY_THUMB_MAX_DPR);
37946
+ return GALLERY_THUMB_WIDTH_TIERS.find(tier => tier >= neededWidth) || GALLERY_THUMB_MAX_TIER;
37947
+ }
37694
37948
 
37695
- // Unloaded tiles within viewport + buffer, on-screen tiles first so the user never
37696
- // watches a visible hole while off-screen pages load. Pages already being rendered
37697
- // (in flight) are excluded so re-derives can't waste loader slots on them.
37698
- function getUnloadedNearViewport() {
37949
+ // Prioritize visible pages before spending work on the surrounding buffer.
37950
+ function getPagesNearViewport(marginRatio, isEligible) {
37699
37951
  const grid = gridRef.current;
37700
37952
  if (!grid) return [];
37701
37953
  const {
37702
37954
  scrollTop,
37703
37955
  clientHeight
37704
37956
  } = grid;
37705
- const bufferZone = clientHeight * 3;
37706
37957
  const viewportBottom = scrollTop + clientHeight;
37707
- const visibleUnloaded = [];
37708
- const bufferedUnloaded = [];
37709
- const tiles = grid.querySelectorAll('[data-page]');
37710
- tiles.forEach(tile => {
37711
- const el = tile;
37712
- if (!el.dataset.page) return;
37713
- const pageNum = parseInt(el.dataset.page, 10);
37714
- if (inFlightRef.current.has(pageNum) || el.querySelector('img')) return;
37715
- const tileTop = el.offsetTop;
37716
- const tileBottom = tileTop + el.offsetHeight;
37958
+ const margin = clientHeight * marginRatio;
37959
+ const visible = [];
37960
+ const nearby = [];
37961
+ grid.querySelectorAll('[data-page]').forEach(tile => {
37962
+ if (!tile.dataset.page) return;
37963
+ const pageNum = parseInt(tile.dataset.page, 10);
37964
+ if (isEligible && !isEligible(tile, pageNum)) return;
37965
+ const tileTop = tile.offsetTop;
37966
+ const tileBottom = tileTop + tile.offsetHeight;
37717
37967
  if (tileBottom > scrollTop && tileTop < viewportBottom) {
37718
- visibleUnloaded.push(pageNum);
37719
- } else if (tileBottom > scrollTop - bufferZone && tileTop < viewportBottom + bufferZone) {
37720
- bufferedUnloaded.push(pageNum);
37968
+ visible.push(pageNum);
37969
+ } else if (tileBottom > scrollTop - margin && tileTop < viewportBottom + margin) {
37970
+ nearby.push(pageNum);
37721
37971
  }
37722
37972
  });
37723
- return [...visibleUnloaded.sort(byDistanceFromAnchor), ...bufferedUnloaded.sort(byDistanceFromAnchor)];
37973
+ return [...visible.sort(byDistanceFromAnchor), ...nearby.sort(byDistanceFromAnchor)];
37974
+ }
37975
+ function syncHighRes() {
37976
+ const store = highResStoreRef.current;
37977
+ if (!store) return;
37978
+ const width = getNeededThumbWidth();
37979
+ const ratio = thumbnail.pageRatio || GALLERY_DEFAULT_PAGE_RATIO;
37980
+ if (width === GALLERY_THUMB_MAX_WIDTH) {
37981
+ store.setRetained([], width, ratio);
37982
+ } else if (!isProcessingRef.current) {
37983
+ store.setRetained(getPagesNearViewport(0.5), width, ratio);
37984
+ }
37985
+ }
37986
+ function getUnloadedNearViewport() {
37987
+ return getPagesNearViewport(3, (tile, pageNum) => !inFlightRef.current.has(pageNum) && !tile.querySelector('img'));
37724
37988
  }
37725
37989
  function processQueue() {
37726
37990
  if (!isMountedRef.current || !thumbnail || queueRef.current.length === 0) {
@@ -37750,6 +38014,7 @@ function GalleryGrid({
37750
38014
  queueRef.current = getUnloadedNearViewport().filter(p => remaining.has(p));
37751
38015
  if (queueRef.current.length === 0) {
37752
38016
  isProcessingRef.current = false;
38017
+ syncHighRes();
37753
38018
  return;
37754
38019
  }
37755
38020
  processQueue();
@@ -37788,6 +38053,7 @@ function GalleryGrid({
37788
38053
  queueRef.current = [...toAdd, ...queueRef.current];
37789
38054
  startProcessing();
37790
38055
  }
38056
+ syncHighRes();
37791
38057
  }, SCROLL_THROTTLE_MS));
37792
38058
 
37793
38059
  // Unthrottled so the anchor is accurate the instant a reflow (e.g. fullscreen) happens.
@@ -37860,6 +38126,20 @@ function GalleryGrid({
37860
38126
  }, [applyZoomLayout, scale]);
37861
38127
  __WEBPACK_EXTERNAL_MODULE_react_useEffect__(() => {
37862
38128
  const throttledScroll = handleScrollRef.current;
38129
+ const highResStore = new HighResThumbnailStore({
38130
+ maxBytes: GALLERY_HIGH_RES_MAX_BYTES,
38131
+ maxConcurrent: GALLERY_HIGH_RES_CONCURRENCY,
38132
+ maxPages: GALLERY_HIGH_RES_MAX_PAGES,
38133
+ onChange: images => {
38134
+ if (isMountedRef.current) {
38135
+ setHighResImages(images);
38136
+ }
38137
+ },
38138
+ render: (pageNum, width) => thumbnail.renderPageImage(pageNum, {
38139
+ thumbMaxWidth: width
38140
+ })
38141
+ });
38142
+ highResStoreRef.current = highResStore;
37863
38143
  if (gridRef.current) {
37864
38144
  const tile = gridRef.current.querySelector(`[data-page="${currentPage}"]`);
37865
38145
  if (tile) {
@@ -37892,11 +38172,14 @@ function GalleryGrid({
37892
38172
  // Guarded start: the mount scrollIntoView can fire the scroll handler first, and a
37893
38173
  // second unguarded pump would double the concurrent thumbnail renders.
37894
38174
  startProcessing();
38175
+ syncHighRes();
37895
38176
  });
37896
38177
  return () => {
37897
38178
  isMountedRef.current = false;
37898
38179
  queueRef.current = [];
37899
38180
  isProcessingRef.current = false;
38181
+ highResStore.destroy();
38182
+ highResStoreRef.current = null;
37900
38183
  throttledScroll.cancel();
37901
38184
  };
37902
38185
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -37927,12 +38210,12 @@ function GalleryGrid({
37927
38210
  observer.observe(grid);
37928
38211
  return () => observer.disconnect();
37929
38212
  }, [applyZoomLayout]);
37930
- const focusTile = __WEBPACK_EXTERNAL_MODULE_react_useCallback__(pageNum => {
38213
+ const focusTile = __WEBPACK_EXTERNAL_MODULE_react_useCallback__((pageNum, options) => {
37931
38214
  const grid = gridRef.current;
37932
38215
  if (!grid) return;
37933
38216
  const tile = grid.querySelector(`[data-page="${pageNum}"]`);
37934
38217
  if (tile) {
37935
- tile.focus();
38218
+ tile.focus(options);
37936
38219
  }
37937
38220
  }, []);
37938
38221
  const handleTileFocus = __WEBPACK_EXTERNAL_MODULE_react_useCallback__(pageNum => {
@@ -37946,7 +38229,9 @@ function GalleryGrid({
37946
38229
  }, [onPageNavigate]);
37947
38230
  const handleGridFocus = __WEBPACK_EXTERNAL_MODULE_react_useCallback__(event => {
37948
38231
  if (event.target === gridRef.current) {
37949
- focusTile(focusedPage);
38232
+ focusTile(focusedPage, {
38233
+ preventScroll: true
38234
+ });
37950
38235
  }
37951
38236
  }, [focusedPage, focusTile]);
37952
38237
  const handleGridKeyDown = __WEBPACK_EXTERNAL_MODULE_react_useCallback__(event => {
@@ -37997,7 +38282,7 @@ function GalleryGrid({
37997
38282
  for (let i = 1; i <= pageCount; i += 1) {
37998
38283
  tiles.push(/*#__PURE__*/__WEBPACK_EXTERNAL_MODULE_react_default__.createElement(GalleryTile, {
37999
38284
  key: i,
38000
- imageSrc: loadedImages[i],
38285
+ imageSrc: highResImages[i] || loadedImages[i],
38001
38286
  isFocused: i === focusedPage,
38002
38287
  onClick: handleTileClick,
38003
38288
  onFocus: handleTileFocus,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-content-preview",
3
- "version": "3.72.0",
3
+ "version": "3.73.0",
4
4
  "description": "Box Content Preview UI Element",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",