From 1c5d750d334c35b4efbaab51d31905216eeff17f Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Sat, 18 Jul 2020 08:23:47 +0300 Subject: [PATCH] Added image upload dropzone --- src/TextBlock/TextBlockEdit.jsx | 171 ++++++++++++++++++---- src/TextBlock/keyboard/indentListItems.js | 4 + src/TextBlock/styles.css | 18 +++ src/actions/content.js | 13 ++ src/actions/index.js | 1 + src/actions/selection.js | 4 +- src/constants.js | 7 +- src/index.js | 3 +- src/reducers/content.js | 74 ++++++++++ src/reducers/index.js | 1 + 10 files changed, 257 insertions(+), 39 deletions(-) create mode 100644 src/TextBlock/styles.css create mode 100644 src/actions/content.js create mode 100644 src/reducers/content.js diff --git a/src/TextBlock/TextBlockEdit.jsx b/src/TextBlock/TextBlockEdit.jsx index a01fd91d..552602a6 100644 --- a/src/TextBlock/TextBlockEdit.jsx +++ b/src/TextBlock/TextBlockEdit.jsx @@ -3,12 +3,20 @@ * especially the list element. */ -import React, { useMemo, useState } from 'react'; +import React from 'react'; import { connect } from 'react-redux'; -import { Button } from 'semantic-ui-react'; +import { Button, Dimmer, Loader, Message } from 'semantic-ui-react'; +import { readAsDataURL } from 'promise-file-reader'; import { Icon, BlockChooser, SidebarPortal } from '@plone/volto/components'; +import { uploadContent } from 'volto-slate/actions'; +import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg'; import addSVG from '@plone/volto/icons/circle-plus.svg'; +import { + flattenToAppURL, + getBaseUrl, + isInternalURL, +} from '@plone/volto/helpers'; import { settings } from '~/config'; import { saveSlateBlockSelection } from 'volto-slate/actions'; @@ -16,6 +24,8 @@ import SlateEditor from 'volto-slate/editor'; import { serializeNodesToText } from 'volto-slate/editor/render'; import ShortcutListing from './ShortcutListing'; import { handleKey } from './keyboard'; +import Dropzone from 'react-dropzone'; +import './styles.css'; // import { withList, withDeserializeHtml } from './extensions'; // import { @@ -34,16 +44,24 @@ const TextBlockEdit = (props) => { onChangeBlock, onMutateBlock, onSelectBlock, + pathname, properties, selected, + uploadRequest, + uploadContent, + uploadedContent, } = props; - // console.log('properties', properties); - const { slate } = settings; const { textblockExtensions } = slate; const { value } = data; - const [addNewBlockOpened, setAddNewBlockOpened] = useState(); + + const [addNewBlockOpened, setAddNewBlockOpened] = React.useState(); + const [showDropzone, setShowDropzone] = React.useState(false); + const [uploading, setUploading] = React.useState(false); + const [newImageId, setNewImageId] = React.useState(null); + + const prevReq = React.useRef(null); // const keyDownHandlers = useMemo(() => { // return { @@ -106,36 +124,116 @@ const TextBlockEdit = (props) => { [props], ); + const onDrop = React.useCallback( + (files) => { + setUploading(true); + const file = files[0]; + readAsDataURL(file).then((data) => { + const fields = data.match(/^data:(.*);(.*),(.*)$/); + uploadContent( + getBaseUrl(pathname), + { + '@type': 'Image', + title: file.name, + image: { + data: fields[3], + encoding: fields[2], + 'content-type': fields[1], + filename: file.name, + }, + }, + block, + ); + }); + setShowDropzone(false); + }, + [pathname, uploadContent, block], + ); + + const { loaded, loading } = uploadRequest; + const imageId = uploadedContent['@id']; + const prevLoaded = prevReq.current; + + React.useLayoutEffect(() => { + if (loaded && !loading && !prevLoaded && newImageId !== imageId) { + const url = flattenToAppURL(imageId); + setNewImageId(imageId); + setTimeout(() => { + const id = onAddBlock('image', index + 1); + const options = { + '@type': 'image', + url, + }; + onChangeBlock(id, options); + }, 0); + } + prevReq.current = loaded; + }, [ + loaded, + loading, + prevLoaded, + imageId, + newImageId, + index, + onChangeBlock, + onAddBlock, + ]); + return ( <>
- { - // without using setTimeout, the user types characters on the right side of the text cursor - // timeoutTillRerender = setTimeout(() => { - // saveSlateBlockSelection(block, selection); - // }); - - onChangeBlock(block, { - ...data, - value, - plaintext: serializeNodesToText(value || []), - }); - }} - onKeyDown={handleKey} - selected={selected} - placeholder="Enter some rich text…" - /> + + setShowDropzone(true)} + onDragLeave={() => setShowDropzone(false)} + > + {showDropzone ? ( +
+ {uploading ? ( + + Uploading image + + ) : ( + +
+ +
+
+ )} +
+ ) : ( + { + // without using setTimeout, the user types characters on the right side of the text cursor + // timeoutTillRerender = setTimeout(() => { + // saveSlateBlockSelection(block, selection); + // }); + + onChangeBlock(block, { + ...data, + value, + plaintext: serializeNodesToText(value || []), + }); + }} + onKeyDown={handleKey} + selected={selected} + placeholder="Enter some rich text…" + /> + )} +
{!detached && !data.plaintext && (