diff --git a/src/files-and-videos/files-page/FilePickerPage.tsx b/src/files-and-videos/files-page/FilePickerPage.tsx new file mode 100644 index 0000000000..bd818fdfdc --- /dev/null +++ b/src/files-and-videos/files-page/FilePickerPage.tsx @@ -0,0 +1,20 @@ +import { CourseAuthoringProvider } from '@src/CourseAuthoringContext'; +import { useLocation, useParams } from 'react-router-dom'; +import FilesPage from './FilesPage'; + +export const FilePickerPage = () => { + const { courseId } = useParams<{ courseId: string; }>(); + const location = useLocation(); + const params = new URLSearchParams(location.search); + const filePickerOptions = { + usageKey: params.get('usage_key')!, + multiSelect: params.get('multiSelect') === 'true', + mimeType: params.get('mimeType'), + }; + + return ( + + + + ); +}; diff --git a/src/files-and-videos/files-page/FilesPage.jsx b/src/files-and-videos/files-page/FilesPage.tsx similarity index 84% rename from src/files-and-videos/files-page/FilesPage.jsx rename to src/files-and-videos/files-page/FilesPage.tsx index 22f9e00980..15a28b892c 100644 --- a/src/files-and-videos/files-page/FilesPage.jsx +++ b/src/files-and-videos/files-page/FilesPage.tsx @@ -1,6 +1,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { Container } from '@openedx/paragon'; +import { DeprecatedReduxState } from '@src/store'; import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -15,11 +16,17 @@ import { AgreementGated } from '@src/constants'; import { EditFileErrors } from '../generic'; import { fetchAssets, resetErrors } from './data/thunks'; -import FilesPageProvider from './FilesPageProvider'; +import FilesPageProvider, { FilePickerOptions } from './FilesPageProvider'; import messages from './messages'; import './FilesPage.scss'; -const FilesPage = () => { +const FilesPage = ({ + filePickerMode = false, + filePickerOptions = undefined, +}: { + filePickerMode?: boolean; + filePickerOptions?: FilePickerOptions; +}) => { const intl = useIntl(); const dispatch = useDispatch(); const { courseId, courseDetails } = useCourseAuthoringContext(); @@ -30,7 +37,7 @@ const FilesPage = () => { deletingStatus: deleteAssetStatus, updatingStatus: updateAssetStatus, errors: errorMessages, - } = useSelector(state => state.assets); + } = useSelector((state: DeprecatedReduxState) => state.assets); useEffect(() => { dispatch(fetchAssets(courseId)); @@ -47,7 +54,7 @@ const FilesPage = () => { } return ( - + { - const contextValue = useMemo(() => ({ - courseId, - path: `/course/${courseId}/assets`, - }), []); - return ( - - {children} - - ); -}; - -FilesPageProvider.propTypes = { - courseId: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, -}; - -export default FilesPageProvider; diff --git a/src/files-and-videos/files-page/FilesPageProvider.tsx b/src/files-and-videos/files-page/FilesPageProvider.tsx new file mode 100644 index 0000000000..440320bd1e --- /dev/null +++ b/src/files-and-videos/files-page/FilesPageProvider.tsx @@ -0,0 +1,40 @@ +import React, { useMemo } from 'react'; + +export interface FilePickerOptions { + usageKey: string; + multiSelect: boolean; + mimeType: string | null; +} + +interface FilesPageContextInterface { + filePickerMode: boolean; + filePickerOptions?: FilePickerOptions; +} + +export const FilesPageContext = React.createContext({ + filePickerMode: false, +}); + +interface FilesPageProviderProps extends FilesPageContextInterface { + children: React.ReactNode; +} + +const FilesPageProvider = ({ + children, + filePickerMode = false, + filePickerOptions, +}: FilesPageProviderProps) => { + const contextValue = useMemo(() => ({ + filePickerMode, + filePickerOptions, + }), []); + return ( + + {children} + + ); +}; + +export default FilesPageProvider; diff --git a/src/files-and-videos/generic/FileTable.jsx b/src/files-and-videos/generic/FileTable.jsx index 0dd632aacb..db5e45bc98 100644 --- a/src/files-and-videos/generic/FileTable.jsx +++ b/src/files-and-videos/generic/FileTable.jsx @@ -1,4 +1,10 @@ -import { useCallback, useEffect, useState } from 'react'; +import { FilesPageContext } from '@src/files-and-videos/files-page/FilesPageProvider'; +import { + useCallback, + useContext, + useEffect, + useState, +} from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import isEmpty from 'lodash/isEmpty'; @@ -11,7 +17,7 @@ import { useToggle, } from '@openedx/paragon'; -import { RequestStatus } from '../../data/constants'; +import { RequestStatus } from '@src/data/constants'; import { sortFiles } from './utils'; import messages from './messages'; @@ -79,6 +85,7 @@ const FileTable = ({ const defaultCurrentView = (fileType === 'video' && localStorage.getItem('videosCurrentView')) || (fileType === 'file' && localStorage.getItem('filesCurrentView')) || defaultView; const [currentView, setCurrentView] = useState(defaultCurrentView); + const { filePickerOptions } = useContext(FilesPageContext); useEffect(() => { if (!isEmpty(selectedRows) && Object.keys(selectedRows[0]).length > 0) { @@ -189,6 +196,7 @@ const FileTable = ({ if (!hasMoreInfoColumn) { tableColumns.push({ ...moreInfoColumn }); } + const maxSelectedRows = filePickerOptions?.multiSelect === false ? 1 : undefined; return (
@@ -198,6 +206,7 @@ const FileTable = ({ isSortable isSelectable isPaginated + maxSelectedRows={maxSelectedRows} defaultColumnValues={{ Filter: TextFilter }} dataViewToggleOptions={{ isDataViewToggleEnabled: true, diff --git a/src/files-and-videos/generic/table-components/TableActions.jsx b/src/files-and-videos/generic/table-components/TableActions.jsx index 188de71315..cd2a339aba 100644 --- a/src/files-and-videos/generic/table-components/TableActions.jsx +++ b/src/files-and-videos/generic/table-components/TableActions.jsx @@ -1,8 +1,5 @@ -import React, { useContext, useEffect } from 'react'; -import { isEmpty } from 'lodash'; -import { PropTypes } from 'prop-types'; -import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { getConfig } from '@edx/frontend-platform'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { Button, DataTableContext, @@ -10,6 +7,10 @@ import { useToggle, } from '@openedx/paragon'; import { Add, Tune } from '@openedx/paragon/icons'; +import { FilesPageContext } from '@src/files-and-videos/files-page/FilesPageProvider'; +import { isEmpty } from 'lodash'; +import { PropTypes } from 'prop-types'; +import React, { useContext, useEffect } from 'react'; import messages from '../messages'; import SortAndFilterModal from './sort-and-filter-modal'; @@ -27,6 +28,9 @@ const TableActions = ({ const [isSortOpen, openSort, closeSort] = useToggle(false); const { state, clearSelection } = useContext(DataTableContext); + const { filePickerMode } = useContext(FilesPageContext); + // If window.opener is not available, show the user some error message. + const showFilePicker = filePickerMode; // && Boolean(window.opener); // This useEffect saves DataTable state so it can persist after table re-renders due to data reload. useEffect(() => { setInitialState(state); @@ -80,6 +84,21 @@ const TableActions = ({ + {showFilePicker && ( + + )} ); diff --git a/src/index.jsx b/src/index.jsx index b62a9e58cf..e8126e4503 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -7,7 +7,8 @@ import { getConfig, getPath, } from '@edx/frontend-platform'; -import { AppProvider, ErrorPage } from '@edx/frontend-platform/react'; +import { AppProvider, ErrorPage, PageWrap } from '@edx/frontend-platform/react'; +import { FilePickerPage } from '@src/files-and-videos/files-page/FilePickerPage'; import React, { StrictMode, useEffect } from 'react'; import { createRoot } from 'react-dom/client'; import { @@ -102,6 +103,14 @@ const App = () => { } /> } /> } /> + + + + } + /> {getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && ( } /> )} diff --git a/src/store.ts b/src/store.ts index 99d9331b30..37f5e8bd61 100644 --- a/src/store.ts +++ b/src/store.ts @@ -33,7 +33,23 @@ type InferState = ReducerType extends Reducer ? T : never; export interface DeprecatedReduxState { customPages: Record; discussions: Record; - assets: Record; + assets: { + assetIds: string[]; + loadingStatus: RequestStatusType; + duplicateFiles: string[]; + updatingStatus: string; + addingStatus: string; + deletingStatus: string; + usageStatus: string; + errors: { + add: string[]; + delete: string[]; + lock: string[]; + download: string[]; + usageMetrics: string[]; + loading: string; + }; + }; pagesAndResources: Record; scheduleAndDetails: Record; studioHome: InferState;