@atlaskit/renderer
Version:
Renderer component
365 lines (362 loc) • 12.7 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import React, { Component, useContext } from 'react';
import { filter } from '@atlaskit/adf-utils/traverse';
import { Card as CardAsync, CardSync, CardLoading, CardError } from '@atlaskit/media-card';
import { MediaClientContext } from '@atlaskit/media-client-react';
import { withImageLoader } from '@atlaskit/editor-common/utils';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import AnalyticsContext from '../analytics/analyticsContext';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
export const mediaIdentifierMap = new Map();
export const getListOfIdentifiersFromDoc = doc => {
if (!doc) {
return [];
}
return filter(doc, node => node.type === 'media').reduce((identifierList, mediaNode) => {
if (mediaNode.attrs) {
const {
type,
url: dataURI,
id
} = mediaNode.attrs;
if (type === 'file' && id) {
identifierList.push({
mediaItemType: 'file',
id
});
} else if (type === 'external' && dataURI) {
identifierList.push({
mediaItemType: 'external-image',
dataURI,
name: dataURI
});
}
}
return identifierList;
}, []);
};
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/react/no-class-components
export class MediaCardView extends Component {
constructor(...args) {
super(...args);
_defineProperty(this, "state", {});
_defineProperty(this, "saveFileState", async id => {
const {
collection: collectionName,
mediaClient
} = this.props;
const options = {
collectionName
};
try {
if (mediaClient) {
const fileState = await mediaClient.file.getCurrentState(id, options);
this.setState({
fileState
});
}
} catch {
// do not set state on error
}
});
_defineProperty(this, "onError", reason => {
var _this$props$fireAnaly, _this$props;
const {
nestedUnder,
rendererContext
} = this.props;
(_this$props$fireAnaly = (_this$props = this.props).fireAnalyticsEvent) === null || _this$props$fireAnaly === void 0 ? void 0 : _this$props$fireAnaly.call(_this$props, {
action: ACTION.ERRORED,
actionSubject: ACTION_SUBJECT.RENDERER,
actionSubjectId: ACTION_SUBJECT_ID.MEDIA,
eventType: EVENT_TYPE.UI,
attributes: {
reason,
external: false,
...(nestedUnder && editorExperiment('platform_synced_block', true) ? {
nestedUnder
} : {}),
...(rendererContext !== null && rendererContext !== void 0 && rendererContext.nestedRendererType && editorExperiment('platform_synced_block', true) ? {
nestedRendererType: rendererContext.nestedRendererType
} : {})
}
});
});
_defineProperty(this, "renderLoadingCard", () => {
const {
cardDimensions
} = this.props;
return /*#__PURE__*/React.createElement(CardLoading, {
dimensions: cardDimensions,
interactionName: "renderer-media-card-loading"
});
});
/**
* We want to call provided `eventHandlers.media.onClick` when it's provided,
* but we also don't want to call it when it's a video and inline video player is enabled.
* This is due to consumers normally process this onClick call by opening media viewer and
* we don't want that to happened described above text.
*/
_defineProperty(this, "getOnCardClickCallback", isInlinePlayer => {
const {
eventHandlers
} = this.props;
if (eventHandlers && eventHandlers.media && eventHandlers.media.onClick) {
return (result, analyticsEvent) => {
const isVideo = result.mediaItemDetails && result.mediaItemDetails.mediaType === 'video';
const isVideoWithInlinePlayer = isInlinePlayer && isVideo;
if (!isVideoWithInlinePlayer && eventHandlers && eventHandlers.media && eventHandlers.media.onClick) {
eventHandlers.media.onClick(result, analyticsEvent);
}
};
}
return undefined;
});
}
async componentDidMount() {
const {
rendererContext,
contextIdentifierProvider,
id,
url,
collection: collectionName
} = this.props;
if (contextIdentifierProvider) {
this.setState({
contextIdentifierProvider: await contextIdentifierProvider
});
}
const nodeIsInCache = id && mediaIdentifierMap.has(id) || url && mediaIdentifierMap.has(url);
if (rendererContext && rendererContext.adDoc && !nodeIsInCache) {
getListOfIdentifiersFromDoc(rendererContext.adDoc).forEach(identifier => {
if (identifier.mediaItemType === 'file' && identifier.id === id) {
mediaIdentifierMap.set(identifier.id, {
...identifier,
collectionName
});
} else if (identifier.mediaItemType === 'external-image') {
mediaIdentifierMap.set(identifier.dataURI, identifier);
}
});
}
if (id) {
this.saveFileState(id);
}
}
componentDidUpdate(prevProps) {
const {
id: oldId
} = prevProps;
if (this.props.id && oldId !== this.props.id) {
this.saveFileState(this.props.id);
}
}
componentWillUnmount() {
const {
id,
url: dataURI
} = this.props;
if (id) {
mediaIdentifierMap.delete(id);
} else if (dataURI) {
mediaIdentifierMap.delete(dataURI);
}
}
renderExternal(shouldOpenMediaViewer) {
const {
cardDimensions,
resizeMode,
appearance,
url,
imageStatus,
disableOverlay,
alt,
featureFlags,
ssr,
mediaClient,
dataAttributes,
enableSyncMediaCard,
localId
} = this.props;
if (imageStatus === 'loading' || !url) {
return this.renderLoadingCard();
}
const identifier = {
dataURI: url,
name: url,
mediaItemType: 'external-image'
};
// we need this statement for the mandatory mediaClientConfig below
const mediaClientConfig = mediaClient === null || mediaClient === void 0 ? void 0 : mediaClient.mediaClientConfig;
const Card = enableSyncMediaCard ? CardSync : CardAsync;
return (
/*#__PURE__*/
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
React.createElement("div", _extends({}, dataAttributes, {
"data-node-type": "media",
"data-local-id": localId
}), /*#__PURE__*/React.createElement(Card
// TODO: MPT-315 - clean up after we move mediaClientConfig into FileIdentifier
// context is not really used when the type is external and we want to render the component asap
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
, {
mediaClientConfig: mediaClientConfig,
alt: alt,
identifier: identifier,
dimensions: cardDimensions,
appearance: appearance,
resizeMode: resizeMode,
disableOverlay: disableOverlay,
shouldOpenMediaViewer: shouldOpenMediaViewer,
mediaViewerItems: Array.from(mediaIdentifierMap.values()),
featureFlags: featureFlags,
ssr: ssr === null || ssr === void 0 ? void 0 : ssr.mode,
shouldHideTooltip: false,
onError: expValEquals('platform_editor_media_error_analytics', 'isEnabled', true) ? this.onError : undefined
}))
);
}
render() {
const {
contextIdentifierProvider,
fileState
} = this.state;
const {
id,
alt,
type,
collection,
occurrenceKey,
cardDimensions,
resizeMode,
disableOverlay,
useInlinePlayer,
originalDimensions,
shouldOpenMediaViewer: forceOpenMediaViewer,
featureFlags,
shouldEnableDownloadButton,
ssr,
mediaClient,
dataAttributes,
enableSyncMediaCard,
localId,
mediaViewerExtensions
} = this.props;
const isMobile = false;
const shouldPlayInline = useInlinePlayer !== undefined ? useInlinePlayer : true;
const isInlinePlayer = isMobile ? false : shouldPlayInline;
const onCardClick = this.getOnCardClickCallback(isInlinePlayer);
const shouldOpenMediaViewer = typeof forceOpenMediaViewer === 'boolean' ? forceOpenMediaViewer : !isMobile && !onCardClick;
if (type === 'external') {
return this.renderExternal(shouldOpenMediaViewer);
}
if (type === 'link') {
return null;
}
const mediaClientConfig = !!ssr ? ssr.config : mediaClient === null || mediaClient === void 0 ? void 0 : mediaClient.mediaClientConfig;
if (!mediaClientConfig || !id) {
return this.renderLoadingCard();
}
if (!id || type !== 'file') {
return /*#__PURE__*/React.createElement(CardError, {
dimensions: cardDimensions
});
}
const contextId = contextIdentifierProvider && contextIdentifierProvider.objectId;
const identifier = {
id,
mediaItemType: 'file',
collectionName: collection,
occurrenceKey
};
const Card = enableSyncMediaCard ? CardSync : CardAsync;
// Quick solution to disable lazy loading of images on PDF export pages in Confluence to remedy an issue with images never loading
// More robust solution will be implemented as part of CCPDF-233 - Link: https://hello.jira.atlassian.cloud/browse/CCPDF-233
const currentUrl = window.location.href;
const shouldDisableLazyLoading = expValEquals('platform_editor_disable_lazy_load_media', 'isEnabled', true) && currentUrl.includes('/wiki/pdf/spaces/');
return /*#__PURE__*/React.createElement("div", _extends({}, getClipboardAttrs({
id,
alt,
collection,
contextIdentifierProvider,
originalDimensions,
fileState
}), dataAttributes, {
"data-local-id": localId
}), /*#__PURE__*/React.createElement(Card, {
identifier: identifier,
alt: alt,
contextId: contextId,
mediaClientConfig: mediaClientConfig,
dimensions: cardDimensions,
originalDimensions: originalDimensions,
onClick: onCardClick,
resizeMode: resizeMode,
isLazy: !isMobile && !shouldDisableLazyLoading,
disableOverlay: disableOverlay,
useInlinePlayer: isInlinePlayer,
shouldOpenMediaViewer: shouldOpenMediaViewer,
mediaViewerItems: Array.from(mediaIdentifierMap.values()),
featureFlags: featureFlags,
shouldEnableDownloadButton: shouldEnableDownloadButton,
ssr: ssr === null || ssr === void 0 ? void 0 : ssr.mode,
shouldHideTooltip: isMobile,
mediaViewerExtensions: mediaViewerExtensions,
onError: expValEquals('platform_editor_media_error_analytics', 'isEnabled', true) ? this.onError : undefined
}));
}
}
// Needed for copy & paste
export const getClipboardAttrs = ({
id,
alt,
collection,
contextIdentifierProvider,
originalDimensions,
fileState
}) => {
const contextId = contextIdentifierProvider && contextIdentifierProvider.objectId;
const width = originalDimensions && originalDimensions.width;
const height = originalDimensions && originalDimensions.height;
let fileName = 'file'; // default name is needed for Confluence
let fileSize = 1;
let fileMimeType = '';
if (fileState && fileState.status !== 'error') {
fileSize = fileState.size;
fileName = fileState.name;
fileMimeType = fileState.mimeType;
}
return {
'data-context-id': contextId,
'data-type': 'file',
'data-node-type': 'media',
'data-width': width,
'data-height': height,
'data-id': id,
'data-collection': collection,
'data-file-name': fileName,
'data-file-size': fileSize,
'data-file-mime-type': fileMimeType,
'data-alt': alt
};
};
export const MediaCardInternal = props => {
const mediaClient = useContext(MediaClientContext);
return /*#__PURE__*/React.createElement(AnalyticsContext.Consumer, null, ({
fireAnalyticsEvent
}) => {
return /*#__PURE__*/React.createElement(MediaCardView
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
, _extends({}, props, {
mediaClient: mediaClient,
fireAnalyticsEvent: fireAnalyticsEvent
}));
});
};
export const MediaCard = withImageLoader(MediaCardInternal);