refactor: files-and-videos folder (#672)
This commit is contained in:
@@ -18,10 +18,10 @@ import {
|
||||
} from '@edx/paragon';
|
||||
import { ContentCopy, InfoOutline } from '@edx/paragon/icons';
|
||||
|
||||
import { getFileSizeToClosestByte } from '../data/utils';
|
||||
import { getFileSizeToClosestByte } from '../generic/utils';
|
||||
import messages from './messages';
|
||||
|
||||
const FileInfoAssetSidebar = ({
|
||||
const FileInfoModalSidebar = ({
|
||||
asset,
|
||||
handleLockedAsset,
|
||||
// injected
|
||||
@@ -109,7 +109,7 @@ const FileInfoAssetSidebar = ({
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
FileInfoAssetSidebar.propTypes = {
|
||||
FileInfoModalSidebar.propTypes = {
|
||||
asset: PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
wrapperType: PropTypes.string.isRequired,
|
||||
@@ -127,4 +127,4 @@ FileInfoAssetSidebar.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(FileInfoAssetSidebar);
|
||||
export default injectIntl(FileInfoModalSidebar);
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
Icon,
|
||||
Image,
|
||||
} from '@edx/paragon';
|
||||
import { getSrc } from '../data/utils';
|
||||
import { getSrc } from './data/utils';
|
||||
import messages from './messages';
|
||||
|
||||
const AssetThumbnail = ({
|
||||
const FileThumbnail = ({
|
||||
thumbnail,
|
||||
wrapperType,
|
||||
externalUrl,
|
||||
@@ -50,13 +50,13 @@ const AssetThumbnail = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
AssetThumbnail.defaultProps = {
|
||||
FileThumbnail.defaultProps = {
|
||||
thumbnail: null,
|
||||
wrapperType: null,
|
||||
externalUrl: null,
|
||||
displayName: null,
|
||||
};
|
||||
AssetThumbnail.propTypes = {
|
||||
FileThumbnail.propTypes = {
|
||||
thumbnail: PropTypes.string,
|
||||
wrapperType: PropTypes.string,
|
||||
externalUrl: PropTypes.string,
|
||||
@@ -69,4 +69,4 @@ AssetThumbnail.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(AssetThumbnail);
|
||||
export default injectIntl(FileThumbnail);
|
||||
@@ -16,19 +16,22 @@ import {
|
||||
getUsagePaths,
|
||||
resetErrors,
|
||||
updateAssetOrder,
|
||||
} from '../data/thunks';
|
||||
} from './data/thunks';
|
||||
import messages from './messages';
|
||||
import FilesAndUploadsProvider from './FilesAndUploadsProvider';
|
||||
import FilesPageProvider from './FilesPageProvider';
|
||||
import getPageHeadTitle from '../../generic/utils';
|
||||
import FileTable from '../FileTable';
|
||||
import EditFileErrors from '../EditFileErrors';
|
||||
import { getFileSizeToClosestByte } from '../data/utils';
|
||||
import ThumbnailColumn from '../table-components/table-custom-columns/ThumbnailColumn';
|
||||
import ActiveColumn from '../table-components/table-custom-columns/ActiveColumn';
|
||||
import AccessColumn from '../table-components/table-custom-columns/AccessColumn';
|
||||
import AssetThumbnail from './AssetThumbnail';
|
||||
import {
|
||||
AccessColumn,
|
||||
ActiveColumn,
|
||||
EditFileErrors,
|
||||
FileTable,
|
||||
ThumbnailColumn,
|
||||
} from '../generic';
|
||||
import { getFileSizeToClosestByte } from '../generic/utils';
|
||||
import FileThumbnail from './FileThumbnail';
|
||||
import FileInfoModalSidebar from './FileInfoModalSidebar';
|
||||
|
||||
const FilesAndUploads = ({
|
||||
const FilesPage = ({
|
||||
courseId,
|
||||
// injected
|
||||
intl,
|
||||
@@ -52,17 +55,24 @@ const FilesAndUploads = ({
|
||||
errors: errorMessages,
|
||||
} = useSelector(state => state.assets);
|
||||
|
||||
const handleErrorReset = (error) => dispatch(resetErrors(error));
|
||||
const handleAddFile = (file) => dispatch(addAssetFile(courseId, file, totalCount));
|
||||
const handleDeleteFile = (id) => dispatch(deleteAssetFile(courseId, id, totalCount));
|
||||
const handleDownloadFile = (selectedRows) => dispatch(fetchAssetDownload({ selectedRows, courseId }));
|
||||
const handleLockFile = ({ fileId, locked }) => dispatch(updateAssetLock({ courseId, assetId: fileId, locked }));
|
||||
const handleLockFile = (fileId, locked) => {
|
||||
handleErrorReset({ errorType: 'lock' });
|
||||
dispatch(updateAssetLock({ courseId, assetId: fileId, locked }));
|
||||
};
|
||||
const handleUsagePaths = (asset) => dispatch(getUsagePaths({ asset, courseId }));
|
||||
const handleErrorReset = (error) => dispatch(resetErrors(error));
|
||||
const handleFileOrder = ({ newFileIdOrder, sortType }) => {
|
||||
dispatch(updateAssetOrder(courseId, newFileIdOrder, sortType));
|
||||
};
|
||||
|
||||
const thumbnailPreview = (props) => AssetThumbnail(props);
|
||||
const thumbnailPreview = (props) => FileThumbnail(props);
|
||||
const infoModalSidebar = (asset) => FileInfoModalSidebar({
|
||||
asset,
|
||||
handleLockedAsset: handleLockFile,
|
||||
});
|
||||
|
||||
const assets = useModels('assets', assetIds);
|
||||
const data = {
|
||||
@@ -141,7 +151,7 @@ const FilesAndUploads = ({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FilesAndUploadsProvider courseId={courseId}>
|
||||
<FilesPageProvider courseId={courseId}>
|
||||
<main>
|
||||
<div className="p-4">
|
||||
<EditFileErrors
|
||||
@@ -169,18 +179,19 @@ const FilesAndUploads = ({
|
||||
tableColumns,
|
||||
maxFileSize,
|
||||
thumbnailPreview,
|
||||
infoModalSidebar,
|
||||
files: assets,
|
||||
}}
|
||||
/>
|
||||
</main>
|
||||
</FilesAndUploadsProvider>
|
||||
</FilesPageProvider>
|
||||
);
|
||||
};
|
||||
|
||||
FilesAndUploads.propTypes = {
|
||||
FilesPage.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(FilesAndUploads);
|
||||
export default injectIntl(FilesPage);
|
||||
@@ -19,7 +19,7 @@ import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import initializeStore from '../../store';
|
||||
import { executeThunk } from '../../utils';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import FilesAndUploads from './FilesAndUploads';
|
||||
import FilesPage from './FilesPage';
|
||||
import {
|
||||
generateFetchAssetApiResponse,
|
||||
generateEmptyApiResponse,
|
||||
@@ -35,9 +35,9 @@ import {
|
||||
deleteAssetFile,
|
||||
updateAssetLock,
|
||||
getUsagePaths,
|
||||
} from '../data/thunks';
|
||||
import { getAssetsUrl } from '../data/api';
|
||||
import messages from '../messages';
|
||||
} from './data/thunks';
|
||||
import { getAssetsUrl } from './data/api';
|
||||
import messages from '../generic/messages';
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
@@ -49,7 +49,7 @@ const renderComponent = () => {
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<AppProvider store={store}>
|
||||
<FilesAndUploads courseId={courseId} />
|
||||
<FilesPage courseId={courseId} />
|
||||
</AppProvider>
|
||||
</IntlProvider>,
|
||||
);
|
||||
@@ -1,25 +1,25 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const FilesAndUploadsContext = React.createContext({});
|
||||
export const FilesPageContext = React.createContext({});
|
||||
|
||||
const FilesAndUploadsProvider = ({ courseId, children }) => {
|
||||
const FilesPageProvider = ({ courseId, children }) => {
|
||||
const contextValue = useMemo(() => ({
|
||||
courseId,
|
||||
path: `/course/${courseId}/assets`,
|
||||
}), []);
|
||||
return (
|
||||
<FilesAndUploadsContext.Provider
|
||||
<FilesPageContext.Provider
|
||||
value={contextValue}
|
||||
>
|
||||
{children}
|
||||
</FilesAndUploadsContext.Provider>
|
||||
</FilesPageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
FilesAndUploadsProvider.propTypes = {
|
||||
FilesPageProvider.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default FilesAndUploadsProvider;
|
||||
export default FilesPageProvider;
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { RequestStatus } from '../../../data/constants';
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'assets',
|
||||
@@ -1,11 +1,11 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { RequestStatus } from '../../../data/constants';
|
||||
import {
|
||||
addModel,
|
||||
addModels,
|
||||
removeModel,
|
||||
updateModel,
|
||||
} from '../../generic/model-store';
|
||||
} from '../../../generic/model-store';
|
||||
import {
|
||||
getAssets,
|
||||
getAssetUsagePaths,
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
AudioFile,
|
||||
} from '@edx/paragon/icons';
|
||||
import { ensureConfig, getConfig } from '@edx/frontend-platform';
|
||||
import FILES_AND_UPLOAD_TYPE_FILTERS from './constant';
|
||||
import FILES_AND_UPLOAD_TYPE_FILTERS from '../../generic/constants';
|
||||
|
||||
ensureConfig([
|
||||
'STUDIO_BASE_URL',
|
||||
@@ -54,55 +54,3 @@ export const getSrc = ({ thumbnail, wrapperType, externalUrl }) => {
|
||||
return InsertDriveFile;
|
||||
}
|
||||
};
|
||||
|
||||
export const getFileSizeToClosestByte = (fileSize, numberOfDivides = 0) => {
|
||||
if (fileSize > 1000) {
|
||||
const updatedSize = fileSize / 1000;
|
||||
const incrementNumberOfDivides = numberOfDivides + 1;
|
||||
return getFileSizeToClosestByte(updatedSize, incrementNumberOfDivides);
|
||||
}
|
||||
const fileSizeFixedDecimal = Number.parseFloat(fileSize).toFixed(2);
|
||||
switch (numberOfDivides) {
|
||||
case 1:
|
||||
return `${fileSizeFixedDecimal} KB`;
|
||||
case 2:
|
||||
return `${fileSizeFixedDecimal} MB`;
|
||||
case 3:
|
||||
return `${fileSizeFixedDecimal} GB`;
|
||||
default:
|
||||
return `${fileSizeFixedDecimal} B`;
|
||||
}
|
||||
};
|
||||
|
||||
export const sortFiles = (files, sortType) => {
|
||||
const [sort, direction] = sortType.split(',');
|
||||
let sortedFiles;
|
||||
if (sort === 'displayName') {
|
||||
sortedFiles = files.sort((f1, f2) => {
|
||||
const lowerCaseF1 = f1[sort].toLowerCase();
|
||||
const lowerCaseF2 = f2[sort].toLowerCase();
|
||||
if (lowerCaseF1 < lowerCaseF2) {
|
||||
return 1;
|
||||
}
|
||||
if (lowerCaseF1 > lowerCaseF2) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
} else {
|
||||
sortedFiles = files.sort((f1, f2) => {
|
||||
if (f1[sort] < f2[sort]) {
|
||||
return 1;
|
||||
}
|
||||
if (f1[sort] > f2[sort]) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
const sortedIds = sortedFiles.map(file => file.id);
|
||||
if (direction === 'asc') {
|
||||
return sortedIds.reverse();
|
||||
}
|
||||
return sortedIds;
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import FilesAndUploads from './FilesAndUploads';
|
||||
import FilesPage from './FilesPage';
|
||||
import FileInfoModalSidebar from './FileInfoModalSidebar';
|
||||
|
||||
export default FilesAndUploads;
|
||||
export default FilesPage;
|
||||
export { FileInfoModalSidebar };
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { ErrorAlert } from '@edx/frontend-lib-content-components';
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const EditFileErrors = ({
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getSupportedFormats } from './videos-page/data/utils';
|
||||
import { getSupportedFormats } from '../videos-page/data/utils';
|
||||
|
||||
export const useFileInput = ({
|
||||
onAddFile,
|
||||
@@ -13,11 +13,11 @@ import {
|
||||
Button,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import { sortFiles } from './data/utils';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { sortFiles } from './utils';
|
||||
import messages from './messages';
|
||||
|
||||
import FileInfo from './FileInfo';
|
||||
import InfoModal from './InfoModal';
|
||||
import FileInput, { useFileInput } from './FileInput';
|
||||
import {
|
||||
GalleryCard,
|
||||
@@ -40,6 +40,7 @@ const FileTable = ({
|
||||
tableColumns,
|
||||
maxFileSize,
|
||||
thumbnailPreview,
|
||||
infoModalSidebar,
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
@@ -112,11 +113,6 @@ const FileTable = ({
|
||||
handleDownloadFile(selectedFlatRows);
|
||||
}, []);
|
||||
|
||||
const handleLockedFile = (fileId, locked) => {
|
||||
handleErrorReset({ errorType: 'lock' });
|
||||
handleLockFile({ fileId, locked });
|
||||
};
|
||||
|
||||
const handleOpenDeleteConfirmation = (selectedFlatRows) => {
|
||||
setSelectedRows(selectedFlatRows);
|
||||
openDeleteConfirmation();
|
||||
@@ -146,7 +142,7 @@ const FileTable = ({
|
||||
const fileCard = ({ className, original }) => (
|
||||
<GalleryCard
|
||||
{...{
|
||||
handleLockedFile,
|
||||
handleLockFile,
|
||||
handleBulkDownload,
|
||||
handleOpenDeleteConfirmation,
|
||||
handleOpenFileInfo,
|
||||
@@ -162,7 +158,7 @@ const FileTable = ({
|
||||
Header: '',
|
||||
Cell: ({ row }) => MoreInfoColumn({
|
||||
row,
|
||||
handleLock: handleLockedFile,
|
||||
handleLock: handleLockFile,
|
||||
handleBulkDownload,
|
||||
handleOpenFileInfo,
|
||||
handleOpenDeleteConfirmation,
|
||||
@@ -238,14 +234,14 @@ const FileTable = ({
|
||||
</DataTable>
|
||||
<FileInput key="generic-file-upload" fileInput={fileInputControl} supportedFileFormats={supportedFileFormats} />
|
||||
{!isEmpty(selectedRows) && (
|
||||
<FileInfo
|
||||
<InfoModal
|
||||
file={selectedRows[0].original}
|
||||
onClose={closeAssetinfo}
|
||||
isOpen={isAssetInfoOpen}
|
||||
handleLockedFile={handleLockedFile}
|
||||
thumbnailPreview={thumbnailPreview}
|
||||
usagePathStatus={usagePathStatus}
|
||||
error={usageErrorMessages}
|
||||
sidebar={infoModalSidebar}
|
||||
/>
|
||||
)}
|
||||
<AlertModal
|
||||
@@ -294,6 +290,7 @@ FileTable.propTypes = {
|
||||
})).isRequired,
|
||||
maxFileSize: PropTypes.number.isRequired,
|
||||
thumbnailPreview: PropTypes.func.isRequired,
|
||||
infoModalSidebar: PropTypes.func.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
@@ -12,18 +12,16 @@ import {
|
||||
|
||||
import messages from './messages';
|
||||
import UsageMetricsMessages from './UsageMetricsMessage';
|
||||
import FileInfoAssetSidebar from './files-page/FileInfoAssetSidebar';
|
||||
import FileInfoVideoSidebar from './videos-page/info-sidebar/FileInfoVideoSidebar';
|
||||
import FileThumbnail from './FileThumbnail';
|
||||
import FileThumbnail from './ThumbnailPreview';
|
||||
|
||||
const FileInfo = ({
|
||||
const InfoModal = ({
|
||||
file,
|
||||
isOpen,
|
||||
onClose,
|
||||
handleLockedFile,
|
||||
thumbnailPreview,
|
||||
usagePathStatus,
|
||||
error,
|
||||
sidebar,
|
||||
}) => (
|
||||
<ModalDialog
|
||||
title={file?.displayName}
|
||||
@@ -58,11 +56,7 @@ const FileInfo = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="col-5">
|
||||
{file?.wrapperType === 'video' ? (
|
||||
<FileInfoVideoSidebar video={file} />
|
||||
) : (
|
||||
<FileInfoAssetSidebar asset={file} handleLockedAsset={handleLockedFile} />
|
||||
)}
|
||||
{sidebar(file)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row m-0 pt-3 font-weight-bold">
|
||||
@@ -73,7 +67,7 @@ const FileInfo = ({
|
||||
</ModalDialog>
|
||||
);
|
||||
|
||||
FileInfo.propTypes = {
|
||||
InfoModal.propTypes = {
|
||||
file: PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
wrapperType: PropTypes.string.isRequired,
|
||||
@@ -89,14 +83,14 @@ FileInfo.propTypes = {
|
||||
}),
|
||||
onClose: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
handleLockedFile: PropTypes.func.isRequired,
|
||||
usagePathStatus: PropTypes.string.isRequired,
|
||||
error: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
thumbnailPreview: PropTypes.func.isRequired,
|
||||
sidebar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
FileInfo.defaultProps = {
|
||||
InfoModal.defaultProps = {
|
||||
file: null,
|
||||
};
|
||||
|
||||
export default injectIntl(FileInfo);
|
||||
export default injectIntl(InfoModal);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const FileThumbnail = ({
|
||||
const ThumbnailPreview = ({
|
||||
thumbnail,
|
||||
wrapperType,
|
||||
externalUrl,
|
||||
@@ -23,7 +23,7 @@ const FileThumbnail = ({
|
||||
})}
|
||||
</>
|
||||
);
|
||||
FileThumbnail.defaultProps = {
|
||||
ThumbnailPreview.defaultProps = {
|
||||
thumbnail: null,
|
||||
wrapperType: null,
|
||||
externalUrl: null,
|
||||
@@ -31,7 +31,7 @@ FileThumbnail.defaultProps = {
|
||||
id: null,
|
||||
status: null,
|
||||
};
|
||||
FileThumbnail.propTypes = {
|
||||
ThumbnailPreview.propTypes = {
|
||||
thumbnail: PropTypes.string,
|
||||
wrapperType: PropTypes.string,
|
||||
externalUrl: PropTypes.string,
|
||||
@@ -45,4 +45,4 @@ FileThumbnail.propTypes = {
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default FileThumbnail;
|
||||
export default ThumbnailPreview;
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { Icon, Row, Spinner } from '@edx/paragon';
|
||||
import { ErrorOutline } from '@edx/paragon/icons';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { RequestStatus } from '../data/constants';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const UsageMetricsMessage = ({
|
||||
24
src/files-and-videos/generic/index.js
Normal file
24
src/files-and-videos/generic/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
TableActions,
|
||||
GalleryCard,
|
||||
AccessColumn,
|
||||
ActiveColumn,
|
||||
MoreInfoColumn,
|
||||
StatusColumn,
|
||||
ThumbnailColumn,
|
||||
} from './table-components';
|
||||
import FileInput, { useFileInput } from './FileInput';
|
||||
|
||||
export {
|
||||
TableActions,
|
||||
GalleryCard,
|
||||
AccessColumn,
|
||||
ActiveColumn,
|
||||
MoreInfoColumn,
|
||||
StatusColumn,
|
||||
ThumbnailColumn,
|
||||
FileInput,
|
||||
useFileInput,
|
||||
};
|
||||
export { default as FileTable } from './FileTable';
|
||||
export { default as EditFileErrors } from './EditFileErrors';
|
||||
@@ -9,20 +9,20 @@ import {
|
||||
} from '@edx/paragon';
|
||||
import { ClosedCaption } from '@edx/paragon/icons';
|
||||
import FileMenu from '../FileMenu';
|
||||
import FileThumbnail from '../FileThumbnail';
|
||||
import FileThumbnail from '../ThumbnailPreview';
|
||||
|
||||
const GalleryCard = ({
|
||||
className,
|
||||
original,
|
||||
handleBulkDownload,
|
||||
handleLockedFile,
|
||||
handleLockFile,
|
||||
handleOpenDeleteConfirmation,
|
||||
handleOpenFileInfo,
|
||||
thumbnailPreview,
|
||||
}) => {
|
||||
const lockFile = () => {
|
||||
const { locked, id } = original;
|
||||
handleLockedFile(id, !locked);
|
||||
handleLockFile(id, !locked);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -99,7 +99,7 @@ GalleryCard.propTypes = {
|
||||
downloadLink: PropTypes.string,
|
||||
}).isRequired,
|
||||
handleBulkDownload: PropTypes.func.isRequired,
|
||||
handleLockedFile: PropTypes.func.isRequired,
|
||||
handleLockFile: PropTypes.func.isRequired,
|
||||
handleOpenDeleteConfirmation: PropTypes.func.isRequired,
|
||||
handleOpenFileInfo: PropTypes.func.isRequired,
|
||||
thumbnailPreview: PropTypes.func.isRequired,
|
||||
19
src/files-and-videos/generic/table-components/index.js
Normal file
19
src/files-and-videos/generic/table-components/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import GalleryCard from './GalleryCard';
|
||||
import TableActions from './TableActions';
|
||||
import {
|
||||
AccessColumn,
|
||||
ActiveColumn,
|
||||
MoreInfoColumn,
|
||||
StatusColumn,
|
||||
ThumbnailColumn,
|
||||
} from './table-custom-columns';
|
||||
|
||||
export {
|
||||
TableActions,
|
||||
GalleryCard,
|
||||
AccessColumn,
|
||||
ActiveColumn,
|
||||
MoreInfoColumn,
|
||||
StatusColumn,
|
||||
ThumbnailColumn,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import FileThumbnail from '../../FileThumbnail';
|
||||
import ThumbnailPreview from '../../ThumbnailPreview';
|
||||
|
||||
const ThumbnailColumn = ({ row, thumbnailPreview }) => {
|
||||
const {
|
||||
@@ -12,7 +12,7 @@ const ThumbnailColumn = ({ row, thumbnailPreview }) => {
|
||||
status,
|
||||
} = row.original;
|
||||
return (
|
||||
<FileThumbnail
|
||||
<ThumbnailPreview
|
||||
{...{
|
||||
thumbnail,
|
||||
wrapperType,
|
||||
51
src/files-and-videos/generic/utils.js
Normal file
51
src/files-and-videos/generic/utils.js
Normal file
@@ -0,0 +1,51 @@
|
||||
export const getFileSizeToClosestByte = (fileSize, numberOfDivides = 0) => {
|
||||
if (fileSize > 1000) {
|
||||
const updatedSize = fileSize / 1000;
|
||||
const incrementNumberOfDivides = numberOfDivides + 1;
|
||||
return getFileSizeToClosestByte(updatedSize, incrementNumberOfDivides);
|
||||
}
|
||||
const fileSizeFixedDecimal = Number.parseFloat(fileSize).toFixed(2);
|
||||
switch (numberOfDivides) {
|
||||
case 1:
|
||||
return `${fileSizeFixedDecimal} KB`;
|
||||
case 2:
|
||||
return `${fileSizeFixedDecimal} MB`;
|
||||
case 3:
|
||||
return `${fileSizeFixedDecimal} GB`;
|
||||
default:
|
||||
return `${fileSizeFixedDecimal} B`;
|
||||
}
|
||||
};
|
||||
|
||||
export const sortFiles = (files, sortType) => {
|
||||
const [sort, direction] = sortType.split(',');
|
||||
let sortedFiles;
|
||||
if (sort === 'displayName') {
|
||||
sortedFiles = files.sort((f1, f2) => {
|
||||
const lowerCaseF1 = f1[sort].toLowerCase();
|
||||
const lowerCaseF2 = f2[sort].toLowerCase();
|
||||
if (lowerCaseF1 < lowerCaseF2) {
|
||||
return 1;
|
||||
}
|
||||
if (lowerCaseF1 > lowerCaseF2) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
} else {
|
||||
sortedFiles = files.sort((f1, f2) => {
|
||||
if (f1[sort] < f2[sort]) {
|
||||
return 1;
|
||||
}
|
||||
if (f1[sort] > f2[sort]) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
const sortedIds = sortedFiles.map(file => file.id);
|
||||
if (direction === 'asc') {
|
||||
return sortedIds.reverse();
|
||||
}
|
||||
return sortedIds;
|
||||
};
|
||||
@@ -1,3 +1,3 @@
|
||||
@import "files-and-videos/videos-page/transcript-settings/TranscriptSettings";
|
||||
@import "files-and-videos/videos-page/VideoThumbnail";
|
||||
@import "files-and-videos/table-components/GalleryCard"
|
||||
@import "files-and-videos/generic/table-components/GalleryCard"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import GalleryCard from './GalleryCard';
|
||||
import TableActions from './TableActions';
|
||||
|
||||
export {
|
||||
TableActions,
|
||||
GalleryCard,
|
||||
};
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Icon,
|
||||
Image,
|
||||
} from '@edx/paragon';
|
||||
import FileInput, { useFileInput } from '../FileInput';
|
||||
import { FileInput, useFileInput } from '../generic';
|
||||
import messages from './messages';
|
||||
|
||||
const VideoThumbnail = ({
|
||||
|
||||
@@ -26,19 +26,22 @@ import {
|
||||
updateVideoOrder,
|
||||
} from './data/thunks';
|
||||
import messages from './messages';
|
||||
import VideosProvider from './VideosProvider';
|
||||
import VideosPageProvider from './VideosPageProvider';
|
||||
import getPageHeadTitle from '../../generic/utils';
|
||||
import FileTable from '../FileTable';
|
||||
import EditFileErrors from '../EditFileErrors';
|
||||
import ThumbnailColumn from '../table-components/table-custom-columns/ThumbnailColumn';
|
||||
import ActiveColumn from '../table-components/table-custom-columns/ActiveColumn';
|
||||
import StatusColumn from '../table-components/table-custom-columns/StatusColumn';
|
||||
import {
|
||||
ActiveColumn,
|
||||
EditFileErrors,
|
||||
FileTable,
|
||||
StatusColumn,
|
||||
ThumbnailColumn,
|
||||
} from '../generic';
|
||||
import TranscriptSettings from './transcript-settings';
|
||||
import VideoThumbnail from './VideoThumbnail';
|
||||
import { getFormattedDuration, resampleFile } from './data/utils';
|
||||
import FILES_AND_UPLOAD_TYPE_FILTERS from '../data/constant';
|
||||
import FILES_AND_UPLOAD_TYPE_FILTERS from '../generic/constants';
|
||||
import VideoInfoModalSidebar from './info-sidebar';
|
||||
|
||||
const Videos = ({
|
||||
const VideosPage = ({
|
||||
courseId,
|
||||
// injected
|
||||
intl,
|
||||
@@ -103,6 +106,7 @@ const Videos = ({
|
||||
usageErrorMessages: errorMessages.usageMetrics,
|
||||
};
|
||||
const thumbnailPreview = (props) => VideoThumbnail({ ...props, handleAddThumbnail, videoImageSettings });
|
||||
const infoModalSidebar = (video) => VideoInfoModalSidebar({ video });
|
||||
const maxFileSize = videoUploadMaxFileSize * 1073741824;
|
||||
const transcriptColumn = {
|
||||
id: 'transcripts',
|
||||
@@ -156,7 +160,7 @@ const Videos = ({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<VideosProvider courseId={courseId}>
|
||||
<VideosPageProvider courseId={courseId}>
|
||||
<main>
|
||||
<div className="p-4">
|
||||
<EditFileErrors
|
||||
@@ -210,18 +214,19 @@ const Videos = ({
|
||||
tableColumns,
|
||||
maxFileSize,
|
||||
thumbnailPreview,
|
||||
infoModalSidebar,
|
||||
files: videos,
|
||||
}}
|
||||
/>
|
||||
</main>
|
||||
</VideosProvider>
|
||||
</VideosPageProvider>
|
||||
);
|
||||
};
|
||||
|
||||
Videos.propTypes = {
|
||||
VideosPage.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(Videos);
|
||||
export default injectIntl(VideosPage);
|
||||
@@ -17,7 +17,7 @@ import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import initializeStore from '../../store';
|
||||
import { executeThunk } from '../../utils';
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import Videos from './Videos';
|
||||
import VideosPage from './VideosPage';
|
||||
import {
|
||||
generateFetchVideosApiResponse,
|
||||
generateEmptyApiResponse,
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
} from './data/thunks';
|
||||
import { getVideosUrl, getCoursVideosApiUrl, getApiBaseUrl } from './data/api';
|
||||
import videoMessages from './messages';
|
||||
import messages from '../messages';
|
||||
import messages from '../generic/messages';
|
||||
|
||||
let axiosMock;
|
||||
let store;
|
||||
@@ -49,7 +49,7 @@ const renderComponent = () => {
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<AppProvider store={store}>
|
||||
<Videos courseId={courseId} />
|
||||
<VideosPage courseId={courseId} />
|
||||
</AppProvider>
|
||||
</IntlProvider>,
|
||||
);
|
||||
@@ -1,25 +1,25 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const VideosContext = React.createContext({});
|
||||
export const VideosPageContext = React.createContext({});
|
||||
|
||||
const VideosProvider = ({ courseId, children }) => {
|
||||
const VideosPageProvider = ({ courseId, children }) => {
|
||||
const contextValue = useMemo(() => ({
|
||||
courseId,
|
||||
path: `/course/${courseId}/videos`,
|
||||
}), []);
|
||||
return (
|
||||
<VideosContext.Provider
|
||||
<VideosPageContext.Provider
|
||||
value={contextValue}
|
||||
>
|
||||
{children}
|
||||
</VideosContext.Provider>
|
||||
</VideosPageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
VideosProvider.propTypes = {
|
||||
VideosPageProvider.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default VideosProvider;
|
||||
export default VideosPageProvider;
|
||||
@@ -1,6 +1,7 @@
|
||||
import TranscriptSettings from './transcript-settings';
|
||||
import Videos from './Videos';
|
||||
import VideosPage from './VideosPage';
|
||||
import VideoThumbnail from './VideoThumbnail';
|
||||
import VideoInfoModalSidebar from './info-sidebar';
|
||||
|
||||
export default Videos;
|
||||
export { TranscriptSettings, VideoThumbnail };
|
||||
export default VideosPage;
|
||||
export { TranscriptSettings, VideoThumbnail, VideoInfoModalSidebar };
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Stack } from '@edx/paragon';
|
||||
import { injectIntl, FormattedDate, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { getFileSizeToClosestByte } from '../../data/utils';
|
||||
import { getFileSizeToClosestByte } from '../../generic/utils';
|
||||
import { getFormattedDuration } from '../data/utils';
|
||||
import messages from './messages';
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
import { getApiBaseUrl } from '../data/api';
|
||||
import messages from './messages';
|
||||
import transcriptRowMessages from './transcript-item/messages';
|
||||
import VideosProvider from '../VideosProvider';
|
||||
import VideosPageProvider from '../VideosPageProvider';
|
||||
import { deleteVideoTranscript } from '../data/thunks';
|
||||
|
||||
ReactDOM.createPortal = jest.fn(node => node);
|
||||
@@ -56,9 +56,9 @@ const renderComponent = (props) => {
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<AppProvider store={store}>
|
||||
<VideosProvider courseId={courseId}>
|
||||
<VideosPageProvider courseId={courseId}>
|
||||
<TranscriptTab video={props} />
|
||||
</VideosProvider>
|
||||
</VideosPageProvider>
|
||||
</AppProvider>
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import InfoTab from './InfoTab';
|
||||
import TranscriptTab from './TranscriptTab';
|
||||
import messages from './messages';
|
||||
|
||||
const FileInfoVideoSidebar = ({
|
||||
const VideoInfoModalSidebar = ({
|
||||
video,
|
||||
// injected
|
||||
intl,
|
||||
@@ -30,7 +30,7 @@ const FileInfoVideoSidebar = ({
|
||||
</Tabs>
|
||||
);
|
||||
|
||||
FileInfoVideoSidebar.propTypes = {
|
||||
VideoInfoModalSidebar.propTypes = {
|
||||
video: PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
wrapperType: PropTypes.string.isRequired,
|
||||
@@ -43,8 +43,8 @@ FileInfoVideoSidebar.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
FileInfoVideoSidebar.defaultProps = {
|
||||
VideoInfoModalSidebar.defaultProps = {
|
||||
video: null,
|
||||
};
|
||||
|
||||
export default injectIntl(FileInfoVideoSidebar);
|
||||
export default injectIntl(VideoInfoModalSidebar);
|
||||
3
src/files-and-videos/videos-page/info-sidebar/index.js
Normal file
3
src/files-and-videos/videos-page/info-sidebar/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import VideoInfoModalSidebar from './VideoInfoModalSidebar';
|
||||
|
||||
export default VideoInfoModalSidebar;
|
||||
@@ -13,7 +13,7 @@ import { isEmpty } from 'lodash';
|
||||
import LanguageSelect from './LanguageSelect';
|
||||
import TranscriptMenu from './TranscriptMenu';
|
||||
import messages from './messages';
|
||||
import FileInput, { useFileInput } from '../../../FileInput';
|
||||
import { FileInput, useFileInput } from '../../../generic';
|
||||
|
||||
const Transcript = ({
|
||||
languages,
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from '../factories/mockApiResponses';
|
||||
import { getApiBaseUrl } from '../data/api';
|
||||
import messages from './messages';
|
||||
import VideosProvider from '../VideosProvider';
|
||||
import VideosProvider from '../VideosPageProvider';
|
||||
|
||||
const defaultProps = {
|
||||
isTranscriptSettingsOpen: true,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { reducer as gradingSettingsReducer } from './grading-settings/data/slice
|
||||
import { reducer as studioHomeReducer } from './studio-home/data/slice';
|
||||
import { reducer as scheduleAndDetailsReducer } from './schedule-and-details/data/slice';
|
||||
import { reducer as liveReducer } from './pages-and-resources/live/data/slice';
|
||||
import { reducer as filesReducer } from './files-and-videos/data/slice';
|
||||
import { reducer as filesReducer } from './files-and-videos/files-page/data/slice';
|
||||
import { reducer as courseTeamReducer } from './course-team/data/slice';
|
||||
import { reducer as CourseUpdatesReducer } from './course-updates/data/slice';
|
||||
import { reducer as processingNotificationReducer } from './generic/processing-notification/data/slice';
|
||||
|
||||
Reference in New Issue
Block a user