UNPKG

@craftercms/studio-ui

Version:

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

515 lines (513 loc) 16.5 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 SiteSearchToolBar from '../SiteSearchToolbar'; import React, { useRef } from 'react'; import Drawer from '@mui/material/Drawer'; import SiteSearchFilters from '../SiteSearchFilters'; import { makeStyles } from 'tss-react/mui'; import palette from '../../styles/palette'; import { drawerWidth } from '../Search/utils'; import FormGroup from '@mui/material/FormGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import Typography from '@mui/material/Typography'; import { translations } from '../Search/translations'; import TablePagination from '@mui/material/TablePagination'; import ApiResponseErrorState from '../ApiResponseErrorState'; import Grid from '@mui/material/Grid'; import MediaCard from '../MediaCard/MediaCard'; import EmptyState from '../EmptyState/EmptyState'; import ItemActionsSnackbar from '../ItemActionsSnackbar'; import Button from '@mui/material/Button'; import ListItemText from '@mui/material/ListItemText'; import { FormattedMessage, useIntl } from 'react-intl'; import IconButton from '@mui/material/IconButton'; import MoreVertRounded from '@mui/icons-material/MoreVertRounded'; import { UNDEFINED } from '../../utils/constants'; import { LoadingState } from '../LoadingState'; const useStyles = makeStyles()((theme) => ({ wrapper: { height: 'calc(100% - 65px)', // 100% - toolbar height margin: 'auto', display: 'flex', flexDirection: 'column', background: theme.palette.background.default, transition: theme.transitions.create('margin', { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen }), '&.hasContent': { height: 'inherit' }, '&.select': {} }, wrapperSelectMode: { height: 'calc(100% - 136px)' }, shift: { transition: theme.transitions.create('margin', { easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen }) }, searchHeader: { padding: '15px 20px', display: 'flex', justifyContent: 'center', alignItems: 'center', background: theme.palette.background.default, borderBottom: `1px solid ${theme.palette.divider}` }, searchDropdown: { marginRight: '7px' }, search: { width: '500px' }, searchHelperBar: { display: 'flex', padding: '0 6px 0 20px', alignItems: 'center', background: theme.palette.background.paper, borderBottom: `1px solid ${theme.palette.divider}` }, clearSelected: { marginLeft: '5px', cursor: 'pointer' }, helperContainer: { display: 'flex', marginLeft: 'auto', alignItems: 'center' }, content: { flexGrow: 1, padding: '25px 30px', overflowY: 'scroll', position: 'relative' }, empty: { height: '100%', justifyContent: 'center' }, pagination: { marginLeft: 'auto', '& p': { padding: 0 }, '& svg': { top: 'inherit' } }, dialogTitle: { display: 'flex', alignItems: 'center', marginLeft: '10px', padding: '10px 0' }, dialogCloseButton: { marginLeft: 'auto' }, mediaPreview: { maxWidth: '700px', minWidth: '400px', minHeight: '200px', display: 'flex', justifyContent: 'center', alignItems: 'center', '& img': { maxWidth: '100%' } }, videoPreview: {}, mediaCardListRoot: { display: 'flex' }, mediaCardListCheckbox: { justifyContent: 'center', order: -2, marginRight: '5px', marginLeft: '16px' }, mediaCardListHeader: { marginLeft: '15px' }, mediaCardListMedia: { paddingTop: 0, height: '80px', width: '80px', order: -1 }, mediaCardListMediaIcon: { height: '80px', width: '80px', paddingTop: '0', order: -1 }, drawer: { flexShrink: 0 }, drawerPaper: { top: 65, bottom: 0, width: drawerWidth, zIndex: theme.zIndex.appBar - 1, height: 'auto', position: 'absolute' }, drawerPaperSelect: { bottom: '71px' }, paginationSelectRoot: { marginRight: 0 }, paginationSelect: { border: 'none' }, filtersActive: { color: '#FFB400', marginLeft: '2px' }, selectAppbar: { boxShadow: 'none', borderBottom: `1px solid ${palette.gray.light3}` }, selectToolbar: { placeContent: 'center space-between', backgroundColor: theme.palette.mode === 'dark' ? theme.palette.background.default : palette.white }, selectToolbarTitle: { flexGrow: 1 }, drawerModal: { position: 'absolute', '& .MuiBackdrop-root': { background: 'transparent' } }, actionsMenu: { flex: '0 0 auto', display: 'flex', padding: '14px 20px', justifyContent: 'flex-end', borderTop: `1px solid ${theme.palette.divider}`, '& > :not(:first-child)': { marginLeft: '12px' } }, container: { height: '100%', position: 'relative', background: theme.palette.background.default }, cardActionArea: { width: 'auto', display: 'flex' }, cardHeader: { flexGrow: 1 } })); export function SearchUI(props) { const { classes, cx } = useStyles(); // region const { ... } = props const { areAllSelected, error, isFetching, sortBy, sortOrder, searchParameters, mode, currentView, desktopScreen, embedded, keyword, handleSearchKeyword, checkedFilters, drawerOpen, searchResults, toggleDrawer, handleChangeView, handleFilterChange, clearFilter, clearFilters, selectedPath, onSelectedPathChanges, onCheckedFiltersChanges, handleSelectAll, handleChangePage, handleChangeRowsPerPage, onPreview, handleSelect, selected, guestBase, selectionOptions, onHeaderButtonClick, onActionClicked, handleClearSelected, onClose, onAcceptSelection } = props; // endregion const { formatMessage } = useIntl(); const container = useRef(); return React.createElement( 'section', { ref: container, className: classes.container }, React.createElement(SiteSearchToolBar, { onChange: handleSearchKeyword, onMenuIconClick: toggleDrawer, handleChangeView: handleChangeView, currentView: currentView, keyword: keyword, showActionButton: Boolean(keyword), showTitle: mode === 'select' || (mode === 'default' && !embedded), embedded: embedded }), React.createElement( Drawer, { variant: desktopScreen ? 'persistent' : 'temporary', container: container.current, anchor: 'left', open: drawerOpen, className: classes.drawer, classes: { paper: cx(classes.drawerPaper, mode === 'select' && classes.drawerPaperSelect), modal: classes.drawerModal }, ModalProps: { ...(!desktopScreen && { onClose: () => toggleDrawer() }) } }, searchResults && searchResults.facets && React.createElement(SiteSearchFilters, { mode: mode, className: classes.searchDropdown, facets: searchResults.facets, handleFilterChange: handleFilterChange, sortBy: sortBy, sortOrder: sortOrder, checkedFilters: checkedFilters, setCheckedFilters: onCheckedFiltersChanges, clearFilters: clearFilters, handleClearClick: clearFilter, selectedPath: selectedPath, setSelectedPath: onSelectedPathChanges }) ), React.createElement( 'section', { className: cx(classes.wrapper, { [classes.shift]: drawerOpen, [classes.wrapperSelectMode]: mode === 'select' }), style: drawerOpen && desktopScreen ? { width: `calc(100% - ${drawerWidth}px`, marginLeft: drawerWidth } : { marginLeft: 0 } }, React.createElement( 'div', { className: classes.searchHelperBar }, React.createElement( FormGroup, null, React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { color: 'primary', checked: areAllSelected, onClick: (e) => handleSelectAll(e.target.checked) }), label: React.createElement(Typography, { color: 'textPrimary' }, formatMessage(translations.selectAll)) }) ), React.createElement(TablePagination, { rowsPerPageOptions: [9, 15, 21], className: classes.pagination, component: 'div', labelRowsPerPage: null, labelDisplayedRows: ({ from, to, count }) => React.createElement( React.Fragment, null, React.createElement(FormattedMessage, { id: 'search.resultsCaption', defaultMessage: '{from}-{to} of {count} results {keywordLength, plural, =0 {}other{ for <b>\u201C{keyword}\u201D</b>}}', values: { from, to, count, keyword: keyword.length > 18 ? keyword.substring(0, 15).trim() + '...' : keyword, keywordLength: keyword.length, b: (content) => React.createElement('strong', { key: content[0] }, content[0]) } }), (Object.keys(checkedFilters).length > 0 || Boolean(selectedPath)) && React.createElement( 'strong', null, React.createElement(FormattedMessage, { id: 'search.filtersActive', defaultMessage: ' \u2022 <span>Filters Active</span>', values: { span: (content) => React.createElement('span', { key: content[0], className: classes.filtersActive }, content[0]) } }) ) ), count: searchResults?.total ?? 0, rowsPerPage: searchParameters.limit, page: Math.ceil(searchParameters.offset / searchParameters.limit), backIconButtonProps: { 'aria-label': formatMessage(translations.previousPage) }, nextIconButtonProps: { 'aria-label': formatMessage(translations.nextPage) }, onPageChange: handleChangePage, onRowsPerPageChange: handleChangeRowsPerPage, classes: { selectRoot: classes.paginationSelectRoot, select: classes.paginationSelect } }) ), React.createElement( 'section', { className: classes.content }, error ? React.createElement(ApiResponseErrorState, { error: error }) : React.createElement( Grid, { container: true, spacing: 3, minHeight: '100%', alignContent: isFetching || searchResults === null ? 'center' : 'start', className: searchResults?.items.length === 0 ? classes.empty : '' }, isFetching || searchResults === null ? React.createElement(LoadingState, null) : React.createElement( React.Fragment, null, searchResults.items.length > 0 ? searchResults.items.map((item, i) => React.createElement( Grid, { key: i, item: true, xs: 12, ...(currentView === 'grid' ? { sm: 6, md: 4, lg: 4, xl: 3 } : {}) }, React.createElement(MediaCard, { classes: currentView === 'list' ? { root: classes.mediaCardListRoot, checkbox: classes.mediaCardListCheckbox, media: classes.mediaCardListMedia, mediaIcon: classes.mediaCardListMediaIcon, cardActionArea: classes.cardActionArea, cardHeader: classes.cardHeader } : {}, item: item, onPreview: mode === 'default' ? () => onPreview(item) : UNDEFINED, onClick: mode === 'select' ? () => handleSelect(item.path, !selected.includes(item.path)) : UNDEFINED, onSelect: handleSelect, selected: selected, previewAppBaseUri: guestBase, action: mode === 'default' ? React.createElement( IconButton, { onClick: (e) => onHeaderButtonClick(e, item), size: 'small' }, React.createElement(MoreVertRounded, null) ) : null }) ) ) : React.createElement(EmptyState, { title: formatMessage(translations.noResults), subtitle: formatMessage(translations.changeQuery) }) ) ) ) ), mode === 'default' && React.createElement(ItemActionsSnackbar, { open: selected.length > 0, options: selectionOptions, onActionClicked: onActionClicked, append: React.createElement( Button, { size: 'small', color: 'primary', variant: 'text', onClick: handleClearSelected }, React.createElement(ListItemText, { primary: formatMessage(translations.clearSelected, { count: selected.length }) }) ) }), mode === 'select' && React.createElement( 'section', { className: classes.actionsMenu }, React.createElement(Button, { variant: 'outlined', onClick: onClose }, formatMessage(translations.cancel)), React.createElement( Button, { variant: 'contained', color: 'primary', disabled: selected.length === 0, onClick: () => onAcceptSelection?.(selected) }, formatMessage(translations.acceptSelection) ) ) ); } export default SearchUI;