Files
frontend-app-authoring/src/editors/sharedComponents/ImageUploadModal/index.jsx
2024-08-17 16:12:58 -07:00

198 lines
4.7 KiB
JavaScript

import 'CourseAuthoring/editors/setupEditorTest';
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl } from '@edx/frontend-platform/i18n';
import * as tinyMCEKeys from '../../data/constants/tinyMCE';
import ImageSettingsModal from './ImageSettingsModal';
import SelectImageModal from './SelectImageModal';
// This 'module' self-import hack enables mocking during tests.
// See src/editors/decisions/0005-internal-editor-testability-decisions.md. The whole approach to how hooks are tested
// should be re-thought and cleaned up to avoid this pattern.
// eslint-disable-next-line import/no-self-import
import * as module from '.';
import { updateImageDimensions } from '../TinyMceWidget/hooks';
export const propsString = (props) => (
Object.keys(props).map((key) => `${key}="${props[key]}"`).join(' ')
);
export const imgProps = ({
settings,
selection,
lmsEndpointUrl,
editorType,
}) => {
let url = selection?.externalUrl;
if (url?.startsWith(lmsEndpointUrl) && editorType !== 'expandable') {
const sourceEndIndex = lmsEndpointUrl.length;
url = url.substring(sourceEndIndex);
}
return {
src: url,
alt: settings.isDecorative ? '' : settings.altText,
width: settings.dimensions.width,
height: settings.dimensions.height,
};
};
export const saveToEditor = ({
settings, selection, lmsEndpointUrl, editorType, editorRef,
}) => {
const newImgTag = module.hooks.imgTag({
settings,
selection,
lmsEndpointUrl,
editorType,
});
editorRef.current.execCommand(
tinyMCEKeys.commands.insertContent,
false,
newImgTag,
);
};
export const updateImagesRef = ({
images, selection, height, width, newImage,
}) => {
const { result: mappedImages, foundMatch: imageAlreadyExists } = updateImageDimensions({
images: images.current, url: selection.externalUrl, height, width,
});
// eslint-disable-next-line no-param-reassign
images.current = imageAlreadyExists ? mappedImages : [...images.current, newImage];
};
export const updateReactState = ({
settings, selection, setSelection, images,
}) => {
const { height, width } = settings.dimensions;
const newImage = {
externalUrl: selection.externalUrl,
altText: settings.altText,
width,
height,
};
updateImagesRef({
images, selection, height, width, newImage,
});
setSelection(newImage);
};
export const hooks = {
createSaveCallback: ({
close,
...args
}) => (
settings,
) => {
saveToEditor({ settings, ...args });
updateReactState({ settings, ...args });
close();
args.setSelection(null);
},
onClose: ({ clearSelection, close }) => () => {
clearSelection();
close();
},
imgTag: ({
settings,
selection,
lmsEndpointUrl,
editorType,
}) => {
const props = module.imgProps({
settings,
selection,
lmsEndpointUrl,
editorType,
});
return `<img ${propsString(props)} />`;
},
updateReactState,
updateImagesRef,
saveToEditor,
imgProps,
propsString,
};
const ImageUploadModal = ({
// eslint-disable-next-line
editorRef,
isOpen,
close,
clearSelection,
selection,
setSelection,
images,
editorType,
lmsEndpointUrl,
}) => {
if (selection && selection.externalUrl) {
return (
<ImageSettingsModal
{...{
isOpen,
close: module.hooks.onClose({ clearSelection, close }),
selection,
images,
saveToEditor: module.hooks.createSaveCallback({
close,
images,
editorRef,
editorType,
selection,
setSelection,
lmsEndpointUrl,
clearSelection,
}),
returnToSelection: clearSelection,
}}
/>
);
}
return (
<SelectImageModal
{...{
isOpen,
close,
setSelection,
clearSelection,
images,
}}
/>
);
};
ImageUploadModal.defaultProps = {
editorRef: null,
editorType: null,
selection: null,
};
ImageUploadModal.propTypes = {
clearSelection: PropTypes.func.isRequired,
close: PropTypes.func.isRequired,
editorRef: PropTypes.oneOfType([
PropTypes.func,
// eslint-disable-next-line react/forbid-prop-types
PropTypes.shape({ current: PropTypes.any }),
]),
isOpen: PropTypes.bool.isRequired,
selection: PropTypes.shape({
url: PropTypes.string,
externalUrl: PropTypes.string,
altText: PropTypes.bool,
}),
setSelection: PropTypes.func.isRequired,
images: PropTypes.shape({}).isRequired,
lmsEndpointUrl: PropTypes.string.isRequired,
editorType: PropTypes.string,
};
export const ImageUploadModalInternal = ImageUploadModal; // For testing only
export default injectIntl(ImageUploadModal);