@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
256 lines (254 loc) • 8.93 kB
JavaScript
/*
* 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, { useEffect, useMemo, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import MenuItem from '@mui/material/MenuItem';
import { makeStyles } from 'tss-react/mui';
import { contentTreeFieldSelected, setContentTypeFilter, setPreviewEditMode } from '../../state/actions/preview';
import { useDispatch } from 'react-redux';
import Suspencified from '../Suspencified/Suspencified';
import SearchBar from '../SearchBar/SearchBar';
import Select from '@mui/material/Select';
import ListItem from '@mui/material/ListItem';
import Avatar from '@mui/material/Avatar';
import { getInitials } from '../../utils/string';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import ListItemText from '@mui/material/ListItemText';
import CircularProgress from '@mui/material/CircularProgress';
import { getHostToGuestBus } from '../../utils/subjects';
import EmptyState from '../EmptyState/EmptyState';
import { useSelection } from '../../hooks/useSelection';
import { usePreviewGuest } from '../../hooks/usePreviewGuest';
import { useContentTypes } from '../../hooks/useContentTypes';
import { useLogicResource } from '../../hooks/useLogicResource';
const translations = defineMessages({
previewInPageInstancesPanel: {
id: 'previewInPageInstancesPanel.title',
defaultMessage: 'In this Page'
},
noResults: {
id: 'previewInPageInstancesPanel.noResults',
defaultMessage: 'No results found.'
},
selectContentType: {
id: 'previewInPageInstancesPanel.selectContentType',
defaultMessage: 'Select content type'
},
chooseContentType: {
id: 'previewInPageInstancesPanel.chooseContentType',
defaultMessage: 'Please choose a content type.'
}
});
const useStyles = makeStyles()(() => ({
search: {
padding: '15px 15px 0 15px'
},
Select: {
width: '100%',
marginTop: '15px'
},
noWrapping: {
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
display: 'block'
},
selectProgress: {
position: 'absolute',
right: '28px'
},
emptyStateTitle: {
fontSize: '1em'
},
item: {}
}));
export function PreviewInPageInstancesPanel() {
const { classes } = useStyles();
const { formatMessage } = useIntl();
const dispatch = useDispatch();
const contentTypeLookup = useContentTypes();
const contentTypeFilter = useSelection((state) => state.preview.components.contentTypeFilter);
const guest = usePreviewGuest();
const [keyword, setKeyword] = useState('');
const hostToGuest$ = getHostToGuestBus();
const editMode = useSelection((state) => state.preview.editMode);
const models = useMemo(() => {
return guest?.models;
}, [guest]);
const selectedModels = useMemo(() => {
return Object.values(models ?? []).filter((model) => {
return (
model.craftercms.contentTypeId === contentTypeFilter &&
(model.craftercms.label.toLowerCase().includes(keyword.toLowerCase()) ||
model.craftercms.contentTypeId.toLowerCase().includes(keyword.toLowerCase()))
);
});
// filter using .includes(keyword) on model.craftercms.label
}, [contentTypeFilter, models, keyword]);
const [contentTypes, setContentTypes] = useState([]);
// setting contentTypes
useEffect(() => {
if (models) {
const contentTypes = [];
Object.values(models ?? []).forEach((model) => {
// TODO: Groovy Controller Issue;
if (model.craftercms.contentTypeId && contentTypes.indexOf(model.craftercms.contentTypeId) <= 0) {
contentTypes.push(model.craftercms.contentTypeId);
}
});
setContentTypes(contentTypes);
}
return () => {
setContentTypes([]);
setKeyword('');
};
}, [models]);
const resource = useLogicResource(
{
models,
contentTypeFilter
},
{
shouldRenew: (source, resource) => Boolean(contentTypeFilter) && !keyword && resource.complete,
shouldResolve: (source) => Boolean(source.models),
shouldReject: (source) => false,
errorSelector: (source) => null,
resultSelector: (source) => {
return Object.values(source.models)?.filter((model) => model.craftercms.contentTypeId === contentTypeFilter);
}
}
);
const handleSearchKeyword = (keyword) => {
setKeyword(keyword);
};
const handleSelectChange = (value) => {
setKeyword('');
dispatch(setContentTypeFilter(value));
};
const onItemClick = (instance) => {
if (!editMode) {
dispatch(setPreviewEditMode({ editMode: true }));
}
hostToGuest$.next(
contentTreeFieldSelected({
name: instance.craftercms.label,
scrollElement: null,
iceProps: {
modelId: instance.craftercms.id,
fieldId: null,
index: null
}
})
);
return;
};
return React.createElement(
React.Fragment,
null,
React.createElement(
'div',
{ className: classes.search },
React.createElement(SearchBar, {
showActionButton: Boolean(keyword),
onChange: handleSearchKeyword,
keyword: keyword,
disabled: !Boolean(contentTypeFilter)
}),
React.createElement(
Select,
{
value: contentTypes.length ? contentTypeFilter : '',
displayEmpty: true,
className: classes.Select,
onChange: (event) => handleSelectChange(event.target.value),
endAdornment:
contentTypes.length && contentTypeLookup
? null
: React.createElement(CircularProgress, { size: 20, className: classes.selectProgress })
},
React.createElement(MenuItem, { value: '', disabled: true }, formatMessage(translations.selectContentType)),
contentTypeLookup &&
contentTypes.map((id, i) =>
React.createElement(MenuItem, { value: contentTypeLookup[id].id, key: i }, contentTypeLookup[id].name)
)
)
),
React.createElement(
Suspencified,
null,
React.createElement(InPageInstancesUI, {
resource: resource,
selectedModels: selectedModels,
onItemClick: onItemClick,
contentTypeFilter: contentTypeFilter
})
)
);
}
function InPageInstancesUI(props) {
const { resource, selectedModels, onItemClick, contentTypeFilter } = props;
resource.read();
const { classes } = useStyles();
const { formatMessage } = useIntl();
return React.createElement(
React.Fragment,
null,
selectedModels.length
? selectedModels.map((instance) =>
React.createElement(
ListItem,
{
key: instance.craftercms.id,
className: classes.item,
button: true,
onClick: () => onItemClick(instance)
},
React.createElement(
ListItemAvatar,
null,
React.createElement(Avatar, null, getInitials(instance.craftercms.label))
),
React.createElement(ListItemText, {
primary: instance.craftercms.label,
secondary: instance.craftercms.contentTypeId,
classes: { primary: classes.noWrapping, secondary: classes.noWrapping }
})
)
)
: React.createElement(EmptyState, {
title: contentTypeFilter
? formatMessage(translations.noResults)
: formatMessage(translations.chooseContentType),
classes: { title: classes.emptyStateTitle }
})
);
}
export default PreviewInPageInstancesPanel;