@atlaskit/media-card 77.2.3 → 77.3.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cjs/card/card.js +1 -1
  3. package/dist/cjs/card/media-card-analytics-error-boundary.js +1 -1
  4. package/dist/cjs/card/ui/imageRenderer/imageRenderer.js +6 -3
  5. package/dist/cjs/card/v2/cardV2.js +18 -1037
  6. package/dist/cjs/card/v2/externalImageCard.js +299 -0
  7. package/dist/cjs/card/v2/fileCard.js +1001 -0
  8. package/dist/cjs/inline/loader.js +1 -1
  9. package/dist/cjs/utils/ufoExperiences.js +1 -1
  10. package/dist/cjs/utils/useCurrentValueRef.js +12 -0
  11. package/dist/cjs/utils/usePrevious.js +14 -0
  12. package/dist/es2019/card/card.js +1 -1
  13. package/dist/es2019/card/media-card-analytics-error-boundary.js +1 -1
  14. package/dist/es2019/card/ui/imageRenderer/imageRenderer.js +6 -3
  15. package/dist/es2019/card/v2/cardV2.js +17 -1021
  16. package/dist/es2019/card/v2/externalImageCard.js +261 -0
  17. package/dist/es2019/card/v2/fileCard.js +881 -0
  18. package/dist/es2019/inline/loader.js +1 -1
  19. package/dist/es2019/utils/ufoExperiences.js +1 -1
  20. package/dist/es2019/utils/useCurrentValueRef.js +6 -0
  21. package/dist/es2019/utils/usePrevious.js +8 -0
  22. package/dist/esm/card/card.js +1 -1
  23. package/dist/esm/card/media-card-analytics-error-boundary.js +1 -1
  24. package/dist/esm/card/ui/imageRenderer/imageRenderer.js +6 -3
  25. package/dist/esm/card/v2/cardV2.js +19 -1036
  26. package/dist/esm/card/v2/externalImageCard.js +289 -0
  27. package/dist/esm/card/v2/fileCard.js +991 -0
  28. package/dist/esm/inline/loader.js +1 -1
  29. package/dist/esm/utils/ufoExperiences.js +1 -1
  30. package/dist/esm/utils/useCurrentValueRef.js +6 -0
  31. package/dist/esm/utils/usePrevious.js +8 -0
  32. package/dist/types/card/v2/cardV2.d.ts +4 -60
  33. package/dist/types/card/v2/externalImageCard.d.ts +14 -0
  34. package/dist/types/card/v2/fileCard.d.ts +19 -0
  35. package/dist/types/utils/useCurrentValueRef.d.ts +2 -0
  36. package/dist/types/utils/usePrevious.d.ts +1 -0
  37. package/dist/types-ts4.5/card/v2/cardV2.d.ts +4 -60
  38. package/dist/types-ts4.5/card/v2/externalImageCard.d.ts +14 -0
  39. package/dist/types-ts4.5/card/v2/fileCard.d.ts +19 -0
  40. package/dist/types-ts4.5/utils/useCurrentValueRef.d.ts +2 -0
  41. package/dist/types-ts4.5/utils/usePrevious.d.ts +1 -0
  42. package/package.json +2 -2
