/*! * FilePondPluginImageEdit 1.6.3 * Licensed under MIT, https://opensource.org/licenses/MIT/ * Please visit https://pqina.nl/filepond/ for details. */ /* eslint-disable */ const isPreviewableImage = file => /^image/.test(file.type); /** * Image Edit Proxy Plugin */ const plugin = _ => { const { addFilter, utils, views } = _; const { Type, createRoute, createItemAPI = item => item } = utils; const { fileActionButton } = views; addFilter( 'SHOULD_REMOVE_ON_REVERT', (shouldRemove, { item, query }) => new Promise(resolve => { const { file } = item; // if this file is editable it shouldn't be removed immidiately even when instant uploading const canEdit = query('GET_ALLOW_IMAGE_EDIT') && query('GET_IMAGE_EDIT_ALLOW_EDIT') && isPreviewableImage(file); // if the file cannot be edited it should be removed on revert resolve(!canEdit); }) ); // open editor when loading a new item addFilter( 'DID_LOAD_ITEM', (item, { query, dispatch }) => new Promise((resolve, reject) => { // if is temp or local file if (item.origin > 1) { resolve(item); return; } // get file reference const { file } = item; if ( !query('GET_ALLOW_IMAGE_EDIT') || !query('GET_IMAGE_EDIT_INSTANT_EDIT') ) { resolve(item); return; } // exit if this is not an image if (!isPreviewableImage(file)) { resolve(item); return; } const createEditorResponseHandler = ( item, resolve, reject ) => userDidConfirm => { // remove item editRequestQueue.shift(); // handle item if (userDidConfirm) { resolve(item); } else { reject(item); } // TODO: Fix, should not be needed to kick the internal loop in case no processes are running dispatch('KICK'); // handle next item! requestEdit(); }; const requestEdit = () => { if (!editRequestQueue.length) return; const { item, resolve, reject } = editRequestQueue[0]; dispatch('EDIT_ITEM', { id: item.id, handleEditorResponse: createEditorResponseHandler( item, resolve, reject ) }); }; queueEditRequest({ item, resolve, reject }); if (editRequestQueue.length === 1) { requestEdit(); } }) ); // extend item methods addFilter('DID_CREATE_ITEM', (item, { query, dispatch }) => { item.extend('edit', () => { dispatch('EDIT_ITEM', { id: item.id }); }); }); const editRequestQueue = []; const queueEditRequest = editRequest => { editRequestQueue.push(editRequest); return editRequest; }; // called for each view that is created right after the 'create' method addFilter('CREATE_VIEW', viewAPI => { // get reference to created view const { is, view, query } = viewAPI; if (!query('GET_ALLOW_IMAGE_EDIT')) return; const canShowImagePreview = query('GET_ALLOW_IMAGE_PREVIEW'); // only run for either the file or the file info panel const shouldExtendView = (is('file-info') && !canShowImagePreview) || (is('file') && canShowImagePreview); if (!shouldExtendView) return; // no editor defined, then exit const editor = query('GET_IMAGE_EDIT_EDITOR'); if (!editor) return; // set default FilePond options and add bridge once if (!editor.filepondCallbackBridge) { editor.outputData = true; editor.outputFile = false; editor.filepondCallbackBridge = { onconfirm: editor.onconfirm || (() => {}), oncancel: editor.oncancel || (() => {}) }; } // opens the editor, if it does not already exist, it creates the editor const openEditor = ({ root, props, action }) => { const { id } = props; const { handleEditorResponse } = action; // update editor props that could have changed editor.cropAspectRatio = root.query('GET_IMAGE_CROP_ASPECT_RATIO') || editor.cropAspectRatio; editor.outputCanvasBackgroundColor = root.query('GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR') || editor.outputCanvasBackgroundColor; // get item const item = root.query('GET_ITEM', id); if (!item) return; // file to open const file = item.file; // crop data to pass to editor const crop = item.getMetadata('crop'); const cropDefault = { center: { x: 0.5, y: 0.5 }, flip: { horizontal: false, vertical: false }, zoom: 1, rotation: 0, aspectRatio: null }; // size data to pass to editor const resize = item.getMetadata('resize'); // filter and color data to pass to editor const filter = item.getMetadata('filter') || null; const filters = item.getMetadata('filters') || null; const colors = item.getMetadata('colors') || null; const markup = item.getMetadata('markup') || null; // build parameters object const imageParameters = { crop: crop || cropDefault, size: resize ? { upscale: resize.upscale, mode: resize.mode, width: resize.size.width, height: resize.size.height } : null, filter: filters ? filters.id || filters.matrix : root.query('GET_ALLOW_IMAGE_FILTER') && root.query('GET_IMAGE_FILTER_COLOR_MATRIX') && !colors ? filter : null, color: colors, markup }; editor.onconfirm = ({ data }) => { const { crop, size, filter, color, colorMatrix, markup } = data; // create new metadata object const metadata = {}; // append crop data if (crop) { metadata.crop = crop; } // append size data if (size) { const initialSize = (item.getMetadata('resize') || {}).size; const targetSize = { width: size.width, height: size.height }; if (!(targetSize.width && targetSize.height) && initialSize) { targetSize.width = initialSize.width; targetSize.height = initialSize.height; } if (targetSize.width || targetSize.height) { metadata.resize = { upscale: size.upscale, mode: size.mode, size: targetSize }; } } if (markup) { metadata.markup = markup; } // set filters and colors so we can restore them when re-editing the image metadata.colors = color; metadata.filters = filter; // set merged color matrix to use in preview plugin metadata.filter = colorMatrix; // update crop metadata item.setMetadata(metadata); // call editor.filepondCallbackBridge.onconfirm(data, createItemAPI(item)); // used in instant edit mode if (!handleEditorResponse) return; editor.onclose = () => { handleEditorResponse(true); editor.onclose = null; }; }; editor.oncancel = () => { // call editor.filepondCallbackBridge.oncancel(createItemAPI(item)); // used in instant edit mode if (!handleEditorResponse) return; editor.onclose = () => { handleEditorResponse(false); editor.onclose = null; }; }; editor.open(file, imageParameters); }; /** * Image Preview related */ // create the image edit plugin, but only do so if the item is an image const didLoadItem = ({ root, props }) => { if (!query('GET_IMAGE_EDIT_ALLOW_EDIT')) return; const { id } = props; // try to access item const item = query('GET_ITEM', id); if (!item) return; // get the file object const file = item.file; // exit if this is not an image if (!isPreviewableImage(file)) return; // handle interactions root.ref.handleEdit = e => { e.stopPropagation(); root.dispatch('EDIT_ITEM', { id }); }; if (canShowImagePreview) { // add edit button to preview const buttonView = view.createChildView(fileActionButton, { label: 'edit', icon: query('GET_IMAGE_EDIT_ICON_EDIT'), opacity: 0 }); // edit item classname buttonView.element.classList.add('filepond--action-edit-item'); buttonView.element.dataset.align = query( 'GET_STYLE_IMAGE_EDIT_BUTTON_EDIT_ITEM_POSITION' ); buttonView.on('click', root.ref.handleEdit); root.ref.buttonEditItem = view.appendChildView(buttonView); } else { // view is file info const filenameElement = view.element.querySelector( '.filepond--file-info-main' ); const editButton = document.createElement('button'); editButton.className = 'filepond--action-edit-item-alt'; editButton.innerHTML = query('GET_IMAGE_EDIT_ICON_EDIT') + 'edit'; editButton.addEventListener('click', root.ref.handleEdit); filenameElement.appendChild(editButton); root.ref.editButton = editButton; } }; view.registerDestroyer(({ root }) => { if (root.ref.buttonEditItem) { root.ref.buttonEditItem.off('click', root.ref.handleEdit); } if (root.ref.editButton) { root.ref.editButton.removeEventListener('click', root.ref.handleEdit); } }); const routes = { EDIT_ITEM: openEditor, DID_LOAD_ITEM: didLoadItem }; if (canShowImagePreview) { // view is file const didPreviewUpdate = ({ root }) => { if (!root.ref.buttonEditItem) return; root.ref.buttonEditItem.opacity = 1; }; routes.DID_IMAGE_PREVIEW_SHOW = didPreviewUpdate; } else { } // start writing view.registerWriter(createRoute(routes)); }); // Expose plugin options return { options: { // enable or disable image editing allowImageEdit: [true, Type.BOOLEAN], // location of processing button styleImageEditButtonEditItemPosition: ['bottom center', Type.STRING], // open editor when image is dropped imageEditInstantEdit: [false, Type.BOOLEAN], // allow editing imageEditAllowEdit: [true, Type.BOOLEAN], // the icon to use for the edit button imageEditIconEdit: [ '', Type.STRING ], // editor object imageEditEditor: [null, Type.OBJECT] } }; }; // fire pluginloaded event if running in browser, this allows registering the plugin when using async script tags const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; if (isBrowser) { document.dispatchEvent( new CustomEvent('FilePond:pluginloaded', { detail: plugin }) ); } export default plugin;