box-content-preview 3.72.0 → 3.74.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.74.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.74.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.74.0/en-US/preview.js
29
+ https://cdn01.boxcdn.net/platform/preview/3.74.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.74.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.74.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.74.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.74.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 = {};
@@ -16487,7 +16487,9 @@ const X_REP_HINT_BASE = '[3d][pdf][text][mp3][json]';
16487
16487
  const X_REP_HINT_DOC_THUMBNAIL = '[jpg?dimensions=1024x1024&paged=false]';
16488
16488
  const X_REP_HINT_IMAGE = '[jpg?dimensions=2048x2048,png?dimensions=2048x2048]';
16489
16489
  const X_REP_HINT_VIDEO_DASH = '[dash,mp4][filmstrip]';
16490
+ const X_REP_HINT_VIDEO_DASH_EXTRACTED_TEXT = '[extracted_text]';
16490
16491
  const X_REP_HINT_VIDEO_MP4 = '[mp4]';
16492
+ const AI_TRANSCRIPTION_FOR_VIDEO_SUBTITLES = 'aiTranscriptionForVideoSubtitles';
16491
16493
  const PDFJS_CSS_UNITS = 96.0 / 72.0; // Should match CSS_UNITS in pdf_viewer.js
16492
16494
  const PDFJS_MAX_AUTO_SCALE = 1.25; // Should match MAX_AUTO_SCALE in pdf_viewer.js
16493
16495
  const PDFJS_WIDTH_PADDING_PX = 40; // Should match SCROLLBAR_PADDING in pdf_viewer.js
