design-comuni-plone-theme
Version:
Volto Theme for Italia design guidelines
228 lines (208 loc) • 7.79 kB
JSX
/*
CUSTOMIZATIONS:
- pasteBlock: fix id handling on copy/cut/paste, which causes wrong css behavior
when getting classes in our custom listing templates, and criteria bugs.
*/
import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import {
messages,
getBlocksFieldname,
getBlocksLayoutFieldname,
} from '@plone/volto/helpers';
import { Icon } from '@plone/volto/components';
import { Plug } from '@plone/volto/components/manage/Pluggable';
import { load } from 'redux-localstorage-simple';
import { isEqual, omit, without } from 'lodash';
import { cloneBlock } from 'design-comuni-plone-theme/helpers/blocks';
import { setBlocksClipboard, resetBlocksClipboard } from '@plone/volto/actions';
import config from '@plone/volto/registry';
import copySVG from '@plone/volto/icons/copy.svg';
import cutSVG from '@plone/volto/icons/cut.svg';
import pasteSVG from '@plone/volto/icons/paste.svg';
import trashSVG from '@plone/volto/icons/delete.svg';
export class BlocksToolbarComponent extends React.Component {
constructor(props) {
super(props);
this.copyBlocksToClipboard = this.copyBlocksToClipboard.bind(this);
this.cutBlocksToClipboard = this.cutBlocksToClipboard.bind(this);
this.deleteBlocks = this.deleteBlocks.bind(this);
this.loadFromStorage = this.loadFromStorage.bind(this);
this.pasteBlocks = this.pasteBlocks.bind(this);
this.setBlocksClipboard = this.setBlocksClipboard.bind(this);
}
loadFromStorage() {
const clipboard = load({ states: ['blocksClipboard'] })?.blocksClipboard;
if (!isEqual(clipboard, this.props.blocksClipboard))
this.props.setBlocksClipboard(clipboard || {});
}
componentDidMount() {
window.addEventListener('storage', this.loadFromStorage, true);
}
componentWillUnmount() {
window.removeEventListener('storage', this.loadFromStorage);
}
deleteBlocks() {
const blockIds = this.props.selectedBlocks;
const { formData } = this.props;
const blocksFieldname = getBlocksFieldname(formData);
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
// Might need ReactDOM.unstable_batchedUpdates()
this.props.onSelectBlock(null);
const newBlockData = {
[blocksFieldname]: omit(formData[blocksFieldname], blockIds),
[blocksLayoutFieldname]: {
...formData[blocksLayoutFieldname],
items: without(formData[blocksLayoutFieldname].items, ...blockIds),
},
};
this.props.onChangeBlocks(newBlockData);
}
copyBlocksToClipboard() {
this.setBlocksClipboard('copy');
}
cutBlocksToClipboard() {
this.setBlocksClipboard('cut');
this.deleteBlocks();
}
setBlocksClipboard(actionType) {
const { formData } = this.props;
const blocksFieldname = getBlocksFieldname(formData);
const blocks = formData[blocksFieldname];
const blocksData = this.props.selectedBlocks.map(
(blockId) => blocks[blockId],
);
this.props.setBlocksClipboard({ [actionType]: blocksData });
this.props.onSetSelectedBlocks([]);
}
// Need to fix id handling on copy/cut/paste, which causes wrong css behavior
// when getting classes in our custom listing templates, and criteria bugs.
// For copy, use newly added cloneBlock util for each listing block, fallback
// to clone util if that function is not defined.
// For cut, we can still use the same logic, not seeing a reason to use
// original code, it keeps old id in blockData while assigning new uuid in layout,
// it's just asking for trouble IMHO
// Keep original use of blockConfig.cloneData, it can be useful if we need to
// define a custom clone behavior for a block, addable in specific block config.
pasteBlocks(e) {
const { formData, blocksClipboard = {}, selectedBlock } = this.props;
const mode = Object.keys(blocksClipboard).includes('cut') ? 'cut' : 'copy';
const blocksData = blocksClipboard[mode] || [];
const cloneWithIds = blocksData
.filter((blockData) => !!blockData['@type'])
.map((blockData) => {
const blockConfig = config.blocks.blocksConfig[blockData['@type']];
return blockConfig.cloneData
? blockConfig.cloneData(blockData)
: cloneBlock(blockData);
})
.filter((info) => !!info); // some blocks may refuse to be copied
const blocksFieldname = getBlocksFieldname(formData);
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
const selectedIndex =
formData[blocksLayoutFieldname].items.indexOf(selectedBlock) + 1;
const newBlockData = {
[blocksFieldname]: {
...formData[blocksFieldname],
...Object.assign(
{},
...cloneWithIds.map(([id, data]) => ({ [id]: data })),
),
},
[blocksLayoutFieldname]: {
...formData[blocksLayoutFieldname],
items: [
...formData[blocksLayoutFieldname].items.slice(0, selectedIndex),
...cloneWithIds.map(([id]) => id),
...formData[blocksLayoutFieldname].items.slice(selectedIndex),
],
},
};
if (!(e.ctrlKey || e.metaKey)) this.props.resetBlocksClipboard();
this.props.onChangeBlocks(newBlockData);
}
render() {
const {
blocksClipboard = {},
selectedBlock,
selectedBlocks,
intl,
} = this.props;
return (
<>
{selectedBlocks.length > 0 ? (
<>
<Plug pluggable="main.toolbar.bottom" id="blocks-delete-btn">
<button
aria-label={intl.formatMessage(messages.deleteBlocks)}
onClick={this.deleteBlocks}
tabIndex={0}
className="deleteBlocks"
id="toolbar-delete-blocks"
>
<Icon name={trashSVG} size="30px" />
</button>
</Plug>
<Plug pluggable="main.toolbar.bottom" id="blocks-cut-btn">
<button
aria-label={intl.formatMessage(messages.cutBlocks)}
onClick={this.cutBlocksToClipboard}
tabIndex={0}
className="cutBlocks"
id="toolbar-cut-blocks"
>
<Icon name={cutSVG} size="30px" />
</button>
</Plug>
<Plug pluggable="main.toolbar.bottom" id="blocks-copy-btn">
<button
aria-label={intl.formatMessage(messages.copyBlocks)}
onClick={this.copyBlocksToClipboard}
tabIndex={0}
className="copyBlocks"
id="toolbar-copy-blocks"
>
<Icon name={copySVG} size="30px" />
</button>
</Plug>
</>
) : (
''
)}
{selectedBlock && (blocksClipboard?.cut || blocksClipboard?.copy) && (
<Plug
pluggable="main.toolbar.bottom"
id="block-paste-btn"
dependencies={[selectedBlock]}
>
<button
aria-label={intl.formatMessage(messages.pasteBlocks)}
onClick={this.pasteBlocks}
tabIndex={0}
className="pasteBlocks"
id="toolbar-paste-blocks"
>
<span className="blockCount">
{(blocksClipboard.cut || blocksClipboard.copy).length}
</span>
<Icon name={pasteSVG} size="30px" />
</button>
</Plug>
)}
</>
);
}
}
export default compose(
injectIntl,
connect(
(state) => {
return {
blocksClipboard: state?.blocksClipboard || {},
};
},
{ setBlocksClipboard, resetBlocksClipboard },
),
)(BlocksToolbarComponent);