fix: Used Dropzone instead of having custom component
This PR fixes style component and remove any new component introduced. We introduce a new thumbnail for setting page as well. Signed-off-by: Farhaan Bukhsh <farhaan@opencraft.com>
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -23393,4 +23393,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { selectors } from '../../../../../../data/redux';
|
||||
import thumbnailMessages from '../ThumbnailWidget/messages';
|
||||
import hooks from './hooks';
|
||||
import LanguageNamesWidget from './LanguageNamesWidget';
|
||||
import videoThumbnail from '../../../../../../data/images/videoThumbnail.svg';
|
||||
|
||||
export const VideoPreviewWidget = ({
|
||||
thumbnail,
|
||||
@@ -19,6 +20,7 @@ export const VideoPreviewWidget = ({
|
||||
}) => {
|
||||
const imgRef = React.useRef();
|
||||
const videoType = intl.formatMessage(hooks.getVideoType(videoSource));
|
||||
const thumbnailImage = thumbnail || videoThumbnail;
|
||||
|
||||
return (
|
||||
<Collapsible.Advanced
|
||||
@@ -30,9 +32,9 @@ export const VideoPreviewWidget = ({
|
||||
<div className="d-flex flex-row">
|
||||
<Image
|
||||
thumbnail
|
||||
className="mr-3"
|
||||
className="mr-3 p-4"
|
||||
ref={imgRef}
|
||||
src={thumbnail}
|
||||
src={thumbnailImage}
|
||||
alt={intl.formatMessage(thumbnailMessages.thumbnailAltText)}
|
||||
style={{
|
||||
maxWidth: '200px',
|
||||
|
||||
78
src/editors/containers/VideoUploadEditor/VideoUploader.jsx
Normal file
78
src/editors/containers/VideoUploadEditor/VideoUploader.jsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Icon, IconButton, Dropzone, InputGroup, FormControl,
|
||||
} from '@edx/paragon';
|
||||
import { ArrowForward, FileUpload } from '@edx/paragon/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { thunkActions } from '../../data/redux';
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
|
||||
const URLUploader = () => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<div className="d-flex flex-column flex-wrap">
|
||||
<div className="justify-content-center align-self-center bg-light rounded-circle p-4">
|
||||
<Icon src={FileUpload} className="text-muted" />
|
||||
</div>
|
||||
<div className="d-flex align-self-center justify-content-center flex-wrap flex-column pt-5">
|
||||
<span style={{ fontSize: '1.35rem' }}>{intl.formatMessage(messages.dropVideoFileHere)}</span>
|
||||
<span className="align-self-center" style={{ fontSize: '0.8rem' }}>{intl.formatMessage(messages.info)}</span>
|
||||
</div>
|
||||
<div className="align-self-center justify-content-center mx-2 text-dark">OR</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const VideoUploader = ({ onUpload, setLoading }) => {
|
||||
const [textInputValue, settextInputValue] = React.useState('');
|
||||
const onURLUpload = hooks.onVideoUpload();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleProcessUpload = ({ fileData }) => {
|
||||
dispatch(thunkActions.video.uploadVideo({
|
||||
supportedFiles: [fileData],
|
||||
setLoadSpinner: setLoading,
|
||||
postUploadRedirect: hooks.onVideoUpload(),
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropzone
|
||||
accept={{ 'video/*': ['.mp4', '.mov'] }}
|
||||
onProcessUpload={handleProcessUpload}
|
||||
inputComponent={<URLUploader />}
|
||||
/>
|
||||
<div className="d-flex video-id-prompt">
|
||||
<InputGroup>
|
||||
<FormControl
|
||||
placeholder={intl.formatMessage(messages.pasteURL)}
|
||||
aria-label={intl.formatMessage(messages.pasteURL)}
|
||||
aria-describedby="basic-addon2"
|
||||
borderless
|
||||
onChange={(event) => { settextInputValue(event.target.value); }}
|
||||
/>
|
||||
<div className="justify-content-center align-self-center bg-light rounded-circle p-0 x-small url-submit-button">
|
||||
<IconButton
|
||||
alt={intl.formatMessage(messages.submitButtonAltText)}
|
||||
src={ArrowForward}
|
||||
iconAs={Icon}
|
||||
size="inline"
|
||||
onClick={() => { onURLUpload(textInputValue); }}
|
||||
/>
|
||||
</div>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
VideoUploader.propTypes = {
|
||||
onUpload: PropTypes.func.isRequired,
|
||||
setLoading: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default VideoUploader;
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent, act } from '@testing-library/react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { initializeMockApp } from '@edx/frontend-platform';
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import * as redux from 'react-redux';
|
||||
import * as hooks from './hooks';
|
||||
import { VideoUploader } from './VideoUploader';
|
||||
|
||||
jest.unmock('react-redux');
|
||||
jest.unmock('@edx/frontend-platform/i18n');
|
||||
jest.unmock('@edx/paragon');
|
||||
jest.unmock('@edx/paragon/icons');
|
||||
|
||||
describe('VideoUploader', () => {
|
||||
const setLoadingMock = jest.fn();
|
||||
const onURLUploadMock = jest.fn();
|
||||
let store;
|
||||
|
||||
beforeEach(async () => {
|
||||
store = configureStore({
|
||||
reducer: (state, action) => ((action && action.newState) ? action.newState : state),
|
||||
preloadedState: {
|
||||
app: {
|
||||
learningContextId: 'course-v1:test+test+test',
|
||||
blockId: 'some-block-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'test-user',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const renderComponent = async (storeParam, setLoadingMockParam) => render(
|
||||
<AppProvider store={storeParam}>
|
||||
<IntlProvider locale="en">
|
||||
<VideoUploader setLoading={setLoadingMockParam} />
|
||||
</IntlProvider>,
|
||||
</AppProvider>,
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders as expected with default behavior', async () => {
|
||||
expect(await renderComponent(store, setLoadingMock)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('calls onURLUpload when URL submit button is clicked', async () => {
|
||||
const onVideoUploadSpy = jest.spyOn(hooks, 'onVideoUpload').mockImplementation(() => onURLUploadMock);
|
||||
|
||||
const { getByPlaceholderText, getAllByRole } = await renderComponent(store, setLoadingMock);
|
||||
|
||||
const urlInput = getByPlaceholderText('Paste your video ID or URL');
|
||||
const urlSubmitButton = getAllByRole('button', { name: /submit/i });
|
||||
expect(urlSubmitButton).toHaveLength(1);
|
||||
|
||||
fireEvent.change(urlInput, { target: { value: 'https://example.com/video.mp4' } });
|
||||
urlSubmitButton.forEach((button) => fireEvent.click(button));
|
||||
expect(onURLUploadMock).toHaveBeenCalledWith('https://example.com/video.mp4');
|
||||
|
||||
onVideoUploadSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('calls handleProcessUpload when file is selected', async () => {
|
||||
const useDispatchSpy = jest.spyOn(redux, 'useDispatch');
|
||||
const mockDispatchFn = jest.fn();
|
||||
useDispatchSpy.mockReturnValue(mockDispatchFn);
|
||||
|
||||
const { getByTestId } = await renderComponent(store, setLoadingMock);
|
||||
|
||||
const fileInput = getByTestId('dropzone-container');
|
||||
const file = new File(['file'], 'video.mp4', {
|
||||
type: 'video/mp4',
|
||||
});
|
||||
Object.defineProperty(fileInput, 'files', {
|
||||
value: [file],
|
||||
});
|
||||
await act(async () => fireEvent.drop(fileInput));
|
||||
// Test dispacting thunkAction
|
||||
expect(mockDispatchFn).toHaveBeenCalledWith(expect.any(Function));
|
||||
useDispatchSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,302 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VideoUploader renders as expected with default behavior 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
class="pgn__dropzone"
|
||||
data-testid="dropzone-container"
|
||||
role="presentation"
|
||||
tabindex="0"
|
||||
>
|
||||
<input
|
||||
accept="video/*,.mp4,.mov"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
type="file"
|
||||
/>
|
||||
<div
|
||||
class="d-flex flex-column justify-content-around align-items-center w-100"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column flex-wrap"
|
||||
>
|
||||
<div
|
||||
class="justify-content-center align-self-center bg-light rounded-circle p-4"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 10h4v6h6v-6h4l-7-7-7 7zm0 8v2h14v-2H5z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex align-self-center justify-content-center flex-wrap flex-column pt-5"
|
||||
>
|
||||
<span
|
||||
style="font-size: 1.35rem;"
|
||||
>
|
||||
Drag and drop video here or click to upload
|
||||
</span>
|
||||
<span
|
||||
class="align-self-center"
|
||||
style="font-size: 0.8rem;"
|
||||
>
|
||||
Upload MP4 or MOV files (5 GB max)
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="align-self-center justify-content-center mx-2 text-dark"
|
||||
>
|
||||
OR
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex video-id-prompt"
|
||||
>
|
||||
<div
|
||||
class="input-group"
|
||||
>
|
||||
<div
|
||||
class="pgn__form-control-decorator-group"
|
||||
>
|
||||
<input
|
||||
aria-describedby="basic-addon2"
|
||||
aria-label="Paste your video ID or URL"
|
||||
class="form-control"
|
||||
placeholder="Paste your video ID or URL"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="justify-content-center align-self-center bg-light rounded-circle p-0 x-small url-submit-button"
|
||||
>
|
||||
<button
|
||||
aria-label="Submit"
|
||||
class="btn-icon btn-icon-primary btn-icon-inline"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="btn-icon__icon-container"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon btn-icon__icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12 4-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
,
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div>
|
||||
<div
|
||||
class="pgn__dropzone"
|
||||
data-testid="dropzone-container"
|
||||
role="presentation"
|
||||
tabindex="0"
|
||||
>
|
||||
<input
|
||||
accept="video/*,.mp4,.mov"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
type="file"
|
||||
/>
|
||||
<div
|
||||
class="d-flex flex-column justify-content-around align-items-center w-100"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column flex-wrap"
|
||||
>
|
||||
<div
|
||||
class="justify-content-center align-self-center bg-light rounded-circle p-4"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 10h4v6h6v-6h4l-7-7-7 7zm0 8v2h14v-2H5z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex align-self-center justify-content-center flex-wrap flex-column pt-5"
|
||||
>
|
||||
<span
|
||||
style="font-size: 1.35rem;"
|
||||
>
|
||||
Drag and drop video here or click to upload
|
||||
</span>
|
||||
<span
|
||||
class="align-self-center"
|
||||
style="font-size: 0.8rem;"
|
||||
>
|
||||
Upload MP4 or MOV files (5 GB max)
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="align-self-center justify-content-center mx-2 text-dark"
|
||||
>
|
||||
OR
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex video-id-prompt"
|
||||
>
|
||||
<div
|
||||
class="input-group"
|
||||
>
|
||||
<div
|
||||
class="pgn__form-control-decorator-group"
|
||||
>
|
||||
<input
|
||||
aria-describedby="basic-addon2"
|
||||
aria-label="Paste your video ID or URL"
|
||||
class="form-control"
|
||||
placeholder="Paste your video ID or URL"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="justify-content-center align-self-center bg-light rounded-circle p-0 x-small url-submit-button"
|
||||
>
|
||||
<button
|
||||
aria-label="Submit"
|
||||
class="btn-icon btn-icon-primary btn-icon-inline"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="btn-icon__icon-container"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon btn-icon__icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12 4-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
,
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
@@ -1,227 +1,380 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VideoUploader snapshots renders as expected with default behavior 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="d-flex flex-column justify-content-center align-items-center p-4 w-100 min-vh-100"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onDragEnter={[Function]}
|
||||
onDragLeave={[Function]}
|
||||
onDragOver={[Function]}
|
||||
onDrop={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="presentation"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column justify-content-center align-items-center gap-2 text-center min-vh-100 w-100
|
||||
dropzone-middle "
|
||||
>
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center bg-light rounded-circle file-upload"
|
||||
>
|
||||
<Icon
|
||||
className="text-muted"
|
||||
/>
|
||||
exports[`VideoUploadEditor renders as expected with default behavior 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
class="marked-area"
|
||||
>
|
||||
<div
|
||||
class="d-flex justify-content-end close-button-container"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="btn-icon btn-icon-primary btn-icon-md"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="btn-icon__icon-container"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon btn-icon__icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="pgn__dropzone"
|
||||
data-testid="dropzone-container"
|
||||
role="presentation"
|
||||
tabindex="0"
|
||||
>
|
||||
<input
|
||||
accept="video/*,.mp4,.mov"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
type="file"
|
||||
/>
|
||||
<div
|
||||
class="d-flex flex-column justify-content-around align-items-center w-100"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column flex-wrap"
|
||||
>
|
||||
<div
|
||||
class="justify-content-center align-self-center bg-light rounded-circle p-4"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 10h4v6h6v-6h4l-7-7-7 7zm0 8v2h14v-2H5z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex align-self-center justify-content-center flex-wrap flex-column pt-5"
|
||||
>
|
||||
<span
|
||||
style="font-size: 1.35rem;"
|
||||
>
|
||||
Drag and drop video here or click to upload
|
||||
</span>
|
||||
<span
|
||||
class="align-self-center"
|
||||
style="font-size: 0.8rem;"
|
||||
>
|
||||
Upload MP4 or MOV files (5 GB max)
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="align-self-center justify-content-center mx-2 text-dark"
|
||||
>
|
||||
OR
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex video-id-prompt"
|
||||
>
|
||||
<div
|
||||
class="input-group"
|
||||
>
|
||||
<div
|
||||
class="pgn__form-control-decorator-group"
|
||||
>
|
||||
<input
|
||||
aria-describedby="basic-addon2"
|
||||
aria-label="Paste your video ID or URL"
|
||||
class="form-control"
|
||||
placeholder="Paste your video ID or URL"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="justify-content-center align-self-center bg-light rounded-circle p-0 x-small url-submit-button"
|
||||
>
|
||||
<button
|
||||
aria-label="Submit"
|
||||
class="btn-icon btn-icon-primary btn-icon-inline"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="btn-icon__icon-container"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon btn-icon__icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12 4-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
,
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div>
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center gap-1 flex-wrap flex-column pt-5"
|
||||
class="marked-area"
|
||||
>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "20px",
|
||||
}
|
||||
}
|
||||
<div
|
||||
class="d-flex justify-content-end close-button-container"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Drag and drop video here or click to upload"
|
||||
description="Display message for Drag and Drop zone"
|
||||
id="VideoUploadEditor.dropVideoFileHere"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "12px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Upload MP4 or MOV files (5 GB max)"
|
||||
description="Info message for supported formats"
|
||||
id="VideoUploadEditor.uploadInfo"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex align-items-center mt-3"
|
||||
>
|
||||
<span
|
||||
className="mx-2 text-dark"
|
||||
>
|
||||
OR
|
||||
</span>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="btn-icon btn-icon-primary btn-icon-md"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="btn-icon__icon-container"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon btn-icon__icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="pgn__dropzone"
|
||||
data-testid="dropzone-container"
|
||||
role="presentation"
|
||||
tabindex="0"
|
||||
>
|
||||
<input
|
||||
accept="video/*,.mp4,.mov"
|
||||
style="display: none;"
|
||||
tabindex="-1"
|
||||
type="file"
|
||||
/>
|
||||
<div
|
||||
class="d-flex flex-column justify-content-around align-items-center w-100"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column flex-wrap"
|
||||
>
|
||||
<div
|
||||
class="justify-content-center align-self-center bg-light rounded-circle p-4"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 10h4v6h6v-6h4l-7-7-7 7zm0 8v2h14v-2H5z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex align-self-center justify-content-center flex-wrap flex-column pt-5"
|
||||
>
|
||||
<span
|
||||
style="font-size: 1.35rem;"
|
||||
>
|
||||
Drag and drop video here or click to upload
|
||||
</span>
|
||||
<span
|
||||
class="align-self-center"
|
||||
style="font-size: 0.8rem;"
|
||||
>
|
||||
Upload MP4 or MOV files (5 GB max)
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="align-self-center justify-content-center mx-2 text-dark"
|
||||
>
|
||||
OR
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex video-id-prompt"
|
||||
>
|
||||
<div
|
||||
class="input-group"
|
||||
>
|
||||
<div
|
||||
class="pgn__form-control-decorator-group"
|
||||
>
|
||||
<input
|
||||
aria-describedby="basic-addon2"
|
||||
aria-label="Paste your video ID or URL"
|
||||
class="form-control"
|
||||
placeholder="Paste your video ID or URL"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="justify-content-center align-self-center bg-light rounded-circle p-0 x-small url-submit-button"
|
||||
>
|
||||
<button
|
||||
aria-label="Submit"
|
||||
class="btn-icon btn-icon-primary btn-icon-inline"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="btn-icon__icon-container"
|
||||
>
|
||||
<span
|
||||
class="pgn__icon btn-icon__icon"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="24"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m12 4-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
accept=""
|
||||
data-testid="fileInput"
|
||||
multiple={false}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"display": "none",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex video-id-container"
|
||||
>
|
||||
<div
|
||||
className="d-flex video-id-prompt"
|
||||
>
|
||||
<input
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Paste your video ID or URL"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<button
|
||||
className="border-start-0"
|
||||
data-testid="inputSaveButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<Icon
|
||||
className="rounded-circle text-dark"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VideoUploader snapshots renders as expected with error message 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="d-flex flex-column justify-content-center align-items-center p-4 w-100 min-vh-100"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onDragEnter={[Function]}
|
||||
onDragLeave={[Function]}
|
||||
onDragOver={[Function]}
|
||||
onDrop={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="presentation"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column justify-content-center align-items-center gap-2 text-center min-vh-100 w-100
|
||||
dropzone-middle "
|
||||
>
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center bg-light rounded-circle file-upload"
|
||||
>
|
||||
<Icon
|
||||
className="text-muted"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center gap-1 flex-wrap flex-column pt-5"
|
||||
>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "20px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Drag and drop video here or click to upload"
|
||||
description="Display message for Drag and Drop zone"
|
||||
id="VideoUploadEditor.dropVideoFileHere"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "12px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Upload MP4 or MOV files (5 GB max)"
|
||||
description="Info message for supported formats"
|
||||
id="VideoUploadEditor.uploadInfo"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex align-items-center mt-3"
|
||||
>
|
||||
<span
|
||||
className="mx-2 text-dark"
|
||||
>
|
||||
OR
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
accept=""
|
||||
data-testid="fileInput"
|
||||
multiple={false}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"display": "none",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex video-id-container"
|
||||
>
|
||||
<div
|
||||
className="d-flex video-id-prompt"
|
||||
>
|
||||
<input
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Paste your video ID or URL"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<button
|
||||
className="border-start-0"
|
||||
data-testid="inputSaveButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<Icon
|
||||
className="rounded-circle text-dark"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VideoUploaderEdirtor snapshots renders as expected with default behavior 1`] = `
|
||||
<ContextConsumer>
|
||||
<Component />
|
||||
</ContextConsumer>
|
||||
,
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -4,11 +4,6 @@ import { selectors } from '../../data/redux';
|
||||
import store from '../../data/store';
|
||||
import * as appHooks from '../../hooks';
|
||||
|
||||
const extToMime = {
|
||||
mp4: 'video/mp4',
|
||||
mov: 'video/quicktime',
|
||||
};
|
||||
|
||||
export const {
|
||||
navigateTo,
|
||||
} = appHooks;
|
||||
@@ -17,19 +12,14 @@ export const state = {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
loading: (val) => React.useState(val),
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
errorMessage: (val) => React.useState(val),
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
textInputValue: (val) => React.useState(val),
|
||||
};
|
||||
|
||||
export const uploadEditor = () => {
|
||||
const [loading, setLoading] = module.state.loading(false);
|
||||
const [errorMessage, setErrorMessage] = module.state.errorMessage(null);
|
||||
return {
|
||||
loading,
|
||||
setLoading,
|
||||
errorMessage,
|
||||
setErrorMessage,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -52,30 +42,9 @@ export const onVideoUpload = () => {
|
||||
return module.postUploadRedirect(storeState);
|
||||
};
|
||||
|
||||
const getFileExtension = (filename) => filename.slice(Math.abs(filename.lastIndexOf('.') - 1) + 2);
|
||||
|
||||
export const fileValidator = (setLoading, setErrorMessage, uploadVideo) => (file) => {
|
||||
const supportedFormats = Object.keys(extToMime);
|
||||
const ext = getFileExtension(file.name);
|
||||
const type = extToMime[ext] || '';
|
||||
const newFile = new File([file], file.name, { type });
|
||||
|
||||
if (supportedFormats.includes(ext)) {
|
||||
uploadVideo({
|
||||
supportedFiles: [newFile],
|
||||
setLoadSpinner: setLoading,
|
||||
postUploadRedirect: onVideoUpload(),
|
||||
});
|
||||
} else {
|
||||
const errorMsg = 'Video must be an MP4 or MOV file';
|
||||
setErrorMessage(errorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
postUploadRedirect,
|
||||
uploadEditor,
|
||||
uploader,
|
||||
onVideoUpload,
|
||||
fileValidator,
|
||||
};
|
||||
|
||||
@@ -2,9 +2,6 @@ import * as hooks from './hooks';
|
||||
import { MockUseState } from '../../../testUtils';
|
||||
|
||||
const state = new MockUseState(hooks);
|
||||
const setLoading = jest.fn();
|
||||
const setErrorMessage = jest.fn();
|
||||
const uploadVideo = jest.fn();
|
||||
|
||||
describe('Video Upload Editor hooks', () => {
|
||||
beforeEach(() => {
|
||||
@@ -12,8 +9,6 @@ describe('Video Upload Editor hooks', () => {
|
||||
});
|
||||
describe('state hooks', () => {
|
||||
state.testGetter(state.keys.loading);
|
||||
state.testGetter(state.keys.errorMessage);
|
||||
state.testGetter(state.keys.textInputValue);
|
||||
});
|
||||
describe('using state', () => {
|
||||
beforeEach(() => { state.mock(); });
|
||||
@@ -26,25 +21,7 @@ describe('Video Upload Editor hooks', () => {
|
||||
});
|
||||
it('initialize state with correct values', () => {
|
||||
expect(state.stateVals.loading).toEqual(false);
|
||||
expect(state.stateVals.errorMessage).toEqual(null);
|
||||
expect(state.stateVals.textInputValue).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('File Validation', () => {
|
||||
it('Checks with valid MIME type', () => {
|
||||
const file = new File(['(⌐□_□)'], 'video.mp4', { type: 'video/mp4' });
|
||||
const validator = hooks.fileValidator(setLoading, setErrorMessage, uploadVideo);
|
||||
validator(file);
|
||||
expect(uploadVideo).toHaveBeenCalled();
|
||||
expect(setErrorMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
it('Checks with invalid MIME type', () => {
|
||||
const file = new File(['(⌐□_□)'], 'video.gif', { type: 'video/mp4' });
|
||||
const validator = hooks.fileValidator(setLoading, setErrorMessage, uploadVideo);
|
||||
validator(file);
|
||||
expect(uploadVideo).not.toHaveBeenCalled();
|
||||
expect(setErrorMessage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,113 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Icon, IconButton, Spinner } from '@edx/paragon';
|
||||
import { ArrowForward, Close, FileUpload } from '@edx/paragon/icons';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Icon, IconButton, Spinner,
|
||||
} from '@edx/paragon';
|
||||
import { Close } from '@edx/paragon/icons';
|
||||
import { connect } from 'react-redux';
|
||||
import { thunkActions } from '../../data/redux';
|
||||
import './index.scss';
|
||||
import * as hooks from './hooks';
|
||||
import messages from './messages';
|
||||
import * as editorHooks from '../EditorContainer/hooks';
|
||||
import { VideoUploader } from './VideoUploader';
|
||||
import * as editorHooks from '../EditorContainer/hooks';
|
||||
|
||||
export const VideoUploader = ({ onUpload, errorMessage }) => {
|
||||
const { textInputValue, setTextInputValue } = hooks.uploader();
|
||||
const onURLUpload = hooks.onVideoUpload();
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
accept: 'video/*',
|
||||
multiple: false,
|
||||
onDrop: (acceptedFiles) => {
|
||||
if (acceptedFiles.length > 0) {
|
||||
const uploadfile = acceptedFiles[0];
|
||||
onUpload(uploadfile);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
setTextInputValue(event.target.value);
|
||||
};
|
||||
|
||||
const handleSaveButtonClick = () => {
|
||||
onURLUpload(textInputValue);
|
||||
};
|
||||
|
||||
if (errorMessage) {
|
||||
return (
|
||||
<div className="d-flex flex-column justify-content-center align-items-center text-center error-message">{errorMessage}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="d-flex flex-column justify-content-center align-items-center p-4 w-100 min-vh-100" {...getRootProps()}>
|
||||
<div className={`d-flex flex-column justify-content-center align-items-center gap-2 text-center min-vh-100 w-100
|
||||
dropzone-middle ${isDragActive ? 'active' : ''}`}
|
||||
>
|
||||
<div className="d-flex justify-content-center align-items-center bg-light rounded-circle file-upload">
|
||||
<Icon src={FileUpload} className="text-muted" />
|
||||
</div>
|
||||
<div className="d-flex align-items-center justify-content-center gap-1 flex-wrap flex-column pt-5">
|
||||
<span style={{ fontSize: '20px' }}><FormattedMessage {...messages.dropVideoFileHere} /></span>
|
||||
<span style={{ fontSize: '12px' }}><FormattedMessage {...messages.info} /></span>
|
||||
</div>
|
||||
<div className="d-flex align-items-center mt-3">
|
||||
<span className="mx-2 text-dark">OR</span>
|
||||
</div>
|
||||
</div>
|
||||
<input {...getInputProps()} data-testid="fileInput" />
|
||||
</div>
|
||||
<div className="d-flex video-id-container">
|
||||
<div className="d-flex video-id-prompt">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Paste your video ID or URL"
|
||||
value={textInputValue}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSaveButtonClick()}
|
||||
onClick={(event) => event.preventDefault()}
|
||||
/>
|
||||
<button className="border-start-0" type="button" onClick={handleSaveButtonClick} data-testid="inputSaveButton">
|
||||
<Icon src={ArrowForward} className="rounded-circle text-dark" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
VideoUploader.propTypes = {
|
||||
onUpload: PropTypes.func.isRequired,
|
||||
errorMessage: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
const VideoUploadEditor = (
|
||||
export const VideoUploadEditor = (
|
||||
{
|
||||
intl,
|
||||
onClose,
|
||||
// Redux states
|
||||
uploadVideo,
|
||||
},
|
||||
) => {
|
||||
const {
|
||||
loading,
|
||||
setLoading,
|
||||
errorMessage,
|
||||
setErrorMessage,
|
||||
} = hooks.uploadEditor();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const handleCancel = editorHooks.handleCancel({ onClose });
|
||||
|
||||
const handleDrop = (file) => {
|
||||
if (!file) {
|
||||
console.log('No file selected.');
|
||||
return;
|
||||
}
|
||||
const validator = hooks.fileValidator(setLoading, setErrorMessage, uploadVideo);
|
||||
validator(file);
|
||||
};
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -115,12 +31,13 @@ const VideoUploadEditor = (
|
||||
<div className="marked-area">
|
||||
<div className="d-flex justify-content-end close-button-container">
|
||||
<IconButton
|
||||
alt={intl.formatMessage(messages.closeButtonAltText)}
|
||||
src={Close}
|
||||
iconAs={Icon}
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
</div>
|
||||
<VideoUploader onUpload={handleDrop} errorMessage={errorMessage} intl={intl} />
|
||||
<VideoUploader onUpload={uploadVideo} setLoading={setLoading} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-6">
|
||||
@@ -136,7 +53,6 @@ const VideoUploadEditor = (
|
||||
};
|
||||
|
||||
VideoUploadEditor.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
uploadVideo: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -147,4 +63,4 @@ export const mapDispatchToProps = {
|
||||
uploadVideo: thunkActions.video.uploadVideo,
|
||||
};
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(VideoUploadEditor));
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(VideoUploadEditor);
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
&.active {
|
||||
border: 2px solid #262626; /* change color when active */
|
||||
}
|
||||
}
|
||||
|
||||
.file-upload {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
.pgn__dropzone {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.video-id-container {
|
||||
@@ -16,33 +16,33 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.url-submit-button {
|
||||
position: absolute;
|
||||
left: 85%;
|
||||
}
|
||||
|
||||
.video-id-prompt {
|
||||
position: absolute;
|
||||
top: 68%;
|
||||
top: 65%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 1rem;
|
||||
border: 1px solid #707070;
|
||||
width: 308px;
|
||||
|
||||
input {
|
||||
border: none !important;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: #454545;
|
||||
// color: #5E35B1;
|
||||
font-family: 'Inter';
|
||||
font-weight: 500;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none !important;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #AB0D02;
|
||||
margin-top: 20rem;
|
||||
.prompt-button {
|
||||
background: rgba(239, 234, 247, 0.70);
|
||||
}
|
||||
}
|
||||
|
||||
.close-button-container {
|
||||
|
||||
@@ -1,37 +1,54 @@
|
||||
import React from 'react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import '@testing-library/jest-dom';
|
||||
import { shallow } from 'enzyme';
|
||||
import VideoUploadEditor, { VideoUploader } from '.';
|
||||
import { formatMessage } from '../../../testUtils';
|
||||
import VideoUploadEditor from '.';
|
||||
|
||||
const defaultEditorProps = {
|
||||
onClose: jest.fn().mockName('props.onClose'),
|
||||
intl: { formatMessage },
|
||||
uploadVideo: jest.fn(),
|
||||
};
|
||||
jest.unmock('react-redux');
|
||||
jest.unmock('@edx/frontend-platform/i18n');
|
||||
jest.unmock('@edx/paragon');
|
||||
jest.unmock('@edx/paragon/icons');
|
||||
|
||||
const defaultUploaderProps = {
|
||||
onUpload: jest.fn(),
|
||||
errorMessage: null,
|
||||
intl: { formatMessage },
|
||||
};
|
||||
describe('VideoUploadEditor', () => {
|
||||
const onCloseMock = jest.fn();
|
||||
let store;
|
||||
|
||||
describe('VideoUploader', () => {
|
||||
describe('snapshots', () => {
|
||||
test('renders as expected with default behavior', () => {
|
||||
expect(shallow(<VideoUploader {...defaultUploaderProps} />)).toMatchSnapshot();
|
||||
const renderComponent = async (storeParam, onCloseMockParam) => render(
|
||||
<AppProvider store={storeParam}>
|
||||
<IntlProvider locale="en">
|
||||
<VideoUploadEditor onClose={onCloseMockParam} />
|
||||
</IntlProvider>,
|
||||
</AppProvider>,
|
||||
);
|
||||
|
||||
beforeEach(async () => {
|
||||
store = configureStore({
|
||||
reducer: (state, action) => ((action && action.newState) ? action.newState : state),
|
||||
preloadedState: {},
|
||||
});
|
||||
test('renders as expected with error message', () => {
|
||||
const defaultUploaderPropsWithError = { ...defaultUploaderProps, errorMessages: 'Some Error' };
|
||||
expect(shallow(<VideoUploader {...defaultUploaderPropsWithError} />)).toMatchSnapshot();
|
||||
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'test-user',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('VideoUploaderEdirtor', () => {
|
||||
describe('snapshots', () => {
|
||||
test('renders as expected with default behavior', () => {
|
||||
expect(shallow(<VideoUploadEditor {...defaultEditorProps} />)).toMatchSnapshot();
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders as expected with default behavior', async () => {
|
||||
expect(await renderComponent(store, onCloseMock)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('calls onClose when close button is clicked', async () => {
|
||||
const container = await renderComponent(store, onCloseMock);
|
||||
const closeButton = container.getAllByRole('button', { name: /close/i });
|
||||
expect(closeButton).toHaveLength(1);
|
||||
closeButton.forEach((button) => fireEvent.click(button));
|
||||
expect(onCloseMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,21 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Upload MP4 or MOV files (5 GB max)',
|
||||
description: 'Info message for supported formats',
|
||||
},
|
||||
pasteURL: {
|
||||
id: 'VideoUploadEditor.pasteURL',
|
||||
defaultMessage: 'Paste your video ID or URL',
|
||||
description: 'Paste URL message for video upload',
|
||||
},
|
||||
closeButtonAltText: {
|
||||
id: 'VideoUploadEditor.closeButtonAltText',
|
||||
defaultMessage: 'Close',
|
||||
description: 'Close button alt text',
|
||||
},
|
||||
submitButtonAltText: {
|
||||
id: 'VideoUploadEditor.submitButtonAltText',
|
||||
defaultMessage: 'Submit',
|
||||
description: 'Submit button alt text',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
3
src/editors/data/images/videoThumbnail.svg
Normal file
3
src/editors/data/images/videoThumbnail.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="33" height="33" viewBox="0 0 33 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.4111 10.6847V21.3514H7.0778V10.6847H20.4111ZM21.7445 8.01807H5.74447C5.01113 8.01807 4.41113 8.61807 4.41113 9.3514V22.6847C4.41113 23.4181 5.01113 24.0181 5.74447 24.0181H21.7445C22.4778 24.0181 23.0778 23.4181 23.0778 22.6847V18.0181L28.4111 23.3514V8.68473L23.0778 14.0181V9.3514C23.0778 8.61807 22.4778 8.01807 21.7445 8.01807Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 464 B |
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable import/no-cycle */
|
||||
import _ from 'lodash-es';
|
||||
import { actions, selectors } from '..';
|
||||
import { removeItemOnce } from '../../../utils';
|
||||
import * as requests from './requests';
|
||||
@@ -377,9 +378,10 @@ export const uploadVideo = ({ supportedFiles, setLoadSpinner, postUploadRedirect
|
||||
const data = { files: [] };
|
||||
setLoadSpinner(true);
|
||||
supportedFiles.forEach((file) => {
|
||||
const fileData = file.get('file');
|
||||
data.files.push({
|
||||
file_name: file.name,
|
||||
content_type: file.type,
|
||||
file_name: fileData.name,
|
||||
content_type: fileData.type,
|
||||
});
|
||||
});
|
||||
dispatch(requests.uploadVideo({
|
||||
@@ -390,13 +392,13 @@ export const uploadVideo = ({ supportedFiles, setLoadSpinner, postUploadRedirect
|
||||
const fileName = fileObj.file_name;
|
||||
const edxVideoId = fileObj.edx_video_id;
|
||||
const uploadUrl = fileObj.upload_url;
|
||||
const uploadFile = supportedFiles.find((file) => file.name === fileName);
|
||||
const uploadFile = supportedFiles.find((file) => file.get('file').name === fileName);
|
||||
if (!uploadFile) {
|
||||
console.error(`Could not find file object with name "${fileName}" in supportedFiles array.`);
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('uploaded-file', uploadFile);
|
||||
formData.append('uploaded-file', uploadFile.get('file'));
|
||||
await fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: formData,
|
||||
|
||||
@@ -676,10 +676,9 @@ describe('uploadVideo', () => {
|
||||
let setLoadSpinner;
|
||||
let postUploadRedirect;
|
||||
let dispatchedAction;
|
||||
const supportedFiles = [
|
||||
new File(['content1'], 'file1.mp4', { type: 'video/mp4' }),
|
||||
new File(['content2'], 'file2.mov', { type: 'video/quicktime' }),
|
||||
];
|
||||
const fileData = new FormData();
|
||||
fileData.append('file', new File(['content1'], 'file1.mp4', { type: 'video/mp4' }));
|
||||
const supportedFiles = [fileData];
|
||||
|
||||
beforeEach(() => {
|
||||
dispatch = jest.fn((action) => ({ dispatch: action }));
|
||||
@@ -693,7 +692,6 @@ describe('uploadVideo', () => {
|
||||
const data = {
|
||||
files: [
|
||||
{ file_name: 'file1.mp4', content_type: 'video/mp4' },
|
||||
{ file_name: 'file2.mov', content_type: 'video/quicktime' },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -711,7 +709,6 @@ describe('uploadVideo', () => {
|
||||
const response = {
|
||||
files: [
|
||||
{ file_name: 'file1.mp4', upload_url: 'http://example.com/put_video1' },
|
||||
{ file_name: 'file2.mov', upload_url: 'http://example.com/put_video2' },
|
||||
],
|
||||
};
|
||||
const mockRequestResponse = { data: response };
|
||||
@@ -720,12 +717,13 @@ describe('uploadVideo', () => {
|
||||
|
||||
dispatchedAction.uploadVideo.onSuccess(mockRequestResponse);
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
response.files.forEach(({ upload_url: uploadUrl }, index) => {
|
||||
expect(fetch.mock.calls[index][0]).toEqual(uploadUrl);
|
||||
});
|
||||
supportedFiles.forEach((file, index) => {
|
||||
expect(fetch.mock.calls[index][1].body.get('uploaded-file')).toBe(file);
|
||||
const fileDataTest = file.get('file');
|
||||
expect(fetch.mock.calls[index][1].body.get('uploaded-file')).toBe(fileDataTest);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -741,7 +739,7 @@ describe('uploadVideo', () => {
|
||||
const mockRequestResponse = { data: response };
|
||||
const spyConsoleError = jest.spyOn(console, 'error');
|
||||
|
||||
thunkActions.uploadVideo({ supportedFiles: [supportedFiles[0]], setLoadSpinner, postUploadRedirect })(dispatch);
|
||||
thunkActions.uploadVideo({ supportedFiles, setLoadSpinner, postUploadRedirect })(dispatch);
|
||||
dispatchedAction.uploadVideo.onSuccess(mockRequestResponse);
|
||||
expect(spyConsoleError).toHaveBeenCalledWith('Could not find file object with name "file2.gif" in supportedFiles array.');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user