UNPKG

@eeacms/volto-accordion-block

Version:

volto-accordion-block: Volto accordion block

475 lines (439 loc) 14.5 kB
import { BlocksForm, Icon, SidebarPortal, BlocksToolbar, BlockDataForm, } from '@plone/volto/components'; import { emptyBlocksForm, getBlocksLayoutFieldname, withBlockExtensions, } from '@plone/volto/helpers'; import { cloneDeepSchema } from '@plone/volto/helpers/Utils/Utils'; import helpSVG from '@plone/volto/icons/help.svg'; import { isEmpty, without, pickBy } from 'lodash'; import React, { useState } from 'react'; import { Button, Segment } from 'semantic-ui-react'; import { defineMessages, useIntl } from 'react-intl'; import AccordionEdit from './AccordionEdit'; import AccordionFilter from './AccordionFilter'; import EditBlockWrapper from './EditBlockWrapper'; import './editor.less'; import { AccordionBlockSchema } from './Schema'; import { emptyAccordion, getPanels } from './util'; import config from '@plone/volto/registry'; const messages = defineMessages({ SectionHelp: { id: 'Section help', defaultMessage: 'Section help', }, AccordionBlock: { id: 'Accordion block', defaultMessage: 'Accordion block', }, Accordion: { id: 'Accordion', defaultMessage: 'Accordion', }, }); const Edit = (props) => { const [selectedBlock, setSelectedBlock] = useState({}); const [multiSelected, setMultiSelected] = useState([]); const [filterValue, setFilterValue] = useState(''); const { block, data, onChangeBlock, onChangeField, pathname, selected, manage, formDescription, } = props; const intl = useIntl(); const properties = isEmpty(data?.data?.blocks) ? emptyAccordion(3) : data.data; const metadata = props.metadata || props.properties; const [currentUid, setCurrentUid] = useState(''); const onSelectBlock = (uid, id, isMultipleSelection, event, activeBlock) => { let newMultiSelected = []; let selected = id; if (Object.values(activeBlock || {})?.length > 0) { activeBlock = Object.values(activeBlock)[0]; } if (data?.data?.blocks?.hasOwnProperty(uid) && isMultipleSelection) { selected = null; const blocksLayoutFieldname = getBlocksLayoutFieldname( data.data.blocks[uid], ); const blocks_layout = data.data.blocks[uid][blocksLayoutFieldname].items; if (event.shiftKey) { const anchor = multiSelected.length > 0 ? blocks_layout.indexOf(multiSelected[0]) : blocks_layout.indexOf(activeBlock); const focus = blocks_layout.indexOf(id); if (anchor === focus) { newMultiSelected = [id]; } else if (focus > anchor) { newMultiSelected = [...blocks_layout.slice(anchor, focus + 1)]; } else { newMultiSelected = [...blocks_layout.slice(focus, anchor + 1)]; } } if ((event.ctrlKey || event.metaKey) && !event.shiftKey) { if (multiSelected.includes(id)) { selected = null; newMultiSelected = without(multiSelected, id); } else { newMultiSelected = [...(multiSelected || []), id]; } } } setSelectedBlock({ [uid]: selected }); setCurrentUid(uid); setMultiSelected(newMultiSelected); }; const searchElementInMultiSelection = (uid, blockprops) => { return !!multiSelected.find((el) => el === blockprops.block); }; const applySchemaEnhancer = (originalSchema) => { let schema, schemaEnhancer; const formData = data; const { blocks } = config; const blockType = formData['@type']; const variations = blocks?.blocksConfig[blockType]?.variations || []; if (variations.length === 0) { // No variations present but anyways // finalize the schema with a schemaEnhancer in the block config is present schemaEnhancer = blocks.blocksConfig?.[blockType]?.schemaEnhancer; if (schemaEnhancer) schema = schemaEnhancer({ schema: originalSchema, formData, intl }); } const activeItemName = formData?.variation; let activeItem = variations.find((item) => item.id === activeItemName); if (!activeItem) activeItem = variations.find((item) => item.isDefault); schemaEnhancer = activeItem?.['schemaEnhancer']; schema = schemaEnhancer ? schemaEnhancer({ schema: cloneDeepSchema(originalSchema), formData, intl, }) : cloneDeepSchema(originalSchema); return schema; }; /** * Will set field values from schema, by matching the default values * @returns {Object} defaultValues */ const setInitialData = () => { const accordionSchema = applySchemaEnhancer(AccordionBlockSchema({ intl })); const defaultValues = Object.keys(accordionSchema.properties).reduce( (accumulator, currentVal) => { return accordionSchema.properties[currentVal].default ? { ...accumulator, [currentVal]: accordionSchema.properties[currentVal].default, } : accumulator; }, {}, ); return { ...defaultValues, ...data, data: { ...properties, }, }; }; React.useEffect(() => { if (isEmpty(data?.data)) { onChangeBlock(block, setInitialData()); } /* eslint-disable-next-line */ }, []); React.useEffect(() => { properties.blocks_layout.items.map((item) => { if (isEmpty(properties.blocks[item]?.blocks)) { return onChangeBlock(block, { ...data, data: { ...properties, blocks: { ...properties.blocks, [item]: emptyBlocksForm(), }, }, }); } return undefined; }); }, [ onChangeBlock, properties, selectedBlock, block, data, properties.blocks, ]); const blockState = {}; const panelData = properties; const panels = getPanels(panelData); const handleTitleChange = (e, value) => { const [uid, panel] = value; const modifiedBlock = { ...panel, title: e.target.value, '@type': 'accordionPanel', }; onChangeBlock(block, { ...data, data: { ...panelData, blocks: { ...panelData.blocks, [uid]: modifiedBlock, }, }, }); }; const handleFilteredValueChange = (value) => { setFilterValue(value); }; // Get editing instructions from block settings or props let instructions = data?.instructions?.data || data?.instructions; if (!instructions || instructions === '<p><br/></p>') { instructions = formDescription; } const changeBlockData = (newBlockData) => { const selectedIndex = data.data.blocks[currentUid].blocks_layout.items.indexOf( Object.values(selectedBlock)[0], ) + 1; let pastedBlocks = Object.entries(newBlockData.blocks).filter((block) => { let key = block[0]; return !data?.data?.blocks[currentUid].blocks_layout.items.find( (x) => x === key, ); }); let blockLayout = pastedBlocks.map((el) => el[0]); onChangeBlock(block, { ...data, data: { blocks: { ...data.data.blocks, [currentUid]: { ...data.data.blocks[currentUid], ...newBlockData, blocks_layout: { items: [ ...data.data.blocks[currentUid].blocks_layout.items.slice( 0, selectedIndex, ), ...blockLayout, ...data.data.blocks[currentUid].blocks_layout.items.slice( selectedIndex, ), ], }, }, }, blocks_layout: data.data.blocks_layout, }, }); }; const blockConfig = config.blocks.blocksConfig.accordion; const blocksConfig = blockConfig.blocksConfig || props.blocksConfig; // The accordion is able to get the allowedBlocks info from the custom DX layout // Fallback to the blockConfig one const allowedBlocks = data.allowedBlocks || blockConfig.allowedBlocks; const allowedBlocksConfig = allowedBlocks ? pickBy(blocksConfig, (value, key) => allowedBlocks.includes(key)) : blocksConfig; const schema = AccordionBlockSchema({ intl }); return ( <> {data.headline && <h2 className="headline">{data.headline}</h2>} <fieldset className="accordion-block"> <legend onClick={() => { setSelectedBlock({}); props.setSidebarTab(1); }} aria-hidden="true" > {data.title || schema.properties.data.title} </legend> {data.filtering && ( <AccordionFilter config={blockConfig} data={data} filterValue={filterValue} handleFilteredValueChange={handleFilteredValueChange} /> )} {panels .filter( (panel) => !data.filtering || filterValue === '' || (filterValue !== '' && panel[1].title ?.toLowerCase() .includes(filterValue.toLowerCase())), ) .map(([uid, panel], index) => ( <AccordionEdit uid={uid} panel={panel} panelData={panelData} handleTitleChange={handleTitleChange} handleTitleClick={() => setSelectedBlock({})} data={data} index={index} key={`accordion-${index}`} > <BlocksForm errors={props.errors} key={uid} title={data.placeholder} description={instructions} manage={manage} blocksConfig={allowedBlocksConfig} metadata={metadata} properties={isEmpty(panel) ? emptyBlocksForm() : panel} selectedBlock={selected ? selectedBlock[uid] : null} onSelectBlock={(id, l, e) => { const isMultipleSelection = e ? e.shiftKey || e.ctrlKey || e.metaKey : false; onSelectBlock(uid, id, isMultipleSelection, e, selectedBlock); }} onChangeFormData={(newFormData) => { onChangeBlock(block, { ...data, data: { ...panelData, blocks: { ...panelData.blocks, [uid]: newFormData, }, }, }); }} onChangeField={(id, value) => { if (['blocks', 'blocks_layout'].indexOf(id) > -1) { blockState[id] = value; onChangeBlock(block, { ...data, data: { ...panelData, blocks: { ...panelData.blocks, [uid]: { ...panelData.blocks?.[uid], ...blockState, }, }, }, }); } else { onChangeField(id, value); } }} pathname={pathname} > {({ draginfo }, editBlock, blockProps) => { return ( <EditBlockWrapper draginfo={draginfo} blockProps={blockProps} disabled={data.disableInnerButtons} multiSelected={searchElementInMultiSelection( uid, blockProps, )} extraControls={ <> {instructions && ( <> <Button icon basic title={intl.formatMessage(messages.SectionHelp)} onClick={() => { setSelectedBlock({}); const tab = manage ? 0 : 1; props.setSidebarTab(tab); }} > <Icon name={helpSVG} className="" size="19px" /> </Button> </> )} </> } > {editBlock} </EditBlockWrapper> ); }} </BlocksForm> </AccordionEdit> ))} {selected ? ( <BlocksToolbar selectedBlock={Object.keys(selectedBlock)[0]} formData={data?.data?.blocks?.[currentUid]} selectedBlocks={multiSelected} onSetSelectedBlocks={(blockIds) => { setMultiSelected(blockIds); }} onSelectBlock={(id, l, e) => { const isMultipleSelection = e ? e.shiftKey || e.ctrlKey || e.metaKey : false; onSelectBlock(id, isMultipleSelection, e, selectedBlock); }} onChangeBlocks={(newBlockData) => { changeBlockData(newBlockData); }} /> ) : ( '' )} <SidebarPortal selected={selected && !Object.keys(selectedBlock).length} > {instructions && ( <Segment attached> <div dangerouslySetInnerHTML={{ __html: instructions }} /> </Segment> )} {!data?.readOnlySettings && ( <BlockDataForm schema={schema} title={schema.title} onChangeField={(id, value) => { onChangeBlock(block, { ...data, [id]: value, }); }} formData={data} block={block} blocksConfig={blocksConfig} onChangeBlock={onChangeBlock} /> )} </SidebarPortal> </fieldset> </> ); }; export default withBlockExtensions(Edit);