style: adding more styles on the selection modal

This commit is contained in:
XnpioChV
2023-03-19 15:41:43 -05:00
parent 3b69958427
commit 71efe876d3
29 changed files with 233 additions and 85 deletions

34
package-lock.json generated
View File

@@ -21,6 +21,7 @@
"codemirror": "^6.0.0",
"fast-xml-parser": "^4.0.10",
"lodash-es": "^4.17.21",
"moment-shortformat": "^2.1.0",
"react-redux": "^7.2.8",
"react-responsive": "8.2.0",
"react-transition-group": "4.4.2",
@@ -26149,6 +26150,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/moment-shortformat": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/moment-shortformat/-/moment-shortformat-2.1.0.tgz",
"integrity": "sha512-TBh8jH4cVQOnZU+fQXkyCgj74ti//0CTdQd5sQLDTZuHLMaUP3uFEhbfb3yrrLYNVzgoiUhyiIMf0rDdn5iTJg==",
"deprecated": "Package is no longer maintained",
"engines": {
"node": ">= 4"
},
"peerDependencies": {
"moment": "^2.4.0"
}
},
"node_modules/moo": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
@@ -54618,6 +54640,18 @@
}
}
},
"moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"peer": true
},
"moment-shortformat": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/moment-shortformat/-/moment-shortformat-2.1.0.tgz",
"integrity": "sha512-TBh8jH4cVQOnZU+fQXkyCgj74ti//0CTdQd5sQLDTZuHLMaUP3uFEhbfb3yrrLYNVzgoiUhyiIMf0rDdn5iTJg==",
"requires": {}
},
"moo": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",

View File

