@atlaskit/editor-plugin-media-insert
Version:
Media Insert plugin for @atlaskit/editor-core
198 lines (194 loc) • 7.59 kB
JavaScript
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'
});
}
}));
});