Skip to content

Commit

Permalink
Merge pull request #77 from open-craft/kshitij/plugin-slots/files-and…
Browse files Browse the repository at this point in the history
…-alerts.redwood

feat: Add slots for video and file upload components and alerts
  • Loading branch information
xitij2000 authored Dec 23, 2024
2 parents bb553c5 + 2cd1e96 commit b7e496b
Show file tree
Hide file tree
Showing 10 changed files with 543 additions and 500 deletions.
2 changes: 2 additions & 0 deletions src/course-outline/page-alerts/PageAlerts.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CourseOutlinePageAlertsSlot from 'CourseAuthoring/plugin-slots/CourseOutlinePageAlertsSlot';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
Expand Down Expand Up @@ -413,6 +414,7 @@ const PageAlerts = ({
{errorFilesPasteAlert()}
{conflictingFilesPasteAlert()}
{newFilesPasteAlert()}
<CourseOutlinePageAlertsSlot />
</>
);
};
Expand Down
12 changes: 8 additions & 4 deletions src/course-outline/page-alerts/PageAlerts.test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { act, render, fireEvent } from '@testing-library/react';
import {
act, render, fireEvent, waitFor,
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeMockApp, getConfig } from '@edx/frontend-platform';
Expand Down Expand Up @@ -103,9 +105,11 @@ describe('<PageAlerts />', () => {
const discussionAlertDismissKey = `discussionAlertDismissed-${pageAlertsData.courseId}`;
expect(localStorage.getItem(discussionAlertDismissKey)).toBe('true');

const feedbackLink = queryByText(messages.discussionNotificationFeedback.defaultMessage);
expect(feedbackLink).toBeInTheDocument();
expect(feedbackLink).toHaveAttribute('href', 'some-feedback-url');
await waitFor(() => {
const feedbackLink = queryByText(messages.discussionNotificationFeedback.defaultMessage);
expect(feedbackLink).toBeInTheDocument();
expect(feedbackLink).toHaveAttribute('href', 'some-feedback-url');
});
});

it('renders deprecation warning alerts', async () => {
Expand Down
192 changes: 18 additions & 174 deletions src/files-and-videos/files-page/FilesPage.jsx
Original file line number Diff line number Diff line change
@@ -1,173 +1,39 @@
import React, { useEffect } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Container } from '@openedx/paragon';
import CourseFilesSlot from 'CourseAuthoring/plugin-slots/CourseFilesSlot';
import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n';
import { CheckboxFilter, Container } from '@openedx/paragon';
import Placeholder from '@edx/frontend-lib-content-components';

import Placeholder from '@edx/frontend-lib-content-components';
import { RequestStatus } from '../../data/constants';
import { useModels, useModel } from '../../generic/model-store';
import {
addAssetFile,
deleteAssetFile,
fetchAssets,
updateAssetLock,
fetchAssetDownload,
getUsagePaths,
resetErrors,
updateAssetOrder,
validateAssetFiles,
} from './data/thunks';
import messages from './messages';
import FilesPageProvider from './FilesPageProvider';
import { useModel } from '../../generic/model-store';
import getPageHeadTitle from '../../generic/utils';
import {
AccessColumn,
ActiveColumn,
EditFileErrors,
FileTable,
ThumbnailColumn,
} from '../generic';
import { getFileSizeToClosestByte } from '../../utils';
import FileThumbnail from './FileThumbnail';
import FileInfoModalSidebar from './FileInfoModalSidebar';
import FileValidationModal from './FileValidationModal';
import { EditFileErrors } from '../generic';
import { fetchAssets, resetErrors } from './data/thunks';
import FilesPageProvider from './FilesPageProvider';
import messages from './messages';

const FilesPage = ({
courseId,
// injected
intl,
}) => {
const dispatch = useDispatch();
const intl = useIntl();
const courseDetails = useModel('courseDetails', courseId);
document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.heading));

useEffect(() => {
dispatch(fetchAssets(courseId));
}, [courseId]);

const {
assetIds,
loadingStatus,
addingStatus: addAssetStatus,
deletingStatus: deleteAssetStatus,
updatingStatus: updateAssetStatus,
usageStatus: usagePathStatus,
errors: errorMessages,
} = useSelector(state => state.assets);