@@ -69,6 +69,7 @@
"codemirror": "^6.0.0",
"fast-xml-parser": "^4.0.10",
"lodash-es": "^4.17.21",
"moment-shortformat": "^2.1.0",
"react-redux": "^7.2.8",
"react-responsive": "8.2.0",
"react-transition-group": "4.4.2",

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import VideoGallery from './containers/VideoGallery';
import * as hooks from './hooks';
export const Selector = ({
export const VideoSelector = ({
learningContextId,
lmsEndpointUrl,
studioEndpointUrl,
@@ -25,10 +25,10 @@ export const Selector = ({
);
};
Selector.propTypes = {
VideoSelector.propTypes = {
learningContextId: PropTypes.string.isRequired,
lmsEndpointUrl: PropTypes.string.isRequired,
studioEndpointUrl: PropTypes.string.isRequired,
};
export default Selector;
export default VideoSelector;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useDispatch } from 'react-redux';
import { shallow } from 'enzyme';
import * as hooks from './hooks';
import Selector from './Selector';
import VideoSelector from './VideoSelector';
jest.mock('./hooks', () => ({
initializeApp: jest.fn(),
@@ -22,15 +22,15 @@ const initData = {
...props,
};
describe('Editor', () => {
describe('Video Selector', () => {
describe('render', () => {
test('rendering correctly with expected Input', () => {
expect(shallow(<Selector {...props} />)).toMatchSnapshot();
expect(shallow(<VideoSelector {...props} />)).toMatchSnapshot();
});
});
describe('behavior', () => {
it('calls initializeApp hook with dispatch, and passed data', () => {
shallow(<Selector {...props} />);
shallow(<VideoSelector {...props} />);
expect(hooks.initializeApp).toHaveBeenCalledWith({
dispatch: useDispatch(),
data: initData,

View File

@@ -2,17 +2,17 @@ import React from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import ErrorBoundary from './sharedComponents/ErrorBoundary';
import { Selector } from './Selector';
import { VideoSelector } from './VideoSelector';
import store from './data/store';
const SelectorPage = ({
const VideoSelectorPage = ({
courseId,
lmsEndpointUrl,
studioEndpointUrl,
}) => (
<ErrorBoundary>
<Provider store={store}>
<Selector
<VideoSelector
{...{
learningContextId: courseId,
lmsEndpointUrl,
@@ -23,16 +23,16 @@ const SelectorPage = ({
</ErrorBoundary>
);
SelectorPage.defaultProps = {
VideoSelectorPage.defaultProps = {
courseId: null,
lmsEndpointUrl: null,
studioEndpointUrl: null,
};
SelectorPage.propTypes = {
VideoSelectorPage.propTypes = {
courseId: PropTypes.string,
lmsEndpointUrl: PropTypes.string,
studioEndpointUrl: PropTypes.string,
};
export default SelectorPage;
export default VideoSelectorPage;

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
import SelectorPage from './SelectorPage';
import VideoSelectorPage from './VideoSelectorPage';
const props = {
courseId: 'course-v1:edX+DemoX+Demo_Course',
@@ -11,15 +11,15 @@ const props = {
jest.mock('react-redux', () => ({
Provider: 'Provider',
}));
jest.mock('./Selector', () => 'Selector');
jest.mock('./VideoSelector', () => 'VideoSelector');
describe('Selector Page', () => {
describe('Video Selector Page', () => {
describe('snapshots', () => {
test('rendering correctly with expected Input', () => {
expect(shallow(<SelectorPage {...props} />)).toMatchSnapshot();
expect(shallow(<VideoSelectorPage {...props} />)).toMatchSnapshot();
});
test('rendering with props to null', () => {
expect(shallow(<SelectorPage />)).toMatchSnapshot();
expect(shallow(<VideoSelectorPage />)).toMatchSnapshot();
});
});
});

View File

@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Editor render rendering correctly with expected Input 1`] = `<VideoGallery />`;

View File

@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Video Selector render rendering correctly with expected Input 1`] = `<VideoGallery />`;

View File

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Selector Page snapshots rendering correctly with expected Input 1`] = `
exports[`Video Selector Page snapshots rendering correctly with expected Input 1`] = `
<ErrorBoundary>
<Provider
store={
@@ -22,7 +22,7 @@ exports[`Selector Page snapshots rendering correctly with expected Input 1`] = `
</ErrorBoundary>
`;
exports[`Selector Page snapshots rendering with props to null 1`] = `
exports[`Video Selector Page snapshots rendering with props to null 1`] = `
<ErrorBoundary>
<Provider
store={

View File

@@ -5,6 +5,7 @@ exports[`EditorContainer component render snapshot: initialized. enable save and
className="position-relative zindex-0"
>
<BaseModal
bodyStyle={null}
close={[MockFunction closeCancelConfirmModal]}
confirmAction={
<Button
@@ -25,6 +26,7 @@ exports[`EditorContainer component render snapshot: initialized. enable save and
</Button>
}
footerAction={null}
headerComponent={null}
isFullscreenScroll={true}
isOpen={false}
size="md"
@@ -84,6 +86,7 @@ exports[`EditorContainer component render snapshot: not initialized. disable sav
className="position-relative zindex-0"
>
<BaseModal
bodyStyle={null}
close={[MockFunction closeCancelConfirmModal]}
confirmAction={
<Button
@@ -104,6 +107,7 @@ exports[`EditorContainer component render snapshot: not initialized. disable sav
</Button>
}
footerAction={null}
headerComponent={null}
isFullscreenScroll={true}
isOpen={false}
size="md"

View File

@@ -10,6 +10,9 @@ jest.mock('../../../../../data/redux', () => ({
problemType: jest.fn(state => ({ problemType: state })),
},
},
thunkActions: {
video: jest.fn(),
},
}));
describe('AnswerOption', () => {

View File

@@ -21,6 +21,11 @@ jest.mock('../../../../../data/redux', () => ({
question: jest.fn(state => ({ question: state })),
},
},
thunkActions: {
video: {
importTranscript: jest.fn(),
},
},
}));
jest.mock('../../../../../sharedComponents/TinyMceWidget/hooks', () => ({

View File

@@ -16,6 +16,9 @@ jest.mock('../../../../../../data/redux', () => ({
learningContextId: jest.fn(state => ({ learningContextId: state })),
},
},
thunkActions: {
video: jest.fn(),
},
}));
describe('ShowAnswerCard', () => {

View File

@@ -5,6 +5,7 @@ exports[`SwitchToAdvancedEditorCard snapshot snapshot: SwitchToAdvancedEditorCar
className="border border-light-700 shadow-none"
>
<BaseModal
bodyStyle={null}
close={[Function]}
confirmAction={
<Button
@@ -21,6 +22,7 @@ exports[`SwitchToAdvancedEditorCard snapshot snapshot: SwitchToAdvancedEditorCar
</Button>
}
footerAction={null}
headerComponent={null}
isFullscreenScroll={true}
isOpen={false}
size="md"

View File

@@ -61,6 +61,11 @@ jest.mock('../../data/redux', () => ({
isFinished: jest.fn((state, params) => ({ isFailed: { state, params } })),
},
},
thunkActions: {
video: {
importTranscript: jest.fn(),
},
},
}));
describe('TextEditor', () => {

View File

@@ -139,20 +139,34 @@ export const buildVideos = ({ rawVideos }) => {
let videos = [];
const videoList = Object.values(rawVideos);
if (videoList.length > 0) {
videos = videoList.map(asset => ({
id: asset.edx_video_id,
displayName: asset.client_video_id,
externalUrl: asset.course_video_image_url,
dateAdded: asset.created,
videos = videoList.map(video => ({
id: video.edx_video_id,
displayName: video.client_video_id,
externalUrl: video.course_video_image_url,
dateAdded: video.created,
locked: false,
thumbnail: asset.course_video_image_url,
status: asset.status,
duration: asset.duration,
thumbnail: video.course_video_image_url,
status: video.status,
statusBadgeVariant: module.getstatusBadgeVariant({ status: video.status }),
duration: video.duration,
transcripts: video.transcripts,
}));
}
return videos;
};
export const getstatusBadgeVariant = ({ status }) => {
switch (status) {
case filterKeys.failed:
return 'danger';
case filterKeys.uploading:
case filterKeys.processing:
return 'light';
default:
return null;
}
};
export const videoHooks = ({ videos }) => {
const searchSortProps = module.searchAndSortHooks();
const videoList = module.videoListHooks({ searchSortProps, videos });

View File

@@ -10,12 +10,20 @@ exports[`BaseModal ImageUploadModal template component snapshot 1`] = `
size="lg"
variant="default"
>
<ModalDialog.Header>
<ModalDialog.Header
style={
Object {
"zIndex": 10000,
}
}
>
<ModalDialog.Title>
props.title node
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body>
<ModalDialog.Body
style={null}
>
props.children node
</ModalDialog.Body>
<ModalDialog.Footer>

View File

@@ -14,10 +14,12 @@ export const BaseModal = ({
close,
title,
children,
headerComponent,
confirmAction,
footerAction,
size,
isFullscreenScroll,
bodyStyle,
}) => (
<ModalDialog
isOpen={isOpen}
@@ -28,12 +30,13 @@ export const BaseModal = ({
isFullscreenOnMobile
isFullscreenScroll={isFullscreenScroll}
>
<ModalDialog.Header>
<ModalDialog.Header style={{ zIndex: 10000 }}>
<ModalDialog.Title>
{title}
</ModalDialog.Title>
{headerComponent}
</ModalDialog.Header>
<ModalDialog.Body>
<ModalDialog.Body style={bodyStyle}>
{children}
</ModalDialog.Body>
<ModalDialog.Footer>
@@ -51,8 +54,10 @@ export const BaseModal = ({
BaseModal.defaultProps = {
footerAction: null,
headerComponent: null,
size: 'lg',
isFullscreenScroll: true,
bodyStyle: null,
};
BaseModal.propTypes = {
@@ -62,8 +67,10 @@ BaseModal.propTypes = {
children: PropTypes.node.isRequired,
confirmAction: PropTypes.node.isRequired,
footerAction: PropTypes.node,
headerComponent: PropTypes.node,
size: PropTypes.string,
isFullscreenScroll: PropTypes.bool,
bodyStyle: PropTypes.shape({}),
};
export default BaseModal;

View File

@@ -2,6 +2,7 @@
exports[`ImageSettingsModal render snapshot 1`] = `
<BaseModal
bodyStyle={null}
close={[MockFunction props.close]}
confirmAction={
<Button
@@ -35,6 +36,7 @@ exports[`ImageSettingsModal render snapshot 1`] = `
</Button>
}
footerAction={null}
headerComponent={null}
isFullscreenScroll={true}
isOpen={false}
size="lg"

View File

@@ -9,7 +9,6 @@ import {
FormattedMessage,
injectIntl,
intlShape,
MessageDescriptor,
} from '@edx/frontend-platform/i18n';
import messages from './messages';
@@ -39,20 +38,20 @@ export const Gallery = ({
}
if (galleryIsEmpty) {
return (
<div className="gallery p-4 bg-gray-100" style={{ height }}>
<div className="gallery p-4 bg-gray-100" style={{ height, margin: '0 -1.5rem' }}>
<FormattedMessage {...emptyGalleryLabel} />
</div>
);
}
if (searchIsEmpty) {
return (
<div className="gallery p-4 bg-gray-100" style={{ height }}>
<div className="gallery p-4 bg-gray-100" style={{ height, margin: '0 -1.5rem' }}>
<FormattedMessage {...messages.emptySearchLabel} />
</div>
);
}
return (
<Scrollable className="gallery bg-gray-100" style={{ height }}>
<Scrollable className="gallery bg-gray-100" style={{ height, margin: '0 -1.5rem' }}>
<div className="p-4">
<SelectableBox.Set
columns={1}
@@ -72,7 +71,6 @@ Gallery.defaultProps = {
highlighted: '',
showIdsOnCards: false,
height: '375px',
emptyGalleryLabel: null,
};
Gallery.propTypes = {
isLoaded: PropTypes.bool.isRequired,
@@ -81,7 +79,7 @@ Gallery.propTypes = {
displayList: PropTypes.arrayOf(PropTypes.object).isRequired,
highlighted: PropTypes.string,
onHighlightChange: PropTypes.func.isRequired,
emptyGalleryLabel: MessageDescriptor,
emptyGalleryLabel: PropTypes.shape({}).isRequired,
showIdsOnCards: PropTypes.bool,
height: PropTypes.string,
// injected

View File

@@ -2,36 +2,53 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
Button,
Icon,
Badge,
Image,
SelectableBox,
} from '@edx/paragon';
import { FormattedMessage, FormattedDate, FormattedTime } from '@edx/frontend-platform/i18n';
import { Link } from '@edx/paragon/icons';
import messages from './messages';
import { formatDuration } from '../../utils';
import LanguageNamesWidget from '../../containers/VideoEditor/components/VideoSettingsModal/components/VideoPreviewWidget/LanguageNamesWidget';
export const GalleryCard = ({
asset,
showId,
}) => (
<SelectableBox className="card bg-white" key={asset.externalUrl} type="radio" value={asset.id}>
<SelectableBox className="card bg-white" key={asset.externalUrl} type="radio" value={asset.id} style={{ padding: '10px 20px' }}>
<div className="card-div d-flex flex-row flex-nowrap">
<Image
style={{ width: '100px', height: '100px' }}
src={asset.externalUrl}
/>
<div className="img-text p-3">
<h3>{asset.displayName}</h3>
{ showId && (
<p>
<Button variant="link" size="inline" onClick={() => { /* TODO */ }}>
<Icon src={Link} /> {asset.id}
</Button>
</p>
<div style={{
position: 'relative',
width: '200px',
height: '100px',
margin: '16px 0 0 0',
}}
>
<Image
style={{ width: '200px', height: '100px' }}
src={asset.externalUrl}
/>
{ asset.status && asset.statusBadgeVariant && (
<Badge variant={asset.statusBadgeVariant} style={{ position: 'absolute', left: '6px', top: '6px' }}>
{asset.status}
</Badge>
)}
<p>
{ asset.duration >= 0 && (
<Badge variant="dark" style={{ position: 'absolute', right: '6px', bottom: '6px' }}>
{formatDuration(asset.duration)}
</Badge>
)}
</div>
<div className="card-text p-3">
<h3>{asset.displayName}</h3>
{ asset.transcripts && (
<div style={{ margin: '0 0 5px 0' }}>
<LanguageNamesWidget
transcripts={asset.transcripts}
/>
</div>
)}
<p style={{ fontSize: '11px' }}>
<FormattedMessage
{...messages.addedDate}
values={{
@@ -45,10 +62,6 @@ export const GalleryCard = ({
</SelectableBox>
);
GalleryCard.defaultProps = {
showId: false,
};
GalleryCard.propTypes = {
asset: PropTypes.shape({
contentType: PropTypes.string,
@@ -62,8 +75,9 @@ GalleryCard.propTypes = {
url: PropTypes.string,
duration: PropTypes.number,
status: PropTypes.string,
statusBadgeVariant: PropTypes.string,
transcripts: PropTypes.array,
}).isRequired,
showId: PropTypes.bool,
};
export default GalleryCard;

View File

@@ -8,7 +8,6 @@ import { Close, Search } from '@edx/paragon/icons';
import {
FormattedMessage,
injectIntl,
MessageDescriptor,
intlShape,
} from '@edx/frontend-platform/i18n';
@@ -109,7 +108,6 @@ SearchSort.defaultProps = {
filterKeys: null,
filterMessages: null,
showSwitch: false,
switchMessage: null,
onSwitchClick: null,
};
@@ -126,7 +124,7 @@ SearchSort.propTypes = {
filterKeys: PropTypes.shape({}),
filterMessages: PropTypes.shape({}),
showSwitch: PropTypes.bool,
switchMessage: MessageDescriptor,
switchMessage: PropTypes.shape({}).isRequired,
onSwitchClick: PropTypes.func,
// injected
intl: intlShape.isRequired,

View File

@@ -6,6 +6,7 @@ exports[`TextEditor Image Gallery component component snapshot: loaded but no im
style={
Object {
"height": "375px",
"margin": "0 -1.5rem",
}
}
>
@@ -19,6 +20,7 @@ exports[`TextEditor Image Gallery component component snapshot: loaded but searc
style={
Object {
"height": "375px",
"margin": "0 -1.5rem",
}
}
>
@@ -36,6 +38,7 @@ exports[`TextEditor Image Gallery component component snapshot: loaded, show gal
style={
Object {
"height": "375px",
"margin": "0 -1.5rem",
}
}
>

View File

@@ -4,27 +4,49 @@ exports[`GalleryCard component snapshot: dateAdded=12345 1`] = `
<SelectableBox
className="card bg-white"
key="props.img.externalUrl"
style={
Object {
"padding": "10px 20px",
}
}
type="radio"
>
<div
className="card-div d-flex flex-row flex-nowrap"
>
<Image
src="props.img.externalUrl"
<div
style={
Object {
"height": "100px",
"width": "100px",
"margin": "16px 0 0 0",
"position": "relative",
"width": "200px",
}
}
/>
>
<Image
src="props.img.externalUrl"
style={
Object {
"height": "100px",
"width": "200px",
}
}
/>
</div>
<div
className="img-text p-3"
className="card-text p-3"
>
<h3>
props.img.displayName
</h3>
<p>
<p
style={
Object {
"fontSize": "11px",
}
}
>
<FormattedMessage
defaultMessage="Added {date} at {time}"
description="File date-added string"

View File

@@ -6,7 +6,6 @@ import { Add } from '@edx/paragon/icons';
import {
FormattedMessage,
injectIntl,
MessageDescriptor,
intlShape,
} from '@edx/frontend-platform/i18n';
@@ -65,6 +64,12 @@ export const SelectionModal = ({
</Button>
)}
title={intl.formatMessage(titleMsg)}
bodyStyle={{ background: '#EBEBEB' }}
headerComponent={(
<div style={{ zIndex: 10000, margin: '18px 0' }}>
<SearchSort {...searchSortProps} />
</div>
)}
>
{/* Error Alerts */}
<FetchErrorAlert isFetchError={isFetchError} message={fetchError} />
@@ -85,8 +90,7 @@ export const SelectionModal = ({
>
<FormattedMessage {...galleryError.message} />
</ErrorAlert>
<Stack gap={3}>
<SearchSort {...searchSortProps} />
<Stack gap={2}>
<Gallery {...galleryPropsValues} />
<FileInput fileInput={fileInput} acceptedFiles={Object.values(acceptedFiles).join()} />
</Stack>
@@ -108,13 +112,13 @@ SelectionModal.propTypes = {
dismiss: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
set: PropTypes.func.isRequired,
message: MessageDescriptor,
message: PropTypes.shape({}).isRequired,
}).isRequired,
inputError: PropTypes.shape({
dismiss: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
set: PropTypes.func.isRequired,
message: MessageDescriptor,
message: PropTypes.shape({}).isRequired,
}).isRequired,
fileInput: PropTypes.shape({
click: PropTypes.func.isRequired,
@@ -125,11 +129,11 @@ SelectionModal.propTypes = {
selectBtnProps: PropTypes.shape({}).isRequired,
acceptedFiles: PropTypes.shape({}).isRequired,
modalMessages: PropTypes.shape({
confirmMsg: MessageDescriptor,
uploadButtonMsg: MessageDescriptor,
titleMsg: MessageDescriptor,
fetchError: MessageDescriptor,
uploadError: MessageDescriptor,
confirmMsg: PropTypes.shape({}).isRequired,
uploadButtonMsg: PropTypes.shape({}).isRequired,
titleMsg: PropTypes.shape({}).isRequired,
fetchError: PropTypes.shape({}).isRequired,
uploadError: PropTypes.shape({}).isRequired,
}).isRequired,
isLoaded: PropTypes.bool.isRequired,
isFetchError: PropTypes.bool.isRequired,

View File

@@ -2,6 +2,7 @@
exports[`SourceCodeModal renders as expected with default behavior 1`] = `
<BaseModal
bodyStyle={null}
close={[MockFunction]}
confirmAction={
<Button
@@ -24,6 +25,7 @@ exports[`SourceCodeModal renders as expected with default behavior 1`] = `
</Button>
}
footerAction={null}
headerComponent={null}
isFullscreenScroll={true}
isOpen={false}
size="xl"

View File

@@ -0,0 +1,18 @@
import * as moment from 'moment-shortformat';
const formatDuration = (duration) => {
const d = moment.duration(duration, 'seconds');
if (d.hours > 0) {
return (
`${d.hours().toString().padStart(2, '0')}:`
+ `${d.minutes().toString().padStart(2, '0')}:`
+ `${d.seconds().toString().padStart(2, '0')}`
);
}
return (
`${d.minutes().toString().padStart(2, '0')}:`
+ `${d.seconds().toString().padStart(2, '0')}`
);
};
export default formatDuration;

View File

@@ -3,3 +3,4 @@ export { default as StrictDict } from './StrictDict';
export { default as keyStore } from './keyStore';
export { default as camelizeKeys } from './camelizeKeys';
export { default as removeItemOnce } from './removeOnce';
export { default as formatDuration } from './formatDuration';

View File

@@ -1,7 +1,7 @@
import Placeholder from './Placeholder';
import messages from './i18n/index';
import EditorPage from './editors/EditorPage';
import SelectorPage from './editors/SelectorPage';
import VideoSelectorPage from './editors/VideoSelectorPage';
export { messages, EditorPage, SelectorPage };
export { messages, EditorPage, VideoSelectorPage };
export default Placeholder;