@mui/material#Popover TypeScript Examples
The following examples show how to use
@mui/material#Popover.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: PlayBar.tsx From rewind with MIT License | 5 votes |
function AudioButton() {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const handlePopOverOpen = (event: any) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const { volume, muted } = useAudioSettings();
const service = useAudioSettingsService();
const handleClick = () => {
service.toggleMuted();
};
return (
<>
<IconButton onClick={handlePopOverOpen}>{muted ? <VolumeOff /> : <VolumeUp />}</IconButton>
<Popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "top",
horizontal: "center",
}}
transformOrigin={{
vertical: "bottom",
horizontal: "center",
}}
>
<Box width={256}>
<BaseAudioSettingsPanel
master={volume.master}
music={volume.music}
/* Currently it's disabled */
effects={0}
onMutedChange={(x) => service.setMuted(x)}
onMasterChange={(x) => service.setMasterVolume(x)}
onMusicChange={(x) => service.setMusicVolume(x)}
onEffectsChange={(x) => service.setEffectsVolume(x)}
/>
</Box>
</Popover>
</>
);
}
Example #2
Source File: EntityCollectionView.tsx From firecms with MIT License | 4 votes |
/**
* This component is in charge of binding a datasource path with an {@link EntityCollection}
* where it's configuration is defined. It includes an infinite scrolling table,
* 'Add' new entities button,
*
* This component is the default one used for displaying entity collections
* and is in charge of generating all the specific actions and customization
* of the lower level {@link CollectionTable}
*
* Please **note** that you only need to use this component if you are building
* a custom view. If you just need to create a default view you can do it
* exclusively with config options.
*
* If you need a lower level implementation with more granular options, you
* can use {@link CollectionTable}.
*
* If you need a table that is not bound to the datasource or entities and
* properties at all, you can check {@link Table}
*
* @param path
* @param collection
* @constructor
* @category Components
*/
export function EntityCollectionView<M extends { [Key: string]: any }>({
path,
collection: baseCollection
}: EntityCollectionViewProps<M>
) {
const sideEntityController = useSideEntityController();
const context = useFireCMSContext();
const authController = useAuthController();
const navigationContext = useNavigation();
const theme = useTheme();
const largeLayout = useMediaQuery(theme.breakpoints.up("md"));
const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
const collectionResolver = navigationContext.getCollectionResolver<M>(path);
if (!collectionResolver) {
throw Error(`Couldn't find the corresponding collection view for the path: ${path}`);
}
const onCollectionModifiedForUser = useCallback((partialCollection: PartialEntityCollection<any>) => {
navigationContext.onCollectionModifiedForUser(path, partialCollection);
}, [path]);
const collection: EntityCollection<M> = collectionResolver ?? baseCollection;
const { schemaResolver } = collectionResolver;
const exportable = collection.exportable === undefined || collection.exportable;
const selectionEnabled = collection.selectionEnabled === undefined || collection.selectionEnabled;
const hoverRow = collection.inlineEditing !== undefined && !collection.inlineEditing;
const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
const selectionController = useSelectionController<M>();
const usedSelectionController = collection.selectionController ?? selectionController;
const {
selectedEntities,
toggleEntitySelection,
isEntitySelected,
setSelectedEntities
} = usedSelectionController;
useEffect(() => {
setDeleteEntityClicked(undefined);
}, [selectedEntities]);
const onEntityClick = useCallback((entity: Entity<M>) => {
return sideEntityController.open({
entityId: entity.id,
path,
permissions: collection.permissions,
schema: collection.schema,
subcollections: collection.subcollections,
callbacks: collection.callbacks,
overrideSchemaRegistry: false
});
}, [path, collection, sideEntityController]);
const onNewClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
return sideEntityController.open({
path,
permissions: collection.permissions,
schema: collection.schema,
subcollections: collection.subcollections,
callbacks: collection.callbacks,
overrideSchemaRegistry: false
});
}, [path, collection, sideEntityController]);
const internalOnEntityDelete = useCallback((_path: string, entity: Entity<M>) => {
setSelectedEntities(selectedEntities.filter((e) => e.id !== entity.id));
}, [selectedEntities, setSelectedEntities]);
const internalOnMultipleEntitiesDelete = useCallback((_path: string, entities: Entity<M>[]) => {
setSelectedEntities([]);
setDeleteEntityClicked(undefined);
}, [setSelectedEntities]);
const checkInlineEditing = useCallback((entity: Entity<any>) => {
if (!canEdit(collection.permissions, entity, authController, path, context)) {
return false;
}
return collection.inlineEditing === undefined || collection.inlineEditing;
}, [collection.inlineEditing, collection.permissions, path]);
const onColumnResize = useCallback(({
width,
key
}: OnColumnResizeParams) => {
// Only for property columns
if (!collection.schema.properties[key]) return;
const property: Partial<AnyProperty> = { columnWidth: width };
const updatedFields: PartialEntityCollection<any> = { schema: { properties: { [key as keyof M]: property } } };
if (onCollectionModifiedForUser)
onCollectionModifiedForUser(updatedFields)
}, [collection.schema.properties, onCollectionModifiedForUser]);
const onSizeChanged = useCallback((size: CollectionSize) => {
if (onCollectionModifiedForUser)
onCollectionModifiedForUser({ defaultSize: size })
}, [onCollectionModifiedForUser]);
const open = anchorEl != null;
const title = useMemo(() => (
<div style={{
padding: "4px"
}}>
<Typography
variant="h6"
style={{
lineHeight: "1.0",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
maxWidth: "160px",
cursor: collection.description ? "pointer" : "inherit"
}}
onClick={collection.description
? (e) => {
setAnchorEl(e.currentTarget);
e.stopPropagation();
}
: undefined}
>
{`${collection.name}`}
</Typography>
<Typography
style={{
display: "block",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
maxWidth: "160px",
direction: "rtl",
textAlign: "left"
}}
variant={"caption"}
color={"textSecondary"}>
{`/${path}`}
</Typography>
{collection.description &&
<Popover
id={"info-dialog"}
open={open}
anchorEl={anchorEl}
elevation={1}
onClose={() => {
setAnchorEl(null);
}}
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
transformOrigin={{
vertical: "top",
horizontal: "center"
}}
>
<Box m={2}>
<Markdown source={collection.description}/>
</Box>
</Popover>
}
</div>
), [collection.description, collection.name, path, open, anchorEl]);
const tableRowActionsBuilder = useCallback(({
entity,
size
}: { entity: Entity<any>, size: CollectionSize }) => {
const isSelected = isEntitySelected(entity);
const createEnabled = canCreate(collection.permissions, authController, path, context);
const editEnabled = canEdit(collection.permissions, entity, authController, path, context);
const deleteEnabled = canDelete(collection.permissions, entity, authController, path, context);
const onCopyClicked = (clickedEntity: Entity<M>) => sideEntityController.open({
entityId: clickedEntity.id,
path,
copy: true,
permissions: {
edit: editEnabled,
create: createEnabled,
delete: deleteEnabled
},
schema: collection.schema,
subcollections: collection.subcollections,
callbacks: collection.callbacks,
overrideSchemaRegistry: false
});
const onEditClicked = (clickedEntity: Entity<M>) => sideEntityController.open({
entityId: clickedEntity.id,
path,
permissions: {
edit: editEnabled,
create: createEnabled,
delete: deleteEnabled
},
schema: collection.schema,
subcollections: collection.subcollections,
callbacks: collection.callbacks,
overrideSchemaRegistry: false
});
return (
<CollectionRowActions
entity={entity}
isSelected={isSelected}
selectionEnabled={selectionEnabled}
size={size}
toggleEntitySelection={toggleEntitySelection}
onEditClicked={onEditClicked}
onCopyClicked={createEnabled ? onCopyClicked : undefined}
onDeleteClicked={deleteEnabled ? setDeleteEntityClicked : undefined}
/>
);
}, [usedSelectionController, sideEntityController, collection.permissions, authController, path]);
const toolbarActionsBuilder = useCallback((_: { size: CollectionSize, data: Entity<any>[] }) => {
const addButton = canCreate(collection.permissions, authController, path, context) && onNewClick && (largeLayout
? <Button
onClick={onNewClick}
startIcon={<Add/>}
size="large"
variant="contained"
color="primary">
Add {collection.schema.name}
</Button>
: <Button
onClick={onNewClick}
size="medium"
variant="contained"
color="primary"
>
<Add/>
</Button>);
const multipleDeleteEnabled = selectedEntities.every((entity) => canDelete(collection.permissions, entity, authController, path, context));
const onMultipleDeleteClick = (event: React.MouseEvent) => {
event.stopPropagation();
setDeleteEntityClicked(selectedEntities);
};
const multipleDeleteButton = selectionEnabled &&
<Tooltip
title={multipleDeleteEnabled ? "Multiple delete" : "You have selected one entity you cannot delete"}>
<span>
{largeLayout && <Button
disabled={!(selectedEntities?.length) || !multipleDeleteEnabled}
startIcon={<Delete/>}
onClick={onMultipleDeleteClick}
color={"primary"}
>
<p style={{ minWidth: 24 }}>({selectedEntities?.length})</p>
</Button>}
{!largeLayout &&
<IconButton
color={"primary"}
disabled={!(selectedEntities?.length) || !multipleDeleteEnabled}
onClick={onMultipleDeleteClick}
size="large">
<Delete/>
</IconButton>}
</span>
</Tooltip>;
const extraActions = collection.extraActions
? collection.extraActions({
path,
collection,
selectionController: usedSelectionController,
context
})
: undefined;
const exportButton = exportable &&
<ExportButton schema={collection.schema}
schemaResolver={schemaResolver}
exportConfig={typeof collection.exportable === "object" ? collection.exportable : undefined}
path={path}/>;
return (
<>
{extraActions}
{multipleDeleteButton}
{exportButton}
{addButton}
</>
);
}, [usedSelectionController, path, collection, largeLayout]);
return (
<>
<CollectionTable
key={`collection_table_${path}`}
title={title}
path={path}
collection={collection}
schemaResolver={schemaResolver}
onSizeChanged={onSizeChanged}
inlineEditing={checkInlineEditing}
onEntityClick={onEntityClick}
onColumnResize={onColumnResize}
tableRowActionsBuilder={tableRowActionsBuilder}
toolbarActionsBuilder={toolbarActionsBuilder}
hoverRow={hoverRow}
/>
{deleteEntityClicked && <DeleteEntityDialog entityOrEntitiesToDelete={deleteEntityClicked}
path={path}
schema={collection.schema}
schemaResolver={schemaResolver}
callbacks={collection.callbacks}
open={!!deleteEntityClicked}
onEntityDelete={internalOnEntityDelete}
onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
onClose={() => setDeleteEntityClicked(undefined)}/>}
</>
);
}
Example #3
Source File: TableHeader.tsx From firecms with MIT License | 4 votes |
function TableHeaderInternal<M extends { [Key: string]: any }>({
sort,
onColumnSort,
onFilterUpdate,
filter,
column
}: TableHeaderProps<M>) {
const [onHover, setOnHover] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const classes = useStyles({ onHover, align: column.align });
const [open, setOpen] = React.useState(false);
const handleSettingsClick = useCallback((event: any) => {
setOpen(true);
}, []);
const handleClose = useCallback(() => {
setOpen(false);
}, []);
const update = useCallback((filterForProperty?: [TableWhereFilterOp, any]) => {
onFilterUpdate(filterForProperty);
setOpen(false);
}, []);
return (
<ErrorBoundary>
<Grid
className={classes.header}
ref={ref}
wrap={"nowrap"}
alignItems={"center"}
onMouseEnter={() => setOnHover(true)}
onMouseMove={() => setOnHover(true)}
onMouseLeave={() => setOnHover(false)}
container>
<Grid item xs={true} className={classes.headerTitle}>
<div className={classes.headerInternal}>
<div className={classes.headerIcon}>
{column.icon && column.icon(onHover || open)}
</div>
<div className={classes.headerTitleInternal}>
{column.label}
</div>
</div>
</Grid>
{column.sortable && (sort || onHover || open) &&
<Grid item>
<Badge color="secondary"
variant="dot"
overlap="circular"
invisible={!sort}>
<IconButton
size={"small"}
className={classes.headerIconButton}
onClick={() => {
onColumnSort(column.key as Extract<keyof M, string>);
}}
>
{!sort && <ArrowUpwardIcon fontSize={"small"}/>}
{sort === "asc" &&
<ArrowUpwardIcon fontSize={"small"}/>}
{sort === "desc" &&
<ArrowDownwardIcon fontSize={"small"}/>}
</IconButton>
</Badge>
</Grid>
}
{column.filter && <Grid item>
<Badge color="secondary"
variant="dot"
overlap="circular"
invisible={!filter}>
<IconButton
className={classes.headerIconButton}
size={"small"}
onClick={handleSettingsClick}>
<ArrowDropDownCircleIcon fontSize={"small"}
color={onHover || open ? undefined : "disabled"}/>
</IconButton>
</Badge>
</Grid>}
</Grid>
{column.sortable && <Popover
id={open ? `popover_${column.key}` : undefined}
open={open}
elevation={2}
anchorEl={ref.current}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "right"
}}
>
<FilterForm column={column}
filter={filter}
onFilterUpdate={update}/>
</Popover>}
</ErrorBoundary>
);
}
Example #4
Source File: ColorPicker.tsx From Cromwell with MIT License | 4 votes |
export function ColorPicker(props: {
label?: string;
value?: string;
className?: string;
style?: React.CSSProperties;
onChange?: (color: string) => void;
}) {
const colorRef = useRef<string | null>(null);
const prevValue = useRef<string | null>(null);
const inputAnchorRef = useRef<HTMLDivElement | null>(null);
const [open, setOpen] = useState(false);
const forceUpdate = useForceUpdate();
if (props.value !== prevValue.current) {
prevValue.current = props.value;
colorRef.current = props.value;
}
const handleChange = (color: { hex: string; rgb: { r: number; g: number; b: number; a: number } }) => {
const colorStr = color.rgb.a === 1 ? color.hex : `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`;
colorRef.current = colorStr;
forceUpdate();
}
const handleClose = () => {
handleApply();
setOpen(false);
}
const handleApply = () => {
props.onChange?.(colorRef.current);
}
const handleInputChange = (event) => {
colorRef.current = event.target.value;
forceUpdate();
handleApply();
}
return (
<>
<TextField
InputProps={{
startAdornment: (
<InputAdornment position="start">
<div style={{ backgroundColor: colorRef.current, width: '20px', height: '20px', borderRadius: '100%' }}></div>
</InputAdornment>
),
}}
variant="standard"
className={props.className}
label={props.label}
fullWidth
value={colorRef.current}
ref={inputAnchorRef}
onChange={handleInputChange}
onClick={() => setOpen(true)}
style={props.style}
/>
<Popover
open={open}
anchorEl={inputAnchorRef.current}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<div>
<SketchPicker
color={colorRef.current ?? '#000'}
onChangeComplete={handleChange}
/>
</div>
</Popover>
</>
)
}
Example #5
Source File: EntityTable.tsx From Cromwell with MIT License | 4 votes |
render() {
const tableColumns = this.getColumns();
return (
<div className={styles.EntityTable}>
<div className={styles.header}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<h1 className={styles.pageTitle}>{this.props.listLabel}</h1>
{this.props.customElements?.listLeftActions}
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
{this.props.customElements?.listRightActions}
{!!(this.filters?.length || this.sortBy?.column
|| this.props.isFilterActive?.()) && (
<Tooltip title="Clear filters">
<IconButton className={styles.iconButton}
onClick={this.clearAllFilters}>
<ClearAllIcon />
</IconButton>
</Tooltip>
)}
<DeleteSelectedButton
style={{ marginRight: '10px' }}
onClick={this.handleDeleteSelected}
totalElements={this.totalElements}
/>
{this.props.entityBaseRoute && !this.props.hideAddNew && (
<Button variant="contained"
size="small"
onClick={this.handleCreate}
>Add new</Button>
)}
</div>
</div>
<div className={styles.tableHeader}>
<div className={commonStyles.center}>
<Tooltip title="Select all">
<Checkbox
checked={this.props.allSelected ?? false}
onChange={this.handleToggleSelectAll}
/>
</Tooltip>
</div>
<div className={styles.tableColumnNames}>
{tableColumns.map(col => {
if (!col.visible) return null;
const columnFilter = this.filters?.find(filter => filter.key === col.name);
let searchQuery = columnFilter?.value !== null && columnFilter?.value !== undefined &&
columnFilter.value !== '' ? columnFilter.value : null;
if (col.searchOptions) {
const autocompleteVal = this.getAutocompleteValueFromSearch(searchQuery, col);
if (Array.isArray(autocompleteVal)) {
searchQuery = autocompleteVal.map(val => val.label).join(', ');
} else if (autocompleteVal) {
searchQuery = autocompleteVal.label;
}
}
return (
<div key={col.name}
id={`column_${col.name}`}
className={clsx(styles.columnName)}
style={this.getColumnStyles(col, tableColumns)}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Tooltip title="Open search" placement="top" enterDelay={500}>
<p onClick={() => this.openColumnSearch(col)}
className={clsx(styles.ellipsis, styles.columnNameText)}>{col.label}</p>
</Tooltip>
{!col.disableSort && (
<Tooltip title="Toggle sort" placement="top" enterDelay={500}>
<div onClick={() => this.toggleOrderBy(col)}
className={styles.orderButton}>
<ArrowDropDownIcon
style={{
transform: this.sortBy?.column?.name === col.name
&& this.sortBy?.sort === 'ASC' ? 'rotate(180deg)' : undefined,
transition: '0.3s',
color: this.sortBy?.column?.name !== col.name ? '#aaa' : '#9747d3',
fill: this.sortBy?.column?.name !== col.name ? '#aaa' : '#9747d3',
fontSize: this.sortBy?.column?.name === col.name ? '26px' : undefined,
}}
/>
</div>
</Tooltip>
)}
</div>
{searchQuery && (
<div style={{ display: 'flex', alignItems: 'center' }}>
<p className={clsx(styles.searchQuery, styles.ellipsis)}>{searchQuery}</p>
<Tooltip title="Clear search" enterDelay={500}>
<div onClick={() => this.clearColumnSearch(col)}
style={{ cursor: 'pointer' }}>
<CloseIcon style={{
fontSize: '14px',
color: '#555'
}} />
</div>
</Tooltip>
</div>
)}
</div>
)
})}
</div>
<div className={clsx(commonStyles.center, styles.headerSettings)}>
<Tooltip title="Configure columns">
<IconButton
onClick={this.toggleConfigureColumns}
ref={this.configureColumnsButtonRef}
disabled={!tableColumns?.length}
>
<SettingsIcon />
</IconButton>
</Tooltip>
<Popover
open={!!this.state?.configureColumnsOpen}
anchorEl={this.configureColumnsButtonRef.current}
onClose={this.toggleConfigureColumns}
classes={{ paper: styles.popoverPaper }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
elevation={0}
>
<div className={styles.columnsConfigure}>
<Button size="small" variant="outlined"
onClick={this.resetConfiguredColumns}
style={{ margin: '10px 0 0 10px' }}
>Reset</Button>
<DraggableList<TColumnConfigureItemData>
data={tableColumns.map(col => ({
id: col.name,
column: col,
sortedColumns: this.sortedColumns,
}))}
onChange={this.changeColumnsOrder}
component={ColumnConfigureItem}
/>
</div>
</Popover>
<Popover
open={!!this.state?.columnSearch}
anchorEl={() => document.getElementById(`column_${this.state?.columnSearch?.name}`)}
onClose={this.closeColumnSearch}
classes={{ paper: styles.popoverPaper }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
elevation={0}
>
<div className={styles.columnsConfigure}
style={{ padding: '10px 15px' }}>
{this.state?.columnSearch?.searchOptions ? (
<Autocomplete
multiple={this.state.columnSearch.multipleOptions}
options={this.state.columnSearch.searchOptions}
getOptionLabel={(option: any) => option?.label ?? ''}
defaultValue={this.getAutocompleteValueFromSearch(this.currentSearch,
this.state.columnSearch)}
className={styles.filterItem}
onChange={(event, newVal) => {
if (Array.isArray(newVal)) newVal = JSON.stringify(newVal.map(val => typeof val === 'object' ? val?.value : val));
this.currentSearch = typeof newVal === 'object' ? newVal?.value : newVal
}}
classes={{ popper: styles.autocompletePopper }}
renderInput={(params) => <TextField
{...params}
variant="standard"
fullWidth
label={`Search ${this.state?.columnSearch?.label ?? ''}`}
/>}
/>
) : (
<TextField
fullWidth
onChange={(event) => this.currentSearch = event.target.value}
variant="standard"
label={`Search ${this.state?.columnSearch?.label ?? ''}`}
defaultValue={this.currentSearch}
/>
)}
</div>
</Popover>
</div>
</div>
<CList<TEntityType, TListItemProps<TEntityType, TFilterType>>
className={styles.listWrapper}
id={this.listId}
ListItem={EntityTableItem}
useAutoLoading
usePagination
listItemProps={{
handleDeleteBtnClick: this.handleDeleteItem,
toggleSelection: this.handleToggleItemSelection,
tableProps: this.props,
getColumns: this.getColumns,
getColumnStyles: this.getColumnStyles,
}}
useQueryPagination
loader={this.getManyFilteredItems}
cssClasses={{
scrollBox: styles.list,
contentWrapper: styles.listContent,
}}
elements={{
pagination: Pagination,
preloader: listPreloader
}}
/>
</div >
)
}
Example #6
Source File: GalleryPicker.tsx From Cromwell with MIT License | 4 votes |
render() {
const images = (this.props.images ?? this.uncontrolledInput ?? []).map((image, index) => {
if (!image.id) image.id = image.src + index;
return image;
});
return (
<div className={clsx(styles.GalleryPicker, this.props.className)} style={this.props.style}>
{this.props.label && (
<p className={styles.label}>{this.props.label}</p>
)}
<ResponsiveGridLayout
margin={[0, 0]}
isResizable={false}
breakpoints={{ xs: 480, xxs: 0 }}
rowHeight={64}
layouts={this.getGridLayout(images)}
onLayoutChange={this.onLayoutChange(images)}
cols={{ xs: 1, xxs: 1 }}
draggableHandle='.draggableHandle'
>
{images.map((image, index) => {
return (<div
key={(image?.id ?? index) + ''}
className={styles.imageItem}
>
<IconButton style={{ cursor: 'move', marginRight: '10px' }}
className="draggableHandle">
<DragIndicatorIcon />
</IconButton>
<ImageItem
draggableHandleClass="draggableHandle"
key={(image?.id ?? index) + ''}
data={image}
hideSrc={this.props.hideSrc}
itemProps={{
onImageChange: this.onImageChange,
classes: this.props.classes?.imagePicker,
allImages: images,
}}
/>
{this.props.editLink && (
<IconButton style={{ cursor: 'pointer', marginLeft: '10px' }}
onClick={(event) => this.handleShowLink(index, event)}
>
<LinkIcon />
</IconButton>
)}
<IconButton style={{ cursor: 'pointer', marginLeft: '10px' }}
onClick={() => this.handleRemoveImage(index)}
>
<DeleteOutlineIcon />
</IconButton>
{this.props.editLink && this.state?.editableLink === index && (
<Popover
elevation={0}
classes={{ paper: styles.popover }}
open={this.state?.editableLink === index}
anchorEl={this.editableLinkRef}
onClose={this.handleCloseLinkEdit}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<TextField
fullWidth
onChange={(e) => this.handleChangeImageLink(e.target.value)}
value={this.state?.editableLinkText ?? ''}
label="Link"
variant="standard"
/>
</Popover>
)}
</div>)
})}
</ResponsiveGridLayout>
<div className={styles.actions}>
<Tooltip title="Add image">
<IconButton
className={styles.galleryAddImageBtn}
aria-label="add image"
onClick={this.handleAddImage}
>
<AddIcon />
</IconButton>
</Tooltip>
<Tooltip title="Clear all images">
<IconButton
className={styles.galleryAddImageBtn}
aria-label="clear image"
onClick={this.handleDeleteAllImages}
>
<DeleteForeverIcon />
</IconButton>
</Tooltip>
</div>
</div>
);
}
Example #7
Source File: NotificationCenter.tsx From Cromwell with MIT License | 4 votes |
function NotificationCenter(props: TPropsType) {
const [open, setOpen] = useState(false);
const popperAnchorEl = useRef<HTMLDivElement | null>(null);
const client = getRestApiClient();
let NotificationIcon = NotificationsNoneIcon;
let tipText = '';
if (props?.status?.updateAvailable) {
NotificationIcon = NotificationImportantIcon;
tipText = 'Update available';
}
const updateInfo = props.status?.updateInfo;
const notifications = props.status?.notifications;
const handleOpen = () => {
if (!notifications?.length && !updateInfo) return;
setOpen(true)
}
const handleStartUpdate = async () => {
store.setStateProp({
prop: 'status',
payload: {
...store.getState().status,
isUpdating: true,
}
});
let success = false;
try {
success = await client.launchCmsUpdate();
} catch (error) {
console.error(error);
}
await updateStatus();
if (success) {
toast.success('CMS updated');
const confirm = await askConfirmation({
title: `CMS has been updated. Please reload this page to apply changes`,
});
if (confirm) {
window.location.reload();
}
}
else toast.error('Failed to update CMS');
}
return (
<div ref={popperAnchorEl}>
<Tooltip title={tipText}>
<IconButton
onClick={handleOpen}
style={{
cursor: notifications?.length ? 'pointer' : 'initial',
opacity: notifications?.length ? '1' : '0.6',
}}
>
<NotificationIcon htmlColor={props.color} />
</IconButton>
</Tooltip>
<Popover open={open} anchorEl={popperAnchorEl.current}
style={{ zIndex: 9999 }}
onClose={() => setOpen(false)}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<Grid container className={styles.list}>
{props.status?.isUpdating && (
<Grid item container xs={12} className={clsx(styles.update, styles.updating)}>
<h3 className={styles.updateTitle}>
<UpdateIcon style={{ marginRight: '7px' }} />
Update in progress...</h3>
<LinearProgress className={styles.updateProgress} />
</Grid>
)}
{props.status?.updateAvailable && updateInfo && !props.status?.isUpdating && (
<UpdateInfoCard
updateInfo={updateInfo}
currentVersion={props.status?.currentVersion}
onStartUpdate={handleStartUpdate}
/>
)}
{notifications && (
notifications.map((note, index) => {
let severity: AlertProps['severity'] = 'info';
if (note.type === 'warning') severity = 'warning';
if (note.type === 'error') severity = 'error';
return (
<Grid key={index} item container xs={12} className={styles.item}>
<Alert severity={severity} className={styles.alert}
classes={{ message: styles.message }}>
<p>{note.message}</p>
{note.documentationLink && (
<Tooltip title="Documentation">
<IconButton
onClick={() => window.open(note.documentationLink, '_blank')}>
<HelpOutlineIcon />
</IconButton>
</Tooltip>
)}
</Alert>
</Grid>
)
})
)}
</Grid>
</Popover>
</div>
)
}
Example #8
Source File: Sidebar.tsx From Cromwell with MIT License | 4 votes |
export default function Sidebar() {
const pageInfos = getPageInfos();
const currentInfo = pageInfos.find(i => i.route === window.location.pathname.replace('/admin', ''));
const currentLink = getLinkByInfo(currentInfo);
const [expanded, setExpanded] = useState<string | false>(currentLink?.parentId ?? false);
const [optionsOpen, setOptionsOpen] = useState<boolean>(false);
const [mobileOpen, setMobileOpen] = useState(false);
const [cmsInfoOpen, setCmsInfoOpen] = useState(false);
const [systemMonitorOpen, setSystemMonitorOpen] = useState(false);
const popperAnchorEl = useRef<HTMLDivElement | null>(null);
const history = useHistory?.();
const forceUpdate = useForceUpdate();
const userInfo: TUser | undefined = getStoreItem('userInfo');
const toggleSubMenu = (panel: string) => (event: React.ChangeEvent<any>, isExpanded: boolean) => {
setExpanded(isExpanded ? panel : false);
};
const handleCloseMenu = () => {
setMobileOpen(false);
}
const handleOpenMenu = () => {
setMobileOpen(true);
}
useEffect(() => {
onStoreChange('userInfo', () => {
setTimeout(forceUpdate, 100);
});
history?.listen(() => {
const currentInfo = pageInfos.find(i => i.route === window.location.pathname.replace('/admin', ''));
const newCurrentLink = getLinkByInfo(currentInfo);
if (newCurrentLink && newCurrentLink !== currentLink) {
// setActiveId(newCurrentLink.id);
if (newCurrentLink.parentId) setExpanded(newCurrentLink.parentId)
}
setTimeout(forceUpdate, 100);
});
store.setStateProp({
prop: 'forceUpdateSidebar',
payload: forceUpdate,
});
}, []);
const handleLogout = async () => {
setOptionsOpen(false);
await getRestApiClient()?.logOut();
forceUpdate();
history?.push(loginPageInfo.route);
}
const handleOptionsToggle = () => {
setOptionsOpen(!optionsOpen);
}
const openFileManager = () => {
getFileManager()?.open();
}
const openCmsInfo = () => {
setCmsInfoOpen(true);
}
const openDocs = () => {
window.open('https://cromwellcms.com/docs/overview/intro', '_blank')
}
// check for disabled sidebar
if (currentInfo?.disableSidebar) return <></>;
const sidebarContent = (
<div className={styles.sidebarContent}>
<div className={styles.sidebarTop}>
<div className={styles.sidebarHeader}>
<div className={styles.logo}
style={{ backgroundImage: `url("/admin/static/logo_small_white.svg")` }}
></div>
{/* <p className={commonStyles.text} style={{ color: '#fff', opacity: 0.7 }}>Admin Panel</p> */}
<div>
<NotificationCenter color="#fff" />
</div>
<div className={styles.sidebarMobileActions}>
<IconButton onClick={handleCloseMenu} >
<CloseIcon htmlColor="#999" />
</IconButton>
</div>
</div>
{getSideBarLinks().map(link => <SidebarLink data={link}
key={link.id}
toggleSubMenu={toggleSubMenu}
expanded={expanded}
forceUpdate={forceUpdate}
activeId={currentLink?.id}
userInfo={userInfo}
/>)}
</div>
<div className={styles.sidebarBottom}>
<div className={styles.bottomBlock} style={{ overflow: 'hidden' }}>
{(userInfo?.avatar && userInfo?.avatar !== '') ? (
<div className={styles.avatar} style={{ backgroundImage: `url(${userInfo.avatar})` }}></div>
) : <AccountCircleIcon className={styles.avatar} />}
<div className={styles.textBlock}>
<p className={styles.nameText}>{userInfo?.fullName ?? ''}</p>
<p className={styles.emailText}>{userInfo?.email ?? ''}</p>
</div>
</div>
<div className={styles.bottomBlock}
style={{ marginRight: '-10px' }}
ref={popperAnchorEl}>
<Tooltip title="Options">
<IconButton
style={{ marginLeft: '-10px' }}
onClick={handleOptionsToggle}
className={styles.actionBtn}
aria-label="Options"
>
<MoreVertOutlinedIcon />
</IconButton>
</Tooltip>
<Popover open={optionsOpen} anchorEl={popperAnchorEl.current}
style={{ zIndex: 9999 }}
onClose={() => setOptionsOpen(false)}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
>
<div onClick={() => setOptionsOpen(false)}>
<Link to={`${userPageInfo.baseRoute}/${userInfo?.id}`}>
<MenuItem className={styles.optionsItem}>
<AccountCircleOutlinedIcon />
<p>Your profile</p>
</MenuItem>
</Link>
<MenuItem className={styles.optionsItem} onClick={openFileManager}>
<PermMediaOutlinedIcon />
<p>Media</p>
</MenuItem>
<MenuItem className={styles.optionsItem} onClick={() => setSystemMonitorOpen(true)}>
<DnsRoundedIcon />
<p>System monitor</p>
</MenuItem>
<MenuItem className={styles.optionsItem} onClick={openCmsInfo}>
<InfoOutlinedIcon />
<p>CMS specs</p>
</MenuItem>
<MenuItem className={styles.optionsItem} onClick={openDocs}>
<HelpOutlineIcon />
<p>Documentation</p>
</MenuItem>
<MenuItem onClick={handleLogout} className={styles.optionsItem}>
<ExitToAppIcon />
<p>Log out</p>
</MenuItem>
</div>
</Popover>
</div>
</div>
</div>
)
return (
<div className={styles.Sidebar}>
<div className={styles.mobileContent}>
<HideOnScroll>
<AppBar
className={styles.appBar}
color="transparent"
>
<Toolbar
className={styles.toolbar}
>
<div className={styles.sidebarMobileHeader}>
<div className={styles.logoMobile}
style={{ backgroundImage: `url("/admin/static/logo_small_white.svg")` }}
></div>
{/* <p className={commonStyles.text} style={{ color: '#fff', opacity: 0.7 }}>Admin Panel</p> */}
<div className={styles.mobileActions}>
<NotificationCenter />
<IconButton onClick={handleOpenMenu}>
<MenuIcon />
</IconButton>
</div>
</div>
</Toolbar>
</AppBar>
</HideOnScroll>
<SwipeableDrawer
open={mobileOpen}
onClose={handleCloseMenu}
onOpen={handleOpenMenu}
>
<div className={styles.drawer}>{sidebarContent}</div>
</SwipeableDrawer>
</div>
<CmsInfo
open={cmsInfoOpen}
onClose={() => setCmsInfoOpen(false)}
/>
<SystemMonitor
open={systemMonitorOpen}
onClose={() => setSystemMonitorOpen(false)}
/>
<div className={styles.desktopContent}>{sidebarContent}</div>
</div>
)
}
Example #9
Source File: PostSettings.tsx From Cromwell with MIT License | 4 votes |
PostSettings = (props: {
postData?: TPost;
isSettingsOpen: boolean;
anchorEl: Element;
allTags?: TTag[] | null;
onClose: (newData: Partial<TPost>) => void;
isSaving?: boolean;
handleUnpublish: () => void;
refetchMeta: () => Promise<Record<string, string> | undefined>;
}) => {
const { postData, refetchMeta } = props;
const [title, setTitle] = useState<string | undefined>(postData?.title ?? null);
const [mainImage, setMainImage] = useState<string | undefined>(postData?.mainImage ?? null);
const [pageDescription, setPageDescription] = useState<string | undefined>(postData?.pageDescription ?? null);
const [pageKeywords, setPageKeywords] = useState<string[] | undefined>(postData?.meta?.keywords ?? null);
const [pageTitle, setPageTitle] = useState<string | undefined>(postData?.pageTitle ?? null);
const [slug, setSlug] = useState<string | undefined>(postData?.slug ?? null);
const [tags, setTags] = useState<TTag[] | undefined>(postData?.tags ?? []);
const [publishDate, setPublishDate] = useState<Date | undefined | null>(postData?.publishDate ?? null);
const [featured, setFeatured] = useState<boolean | undefined | null>(postData?.featured ?? null);
const handleChangeTags = (event: any, newValue: TTag[]) => {
setTags(newValue);
}
const handleChangeKeywords = (event: any, newValue: string[]) => {
setPageKeywords(newValue);
}
const handleClose = async () => {
const newData = Object.assign({}, postData);
newData.title = title;
newData.mainImage = mainImage;
newData.pageDescription = pageDescription;
newData.pageTitle = pageTitle;
newData.slug = slug;
newData.tags = tags;
newData.publishDate = publishDate;
newData.featured = featured;
if (pageKeywords) {
if (!newData.meta) newData.meta = {};
newData.meta.keywords = pageKeywords;
}
newData.customMeta = Object.assign({}, postData.customMeta, await getCustomMetaFor(EDBEntity.Post));
props.onClose(newData);
}
let pageFullUrl;
if (slug) {
pageFullUrl = serviceLocator.getFrontendUrl() + resolvePageRoute('post', { slug: slug ?? postData.id + '' });
}
return (
<Popover
disableEnforceFocus
open={props.isSettingsOpen}
elevation={0}
anchorEl={props.anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
classes={{ paper: styles.popover }}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<div className={styles.PostSettings}>
<p className={styles.headerText}>Post meta</p>
<IconButton className={styles.closeBtn}
id="post-settings-close-btn"
onClick={handleClose}>
<CloseIcon />
</IconButton>
<TextField
label="Title"
value={title ?? ''}
fullWidth
className={styles.settingItem}
variant="standard"
onChange={e => setTitle(e.target.value)}
/>
<TextField
label="Page URL"
className={styles.settingItem}
fullWidth
value={slug ?? ''}
onChange={e => setSlug(e.target.value)}
variant="standard"
helperText={pageFullUrl}
/>
<ImagePicker
label="Main image"
onChange={(val) => setMainImage(val)}
value={mainImage}
className={styles.imageBox}
backgroundSize='cover'
showRemove
/>
<Autocomplete
multiple
options={props.allTags ?? []}
defaultValue={tags?.map(tag => (props.allTags ?? []).find(allTag => allTag.name === tag.name)) ?? []}
getOptionLabel={(option) => option.name}
onChange={handleChangeTags}
renderInput={(params) => (
<TextField
{...params}
className={styles.settingItem}
variant="standard"
label="Tags"
/>
)}
/>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label="Publish date"
value={publishDate}
onChange={(newValue) => {
if (!newValue) {
setPublishDate(null);
return;
}
const date = new Date(newValue);
if (isNaN(date.getTime())) {
setPublishDate(null);
return;
}
setPublishDate(date);
}}
renderInput={(params) => <TextField
variant="standard"
fullWidth
{...params} />}
/>
</LocalizationProvider>
<FormControlLabel
control={
<Checkbox
checked={featured}
onChange={() => setFeatured(!featured)}
color="primary"
/>
}
style={{ margin: '10px 0' }}
className={styles.settingItem}
label="Featured post"
/>
<TextField
label="Meta title"
className={styles.settingItem}
fullWidth
variant="standard"
value={pageTitle ?? ''}
onChange={e => setPageTitle(e.target.value)}
/>
<TextField
label="Meta description"
className={styles.settingItem}
fullWidth
variant="standard"
value={pageDescription ?? ''}
onChange={e => setPageDescription(e.target.value)}
/>
<Autocomplete
multiple
freeSolo
options={[]}
value={(pageKeywords ?? []) as any}
getOptionLabel={(option) => option}
onChange={handleChangeKeywords}
renderInput={(params) => (
<Tooltip title="Press ENTER to add">
<TextField
{...params}
className={styles.settingItem}
variant="standard"
label="Meta keywords"
/>
</Tooltip>
)}
/>
{postData?.published && (
<Tooltip title="Remove post from publication">
<Button variant="contained" color="primary"
className={styles.publishBtn}
size="small"
disabled={props.isSaving}
onClick={props.handleUnpublish}
>Unpublish</Button>
</Tooltip>
)}
<div style={{ marginBottom: '15px' }}></div>
{postData && (
<RenderCustomFields
entityType={EDBEntity.Post}
entityData={postData}
refetchMeta={refetchMeta}
/>
)}
</div>
</Popover>
)
}
Example #10
Source File: CustomData.tsx From Cromwell with MIT License | 4 votes |
CustomFieldSettings = (props: {
data: TCustomFieldSettingsData;
}) => {
const data = props.data;
const { settings, changeSettings } = data;
const fieldData = data.field;
const [selectOptionsOpen, setSelectOptionsOpen] = useState(false);
const selectOptionsButtonRef = useRef();
const changeFieldValue = (key: keyof TAdminCustomField, value: any) => {
const customFields = (settings?.customFields ?? []).map(field => {
if (field.id === props.data.id) {
(field as any)[key] = value;
}
return field;
})
changeSettings('customFields', customFields)
}
const deleteField = () => {
changeSettings('customFields', (settings?.customFields ?? [])
.filter(field => field.id !== fieldData.id));
unregisterCustomField(fieldData.entityType, fieldData.key);
}
const toggleSelectOptions = () => {
setSelectOptionsOpen(!selectOptionsOpen);
}
const addSelectOption = () => {
changeSettings('customFields', (settings?.customFields ?? [])
.map(field => {
if (field.id === fieldData.id) {
return {
...fieldData,
options: [...(fieldData.options ?? []), ''],
}
}
return field;
}));
}
const deleteSelectOption = (option: string) => {
changeSettings('customFields', (settings?.customFields ?? [])
.map(field => {
if (field.id === fieldData.id) {
return {
...fieldData,
options: (fieldData.options ?? []).filter(opt => opt !== option),
}
}
return field;
}));
}
const changeSelectOption = (index: number, value: string) => {
changeSettings('customFields', (settings?.customFields ?? [])
.map(field => {
if (field.id === fieldData.id) {
return {
...fieldData,
options: (fieldData.options ?? []).map((opt, idx) => idx === index ? value : opt),
}
}
return field;
}));
}
return (
<div className={styles.customFieldItem}>
<TextField
label="Key"
value={fieldData.key}
onChange={e => changeFieldValue('key', e.target.value.replace(/\W/g, '_'))}
size="small"
className={styles.customFieldItemField}
/>
<TextField
label="Label"
value={fieldData.label}
onChange={e => changeFieldValue('label', e.target.value)}
size="small"
className={styles.customFieldItemField}
/>
<Select
className={styles.customFieldItemField}
value={fieldData.fieldType}
onChange={e => changeFieldValue('fieldType', e.target.value)}
size="small"
variant="outlined"
label="Type"
options={['Simple text', 'Text editor', 'Select', 'Image', 'Gallery', 'Color'] as TAdminCustomField['fieldType'][]}
/>
{fieldData?.fieldType === 'Select' && (
<Tooltip title="Select options">
<IconButton ref={selectOptionsButtonRef} onClick={toggleSelectOptions}>
<FormatListBulletedIcon />
</IconButton>
</Tooltip>
)}
<Tooltip title="Delete field">
<IconButton onClick={deleteField}>
<DeleteIcon />
</IconButton>
</Tooltip>
{fieldData?.fieldType === 'Select' && (
<Popover
open={selectOptionsOpen}
anchorEl={selectOptionsButtonRef.current}
onClose={toggleSelectOptions}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<div className={styles.selectOptions}>
{fieldData?.options?.map((option, idx) => (
<div key={idx}
style={{ display: 'flex', alignItems: 'center' }}
>
<TextField
value={option}
onChange={e => changeSelectOption(idx, e.target.value)}
size="small"
variant="standard"
/>
<IconButton onClick={() => deleteSelectOption(option)}>
<DeleteIcon />
</IconButton>
</div>
))}
<IconButton onClick={addSelectOption}>
<AddIcon />
</IconButton>
</div>
</Popover>
)}
</div>
);
}
Example #11
Source File: BlockMenu.tsx From Cromwell with MIT License | 4 votes |
render() {
this.props.getInst(this);
if (!this.selectedFrame) return <></>;
const block = this.selectedBlock;
const data = block?.getData();
const bType = data?.type;
const isConstant = data?.isConstant;
const blockProps = this.props.createBlockProps(block);
const addNewBlock = (bType: TCromwellBlockType) => () => {
blockProps?.addNewBlockAfter?.(bType);
this.setState({ addNewOpen: false });
}
let icon: JSX.Element;
if (bType === 'text') {
icon = <Tooltip title="Text block">
<SubjectIcon />
</Tooltip>
}
if (bType === 'plugin') {
icon = <Tooltip title="Plugin block">
<PluginIcon className={styles.customIcon} />
</Tooltip>
}
if (bType === 'container') {
icon = <Tooltip title="Container block">
<WidgetsIcon />
</Tooltip>
}
if (bType === 'HTML') {
icon = <Tooltip title="HTML block">
<CodeIcon />
</Tooltip>
}
if (bType === 'image') {
icon = <Tooltip title="Image block">
<ImageIcon />
</Tooltip>
}
if (bType === 'gallery') {
icon = <Tooltip title="Gallery block">
<PhotoLibraryIcon />
</Tooltip>
}
if (bType === 'editor') {
icon = <Tooltip title="Editor block">
<EditOutlinedIcon />
</Tooltip>
}
return ReactDom.createPortal(<>
{!isConstant && (
<div className={styles.actions}>
<div className={styles.typeIcon}>{icon}</div>
<Tooltip title="Delete block">
<MenuItem onClick={blockProps.deleteBlock}>
<DeleteForeverIcon />
</MenuItem>
</Tooltip>
</div>
)}
<div className={styles.bottomActions} ref={this.addNewBtnEl}>
<Tooltip title="Add block">
<IconButton onClick={this.handleOpenAddNew}>
<AddCircleOutlineIcon className={styles.addIcon} />
</IconButton>
</Tooltip>
<Popover
open={this.state.addNewOpen}
elevation={6}
anchorEl={this.addNewBtnEl.current}
onClose={this.handleCloseAddNew}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
>
<div className={styles.widgetsContainer}>
<Grid container spacing={1} >
<Grid item xs={4} >
<MenuItem className={styles.widgetItem}
onClick={addNewBlock('text')}
>
<SubjectIcon />
<p>Simple Text</p>
</MenuItem>
</Grid>
<Grid item xs={4} >
<MenuItem className={styles.widgetItem}
onClick={addNewBlock('editor')}
>
<EditOutlinedIcon />
<p>Text Editor</p>
</MenuItem>
</Grid>
<Grid item xs={4} >
<MenuItem className={styles.widgetItem}
onClick={addNewBlock('container')}
>
<WidgetsIcon />
<p>Container</p>
</MenuItem>
</Grid>
<Grid item xs={4} >
<MenuItem className={styles.widgetItem}
onClick={addNewBlock('HTML')}
>
<CodeIcon />
<p>HTML</p>
</MenuItem>
</Grid>
<Grid item xs={4} >
<MenuItem className={styles.widgetItem}
onClick={addNewBlock('plugin')}
>
<PluginIcon className={styles.customIcon}
style={{ filter: 'none' }} />
<p>Plugin</p>
</MenuItem>
</Grid>
<Grid item xs={4} >
<MenuItem className={styles.widgetItem}
onClick={addNewBlock('image')}
>
<ImageIcon />
<p>Image</p>
</MenuItem>
</Grid>
<Grid item xs={4} >
<MenuItem className={styles.widgetItem}
onClick={addNewBlock('gallery')}
>
<PhotoLibraryIcon />
<p>Gallery</p>
</MenuItem>
</Grid>
</Grid>
</div>
</Popover>
</div>
</>, this.selectedFrame)
}
Example #12
Source File: PageListItem.tsx From Cromwell with MIT License | 4 votes |
PageListItem = (props: {
page: TExtendedPageInfo;
activePage?: TPageInfo;
handleOpenPage: (page: TPageInfo) => void;
handleDeletePage: (page: TPageInfo) => void;
onPreviewChange: (url: string) => any;
}) => {
const { page, handleOpenPage, handleDeletePage, activePage } = props;
const [editingPreview, setEditingPreview] = useState(false);
const [previewUrl, setPreviewUrl] = useState(page?.previewUrl ?? page?.route);
const changeUrlBtn = useRef();
const active = activePage && page && activePage.route === page.route && activePage.id === page.id;
const isGeneric = page.route && (page.route.endsWith('[slug]') || page.route.endsWith('[id]'));
const openEditingPreview = (event) => {
event.stopPropagation();
setEditingPreview(true)
}
const closeEditingPreview = (event) => {
event.stopPropagation();
setEditingPreview(false);
props.onPreviewChange(previewUrl);
}
const handleChangeUrl = (val: string) => {
const rootUrl = page.route.replace('[slug]', '').replace('[id]', '');
if (!val) val = '';
val = val.replace(rootUrl, '');
val = val.replace(/\W/g, '-');
val = rootUrl + val;
setPreviewUrl(val);
}
if (!page) return null;
return (
<div ref={changeUrlBtn} onClick={e => e.stopPropagation()}>
<MenuItem
className={`${styles.pageItem} ${active ? styles.activeItem : ''}`}
onClick={() => handleOpenPage(page)}
>
<p>{page.name}</p>
<div className={styles.pageItemActions}>
{isGeneric && (
<>
<Tooltip title="Edit preview URL">
<IconButton
onClick={openEditingPreview}
>
<LinkIcon />
</IconButton>
</Tooltip>
<Popover open={editingPreview}
anchorEl={changeUrlBtn.current}
style={{ zIndex: 9999 }}
onClose={closeEditingPreview}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
elevation={5}
>
<div className={styles.previewChangeContainer} onClick={e => e.stopPropagation()}>
<TextField
onChange={(e) => handleChangeUrl(e.target.value)}
fullWidth
value={previewUrl ?? ''}
className={styles.settingsInput}
variant="standard"
label="Preview URL" />
</div>
</Popover>
</>
)}
{page.isVirtual && (
<Tooltip title="Delete page">
<IconButton
aria-label="Delete page"
onClick={(e) => {
e.stopPropagation();
handleDeletePage(page)
}}
>
<DeleteForeverIcon />
</IconButton>
</Tooltip>
)}
<KeyboardArrowRightIcon className={styles.activeIcon} htmlColor="#fff" />
</div>
</MenuItem>
</div>
)
}
Example #13
Source File: ThemeEditActions.tsx From Cromwell with MIT License | 4 votes |
render() {
const { isSidebarOpen, themeConfig } = this.state;
const editingPageConfig = this.getThemeEditor().getEditingPageConfig();
const pageInfos = this.state.pageInfos?.map(p => {
if (p.id === editingPageConfig?.id) {
return Object.assign({}, p, editingPageConfig);
}
return p;
});
const defaultPages = pageInfos?.filter(p => !p.isVirtual);
const customPages = pageInfos?.filter(p => p.isVirtual);
if (!themeConfig) return null;
return (<>
<div className={styles.ThemeEditActions} ref={this.wrapperRef}>
<div>
<Tooltip title="Pages">
<IconButton
onClick={this.handlePagesToggle}
>
<PagesIcon />
</IconButton>
</Tooltip>
<Tooltip title="Page meta info">
<IconButton
onClick={this.handleMetaToggle}
>
<InfoOutlinedIcon />
</IconButton>
</Tooltip>
<Popover open={this.state.pageMetaOpen}
anchorEl={this.wrapperRef.current}
style={{ zIndex: 9999 }}
onClose={this.handleMetaToggle}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<PageSettings
themeConfig={themeConfig}
pageConfig={editingPageConfig}
handlePageInfoChange={this.handlePageInfoChange}
/>
</Popover>
<Tooltip title="Undo">
<IconButton
ref={this.undoBtnRef}
onClick={this.undoModification}
>
<UndoIcon />
</IconButton>
</Tooltip>
<Tooltip title="Redo">
<IconButton
ref={this.redoBtnRef}
onClick={this.redoModification}
>
<RedoIcon />
</IconButton>
</Tooltip>
</div>
<div className={styles.bottomBlock} >
<Tooltip title="Options">
<IconButton
onClick={this.handleOptionsToggle}
className={styles.actionBtn}
aria-label="Options"
ref={this.optionsAnchorEl}
>
<MoreVertOutlinedIcon />
</IconButton>
</Tooltip>
<Popover open={this.state.pageOptionsOpen}
anchorEl={this.optionsAnchorEl.current}
style={{ zIndex: 9999 }}
onClose={this.handleOptionsToggle}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
<div>
<MenuItem
onClick={this.handleResetCurrentPage} className={styles.optionsItem}>
<SettingsBackupRestoreIcon />
<p>Reset to default</p>
</MenuItem>
<MenuItem
disabled={!editingPageConfig?.isVirtual}
onClick={this.handleDeleteCurrentPage} className={styles.optionsItem}>
<DeleteForeverIcon />
<p>Delete page</p>
</MenuItem>
</div>
</Popover>
<Button variant="contained" color="primary"
className={styles.saveBtn}
size="small"
onClick={this.handleSaveEditingPage}
>Save</Button>
</div>
</div>
<LoadingStatus isActive={this.state.loadingStatus} />
<Drawer
classes={{ paper: styles.sidebarPaper }}
variant="persistent"
anchor={'left'}
open={isSidebarOpen}
onClick={(e) => e.stopPropagation()}
>
<div className={styles.sidebar}>
<div className={styles.pageList} key="_2_">
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<p className={styles.pageListHeader}>Theme pages</p>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Tooltip title="Theme settings">
<IconButton
className={styles.sidebarCloseBtn}
onClick={this.handleThemeSettingsToggle}>
<SettingsIcon />
</IconButton>
</Tooltip>
<Tooltip title="Close">
<IconButton
className={styles.sidebarCloseBtn}
onClick={this.handlePagesToggle}>
<CloseIcon />
</IconButton>
</Tooltip>
</div>
</div>
{defaultPages?.map(p => (
<PageListItem
activePage={editingPageConfig}
key={p.id + p.route}
page={p}
handleOpenPage={this.handleOpenPage}
handleDeletePage={this.handleDeletePage}
onPreviewChange={this.handlePreviewChange(p)}
/>
))}
</div>
{customPages && (
<div className={styles.pageList} key="_3_">
<p className={styles.pageListHeader}>Custom pages</p>
{customPages.map(p => (
<PageListItem
activePage={editingPageConfig}
key={p.id + p.route}
page={p}
handleOpenPage={this.handleOpenPage}
handleDeletePage={this.handleDeletePage}
onPreviewChange={this.handlePreviewChange(p)}
/>
))}
<Tooltip title="Add a new page">
<MenuItem
className={clsx(styles.addPageItem, styles.navBarItem)}
>
<IconButton
aria-label="add page"
onClick={this.handleAddCustomPage}
>
<AddCircleIcon />
</IconButton>
</MenuItem>
</Tooltip>
</div>
)}
</div>
<Modal
open={this.state?.themeSettingsOpen}
blurSelector="#root"
className={commonStyles.center}
onClose={this.handleThemeSettingsToggle}
>
<div className={styles.themeSettings}>
<div className={styles.themeSettingsItem}
style={{ justifyContent: 'space-between' }}
>
<h3 className={styles.themeSettingsTitle}>Theme settings</h3>
<IconButton
onClick={this.handleThemeSettingsToggle}>
<CloseIcon />
</IconButton>
</div>
<div className={styles.themeSettingsItem}>
<ModeSwitch
value={this.state?.themePalette?.mode ?? 'light'}
onToggle={() => {
this.changedPalette = true;
this.setState(prev => {
const isLight = prev.themePalette?.mode !== 'dark';
return {
themePalette: Object.assign({}, prev.themePalette, {
mode: isLight ? 'dark' : 'light'
})
}
});
}}
/>
</div>
<div className={styles.themeSettingsItem}>
<ColorPicker
label="Primary color"
value={this.state.themePalette?.primaryColor}
onChange={(color) => {
this.changedPalette = true;
this.setState(prev => {
return {
themePalette: Object.assign({}, prev.themePalette, {
primaryColor: color,
})
}
})
}}
/>
</div>
<div className={styles.themeSettingsItem}>
<ColorPicker
label="Secondary color"
value={this.state.themePalette?.secondaryColor}
onChange={(color) => {
this.changedPalette = true;
this.setState(prev => {
return {
themePalette: Object.assign({}, prev.themePalette, {
secondaryColor: color,
})
}
})
}}
/>
</div>
</div>
</Modal>
</Drawer>
</>)
}
Example #14
Source File: Header.tsx From Cromwell with MIT License | 4 votes |
Header = () => {
const cmsSettings = getCmsSettings();
const cart = useCart();
const userInfo = useUserInfo();
const [userOptionsOpen, setUserOptionsOpen] = useState<boolean>(false);
const popperAnchorEl = useRef<HTMLDivElement | null>(null);
const authClient = useAuthClient();
const handleCartClick = () => {
appState.isCartOpen = true;
}
const handleLogout = async () => {
setUserOptionsOpen(false);
authClient.signOut();
}
const handleOpenWishlist = () => {
appState.isWishlistOpen = true;
}
const handleOpenWatched = () => {
appState.isWatchedOpen = true;
}
const handleOpenSignIn = () => {
appState.isSignInOpen = true;
}
return (
<CContainer global id="header_1" className={`${styles.Header} ${commonStyles.text}`}>
<CContainer id="header_21" className={styles.topPanel}>
<CContainer id="header_22" className={`${commonStyles.content} ${styles.topPanelContent}`}>
<CContainer className={styles.leftBlock} id="header_11">
<CContainer id="header_01" className={styles.currencyOption}>
<MuiCurrencySwitch />
</CContainer>
<CContainer id="header_51">
<Tooltip title="Viewed items">
<IconButton
aria-label="Open recently viewed items"
onClick={handleOpenWatched} style={{ margin: '-12px 0' }}>
<VisibilityIcon />
</IconButton>
</Tooltip>
<Tooltip title="Wishlist">
<IconButton
aria-label="Open wishlist"
onClick={handleOpenWishlist} style={{ margin: '-12px 0' }}>
<FavoriteIcon />
</IconButton>
</Tooltip>
</CContainer>
<CHTML id="header_02">
<div className={styles.languageOption}>
</div>
</CHTML>
</CContainer>
<CContainer className={styles.rightBlock} id="header_12">
<CContainer id="header_03" className={styles.welcomeMessage}>
<CText id="header_35">Welcome message</CText>
</CContainer>
<CContainer id="header_04" className={styles.topPanelLinks}>
<CText id="header_31" href="/pages/contact-us" className={clsx(commonStyles.link, styles.topPanelLink)}>Contact us</CText>
{!userInfo && (
<CText id="header_32" onClick={handleOpenSignIn} className={clsx(commonStyles.link, styles.topPanelLink)}>Sign in</CText>
)}
{userInfo && (
<>
<div className={styles.userBox} ref={popperAnchorEl}
onClick={() => setUserOptionsOpen(true)}
>
{(userInfo?.avatar && userInfo?.avatar !== '') ? (
<div className={styles.avatar} style={{ backgroundImage: `url(${userInfo.avatar})` }}></div>
) : <AccountCircleIcon className={styles.avatar} />}
<p className={clsx(styles.userName)}>{userInfo.fullName ?? ''}</p>
</div>
<Popover open={userOptionsOpen}
anchorEl={popperAnchorEl.current}
style={{ zIndex: 9999 }}
onClose={() => setUserOptionsOpen(false)}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
>
<div>
<Link href="/account">
<MenuItem className={styles.optionsItem} onClick={() => setUserOptionsOpen(false)}>
<AccountCircleOutlinedIcon />
<p>Your profile</p>
</MenuItem>
</Link>
<MenuItem onClick={handleLogout} className={styles.optionsItem}>
<ExitToAppIcon />
<p>Log out</p>
</MenuItem>
</div>
</Popover>
</>
)}
</CContainer>
</CContainer>
</CContainer>
</CContainer>
<CContainer id="header_23" className={styles.mainPanel}>
<CContainer id="header_41" className={`${commonStyles.content} ${styles.mainPanelContent}`}>
<CContainer id="header_36" className={styles.logo}>
<Link href="/">
<img className={styles.logo} src={cmsSettings?.logo} alt="logo" />
</Link>
</CContainer>
<CContainer id="header_37" className={styles.search}>
<MuiProductSearch />
</CContainer>
<CContainer id="header_38" className={styles.phone}>
<CText id="header_39" className={styles.phoneActionTip}>Call us now!</CText>
<CText id="header_33" href={`tel:+123 (456) 78-90`} className={commonStyles.link}>+123 (456) 78-90</CText>
</CContainer>
<CContainer id="header_40">
<ListItem button className={styles.cart} onClick={handleCartClick} >
<div className={styles.cartIcon}></div>
<div className={styles.cartExpandBlock}>
<p className={styles.itemsInCart}>{cart?.length || 0}</p>
<ExpandMoreIcon className={styles.cartExpandIcon} />
</div>
</ListItem>
</CContainer>
</CContainer>
</CContainer>
<CContainer id="header_24" className={styles.mainMenu}>
<CContainer className={`${commonStyles.content} ${styles.mainMenuContent}`} id="header_13">
<CPlugin id="header_main_menu" pluginName={"@cromwell/plugin-main-menu"} blockName="Main menu" />
</CContainer>
</CContainer>
<div className={styles.mobileHeader}>
<MobileHeader />
</div>
</CContainer>
)
}
Example #15
Source File: TableWrapper.tsx From console with GNU Affero General Public License v3.0 | 4 votes |
TableWrapper = ({
itemActions,
columns,
onSelect,
records,
isLoading,
loadingMessage = <Typography component="h3">Loading...</Typography>,
entityName,
selectedItems,
idField,
classes,
radioSelection = false,
customEmptyMessage = "",
customPaperHeight = "",
noBackground = false,
columnsSelector = false,
textSelectable = false,
columnsShown = [],
onColumnChange = (column: string, state: boolean) => {},
infiniteScrollConfig,
sortConfig,
autoScrollToBottom = false,
disabled = false,
onSelectAll,
rowStyle,
parentClassName = "",
}: TableWrapperProps) => {
const [columnSelectorOpen, setColumnSelectorOpen] = useState<boolean>(false);
const [anchorEl, setAnchorEl] = React.useState<any>(null);
const findView = itemActions
? itemActions.find((el) => el.type === "view")
: null;
const clickAction = (rowItem: any) => {
if (findView) {
const valueClick = findView.sendOnlyId ? rowItem[idField] : rowItem;
let disabled = false;
if (findView.disableButtonFunction) {
if (findView.disableButtonFunction(valueClick)) {
disabled = true;
}
}
if (findView.to && !disabled) {
history.push(`${findView.to}/${valueClick}`);
return;
}
if (findView.onClick && !disabled) {
findView.onClick(valueClick);
}
}
};
const openColumnsSelector = (event: { currentTarget: any }) => {
setColumnSelectorOpen(!columnSelectorOpen);
setAnchorEl(event.currentTarget);
};
const closeColumnSelector = () => {
setColumnSelectorOpen(false);
setAnchorEl(null);
};
const columnsSelection = (columns: IColumns[]) => {
return (
<Fragment>
<IconButton
aria-describedby={"columnsSelector"}
color="primary"
onClick={openColumnsSelector}
size="large"
>
<ViewColumnIcon fontSize="inherit" />
</IconButton>
<Popover
anchorEl={anchorEl}
id={"columnsSelector"}
open={columnSelectorOpen}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
onClose={closeColumnSelector}
>
<div className={classes.shownColumnsLabel}>Shown Columns</div>
<div className={classes.popoverContent}>
{columns.map((column: IColumns) => {
return (
<CheckboxWrapper
key={`tableColumns-${column.label}`}
label={column.label}
checked={columnsShown.includes(column.elementKey!)}
onChange={(e) => {
onColumnChange(column.elementKey!, e.target.checked);
}}
id={`chbox-${column.label}`}
name={`chbox-${column.label}`}
value={column.label}
/>
);
})}
</div>
</Popover>
</Fragment>
);
};
return (
<Grid item xs={12} className={parentClassName}>
<Paper
className={`${classes.paper} ${noBackground ? classes.noBackground : ""}
${disabled ? classes.disabled : ""}
${
customPaperHeight !== ""
? customPaperHeight
: classes.defaultPaperHeight
}`}
>
{isLoading && (
<Grid container className={classes.loadingBox}>
<Grid item xs={12} style={{ textAlign: "center" }}>
{loadingMessage}
</Grid>
<Grid item xs={12}>
<LinearProgress />
</Grid>
</Grid>
)}
{columnsSelector && !isLoading && records.length > 0 && (
<div className={classes.overlayColumnSelection}>
{columnsSelection(columns)}
</div>
)}
{records && !isLoading && records.length > 0 ? (
// @ts-ignore
<InfiniteLoader
isRowLoaded={({ index }) => !!records[index]}
loadMoreRows={
infiniteScrollConfig
? infiniteScrollConfig.loadMoreRecords
: () => new Promise(() => true)
}
rowCount={
infiniteScrollConfig
? infiniteScrollConfig.recordsCount
: records.length
}
>
{({ onRowsRendered, registerChild }) => (
// @ts-ignore
<AutoSizer>
{({ width, height }: any) => {
const optionsWidth = calculateOptionsSize(
width,
itemActions
? itemActions.filter((el) => el.type !== "view").length
: 0
);
const hasSelect: boolean = !!(onSelect && selectedItems);
const hasOptions: boolean = !!(
(itemActions && itemActions.length > 1) ||
(itemActions &&
itemActions.length === 1 &&
itemActions[0].type !== "view")
);
return (
// @ts-ignore
<Table
ref={registerChild}
disableHeader={false}
headerClassName={"headerItem"}
headerHeight={40}
height={height}
noRowsRenderer={() => (
<Fragment>
{customEmptyMessage !== ""
? customEmptyMessage
: `There are no ${entityName} yet.`}
</Fragment>
)}
overscanRowCount={10}
rowHeight={40}
width={width}
rowCount={records.length}
rowGetter={({ index }) => records[index]}
onRowClick={({ rowData }) => {
clickAction(rowData);
}}
rowClassName={`rowLine ${findView ? "canClick" : ""} ${
!findView && textSelectable ? "canSelectText" : ""
}`}
onRowsRendered={onRowsRendered}
sort={sortConfig ? sortConfig.triggerSort : undefined}
sortBy={sortConfig ? sortConfig.currentSort : undefined}
sortDirection={
sortConfig ? sortConfig.currentDirection : undefined
}
scrollToIndex={
autoScrollToBottom ? records.length - 1 : -1
}
rowStyle={(r) => {
if (rowStyle) {
const returnElement = rowStyle(r);
if (typeof returnElement === "string") {
return get(TableRowPredefStyles, returnElement, {});
}
return returnElement;
}
return {};
}}
>
{hasSelect && (
// @ts-ignore
<Column
headerRenderer={() => (
<Fragment>
{onSelectAll ? (
<div className={classes.checkAllWrapper}>
<CheckboxWrapper
label={""}
onChange={onSelectAll}
value="all"
id={"selectAll"}
name={"selectAll"}
checked={
selectedItems?.length === records.length
}
/>
</div>
) : (
<Fragment>Select</Fragment>
)}
</Fragment>
)}
dataKey={`select-${idField}`}
width={selectWidth}
disableSort
cellRenderer={({ rowData }) => {
const isSelected = selectedItems
? selectedItems.includes(
isString(rowData) ? rowData : rowData[idField]
)
: false;
return (
<Checkbox
value={
isString(rowData) ? rowData : rowData[idField]
}
color="primary"
inputProps={{
"aria-label": "secondary checkbox",
}}
className="TableCheckbox"
checked={isSelected}
onChange={onSelect}
onClick={(e) => {
e.stopPropagation();
}}
checkedIcon={
<span
className={
radioSelection
? classes.radioSelectedIcon
: classes.checkedIcon
}
/>
}
icon={
<span
className={
radioSelection
? classes.radioUnselectedIcon
: classes.unCheckedIcon
}
/>
}
/>
);
}}
/>
)}
{generateColumnsMap(
columns,
width,
optionsWidth,
hasSelect,
hasOptions,
selectedItems || [],
idField,
columnsSelector,
columnsShown,
sortConfig ? sortConfig.currentSort : "",
sortConfig ? sortConfig.currentDirection : undefined
)}
{hasOptions && (
// @ts-ignore
<Column
dataKey={idField}
width={optionsWidth}
headerClassName="optionsAlignment"
className="optionsAlignment"
cellRenderer={({ rowData }) => {
const isSelected = selectedItems
? selectedItems.includes(
isString(rowData) ? rowData : rowData[idField]
)
: false;
return elementActions(
itemActions || [],
rowData,
isSelected,
idField
);
}}
/>
)}
</Table>
);
}}
</AutoSizer>
)}
</InfiniteLoader>
) : (
<Fragment>
{!isLoading && (
<div>
{customEmptyMessage !== ""
? customEmptyMessage
: `There are no ${entityName} yet.`}
</div>
)}
</Fragment>
)}
</Paper>
</Grid>
);
}