UNPKG

@atlaskit/editor-plugin-media

Version:

Media plugin for @atlaskit/editor-core

375 lines (369 loc) 12.9 kB
import _extends from "@babel/runtime/helpers/extends"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; /** * @jsxRuntime classic * @jsx jsx */ import React from 'react'; // eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports import { jsx } from '@emotion/react'; import { calculateOffsetLeft } from '@atlaskit/editor-common/media-single'; import { calcColumnsFromPx, calcMediaPxWidth, calcPctFromPx, handleSides, imageAlignmentMap, Resizer, snapTo, wrappedLayouts } from '@atlaskit/editor-common/ui'; import { calculateSnapPoints } from '@atlaskit/editor-common/utils'; import { findParentNodeOfTypeClosestToPos, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { akEditorWideLayoutWidth } from '@atlaskit/editor-shared-styles'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { checkMediaType } from '../../pm-plugins/utils/check-media-type'; import { wrapperStyle } from './styled'; // eslint-disable-next-line @repo/internal/react/no-class-components export default class ResizableMediaSingle extends React.Component { constructor(...args) { super(...args); _defineProperty(this, "hasResized", false); _defineProperty(this, "state", { offsetLeft: calculateOffsetLeft(this.insideInlineLike, this.insideLayout, this.props.view.dom, undefined), isVideoFile: false }); _defineProperty(this, "displayGrid", (visible, gridType, highlight) => { var _pluginInjectionApi$g, _pluginInjectionApi$g2; const { pluginInjectionApi, view } = this.props; pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : (_pluginInjectionApi$g = pluginInjectionApi.grid) === null || _pluginInjectionApi$g === void 0 ? void 0 : (_pluginInjectionApi$g2 = _pluginInjectionApi$g.actions) === null || _pluginInjectionApi$g2 === void 0 ? void 0 : _pluginInjectionApi$g2.displayGrid(view)({ visible, gridType, highlight: highlight }); }); _defineProperty(this, "calcNewSize", (newWidth, stop) => { const { layout, view: { state } } = this.props; if (!this.hasResized && expValEquals('platform_editor_media_vc_fixes', 'isEnabled', true)) { var _this$wrapper; const mediaDomEl = (_this$wrapper = this.wrapper) === null || _this$wrapper === void 0 ? void 0 : _this$wrapper.querySelector('div[data-prosemirror-node-name="media"]'); if (mediaDomEl) { const event = new CustomEvent('resized'); mediaDomEl === null || mediaDomEl === void 0 ? void 0 : mediaDomEl.dispatchEvent(event); } this.hasResized = true; } const newPct = calcPctFromPx(newWidth, this.props.lineLength) * 100; this.setState({ resizedPctWidth: newPct }); let newLayout = hasParentNodeOfType(state.schema.nodes.table)(state.selection) ? layout : this.calcUnwrappedLayout(newPct, newWidth); if (newPct <= 100) { if (this.wrappedLayout && (stop ? newPct !== 100 : true)) { newLayout = layout; } return { width: newPct, layout: newLayout }; } else { return { width: this.props.pctWidth || null, layout: newLayout }; } }); _defineProperty(this, "calcUnwrappedLayout", (pct, width) => { if (pct <= 100) { return 'center'; } if (width <= akEditorWideLayoutWidth) { return 'wide'; } return 'full-width'; }); _defineProperty(this, "calcColumnLeftOffset", () => { const { offsetLeft } = this.state; return this.insideInlineLike ? calcColumnsFromPx(offsetLeft, this.props.lineLength, this.props.gridSize) : 0; }); _defineProperty(this, "calcPxWidth", useLayout => { const { width: origWidth = 0, height: origHeight, layout, pctWidth, lineLength, containerWidth, fullWidthMode, getPos, view: { state } } = this.props; const { resizedPctWidth } = this.state; const pos = typeof getPos === 'function' ? getPos() : undefined; return calcMediaPxWidth({ origWidth, origHeight, pctWidth, state, containerWidth: { width: containerWidth, lineLength }, isFullWidthModeEnabled: fullWidthMode, layout: useLayout || layout, pos, resizedPctWidth }); }); _defineProperty(this, "highlights", (newWidth, snapPoints) => { const snapWidth = snapTo(newWidth, snapPoints); const { layoutColumn, table, expand, nestedExpand, panel } = this.props.view.state.schema.nodes; if (this.$pos && !!findParentNodeOfTypeClosestToPos(this.$pos, [layoutColumn, table, expand, nestedExpand, panel].filter(Boolean))) { return []; } if (snapWidth > akEditorWideLayoutWidth) { return ['full-width']; } const { layout, lineLength, gridSize } = this.props; const columns = calcColumnsFromPx(snapWidth, lineLength, gridSize); const columnWidth = Math.round(columns); const highlight = []; if (layout === 'wrap-left' || layout === 'align-start') { highlight.push(0, columnWidth); } else if (layout === 'wrap-right' || layout === 'align-end') { highlight.push(gridSize, gridSize - columnWidth); } else if (this.insideInlineLike) { highlight.push(Math.round(columns + this.calcColumnLeftOffset())); } else { highlight.push(Math.floor((gridSize - columnWidth) / 2), Math.ceil((gridSize + columnWidth) / 2)); } return highlight; }); _defineProperty(this, "saveWrapper", wrapper => this.wrapper = wrapper); } componentDidUpdate(prevProps) { const offsetLeft = calculateOffsetLeft(this.insideInlineLike, this.insideLayout, this.props.view.dom, this.wrapper); if (offsetLeft !== this.state.offsetLeft && offsetLeft >= 0) { this.setState({ offsetLeft }); } // Handle undo, when the actual pctWidth changed, // we sync up with the internal state. if (prevProps.pctWidth !== this.props.pctWidth) { this.setState({ resizedPctWidth: this.props.pctWidth }); } return true; } get wrappedLayout() { return wrappedLayouts.indexOf(this.props.layout) > -1; } // check if is inside of a table isNestedInTable() { const { table } = this.props.view.state.schema.nodes; if (!this.$pos) { return false; } return !!findParentNodeOfTypeClosestToPos(this.$pos, table); } async componentDidMount() { const { viewMediaClientConfig } = this.props; if (viewMediaClientConfig) { await this.checkVideoFile(viewMediaClientConfig); } } UNSAFE_componentWillReceiveProps(nextProps) { if (this.props.viewMediaClientConfig !== nextProps.viewMediaClientConfig) { this.checkVideoFile(nextProps.viewMediaClientConfig); } if (this.props.layout !== nextProps.layout) { this.checkLayout(this.props.layout, nextProps.layout); } } async checkVideoFile(viewMediaClientConfig) { const $pos = this.$pos; if (!$pos || !viewMediaClientConfig) { return; } const mediaNode = this.props.view.state.doc.nodeAt($pos.pos + 1); const mediaType = mediaNode ? await checkMediaType(mediaNode, viewMediaClientConfig) : undefined; const isVideoFile = mediaType !== 'external' && mediaType !== 'image'; if (this.state.isVideoFile !== isVideoFile) { this.setState({ isVideoFile }); } } /** * When returning to center layout from a wrapped/aligned layout, it might actually * be wide or full-width */ checkLayout(oldLayout, newLayout) { const { resizedPctWidth } = this.state; if (wrappedLayouts.indexOf(oldLayout) > -1 && newLayout === 'center' && resizedPctWidth) { const layout = this.calcUnwrappedLayout(resizedPctWidth, this.calcPxWidth(newLayout)); this.props.updateSize(resizedPctWidth, layout); } } get $pos() { if (typeof this.props.getPos !== 'function') { return null; } const pos = this.props.getPos(); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any if (Number.isNaN(pos) || typeof pos !== 'number') { return null; } // need to pass view because we may not get updated props in time return this.props.view.state.doc.resolve(pos); } /** * The maxmimum number of grid columns this node can resize to. */ get gridWidth() { const { gridSize } = this.props; return !(this.wrappedLayout || this.insideInlineLike) ? gridSize / 2 : gridSize; } get insideInlineLike() { const $pos = this.$pos; if (!$pos) { return false; } const { listItem } = this.props.view.state.schema.nodes; return !!findParentNodeOfTypeClosestToPos($pos, [listItem]); } get insideLayout() { const $pos = this.$pos; if (!$pos) { return false; } const { layoutColumn } = this.props.view.state.schema.nodes; return !!findParentNodeOfTypeClosestToPos($pos, [layoutColumn]); } render() { const { width: origWidth, height: origHeight, layout, pctWidth, containerWidth, fullWidthMode, selected, children } = this.props; const initialWidth = this.calcPxWidth(); // width with padding let ratio; if (origWidth) { ratio = (origHeight / origWidth * 100).toFixed(3); } const enable = {}; handleSides.forEach(side => { const oppositeSide = side === 'left' ? 'right' : 'left'; if (expValEquals('platform_editor_media_vc_fixes', 'isEnabled', true)) { if (this.props.disableHandles) { enable[side] = false; return; } } enable[side] = ['full-width', 'wide', 'center'].concat(`wrap-${oppositeSide}`).concat(`align-${imageAlignmentMap[oppositeSide]}`).indexOf(layout) > -1; if (side === 'left' && this.insideInlineLike) { enable[side] = false; } }); const snapPointsProps = { $pos: this.$pos, akEditorWideLayoutWidth: akEditorWideLayoutWidth, allowBreakoutSnapPoints: this.props.allowBreakoutSnapPoints, containerWidth: this.props.containerWidth, gridSize: this.props.gridSize, gridWidth: this.gridWidth, insideInlineLike: this.insideInlineLike, insideLayout: this.insideLayout, isVideoFile: this.state.isVideoFile, lineLength: this.props.lineLength, offsetLeft: this.state.offsetLeft, wrappedLayout: this.wrappedLayout }; const nestedInTableHandleStyles = isNestedInTable => { if (!isNestedInTable) { return; } return { left: { left: `calc(${"var(--ds-space-025, 2px)"} * -0.5)`, paddingLeft: '0px' }, right: { right: `calc(${"var(--ds-space-025, 2px)"} * -0.5)`, paddingRight: '0px' } }; }; return jsx("div", { ref: this.saveWrapper // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values, @atlaskit/design-system/consistent-css-prop-usage -- Ignored via go/DSP-18766 , css: wrapperStyle({ layout, isResized: !!pctWidth, containerWidth: containerWidth || origWidth, fullWidthMode, width: origWidth }) }, jsx(Resizer // Ignored via go/ees005 // eslint-disable-next-line react/jsx-props-no-spreading , _extends({}, this.props, { displayGrid: this.displayGrid, ratio: ratio, width: initialWidth, selected: selected, enable: enable, calcNewSize: this.calcNewSize, snapPoints: calculateSnapPoints(snapPointsProps), scaleFactor: !this.wrappedLayout && !this.insideInlineLike ? 2 : 1, highlights: this.highlights, nodeType: "media", dispatchAnalyticsEvent: this.props.dispatchAnalyticsEvent // when cursor is located below a media with caption, // press “Up“ key will result cursor focus on an invalid position, (on the resize handler) // This workaround adds an empty div inside the resize handler to prevent the issue. // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , handleComponentFunc: () => jsx("div", { contentEditable: false }), handleStyles: nestedInTableHandleStyles(this.isNestedInTable()) }), children)); } }