@@ -17083,7 +17085,7 @@ class Browser {
17083
17085
  ;// ./src/lib/Logger.js
17084
17086
  /* eslint-disable no-undef */
17085
17087
  const Logger_CLIENT_NAME = "box-content-preview";
17086
- const Logger_CLIENT_VERSION = "3.72.0";
17088
+ const Logger_CLIENT_VERSION = "3.74.0";
17087
17089
  /* eslint-enable no-undef */
17088
17090
 
17089
17091
  class Logger {
@@ -27374,6 +27376,23 @@ class Settings extends (events_default()) {
27374
27376
  this.reset();
27375
27377
  }
27376
27378
 
27379
+ /**
27380
+ * Appends a single subtitle entry to the settings menu without disturbing
27381
+ * existing items or the user's current selection.
27382
+ *
27383
+ * @param {string} subtitle - Display name for the subtitle track
27384
+ * @param {number} idx - data-value index for the new menu item
27385
+ * @return {void}
27386
+ */
27387
+ addSubtitle(subtitle, idx) {
27388
+ const subtitlesSubMenu = this.settingsEl.querySelector('.bp-media-settings-menu-subtitles');
27389
+ insertTemplate(subtitlesSubMenu, SUBMENU_SUBITEM_TEMPLATE.replace(/{{dataType}}/g, 'subtitles').replace(/{{dataValue}}/g, idx));
27390
+ const languageNode = subtitlesSubMenu.lastChild.querySelector('.bp-media-settings-value');
27391
+ languageNode.textContent = subtitle;
27392
+ this.subtitles.push(subtitle);
27393
+ this.reset();
27394
+ }
27395
+
27377
27396
  /**
27378
27397
  * Takes an ordered list of audio languages and populates the settings menu
27379
27398
  *
@@ -34426,6 +34445,8 @@ class DashViewer extends media_VideoBaseViewer {
34426
34445
  DashViewer_defineProperty(this, "filmstripInterval", void 0);
34427
34446
  /** @property {Object} - Status of the filmstrip representation */
34428
34447
  DashViewer_defineProperty(this, "filmstripStatus", void 0);
34448
+ /** @property {Object} - Status of the extracted_text (transcription) representation */
34449
+ DashViewer_defineProperty(this, "transcriptionStatus", void 0);
34429
34450
  /** @property {string} - URL for the filmstrip image */
34430
34451
  DashViewer_defineProperty(this, "filmstripUrl", void 0);
34431
34452
  /** @property {string} - ID of the selected audio track */
@@ -34518,6 +34539,9 @@ class DashViewer extends media_VideoBaseViewer {
34518
34539
  if (this.filmstripUrl && this.filmstripUrl.startsWith('blob:')) {
34519
34540
  URL.revokeObjectURL(this.filmstripUrl);
34520
34541
  }
34542
+ if (this.transcriptionStatus) {
34543
+ this.transcriptionStatus.destroy();
34544
+ }
34521
34545
  clearInterval(this.statsIntervalId);
34522
34546
  if (this.player) {
34523
34547
  this.player.destroy();
@@ -35040,6 +35064,20 @@ class DashViewer extends media_VideoBaseViewer {
35040
35064
  this.mediaControls.addListener('audiochange', this.handleAudioTrack);
35041
35065
  }
35042
35066
 
35067
+ /**
35068
+ * Returns the display-friendly name for a text track's language.
35069
+ * Maps the undetermined language code 'und' to a localized "Auto-Generated" label.
35070
+ *
35071
+ * @param {Object} track - A Shaka text track object
35072
+ * @return {string} Localized language name or the raw language code
35073
+ */
35074
+ getTrackDisplayLanguage(track) {
35075
+ if (this.featureEnabled(AI_TRANSCRIPTION_FOR_VIDEO_SUBTITLES) && track.language === 'und') {
35076
+ return "Auto-Generated";
35077
+ }
35078
+ return getLanguageName(track.language) || track.language;
35079
+ }
35080
+
35043
35081
  /**
35044
35082
  * Loads captions/subtitles into the settings menu
35045
35083
  *
@@ -35052,7 +35090,7 @@ class DashViewer extends media_VideoBaseViewer {
35052
35090
  if (this.useReactControls()) {
35053
35091
  this.initSubtitles();
35054
35092
  } else {
35055
- this.mediaControls.initSubtitles(this.textTracks.map(track => getLanguageName(track.language) || track.language), getLanguageName(this.options.location.locale.substring(0, 2)));
35093
+ this.mediaControls.initSubtitles(this.textTracks.map(track => this.getTrackDisplayLanguage(track)), getLanguageName(this.options.location.locale.substring(0, 2)));
35056
35094
  }
35057
35095
  }
35058
35096
  }
@@ -35086,7 +35124,7 @@ class DashViewer extends media_VideoBaseViewer {
35086
35124
  this.cache.set('media-subtitles-toggle', true, true);
35087
35125
  }
35088
35126
  this.textTracks = this.textTracks.map(track => DashViewer_objectSpread(DashViewer_objectSpread({}, track), {}, {
35089
- displayLanguage: getLanguageName(track.language) || track.language
35127
+ displayLanguage: this.getTrackDisplayLanguage(track)
35090
35128
  }));
35091
35129
 
35092
35130
  // Do intelligent selection: Prefer user's language, fallback to English, then first subtitle in list
@@ -35244,6 +35282,7 @@ class DashViewer extends media_VideoBaseViewer {
35244
35282
  this.startBandwidthTracking();
35245
35283
  this.loadFilmStrip();
35246
35284
  this.loadSubtitles();
35285
+ this.loadTranscription();
35247
35286
  this.loadAlternateAudio();
35248
35287
  this.showPlayButton();
35249
35288
  if (this.userRequestedPlay) {
@@ -35348,6 +35387,58 @@ class DashViewer extends media_VideoBaseViewer {
35348
35387
  }
35349
35388
  }
35350
35389
 
35390
+ /**
35391
+ * Loads the extracted_text transcription (.vtt) as a text track when available
35392
+ *
35393
+ * @private
35394
+ * @return {void}
35395
+ */
35396
+ async loadTranscription() {
35397
+ if (!this.featureEnabled(AI_TRANSCRIPTION_FOR_VIDEO_SUBTITLES)) {
35398
+ return;
35399
+ }
35400
+ const extractedText = getRepresentation(this.options.file, 'extracted_text');
35401
+ if (!extractedText?.content?.url_template) {
35402
+ return;
35403
+ }
35404
+ const transcriptionUrl = this.featureEnabled('migrateAccessTokenToHeader') ? this.createContentUrlV2(extractedText.content.url_template) : this.createContentUrlWithAuthParams(extractedText.content.url_template);
35405
+ this.transcriptionStatus = this.getRepStatus(extractedText);
35406
+ try {
35407
+ await this.transcriptionStatus.getPromise();
35408
+ if (this.isDestroyed() || !this.player) {
35409
+ return;
35410
+ }
35411
+ await this.player.addTextTrackAsync(transcriptionUrl, 'und', 'subtitles', 'text/vtt', undefined, "Auto-Generated");
35412
+ if (this.isDestroyed()) {
35413
+ return;
35414
+ }
35415
+ if (this.textTracks.length > 0) {
35416
+ // Subtitles were already initialized — append only the new track(s) at the
35417
+ // end of `this.textTracks`. We must not re-sort: in the non-React path, the
35418
+ // user's selection is cached as an INDEX into `this.textTracks` (see
35419
+ // handleSubtitle) and Settings menu items use the same number as their
35420
+ // `data-value`, so reordering would silently swap which track plays.
35421
+ const existingIds = new Set(this.textTracks.map(t => t.id));
35422
+ const newTracks = this.player.getTextTracks().filter(t => !existingIds.has(t.id));
35423
+ if (this.useReactControls()) {
35424
+ this.textTracks = [...this.textTracks, ...newTracks].map(track => DashViewer_objectSpread(DashViewer_objectSpread({}, track), {}, {
35425
+ displayLanguage: this.getTrackDisplayLanguage(track)
35426
+ }));
35427
+ this.renderUI();
35428
+ } else {
35429
+ newTracks.forEach(track => {
35430
+ this.textTracks.push(track);
35431
+ this.mediaControls.settings.addSubtitle(this.getTrackDisplayLanguage(track), this.textTracks.length - 1);
35432
+ });
35433
+ }
35434
+ } else {
35435
+ this.loadSubtitles();
35436
+ }
35437
+ } catch {
35438
+ // Transcription is non-critical; allow the viewer to continue without it
35439
+ }
35440
+ }
35441
+
35351
35442
  /**
35352
35443
  * Calculates the video dimension based on representations
35353
35444
  *
@@ -37237,10 +37328,17 @@ function Thumbnail_toPropertyKey(t) { var i = Thumbnail_toPrimitive(t, "string")
37237
37328
  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
37329
 
37239
37330
 
37331
+
37240
37332
  const CLASS_BOX_PREVIEW_THUMBNAIL_IMAGE = 'bp-thumbnail-image';
37241
37333
  const THUMBNAIL_TOTAL_WIDTH = 150; // 190px sidebar width - 40px margins
37242
37334
  const THUMBNAIL_IMAGE_WIDTH = THUMBNAIL_TOTAL_WIDTH * 2; // Multiplied by a scaling factor so that we render the image at a higher resolution
37243
37335
 
37336
+ function createRejectedTask(message) {
37337
+ return {
37338
+ cancel: (noop_default()),
37339
+ promise: Promise.reject(new Error(message))
37340
+ };
37341
+ }
37244
37342
  class Thumbnail {
37245
37343
  /**
37246
37344
  * [constructor]
@@ -37252,13 +37350,17 @@ class Thumbnail {
37252
37350
  Thumbnail_defineProperty(this, "pdfViewer", void 0);
37253
37351
  /** @property {Object} - Cache for the thumbnail image elements */
37254
37352
  Thumbnail_defineProperty(this, "thumbnailImageCache", void 0);
37353
+ /** @property {Set} - Active PDF.js render tasks */
37354
+ Thumbnail_defineProperty(this, "renderTasks", void 0);
37255
37355
  Thumbnail_defineProperty(this, "preloader", void 0);
37256
37356
  this.preloader = preloader;
37257
37357
  this.createImageEl = this.createImageEl.bind(this);
37258
37358
  this.createThumbnailImage = this.createThumbnailImage.bind(this);
37259
37359
  this.getThumbnailDataURL = this.getThumbnailDataURL.bind(this);
37360
+ this.renderPageImage = this.renderPageImage.bind(this);
37260
37361
  this.pdfViewer = pdfViewer;
37261
37362
  this.thumbnailImageCache = new lib_BoundedCache();
37363
+ this.renderTasks = new Set();
37262
37364
  }
37263
37365
 
37264
37366
  /**
@@ -37267,6 +37369,11 @@ class Thumbnail {
37267
37369
  * @return {void}
37268
37370
  */
37269
37371
  destroy() {
37372
+ if (this.renderTasks) {
37373
+ this.renderTasks.forEach(task => task.cancel());
37374
+ this.renderTasks.clear();
37375
+ this.renderTasks = null;
37376
+ }
37270
37377
  if (this.thumbnailImageCache) {
37271
37378
  this.thumbnailImageCache.destroy();
37272
37379
  this.thumbnailImageCache = null;
@@ -37385,6 +37492,12 @@ class Thumbnail {
37385
37492
  if (!this.thumbnailImageCache) {
37386
37493
  return null;
37387
37494
  }
37495
+ if (!dataUrl) {
37496
+ this.thumbnailImageCache.set(itemIndex, Thumbnail_objectSpread(Thumbnail_objectSpread({}, cacheEntry), {}, {
37497
+ inProgress: false
37498
+ }));
37499
+ return null;
37500
+ }
37388
37501
  const imageEl = this.createImageEl(dataUrl, thumbOptions);
37389
37502
  // Cache this image element for future use
37390
37503
  this.thumbnailImageCache.set(itemIndex, {
@@ -37414,22 +37527,35 @@ class Thumbnail {
37414
37527
  }
37415
37528
 
37416
37529
  /**
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
37530
+ * Renders a page image without caching it.
37531
+ * @param {number} pageNum - The page number of the document (1 indexed)
37532
+ * @param {Object} thumbOptions - Thumbnail render options
37533
+ * @return {Object} A cancellable render task
37420
37534
  */
37421
- getThumbnailDataURL(pageNum, thumbOptions) {
37422
- if (this.preloader?.preloadedImages?.[pageNum]) {
37423
- return Promise.resolve(this.preloader.preloadedImages[pageNum]);
37424
- }
37535
+ renderPageImage(pageNum, thumbOptions) {
37425
37536
  const pdfDocument = this.pdfViewer?.pdfDocument;
37426
37537
  if (!pdfDocument) {
37427
- return Promise.reject(new Error('PDF document not ready'));
37538
+ return createRejectedTask('PDF document not ready');
37428
37539
  }
37429
- const canvas = document.createElement('canvas');
37430
37540
  const thumbnailImageWidth = thumbOptions && thumbOptions.thumbMaxWidth ? thumbOptions.thumbMaxWidth : THUMBNAIL_IMAGE_WIDTH;
37431
- return pdfDocument.getPage(pageNum).then(page => {
37432
- if (!this.thumbnailImageCache) {
37541
+ if (!isFinite_default()(thumbnailImageWidth) || thumbnailImageWidth <= 0) {
37542
+ return createRejectedTask('Invalid thumbnail render dimensions');
37543
+ }
37544
+ const canvas = document.createElement('canvas');
37545
+ let isCancelled = false;
37546
+ let pdfRenderTask = null;
37547
+ const task = {
37548
+ cancel: () => {
37549
+ if (isCancelled) {
37550
+ return;
37551
+ }
37552
+ isCancelled = true;
37553
+ pdfRenderTask?.cancel();
37554
+ },
37555
+ promise: null
37556
+ };
37557
+ task.promise = pdfDocument.getPage(pageNum).then(page => {
37558
+ if (isCancelled || !this.thumbnailImageCache) {
37433
37559
  return null;
37434
37560
  }
37435
37561
  const rotation = ((this.pdfViewer.pagesRotation || 0) + page.rotate) % 360;
@@ -37437,48 +37563,249 @@ class Thumbnail {
37437
37563
  scale: 1,
37438
37564
  rotation
37439
37565
  });
37440
- if (!viewport || !isFinite_default()(viewport.width) || !isFinite_default()(viewport.height)) {
37566
+ if (!viewport || !isFinite_default()(viewport.width) || viewport.width <= 0 || !isFinite_default()(viewport.height) || viewport.height <= 0) {
37441
37567
  return Promise.reject(new Error('Invalid page viewport'));
37442
37568
  }
37443
37569
  const {
37444
37570
  width,
37445
37571
  height
37446
37572
  } = 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
37573
+ const currentPageRatio = width / height;
37574
+
37575
+ // More-portrait pages are constrained by the first page's height budget.
37576
+ if (currentPageRatio < this.pageRatio) {
37455
37577
  canvas.height = Math.ceil(thumbnailImageWidth / this.pageRatio);
37456
- // Find the canvas width based on the current page ratio
37457
- canvas.width = canvas.height * curPageRatio;
37578
+ canvas.width = canvas.height * currentPageRatio;
37458
37579
  } 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
37580
  canvas.width = thumbnailImageWidth;
37463
- // Find the height based on the current page ratio
37464
- canvas.height = Math.ceil(thumbnailImageWidth / curPageRatio);
37581
+ canvas.height = Math.ceil(thumbnailImageWidth / currentPageRatio);
37465
37582
  }
37466
- // The amount for which to scale down the current page
37467
37583
  const {
37468
37584
  width: canvasWidth
37469
37585
  } = canvas;
37470
37586
  const scale = canvasWidth / width;
37471
- return page.render({
37587
+ pdfRenderTask = page.render({
37472
37588
  canvasContext: canvas.getContext('2d'),
37473
37589
  viewport: page.getViewport({
37474
37590
  scale,
37475
37591
  rotation
37476
37592
  })
37477
- }).promise;
37478
- }).then(() => canvas.toDataURL());
37593
+ });
37594
+ return pdfRenderTask.promise.then(() => {
37595
+ if (isCancelled || !this.thumbnailImageCache) {
37596
+ return null;
37597
+ }
37598
+ return {
37599
+ dataUrl: canvas.toDataURL(),
37600
+ height: canvas.height,
37601
+ width: canvas.width
37602
+ };
37603
+ });
37604
+ }).catch(error => {
37605
+ if (isCancelled) {
37606
+ return null;
37607
+ }
37608
+ throw error;
37609
+ }).finally(() => {
37610
+ canvas.width = 0;
37611
+ canvas.height = 0;
37612
+ this.renderTasks?.delete(task);
37613
+ });
37614
+ this.renderTasks.add(task);
37615
+ return task;
37616
+ }
37617
+
37618
+ /**
37619
+ * Given a page number, generates the image data URL for the image of the page
37620
+ * @param {number} pageNum - The page number of the document
37621
+ * @return {string} The data URL of the page image
37622
+ */
37623
+ getThumbnailDataURL(pageNum, thumbOptions) {
37624
+ if (this.preloader?.preloadedImages?.[pageNum]) {
37625
+ return Promise.resolve(this.preloader.preloadedImages[pageNum]);
37626
+ }
37627
+ return this.renderPageImage(pageNum, thumbOptions).promise.then(result => result?.dataUrl || null);
37479
37628
  }
37480
37629
  }
37481
37630
  /* harmony default export */ const lib_Thumbnail = (Thumbnail);
37631
+ ;// ./src/lib/viewers/gallery/HighResThumbnailStore.ts
37632
+ 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; }
37633
+ 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; }
37634
+ 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; }
37635
+ function HighResThumbnailStore_toPropertyKey(t) { var i = HighResThumbnailStore_toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
37636
+ 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); }
37637
+ class HighResThumbnailStore {
37638
+ constructor({
37639
+ maxBytes,
37640
+ maxConcurrent,
37641
+ maxPages,
37642
+ onChange,
37643
+ render
37644
+ }) {
37645
+ HighResThumbnailStore_defineProperty(this, "activeCount", 0);
37646
+ HighResThumbnailStore_defineProperty(this, "cache", new Map());
37647
+ HighResThumbnailStore_defineProperty(this, "currentBytes", 0);
37648
+ HighResThumbnailStore_defineProperty(this, "failed", new Set());
37649
+ HighResThumbnailStore_defineProperty(this, "inFlight", new Map());
37650
+ HighResThumbnailStore_defineProperty(this, "isDestroyed", false);
37651
+ HighResThumbnailStore_defineProperty(this, "maxBytes", void 0);
37652
+ HighResThumbnailStore_defineProperty(this, "maxConcurrent", void 0);
37653
+ HighResThumbnailStore_defineProperty(this, "maxPages", void 0);
37654
+ HighResThumbnailStore_defineProperty(this, "onChange", void 0);
37655
+ HighResThumbnailStore_defineProperty(this, "queue", []);
37656
+ HighResThumbnailStore_defineProperty(this, "render", void 0);
37657
+ HighResThumbnailStore_defineProperty(this, "retained", new Set());
37658
+ HighResThumbnailStore_defineProperty(this, "targetWidth", 0);
37659
+ this.maxBytes = maxBytes;
37660
+ this.maxConcurrent = maxConcurrent;
37661
+ this.maxPages = maxPages;
37662
+ this.onChange = onChange;
37663
+ this.render = render;
37664
+ }
37665
+ setRetained(pages, width, pageRatio) {
37666
+ if (this.isDestroyed) {
37667
+ return;
37668
+ }
37669
+ const previousWidth = this.targetWidth;
37670
+ this.targetWidth = width;
37671
+ if (previousWidth !== width) {
37672
+ this.failed.clear();
37673
+ }
37674
+ const ratio = Number.isFinite(pageRatio) && pageRatio > 0 ? pageRatio : 1;
37675
+ const estimatedBytes = width * Math.ceil(width / ratio) * 4;
37676
+ const pageLimit = Math.min(this.maxPages, Math.floor(this.maxBytes / estimatedBytes));
37677
+ const retainedPages = Array.from(new Set(pages)).slice(0, pageLimit);
37678
+ const retainedSet = new Set(retainedPages);
37679
+ this.retained = retainedSet;
37680
+ this.failed.forEach(pageNum => {
37681
+ if (!retainedSet.has(pageNum)) {
37682
+ this.failed.delete(pageNum);
37683
+ }
37684
+ });
37685
+ let didEvict = false;
37686
+ this.cache.forEach((entry, pageNum) => {
37687
+ if (!this.retained.has(pageNum) || entry.requestedWidth !== width) {
37688
+ this.cache.delete(pageNum);
37689
+ this.currentBytes -= entry.bytes;
37690
+ didEvict = true;
37691
+ }
37692
+ });
37693
+ this.inFlight.forEach((pending, pageNum) => {
37694
+ if (!this.retained.has(pageNum) || pending.width !== width) {
37695
+ pending.task.cancel();
37696
+ this.inFlight.delete(pageNum);
37697
+ }
37698
+ });
37699
+ this.queue = retainedPages.filter(pageNum => !this.cache.has(pageNum) && !this.inFlight.has(pageNum) && !this.failed.has(pageNum));
37700
+ if (didEvict) {
37701
+ this.emit();
37702
+ }
37703
+ this.pump();
37704
+ }
37705
+ destroy() {
37706
+ if (this.isDestroyed) {
37707
+ return;
37708
+ }
37709
+ this.isDestroyed = true;
37710
+ this.inFlight.forEach(({
37711
+ task
37712
+ }) => task.cancel());
37713
+ this.inFlight.clear();
37714
+ this.cache.clear();
37715
+ this.failed.clear();
37716
+ this.queue = [];
37717
+ this.retained.clear();
37718
+ this.currentBytes = 0;
37719
+ this.onChange = null;
37720
+ this.render = null;
37721
+ }
37722
+ emit() {
37723
+ if (!this.onChange) {
37724
+ return;
37725
+ }
37726
+ const images = {};
37727
+ this.cache.forEach((entry, pageNum) => {
37728
+ images[pageNum] = entry.dataUrl;
37729
+ });
37730
+ this.onChange(images);
37731
+ }
37732
+ pump() {
37733
+ if (this.isDestroyed || !this.render) {
37734
+ return;
37735
+ }
37736
+ while (this.activeCount < this.maxConcurrent && this.queue.length > 0) {
37737
+ const pageNum = this.queue.shift();
37738
+ if (pageNum && this.retained.has(pageNum)) {
37739
+ this.startRender(pageNum);
37740
+ }
37741
+ }
37742
+ }
37743
+ startRender(pageNum) {
37744
+ if (!this.render) {
37745
+ return;
37746
+ }
37747
+ const width = this.targetWidth;
37748
+ let task;
37749
+ try {
37750
+ task = this.render(pageNum, width);
37751
+ } catch {
37752
+ this.failed.add(pageNum);
37753
+ return;
37754
+ }
37755
+ const pending = {
37756
+ task,
37757
+ width
37758
+ };
37759
+ this.activeCount += 1;
37760
+ this.inFlight.set(pageNum, pending);
37761
+ task.promise.then(result => this.handleRenderResult(pageNum, pending, result), () => this.handleRenderFailure(pageNum, pending)).then(() => this.finishRender(), () => this.finishRender());
37762
+ }
37763
+ clearInFlight(pageNum, pending) {
37764
+ if (this.inFlight.get(pageNum) !== pending) {
37765
+ return false;
37766
+ }
37767
+ this.inFlight.delete(pageNum);
37768
+ return true;
37769
+ }
37770
+ finishRender() {
37771
+ this.activeCount -= 1;
37772
+ this.pump();
37773
+ }
37774
+ handleRenderFailure(pageNum, pending) {
37775
+ if (this.clearInFlight(pageNum, pending) && this.isStillWanted(pageNum, pending)) {
37776
+ this.failed.add(pageNum);
37777
+ }
37778
+ }
37779
+ handleRenderResult(pageNum, pending, result) {
37780
+ if (!this.clearInFlight(pageNum, pending) || !result || !this.isStillWanted(pageNum, pending)) {
37781
+ return;
37782
+ }
37783
+ const {
37784
+ height,
37785
+ width
37786
+ } = result;
37787
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
37788
+ this.failed.add(pageNum);
37789
+ return;
37790
+ }
37791
+ const bytes = width * height * 4;
37792
+ const existing = this.cache.get(pageNum);
37793
+ const nextBytes = this.currentBytes - (existing?.bytes || 0) + bytes;
37794
+ if (nextBytes > this.maxBytes) {
37795
+ this.failed.add(pageNum);
37796
+ return;
37797
+ }
37798
+ this.cache.set(pageNum, HighResThumbnailStore_objectSpread(HighResThumbnailStore_objectSpread({}, result), {}, {
37799
+ bytes,
37800
+ requestedWidth: pending.width
37801
+ }));
37802
+ this.currentBytes = nextBytes;
37803
+ this.emit();
37804
+ }
37805
+ isStillWanted(pageNum, pending) {
37806
+ return !this.isDestroyed && this.targetWidth === pending.width && this.retained.has(pageNum);
37807
+ }
37808
+ }
37482
37809
  ;// ./src/lib/viewers/gallery/useGalleryPinch.ts
37483
37810
 
37484
37811
 
@@ -37613,12 +37940,20 @@ function GalleryGrid_toPrimitive(t, r) { if ("object" != typeof t || !t) return
37613
37940
 
37614
37941
 
37615
37942
 
37943
+
37616
37944
  const GALLERY_THUMB_MAX_WIDTH = 440;
37617
37945
  const CONCURRENT_LOADS = 4;
37618
37946
  const SCROLL_THROTTLE_MS = 200;
37619
37947
  // Keep in sync with the 100% grid rule in GalleryGrid.scss
37620
37948
  const GALLERY_TILE_GAP = 16;
37621
37949
  const GALLERY_TILE_MIN_WIDTH = 220;
37950
+ const GALLERY_THUMB_WIDTH_TIERS = [GALLERY_THUMB_MAX_WIDTH, GALLERY_THUMB_MAX_WIDTH * 2, GALLERY_THUMB_MAX_WIDTH * 3];
37951
+ const GALLERY_THUMB_MAX_TIER = GALLERY_THUMB_WIDTH_TIERS[GALLERY_THUMB_WIDTH_TIERS.length - 1];
37952
+ const GALLERY_THUMB_MAX_DPR = 2;
37953
+ const GALLERY_HIGH_RES_MAX_BYTES = 64 * 1024 * 1024;
37954
+ const GALLERY_HIGH_RES_MAX_PAGES = 16;
37955
+ const GALLERY_HIGH_RES_CONCURRENCY = 2;
37956
+ const GALLERY_DEFAULT_PAGE_RATIO = 0.775;
37622
37957
  const GalleryTile = /*#__PURE__*/__WEBPACK_EXTERNAL_MODULE_react_default__.memo(function GalleryTile({
37623
37958
  pageNum,
37624
37959
  isFocused,
@@ -37678,6 +38013,7 @@ function GalleryGrid({
37678
38013
  thumbnail
37679
38014
  }) {
37680
38015
  const [loadedImages, setLoadedImages] = __WEBPACK_EXTERNAL_MODULE_react_useState__({});
38016
+ const [highResImages, setHighResImages] = __WEBPACK_EXTERNAL_MODULE_react_useState__({});
37681
38017
  const [focusedPage, setFocusedPage] = __WEBPACK_EXTERNAL_MODULE_react_useState__(currentPage);
37682
38018
  const [pageRatio, setPageRatio] = __WEBPACK_EXTERNAL_MODULE_react_useState__(null);
37683
38019
  // Topmost visible page — the scroll anchor used to restore the viewed area after a reflow.
@@ -37690,37 +38026,56 @@ function GalleryGrid({
37690
38026
  const isProcessingRef = __WEBPACK_EXTERNAL_MODULE_react_useRef__(false);
37691
38027
  const isMountedRef = __WEBPACK_EXTERNAL_MODULE_react_useRef__(true);
37692
38028
  const inFlightRef = __WEBPACK_EXTERNAL_MODULE_react_useRef__(new Set());
38029
+ const highResStoreRef = __WEBPACK_EXTERNAL_MODULE_react_useRef__(null);
37693
38030
  const byDistanceFromAnchor = (a, b) => Math.abs(a - anchorPageRef.current) - Math.abs(b - anchorPageRef.current);
38031
+ function getNeededThumbWidth() {
38032
+ if (scaleRef.current === 1) {
38033
+ return GALLERY_THUMB_MAX_WIDTH;
38034
+ }
38035
+ const tile = gridRef.current?.querySelector('[data-page]');
38036
+ const neededWidth = (tile?.offsetWidth || 0) * Math.min(window.devicePixelRatio || 1, GALLERY_THUMB_MAX_DPR);
38037
+ return GALLERY_THUMB_WIDTH_TIERS.find(tier => tier >= neededWidth) || GALLERY_THUMB_MAX_TIER;
38038
+ }
37694
38039
 
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() {
38040
+ // Prioritize visible pages before spending work on the surrounding buffer.
38041
+ function getPagesNearViewport(marginRatio, isEligible) {
37699
38042
  const grid = gridRef.current;
37700
38043
  if (!grid) return [];
37701
38044
  const {
37702
38045
  scrollTop,
37703
38046
  clientHeight
37704
38047
  } = grid;
37705
- const bufferZone = clientHeight * 3;
37706
38048
  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;
38049
+ const margin = clientHeight * marginRatio;
38050
+ const visible = [];
38051
+ const nearby = [];
38052
+ grid.querySelectorAll('[data-page]').forEach(tile => {
38053
+ if (!tile.dataset.page) return;
38054
+ const pageNum = parseInt(tile.dataset.page, 10);
38055
+ if (isEligible && !isEligible(tile, pageNum)) return;
38056
+ const tileTop = tile.offsetTop;
38057
+ const tileBottom = tileTop + tile.offsetHeight;
37717
38058
  if (tileBottom > scrollTop && tileTop < viewportBottom) {
37718
- visibleUnloaded.push(pageNum);
37719
- } else if (tileBottom > scrollTop - bufferZone && tileTop < viewportBottom + bufferZone) {
37720
- bufferedUnloaded.push(pageNum);
38059
+ visible.push(pageNum);
38060
+ } else if (tileBottom > scrollTop - margin && tileTop < viewportBottom + margin) {
38061
+ nearby.push(pageNum);
37721
38062
  }
37722
38063
  });
37723
- return [...visibleUnloaded.sort(byDistanceFromAnchor), ...bufferedUnloaded.sort(byDistanceFromAnchor)];
38064
+ return [...visible.sort(byDistanceFromAnchor), ...nearby.sort(byDistanceFromAnchor)];
38065
+ }
38066
+ function syncHighRes() {
38067
+ const store = highResStoreRef.current;
38068
+ if (!store) return;
38069
+ const width = getNeededThumbWidth();
38070
+ const ratio = thumbnail.pageRatio || GALLERY_DEFAULT_PAGE_RATIO;
38071
+ if (width === GALLERY_THUMB_MAX_WIDTH) {
38072
+ store.setRetained([], width, ratio);
38073
+ } else if (!isProcessingRef.current) {
38074
+ store.setRetained(getPagesNearViewport(0.5), width, ratio);
38075
+ }
38076
+ }
38077
+ function getUnloadedNearViewport() {
38078
+ return getPagesNearViewport(3, (tile, pageNum) => !inFlightRef.current.has(pageNum) && !tile.querySelector('img'));
37724
38079
  }
37725
38080
  function processQueue() {
37726
38081
  if (!isMountedRef.current || !thumbnail || queueRef.current.length === 0) {
@@ -37750,6 +38105,7 @@ function GalleryGrid({
37750
38105
  queueRef.current = getUnloadedNearViewport().filter(p => remaining.has(p));
37751
38106
  if (queueRef.current.length === 0) {
37752
38107
  isProcessingRef.current = false;
38108
+ syncHighRes();
37753
38109
  return;
37754
38110
  }
37755
38111
  processQueue();
@@ -37788,6 +38144,7 @@ function GalleryGrid({
37788
38144
  queueRef.current = [...toAdd, ...queueRef.current];
37789
38145
  startProcessing();
37790
38146
  }
38147
+ syncHighRes();
37791
38148
  }, SCROLL_THROTTLE_MS));
37792
38149
 
37793
38150
  // Unthrottled so the anchor is accurate the instant a reflow (e.g. fullscreen) happens.
@@ -37860,6 +38217,20 @@ function GalleryGrid({
37860
38217
  }, [applyZoomLayout, scale]);
37861
38218
  __WEBPACK_EXTERNAL_MODULE_react_useEffect__(() => {
37862
38219
  const throttledScroll = handleScrollRef.current;
38220
+ const highResStore = new HighResThumbnailStore({
38221
+ maxBytes: GALLERY_HIGH_RES_MAX_BYTES,
38222
+ maxConcurrent: GALLERY_HIGH_RES_CONCURRENCY,
38223
+ maxPages: GALLERY_HIGH_RES_MAX_PAGES,
38224
+ onChange: images => {
38225
+ if (isMountedRef.current) {
38226
+ setHighResImages(images);
38227
+ }
38228
+ },
38229
+ render: (pageNum, width) => thumbnail.renderPageImage(pageNum, {
38230
+ thumbMaxWidth: width
38231
+ })
38232
+ });
38233
+ highResStoreRef.current = highResStore;
37863
38234
  if (gridRef.current) {
37864
38235
  const tile = gridRef.current.querySelector(`[data-page="${currentPage}"]`);
37865
38236
  if (tile) {
@@ -37892,11 +38263,14 @@ function GalleryGrid({
37892
38263
  // Guarded start: the mount scrollIntoView can fire the scroll handler first, and a
37893
38264
  // second unguarded pump would double the concurrent thumbnail renders.
37894
38265
  startProcessing();
38266
+ syncHighRes();
37895
38267
  });
37896
38268
  return () => {
37897
38269
  isMountedRef.current = false;
37898
38270
  queueRef.current = [];
37899
38271
  isProcessingRef.current = false;
38272
+ highResStore.destroy();
38273
+ highResStoreRef.current = null;
37900
38274
  throttledScroll.cancel();
37901
38275
  };
37902
38276
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -37927,12 +38301,12 @@ function GalleryGrid({
37927
38301
  observer.observe(grid);
37928
38302
  return () => observer.disconnect();
37929
38303
  }, [applyZoomLayout]);
37930
- const focusTile = __WEBPACK_EXTERNAL_MODULE_react_useCallback__(pageNum => {
38304
+ const focusTile = __WEBPACK_EXTERNAL_MODULE_react_useCallback__((pageNum, options) => {
37931
38305
  const grid = gridRef.current;
37932
38306
  if (!grid) return;
37933
38307
  const tile = grid.querySelector(`[data-page="${pageNum}"]`);
37934
38308
  if (tile) {
37935
- tile.focus();
38309
+ tile.focus(options);
37936
38310
  }
37937
38311
  }, []);
37938
38312
  const handleTileFocus = __WEBPACK_EXTERNAL_MODULE_react_useCallback__(pageNum => {
@@ -37946,7 +38320,9 @@ function GalleryGrid({
37946
38320
  }, [onPageNavigate]);
37947
38321
  const handleGridFocus = __WEBPACK_EXTERNAL_MODULE_react_useCallback__(event => {
37948
38322
  if (event.target === gridRef.current) {
37949
- focusTile(focusedPage);
38323
+ focusTile(focusedPage, {
38324
+ preventScroll: true
38325
+ });
37950
38326
  }
37951
38327
  }, [focusedPage, focusTile]);
37952
38328
  const handleGridKeyDown = __WEBPACK_EXTERNAL_MODULE_react_useCallback__(event => {
@@ -37997,7 +38373,7 @@ function GalleryGrid({
37997
38373
  for (let i = 1; i <= pageCount; i += 1) {
37998
38374
  tiles.push(/*#__PURE__*/__WEBPACK_EXTERNAL_MODULE_react_default__.createElement(GalleryTile, {
37999
38375
  key: i,
38000
- imageSrc: loadedImages[i],
38376
+ imageSrc: highResImages[i] || loadedImages[i],
38001
38377
  isFocused: i === focusedPage,
38002
38378
  onClick: handleTileClick,
38003
38379
  onFocus: handleTileFocus,
@@ -49561,7 +49937,11 @@ class Preview extends (events_default()) {
49561
49937
  * @return {Object} Headers
49562
49938
  */
49563
49939
  getRequestHeaders(token) {
49564
- const videoHint = lib_Browser.canPlayDash() && !this.disabledViewers.Dash ? X_REP_HINT_VIDEO_DASH : X_REP_HINT_VIDEO_MP4;
49940
+ const isDash = lib_Browser.canPlayDash() && !this.disabledViewers.Dash;
49941
+ let videoHint = isDash ? X_REP_HINT_VIDEO_DASH : X_REP_HINT_VIDEO_MP4;
49942
+ if (isDash && isFeatureEnabled(this.options.features, AI_TRANSCRIPTION_FOR_VIDEO_SUBTITLES)) {
49943
+ videoHint += X_REP_HINT_VIDEO_DASH_EXTRACTED_TEXT;
49944
+ }
49565
49945
  const headers = {
49566
49946
  'X-Rep-Hints': `${X_REP_HINT_BASE}${X_REP_HINT_DOC_THUMBNAIL}${X_REP_HINT_IMAGE}${videoHint}`
49567
49947
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-content-preview",
3
- "version": "3.72.0",
3
+ "version": "3.74.0",
4
4
  "description": "Box Content Preview UI Element",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",