Setting up Doka Image Editor with React Dropzone

For a quick start use the React Dropzone example project included in the product package as a guideline.

To set up the editor with React Dropzone we use the JavaScript version of Doka ImageEditor.

We can install the doka package with npm by copying the dependency to the 'local_modules' folder in the root of our React project and running the following command to install it locally.

npm install --save ./local_modules/doka

Before continuing it's highly recommended to read the JavaScript installation guide as it clarifies important internal functionality of the image editor.

import React, { useState, useEffect } from 'react';

// React Dropzone
import { useDropzone } from 'react-dropzone';

// Doka
import 'doka/doka.css';
import {
    // Image editor
    openEditor,
    createDefaultImageReader,
    createDefaultImageWriter,

    // The method used to register the plugins
    setPlugins,

    // The plugins we want to use
    plugin_crop,
    plugin_finetune,
    plugin_annotate,

    // The main UI and plugin locale objects
    locale_en_gb,
    plugin_crop_locale_en_gb,
    plugin_finetune_locale_en_gb,
    plugin_annotate_locale_en_gb,

    // Because we use the annotate plugin we also need
    // to import the markup editor locale
    markup_editor_locale_en_gb,

    // Import the default properties
    markup_editor_defaults,
    plugin_finetune_defaults,
} from 'doka';

// This registers the plugins with Doka Image Editor
setPlugins(plugin_crop, plugin_finetune, plugin_annotate);

// Based on the default React Dropzone image thumbnail example
// The `thumbButton` style positions the edit button in the bottom right corner of the thumbnail
const thumbsContainer = {
    display: 'flex',
    flexDirection: 'row',
    flexWrap: 'wrap',
    marginTop: 16,
    padding: 20,
};

const thumb = {
    position: 'relative',
    display: 'inline-flex',
    borderRadius: 2,
    border: '1px solid #eaeaea',
    marginBottom: 8,
    marginRight: 8,
    width: 100,
    height: 100,
    padding: 4,
    boxSizing: 'border-box',
};

const thumbInner = {
    display: 'flex',
    minWidth: 0,
    overflow: 'hidden',
};

const img = {
    display: 'block',
    width: 'auto',
    height: '100%',
};

const thumbButton = {
    position: 'absolute',
    right: 10,
    bottom: 10,
};

// This function is called when the user taps the edit button.
// It opens the editor and returns the modified file when done
const editImage = (image, done) => {
    const imageFile = image.doka ? image.doka.file : image;
    const imageState = image.doka ? image.doka.data : {};

    const editor = openEditor({
        src: imageFile,
        imageState,
        imageReader: createDefaultImageReader(),
        imageWriter: createDefaultImageWriter(),

        // Set Markup Editor defaults
        ...markup_editor_defaults,

        // Set Finetune plugin defaults
        ...plugin_finetune_defaults,

        // Set locale
        locale: {
            ...locale_en_gb,
            ...plugin_crop_locale_en_gb,
            ...plugin_finetune_locale_en_gb,
            ...plugin_annotate_locale_en_gb,
            ...markup_editor_locale_en_gb,
        },
    });

    editor.on('close', () => {
        // the user cancelled editing the image
    });

    editor.on('process', ({ dest, imageState }) => {
        Object.assign(dest, {
            doka: { file: imageFile, data: imageState },
        });
        done(dest);
    });
};

function App() {
    const [files, setFiles] = useState([]);
    const { getRootProps, getInputProps } = useDropzone({
        accept: 'image/*',
        onDrop: (acceptedFiles) => {
            setFiles(
                acceptedFiles.map((file) =>
                    Object.assign(file, {
                        preview: URL.createObjectURL(file),
                    })
                )
            );
        },
    });

    const thumbs = files.map((file, index) => (
        <div style={thumb} key={file.name}>
            <div style={thumbInner}>
                <img src={file.preview} style={img} alt="" />
            </div>
            <button
                style={thumbButton}
                onClick={() =>
                    editImage(file, (output) => {
                        const updatedFiles = [...files];

                        // replace original image with new image
                        updatedFiles[index] = output;

                        // revoke preview URL for old image
                        if (file.preview) URL.revokeObjectURL(file.preview);

                        // set new preview URL
                        Object.assign(output, {
                            preview: URL.createObjectURL(output),
                        });

                        // update view
                        setFiles(updatedFiles);
                    })
                }
            >
                Edit
            </button>
        </div>
    ));

    useEffect(
        () => () => {
            // Make sure to revoke the Object URL to avoid memory leaks
            files.forEach((file) => URL.revokeObjectURL(file.preview));
        },
        [files]
    );

    return (
        <section className="container">
            <div {...getRootProps({ className: 'dropzone' })}>
                <input {...getInputProps()} />
                <p>Drag 'n' drop some files here, or click to select files</p>
            </div>
            <aside style={thumbsContainer}>{thumbs}</aside>
        </section>
    );
}

export default App;

Next step

With the editor set up, we can continue to configure the editor to our liking with the available instance properties