const handleErrorReset = (error) => dispatch(resetErrors(error));
const handleDeleteFile = (id) => dispatch(deleteAssetFile(courseId, id));
const handleDownloadFile = (selectedRows) => dispatch(fetchAssetDownload({ selectedRows, courseId }));
const handleAddFile = (files) => {
handleErrorReset({ errorType: 'add' });
dispatch(validateAssetFiles(courseId, files));
};
const handleFileOverwrite = (close, files) => {
Object.values(files).forEach(file => dispatch(addAssetFile(courseId, file, true)));
close();
};
const handleLockFile = (fileId, locked) => {
handleErrorReset({ errorType: 'lock' });
dispatch(updateAssetLock({ courseId, assetId: fileId, locked }));
};
const handleUsagePaths = (asset) => dispatch(getUsagePaths({ asset, courseId }));
const handleFileOrder = ({ newFileIdOrder, sortType }) => {
dispatch(updateAssetOrder(courseId, newFileIdOrder, sortType));
};

const thumbnailPreview = (props) => FileThumbnail(props);
const infoModalSidebar = (asset) => FileInfoModalSidebar({
asset,
handleLockedAsset: handleLockFile,
});

const assets = useModels('assets', assetIds);
const data = {
fileIds: assetIds,
loadingStatus,
usagePathStatus,
usageErrorMessages: errorMessages.usageMetrics,
fileType: 'file',
};
const maxFileSize = 20 * 1048576;

const activeColumn = {
id: 'activeStatus',
Header: 'Active',
accessor: 'activeStatus',
Cell: ({ row }) => ActiveColumn({ row, pageLoadStatus: loadingStatus }),
Filter: CheckboxFilter,
filter: 'exactTextCase',
filterChoices: [
{ name: intl.formatMessage(messages.activeCheckboxLabel), value: 'active' },
{ name: intl.formatMessage(messages.inactiveCheckboxLabel), value: 'inactive' },
],
};
const accessColumn = {
id: 'lockStatus',
Header: 'Access',
accessor: 'lockStatus',
Cell: ({ row }) => AccessColumn({ row }),
Filter: CheckboxFilter,
filterChoices: [
{ name: intl.formatMessage(messages.lockedCheckboxLabel), value: 'locked' },
{ name: intl.formatMessage(messages.publicCheckboxLabel), value: 'public' },
],
};
const thumbnailColumn = {
id: 'thumbnail',
Header: '',
Cell: ({ row }) => ThumbnailColumn({ row, thumbnailPreview }),
};
const fileSizeColumn = {
id: 'fileSize',
Header: 'File size',
accessor: 'fileSize',
Cell: ({ row }) => {
const { fileSize } = row.original;
return getFileSizeToClosestByte(fileSize);
},
};
useEffect(() => {
dispatch(fetchAssets(courseId));
}, [courseId]);

const tableColumns = [
{ ...thumbnailColumn },
{
Header: 'File name',
accessor: 'displayName',
},
{ ...fileSizeColumn },
{
Header: 'Type',
accessor: 'wrapperType',
Filter: CheckboxFilter,
filter: 'includesValue',
filterChoices: [
{
name: intl.formatMessage(messages.codeCheckboxLabel),
value: 'code',
},
{
name: intl.formatMessage(messages.imageCheckboxLabel),
value: 'image',
},
{
name: intl.formatMessage(messages.documentCheckboxLabel),
value: 'document',
},
{
name: intl.formatMessage(messages.audioCheckboxLabel),
value: 'audio',
},
{
name: intl.formatMessage(messages.otherCheckboxLabel),
value: 'other',
},
],
},
{ ...activeColumn },
{ ...accessColumn },
];
const handleErrorReset = (error) => dispatch(resetErrors(error));

if (loadingStatus === RequestStatus.DENIED) {
return (
Expand All @@ -189,30 +55,10 @@ const FilesPage = ({
loadingStatus={loadingStatus}
/>
<div className="h2">
<FormattedMessage {...messages.heading} />
{intl.formatMessage(messages.heading)}
</div>
{loadingStatus !== RequestStatus.FAILED && (
<>
<FileTable
{...{
courseId,
data,
handleAddFile,
handleDeleteFile,
handleDownloadFile,
handleLockFile,
handleUsagePaths,
handleErrorReset,
handleFileOrder,
tableColumns,
maxFileSize,
thumbnailPreview,
infoModalSidebar,
files: assets,
}}
/>
<FileValidationModal {...{ handleFileOverwrite }} />
</>
<CourseFilesSlot courseId={courseId} />
)}
</Container>
</FilesPageProvider>
Expand All @@ -221,8 +67,6 @@ const FilesPage = ({

FilesPage.propTypes = {
courseId: PropTypes.string.isRequired,
// injected
intl: intlShape.isRequired,
};

export default injectIntl(FilesPage);
export default FilesPage;
Loading

0 comments on commit b7e496b

Please sign in to comment.