UNPKG

@atlaskit/editor-plugin-media-insert

Version:

Media Insert plugin for @atlaskit/editor-core

198 lines (194 loc) 7.59 kB
import React, { useCallback } from 'react'; import { useIntl } from 'react-intl'; import Button from '@atlaskit/button/new'; import { INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { mediaInsertMessages } from '@atlaskit/editor-common/messages'; import UploadIcon from '@atlaskit/icon/core/upload'; import { Browser } from '@atlaskit/media-picker'; import { Stack } from '@atlaskit/primitives/compiled'; import SectionMessage from '@atlaskit/section-message'; import { useAnalyticsEvents } from './useAnalyticsEvents'; const INITIAL_UPLOAD_STATE = Object.freeze({ isOpen: false, error: null }); const uploadReducer = (state, action) => { switch (action.type) { case 'open': return { ...INITIAL_UPLOAD_STATE, isOpen: true }; case 'close': // This is the only case where we don't reset state. This is because // onClose gets called for cancel _and_ upload, so we don't want to // reset any loading or error states that may have occured return { ...state, isOpen: false }; case 'error': return { ...INITIAL_UPLOAD_STATE, error: action.error }; case 'reset': return INITIAL_UPLOAD_STATE; } }; const isImagePreview = preview => { return 'dimensions' in preview; }; export const LocalMedia = /*#__PURE__*/React.forwardRef(({ mediaProvider, dispatchAnalyticsEvent, closeMediaInsertPicker, insertFile }, ref) => { const intl = useIntl(); const strings = { upload: intl.formatMessage(mediaInsertMessages.upload), networkError: intl.formatMessage(mediaInsertMessages.localFileNetworkErrorMessage), genericError: intl.formatMessage(mediaInsertMessages.localFileErrorMessage) }; const { onUploadButtonClickedAnalytics, onUploadCommencedAnalytics, onUploadSuccessAnalytics, onUploadFailureAnalytics } = useAnalyticsEvents(dispatchAnalyticsEvent); const [uploadState, dispatch] = React.useReducer(uploadReducer, INITIAL_UPLOAD_STATE); const erroredFileIds = React.useState(new Set())[0]; // This is a bit horrendous. Fundementally though, `insertFile` takes a // callback that can ask for us to add listeners to any and all of the // `Browser` events for each file uplaoded. We add a track those in a // ref, call the CBs so the media-plugin can do it's thing, and then // remove all listeners `onEnd`. const eventSubscribers = React.useRef({}); const onStateChanged = React.useCallback(fileId => cb => { var _eventSubscribers$cur, _eventSubscribers$cur2; eventSubscribers.current = { ...eventSubscribers.current, [fileId]: [...((_eventSubscribers$cur = (_eventSubscribers$cur2 = eventSubscribers.current) === null || _eventSubscribers$cur2 === void 0 ? void 0 : _eventSubscribers$cur2[fileId]) !== null && _eventSubscribers$cur !== void 0 ? _eventSubscribers$cur : []), cb] }; }, []); const onPreviewUpdate = ({ file, preview }) => { var _mediaProvider$upload; onUploadSuccessAnalytics('local'); const isErroredFile = erroredFileIds.has(file.id); const { dimensions, scaleFactor } = isImagePreview(preview) ? preview : { dimensions: undefined, scaleFactor: undefined }; const mediaState = { id: file.id, collection: (_mediaProvider$upload = mediaProvider.uploadParams) === null || _mediaProvider$upload === void 0 ? void 0 : _mediaProvider$upload.collection, fileMimeType: file.type, fileSize: file.size, fileName: file.name, dimensions, scaleFactor, status: isErroredFile ? 'error' : undefined }; insertFile({ mediaState, inputMethod: INPUT_METHOD.MEDIA_PICKER, onMediaStateChanged: onStateChanged(file.id) }); closeMediaInsertPicker(); // Probably not needed but I guess it _could_ fail to close for some reason dispatch({ type: 'reset' }); }; const { uploadParams, uploadMediaClientConfig } = mediaProvider; const onEnd = useCallback(payload => { var _eventSubscribers$cur3, _eventSubscribers$cur4, _eventSubscribers$cur5; (_eventSubscribers$cur3 = eventSubscribers.current) === null || _eventSubscribers$cur3 === void 0 ? void 0 : (_eventSubscribers$cur4 = _eventSubscribers$cur3[payload.file.id]) === null || _eventSubscribers$cur4 === void 0 ? void 0 : _eventSubscribers$cur4.forEach(cb => cb({ id: payload.file.id, status: 'ready' })); (_eventSubscribers$cur5 = eventSubscribers.current) === null || _eventSubscribers$cur5 === void 0 ? true : delete _eventSubscribers$cur5[payload.file.id]; }, []); const onError = useCallback(({ error, fileId }) => { var _eventSubscribers$cur6, _eventSubscribers$cur7, _eventSubscribers$cur8; // Dispatch the error events onUploadFailureAnalytics(error.name, 'local'); dispatch({ type: 'error', error: error.name }); // Update the status of the errored file erroredFileIds.add(fileId); // Update and remove listeners (_eventSubscribers$cur6 = eventSubscribers.current) === null || _eventSubscribers$cur6 === void 0 ? void 0 : (_eventSubscribers$cur7 = _eventSubscribers$cur6[fileId]) === null || _eventSubscribers$cur7 === void 0 ? void 0 : _eventSubscribers$cur7.forEach(cb => cb({ id: fileId, status: 'error', error: error })); (_eventSubscribers$cur8 = eventSubscribers.current) === null || _eventSubscribers$cur8 === void 0 ? true : delete _eventSubscribers$cur8[fileId]; }, [erroredFileIds, onUploadFailureAnalytics]); return /*#__PURE__*/React.createElement(Stack, { grow: "fill", space: "space.200" }, uploadState.error && /*#__PURE__*/React.createElement(SectionMessage, { appearance: "error" }, uploadState.error === 'upload_fail' ? strings.networkError : strings.genericError), /*#__PURE__*/React.createElement(Button, { id: "local-media-upload-button" // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , iconBefore: () => /*#__PURE__*/React.createElement(UploadIcon, { label: "" }), ref: ref, shouldFitContainer: true, isDisabled: !uploadMediaClientConfig || !uploadParams // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onClick: () => { onUploadButtonClickedAnalytics(); dispatch({ type: 'open' }); } }, strings.upload), uploadMediaClientConfig && uploadParams && /*#__PURE__*/React.createElement(Browser, { isOpen: uploadState.isOpen // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , config: { uploadParams: uploadParams, multiple: true }, mediaClientConfig: uploadMediaClientConfig // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onUploadsStart: () => onUploadCommencedAnalytics('local'), onPreviewUpdate: onPreviewUpdate, onEnd: onEnd // NOTE: this will fire for some errors like network failures, but not // for others like empty files. Those have their own feedback toast // owned by media. , onError: onError // eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed) , onClose: () => { erroredFileIds.clear(); dispatch({ type: 'close' }); } })); });