UNPKG

@craftercms/studio-ui

Version:

Services, components, models & utils to build CrafterCMS authoring extensions.

395 lines (393 loc) 12.4 kB
/* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import React, { useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import Typography from '@mui/material/Typography'; import ExpandMoreIcon from '@mui/icons-material/KeyboardArrowDown'; import { camelize } from '../../utils/string'; import { makeStyles } from 'tss-react/mui'; import CheckIcon from '@mui/icons-material/Check'; import SiteSearchSortBy from '../SiteSearchSortBy'; import SiteSearchSortOrder from '../SiteSearchSortOrder'; import SiteSearchFilter from '../SiteSearchFilter'; import PathSelector from '../SiteSearchPathSelector'; import Button from '@mui/material/Button'; import palette from '../../styles/palette'; import MuiAccordion, { accordionClasses } from '@mui/material/Accordion'; import MuiAccordionSummary, { accordionSummaryClasses } from '@mui/material/AccordionSummary'; import AccordionDetails from '@mui/material/AccordionDetails'; import Divider from '@mui/material/Divider'; import { useSpreadState } from '../../hooks/useSpreadState'; import { styled } from '@mui/material/styles'; import useContentTypes from '../../hooks/useContentTypes'; import { getMimeTypeTranslation } from '../../utils/mimeTypes'; import Box from '@mui/material/Box'; const AccordionSummary = styled(MuiAccordionSummary)(() => ({ [`&.${accordionSummaryClasses.expanded}`]: { minHeight: 0, [`& .${accordionSummaryClasses.content}`]: { margin: '12px 0' } } })); const Accordion = styled(MuiAccordion)(() => ({ [`&.${accordionClasses.expanded}`]: { margin: 'auto' } })); const useStyles = makeStyles()((theme) => ({ header: { width: '100%', padding: '10px 15px 10px 20px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', border: 'none', color: theme.palette.mode === 'dark' ? palette.white : '' }, accordionTitle: { display: 'flex', fontWeight: 600, alignItems: 'center' }, divider: { width: 'auto', margin: '0 10px' } })); const messages = defineMessages({ path: { id: 'words.path', defaultMessage: 'Path' }, sortBy: { id: 'searchFilter.sortBy', defaultMessage: 'Sort By' }, relevance: { id: 'words.relevance', defaultMessage: 'Relevance' }, internalName: { id: 'searchFilter.internalName', defaultMessage: 'Name' }, width: { id: 'words.width', defaultMessage: 'Width' }, contentType: { id: 'searchFilter.contentType', defaultMessage: 'Content Type' }, mimeType: { id: 'searchFilter.mimeType', defaultMessage: 'MIME Type' }, size: { id: 'searchFilter.size', defaultMessage: 'Content Size' }, lastEditDate: { id: 'searchFilter.lastEditDate', defaultMessage: 'Last Edit Date' }, height: { id: 'words.height', defaultMessage: 'Height' }, clearFilters: { id: 'searchFilter.clearFilters', defaultMessage: 'Clear Filters' }, expandAll: { id: 'common.expandAll', defaultMessage: 'Expand All' }, collapseAll: { id: 'common.collapseAll', defaultMessage: 'Collapse All' } }); const filterToFacet = (filterKey, filterValue) => { const isMultiple = typeof filterValue === 'object'; const isDate = !isMultiple && filterValue.includes('TODATE'); const isRange = !isMultiple && !isDate && filterValue.includes('TO'); const name = filterKey; let values = {}; if (isMultiple) { Object.keys(filterValue).forEach((value) => { values[value] = 0; }); } else if (isDate) { const deserializedValue = filterValue.match(/(.+)TODATE(.+)ID(.+)/); const id = deserializedValue[3].replace(filterKey, ''); const from = deserializedValue[1] === 'null' ? null : deserializedValue[1]; const to = deserializedValue[2] === 'null' ? null : deserializedValue[2]; values[id] = { count: 0, from, to }; } else { const deserializedValue = filterValue.match(/(.+)?TO(.+)?/); const rangeStart = deserializedValue[1]; const rangeEnd = deserializedValue[2]; const id = `${rangeStart !== null && rangeStart !== void 0 ? rangeStart : '*'}-${ rangeEnd !== null && rangeEnd !== void 0 ? rangeEnd : '*' }`; values[id] = { count: 0, from: rangeStart !== null && rangeStart !== void 0 ? rangeStart : null, to: rangeEnd !== null && rangeEnd !== void 0 ? rangeEnd : null }; } return { date: isDate, multiple: isMultiple, name, range: isRange, values }; }; export function SiteSearchFilters(props) { const { classes } = useStyles(); // region const { ... } = props const { sortBy, sortOrder, facets, handleFilterChange, mode, checkedFilters, setCheckedFilters, clearFilters, handleClearClick, selectedPath, setSelectedPath } = props; // endregion const { formatMessage } = useIntl(); const [expanded, setExpanded] = useSpreadState({ sortBy: false, path: false }); const contentTypes = useContentTypes(); let filterKeys = []; let facetsLookupTable = {}; const facetLabelLookup = {}; const parsedSelectedPath = selectedPath === null || selectedPath === void 0 ? void 0 : selectedPath.trim().replace('.+', '').replace(/\/$/, ''); // Don't want the root path set everytime select path changes. Only when select mode is first detected for it to get set once. // TODO: Ultimately, the rootPath should change to be driven by a different explicit prop other than `mode`. // eslint-disable-next-line react-hooks/exhaustive-deps const rootPath = useMemo(() => (mode === 'select' ? parsedSelectedPath : null), [mode]); const addFacetValuesLabels = (facet) => { Object.keys(facet.values).forEach((value) => { var _a, _b; let label = value; if (facet.name === 'content-type') { label = (_b = (_a = contentTypes === null || contentTypes === void 0 ? void 0 : contentTypes[value]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : value; } else if (facet.name === 'mime-type') { label = getMimeTypeTranslation(value, formatMessage); } facetLabelLookup[value] = label; }); }; facets.forEach((facet) => { filterKeys.push(facet.name); facetsLookupTable[facet.name] = facet; addFacetValuesLabels(facet); }); // Add filters already selected not coming from facets Object.keys(checkedFilters).forEach((filterKey) => { if (!filterKeys.includes(filterKey)) { filterKeys.push(filterKey); facetsLookupTable[filterKey] = filterToFacet(filterKey, checkedFilters[filterKey]); } }); const handleExpandClick = (item) => { setExpanded({ [item]: !expanded[item] }); }; const pathToFilter = (path) => { if (path) { if (path.endsWith('/')) { return `${path}.+`; } else { return `${path}/.+`; } } else { return undefined; } }; const onPathSelected = (path) => { handleFilterChange({ name: 'path', value: pathToFilter(path) }); setSelectedPath(path); }; const expandCollapseAll = (expand) => { const state = {}; state.path = expand; state.sortBy = expand; filterKeys.forEach((key) => { state[key] = expand; }); setExpanded(state); }; return React.createElement( Box, null, React.createElement( Box, { sx: { p: 1, display: 'flex', justifyContent: 'space-evenly' } }, React.createElement( Button, { size: 'small', onClick: () => expandCollapseAll(true) }, formatMessage(messages.expandAll) ), React.createElement( Button, { size: 'small', onClick: () => expandCollapseAll(false) }, formatMessage(messages.collapseAll) ), React.createElement( Button, { size: 'small', disabled: Object.keys(checkedFilters).length === 0 && !Boolean(selectedPath), onClick: clearFilters }, formatMessage(messages.clearFilters) ) ), React.createElement(Divider, { className: classes.divider }), React.createElement( Accordion, { expanded: expanded.sortBy, elevation: 0, onChange: () => handleExpandClick('sortBy') }, React.createElement( AccordionSummary, { expandIcon: React.createElement(ExpandMoreIcon, null) }, React.createElement( Typography, { className: classes.accordionTitle }, formatMessage(messages.sortBy), sortBy && React.createElement(CheckIcon, null) ) ), React.createElement( AccordionDetails, null, React.createElement( React.Fragment, null, React.createElement(SiteSearchSortBy, { sortBy: sortBy, filterKeys: filterKeys, handleFilterChange: handleFilterChange }), React.createElement(SiteSearchSortOrder, { sortOrder: sortOrder, sortBy: sortBy, handleFilterChange: handleFilterChange }) ) ) ), React.createElement( Accordion, { expanded: expanded.path, elevation: 0, onChange: () => handleExpandClick('path') }, React.createElement( AccordionSummary, { expandIcon: React.createElement(ExpandMoreIcon, null) }, React.createElement( Typography, { className: classes.accordionTitle }, formatMessage(messages.path), selectedPath && React.createElement(CheckIcon, null) ) ), React.createElement( AccordionDetails, null, React.createElement(PathSelector, { value: parsedSelectedPath, onPathSelected: onPathSelected, rootPath: rootPath }) ) ), filterKeys.map((key) => { var _a; return React.createElement( Accordion, { key: key, expanded: (_a = expanded[key]) !== null && _a !== void 0 ? _a : false, elevation: 0, onChange: () => handleExpandClick(key) }, React.createElement( AccordionSummary, { expandIcon: React.createElement(ExpandMoreIcon, null) }, React.createElement( Typography, { className: classes.accordionTitle }, formatMessage(messages[camelize(key)]), checkedFilters[key] && React.createElement(CheckIcon, null) ) ), React.createElement( AccordionDetails, null, React.createElement(SiteSearchFilter, { facet: key, handleFilterChange: handleFilterChange, checkedFilters: checkedFilters, setCheckedFilters: setCheckedFilters, facetsLookupTable: facetsLookupTable, facetLabelLookup: facetLabelLookup, handleClearClick: handleClearClick }) ) ); }) ); } export default SiteSearchFilters;