414 lines
11 KiB
JavaScript
414 lines
11 KiB
JavaScript
/*!
|
|
* 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') + '<span>edit</span>';
|
|
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: [
|
|
'<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path d="M8.5 17h1.586l7-7L15.5 8.414l-7 7V17zm-1.707-2.707l8-8a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-8 8A1 1 0 0 1 10.5 19h-3a1 1 0 0 1-1-1v-3a1 1 0 0 1 .293-.707z" fill="currentColor" fill-rule="nonzero"/></svg>',
|
|
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;
|