@@ -0,0 +1,881 @@
1
+ import DownloadIcon from '@atlaskit/icon/glyph/download';
2
+ import { RECENTS_COLLECTION, addFileAttrsToUrl, globalMediaEventEmitter, imageResizeModeToFileImageMode, isFileIdentifier, isImageRepresentationReady } from '@atlaskit/media-client';
3
+ import { useFileState } from '@atlaskit/media-client-react';
4
+ import { getMediaTypeFromMimeType, getRandomHex, isMimeTypeSupportedByBrowser } from '@atlaskit/media-common';
5
+ import { getOrientation } from '@atlaskit/media-ui';
6
+ import { MediaViewer } from '@atlaskit/media-viewer';
7
+ import React, { Suspense, useEffect, useMemo, useRef, useState } from 'react';
8
+ import ReactDOM from 'react-dom';
9
+ import { ImageLoadError, LocalPreviewError, MediaCardError, MediaFileStateError, ensureMediaCardError, isLocalPreviewError, isUnsupportedLocalPreviewError } from '../../errors';
10
+ import { extractErrorInfo } from '../../utils/analytics';
11
+ import { isBigger } from '../../utils/dimensionComparer';
12
+ import getDocument from '../../utils/document';
13
+ import { generateUniqueId } from '../../utils/generateUniqueId';
14
+ import { getRequestedDimensions } from '../../utils/getDataURIDimension';
15
+ import { getMediaCardCursor } from '../../utils/getMediaCardCursor';
16
+ import { getSSRData, StoreSSRDataScript } from '../../utils/globalScope';
17
+ import { getFileDetails } from '../../utils/metadata';
18
+ import { abortUfoExperience, completeUfoExperience, startUfoExperience } from '../../utils/ufoExperiences';
19
+ import { useCurrentValueRef } from '../../utils/useCurrentValueRef';
20
+ import { usePrevious } from '../../utils/usePrevious';
21
+ import { videoIsPlayable } from '../../utils/videoIsPlayable';
22
+ import { takeSnapshot } from '../../utils/videoSnapshot';
23
+ import { ViewportDetector } from '../../utils/viewportDetector';
24
+ import { fireCommencedEvent, fireCopiedEvent, fireNonCriticalErrorEvent, fireOperationalEvent, fireScreenEvent } from '../cardAnalytics';
25
+ import { fetchAndCacheRemotePreview, getCardPreviewFromCache, getSSRCardPreview, isLocalPreview, isSSRClientPreview, isSSRDataPreview, isSSRPreview, removeCardPreviewFromCache, shouldResolvePreview } from '../getCardPreview';
26
+ import cardPreviewCache from '../getCardPreview/cache';
27
+ import { CardViewV2 } from './cardViewV2';
28
+ import { InlinePlayerLazyV2 } from './inlinePlayerLazyV2';
29
+ export const FileCard = ({
30
+ appearance = 'auto',
31
+ resizeMode = 'crop',
32
+ isLazy = true,
33
+ disableOverlay = false,
34
+ // Media Feature Flag defaults are defined in @atlaskit/media-common
35
+ featureFlags = {},
36
+ identifier,
37
+ ssr,
38
+ mediaClient,
39
+ dimensions,
40
+ originalDimensions,
41
+ contextId,
42
+ alt,
43
+ actions,
44
+ shouldEnableDownloadButton,
45
+ useInlinePlayer,
46
+ shouldOpenMediaViewer,
47
+ onFullscreenChange,
48
+ selectable,
49
+ selected,
50
+ testId,
51
+ titleBoxBgColor,
52
+ titleBoxIcon,
53
+ shouldHideTooltip,
54
+ mediaViewerItems,
55
+ onClick,
56
+ onMouseEnter,
57
+ createAnalyticsEvent
58
+ }) => {
59
+ var _ssrReliabilityRef$cu;
60
+ //----------------------------------------------------------------//
61
+ //---------------- State Initializer Functions -------------------//
62
+ //----------------------------------------------------------------//
63
+
64
+ const {
65
+ fileState
66
+ } = useFileState(identifier.id, {
67
+ collectionName: identifier.collectionName,
68
+ occurrenceKey: identifier.occurrenceKey
69
+ });
70
+ const prevFileState = usePrevious(fileState);
71
+ const fileStateValue = useMemo(() => {
72
+ if (fileState && (fileState === null || fileState === void 0 ? void 0 : fileState.status) !== 'error') {
73
+ return fileState;
74
+ }
75
+ return prevFileState;
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps
77
+ }, [fileState]);
78
+ const ssrDataRef = useRef();
79
+ const ssrReliabilityRef = useRef({
80
+ server: {
81
+ status: 'unknown'
82
+ },
83
+ client: {
84
+ status: 'unknown'
85
+ }
86
+ });
87
+ const [cardElement, setCardElement] = useState(null);
88
+ const imageURLParams = useMemo(() => ({
89
+ collection: identifier.collectionName,
90
+ mode: resizeMode === 'stretchy-fit' ? 'full-fit' : resizeMode,
91
+ ...getRequestedDimensions({
92
+ dimensions,
93
+ element: cardElement
94
+ }),
95
+ allowAnimated: true
96
+ }), [cardElement, dimensions, identifier.collectionName, resizeMode]);
97
+ const mediaBlobUrlAttrs = useMemo(() => {
98
+ const {
99
+ id,
100
+ collectionName: collection
101
+ } = identifier;
102
+ const {
103
+ mimeType,
104
+ name,
105
+ size
106
+ } = getFileDetails(identifier, fileStateValue);
107
+ return contextId ? {
108
+ id,
109
+ collection,
110
+ contextId,
111
+ mimeType,
112
+ name,
113
+ size,
114
+ ...(originalDimensions || getRequestedDimensions({
115
+ dimensions,
116
+ element: cardElement
117
+ })),
118
+ alt
119
+ } : undefined;
120
+ }, [alt, cardElement, contextId, dimensions, fileStateValue, identifier, originalDimensions]);
121
+ const getSSRPreview = (ssr, identifier, mediaClient) => {
122
+ var _ssrDataRef$current, _ssrDataRef$current2;
123
+ ssrDataRef.current = getSSRData(identifier);
124
+ if ((_ssrDataRef$current = ssrDataRef.current) !== null && _ssrDataRef$current !== void 0 && _ssrDataRef$current.error) {
125
+ ssrReliabilityRef.current.server = {
126
+ status: 'fail',
127
+ ...ssrDataRef.current.error
128
+ };
129
+ }
130
+ if (!((_ssrDataRef$current2 = ssrDataRef.current) !== null && _ssrDataRef$current2 !== void 0 && _ssrDataRef$current2.dataURI)) {
131
+ try {
132
+ return getSSRCardPreview(ssr, mediaClient, identifier.id, imageURLParams, mediaBlobUrlAttrs);
133
+ } catch (e) {
134
+ ssrReliabilityRef.current[ssr] = {
135
+ status: 'fail',
136
+ ...extractErrorInfo(e)
137
+ };
138
+ }
139
+ } else {
140
+ return {
141
+ dataURI: ssrDataRef.current.dataURI,
142
+ source: 'ssr-data'
143
+ };
144
+ }
145
+ };
146
+ const cardPreviewInitializer = () => {
147
+ let cardPreview;
148
+ const {
149
+ id
150
+ } = identifier;
151
+ const fileImageMode = imageResizeModeToFileImageMode(resizeMode);
152
+ cardPreview = getCardPreviewFromCache(id, fileImageMode);
153
+ if (!cardPreview && ssr) {
154
+ cardPreview = getSSRPreview(ssr, identifier, mediaClient);
155
+ }
156
+ return cardPreview;
157
+ };
158
+
159
+ //----------------------------------------------------------------//
160
+ //------------ State, Refs & Initial Values ----------------------//
161
+ //----------------------------------------------------------------//
162
+
163
+ const internalOccurrenceKey = useMemo(() => generateUniqueId(), []);
164
+ const timeElapsedTillCommenced = useMemo(() => performance.now(), []);
165
+ const fileStateFlagsRef = useRef({
166
+ wasStatusUploading: false,
167
+ wasStatusProcessing: false
168
+ });
169
+
170
+ // Generate unique traceId for file
171
+ const traceContext = useMemo(() => ({
172
+ traceId: getRandomHex(8)
173
+ }), []);
174
+ const [status, setStatus] = useState('loading');
175
+ useEffect(() => {
176
+ setStatus('loading');
177
+ }, [identifier]);
178
+ const [cardPreview, setCardPreview] = useState(cardPreviewInitializer);
179
+
180
+ // If cardPreview is available from local cache or external, `isCardVisible`
181
+ // should be true to avoid flickers during re-mount of the component
182
+ // should not be visible for SSR preview, otherwise we'll fire the metadata fetch from
183
+ // outside the viewport
184
+ const [isCardVisible, setIsCardVisible] = useState(() => !isLazy || !!cardPreview && !isSSRPreview(cardPreview));
185
+ const [isPlayingFile, setIsPlayingFile] = useState(false);
186
+ const [shouldAutoplay, setShouldAutoplay] = useState(false);
187
+ const [isBannedLocalPreview, setIsBannedLocalPreview] = useState(false);
188
+ const [previewDidRender, setPreviewDidRender] = useState(false);
189
+ const [error, setError] = useState();
190
+ const wasResolvedUpfrontPreviewRef = useRef(false);
191
+ const [mediaViewerSelectedItem, setMediaViewerSelectedItem] = useState(null);
192
+ const uploadProgressRef = useRef();
193
+ const metadata = useMemo(() => {
194
+ const getProcessingStatusFromFileState = status => {
195
+ switch (status) {
196
+ case 'processed':
197
+ return 'succeeded';
198
+ case 'processing':
199
+ return 'running';
200
+ case 'failed-processing':
201
+ return 'failed';
202
+ }
203
+ };
204
+ if (fileStateValue && (fileStateValue === null || fileStateValue === void 0 ? void 0 : fileStateValue.status) !== 'error') {
205
+ return {
206
+ id: fileStateValue.id,
207
+ name: fileStateValue.name,
208
+ size: fileStateValue.size,
209
+ mimeType: fileStateValue.mimeType,
210
+ createdAt: fileStateValue.createdAt,
211
+ mediaType: fileStateValue.mediaType,
212
+ processingStatus: getProcessingStatusFromFileState(fileStateValue.status)
213
+ };
214
+ } else {
215
+ return {
216
+ id: identifier.id
217
+ };
218
+ }
219
+ }, [fileStateValue, identifier.id]);
220
+ const fileAttributes = useMemo(() => {
221
+ return {
222
+ fileMediatype: metadata.mediaType,
223
+ fileMimetype: metadata.mimeType,
224
+ fileId: metadata.id,
225
+ fileSize: metadata.size,
226
+ fileStatus: fileStateValue === null || fileStateValue === void 0 ? void 0 : fileStateValue.status
227
+ };
228
+ }, [fileStateValue === null || fileStateValue === void 0 ? void 0 : fileStateValue.status, metadata.id, metadata.mediaType, metadata.mimeType, metadata.size]);
229
+ const computedActions = useMemo(() => {
230
+ if (status === 'failed-processing' || shouldEnableDownloadButton) {
231
+ const downloadAction = {
232
+ label: 'Download',
233
+ icon: /*#__PURE__*/React.createElement(DownloadIcon, {
234
+ label: "Download"
235
+ }),
236
+ handler: () => mediaClient.file.downloadBinary(identifier.id, metadata.name, identifier.collectionName)
237
+ };
238
+ return [downloadAction, ...(actions !== null && actions !== void 0 ? actions : [])];
239
+ } else {
240
+ return actions;
241
+ }
242
+ }, [actions, identifier.collectionName, identifier.id, mediaClient.file, metadata.name, shouldEnableDownloadButton, status]);
243
+
244
+ //----------------------------------------------------------------//
245
+ //---------------------- Analytics ------------------------------//
246
+ //----------------------------------------------------------------//
247
+
248
+ const fireOperationalEventRef = useCurrentValueRef(() => {
249
+ const timeElapsedTillEvent = performance.now();
250
+ const durationSinceCommenced = timeElapsedTillEvent - timeElapsedTillCommenced;
251
+ const performanceAttributes = {
252
+ overall: {
253
+ durationSincePageStart: timeElapsedTillEvent,
254
+ durationSinceCommenced
255
+ }
256
+ };
257
+ createAnalyticsEvent && fireOperationalEvent(createAnalyticsEvent, status, fileAttributes, performanceAttributes, ssrReliabilityRef.current, error, traceContext, fileStateValue === null || fileStateValue === void 0 ? void 0 : fileStateValue.metadataTraceContext);
258
+ completeUfoExperience(internalOccurrenceKey, status, fileAttributes, fileStateFlagsRef.current, ssrReliabilityRef.current, error);
259
+ });
260
+ const fireNonCriticalErrorEventRef = useCurrentValueRef(error => {
261
+ createAnalyticsEvent && fireNonCriticalErrorEvent(createAnalyticsEvent, status, fileAttributes, ssrReliabilityRef.current, error, traceContext, fileStateValue === null || fileStateValue === void 0 ? void 0 : fileStateValue.metadataTraceContext);
262
+ });
263
+ const fireScreenEventRef = useCurrentValueRef(() => {
264
+ createAnalyticsEvent && fireScreenEvent(createAnalyticsEvent, fileAttributes);
265
+ });
266
+ const fireCommencedEventRef = useCurrentValueRef(() => {
267
+ createAnalyticsEvent && fireCommencedEvent(createAnalyticsEvent, fileAttributes, {
268
+ overall: {
269
+ durationSincePageStart: timeElapsedTillCommenced
270
+ }
271
+ }, traceContext);
272
+ startUfoExperience(internalOccurrenceKey);
273
+ });
274
+ const fireAbortedEventRef = useCurrentValueRef(() => {
275
+ // UFO won't abort if it's already in a final state (succeeded, failed, aborted, etc)
276
+ abortUfoExperience(internalOccurrenceKey, {
277
+ fileAttributes,
278
+ fileStateFlags: fileStateFlagsRef === null || fileStateFlagsRef === void 0 ? void 0 : fileStateFlagsRef.current,
279
+ ssrReliability: ssrReliabilityRef === null || ssrReliabilityRef === void 0 ? void 0 : ssrReliabilityRef.current
280
+ });
281
+ });
282
+
283
+ //----------------------------------------------------------------//
284
+ //---------------------- Callbacks & Handlers -------------------//
285
+ //----------------------------------------------------------------//
286
+
287
+ const onImageError = newCardPreview => {
288
+ if (newCardPreview) {
289
+ const failedSSRObject = {
290
+ status: 'fail',
291
+ ...extractErrorInfo(new ImageLoadError(newCardPreview.source))
292
+ };
293
+ if (isSSRClientPreview(newCardPreview)) {
294
+ ssrReliabilityRef.current.client = failedSSRObject;
295
+ }
296
+
297
+ /*
298
+ If the cardPreview failed and it comes from server (global scope / ssrData), it means that we have reused it in client and the error counts for both: server & client.
299
+ */
300
+
301
+ if (isSSRDataPreview(newCardPreview)) {
302
+ ssrReliabilityRef.current.server = failedSSRObject;
303
+ ssrReliabilityRef.current.client = failedSSRObject;
304
+ }
305
+ }
306
+
307
+ // If the dataURI has been replaced, we can dismiss this error
308
+ if ((newCardPreview === null || newCardPreview === void 0 ? void 0 : newCardPreview.dataURI) !== (cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.dataURI)) {
309
+ return;
310
+ }
311
+ const error = new ImageLoadError(newCardPreview === null || newCardPreview === void 0 ? void 0 : newCardPreview.source);
312
+ const isLocal = newCardPreview && isLocalPreview(newCardPreview);
313
+ const isSSR = newCardPreview && (isSSRClientPreview(newCardPreview) || isSSRDataPreview(newCardPreview));
314
+ if (isLocal || isSSR) {
315
+ if (isLocal) {
316
+ setIsBannedLocalPreview(true);
317
+ fireNonCriticalErrorEventRef.current && fireNonCriticalErrorEventRef.current(error);
318
+ }
319
+ const fileImageMode = imageResizeModeToFileImageMode(resizeMode);
320
+ isFileIdentifier(identifier) && removeCardPreviewFromCache(identifier.id, fileImageMode);
321
+ setCardPreview(undefined);
322
+ } else {
323
+ if (!['complete', 'error', 'failed-processing'].includes(status)) {
324
+ setStatus('error');
325
+ setError(error);
326
+ }
327
+ }
328
+ };
329
+ const onImageLoad = newCardPreview => {
330
+ if (newCardPreview) {
331
+ if (isSSRClientPreview(newCardPreview) && ssrReliabilityRef.current.client.status === 'unknown') {
332
+ ssrReliabilityRef.current.client = {
333
+ status: 'success'
334
+ };
335
+ }
336
+
337
+ /*
338
+ If the image loads successfully and it comes from server (global scope / ssrData), it means that we have reused it in client and the success counts for both: server & client.
339
+ */
340
+
341
+ if (isSSRDataPreview(newCardPreview) && ssrReliabilityRef.current.server.status === 'unknown') {
342
+ ssrReliabilityRef.current.server = {
343
+ status: 'success'
344
+ };
345
+ ssrReliabilityRef.current.client = {
346
+ status: 'success'
347
+ };
348
+ }
349
+ }
350
+
351
+ // If the dataURI has been replaced, we can dismiss this callback
352
+ if ((newCardPreview === null || newCardPreview === void 0 ? void 0 : newCardPreview.dataURI) !== (cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.dataURI)) {
353
+ return;
354
+ }
355
+ setPreviewDidRender(true);
356
+ };
357
+ const onCardClick = (event, analyticsEvent) => {
358
+ if (onClick) {
359
+ const cardEvent = {
360
+ event,
361
+ mediaItemDetails: metadata
362
+ };
363
+ onClick(cardEvent, analyticsEvent);
364
+ }
365
+ };
366
+ const onCardViewClick = (event, analyticsEvent) => {
367
+ onCardClick(event, analyticsEvent);
368
+ if (!metadata) {
369
+ return;
370
+ }
371
+ const isVideo = metadata && metadata.mediaType === 'video';
372
+ if (useInlinePlayer && isVideo && !!cardPreview) {
373
+ setIsPlayingFile(true);
374
+ setShouldAutoplay(true);
375
+ } else if (shouldOpenMediaViewer) {
376
+ setMediaViewerSelectedItem({
377
+ id: identifier.id,
378
+ mediaItemType: 'file',
379
+ collectionName: identifier.collectionName,
380
+ occurrenceKey: identifier.occurrenceKey
381
+ });
382
+ }
383
+ };
384
+
385
+ //----------------------------------------------------------------//
386
+ //---------------------- Helper Functions -----------------------//
387
+ //----------------------------------------------------------------//
388
+
389
+ const fetchRemotePreviewRef = useCurrentValueRef(identifier => {
390
+ return fetchAndCacheRemotePreview(mediaClient, identifier.id, dimensions !== null && dimensions !== void 0 ? dimensions : {}, imageURLParams, mediaBlobUrlAttrs);
391
+ });
392
+ const resolvePreviewRef = useCurrentValueRef(async (identifier, fileState) => {
393
+ const filePreview = isBannedLocalPreview ? undefined : fileState.status !== 'error' && 'mimeType' in fileState && isMimeTypeSupportedByBrowser(fileState.mimeType) ? fileState.preview : undefined;
394
+ const isRemotePreviewReady = isImageRepresentationReady(fileState);
395
+ try {
396
+ const mode = imageURLParams.mode;
397
+ const cachedPreview = cardPreviewCache.get(identifier.id, mode);
398
+ const dimensionsAreBigger = isBigger(cachedPreview === null || cachedPreview === void 0 ? void 0 : cachedPreview.dimensions, dimensions);
399
+ if (cachedPreview && !dimensionsAreBigger) {
400
+ return cachedPreview;
401
+ }
402
+ let localPreview;
403
+ try {
404
+ if (filePreview) {
405
+ let value;
406
+ try {
407
+ const resolvedFilePreview = await filePreview;
408
+ value = resolvedFilePreview.value;
409
+ } catch (e) {
410
+ throw new LocalPreviewError('local-preview-rejected', e instanceof Error ? e : undefined);
411
+ }
412
+ if (typeof value === 'string') {
413
+ localPreview = {
414
+ dataURI: value,
415
+ orientation: 1,
416
+ source: 'local'
417
+ };
418
+ } else if (value instanceof Blob) {
419
+ const {
420
+ type
421
+ } = value;
422
+ const mediaType = getMediaTypeFromMimeType(type);
423
+ switch (mediaType) {
424
+ case 'image':
425
+ try {
426
+ const orientation = await getOrientation(value);
427
+ const dataURI = URL.createObjectURL(value);
428
+ localPreview = {
429
+ dataURI,
430
+ orientation,
431
+ source: 'local'
432
+ };
433
+ } catch (e) {
434
+ throw new LocalPreviewError('local-preview-image', e instanceof Error ? e : undefined);
435
+ }
436
+ break;
437
+ case 'video':
438
+ try {
439
+ const dataURI = await takeSnapshot(value);
440
+ localPreview = {
441
+ dataURI,
442
+ orientation: 1,
443
+ source: 'local'
444
+ };
445
+ } catch (e) {
446
+ throw new LocalPreviewError('local-preview-video', e instanceof Error ? e : undefined);
447
+ }
448
+ break;
449
+ default:
450
+ throw new LocalPreviewError('local-preview-unsupported');
451
+ }
452
+ } else {
453
+ throw new LocalPreviewError('local-preview-unsupported');
454
+ }
455
+ const preview = {
456
+ ...localPreview,
457
+ dimensions
458
+ };
459
+ let source;
460
+ switch (preview.source) {
461
+ case 'local':
462
+ source = 'cache-local';
463
+ break;
464
+ case 'remote':
465
+ source = 'cache-remote';
466
+ break;
467
+ case 'ssr-server':
468
+ source = 'cache-ssr-server';
469
+ break;
470
+ case 'ssr-client':
471
+ source = 'cache-ssr-client';
472
+ break;
473
+ default:
474
+ source = preview.source;
475
+ }
476
+ // We want to embed some meta context into dataURI for Copy/Paste to work.
477
+ const dataURI = mediaBlobUrlAttrs ? addFileAttrsToUrl(preview.dataURI, mediaBlobUrlAttrs) : preview.dataURI;
478
+ // We store new cardPreview into cache
479
+ cardPreviewCache.set(identifier.id, mode, {
480
+ ...preview,
481
+ source,
482
+ dataURI
483
+ });
484
+ setCardPreview({
485
+ ...preview,
486
+ dataURI
487
+ });
488
+ return;
489
+ }
490
+ } catch (e) {
491
+ /**
492
+ * We report the error if:
493
+ * - local preview is supported and fails
494
+ * - local preview is unsupported and remote preview is NOT READY
495
+ * i.e. the function was called for "no reason".
496
+ * We DON'T report the error if:
497
+ * - local preview is unsupported and remote preview IS READY
498
+ * i.e. local preview is available and not supported,
499
+ * but we are after the remote preview instead.
500
+ */
501
+ if (!isUnsupportedLocalPreviewError(e) || isUnsupportedLocalPreviewError(e) && !isRemotePreviewReady) {
502
+ fireNonCriticalErrorEventRef.current && fireNonCriticalErrorEventRef.current(e);
503
+ }
504
+ /**
505
+ * No matter the reason why the local preview failed, we break the process
506
+ * if there is no remote preview available
507
+ */
508
+ if (!isRemotePreviewReady) {
509
+ throw e;
510
+ }
511
+ }
512
+ if (!isRemotePreviewReady) {
513
+ /**
514
+ * We throw this in case this function has been called
515
+ * without checking isRemotePreviewReady first.
516
+ * If remote preview is not ready, the call to getCardPreviewFromBackend
517
+ * will generate a console error due to a 404 code
518
+ */
519
+ throw new MediaCardError('remote-preview-not-ready');
520
+ }
521
+ const remotePreview = await fetchAndCacheRemotePreview(mediaClient, identifier.id, dimensions !== null && dimensions !== void 0 ? dimensions : {}, imageURLParams, mediaBlobUrlAttrs, traceContext);
522
+ setCardPreview(remotePreview);
523
+ return;
524
+ } catch (e) {
525
+ const wrappedError = ensureMediaCardError('preview-fetch', e);
526
+ // If remote preview fails, we set status 'error'
527
+ // If local preview fails (i.e, no remote preview available),
528
+ // we can stay in the same status until there is a remote preview available
529
+ // If it's any other error we set status 'error'
530
+ if (isLocalPreviewError(wrappedError)) {
531
+ // This error should already been logged inside the getCardPreview. No need to log it here.
532
+ setIsBannedLocalPreview(true);
533
+ } else {
534
+ if (!['complete', 'error', 'failed-processing'].includes(status)) {
535
+ setStatus('error');
536
+ setError(wrappedError);
537
+ }
538
+ }
539
+ }
540
+ });
541
+
542
+ //----------------------------------------------------------------//
543
+ //------------ resolveUpfrontPreview useEffect -------------------//
544
+ //----------------------------------------------------------------//
545
+ const prevCardPreview = usePrevious(cardPreview);
546
+ const dimensionsRef = useCurrentValueRef(dimensions);
547
+ useEffect(() => {
548
+ const resolveUpfrontPreview = async identifier => {
549
+ // We block any possible future call to this method regardless of the outcome (success or fail)
550
+ // If it fails, the normal preview fetch should occur after the file state is fetched anyways
551
+ wasResolvedUpfrontPreviewRef.current = true;
552
+ try {
553
+ const requestedDimensions = {
554
+ ...dimensions
555
+ };
556
+ const newCardPreview = await fetchRemotePreviewRef.current(identifier);
557
+ const areValidRequestedDimensions = !isBigger(requestedDimensions, dimensionsRef.current);
558
+
559
+ // If there are new and bigger dimensions in the props, and the upfront preview is still resolving,
560
+ // the fetched preview is no longer valid, and thus, we dismiss it
561
+ if (areValidRequestedDimensions) {
562
+ setCardPreview(newCardPreview);
563
+ }
564
+ } catch (e) {
565
+ // NO need to log error. If this call fails, a refetch will happen after
566
+ }
567
+ };
568
+ const hadSSRCardPreview = ssr === 'client' && !!prevCardPreview && isSSRClientPreview(prevCardPreview);
569
+ if ((isCardVisible || hadSSRCardPreview) && !cardPreview && !wasResolvedUpfrontPreviewRef.current) {
570
+ resolveUpfrontPreview(identifier);
571
+ }
572
+ }, [cardPreview, dimensions, dimensionsRef, fetchRemotePreviewRef, identifier, isCardVisible, prevCardPreview, ssr]);
573
+
574
+ //----------------------------------------------------------------//
575
+ //------------------------ handle fireCopiedEvent --------------//
576
+ //----------------------------------------------------------------//
577
+
578
+ useEffect(() => {
579
+ var _getDocument;
580
+ const fireCopiedCardEvent = () => {
581
+ cardElement && createAnalyticsEvent && fireCopiedEvent(createAnalyticsEvent, metadata.id, cardElement);
582
+ };
583
+
584
+ // we add a listener for each of the cards on the page
585
+ // and then check if the triggered listener is from the card
586
+ // that contains a div in current window.getSelection()
587
+ // won't work in IE11
588
+ (_getDocument = getDocument()) === null || _getDocument === void 0 ? void 0 : _getDocument.addEventListener('copy', fireCopiedCardEvent);
589
+ return () => {
590
+ var _getDocument2;
591
+ (_getDocument2 = getDocument()) === null || _getDocument2 === void 0 ? void 0 : _getDocument2.removeEventListener('copy', fireCopiedCardEvent);
592
+ };
593
+ }, [cardElement, createAnalyticsEvent, metadata.id]);
594
+
595
+ //----------------------------------------------------------------//
596
+ //----------------- update status flags --------------------------//
597
+ //----------------------------------------------------------------//
598
+
599
+ useEffect(() => {
600
+ if ((fileStateValue === null || fileStateValue === void 0 ? void 0 : fileStateValue.status) === 'processing') {
601
+ fileStateFlagsRef.current.wasStatusProcessing = true;
602
+ } else if ((fileStateValue === null || fileStateValue === void 0 ? void 0 : fileStateValue.status) === 'uploading') {
603
+ fileStateFlagsRef.current.wasStatusUploading = true;
604
+ }
605
+ }, [fileStateValue === null || fileStateValue === void 0 ? void 0 : fileStateValue.status]);
606
+
607
+ //----------------------------------------------------------------//
608
+ //---------------- fetch and resolve card preview ----------------//
609
+ //----------------------------------------------------------------//
610
+
611
+ const prevDimensions = usePrevious(dimensions);
612
+ const prevIsCardVisible = usePrevious(isCardVisible);
613
+ const prevStatus = usePrevious(status);
614
+ useEffect(() => {
615
+ if (prevStatus !== undefined && status !== prevStatus) {
616
+ fireOperationalEventRef.current();
617
+ }
618
+ }, [fireOperationalEventRef, prevStatus, status]);
619
+ useEffect(() => {
620
+ var _ssrDataRef$current3;
621
+ /**
622
+ * Variable turnedVisible should only be true when media card
623
+ * was invisible in the previous state and is visible in the current one
624
+ *
625
+ * prevIsCardVisible | isCardVisible | turnedVisible
626
+ * ----------------------------------------------------
627
+ * false | false | false
628
+ * false | true | true
629
+ * true | true | false
630
+ * true | false | false (unreachable case)
631
+ * ----------------------------------------------------
632
+ */
633
+
634
+ const turnedVisible = !prevIsCardVisible && isCardVisible;
635
+ if (turnedVisible) {
636
+ fireCommencedEventRef.current();
637
+ }
638
+ if (cardPreview && turnedVisible && isSSRDataPreview(cardPreview) && isBigger((_ssrDataRef$current3 = ssrDataRef.current) === null || _ssrDataRef$current3 === void 0 ? void 0 : _ssrDataRef$current3.dimensions, dimensions)) {
639
+ // If dimensions from Server have changed and are bigger,
640
+ // we need to refetch
641
+ // refetchSRRPreview
642
+ fetchRemotePreviewRef.current(identifier).then(setCardPreview).catch(e => {
643
+ const wrappedError = ensureMediaCardError('remote-preview-fetch-ssr', e, true);
644
+ fireNonCriticalErrorEventRef.current(wrappedError);
645
+ });
646
+ }
647
+ if (fileStateValue && shouldResolvePreview({
648
+ status,
649
+ fileState: fileStateValue,
650
+ prevDimensions,
651
+ dimensions,
652
+ hasCardPreview: !!cardPreview,
653
+ isBannedLocalPreview,
654
+ wasResolvedUpfrontPreview: wasResolvedUpfrontPreviewRef.current
655
+ })) {
656
+ resolvePreviewRef.current(identifier, fileStateValue);
657
+ }
658
+ if (turnedVisible && ssr && !!cardPreview && isSSRClientPreview(cardPreview)) {
659
+ // Since the SSR preview brings the token in the query params,
660
+ // We need to fetch the remote preview to be able to cache it,
661
+ fetchRemotePreviewRef.current(identifier).catch(() => {
662
+ // No need to log this error.
663
+ // If preview fails, it will be refetched later
664
+ //TODO: test this catch
665
+ // https://product-fabric.atlassian.net/browse/MEX-1071
666
+ });
667
+ }
668
+ }, [cardPreview, dimensions, fetchRemotePreviewRef, fileStateValue, fireCommencedEventRef, fireNonCriticalErrorEventRef, identifier, isBannedLocalPreview, isCardVisible, prevDimensions, prevIsCardVisible, resolvePreviewRef, ssr, status]);
669
+
670
+ //----------------------------------------------------------------//
671
+ //----------------- set complete status --------------------------//
672
+ //----------------------------------------------------------------//
673
+
674
+ useEffect(() => {
675
+ if (previewDidRender &&
676
+ // We should't complete if status is uploading
677
+ ['loading-preview', 'processing'].includes(status)) {
678
+ setStatus('complete');
679
+ // TODO MEX-788: add test for "do not remove the card preview when unsubscribing".
680
+ setIsBannedLocalPreview(false);
681
+ }
682
+ }, [previewDidRender, status]);
683
+
684
+ //----------------------------------------------------------------//
685
+ //----------------- set isPlayingFile state ----------------------//
686
+ //----------------------------------------------------------------//
687
+
688
+ useEffect(() => {
689
+ const isVideo = fileAttributes.fileMediatype === 'video';
690
+ const {
691
+ mimeType
692
+ } = getFileDetails(identifier, fileStateValue);
693
+ const isVideoPlayable = videoIsPlayable(isBannedLocalPreview, fileStateValue, mimeType);
694
+ if (isVideo && !isPlayingFile && disableOverlay && useInlinePlayer && isVideoPlayable) {
695
+ setIsPlayingFile(true);
696
+ }
697
+ }, [disableOverlay, fileAttributes.fileMediatype, fileStateValue, identifier, isBannedLocalPreview, isPlayingFile, useInlinePlayer]);
698
+
699
+ //----------------------------------------------------------------//
700
+ //----------------- fireScreenEvent ------------------------------//
701
+ //----------------------------------------------------------------//
702
+
703
+ useEffect(() => {
704
+ if (prevStatus !== undefined && status !== prevStatus) {
705
+ if (status === 'complete' || fileAttributes.fileMediatype === 'video' && !!cardPreview && status === 'processing') {
706
+ fireScreenEventRef.current();
707
+ }
708
+ }
709
+ }, [status, prevStatus, fileAttributes, cardPreview, fireScreenEventRef]);
710
+
711
+ //----------------------------------------------------------------//
712
+ //----------------- abort UFO experience -------------------------//
713
+ //----------------------------------------------------------------//
714
+
715
+ useEffect(() => {
716
+ return () => {
717
+ // eslint-disable-next-line react-hooks/exhaustive-deps
718
+ fireAbortedEventRef.current();
719
+ };
720
+ }, [fireAbortedEventRef]);
721
+
722
+ //----------------------------------------------------------------//
723
+ //------------------ Subscribe to file state ---------------------//
724
+ //----------------------------------------------------------------//
725
+
726
+ const updateFileStateRef = useCurrentValueRef(() => {
727
+ if (fileState) {
728
+ // do not update the card status if the status is final
729
+ if (['complete', 'error', 'failed-processing'].includes(status)) {
730
+ return;
731
+ }
732
+ if (fileState.status !== 'error') {
733
+ const mediaType = 'mediaType' in fileState ? fileState.mediaType : undefined;
734
+ const isPreviewable = !!mediaType && ['audio', 'video', 'image', 'doc'].indexOf(mediaType) > -1;
735
+ const isPreviewableFileState = !!fileState.preview;
736
+ const isSupportedLocalPreview = mediaType === 'image' || mediaType === 'video';
737
+ const hasLocalPreview = !isBannedLocalPreview && isPreviewableFileState && isSupportedLocalPreview && !!fileState.mimeType && isMimeTypeSupportedByBrowser(fileState.mimeType);
738
+ const hasRemotePreview = isImageRepresentationReady(fileState);
739
+ const hasPreview = hasLocalPreview || hasRemotePreview;
740
+ let newStatus;
741
+ switch (fileState.status) {
742
+ case 'uploading':
743
+ case 'failed-processing':
744
+ case 'processing':
745
+ newStatus = fileState.status;
746
+ break;
747
+ case 'processed':
748
+ if (!isPreviewable || !hasPreview) {
749
+ newStatus = 'complete';
750
+ break;
751
+ }
752
+ newStatus = 'loading-preview';
753
+ break;
754
+ default:
755
+ newStatus = 'loading';
756
+ }
757
+ const newProgress = newStatus === 'uploading' && fileState.status === 'uploading' ? fileState.progress : 1;
758
+ setStatus(newStatus);
759
+ uploadProgressRef.current = newProgress;
760
+ } else {
761
+ const e = new MediaFileStateError(fileState.id, fileState.reason, fileState.message, fileState.details);
762
+ const errorReason = status === 'uploading' ? 'upload' : 'metadata-fetch';
763
+ setError(new MediaCardError(errorReason, e));
764
+ setStatus('error');
765
+ }
766
+ }
767
+ });
768
+ useEffect(() => {
769
+ updateFileStateRef.current();
770
+ }, [fileState, updateFileStateRef]);
771
+
772
+ //----------------------------------------------------------------//
773
+ //---------------------- Render Card Function --------------------//
774
+ //----------------------------------------------------------------//
775
+
776
+ const renderCard = (withCallbacks = true, cardStatusOverride, izLazyOverride) => {
777
+ const {
778
+ mediaItemType
779
+ } = identifier;
780
+ const isLazyWithOverride = izLazyOverride === undefined ? isLazy : izLazyOverride;
781
+
782
+ // Card can be artificially turned visible before entering the viewport
783
+ // For example, when we have the image in cache
784
+ const nativeLazyLoad = isLazyWithOverride && !isCardVisible;
785
+ // Force Media Image to always display img for SSR
786
+ const forceSyncDisplay = !!ssr;
787
+ const mediaCardCursor = getMediaCardCursor(!!useInlinePlayer, !!shouldOpenMediaViewer, status === 'error' || status === 'failed-processing', !!cardPreview, metadata.mediaType);
788
+ const card = /*#__PURE__*/React.createElement(CardViewV2, {
789
+ status: cardStatusOverride || status,
790
+ error: error,
791
+ mediaItemType: mediaItemType,
792
+ metadata: metadata,
793
+ cardPreview: cardPreview,
794
+ alt: alt,
795
+ appearance: appearance,
796
+ resizeMode: resizeMode,
797
+ dimensions: dimensions,
798
+ actions: computedActions,
799
+ selectable: selectable,
800
+ selected: selected,
801
+ onClick: withCallbacks ? onCardViewClick : undefined,
802
+ onMouseEnter: withCallbacks ? event => {
803
+ onMouseEnter === null || onMouseEnter === void 0 ? void 0 : onMouseEnter({
804
+ event,
805
+ mediaItemDetails: metadata
806
+ });
807
+ } : undefined,
808
+ disableOverlay: disableOverlay,
809
+ progress: uploadProgressRef.current,
810
+ onDisplayImage: withCallbacks ? () => {
811
+ const payloadPart = {
812
+ fileId: identifier.id,
813
+ isUserCollection: identifier.collectionName === RECENTS_COLLECTION
814
+ };
815
+ globalMediaEventEmitter.emit('media-viewed', {
816
+ viewingLevel: 'minimal',
817
+ ...payloadPart
818
+ });
819
+ } : undefined,
820
+ innerRef: setCardElement,
821
+ testId: testId,
822
+ featureFlags: featureFlags,
823
+ titleBoxBgColor: titleBoxBgColor,
824
+ titleBoxIcon: titleBoxIcon,
825
+ onImageError: withCallbacks ? onImageError : undefined,
826
+ onImageLoad: withCallbacks ? onImageLoad : undefined,
827
+ nativeLazyLoad: nativeLazyLoad,
828
+ forceSyncDisplay: forceSyncDisplay,
829
+ mediaCardCursor: mediaCardCursor,
830
+ shouldHideTooltip: shouldHideTooltip
831
+ });
832
+ return isLazyWithOverride ? /*#__PURE__*/React.createElement(ViewportDetector, {
833
+ cardEl: cardElement,
834
+ onVisible: () => {
835
+ setIsCardVisible(true);
836
+ }
837
+ }, card) : card;
838
+ };
839
+
840
+ //----------------------------------------------------------------//
841
+ //-------------------------- RENDER ------------------------------//
842
+ //----------------------------------------------------------------//
843
+
844
+ const inlinePlayerFallback = renderCard(false, 'loading', false);
845
+ const collectionName = identifier.collectionName || '';
846
+ return /*#__PURE__*/React.createElement(React.Fragment, null, isPlayingFile ? /*#__PURE__*/React.createElement(Suspense, {
847
+ fallback: inlinePlayerFallback
848
+ }, /*#__PURE__*/React.createElement(InlinePlayerLazyV2, {
849
+ dimensions: dimensions,
850
+ originalDimensions: originalDimensions,
851
+ identifier: identifier,
852
+ autoplay: !!shouldAutoplay,
853
+ onFullscreenChange: onFullscreenChange,
854
+ onError: () => {
855
+ setIsPlayingFile(false);
856
+ },
857
+ onClick: onCardClick,
858
+ selected: selected,
859
+ ref: setCardElement,
860
+ testId: testId,
861
+ cardPreview: cardPreview
862
+ })) : renderCard(), mediaViewerSelectedItem ? /*#__PURE__*/ReactDOM.createPortal( /*#__PURE__*/React.createElement(MediaViewer, {
863
+ collectionName: collectionName,
864
+ items: mediaViewerItems || [],
865
+ mediaClientConfig: mediaClient.config,
866
+ selectedItem: mediaViewerSelectedItem,
867
+ onClose: () => {
868
+ setMediaViewerSelectedItem(null);
869
+ },
870
+ contextId: contextId,
871
+ featureFlags: featureFlags
872
+ }), document.body) : null, ssr === 'server' && /*#__PURE__*/React.createElement(StoreSSRDataScript, {
873
+ identifier: identifier,
874
+ dataURI: cardPreview === null || cardPreview === void 0 ? void 0 : cardPreview.dataURI,
875
+ dimensions: getRequestedDimensions({
876
+ dimensions,
877
+ element: cardElement
878
+ }),
879
+ error: ((_ssrReliabilityRef$cu = ssrReliabilityRef.current.server) === null || _ssrReliabilityRef$cu === void 0 ? void 0 : _ssrReliabilityRef$cu.status) === 'fail' ? ssrReliabilityRef.current.server : undefined
880
+ }));
881
+ };