fix: combine filter and sort into one modal (#680)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { CheckboxFilter } from '@edx/paragon';
|
||||
@@ -85,12 +86,24 @@ const FilesPage = ({
|
||||
const activeColumn = {
|
||||
id: 'usageLocations',
|
||||
Header: 'Active',
|
||||
accessor: (({ usageLocations }) => !isEmpty(usageLocations)),
|
||||
Cell: ({ row }) => ActiveColumn({ row }),
|
||||
Filter: CheckboxFilter,
|
||||
filterChoices: [
|
||||
{ name: intl.formatMessage(messages.activeCheckboxLabel), value: true },
|
||||
{ name: intl.formatMessage(messages.inactiveCheckboxLabel), value: false },
|
||||
],
|
||||
};
|
||||
const accessColumn = {
|
||||
id: 'locked',
|
||||
Header: 'Access',
|
||||
accessor: 'locked',
|
||||
Cell: ({ row }) => AccessColumn({ row }),
|
||||
Filter: CheckboxFilter,
|
||||
filterChoices: [
|
||||
{ name: intl.formatMessage(messages.lockedCheckboxLabel), value: true },
|
||||
{ name: intl.formatMessage(messages.publicCheckboxLabel), value: false },
|
||||
],
|
||||
};
|
||||
const thumbnailColumn = {
|
||||
id: 'thumbnail',
|
||||
@@ -100,6 +113,7 @@ const FilesPage = ({
|
||||
const fileSizeColumn = {
|
||||
id: 'fileSize',
|
||||
Header: 'File size',
|
||||
accessor: 'fileSize',
|
||||
Cell: ({ row }) => {
|
||||
const { fileSize } = row.original;
|
||||
return getFileSizeToClosestByte(fileSize);
|
||||
@@ -120,21 +134,25 @@ const FilesPage = ({
|
||||
filter: 'includesValue',
|
||||
filterChoices: [
|
||||
{
|
||||
name: 'Code',
|
||||
name: intl.formatMessage(messages.codeCheckboxLabel),
|
||||
value: 'code',
|
||||
},
|
||||
{
|
||||
name: 'Images',
|
||||
name: intl.formatMessage(messages.imageCheckboxLabel),
|
||||
value: 'image',
|
||||
},
|
||||
{
|
||||
name: 'Documents',
|
||||
name: intl.formatMessage(messages.documentCheckboxLabel),
|
||||
value: 'document',
|
||||
},
|
||||
{
|
||||
name: 'Audio',
|
||||
name: intl.formatMessage(messages.audioCheckboxLabel),
|
||||
value: 'audio',
|
||||
},
|
||||
{
|
||||
name: intl.formatMessage(messages.otherCheckboxLabel),
|
||||
value: 'other',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ ...activeColumn },
|
||||
|
||||
@@ -45,6 +45,42 @@ const messages = defineMessages({
|
||||
you lock a file, the web URL only allows learners who are enrolled
|
||||
in your course and signed in to access the file.`,
|
||||
},
|
||||
activeCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.activeCheckbox.label',
|
||||
defaultMessage: 'Active',
|
||||
},
|
||||
inactiveCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.inactiveCheckbox.label',
|
||||
defaultMessage: 'Inactive',
|
||||
},
|
||||
lockedCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.lockedCheckbox.label',
|
||||
defaultMessage: 'Locked',
|
||||
},
|
||||
publicCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.publicCheckbox.label',
|
||||
defaultMessage: 'Public',
|
||||
},
|
||||
codeCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.codeCheckbox.label',
|
||||
defaultMessage: 'Code',
|
||||
},
|
||||
imageCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.imageCheckbox.label',
|
||||
defaultMessage: 'Images',
|
||||
},
|
||||
documentCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.documentCheckbox.label',
|
||||
defaultMessage: 'Documents',
|
||||
},
|
||||
audioCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.audioCheckbox.label',
|
||||
defaultMessage: 'Audio',
|
||||
},
|
||||
otherCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.otherCheckbox.label',
|
||||
defaultMessage: 'Other',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -22,10 +22,11 @@ import FileInput, { useFileInput } from './FileInput';
|
||||
import {
|
||||
GalleryCard,
|
||||
TableActions,
|
||||
RowStatus,
|
||||
MoreInfoColumn,
|
||||
FilterStatus,
|
||||
} from './table-components';
|
||||
import ApiStatusToast from './ApiStatusToast';
|
||||
import FilterStatus from './table-components/FilterStatus';
|
||||
import MoreInfoColumn from './table-components/table-custom-columns/MoreInfoColumn';
|
||||
|
||||
const FileTable = ({
|
||||
files,
|
||||
@@ -171,7 +172,7 @@ const FileTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="files-table">
|
||||
<DataTable
|
||||
isFilterable
|
||||
isLoading={loadingStatus === RequestStatus.IN_PROGRESS}
|
||||
@@ -195,6 +196,7 @@ const FileTable = ({
|
||||
pageCount={pageCount}
|
||||
data={files}
|
||||
FilterStatusComponent={FilterStatus}
|
||||
RowStatusComponent={RowStatus}
|
||||
>
|
||||
{isEmpty(files) && loadingStatus !== RequestStatus.IN_PROGRESS ? (
|
||||
<Dropzone
|
||||
@@ -261,7 +263,7 @@ const FileTable = ({
|
||||
>
|
||||
{intl.formatMessage(messages.deleteConfirmationMessage, { fileNumber: selectedRows.length })}
|
||||
</AlertModal>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
rowStatusMessage: {
|
||||
id: 'course-authoring.files-and-upload.rowStatus.message',
|
||||
defaultMessage: 'Showing {fileCount} of {rowCount}',
|
||||
},
|
||||
apiStatusToastMessage: {
|
||||
id: 'course-authoring.files-and-upload.apiStatus.message',
|
||||
defaultMessage: '{actionType} {selectedRowCount} file(s)',
|
||||
@@ -95,7 +99,7 @@ const messages = defineMessages({
|
||||
},
|
||||
sortButtonLabel: {
|
||||
id: 'course-authoring.files-and-uploads.sortButton.label',
|
||||
defaultMessage: 'Sort',
|
||||
defaultMessage: 'Sort and Filter',
|
||||
},
|
||||
sortModalTitleLabel: {
|
||||
id: 'course-authoring.files-and-uploads.sortModal.title',
|
||||
|
||||
@@ -1,46 +1,56 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { DataTableContext, Button } from '@edx/paragon';
|
||||
import {
|
||||
DataTableContext, Button, Row, Chip,
|
||||
} from '@edx/paragon';
|
||||
import { Close } from '@edx/paragon/icons';
|
||||
import { getFilters, removeFilter } from './utils';
|
||||
|
||||
const FilterStatus = ({
|
||||
className, variant, size, clearFiltersText, buttonClassName,
|
||||
}) => {
|
||||
const {
|
||||
setAllFilters, RowStatusComponent, page, rows,
|
||||
state, setAllFilters, setFilter, RowStatusComponent, columns,
|
||||
} = useContext(DataTableContext);
|
||||
|
||||
if (!setAllFilters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const RowStatus = RowStatusComponent;
|
||||
|
||||
const pageSize = page?.length || rows?.length;
|
||||
const filters = getFilters(state, columns);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="pl-1">
|
||||
<span>Filters applied</span>
|
||||
{!!pageSize && ' ('}
|
||||
<RowStatus className="d-inline" />
|
||||
{!!pageSize && ')'}
|
||||
</div>
|
||||
<Button
|
||||
className={buttonClassName}
|
||||
variant={variant}
|
||||
size={size}
|
||||
onClick={() => setAllFilters([])}
|
||||
>
|
||||
{clearFiltersText === undefined
|
||||
? (
|
||||
<FormattedMessage
|
||||
id="pgn.DataTable.FilterStatus.clearFiltersText"
|
||||
defaultMessage="Clear filters"
|
||||
description="A text that appears on the `Clear filters` button"
|
||||
/>
|
||||
)
|
||||
: clearFiltersText}
|
||||
</Button>
|
||||
<RowStatusComponent />
|
||||
<Row className="m-0 align-items-center">
|
||||
<span className="mr-2">Filters applied</span>
|
||||
{filters.map(({ name, value }) => (
|
||||
<Chip
|
||||
key={value}
|
||||
iconAfter={Close}
|
||||
onIconAfterClick={() => removeFilter(value, setFilter, setAllFilters, state)}
|
||||
>
|
||||
{name}
|
||||
</Chip>
|
||||
))}
|
||||
<Button
|
||||
className={buttonClassName}
|
||||
variant={variant}
|
||||
size={size}
|
||||
onClick={() => setAllFilters([])}
|
||||
>
|
||||
{clearFiltersText === undefined
|
||||
? (
|
||||
<FormattedMessage
|
||||
id="pgn.DataTable.FilterStatus.clearFiltersText"
|
||||
defaultMessage="Clear filters"
|
||||
description="A text that appears on the `Clear filters` button"
|
||||
/>
|
||||
)
|
||||
: clearFiltersText}
|
||||
</Button>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
30
src/files-and-videos/generic/table-components/RowStatus.jsx
Normal file
30
src/files-and-videos/generic/table-components/RowStatus.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { DataTableContext } from '@edx/paragon';
|
||||
import { getCurrentViewRange } from './utils';
|
||||
|
||||
const RowStatus = ({
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
const { filteredRows, page, initialRows } = useContext(DataTableContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>
|
||||
{getCurrentViewRange({
|
||||
filterRowCount: filteredRows.length,
|
||||
initialRowCount: initialRows.length,
|
||||
fileCount: page.length,
|
||||
intl,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RowStatus.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(RowStatus);
|
||||
@@ -1,18 +1,16 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
ActionRow,
|
||||
Button,
|
||||
Dropdown,
|
||||
ModalDialog,
|
||||
SelectableBox,
|
||||
useToggle,
|
||||
} from '@edx/paragon';
|
||||
import { Add } from '@edx/paragon/icons';
|
||||
import { Add, Tune } from '@edx/paragon/icons';
|
||||
import messages from '../messages';
|
||||
import SortAndFilterModal from './sort-and-filter-modal';
|
||||
|
||||
const TableActions = ({
|
||||
selectedFlatRows,
|
||||
@@ -21,17 +19,11 @@ const TableActions = ({
|
||||
handleBulkDownload,
|
||||
handleOpenDeleteConfirmation,
|
||||
encodingsDownloadUrl,
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
const [isSortOpen, openSort, closeSort] = useToggle(false);
|
||||
const [sortBy, setSortBy] = useState('dateAdded,desc');
|
||||
const handleChange = (e) => {
|
||||
setSortBy(e.target.value);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button variant="outline-primary" onClick={openSort}>
|
||||
<Button variant="outline-primary" onClick={openSort} iconBefore={Tune}>
|
||||
<FormattedMessage {...messages.sortButtonLabel} />
|
||||
</Button>
|
||||
<Dropdown className="mx-2">
|
||||
@@ -70,94 +62,7 @@ const TableActions = ({
|
||||
<Button iconBefore={Add} onClick={fileInputControl.click}>
|
||||
<FormattedMessage {...messages.addFilesButtonLabel} />
|
||||
</Button>
|
||||
<ModalDialog
|
||||
title={intl.formatMessage(messages.sortModalTitleLabel)}
|
||||
isOpen={isSortOpen}
|
||||
onClose={closeSort}
|
||||
size="lg"
|
||||
hasCloseButton
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title>
|
||||
<FormattedMessage {...messages.sortModalTitleLabel} />
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>
|
||||
<SelectableBox.Set
|
||||
type="radio"
|
||||
value={sortBy}
|
||||
onChange={handleChange}
|
||||
name="sort options"
|
||||
columns={3}
|
||||
ariaLabel="sort by selection"
|
||||
>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="displayName,asc"
|
||||
type="radio"
|
||||
aria-label="name descending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortByNameAscending} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="dateAdded,desc"
|
||||
type="radio"
|
||||
aria-label="date added descending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortByNewest} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="fileSize,desc"
|
||||
type="radio"
|
||||
aria-label="date added descending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortBySizeDescending} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="displayName,desc"
|
||||
type="radio"
|
||||
aria-label="name ascending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortByNameDescending} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="dateAdded,asc"
|
||||
type="radio"
|
||||
aria-label="date added ascending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortByOldest} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="fileSize,asc"
|
||||
type="radio"
|
||||
aria-label="date added ascending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortBySizeAscending} />
|
||||
</SelectableBox>
|
||||
</SelectableBox.Set>
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="tertiary">
|
||||
<FormattedMessage {...messages.cancelButtonLabel} />
|
||||
</ModalDialog.CloseButton>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
closeSort();
|
||||
handleSort(sortBy);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage {...messages.applySortButton} />
|
||||
</Button>
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
<SortAndFilterModal {...{ isSortOpen, closeSort, handleSort }} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -186,8 +91,6 @@ TableActions.propTypes = {
|
||||
handleBulkDownload: PropTypes.func.isRequired,
|
||||
encodingsDownloadUrl: PropTypes.string,
|
||||
handleSort: PropTypes.func.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
TableActions.defaultProps = {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import GalleryCard from './GalleryCard';
|
||||
import TableActions from './TableActions';
|
||||
import FilterStatus from './FilterStatus';
|
||||
import RowStatus from './RowStatus';
|
||||
import {
|
||||
AccessColumn,
|
||||
ActiveColumn,
|
||||
@@ -11,6 +13,8 @@ import {
|
||||
export {
|
||||
TableActions,
|
||||
GalleryCard,
|
||||
FilterStatus,
|
||||
RowStatus,
|
||||
AccessColumn,
|
||||
ActiveColumn,
|
||||
MoreInfoColumn,
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
ActionRow,
|
||||
Button,
|
||||
DataTableContext,
|
||||
Form,
|
||||
ModalDialog,
|
||||
SelectableBox,
|
||||
useCheckboxSetValues,
|
||||
} from '@edx/paragon';
|
||||
import messages from './messages';
|
||||
import { getCheckedFilters, getFilterOptions, processFilters } from './utils';
|
||||
|
||||
const SortAndFilterModal = ({
|
||||
isSortOpen,
|
||||
closeSort,
|
||||
handleSort,
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
const { state, setAllFilters, columns } = useContext(DataTableContext);
|
||||
const filterOptions = getFilterOptions(columns);
|
||||
const currentFilters = getCheckedFilters(state);
|
||||
const [sortBy, setSortBy] = useState('dateAdded,desc');
|
||||
const [filterBy, {
|
||||
add, remove, set, clear,
|
||||
}] = useCheckboxSetValues(currentFilters);
|
||||
|
||||
useEffect(() => {
|
||||
const updatedFilters = getCheckedFilters(state);
|
||||
set(updatedFilters);
|
||||
}, [state]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setSortBy(e.target.value);
|
||||
};
|
||||
|
||||
const handleFilterUpdate = (e) => {
|
||||
if (e.target.checked) {
|
||||
add(e.target.value);
|
||||
} else {
|
||||
remove(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
closeSort();
|
||||
handleSort(sortBy);
|
||||
processFilters(filterBy, columns, setAllFilters);
|
||||
};
|
||||
|
||||
const handleClearAll = () => {
|
||||
setSortBy('dateAdded,desc');
|
||||
clear();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalDialog
|
||||
title={intl.formatMessage(messages.modalTitle)}
|
||||
isOpen={isSortOpen}
|
||||
onClose={closeSort}
|
||||
size="lg"
|
||||
hasCloseButton
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title>
|
||||
<FormattedMessage {...messages.modalTitle} />
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>
|
||||
<div className="h4 mb-4">
|
||||
<FormattedMessage {...messages.sortByHeader} />
|
||||
</div>
|
||||
<SelectableBox.Set
|
||||
type="radio"
|
||||
value={sortBy}
|
||||
onChange={handleChange}
|
||||
name="sort options"
|
||||
columns={3}
|
||||
ariaLabel="sort by selection"
|
||||
className="mb-4.5"
|
||||
>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="displayName,asc"
|
||||
type="radio"
|
||||
aria-label="name descending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortByNameAscending} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="dateAdded,desc"
|
||||
type="radio"
|
||||
aria-label="date added descending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortByNewest} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="fileSize,desc"
|
||||
type="radio"
|
||||
aria-label="file size descending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortBySizeDescending} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="displayName,desc"
|
||||
type="radio"
|
||||
aria-label="name ascending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortByNameDescending} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="dateAdded,asc"
|
||||
type="radio"
|
||||
aria-label="date added ascending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortByOldest} />
|
||||
</SelectableBox>
|
||||
<SelectableBox
|
||||
className="text-center"
|
||||
value="fileSize,asc"
|
||||
type="radio"
|
||||
aria-label="file size ascending radio"
|
||||
>
|
||||
<FormattedMessage {...messages.sortBySizeAscending} />
|
||||
</SelectableBox>
|
||||
</SelectableBox.Set>
|
||||
<hr />
|
||||
<div className="h4 my-4">
|
||||
<FormattedMessage {...messages.filterByHeader} />
|
||||
</div>
|
||||
<Form.Group>
|
||||
<Form.CheckboxSet
|
||||
name="filters"
|
||||
onChange={handleFilterUpdate}
|
||||
value={filterBy}
|
||||
isInline
|
||||
>
|
||||
{filterOptions.map(({ name, value }) => (
|
||||
<Form.Checkbox {...{ value, key: value }}>{name}</Form.Checkbox>
|
||||
))}
|
||||
</Form.CheckboxSet>
|
||||
</Form.Group>
|
||||
<Button className="pl-0" variant="link" onClick={handleClearAll}>
|
||||
<FormattedMessage {...messages.clearAllButtonLabel} />
|
||||
</Button>
|
||||
<hr />
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="tertiary">
|
||||
<FormattedMessage {...messages.cancelButtonLabel} />
|
||||
</ModalDialog.CloseButton>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleApply}
|
||||
>
|
||||
<FormattedMessage {...messages.applySortButton} />
|
||||
</Button>
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
);
|
||||
};
|
||||
|
||||
SortAndFilterModal.propTypes = {
|
||||
handleSort: PropTypes.func.isRequired,
|
||||
isSortOpen: PropTypes.bool.isRequired,
|
||||
closeSort: PropTypes.func.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(SortAndFilterModal);
|
||||
@@ -0,0 +1,3 @@
|
||||
import SortAndFilterModal from './SortAndFilterModal';
|
||||
|
||||
export default SortAndFilterModal;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
modalTitle: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.title',
|
||||
defaultMessage: 'Sort and Filter',
|
||||
},
|
||||
sortByHeader: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.sortBySection.header',
|
||||
defaultMessage: 'Sort by',
|
||||
},
|
||||
filterByHeader: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filterBySection.header',
|
||||
defaultMessage: 'Filter by',
|
||||
},
|
||||
clearAllButtonLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.clearAllButton.label',
|
||||
defaultMessage: 'Clear all',
|
||||
},
|
||||
cancelButtonLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.cancelButton.label',
|
||||
defaultMessage: 'Cancel',
|
||||
},
|
||||
sortByNameAscending: {
|
||||
id: 'course-authoring..files-and-videos.sort-and-filter.modal.sortByNameAscendingButton.label',
|
||||
defaultMessage: 'Name (A-Z)',
|
||||
},
|
||||
sortByNewest: {
|
||||
id: 'course-authoring..files-and-videos.sort-and-filter.modal.sortByNewestButton.label',
|
||||
defaultMessage: 'Newest',
|
||||
},
|
||||
sortBySizeDescending: {
|
||||
id: 'course-authoring..files-and-videos.sort-and-filter.modal.sortBySizeDescendingButton.label',
|
||||
defaultMessage: 'File size (High to low)',
|
||||
},
|
||||
sortByNameDescending: {
|
||||
id: 'course-authoring..files-and-videos.sort-and-filter.modal.sortByNameDescendingButton.label',
|
||||
defaultMessage: 'Name (Z-A)',
|
||||
},
|
||||
sortByOldest: {
|
||||
id: 'course-authoring..files-and-videos.sort-and-filter.modal.sortByOldestButton.label',
|
||||
defaultMessage: 'Oldest',
|
||||
},
|
||||
sortBySizeAscending: {
|
||||
id: 'course-authoring..files-and-videos.sort-and-filter.modal.sortBySizeAscendingButton.label',
|
||||
defaultMessage: 'File size (Low to high)',
|
||||
},
|
||||
applySortButton: {
|
||||
id: 'course-authoring..files-and-videos.sort-and-filter.modal.applyySortButton.label',
|
||||
defaultMessage: 'Apply',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -0,0 +1,119 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
export const getFilterOptions = (columns) => {
|
||||
const allOptions = [];
|
||||
const filterableColumns = columns.filter(column => column?.filterChoices);
|
||||
|
||||
filterableColumns.forEach(column => {
|
||||
const { id, filterChoices } = column;
|
||||
let updatedChoices = filterChoices;
|
||||
|
||||
switch (id) {
|
||||
case 'locked':
|
||||
updatedChoices = filterChoices.map(choice => (
|
||||
{ ...choice, value: choice.value ? 'locked' : 'public' }
|
||||
));
|
||||
break;
|
||||
case 'usageLocations':
|
||||
updatedChoices = filterChoices.map(choice => (
|
||||
{ ...choice, value: choice.value ? 'active' : 'inactive' }
|
||||
));
|
||||
break;
|
||||
case 'transcripts':
|
||||
updatedChoices = filterChoices.map(choice => (
|
||||
{ ...choice, value: choice.value ? 'transcribed' : 'notTranscribed' }
|
||||
));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
allOptions.push(...updatedChoices);
|
||||
});
|
||||
|
||||
return allOptions;
|
||||
};
|
||||
|
||||
export const getCheckedFilters = (state) => {
|
||||
const { filters } = state;
|
||||
const allFilters = [];
|
||||
|
||||
filters.forEach(filter => {
|
||||
const { id, value } = filter;
|
||||
let updatedValues = value;
|
||||
|
||||
switch (id) {
|
||||
case 'locked':
|
||||
updatedValues = value.map(val => (val ? 'locked' : 'public'));
|
||||
break;
|
||||
case 'usageLocations':
|
||||
updatedValues = value.map(val => (val ? 'active' : 'inactive'));
|
||||
break;
|
||||
case 'transcripts':
|
||||
updatedValues = value.map(val => (val ? 'transcribed' : 'notTranscribed'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
allFilters.push(...updatedValues);
|
||||
});
|
||||
|
||||
return allFilters;
|
||||
};
|
||||
|
||||
export const processFilters = (filters, columns, setAllFilters) => {
|
||||
const filterableColumns = columns.filter(column => column?.filterChoices);
|
||||
const allFilters = [];
|
||||
|
||||
filterableColumns.forEach(({ id, filterChoices }) => {
|
||||
const filterValues = filterChoices.map(choice => choice.value);
|
||||
let processedFilters = filters;
|
||||
|
||||
switch (id) {
|
||||
case 'locked':
|
||||
processedFilters = filters.map(match => {
|
||||
if (match === 'locked') {
|
||||
return true;
|
||||
}
|
||||
if (match === 'public') {
|
||||
return false;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
break;
|
||||
case 'usageLocations':
|
||||
processedFilters = filters.map(match => {
|
||||
if (match === 'active') {
|
||||
return true;
|
||||
}
|
||||
if (match === 'inactive') {
|
||||
return false;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
break;
|
||||
case 'transcripts':
|
||||
processedFilters = filters.map(match => {
|
||||
if (match === 'transcribed') {
|
||||
return true;
|
||||
}
|
||||
if (match === 'notTranscribed') {
|
||||
return false;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const matchingFilters = filterValues.filter(value => processedFilters.includes(value));
|
||||
|
||||
if (!isEmpty(matchingFilters)) {
|
||||
allFilters.push({ id, value: matchingFilters });
|
||||
}
|
||||
});
|
||||
|
||||
setAllFilters(allFilters);
|
||||
};
|
||||
@@ -0,0 +1,304 @@
|
||||
import { getCheckedFilters, getFilterOptions, processFilters } from './utils';
|
||||
|
||||
describe('getCheckboxFilters', () => {
|
||||
describe('switch case locked', () => {
|
||||
it('should equal array with string locked', () => {
|
||||
const state = {
|
||||
filters: [
|
||||
{ id: 'locked', value: [true] },
|
||||
],
|
||||
};
|
||||
const expected = ['locked'];
|
||||
const actual = getCheckedFilters(state);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('value attribute should equal public', () => {
|
||||
const state = {
|
||||
filters: [
|
||||
{ id: 'locked', value: [false] },
|
||||
],
|
||||
};
|
||||
const expected = ['public'];
|
||||
const actual = getCheckedFilters(state);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case usageLocations', () => {
|
||||
it('value attribute should equal active', () => {
|
||||
const state = {
|
||||
filters: [
|
||||
{ id: 'usageLocations', value: [true] },
|
||||
],
|
||||
};
|
||||
const expected = ['active'];
|
||||
const actual = getCheckedFilters(state);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('value attribute should equal inactive', () => {
|
||||
const state = {
|
||||
filters: [
|
||||
{ id: 'usageLocations', value: [false] },
|
||||
],
|
||||
};
|
||||
const expected = ['inactive'];
|
||||
const actual = getCheckedFilters(state);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case transcripts', () => {
|
||||
it('should equal array with string transcribed', () => {
|
||||
const state = {
|
||||
filters: [
|
||||
{ id: 'transcripts', value: [true] },
|
||||
],
|
||||
};
|
||||
const expected = ['transcribed'];
|
||||
const actual = getCheckedFilters(state);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should equal array with string notTranscribed', () => {
|
||||
const state = {
|
||||
filters: [
|
||||
{ id: 'transcripts', value: [false] },
|
||||
],
|
||||
};
|
||||
const expected = ['notTranscribed'];
|
||||
const actual = getCheckedFilters(state);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case default', () => {
|
||||
it('should equal array with string test', () => {
|
||||
const state = {
|
||||
filters: [
|
||||
{ id: 'testId', value: ['testValue'] },
|
||||
],
|
||||
};
|
||||
const expected = ['testValue'];
|
||||
const actual = getCheckedFilters(state);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFilterOptions', () => {
|
||||
describe('switch case locked', () => {
|
||||
it('value attribute should equal locked', () => {
|
||||
const columns = [
|
||||
{ id: 'locked', filterChoices: [{ name: 'Locked', value: true }] },
|
||||
];
|
||||
const expected = [
|
||||
{ name: 'Locked', value: 'locked' },
|
||||
];
|
||||
const actual = getFilterOptions(columns);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('value attribute should equal public', () => {
|
||||
const columns = [
|
||||
{ id: 'locked', filterChoices: [{ name: 'Public', value: false }] },
|
||||
];
|
||||
const expected = [
|
||||
{ name: 'Public', value: 'public' },
|
||||
];
|
||||
const actual = getFilterOptions(columns);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case usageLocation', () => {
|
||||
it('value attribute should equal active', () => {
|
||||
const columns = [
|
||||
{ id: 'usageLocations', filterChoices: [{ name: 'Active', value: true }] },
|
||||
];
|
||||
const expected = [
|
||||
{ name: 'Active', value: 'active' },
|
||||
];
|
||||
const actual = getFilterOptions(columns);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('value attribute should equal inactive', () => {
|
||||
const columns = [
|
||||
{ id: 'usageLocations', filterChoices: [{ name: 'Inactive', value: false }] },
|
||||
];
|
||||
const expected = [
|
||||
{ name: 'Inactive', value: 'inactive' },
|
||||
];
|
||||
const actual = getFilterOptions(columns);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case transcripts', () => {
|
||||
it('value attribute should equal transcribed', () => {
|
||||
const columns = [
|
||||
{ id: 'transcripts', filterChoices: [{ name: 'Transcribed', value: true }] },
|
||||
];
|
||||
const expected = [
|
||||
{ name: 'Transcribed', value: 'transcribed' },
|
||||
];
|
||||
const actual = getFilterOptions(columns);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('value attribute should equal notTranscribed', () => {
|
||||
const columns = [
|
||||
{ id: 'transcripts', filterChoices: [{ name: 'Not transcribed', value: false }] },
|
||||
];
|
||||
const expected = [
|
||||
{ name: 'Not transcribed', value: 'notTranscribed' },
|
||||
];
|
||||
const actual = getFilterOptions(columns);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case default', () => {
|
||||
it('value attribute should equal test', () => {
|
||||
const columns = [
|
||||
{ id: 'other', filterChoices: [{ name: 'Test', value: 'test' }] },
|
||||
];
|
||||
const expected = [
|
||||
{ name: 'Test', value: 'test' },
|
||||
];
|
||||
const actual = getFilterOptions(columns);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('processFilters', () => {
|
||||
const setAllFilters = jest.fn();
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should call setAllFilters with an empty array', () => {
|
||||
const filters = [];
|
||||
const columns = [
|
||||
{ id: 'locked', filterChoices: [{ name: 'Locked', value: true }] },
|
||||
];
|
||||
const expectedParameter = [];
|
||||
processFilters(filters, columns, setAllFilters);
|
||||
|
||||
expect(setAllFilters).toHaveBeenCalledWith(expectedParameter);
|
||||
});
|
||||
|
||||
describe('switch case locked', () => {
|
||||
it('should call setAllFilters with locked filter', () => {
|
||||
const filters = ['locked'];
|
||||
const columns = [
|
||||
{ id: 'locked', filterChoices: [{ name: 'Locked', value: true }, { name: 'Public', value: false }] },
|
||||
];
|
||||
const expectedParameter = [{ id: 'locked', value: [true] }];
|
||||
processFilters(filters, columns, setAllFilters);
|
||||
|
||||
expect(setAllFilters).toHaveBeenCalledWith(expectedParameter);
|
||||
});
|
||||
|
||||
it('should call setAllFilters with public filter', () => {
|
||||
const filters = ['public', 'filter'];
|
||||
const columns = [
|
||||
{ id: 'locked', filterChoices: [{ name: 'Public', value: false }] },
|
||||
{ id: 'test', filterChoices: [{ name: 'Filter', value: 'filter' }] },
|
||||
];
|
||||
const expectedParameter = [
|
||||
{ id: 'locked', value: [false] },
|
||||
{ id: 'test', value: ['filter'] },
|
||||
];
|
||||
processFilters(filters, columns, setAllFilters);
|
||||
|
||||
expect(setAllFilters).toHaveBeenCalledWith(expectedParameter);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case usageLocations', () => {
|
||||
it('should call setAllFilters with active filter', () => {
|
||||
const filters = ['active'];
|
||||
const columns = [
|
||||
{ id: 'usageLocations', filterChoices: [{ name: 'Active', value: true }] },
|
||||
];
|
||||
const expectedParameter = [{ id: 'usageLocations', value: [true] }];
|
||||
processFilters(filters, columns, setAllFilters);
|
||||
|
||||
expect(setAllFilters).toHaveBeenCalledWith(expectedParameter);
|
||||
});
|
||||
|
||||
it('should call setAllFilters with inactive filter', () => {
|
||||
const filters = ['inactive', 'filter'];
|
||||
const columns = [
|
||||
{ id: 'usageLocations', filterChoices: [{ name: 'Inactive', value: false }] },
|
||||
{ id: 'test', filterChoices: [{ name: 'Filter', value: 'filter' }] },
|
||||
];
|
||||
const expectedParameter = [
|
||||
{ id: 'usageLocations', value: [false] },
|
||||
{ id: 'test', value: ['filter'] },
|
||||
];
|
||||
processFilters(filters, columns, setAllFilters);
|
||||
|
||||
expect(setAllFilters).toHaveBeenCalledWith(expectedParameter);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case transcripts', () => {
|
||||
it('should call setAllFilters with transcribed filter', () => {
|
||||
const filters = ['transcribed', 'filter'];
|
||||
const columns = [
|
||||
{ id: 'transcripts', filterChoices: [{ name: 'Transcribed', value: true }] },
|
||||
{ id: 'test', filterChoices: [{ name: 'Filter', value: 'filter' }] },
|
||||
];
|
||||
const expectedParameter = [
|
||||
{ id: 'transcripts', value: [true] },
|
||||
{ id: 'test', value: ['filter'] },
|
||||
];
|
||||
processFilters(filters, columns, setAllFilters);
|
||||
|
||||
expect(setAllFilters).toHaveBeenCalledWith(expectedParameter);
|
||||
});
|
||||
|
||||
it('should call setAllFilters with notTranscribed filter', () => {
|
||||
const filters = ['notTranscribed'];
|
||||
const columns = [
|
||||
{ id: 'transcripts', filterChoices: [{ name: 'Not transcribed', value: false }] },
|
||||
];
|
||||
const expectedParameter = [{ id: 'transcripts', value: [false] }];
|
||||
processFilters(filters, columns, setAllFilters);
|
||||
|
||||
expect(setAllFilters).toHaveBeenCalledWith(expectedParameter);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switch case default', () => {
|
||||
it('should call setAllFilters with test filter', () => {
|
||||
const filters = ['filter'];
|
||||
const columns = [
|
||||
{ id: 'test', filterChoices: [{ name: 'Filter', value: 'filter' }] },
|
||||
];
|
||||
const expectedParameter = [{ id: 'test', value: ['filter'] }];
|
||||
processFilters(filters, columns, setAllFilters);
|
||||
|
||||
expect(setAllFilters).toHaveBeenCalledWith(expectedParameter);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,12 @@ import { Badge } from '@edx/paragon';
|
||||
|
||||
const StatusColumn = ({ row }) => {
|
||||
const { status } = row.original;
|
||||
const isUploaded = status === 'Success';
|
||||
|
||||
if (isUploaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge variant="light">
|
||||
{status}
|
||||
|
||||
55
src/files-and-videos/generic/table-components/utils.js
Normal file
55
src/files-and-videos/generic/table-components/utils.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import messages from '../messages';
|
||||
|
||||
const getFilterDisplayName = (column, values) => {
|
||||
const displayNames = [];
|
||||
const { filterChoices } = column;
|
||||
values.forEach(value => {
|
||||
const [displayName] = filterChoices.filter(choice => choice.value === value);
|
||||
displayNames.push(displayName);
|
||||
});
|
||||
return displayNames;
|
||||
};
|
||||
|
||||
export const getFilters = (state, columns) => {
|
||||
const { filters } = state;
|
||||
const filterableColumns = columns.filter(column => column?.filterChoices);
|
||||
const allFilters = [];
|
||||
filters.forEach(filter => {
|
||||
const { id, value } = filter;
|
||||
const [filterColumn] = filterableColumns.filter(column => column.id === id);
|
||||
const currentFilters = getFilterDisplayName(filterColumn, value);
|
||||
allFilters.push(...currentFilters);
|
||||
});
|
||||
return allFilters;
|
||||
};
|
||||
|
||||
export const removeFilter = (filter, setFilter, setAllFilters, state) => {
|
||||
const { filters } = state;
|
||||
const [editedFilter] = filters.filter(currentFilter => currentFilter.value.includes(filter));
|
||||
const updatedFilterValue = editedFilter.value.filter(value => value !== filter);
|
||||
if (isEmpty(updatedFilterValue)) {
|
||||
const updatedFilters = filters.filter(currentFilter => currentFilter.id !== editedFilter.id);
|
||||
setAllFilters(updatedFilters);
|
||||
} else {
|
||||
setFilter(editedFilter.id, updatedFilterValue);
|
||||
}
|
||||
};
|
||||
|
||||
export const getCurrentViewRange = ({
|
||||
filterRowCount,
|
||||
initialRowCount,
|
||||
fileCount,
|
||||
intl,
|
||||
}) => {
|
||||
if (filterRowCount === initialRowCount) {
|
||||
return intl.formatMessage(
|
||||
messages.rowStatusMessage,
|
||||
{ fileCount, rowCount: initialRowCount },
|
||||
);
|
||||
}
|
||||
return intl.formatMessage(
|
||||
messages.rowStatusMessage,
|
||||
{ fileCount, rowCount: filterRowCount },
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,21 @@
|
||||
@import "files-and-videos/videos-page/transcript-settings/TranscriptSettings";
|
||||
@import "files-and-videos/videos-page/VideoThumbnail";
|
||||
@import "files-and-videos/generic/table-components/GalleryCard"
|
||||
@import "files-and-videos/generic/table-components/GalleryCard";
|
||||
|
||||
.files-table {
|
||||
#table-filters-dropdown {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.pgn__form-control-set-inline {
|
||||
.pgn__form-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
width: 90%;
|
||||
display: inline-grid;
|
||||
grid-auto-flow: row;
|
||||
gap: 24px 16px;
|
||||
grid-template-columns: repeat(3, 33%);
|
||||
}
|
||||
|
||||
@@ -38,18 +38,7 @@ const VideoThumbnail = ({
|
||||
}
|
||||
const supportedFiles = videoImageSettings?.supportedFileFormats
|
||||
? Object.values(videoImageSettings.supportedFileFormats) : null;
|
||||
let isUploaded = false;
|
||||
|
||||
switch (status) {
|
||||
case 'Ready':
|
||||
isUploaded = true;
|
||||
break;
|
||||
case 'Imported':
|
||||
isUploaded = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const isUploaded = status === 'Success';
|
||||
|
||||
const showThumbnail = allowThumbnailUpload && thumbnail && isUploaded;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
injectIntl,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
useToggle,
|
||||
ActionRow,
|
||||
Button,
|
||||
CheckboxFilter,
|
||||
} from '@edx/paragon';
|
||||
import Placeholder from '@edx/frontend-lib-content-components';
|
||||
|
||||
@@ -109,20 +111,33 @@ const VideosPage = ({
|
||||
const transcriptColumn = {
|
||||
id: 'transcripts',
|
||||
Header: 'Transcript',
|
||||
accessor: (({ transcripts }) => !isEmpty(transcripts)),
|
||||
Cell: ({ row }) => {
|
||||
const { transcripts } = row.original;
|
||||
const numOfTranscripts = transcripts?.length;
|
||||
return numOfTranscripts > 0 ? `(${numOfTranscripts}) available` : null;
|
||||
},
|
||||
Filter: CheckboxFilter,
|
||||
filterChoices: [
|
||||
{ name: intl.formatMessage(messages.transcribedCheckboxLabel), value: true },
|
||||
{ name: intl.formatMessage(messages.notTranscribedCheckboxLabel), value: false },
|
||||
],
|
||||
};
|
||||
const activeColumn = {
|
||||
id: 'usageLocations',
|
||||
Header: 'Active',
|
||||
accessor: (({ usageLocations }) => !isEmpty(usageLocations)),
|
||||
Cell: ({ row }) => ActiveColumn({ row }),
|
||||
Filter: CheckboxFilter,
|
||||
filterChoices: [
|
||||
{ name: intl.formatMessage(messages.activeCheckboxLabel), value: true },
|
||||
{ name: intl.formatMessage(messages.inactiveCheckboxLabel), value: false },
|
||||
],
|
||||
};
|
||||
const durationColumn = {
|
||||
id: 'duration',
|
||||
Header: 'Video length',
|
||||
accessor: 'duration',
|
||||
Cell: ({ row }) => {
|
||||
const { duration } = row.original;
|
||||
return getFormattedDuration(duration);
|
||||
@@ -132,6 +147,8 @@ const VideosPage = ({
|
||||
id: 'status',
|
||||
Header: '',
|
||||
Cell: ({ row }) => StatusColumn({ row }),
|
||||
Filter: CheckboxFilter,
|
||||
filterChoices: [{ name: intl.formatMessage(messages.processingCheckboxLabel), value: 'Processing' }],
|
||||
};
|
||||
const videoThumbnailColumn = {
|
||||
id: 'courseVideoImageUrl',
|
||||
|
||||
@@ -336,38 +336,84 @@ describe('FilesAndUploads', () => {
|
||||
expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL);
|
||||
});
|
||||
|
||||
it('sort button should be enabled and sort files by name', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const sortsButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
|
||||
expect(sortsButton).toBeVisible();
|
||||
describe('Sort and filter button', () => {
|
||||
beforeEach(async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const sortAndFilterButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
|
||||
|
||||
await waitFor(() => {
|
||||
fireEvent.click(sortsButton);
|
||||
expect(screen.getByText(messages.sortModalTitleLabel.defaultMessage)).toBeVisible();
|
||||
await waitFor(() => {
|
||||
fireEvent.click(sortAndFilterButton);
|
||||
});
|
||||
});
|
||||
|
||||
const sortNameAscendingButton = screen.getByText(messages.sortByNameAscending.defaultMessage);
|
||||
fireEvent.click(sortNameAscendingButton);
|
||||
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
|
||||
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
|
||||
});
|
||||
describe('sort function', () => {
|
||||
it('should be enabled and sort files by name', async () => {
|
||||
const sortNameAscendingButton = screen.getByText(messages.sortByNameAscending.defaultMessage);
|
||||
fireEvent.click(sortNameAscendingButton);
|
||||
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
|
||||
|
||||
it('sort button should be enabled and sort files by file size', async () => {
|
||||
renderComponent();
|
||||
await mockStore(RequestStatus.SUCCESSFUL);
|
||||
const sortsButton = screen.getByText(messages.sortButtonLabel.defaultMessage);
|
||||
expect(sortsButton).toBeVisible();
|
||||
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
fireEvent.click(sortsButton);
|
||||
expect(screen.getByText(messages.sortModalTitleLabel.defaultMessage)).toBeVisible();
|
||||
it('sort button should be enabled and sort files by file size', async () => {
|
||||
const sortBySizeDescendingButton = screen.getByText(messages.sortBySizeDescending.defaultMessage);
|
||||
fireEvent.click(sortBySizeDescendingButton);
|
||||
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
|
||||
|
||||
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
const sortBySizeDescendingButton = screen.getByText(messages.sortBySizeDescending.defaultMessage);
|
||||
fireEvent.click(sortBySizeDescendingButton);
|
||||
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
|
||||
expect(screen.queryByText(messages.sortModalTitleLabel.defaultMessage)).toBeNull();
|
||||
describe('filter function', () => {
|
||||
it('should filter videos with transcripts', async () => {
|
||||
const notTranscribedCheckboxFilter = screen.getByText(
|
||||
videoMessages.notTranscribedCheckboxLabel.defaultMessage,
|
||||
);
|
||||
const transcribedCheckboxFilter = screen.getByText(videoMessages.transcribedCheckboxLabel.defaultMessage);
|
||||
fireEvent.click(transcribedCheckboxFilter);
|
||||
fireEvent.click(notTranscribedCheckboxFilter);
|
||||
fireEvent.click(transcribedCheckboxFilter);
|
||||
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
|
||||
|
||||
const galleryCards = screen.getAllByTestId('grid-card', { exact: false });
|
||||
|
||||
expect(galleryCards).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should clearAll selections', async () => {
|
||||
const sortByNewest = screen.getByText(messages.sortByNewest.defaultMessage);
|
||||
const sortBySizeDescendingButton = screen.getByText(messages.sortBySizeDescending.defaultMessage);
|
||||
const transcribedCheckboxFilter = screen.getByLabelText(
|
||||
videoMessages.transcribedCheckboxLabel.defaultMessage,
|
||||
);
|
||||
|
||||
fireEvent.click(sortBySizeDescendingButton);
|
||||
fireEvent.click(transcribedCheckboxFilter);
|
||||
|
||||
const clearAllButton = screen.getByText('Clear all');
|
||||
await waitFor(() => fireEvent.click(clearAllButton));
|
||||
|
||||
expect(transcribedCheckboxFilter).toHaveProperty('checked', false);
|
||||
|
||||
expect(within(sortBySizeDescendingButton).getByLabelText('file size descending radio'))
|
||||
.toHaveProperty('checked', false);
|
||||
|
||||
expect(within(sortByNewest).getByLabelText('date added descending radio'))
|
||||
.toHaveProperty('checked', true);
|
||||
});
|
||||
|
||||
it('should remove Transcribed filter chip', async () => {
|
||||
const transcribedCheckboxFilter = screen.getByText(videoMessages.transcribedCheckboxLabel.defaultMessage);
|
||||
fireEvent.click(transcribedCheckboxFilter);
|
||||
fireEvent.click(screen.getByText(messages.applySortButton.defaultMessage));
|
||||
|
||||
const imageFilterChip = screen.getByTestId('icon-after');
|
||||
fireEvent.click(imageFilterChip);
|
||||
|
||||
expect(screen.queryByText(videoMessages.transcribedCheckboxLabel.defaultMessage)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export const updateFileValues = (files) => {
|
||||
clientVideoId,
|
||||
created,
|
||||
courseVideoImageUrl,
|
||||
status,
|
||||
} = file;
|
||||
const wrapperType = 'video';
|
||||
|
||||
@@ -29,6 +30,13 @@ export const updateFileValues = (files) => {
|
||||
thumbnail = `${getConfig().STUDIO_BASE_URL}${thumbnail}`;
|
||||
}
|
||||
|
||||
let uploadStatus = status;
|
||||
if (status === 'Ready' || status === 'Imported') {
|
||||
uploadStatus = 'Success';
|
||||
} else if (status === 'In Progress' || status === 'Uploaded') {
|
||||
uploadStatus = 'Processing';
|
||||
}
|
||||
|
||||
updatedFiles.push({
|
||||
...file,
|
||||
displayName: clientVideoId,
|
||||
@@ -36,6 +44,7 @@ export const updateFileValues = (files) => {
|
||||
wrapperType,
|
||||
dateAdded: created.toString(),
|
||||
usageLocations: [],
|
||||
status: uploadStatus,
|
||||
thumbnail,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,7 +98,7 @@ export const initialState = {
|
||||
created: '',
|
||||
courseVideoImageUrl: '/video',
|
||||
transcripts: [],
|
||||
status: 'Imported',
|
||||
status: 'In Progress',
|
||||
downloadLink: 'http://mOckID0.mp4',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -13,6 +13,26 @@ const messages = defineMessages({
|
||||
id: 'course-authoring.video-uploads.thumbnail.alt',
|
||||
defaultMessage: '{displayName} video thumbnail',
|
||||
},
|
||||
activeCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.activeCheckbox.label',
|
||||
defaultMessage: 'Active',
|
||||
},
|
||||
inactiveCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.inactiveCheckbox.label',
|
||||
defaultMessage: 'Inactive',
|
||||
},
|
||||
transcribedCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.transcribedCheckbox.label',
|
||||
defaultMessage: 'Transcribed',
|
||||
},
|
||||
notTranscribedCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.notTranscribedCheckbox.label',
|
||||
defaultMessage: 'Not transcribed',
|
||||
},
|
||||
processingCheckboxLabel: {
|
||||
id: 'course-authoring.files-and-videos.sort-and-filter.modal.filter.processingCheckbox.label',
|
||||
defaultMessage: 'Processing',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
Reference in New Issue
Block a user