UNPKG

@atlaskit/renderer

Version:
280 lines (273 loc) • 9.66 kB
import _extends from "@babel/runtime/helpers/extends"; /** * @jsxRuntime classic * @jsx jsx */ import { default as React, Fragment, useCallback, useContext, useMemo, useEffect } from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled -- Ignored via go/DSP-18766 import { css, jsx } from '@emotion/react'; import { injectIntl } from 'react-intl'; import { MediaSingle as UIMediaSingle, WidthContext } from '@atlaskit/editor-common/ui'; import { fg } from '@atlaskit/platform-feature-flags'; import { akEditorFullWidthLayoutWidth, akEditorDefaultLayoutWidth, akEditorWideLayoutWidth } from '@atlaskit/editor-shared-styles'; import { isSSR } from '@atlaskit/editor-common/core-utils'; import { FullPagePadding } from '../../../ui/Renderer/style'; import { useAnnotationRangeDispatch } from '../../../ui/annotations/contexts/AnnotationRangeContext'; import { useAnnotationHoverDispatch } from '../../../ui/annotations/contexts/AnnotationHoverContext'; const DEFAULT_WIDTH = 250; const DEFAULT_HEIGHT = 200; const uiMediaSingleBaseStyles = css({ transition: 'all 0.1s linear' }); const uiMediaSingleLayoutStyles = css({ // eslint-disable-next-line @atlaskit/design-system/use-tokens-space marginLeft: '50%', transform: 'translateX(-50%)' }); const isMediaElement = media => { if (!media) { return false; } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any const { nodeType, type } = media.props || {}; // Use this to perform a rough check // better than assume the first item in children is media return nodeType === 'media' || ['external', 'file', 'link'].indexOf(type) >= 0; }; const checkForMediaElement = children => { const [media] = React.Children.toArray(children); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any if (media && !isMediaElement(media) && media.props.children) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any return checkForMediaElement(media.props.children); } return media; }; // returns the existing container width if available (non SSR mode), otherwise // we return a default width value export const getMediaContainerWidth = (currentContainerWidth, layout) => { if (!currentContainerWidth) { // SSR mode fallback to default layout width switch (layout) { case 'full-width': return akEditorFullWidthLayoutWidth; case 'wide': return akEditorWideLayoutWidth; default: return akEditorDefaultLayoutWidth; } } return currentContainerWidth; }; const MediaSingleWithChildren = props => { const { rendererAppearance, featureFlags, isInsideOfBlockNode, layout, width: widthAttr, widthType, allowCaptions = false, isInsideOfInlineExtension = false, dataAttributes, media, caption, editorAppearance } = props; const [externalImageDimensions, setExternalImageDimensions] = React.useState({ width: 0, height: 0 }); const ref = React.useRef(null); const onExternalImageLoaded = React.useCallback(({ width, height }) => { setExternalImageDimensions({ width, height }); }, []); // Ignored via go/ees005 // eslint-disable-next-line prefer-const let { width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT, type } = media.props; if (type === 'external') { const { width: stateWidth, height: stateHeight } = externalImageDimensions; if (width === null) { width = stateWidth || DEFAULT_WIDTH; } if (height === null) { height = stateHeight || DEFAULT_HEIGHT; } } if (width === null) { width = DEFAULT_WIDTH; height = DEFAULT_HEIGHT; } const isFullPage = rendererAppearance === 'full-page'; const isFullWidth = rendererAppearance === 'full-width'; const padding = isFullPage ? FullPagePadding * 2 : 0; const calcDimensions = useCallback(mediaContainerWidth => { const containerWidth = getMediaContainerWidth(mediaContainerWidth, layout); let cardDimensions = {}; if (fg('media-perf-uplift-mutation-fix')) { const maxWidth = widthAttr && typeof widthAttr === 'number' && widthType === 'pixel' ? widthAttr : containerWidth; cardDimensions = { width: `${maxWidth}px`, height: `100%` }; } else { const maxWidth = isSSR() && widthAttr && typeof widthAttr === 'number' ? Math.max(widthAttr, containerWidth) : containerWidth; const maxHeight = height / width * maxWidth; cardDimensions = { width: `${maxWidth}px`, height: `${maxHeight}px` }; } let nonFullWidthSize = containerWidth; if (!isInsideOfBlockNode && rendererAppearance !== 'comment') { const isContainerSizeGreaterThanMaxFullPageWidth = containerWidth - padding >= akEditorDefaultLayoutWidth; if (isContainerSizeGreaterThanMaxFullPageWidth) { nonFullWidthSize = akEditorDefaultLayoutWidth; } else { nonFullWidthSize = containerWidth - padding; } } const minWidth = Math.min(akEditorFullWidthLayoutWidth, containerWidth - padding); const lineLength = isFullWidth ? minWidth : nonFullWidthSize; return { cardDimensions, lineLength }; }, [height, isFullWidth, isInsideOfBlockNode, layout, padding, rendererAppearance, width, widthAttr, widthType]); const originalDimensions = useMemo(() => ({ width, height }), [height, width]); const { setHoverTarget } = useAnnotationRangeDispatch(); const { cancelTimeout, initiateTimeout, setIsWithinRange } = useAnnotationHoverDispatch(); const isFullPageRenderer = isFullPage || isFullWidth; useEffect(() => { const mediaSingleElement = ref.current; const handleMouseEnter = event => { cancelTimeout(); if (event.buttons === 0) { // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting setHoverTarget && setHoverTarget(event.target); setIsWithinRange(true); } }; const handleMouseLeave = () => { initiateTimeout(); }; if (mediaSingleElement && isFullPageRenderer) { // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners mediaSingleElement.addEventListener('mouseenter', handleMouseEnter); // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners mediaSingleElement.addEventListener('mouseleave', handleMouseLeave); } return () => { if (mediaSingleElement && isFullPageRenderer) { // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners mediaSingleElement.removeEventListener('mouseenter', handleMouseEnter); // Ignored via go/ees005 // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners mediaSingleElement.removeEventListener('mouseleave', handleMouseLeave); } }; }, [setHoverTarget, isFullPageRenderer, cancelTimeout, initiateTimeout, setIsWithinRange]); // Note: in SSR mode the `window` object is not defined, // therefore width here is 0, see: // packages/editor/editor-common/src/ui/WidthProvider/index.tsx const { width: renderWidth } = useContext(WidthContext); const containerWidth = getMediaContainerWidth(renderWidth, layout); const { cardDimensions, lineLength } = useMemo(() => calcDimensions(containerWidth), [calcDimensions, containerWidth]); const renderMediaSingle = () => { const mediaComponent = /*#__PURE__*/React.cloneElement(media, { resizeMode: 'stretchy-fit', cardDimensions, originalDimensions, onExternalImageLoaded, disableOverlay: true, featureFlags, mediaSingleElement: ref.current }); const uiMediaSingleStyles = layout === 'full-width' || layout === 'wide' ? [uiMediaSingleBaseStyles, uiMediaSingleLayoutStyles] : [uiMediaSingleBaseStyles]; return jsx(UIMediaSingle, { css: uiMediaSingleStyles, handleMediaSingleRef: ref, layout: layout, width: width, height: height, lineLength: isInsideOfBlockNode ? containerWidth : lineLength, containerWidth: containerWidth // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , size: { width: widthAttr, widthType }, fullWidthMode: isFullWidth, isInsideOfInlineExtension: isInsideOfInlineExtension, dataAttributes: dataAttributes, editorAppearance: editorAppearance, isInRenderer: true }, jsx(Fragment, null, mediaComponent), allowCaptions && caption); }; return renderMediaSingle(); }; const MediaSingle = props => { const { children } = props; let media; const [node, caption] = React.Children.toArray(children); if (!isMediaElement(node)) { // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any const mediaElement = checkForMediaElement(node.props.children); if (!mediaElement) { return node; } media = mediaElement; } else { media = node; } // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading return jsx(MediaSingleWithChildren, _extends({}, props, { media: media, caption: caption })); }; // eslint-disable-next-line @typescript-eslint/ban-types const _default_1 = injectIntl(MediaSingle); export default _default_1;