@atlaskit/editor-plugin-media
Version:
Media plugin for @atlaskit/editor-core
527 lines (519 loc) • 27.2 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import React, { Component } from 'react';
import { bind } from 'bind-event-listener';
import memoizeOne from 'memoize-one';
import { MEDIA_CONTEXT } from '@atlaskit/analytics-namespaced-context/MediaAnalyticsContext';
import AnalyticsContext from '@atlaskit/analytics-next/AnalyticsContext';
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
import { setNodeSelection, setTextSelection, withImageLoader } from '@atlaskit/editor-common/utils';
import { findParentNodeClosestToPos } from '@atlaskit/editor-prosemirror/utils';
import { CellSelection } from '@atlaskit/editor-tables/cell-selection';
import Card from '@atlaskit/media-card/cardLoader';
import { CardLoading } from '@atlaskit/media-card/cardLoading';
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
import { fg } from '@atlaskit/platform-feature-flags/fg';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/editor-experiment';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure';
import { stateKey as mediaStateKey } from '../../pm-plugins/plugin-key';
import { GeneratedMediaReveal } from '../../ui/GeneratedMediaReveal';
import { createMediaNodeUpdater } from '../mediaNodeUpdater';
import { MediaCardWrapper } from '../styles';
// This is being used by DropPlaceholder now
export const MEDIA_HEIGHT = 125;
export const FILE_WIDTH = 156;
// eslint-disable-next-line @repo/internal/react/no-class-components
export class MediaNode extends Component {
constructor(_props) {
super(_props);
_defineProperty(this, "mediaInstance", {});
_defineProperty(this, "state", {});
_defineProperty(this, "videoControlsWrapperRef", /*#__PURE__*/React.createRef());
_defineProperty(this, "unbindKeyDown", null);
_defineProperty(this, "getDataConsumerMark", () => fg('cc-maui-add-mark-for-remix-generated-images') ? this.props.node.marks.find(m => m.type.name === 'dataConsumer') : undefined);
_defineProperty(this, "getDataConsumerSource", (props = this.props) => {
var _props$node$marks$fin, _props$node$marks$fin2;
return (_props$node$marks$fin = props.node.marks.find(mark => mark.type.name === 'dataConsumer')) === null || _props$node$marks$fin === void 0 ? void 0 : (_props$node$marks$fin2 = _props$node$marks$fin.attrs.sources) === null || _props$node$marks$fin2 === void 0 ? void 0 : _props$node$marks$fin2[0];
});
_defineProperty(this, "emitMediaRenderEvent", (event, props = this.props) => {
var _props$mediaOptions, _props$mediaOptions$o;
(_props$mediaOptions = props.mediaOptions) === null || _props$mediaOptions === void 0 ? void 0 : (_props$mediaOptions$o = _props$mediaOptions.onMediaRenderEvent) === null || _props$mediaOptions$o === void 0 ? void 0 : _props$mediaOptions$o.call(_props$mediaOptions, {
...event,
dataConsumerSource: this.getDataConsumerSource(props),
mediaId: props.node.attrs.id,
mediaInstance: this.mediaInstance
});
});
_defineProperty(this, "setViewMediaClientConfig", async () => {
// mediaProvider is Promise<MediaProvider>, so await it first to get the actual provider
const mediaProvider = await this.props.mediaProvider;
if (mediaProvider) {
const viewMediaClientConfig = mediaProvider.viewMediaClientConfig;
const viewAndUploadMediaClientConfig = mediaProvider.viewAndUploadMediaClientConfig;
// Only update state if new configs are available and different from current state
if (viewMediaClientConfig && this.state.viewMediaClientConfig !== viewMediaClientConfig || viewAndUploadMediaClientConfig && this.state.viewAndUploadMediaClientConfig !== viewAndUploadMediaClientConfig) {
this.setState({
viewMediaClientConfig,
viewAndUploadMediaClientConfig
});
}
}
});
_defineProperty(this, "selectMediaSingleFromCard", ({
event
}) => {
var _this$props$pluginInj;
this.selectMediaSingle(event);
// In edit mode (node content wrapper has contenteditable set to true), link redirection is disabled by default
// We need to call "stopPropagation" here in order to prevent in editor view mode, the browser from navigating to
// another URL if the media node is wrapped in a link mark.
if (this.props.isViewOnly && areToolbarFlagsEnabled(Boolean((_this$props$pluginInj = this.props.pluginInjectionApi) === null || _this$props$pluginInj === void 0 ? void 0 : _this$props$pluginInj.toolbar))) {
event.preventDefault();
}
});
_defineProperty(this, "selectMediaSingle", event => {
const propPos = this.props.getPos();
if (typeof propPos !== 'number') {
return;
}
// NOTE: This does not prevent the link navigation in the editor view mode, .preventDefault is needed (see selectMediaSingleFromCard)
// Hence it should be removed
// We need to call "stopPropagation" here in order to prevent the browser from navigating to
// another URL if the media node is wrapped in a link mark.
if (editorExperiment('platform_editor_controls', 'control')) {
event.stopPropagation();
}
const {
state
} = this.props.view;
if (event.shiftKey) {
// don't select text if there is current selection in a table (as this would override selected cells)
if (state.selection instanceof CellSelection) {
return;
}
setTextSelection(this.props.view, state.selection.from < propPos ? state.selection.from : propPos - 1,
// + 3 needed for offset of the media inside mediaSingle and cursor to make whole mediaSingle selected
state.selection.to > propPos ? state.selection.to : propPos + 2);
} else {
setNodeSelection(this.props.view, propPos - 1);
}
});
_defineProperty(this, "getMediaSettings", memoizeOne((viewAndUploadMediaClientConfig, isViewOnly) => {
return {
canUpdateVideoCaptions: fg('platform_media_video_captions') ? !!viewAndUploadMediaClientConfig && !isViewOnly : false
};
}));
/**
* Media written straight into the document arrives without intrinsic dimensions, and the
* fetch that would supply them resolves `false` while the file is still processing. Only
* editor-driven uploads get a second attempt, from the plugin's media-state listener, so
* otherwise the card would keep its placeholder ratio forever. The preview rendering is the
* one moment the file is guaranteed ready, so the fetch is retried there.
*/
_defineProperty(this, "fetchMissingDimensions", async () => {
var _pluginInjectionApi$w, _pluginInjectionApi$w2;
const {
node,
view,
mediaProvider,
contextIdentifierProvider,
mediaOptions,
pluginInjectionApi
} = this.props;
if (node.attrs.type === 'external' || !node.attrs.id || node.attrs.width && node.attrs.height) {
return;
}
const updater = createMediaNodeUpdater({
view,
mediaProvider,
contextIdentifierProvider,
node,
// Carries `allowRemoteDimensionsFetch`, so surfaces that cannot reach the media
// APIs get the default dimensions back instead of a request.
mediaOptions,
isMediaSingle: true,
lineLength: pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$w = pluginInjectionApi.width) === null || _pluginInjectionApi$w === void 0 ? void 0 : (_pluginInjectionApi$w2 = _pluginInjectionApi$w.sharedState.currentState()) === null || _pluginInjectionApi$w2 === void 0 ? void 0 : _pluginInjectionApi$w2.lineLength
});
try {
const dimensions = await updater.getRemoteDimensions();
if (dimensions) {
updater.updateDimensions(dimensions);
}
} catch {
// Leaves the card at its placeholder ratio, which the reveal's own timeout covers.
}
});
_defineProperty(this, "onPreviewRender", fileId => {
this.emitMediaRenderEvent({
renderedMediaId: fileId,
type: 'preview-rendered'
});
// Only tracked where the reveal uses it. Every media card in every editor calls this,
// and the extra render it would otherwise trigger is not free.
if (this.hasGeneratedMediaMotion && this.state.previewRenderedFileId !== fileId) {
this.setState({
previewRenderedFileId: fileId
});
this.fetchMissingDimensions();
}
if (isExperimentEnabled('aifc_page_create_with_rovo_include_infographics')) {
var _this$props$pluginInj2, _this$props$pluginInj3;
(_this$props$pluginInj2 = this.props.pluginInjectionApi) === null || _this$props$pluginInj2 === void 0 ? void 0 : (_this$props$pluginInj3 = _this$props$pluginInj2.core) === null || _this$props$pluginInj3 === void 0 ? void 0 : _this$props$pluginInj3.actions.execute(({
tr
}) => tr.setMeta(mediaStateKey, {
type: 'PREVIEW_RENDERED',
fileId
}));
}
});
_defineProperty(this, "onError", reason => {
var _this$props$api;
// The card is already showing its error treatment, so let the reveal open onto
// that instead of waiting out its timeout.
this.setState({
failedFileId: this.props.node.attrs.id
});
this.emitMediaRenderEvent({
reason,
type: 'error'
});
// `getMediaRenderErrorHandler` also wires this for surfaces the analytics
// experiment does not cover, so re-read without exposure — the render-time read
// owns that — to keep the dispatch inside the experiment's own cohort.
if (!expValEqualsNoExposure('platform_editor_media_error_analytics', 'isEnabled', true)) {
return;
}
const nestedUnder = this.getNestedUnder();
(_this$props$api = this.props.api) === null || _this$props$api === void 0 ? void 0 : _this$props$api.media.actions.handleMediaNodeRenderError(this.props.node, reason, nestedUnder);
});
_defineProperty(this, "onRemixRenderError", reason => {
this.emitMediaRenderEvent({
reason,
type: 'error'
});
});
_defineProperty(this, "getMediaRenderErrorHandler", () => {
var _this$props$mediaOpti;
if (expValEquals('platform_editor_media_error_analytics', 'isEnabled', true)) {
return this.onError;
}
// The reveal needs terminal errors too, so it can open onto the card's error treatment
// rather than waiting out its timeout. `onError` keeps the analytics dispatch itself
// inside the experiment's cohort.
if (this.hasGeneratedMediaMotion) {
return this.onError;
}
return (_this$props$mediaOpti = this.props.mediaOptions) !== null && _this$props$mediaOpti !== void 0 && _this$props$mediaOpti.onMediaRenderEvent ? this.onRemixRenderError : undefined;
});
/**
* This function checks if the media node is nested under a certain nodes, and if so,
* returns the name of the parent node type. This is used for providing more context in media render errors.
* @returns
*/
_defineProperty(this, "getNestedUnder", () => {
const pos = this.props.getPos();
if (typeof pos !== 'number') {
return undefined;
}
const {
doc,
schema
} = this.props.view.state;
const {
bodiedSyncBlock
} = schema.nodes;
if (!bodiedSyncBlock) {
return undefined;
}
const resolvedPos = doc.resolve(pos);
const bodiedSyncBlockNode = findParentNodeClosestToPos(resolvedPos, currentNode => currentNode.type === bodiedSyncBlock);
return bodiedSyncBlockNode === null || bodiedSyncBlockNode === void 0 ? void 0 : bodiedSyncBlockNode.node.type.name;
});
_defineProperty(this, "onFullscreenChange", fullscreen => {
var _this$mediaPluginStat;
(_this$mediaPluginStat = this.mediaPluginState) === null || _this$mediaPluginStat === void 0 ? void 0 : _this$mediaPluginStat.updateAndDispatch({
isFullscreen: fullscreen
});
});
_defineProperty(this, "handleNewNode", props => {
var _this$mediaPluginStat2;
const {
node
} = props;
(_this$mediaPluginStat2 = this.mediaPluginState) === null || _this$mediaPluginStat2 === void 0 ? void 0 : _this$mediaPluginStat2.handleMediaNodeMount(node, () => this.props.getPos());
});
const {
view: _view,
syncProvider
} = this.props;
this.mediaPluginState = mediaStateKey.getState(_view.state);
// Initialize state from syncProvider (available on both server and client for SSR)
if (syncProvider) {
this.state = {
viewMediaClientConfig: syncProvider.viewMediaClientConfig,
viewAndUploadMediaClientConfig: syncProvider.viewAndUploadMediaClientConfig
};
}
}
shouldComponentUpdate(nextProps, nextState) {
var _this$props$mediaOpti2, _nextProps$mediaOptio;
const hasNewViewMediaClientConfig = !this.state.viewMediaClientConfig && nextState.viewMediaClientConfig;
const hasNewViewAndUploadMediaClientConfig = !this.state.viewAndUploadMediaClientConfig && nextState.viewAndUploadMediaClientConfig;
if (this.props.selected !== nextProps.selected || this.props.node.attrs.id !== nextProps.node.attrs.id || this.props.node.attrs.collection !== nextProps.node.attrs.collection ||
// Patched in asynchronously once media services report the real size.
this.props.node.attrs.width !== nextProps.node.attrs.width || this.props.node.attrs.height !== nextProps.node.attrs.height || this.props.isAIGenerating !== nextProps.isAIGenerating || this.props.isCwrAIGenerating !== nextProps.isCwrAIGenerating || this.props.maxDimensions.height !== nextProps.maxDimensions.height || this.props.maxDimensions.width !== nextProps.maxDimensions.width || this.props.contextIdentifierProvider !== nextProps.contextIdentifierProvider || this.props.isLoading !== nextProps.isLoading || this.props.isViewOnly !== nextProps.isViewOnly || this.props.mediaProvider !== nextProps.mediaProvider || this.props.syncProvider !== nextProps.syncProvider || this.getDataConsumerSource() !== this.getDataConsumerSource(nextProps) || ((_this$props$mediaOpti2 = this.props.mediaOptions) === null || _this$props$mediaOpti2 === void 0 ? void 0 : _this$props$mediaOpti2.onMediaRenderEvent) !== ((_nextProps$mediaOptio = nextProps.mediaOptions) === null || _nextProps$mediaOptio === void 0 ? void 0 : _nextProps$mediaOptio.onMediaRenderEvent) || this.state.previewRenderedFileId !== nextState.previewRenderedFileId || this.state.failedFileId !== nextState.failedFileId || hasNewViewMediaClientConfig || hasNewViewAndUploadMediaClientConfig) {
return true;
}
return false;
}
async componentDidMount() {
var _dataConsumerMark$att;
this.handleNewNode(this.props);
this.emitMediaRenderEvent({
type: 'mounted'
});
const {
node,
pluginInjectionApi
} = this.props;
const dataConsumerMark = this.getDataConsumerMark();
const infographicType = dataConsumerMark === null || dataConsumerMark === void 0 ? void 0 : (_dataConsumerMark$att = dataConsumerMark.attrs.sources) === null || _dataConsumerMark$att === void 0 ? void 0 : _dataConsumerMark$att[0];
if (infographicType) {
var _pluginInjectionApi$a;
pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$a = pluginInjectionApi.analytics) === null || _pluginInjectionApi$a === void 0 ? void 0 : _pluginInjectionApi$a.actions.fireAnalyticsEvent({
action: ACTION.RENDERED,
actionSubject: ACTION_SUBJECT.MEDIA,
actionSubjectId: node.attrs.id,
eventType: EVENT_TYPE.TRACK,
attributes: {
infographicType,
pageMode: 'edit',
mediaId: node.attrs.id
}
});
}
const {
contextIdentifierProvider
} = this.props;
this.setState({
contextIdentifierProvider: await contextIdentifierProvider
});
await this.setViewMediaClientConfig();
}
componentWillUnmount() {
var _this$mediaPluginStat3;
const {
node
} = this.props;
this.emitMediaRenderEvent({
type: 'unmounted'
});
(_this$mediaPluginStat3 = this.mediaPluginState) === null || _this$mediaPluginStat3 === void 0 ? void 0 : _this$mediaPluginStat3.handleMediaNodeUnmount(node);
if (this.unbindKeyDown && typeof this.unbindKeyDown === 'function') {
this.unbindKeyDown();
}
}
componentDidUpdate(prevProps) {
var _prevProps$mediaOptio, _this$props$mediaOpti3, _this$mediaPluginStat5;
if (prevProps.node.attrs.id !== this.props.node.attrs.id || this.getDataConsumerSource(prevProps) !== this.getDataConsumerSource() || ((_prevProps$mediaOptio = prevProps.mediaOptions) === null || _prevProps$mediaOptio === void 0 ? void 0 : _prevProps$mediaOptio.onMediaRenderEvent) !== ((_this$props$mediaOpti3 = this.props.mediaOptions) === null || _this$props$mediaOpti3 === void 0 ? void 0 : _this$props$mediaOpti3.onMediaRenderEvent)) {
this.emitMediaRenderEvent({
type: 'unmounted'
}, prevProps);
this.emitMediaRenderEvent({
type: 'mounted'
});
}
if (prevProps.node.attrs.id !== this.props.node.attrs.id) {
var _this$mediaPluginStat4;
(_this$mediaPluginStat4 = this.mediaPluginState) === null || _this$mediaPluginStat4 === void 0 ? void 0 : _this$mediaPluginStat4.handleMediaNodeUnmount(prevProps.node);
this.handleNewNode(this.props);
}
(_this$mediaPluginStat5 = this.mediaPluginState) === null || _this$mediaPluginStat5 === void 0 ? void 0 : _this$mediaPluginStat5.updateElement();
this.setViewMediaClientConfig();
// this.videoControlsWrapperRef is null on componentDidMount. We need to wait until it has value
if (this.videoControlsWrapperRef && this.videoControlsWrapperRef.current) {
var _this$mediaPluginStat6;
if (!((_this$mediaPluginStat6 = this.mediaPluginState) !== null && _this$mediaPluginStat6 !== void 0 && _this$mediaPluginStat6.videoControlsWrapperRef)) {
var _this$mediaPluginStat7;
this.bindKeydown();
(_this$mediaPluginStat7 = this.mediaPluginState) === null || _this$mediaPluginStat7 === void 0 ? void 0 : _this$mediaPluginStat7.updateAndDispatch({
videoControlsWrapperRef: this.videoControlsWrapperRef.current
});
}
}
}
bindKeydown() {
var _this$videoControlsWr3;
const onKeydown = event => {
if (event.key === 'Tab') {
var _this$videoControlsWr, _this$videoControlsWr2;
// Add focus trap for controls panel
let firstElement;
let lastElement;
const focusableElements = (_this$videoControlsWr = this.videoControlsWrapperRef) === null || _this$videoControlsWr === void 0 ? void 0 : (_this$videoControlsWr2 = _this$videoControlsWr.current) === null || _this$videoControlsWr2 === void 0 ? void 0 : _this$videoControlsWr2.querySelectorAll('button, input, [tabindex]:not([tabindex="-1"])');
if (focusableElements && focusableElements.length) {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
firstElement = focusableElements[0];
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
lastElement = focusableElements[focusableElements.length - 1];
if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
} else if (!event.shiftKey && document.activeElement === lastElement) {
var _firstElement;
event.preventDefault();
(_firstElement = firstElement) === null || _firstElement === void 0 ? void 0 : _firstElement.focus();
}
}
}
};
if ((_this$videoControlsWr3 = this.videoControlsWrapperRef) !== null && _this$videoControlsWr3 !== void 0 && _this$videoControlsWr3.current) {
this.unbindKeyDown = bind(this.videoControlsWrapperRef.current, {
type: 'keydown',
listener: onKeydown,
options: {
capture: true,
passive: false
}
});
}
}
/**
* Surface opt-in first, so surfaces that never ask for the motion are not exposed to the
* gate. The node type is checked after it, so every node on an opted-in surface counts
* towards the same exposure population.
*/
get hasGeneratedMediaMotion() {
var _this$props$mediaOpti4;
return !!((_this$props$mediaOpti4 = this.props.mediaOptions) !== null && _this$props$mediaOpti4 !== void 0 && _this$props$mediaOpti4.allowAIGeneratedMediaMotion) && fg('aifc_page_create_defer_generated_visuals') &&
// File-backed only: `ExternalImageCard` reports neither its rendered preview nor its
// dimensions, so an external node could only ever open on the reveal's timeout.
this.props.node.attrs.type !== 'external';
}
render() {
var _dataConsumerMark$att2;
const {
node,
selected,
originalDimensions,
isLoading,
maxDimensions,
mediaOptions
} = this.props;
const borderMark = node.marks.find(m => m.type.name === 'border');
const dataConsumerMark = this.getDataConsumerMark();
const {
viewMediaClientConfig,
viewAndUploadMediaClientConfig,
contextIdentifierProvider
} = this.state;
const {
id,
type,
collection,
url,
alt
} = node.attrs;
// Check if we have any media client config available (syncProvider, state, or upload config)
const hasNoMediaClientConfig = !viewMediaClientConfig && (fg('platform_media_video_captions') ? !viewAndUploadMediaClientConfig : true);
const hasGeneratedMediaMotion = this.hasGeneratedMediaMotion;
// Opening before the real dimensions land would animate to the wrong height and snap
// when they correct. Nodes that never get them fall through to the reveal's timeout.
//
// A terminal error is readiness too: the card is already showing its error treatment,
// so the space opens onto that rather than hiding it until the timeout.
const isRevealReady = this.state.failedFileId === id || this.state.previewRenderedFileId === id && !!node.attrs.width && !!node.attrs.height;
if (isLoading || type !== 'external' && hasNoMediaClientConfig) {
return /*#__PURE__*/React.createElement(GeneratedMediaReveal, {
isEnabled: hasGeneratedMediaMotion,
isReady: isRevealReady,
mediaKey: id
}, /*#__PURE__*/React.createElement(MediaCardWrapper, {
dimensions: originalDimensions,
borderWidth: borderMark === null || borderMark === void 0 ? void 0 : borderMark.attrs.size,
selected: selected
}, /*#__PURE__*/React.createElement(CardLoading, {
interactionName: "editor-media-card-loading"
})));
}
const contextId = contextIdentifierProvider && contextIdentifierProvider.objectId;
const identifier = type === 'external' ? {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dataURI: url,
name: url,
mediaItemType: 'external-image'
} : {
id,
mediaItemType: 'file',
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
collectionName: collection
};
const resolvedViewAndUploadMediaClientConfig = fg('platform_media_video_captions') ? viewAndUploadMediaClientConfig : undefined;
// mediaClientConfig is not needed for "external" case. So we have to cheat here.
// there is a possibility mediaClientConfig will be part of a identifier,
// so this might be not an issue
const mediaClientConfig = resolvedViewAndUploadMediaClientConfig || viewMediaClientConfig || {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
authProvider: () => ({})
};
const ssr = process.env.REACT_SSR ? 'server' : 'client';
// CWR (create-with-Rovo) infographics get a distinct loading treatment so the
// generic media type icon isn't shown while the image streams in. Gated on the
// existing infographics experiment.
const isCWR = !!this.props.isCwrAIGenerating && isExperimentEnabled('aifc_page_create_with_rovo_include_infographics');
return /*#__PURE__*/React.createElement(GeneratedMediaReveal, {
isEnabled: hasGeneratedMediaMotion,
isReady: isRevealReady,
mediaKey: id
}, /*#__PURE__*/React.createElement(MediaCardWrapper, {
dimensions: originalDimensions,
onContextMenu: this.selectMediaSingle,
borderWidth: borderMark === null || borderMark === void 0 ? void 0 : borderMark.attrs.size,
selected: selected
}, /*#__PURE__*/React.createElement(AnalyticsContext
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
, {
data: {
[MEDIA_CONTEXT]: {
border: !!borderMark,
// Only defined for remix-generated media (i.e. media nodes with a dataConsumer mark).
// Format: "remix:{type}:{subtype}" e.g. "remix:infographic:corporate-doodle"
remixSource: dataConsumerMark === null || dataConsumerMark === void 0 ? void 0 : (_dataConsumerMark$att2 = dataConsumerMark.attrs.sources) === null || _dataConsumerMark$att2 === void 0 ? void 0 : _dataConsumerMark$att2[0]
}
}
}, /*#__PURE__*/React.createElement(Card, {
mediaClientConfig: mediaClientConfig,
resizeMode: "stretchy-fit",
dimensions: maxDimensions,
originalDimensions: originalDimensions,
identifier: identifier,
selectable: true,
selected: selected,
disableOverlay: true,
onFullscreenChange: this.onFullscreenChange,
onClick: this.selectMediaSingleFromCard,
useInlinePlayer: mediaOptions && mediaOptions.allowLazyLoading,
isLazy: mediaOptions && mediaOptions.allowLazyLoading,
featureFlags: mediaOptions && mediaOptions.featureFlags,
contextId: contextId,
alt: alt,
videoControlsWrapperRef: this.videoControlsWrapperRef,
ssr: ssr,
mediaSettings: this.getMediaSettings(viewAndUploadMediaClientConfig, this.props.isViewOnly),
isAIGenerating: !!this.props.isAIGenerating,
isCWR: isCWR,
hasLoadingMotion: hasGeneratedMediaMotion,
onPreviewRender: this.onPreviewRender,
fallbackMediaNameFetcher: mediaOptions === null || mediaOptions === void 0 ? void 0 : mediaOptions.fallbackMediaNameFetcher,
onError: this.getMediaRenderErrorHandler()
}))));
}
}
const _default_1 = withImageLoader(MediaNode);
export default _default_1;