Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

Commit

Permalink
Added Copy/Paste appextras
Browse files Browse the repository at this point in the history
  • Loading branch information
razvanMiu committed Jan 26, 2022
1 parent 5a986f4 commit 3e752fb
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 1 deletion.
176 changes: 176 additions & 0 deletions src/components/theme/AppExtras/CopyPaste/CopyPaste.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import React from 'react';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Portal } from 'react-portal';
import { toast } from 'react-toastify';
import { Button } from 'semantic-ui-react';
import { getBaseUrl } from '@plone/volto/helpers';
import { updateContent } from '@plone/volto/actions';
import { Icon, Toast } from '@plone/volto/components';
import copySVG from '@plone/volto/icons/copy.svg';
import pasteSVG from '@plone/volto/icons/paste.svg';

import './style.less';

const TIMEOUT = 2000;

const CopyPaste = (props) => {
const [readyToRender, setReadyToRender] = React.useState(false);
const clock = React.useRef(null);
const time = React.useRef(0);
const toolbar = React.useRef(null);
const { content } = props;

const copyData = () => {
navigator.clipboard.writeText(
JSON.stringify({
blocks: content.blocks,
blocks_layout: content.blocks_layout,
}),
);
toast.success(
<Toast success title={'Success'} content={`Copied blocks`} />,
);
};

const pasteData = () => {
const message = [
'============= BRAKING CHANGE =============',
'\nAre you sure you want to paste from clipboard?',
'\nThis action will replace all the blocks with those from clipboard and will trigger SUBMIT !!!',
];
navigator.clipboard.readText().then((text) => {
if (
// eslint-disable-next-line no-alert
window.confirm(message.join(''))
) {
try {
const data = JSON.parse(text) || {};
const { blocks = {}, blocks_layout = {} } = data;
const blocksIds = Object.keys(blocks);
let valid = true;
if (
blocks_layout &&
blocks_layout.items &&
blocks_layout.items.length === blocksIds.length
) {
blocks_layout.items.forEach((block) => {
if (valid && !blocksIds.includes(block)) {
valid = false;
}
});
}
if (valid) {
props.updateContent(getBaseUrl(props.pathname), data);
toast.success(
<Toast
success
title={'Success'}
content={'Blocks replaced successfully'}
/>,
);
} else {
toast.error(
<Toast
error
title={'Error'}
content={'Your clipboard contains incompatible data'}
/>,
);
}
} catch {
toast.error(
<Toast
error
title={'Error'}
content={'Your clipboard contains incompatible data'}
/>,
);
}
}
});
};

React.useEffect(() => {
clock.current = setInterval(() => {
const element = document.querySelector('#toolbar .toolbar-actions');
if (element) {
setReadyToRender(true);
clearInterval(clock.current);
time.current = 0;
return;
}
if (time.current >= TIMEOUT) {
clearInterval(clock.current);
time.current = 0;
return;
}
time.current += 100;
}, 100);
return () => {
clearInterval(clock.current);
time.current = 0;
};
}, []);

if (!__CLIENT__ || !readyToRender) return '';

return (
<Portal node={document.querySelector('#toolbar .toolbar-actions')}>
<div
ref={toolbar}
id="__developer_tools"
onMouseEnter={(e) => {
if (
e.altKey &&
e.ctrlKey &&
toolbar.current &&
!toolbar.current.classList.contains('__dev_on')
) {
toolbar.current.classList.add('__dev_on');
}
}}
onMouseLeave={(e) => {
if (
toolbar.current &&
toolbar.current.classList.contains('__dev_on')
) {
toolbar.current.classList.remove('__dev_on');
}
}}
onFocus={() => {}}
>
<Button
className="copy"
aria-label="Copy blocks data"
title="Copy blocks data"
onClick={copyData}
>
<Icon name={copySVG} className="circled" size="30px" />
</Button>
<Button
className="paste"
aria-label="Paste blocks data"
title="Paste blocks data"
onClick={pasteData}
disabled={props.updateRequest.loading}
>
<Icon name={pasteSVG} className="circled" size="30px" />
</Button>
</div>
</Portal>
);
};

export default compose(
connect(
(state, props) => ({
content: state.content.data,
updateRequest: state.content.update,
pathname: props.location.pathname,
}),
{
updateContent,
},
),
)(CopyPaste);
3 changes: 3 additions & 0 deletions src/components/theme/AppExtras/CopyPaste/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import CopyPaste from './CopyPaste';

export default CopyPaste;
18 changes: 18 additions & 0 deletions src/components/theme/AppExtras/CopyPaste/style.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#__developer_tools {
.ui.button {
color: #007eb1;
opacity: 0;
pointer-events: none;

&.disabled {
opacity: 0 !important;
}
}

&.__dev_on {
.ui.button {
opacity: 1;
pointer-events: all;
}
}
}
13 changes: 13 additions & 0 deletions src/components/theme/AppExtras/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import CopyPaste from './CopyPaste';

export default (config) => {
config.settings.appExtras = [
...(config.settings.appExtras || []),
{
match: '/**/edit',
component: CopyPaste,
},
];

return config;
};
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Forbidden from '@plone/volto/components/theme/Forbidden/Forbidden';
import Unauthorized from '@plone/volto/components/theme/Unauthorized/Unauthorized';

import installAppExtras from '@eeacms/volto-forests-theme/components/theme/AppExtras';
import { installBlocks } from '@eeacms/volto-plotlycharts';
import { applyConfig as installFiseFrontend } from './localconfig';

Expand All @@ -11,7 +12,7 @@ import './slate-styles.css';

export default function applyConfig(config) {
// Add here your project's configuration here by modifying `config` accordingly
config = [installBlocks, installFiseFrontend].reduce(
config = [installBlocks, installAppExtras, installFiseFrontend].reduce(
(acc, apply) => apply(acc),
config,
);
Expand Down

0 comments on commit 3e752fb

Please sign in to comment.