├── .nvmrc ├── .gitignore ├── .gitattributes ├── .prettierrc ├── banner.js ├── src ├── css │ ├── modifiers.scss │ └── edit-item-button.scss └── js │ └── index.js ├── banner-cli.js ├── .babelrc ├── README.md ├── rollup.config.js ├── types └── index.d.ts ├── dist ├── filepond-plugin-image-edit.min.css ├── filepond-plugin-image-edit.css ├── filepond-plugin-image-edit.esm.min.js ├── filepond-plugin-image-edit.min.js ├── filepond-plugin-image-edit.esm.js └── filepond-plugin-image-edit.js ├── LICENSE ├── rollup.scripts.js ├── package.json └── index.html /.nvmrc: -------------------------------------------------------------------------------- 1 | v10.15.3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules/ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/* linguist-vendored=false 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "svelteSortOrder": "scripts-markup-styles", 3 | "trailingComma": "es5", 4 | "tabWidth": 4, 5 | "printWidth": 80, 6 | "singleQuote": true, 7 | "semi": true 8 | } 9 | -------------------------------------------------------------------------------- /banner.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ id, version, license, homepage }) => `/*! 2 | * ${ id } ${ version } 3 | * Licensed under ${ license }, https://opensource.org/licenses/${ license }/ 4 | * Please visit ${ homepage } for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | `; -------------------------------------------------------------------------------- /src/css/modifiers.scss: -------------------------------------------------------------------------------- 1 | .filepond--root { 2 | 3 | &[data-style-panel-layout~='circle'] { 4 | 5 | .filepond--action-edit-item { 6 | opacity:1 !important; 7 | visibility: visible !important; 8 | } 9 | 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /banner-cli.js: -------------------------------------------------------------------------------- 1 | const banner = require('./banner'); 2 | const pkg = require('./package.json'); 3 | const args = process.argv.slice(2); 4 | process.stdin.resume(); 5 | process.stdin.setEncoding('utf8'); 6 | process.stdin.on('data', function(data) { 7 | process.stdout.write(banner({ id: args[0], ...pkg }) + data); 8 | }); -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "retainLines": true, 3 | "presets": [ 4 | ["@babel/preset-env", { 5 | "exclude": ["transform-typeof-symbol"], 6 | "modules": false 7 | }] 8 | ], 9 | "plugins": [ 10 | ["@babel/plugin-proposal-object-rest-spread", { 11 | "loose": true, 12 | "useBuiltIns": true 13 | }] 14 | ] 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Image Edit plugin for FilePond 2 | 3 | [![npm version](https://badge.fury.io/js/filepond-plugin-image-edit.svg)](https://badge.fury.io/js/filepond) 4 | 5 | https://pqina.nl/filepond/docs/patterns/plugins/image-edit/ 6 | 7 | The Image Edit plugin facilitates integration of image editor libraries like [Pintura](https://pqina.nl/pintura/) with FilePond. 8 | 9 | **This is not an image editor in itself, it requires another library to edit the images** 10 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import * as pkg from './package.json'; 2 | import build from './rollup.scripts'; 3 | 4 | export default build( 5 | { 6 | id: 'FilePondPluginImageEdit', 7 | ...pkg 8 | }, 9 | [ 10 | { 11 | format: 'umd', 12 | transpile: true 13 | }, 14 | { 15 | format: 'umd', 16 | transpile: true, 17 | minify: true 18 | }, 19 | { 20 | format: 'es' 21 | }, 22 | { 23 | format: 'es', 24 | minify: true 25 | } 26 | ] 27 | ); -------------------------------------------------------------------------------- /src/css/edit-item-button.scss: -------------------------------------------------------------------------------- 1 | .filepond--action-edit-item.filepond--action-edit-item { 2 | width:2em; 3 | height:2em; 4 | padding:.1875em; 5 | 6 | &[data-align*='center'] { 7 | margin-left:-.1875em; 8 | } 9 | 10 | &[data-align*='bottom'] { 11 | margin-bottom:-.1875em; 12 | } 13 | } 14 | 15 | .filepond--action-edit-item-alt { 16 | border: none; 17 | line-height: inherit; 18 | background: transparent; 19 | font-family: inherit; 20 | color: inherit; 21 | outline: none; 22 | padding: 0; 23 | margin: 0 0 0 .25em; 24 | pointer-events: all; 25 | position: absolute; 26 | 27 | svg { 28 | width: 1.3125em; 29 | height: 1.3125em; 30 | } 31 | 32 | span { 33 | font-size: 0; 34 | opacity: 0; 35 | } 36 | } -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { FilePondOptions } from 'filepond'; 3 | 4 | declare module 'filepond' { 5 | export interface FilePondOptions { 6 | /** Enable or disable image editing */ 7 | allowImageEdit?: boolean; 8 | 9 | /** Position of the image edit button within the image preview window */ 10 | styleImageEditButtonEditItemPosition?: string; 11 | 12 | /** Instantly opens the editor when an image is added, when editing is cancelled the image is not added to FilePond */ 13 | imageEditInstantEdit?: boolean; 14 | 15 | /** Disables the manual edit button. */ 16 | imageEditAllowEdit?: boolean; 17 | 18 | /** The SVG icon used in the image edit button */ 19 | imageEditIconEdit?: string; 20 | 21 | /** The Image Editor to link to FilePond */ 22 | imageEditEditor?: any; 23 | } 24 | } -------------------------------------------------------------------------------- /dist/filepond-plugin-image-edit.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * FilePondPluginImageEdit 1.6.3 3 | * Licensed under MIT, https://opensource.org/licenses/MIT/ 4 | * Please visit https://pqina.nl/filepond/ for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | .filepond--action-edit-item.filepond--action-edit-item{width:2em;height:2em;padding:.1875em;}.filepond--action-edit-item.filepond--action-edit-item[data-align*=center]{margin-left:-.1875em}.filepond--action-edit-item.filepond--action-edit-item[data-align*=bottom]{margin-bottom:-.1875em}.filepond--action-edit-item-alt{border:none;line-height:inherit;background:transparent;font-family:inherit;color:inherit;outline:none;padding:0;margin:0 0 0 .25em;pointer-events:all;position:absolute;}.filepond--action-edit-item-alt svg{width:1.3125em;height:1.3125em}.filepond--action-edit-item-alt span{font-size:0;opacity:0}.filepond--root[data-style-panel-layout~=circle] .filepond--action-edit-item{opacity:1!important;visibility:visible!important} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 PQINA | Rik Schennink 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /dist/filepond-plugin-image-edit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * FilePondPluginImageEdit 1.6.3 3 | * Licensed under MIT, https://opensource.org/licenses/MIT/ 4 | * Please visit https://pqina.nl/filepond/ for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | .filepond--action-edit-item.filepond--action-edit-item { 9 | width: 2em; 10 | height: 2em; 11 | padding: 0.1875em; 12 | } 13 | 14 | .filepond--action-edit-item.filepond--action-edit-item[data-align*='center'] { 15 | margin-left: -0.1875em; 16 | } 17 | 18 | .filepond--action-edit-item.filepond--action-edit-item[data-align*='bottom'] { 19 | margin-bottom: -0.1875em; 20 | } 21 | 22 | .filepond--action-edit-item-alt { 23 | border: none; 24 | line-height: inherit; 25 | background: transparent; 26 | font-family: inherit; 27 | color: inherit; 28 | outline: none; 29 | padding: 0; 30 | margin: 0 0 0 0.25em; 31 | pointer-events: all; 32 | position: absolute; 33 | } 34 | 35 | .filepond--action-edit-item-alt svg { 36 | width: 1.3125em; 37 | height: 1.3125em; 38 | } 39 | 40 | .filepond--action-edit-item-alt span { 41 | font-size: 0; 42 | opacity: 0; 43 | } 44 | .filepond--root[data-style-panel-layout~='circle'] .filepond--action-edit-item { 45 | opacity: 1 !important; 46 | visibility: visible !important; 47 | } 48 | -------------------------------------------------------------------------------- /rollup.scripts.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import license from 'rollup-plugin-license'; 3 | import { terser } from 'rollup-plugin-terser'; 4 | import prettier from 'rollup-plugin-prettier'; 5 | const banner = require('./banner'); 6 | 7 | const createBuild = (options) => { 8 | const { format, id, name, minify = false, transpile = false } = options; 9 | 10 | // get filename 11 | const filename = ['dist/', name]; 12 | if (format === 'es') { 13 | filename.push('.esm'); 14 | } 15 | if (minify) { 16 | filename.push('.min'); 17 | } 18 | filename.push('.js'); 19 | 20 | // collect plugins 21 | const plugins = []; 22 | if (transpile) { 23 | plugins.push(babel({ 24 | exclude: ['node_modules/**'] 25 | })) 26 | } 27 | if (minify) { 28 | plugins.push(terser()) 29 | } 30 | else { 31 | plugins.push(prettier({ 32 | singleQuote: true, 33 | parser: 'babel' 34 | })) 35 | } 36 | plugins.push(license({banner: banner(options)})) 37 | 38 | // return Rollup config 39 | return { 40 | input: 'src/js/index.js', 41 | treeshake: false, 42 | output: [ 43 | { 44 | format, 45 | name: id, 46 | file: filename.join('') 47 | } 48 | ], 49 | plugins 50 | } 51 | }; 52 | 53 | export default (metadata, configs) => configs.map(config => createBuild({ ...metadata, ...config })); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filepond-plugin-image-edit", 3 | "version": "1.6.3", 4 | "description": "Image Edit Plugin for FilePond", 5 | "license": "MIT", 6 | "author": { 7 | "name": "PQINA", 8 | "url": "https://pqina.nl/" 9 | }, 10 | "homepage": "https://pqina.nl/filepond/", 11 | "repository": "pqina/filepond-plugin-image-edit", 12 | "main": "dist/filepond-plugin-image-edit.js", 13 | "browser": "dist/filepond-plugin-image-edit.js", 14 | "module": "dist/filepond-plugin-image-edit.esm.js", 15 | "browserslist": [ 16 | "last 1 version and not Explorer 10", 17 | "Explorer 11", 18 | "iOS >= 9", 19 | "Android >= 4.4" 20 | ], 21 | "files": [ 22 | "dist", 23 | "types/*.d.ts" 24 | ], 25 | "types": "types/index.d.ts", 26 | "scripts": { 27 | "start": "npx rollup -c -w", 28 | "build": "npm run scripts | npm run styles", 29 | "scripts": "npx rollup -c", 30 | "styles": "npm run styles:pretty && npm run styles:nano", 31 | "styles:pretty": "cat src/css/* | npx postcss --no-map --use postcss-nested autoprefixer | npx prettier --single-quote --parser css | node banner-cli.js FilePondPluginImageEdit > dist/filepond-plugin-image-edit.css", 32 | "styles:nano": "cat src/css/* | npx postcss --no-map --use postcss-nested autoprefixer --use cssnano | node banner-cli.js FilePondPluginImageEdit > dist/filepond-plugin-image-edit.min.css" 33 | }, 34 | "peerDependencies": { 35 | "filepond": ">=3.7.2 <5.x" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.4.0", 39 | "@babel/preset-env": "^7.4.2", 40 | "autoprefixer": "^9.5.0", 41 | "cssnano": "^4.1.10", 42 | "node-sass": "^4.11.0", 43 | "postcss": "^8.2.8", 44 | "postcss-cli": "^8.3.1", 45 | "postcss-nested": "^5.0.5", 46 | "prettier": "^1.16.4", 47 | "rollup": "^1.7.0", 48 | "rollup-plugin-babel": "^4.3.2", 49 | "rollup-plugin-commonjs": "^9.2.1", 50 | "rollup-plugin-license": "^0.8.1", 51 | "rollup-plugin-node-resolve": "^4.0.1", 52 | "rollup-plugin-prettier": "^0.6.0", 53 | "rollup-plugin-terser": "^4.0.4" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /dist/filepond-plugin-image-edit.esm.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * FilePondPluginImageEdit 1.6.3 3 | * Licensed under MIT, https://opensource.org/licenses/MIT/ 4 | * Please visit https://pqina.nl/filepond/ for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | 9 | const e=e=>/^image/.test(e.type),t=t=>{const{addFilter:i,utils:o,views:n}=t,{Type:r,createRoute:l,createItemAPI:a=(e=>e)}=o,{fileActionButton:d}=n;i("SHOULD_REMOVE_ON_REVERT",(t,{item:i,query:o})=>new Promise(t=>{const{file:n}=i;t(!(o("GET_ALLOW_IMAGE_EDIT")&&o("GET_IMAGE_EDIT_ALLOW_EDIT")&&e(n)))})),i("DID_LOAD_ITEM",(t,{query:i,dispatch:o})=>new Promise((n,r)=>{if(t.origin>1)return void n(t);const{file:l}=t;if(!i("GET_ALLOW_IMAGE_EDIT")||!i("GET_IMAGE_EDIT_INSTANT_EDIT"))return void n(t);if(!e(l))return void n(t);const a=(e,t,i)=>n=>{E.shift(),n?t(e):i(e),o("KICK"),d()},d=()=>{if(!E.length)return;const{item:e,resolve:t,reject:i}=E[0];o("EDIT_ITEM",{id:e.id,handleEditorResponse:a(e,t,i)})};c({item:t,resolve:n,reject:r}),1===E.length&&d()})),i("DID_CREATE_ITEM",(e,{query:t,dispatch:i})=>{e.extend("edit",()=>{i("EDIT_ITEM",{id:e.id})})});const E=[],c=e=>(E.push(e),e);return i("CREATE_VIEW",t=>{const{is:i,view:o,query:n}=t;if(!n("GET_ALLOW_IMAGE_EDIT"))return;const r=n("GET_ALLOW_IMAGE_PREVIEW");if(!(i("file-info")&&!r||i("file")&&r))return;const E=n("GET_IMAGE_EDIT_EDITOR");if(!E)return;E.filepondCallbackBridge||(E.outputData=!0,E.outputFile=!1,E.filepondCallbackBridge={onconfirm:E.onconfirm||(()=>{}),oncancel:E.oncancel||(()=>{})});o.registerDestroyer(({root:e})=>{e.ref.buttonEditItem&&e.ref.buttonEditItem.off("click",e.ref.handleEdit),e.ref.editButton&&e.ref.editButton.removeEventListener("click",e.ref.handleEdit)});const c={EDIT_ITEM:({root:e,props:t,action:i})=>{const{id:o}=t,{handleEditorResponse:n}=i;E.cropAspectRatio=e.query("GET_IMAGE_CROP_ASPECT_RATIO")||E.cropAspectRatio,E.outputCanvasBackgroundColor=e.query("GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR")||E.outputCanvasBackgroundColor;const r=e.query("GET_ITEM",o);if(!r)return;const l=r.file,d=r.getMetadata("crop"),c=r.getMetadata("resize"),s=r.getMetadata("filter")||null,I=r.getMetadata("filters")||null,_=r.getMetadata("colors")||null,u=r.getMetadata("markup")||null,T={crop:d||{center:{x:.5,y:.5},flip:{horizontal:!1,vertical:!1},zoom:1,rotation:0,aspectRatio:null},size:c?{upscale:c.upscale,mode:c.mode,width:c.size.width,height:c.size.height}:null,filter:I?I.id||I.matrix:e.query("GET_ALLOW_IMAGE_FILTER")&&e.query("GET_IMAGE_FILTER_COLOR_MATRIX")&&!_?s:null,color:_,markup:u};E.onconfirm=(({data:e})=>{const{crop:t,size:i,filter:o,color:l,colorMatrix:d,markup:c}=e,s={};if(t&&(s.crop=t),i){const e=(r.getMetadata("resize")||{}).size,t={width:i.width,height:i.height};t.width&&t.height||!e||(t.width=e.width,t.height=e.height),(t.width||t.height)&&(s.resize={upscale:i.upscale,mode:i.mode,size:t})}c&&(s.markup=c),s.colors=l,s.filters=o,s.filter=d,r.setMetadata(s),E.filepondCallbackBridge.onconfirm(e,a(r)),n&&(E.onclose=(()=>{n(!0),E.onclose=null}))}),E.oncancel=(()=>{E.filepondCallbackBridge.oncancel(a(r)),n&&(E.onclose=(()=>{n(!1),E.onclose=null}))}),E.open(l,T)},DID_LOAD_ITEM:({root:t,props:i})=>{if(!n("GET_IMAGE_EDIT_ALLOW_EDIT"))return;const{id:l}=i,a=n("GET_ITEM",l);if(!a)return;const E=a.file;if(e(E))if(t.ref.handleEdit=(e=>{e.stopPropagation(),t.dispatch("EDIT_ITEM",{id:l})}),r){const e=o.createChildView(d,{label:"edit",icon:n("GET_IMAGE_EDIT_ICON_EDIT"),opacity:0});e.element.classList.add("filepond--action-edit-item"),e.element.dataset.align=n("GET_STYLE_IMAGE_EDIT_BUTTON_EDIT_ITEM_POSITION"),e.on("click",t.ref.handleEdit),t.ref.buttonEditItem=o.appendChildView(e)}else{const e=o.element.querySelector(".filepond--file-info-main"),i=document.createElement("button");i.className="filepond--action-edit-item-alt",i.innerHTML=n("GET_IMAGE_EDIT_ICON_EDIT")+"edit",i.addEventListener("click",t.ref.handleEdit),e.appendChild(i),t.ref.editButton=i}}};if(r){const e=({root:e})=>{e.ref.buttonEditItem&&(e.ref.buttonEditItem.opacity=1)};c.DID_IMAGE_PREVIEW_SHOW=e}o.registerWriter(l(c))}),{options:{allowImageEdit:[!0,r.BOOLEAN],styleImageEditButtonEditItemPosition:["bottom center",r.STRING],imageEditInstantEdit:[!1,r.BOOLEAN],imageEditAllowEdit:[!0,r.BOOLEAN],imageEditIconEdit:['',r.STRING],imageEditEditor:[null,r.OBJECT]}}};"undefined"!=typeof window&&void 0!==window.document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:t}));export default t; 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FilePond Plugin Image Transform Demo 7 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /dist/filepond-plugin-image-edit.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * FilePondPluginImageEdit 1.6.3 3 | * Licensed under MIT, https://opensource.org/licenses/MIT/ 4 | * Please visit https://pqina.nl/filepond/ for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | 9 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).FilePondPluginImageEdit=t()}(this,function(){"use strict";var e=function(e){return/^image/.test(e.type)},t=function(t){var i=t.addFilter,n=t.utils,o=t.views,r=n.Type,a=n.createRoute,l=n.createItemAPI,d=void 0===l?function(e){return e}:l,E=o.fileActionButton;i("SHOULD_REMOVE_ON_REVERT",function(t,i){var n=i.item,o=i.query;return new Promise(function(t){var i=n.file;t(!(o("GET_ALLOW_IMAGE_EDIT")&&o("GET_IMAGE_EDIT_ALLOW_EDIT")&&e(i)))})}),i("DID_LOAD_ITEM",function(t,i){var n=i.query,o=i.dispatch;return new Promise(function(i,r){if(t.origin>1)i(t);else{var a=t.file;if(n("GET_ALLOW_IMAGE_EDIT")&&n("GET_IMAGE_EDIT_INSTANT_EDIT"))if(e(a)){var l=function(e,t,i){return function(n){c.shift(),n?t(e):i(e),o("KICK"),d()}},d=function(){if(c.length){var e=c[0],t=e.item,i=e.resolve,n=e.reject;o("EDIT_ITEM",{id:t.id,handleEditorResponse:l(t,i,n)})}};u({item:t,resolve:i,reject:r}),1===c.length&&d()}else i(t);else i(t)}})}),i("DID_CREATE_ITEM",function(e,t){t.query;var i=t.dispatch;e.extend("edit",function(){i("EDIT_ITEM",{id:e.id})})});var c=[],u=function(e){return c.push(e),e};return i("CREATE_VIEW",function(t){var i=t.is,n=t.view,o=t.query;if(o("GET_ALLOW_IMAGE_EDIT")){var r=o("GET_ALLOW_IMAGE_PREVIEW");if(i("file-info")&&!r||i("file")&&r){var l=o("GET_IMAGE_EDIT_EDITOR");if(l){l.filepondCallbackBridge||(l.outputData=!0,l.outputFile=!1,l.filepondCallbackBridge={onconfirm:l.onconfirm||function(){},oncancel:l.oncancel||function(){}});n.registerDestroyer(function(e){var t=e.root;t.ref.buttonEditItem&&t.ref.buttonEditItem.off("click",t.ref.handleEdit),t.ref.editButton&&t.ref.editButton.removeEventListener("click",t.ref.handleEdit)});var c={EDIT_ITEM:function(e){var t=e.root,i=e.props,n=e.action,o=i.id,r=n.handleEditorResponse;l.cropAspectRatio=t.query("GET_IMAGE_CROP_ASPECT_RATIO")||l.cropAspectRatio,l.outputCanvasBackgroundColor=t.query("GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR")||l.outputCanvasBackgroundColor;var a=t.query("GET_ITEM",o);if(a){var E=a.file,c=a.getMetadata("crop"),u=a.getMetadata("resize"),f=a.getMetadata("filter")||null,s=a.getMetadata("filters")||null,I=a.getMetadata("colors")||null,_=a.getMetadata("markup")||null,T={crop:c||{center:{x:.5,y:.5},flip:{horizontal:!1,vertical:!1},zoom:1,rotation:0,aspectRatio:null},size:u?{upscale:u.upscale,mode:u.mode,width:u.size.width,height:u.size.height}:null,filter:s?s.id||s.matrix:t.query("GET_ALLOW_IMAGE_FILTER")&&t.query("GET_IMAGE_FILTER_COLOR_MATRIX")&&!I?f:null,color:I,markup:_};l.onconfirm=function(e){var t=e.data,i=t.crop,n=t.size,o=t.filter,E=t.color,c=t.colorMatrix,u=t.markup,f={};if(i&&(f.crop=i),n){var s=(a.getMetadata("resize")||{}).size,I={width:n.width,height:n.height};I.width&&I.height||!s||(I.width=s.width,I.height=s.height),(I.width||I.height)&&(f.resize={upscale:n.upscale,mode:n.mode,size:I})}u&&(f.markup=u),f.colors=E,f.filters=o,f.filter=c,a.setMetadata(f),l.filepondCallbackBridge.onconfirm(t,d(a)),r&&(l.onclose=function(){r(!0),l.onclose=null})},l.oncancel=function(){l.filepondCallbackBridge.oncancel(d(a)),r&&(l.onclose=function(){r(!1),l.onclose=null})},l.open(E,T)}},DID_LOAD_ITEM:function(t){var i=t.root,a=t.props;if(o("GET_IMAGE_EDIT_ALLOW_EDIT")){var l=a.id,d=o("GET_ITEM",l);if(d){var c=d.file;if(e(c))if(i.ref.handleEdit=function(e){e.stopPropagation(),i.dispatch("EDIT_ITEM",{id:l})},r){var u=n.createChildView(E,{label:"edit",icon:o("GET_IMAGE_EDIT_ICON_EDIT"),opacity:0});u.element.classList.add("filepond--action-edit-item"),u.element.dataset.align=o("GET_STYLE_IMAGE_EDIT_BUTTON_EDIT_ITEM_POSITION"),u.on("click",i.ref.handleEdit),i.ref.buttonEditItem=n.appendChildView(u)}else{var f=n.element.querySelector(".filepond--file-info-main"),s=document.createElement("button");s.className="filepond--action-edit-item-alt",s.innerHTML=o("GET_IMAGE_EDIT_ICON_EDIT")+"edit",s.addEventListener("click",i.ref.handleEdit),f.appendChild(s),i.ref.editButton=s}}}}};if(r){c.DID_IMAGE_PREVIEW_SHOW=function(e){var t=e.root;t.ref.buttonEditItem&&(t.ref.buttonEditItem.opacity=1)}}n.registerWriter(a(c))}}}}),{options:{allowImageEdit:[!0,r.BOOLEAN],styleImageEditButtonEditItemPosition:["bottom center",r.STRING],imageEditInstantEdit:[!1,r.BOOLEAN],imageEditAllowEdit:[!0,r.BOOLEAN],imageEditIconEdit:['',r.STRING],imageEditEditor:[null,r.OBJECT]}}};return"undefined"!=typeof window&&void 0!==window.document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:t})),t}); 10 | -------------------------------------------------------------------------------- /dist/filepond-plugin-image-edit.esm.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * FilePondPluginImageEdit 1.6.3 3 | * Licensed under MIT, https://opensource.org/licenses/MIT/ 4 | * Please visit https://pqina.nl/filepond/ for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | 9 | const isPreviewableImage = file => /^image/.test(file.type); 10 | 11 | /** 12 | * Image Edit Proxy Plugin 13 | */ 14 | const plugin = _ => { 15 | const { addFilter, utils, views } = _; 16 | const { Type, createRoute, createItemAPI = item => item } = utils; 17 | const { fileActionButton } = views; 18 | 19 | addFilter( 20 | 'SHOULD_REMOVE_ON_REVERT', 21 | (shouldRemove, { item, query }) => 22 | new Promise(resolve => { 23 | const { file } = item; 24 | 25 | // if this file is editable it shouldn't be removed immidiately even when instant uploading 26 | const canEdit = 27 | query('GET_ALLOW_IMAGE_EDIT') && 28 | query('GET_IMAGE_EDIT_ALLOW_EDIT') && 29 | isPreviewableImage(file); 30 | 31 | // if the file cannot be edited it should be removed on revert 32 | resolve(!canEdit); 33 | }) 34 | ); 35 | 36 | // open editor when loading a new item 37 | addFilter( 38 | 'DID_LOAD_ITEM', 39 | (item, { query, dispatch }) => 40 | new Promise((resolve, reject) => { 41 | // if is temp or local file 42 | if (item.origin > 1) { 43 | resolve(item); 44 | return; 45 | } 46 | 47 | // get file reference 48 | const { file } = item; 49 | if ( 50 | !query('GET_ALLOW_IMAGE_EDIT') || 51 | !query('GET_IMAGE_EDIT_INSTANT_EDIT') 52 | ) { 53 | resolve(item); 54 | return; 55 | } 56 | 57 | // exit if this is not an image 58 | if (!isPreviewableImage(file)) { 59 | resolve(item); 60 | return; 61 | } 62 | 63 | const createEditorResponseHandler = ( 64 | item, 65 | resolve, 66 | reject 67 | ) => userDidConfirm => { 68 | // remove item 69 | editRequestQueue.shift(); 70 | 71 | // handle item 72 | if (userDidConfirm) { 73 | resolve(item); 74 | } else { 75 | reject(item); 76 | } 77 | 78 | // TODO: Fix, should not be needed to kick the internal loop in case no processes are running 79 | dispatch('KICK'); 80 | 81 | // handle next item! 82 | requestEdit(); 83 | }; 84 | 85 | const requestEdit = () => { 86 | if (!editRequestQueue.length) return; 87 | 88 | const { item, resolve, reject } = editRequestQueue[0]; 89 | 90 | dispatch('EDIT_ITEM', { 91 | id: item.id, 92 | handleEditorResponse: createEditorResponseHandler( 93 | item, 94 | resolve, 95 | reject 96 | ) 97 | }); 98 | }; 99 | 100 | queueEditRequest({ item, resolve, reject }); 101 | 102 | if (editRequestQueue.length === 1) { 103 | requestEdit(); 104 | } 105 | }) 106 | ); 107 | 108 | // extend item methods 109 | addFilter('DID_CREATE_ITEM', (item, { query, dispatch }) => { 110 | item.extend('edit', () => { 111 | dispatch('EDIT_ITEM', { id: item.id }); 112 | }); 113 | }); 114 | 115 | const editRequestQueue = []; 116 | const queueEditRequest = editRequest => { 117 | editRequestQueue.push(editRequest); 118 | return editRequest; 119 | }; 120 | 121 | // called for each view that is created right after the 'create' method 122 | addFilter('CREATE_VIEW', viewAPI => { 123 | // get reference to created view 124 | const { is, view, query } = viewAPI; 125 | 126 | if (!query('GET_ALLOW_IMAGE_EDIT')) return; 127 | 128 | const canShowImagePreview = query('GET_ALLOW_IMAGE_PREVIEW'); 129 | 130 | // only run for either the file or the file info panel 131 | const shouldExtendView = 132 | (is('file-info') && !canShowImagePreview) || 133 | (is('file') && canShowImagePreview); 134 | 135 | if (!shouldExtendView) return; 136 | 137 | // no editor defined, then exit 138 | const editor = query('GET_IMAGE_EDIT_EDITOR'); 139 | if (!editor) return; 140 | 141 | // set default FilePond options and add bridge once 142 | if (!editor.filepondCallbackBridge) { 143 | editor.outputData = true; 144 | editor.outputFile = false; 145 | editor.filepondCallbackBridge = { 146 | onconfirm: editor.onconfirm || (() => {}), 147 | oncancel: editor.oncancel || (() => {}) 148 | }; 149 | } 150 | 151 | // opens the editor, if it does not already exist, it creates the editor 152 | const openEditor = ({ root, props, action }) => { 153 | const { id } = props; 154 | const { handleEditorResponse } = action; 155 | 156 | // update editor props that could have changed 157 | editor.cropAspectRatio = 158 | root.query('GET_IMAGE_CROP_ASPECT_RATIO') || editor.cropAspectRatio; 159 | editor.outputCanvasBackgroundColor = 160 | root.query('GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR') || 161 | editor.outputCanvasBackgroundColor; 162 | 163 | // get item 164 | const item = root.query('GET_ITEM', id); 165 | if (!item) return; 166 | 167 | // file to open 168 | const file = item.file; 169 | 170 | // crop data to pass to editor 171 | const crop = item.getMetadata('crop'); 172 | const cropDefault = { 173 | center: { 174 | x: 0.5, 175 | y: 0.5 176 | }, 177 | flip: { 178 | horizontal: false, 179 | vertical: false 180 | }, 181 | zoom: 1, 182 | rotation: 0, 183 | aspectRatio: null 184 | }; 185 | 186 | // size data to pass to editor 187 | const resize = item.getMetadata('resize'); 188 | 189 | // filter and color data to pass to editor 190 | const filter = item.getMetadata('filter') || null; 191 | const filters = item.getMetadata('filters') || null; 192 | const colors = item.getMetadata('colors') || null; 193 | const markup = item.getMetadata('markup') || null; 194 | 195 | // build parameters object 196 | const imageParameters = { 197 | crop: crop || cropDefault, 198 | size: resize 199 | ? { 200 | upscale: resize.upscale, 201 | mode: resize.mode, 202 | width: resize.size.width, 203 | height: resize.size.height 204 | } 205 | : null, 206 | filter: filters 207 | ? filters.id || filters.matrix 208 | : root.query('GET_ALLOW_IMAGE_FILTER') && 209 | root.query('GET_IMAGE_FILTER_COLOR_MATRIX') && 210 | !colors 211 | ? filter 212 | : null, 213 | color: colors, 214 | markup 215 | }; 216 | 217 | editor.onconfirm = ({ data }) => { 218 | const { crop, size, filter, color, colorMatrix, markup } = data; 219 | 220 | // create new metadata object 221 | const metadata = {}; 222 | 223 | // append crop data 224 | if (crop) { 225 | metadata.crop = crop; 226 | } 227 | 228 | // append size data 229 | if (size) { 230 | const initialSize = (item.getMetadata('resize') || {}).size; 231 | const targetSize = { 232 | width: size.width, 233 | height: size.height 234 | }; 235 | 236 | if (!(targetSize.width && targetSize.height) && initialSize) { 237 | targetSize.width = initialSize.width; 238 | targetSize.height = initialSize.height; 239 | } 240 | 241 | if (targetSize.width || targetSize.height) { 242 | metadata.resize = { 243 | upscale: size.upscale, 244 | mode: size.mode, 245 | size: targetSize 246 | }; 247 | } 248 | } 249 | 250 | if (markup) { 251 | metadata.markup = markup; 252 | } 253 | 254 | // set filters and colors so we can restore them when re-editing the image 255 | metadata.colors = color; 256 | metadata.filters = filter; 257 | 258 | // set merged color matrix to use in preview plugin 259 | metadata.filter = colorMatrix; 260 | 261 | // update crop metadata 262 | item.setMetadata(metadata); 263 | 264 | // call 265 | editor.filepondCallbackBridge.onconfirm(data, createItemAPI(item)); 266 | 267 | // used in instant edit mode 268 | if (!handleEditorResponse) return; 269 | editor.onclose = () => { 270 | handleEditorResponse(true); 271 | editor.onclose = null; 272 | }; 273 | }; 274 | 275 | editor.oncancel = () => { 276 | // call 277 | editor.filepondCallbackBridge.oncancel(createItemAPI(item)); 278 | 279 | // used in instant edit mode 280 | if (!handleEditorResponse) return; 281 | editor.onclose = () => { 282 | handleEditorResponse(false); 283 | editor.onclose = null; 284 | }; 285 | }; 286 | 287 | editor.open(file, imageParameters); 288 | }; 289 | 290 | /** 291 | * Image Preview related 292 | */ 293 | 294 | // create the image edit plugin, but only do so if the item is an image 295 | const didLoadItem = ({ root, props }) => { 296 | if (!query('GET_IMAGE_EDIT_ALLOW_EDIT')) return; 297 | 298 | const { id } = props; 299 | 300 | // try to access item 301 | const item = query('GET_ITEM', id); 302 | if (!item) return; 303 | 304 | // get the file object 305 | const file = item.file; 306 | 307 | // exit if this is not an image 308 | if (!isPreviewableImage(file)) return; 309 | 310 | // handle interactions 311 | root.ref.handleEdit = e => { 312 | e.stopPropagation(); 313 | root.dispatch('EDIT_ITEM', { id }); 314 | }; 315 | 316 | if (canShowImagePreview) { 317 | // add edit button to preview 318 | const buttonView = view.createChildView(fileActionButton, { 319 | label: 'edit', 320 | icon: query('GET_IMAGE_EDIT_ICON_EDIT'), 321 | opacity: 0 322 | }); 323 | 324 | // edit item classname 325 | buttonView.element.classList.add('filepond--action-edit-item'); 326 | buttonView.element.dataset.align = query( 327 | 'GET_STYLE_IMAGE_EDIT_BUTTON_EDIT_ITEM_POSITION' 328 | ); 329 | buttonView.on('click', root.ref.handleEdit); 330 | 331 | root.ref.buttonEditItem = view.appendChildView(buttonView); 332 | } else { 333 | // view is file info 334 | const filenameElement = view.element.querySelector( 335 | '.filepond--file-info-main' 336 | ); 337 | const editButton = document.createElement('button'); 338 | editButton.className = 'filepond--action-edit-item-alt'; 339 | editButton.innerHTML = 340 | query('GET_IMAGE_EDIT_ICON_EDIT') + 'edit'; 341 | editButton.addEventListener('click', root.ref.handleEdit); 342 | filenameElement.appendChild(editButton); 343 | 344 | root.ref.editButton = editButton; 345 | } 346 | }; 347 | 348 | view.registerDestroyer(({ root }) => { 349 | if (root.ref.buttonEditItem) { 350 | root.ref.buttonEditItem.off('click', root.ref.handleEdit); 351 | } 352 | if (root.ref.editButton) { 353 | root.ref.editButton.removeEventListener('click', root.ref.handleEdit); 354 | } 355 | }); 356 | 357 | const routes = { 358 | EDIT_ITEM: openEditor, 359 | DID_LOAD_ITEM: didLoadItem 360 | }; 361 | 362 | if (canShowImagePreview) { 363 | // view is file 364 | const didPreviewUpdate = ({ root }) => { 365 | if (!root.ref.buttonEditItem) return; 366 | root.ref.buttonEditItem.opacity = 1; 367 | }; 368 | 369 | routes.DID_IMAGE_PREVIEW_SHOW = didPreviewUpdate; 370 | } else { 371 | } 372 | 373 | // start writing 374 | view.registerWriter(createRoute(routes)); 375 | }); 376 | 377 | // Expose plugin options 378 | return { 379 | options: { 380 | // enable or disable image editing 381 | allowImageEdit: [true, Type.BOOLEAN], 382 | 383 | // location of processing button 384 | styleImageEditButtonEditItemPosition: ['bottom center', Type.STRING], 385 | 386 | // open editor when image is dropped 387 | imageEditInstantEdit: [false, Type.BOOLEAN], 388 | 389 | // allow editing 390 | imageEditAllowEdit: [true, Type.BOOLEAN], 391 | 392 | // the icon to use for the edit button 393 | imageEditIconEdit: [ 394 | '', 395 | Type.STRING 396 | ], 397 | 398 | // editor object 399 | imageEditEditor: [null, Type.OBJECT] 400 | } 401 | }; 402 | }; 403 | 404 | // fire pluginloaded event if running in browser, this allows registering the plugin when using async script tags 405 | const isBrowser = 406 | typeof window !== 'undefined' && typeof window.document !== 'undefined'; 407 | if (isBrowser) { 408 | document.dispatchEvent( 409 | new CustomEvent('FilePond:pluginloaded', { detail: plugin }) 410 | ); 411 | } 412 | 413 | export default plugin; 414 | -------------------------------------------------------------------------------- /dist/filepond-plugin-image-edit.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * FilePondPluginImageEdit 1.6.3 3 | * Licensed under MIT, https://opensource.org/licenses/MIT/ 4 | * Please visit https://pqina.nl/filepond/ for details. 5 | */ 6 | 7 | /* eslint-disable */ 8 | 9 | (function(global, factory) { 10 | typeof exports === 'object' && typeof module !== 'undefined' 11 | ? (module.exports = factory()) 12 | : typeof define === 'function' && define.amd 13 | ? define(factory) 14 | : ((global = global || self), (global.FilePondPluginImageEdit = factory())); 15 | })(this, function() { 16 | 'use strict'; 17 | 18 | var isPreviewableImage = function isPreviewableImage(file) { 19 | return /^image/.test(file.type); 20 | }; 21 | 22 | /** 23 | * Image Edit Proxy Plugin 24 | */ 25 | var plugin = function plugin(_) { 26 | var addFilter = _.addFilter, 27 | utils = _.utils, 28 | views = _.views; 29 | var Type = utils.Type, 30 | createRoute = utils.createRoute, 31 | _utils$createItemAPI = utils.createItemAPI, 32 | createItemAPI = 33 | _utils$createItemAPI === void 0 34 | ? function(item) { 35 | return item; 36 | } 37 | : _utils$createItemAPI; 38 | var fileActionButton = views.fileActionButton; 39 | 40 | addFilter('SHOULD_REMOVE_ON_REVERT', function(shouldRemove, _ref) { 41 | var item = _ref.item, 42 | query = _ref.query; 43 | return new Promise(function(resolve) { 44 | var file = item.file; 45 | 46 | // if this file is editable it shouldn't be removed immidiately even when instant uploading 47 | var canEdit = 48 | query('GET_ALLOW_IMAGE_EDIT') && 49 | query('GET_IMAGE_EDIT_ALLOW_EDIT') && 50 | isPreviewableImage(file); 51 | 52 | // if the file cannot be edited it should be removed on revert 53 | resolve(!canEdit); 54 | }); 55 | }); 56 | 57 | // open editor when loading a new item 58 | addFilter('DID_LOAD_ITEM', function(item, _ref2) { 59 | var query = _ref2.query, 60 | dispatch = _ref2.dispatch; 61 | return new Promise(function(resolve, reject) { 62 | // if is temp or local file 63 | if (item.origin > 1) { 64 | resolve(item); 65 | return; 66 | } 67 | 68 | // get file reference 69 | var file = item.file; 70 | if ( 71 | !query('GET_ALLOW_IMAGE_EDIT') || 72 | !query('GET_IMAGE_EDIT_INSTANT_EDIT') 73 | ) { 74 | resolve(item); 75 | return; 76 | } 77 | 78 | // exit if this is not an image 79 | if (!isPreviewableImage(file)) { 80 | resolve(item); 81 | return; 82 | } 83 | 84 | var createEditorResponseHandler = function createEditorResponseHandler( 85 | item, 86 | resolve, 87 | reject 88 | ) { 89 | return function(userDidConfirm) { 90 | // remove item 91 | editRequestQueue.shift(); 92 | 93 | // handle item 94 | if (userDidConfirm) { 95 | resolve(item); 96 | } else { 97 | reject(item); 98 | } 99 | 100 | // TODO: Fix, should not be needed to kick the internal loop in case no processes are running 101 | dispatch('KICK'); 102 | 103 | // handle next item! 104 | requestEdit(); 105 | }; 106 | }; 107 | 108 | var requestEdit = function requestEdit() { 109 | if (!editRequestQueue.length) return; 110 | var _editRequestQueue$ = editRequestQueue[0], 111 | item = _editRequestQueue$.item, 112 | resolve = _editRequestQueue$.resolve, 113 | reject = _editRequestQueue$.reject; 114 | 115 | dispatch('EDIT_ITEM', { 116 | id: item.id, 117 | handleEditorResponse: createEditorResponseHandler( 118 | item, 119 | resolve, 120 | reject 121 | ) 122 | }); 123 | }; 124 | 125 | queueEditRequest({ item: item, resolve: resolve, reject: reject }); 126 | 127 | if (editRequestQueue.length === 1) { 128 | requestEdit(); 129 | } 130 | }); 131 | }); 132 | 133 | // extend item methods 134 | addFilter('DID_CREATE_ITEM', function(item, _ref3) { 135 | var query = _ref3.query, 136 | dispatch = _ref3.dispatch; 137 | item.extend('edit', function() { 138 | dispatch('EDIT_ITEM', { id: item.id }); 139 | }); 140 | }); 141 | 142 | var editRequestQueue = []; 143 | var queueEditRequest = function queueEditRequest(editRequest) { 144 | editRequestQueue.push(editRequest); 145 | return editRequest; 146 | }; 147 | 148 | // called for each view that is created right after the 'create' method 149 | addFilter('CREATE_VIEW', function(viewAPI) { 150 | // get reference to created view 151 | var is = viewAPI.is, 152 | view = viewAPI.view, 153 | query = viewAPI.query; 154 | 155 | if (!query('GET_ALLOW_IMAGE_EDIT')) return; 156 | 157 | var canShowImagePreview = query('GET_ALLOW_IMAGE_PREVIEW'); 158 | 159 | // only run for either the file or the file info panel 160 | var shouldExtendView = 161 | (is('file-info') && !canShowImagePreview) || 162 | (is('file') && canShowImagePreview); 163 | 164 | if (!shouldExtendView) return; 165 | 166 | // no editor defined, then exit 167 | var editor = query('GET_IMAGE_EDIT_EDITOR'); 168 | if (!editor) return; 169 | 170 | // set default FilePond options and add bridge once 171 | if (!editor.filepondCallbackBridge) { 172 | editor.outputData = true; 173 | editor.outputFile = false; 174 | editor.filepondCallbackBridge = { 175 | onconfirm: editor.onconfirm || function() {}, 176 | oncancel: editor.oncancel || function() {} 177 | }; 178 | } 179 | 180 | // opens the editor, if it does not already exist, it creates the editor 181 | var openEditor = function openEditor(_ref4) { 182 | var root = _ref4.root, 183 | props = _ref4.props, 184 | action = _ref4.action; 185 | var id = props.id; 186 | var handleEditorResponse = action.handleEditorResponse; 187 | 188 | // update editor props that could have changed 189 | editor.cropAspectRatio = 190 | root.query('GET_IMAGE_CROP_ASPECT_RATIO') || editor.cropAspectRatio; 191 | editor.outputCanvasBackgroundColor = 192 | root.query('GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR') || 193 | editor.outputCanvasBackgroundColor; 194 | 195 | // get item 196 | var item = root.query('GET_ITEM', id); 197 | if (!item) return; 198 | 199 | // file to open 200 | var file = item.file; 201 | 202 | // crop data to pass to editor 203 | var crop = item.getMetadata('crop'); 204 | var cropDefault = { 205 | center: { 206 | x: 0.5, 207 | y: 0.5 208 | }, 209 | 210 | flip: { 211 | horizontal: false, 212 | vertical: false 213 | }, 214 | 215 | zoom: 1, 216 | rotation: 0, 217 | aspectRatio: null 218 | }; 219 | 220 | // size data to pass to editor 221 | var resize = item.getMetadata('resize'); 222 | 223 | // filter and color data to pass to editor 224 | var filter = item.getMetadata('filter') || null; 225 | var filters = item.getMetadata('filters') || null; 226 | var colors = item.getMetadata('colors') || null; 227 | var markup = item.getMetadata('markup') || null; 228 | 229 | // build parameters object 230 | var imageParameters = { 231 | crop: crop || cropDefault, 232 | size: resize 233 | ? { 234 | upscale: resize.upscale, 235 | mode: resize.mode, 236 | width: resize.size.width, 237 | height: resize.size.height 238 | } 239 | : null, 240 | filter: filters 241 | ? filters.id || filters.matrix 242 | : root.query('GET_ALLOW_IMAGE_FILTER') && 243 | root.query('GET_IMAGE_FILTER_COLOR_MATRIX') && 244 | !colors 245 | ? filter 246 | : null, 247 | color: colors, 248 | markup: markup 249 | }; 250 | 251 | editor.onconfirm = function(_ref5) { 252 | var data = _ref5.data; 253 | var crop = data.crop, 254 | size = data.size, 255 | filter = data.filter, 256 | color = data.color, 257 | colorMatrix = data.colorMatrix, 258 | markup = data.markup; 259 | 260 | // create new metadata object 261 | var metadata = {}; 262 | 263 | // append crop data 264 | if (crop) { 265 | metadata.crop = crop; 266 | } 267 | 268 | // append size data 269 | if (size) { 270 | var initialSize = (item.getMetadata('resize') || {}).size; 271 | var targetSize = { 272 | width: size.width, 273 | height: size.height 274 | }; 275 | 276 | if (!(targetSize.width && targetSize.height) && initialSize) { 277 | targetSize.width = initialSize.width; 278 | targetSize.height = initialSize.height; 279 | } 280 | 281 | if (targetSize.width || targetSize.height) { 282 | metadata.resize = { 283 | upscale: size.upscale, 284 | mode: size.mode, 285 | size: targetSize 286 | }; 287 | } 288 | } 289 | 290 | if (markup) { 291 | metadata.markup = markup; 292 | } 293 | 294 | // set filters and colors so we can restore them when re-editing the image 295 | metadata.colors = color; 296 | metadata.filters = filter; 297 | 298 | // set merged color matrix to use in preview plugin 299 | metadata.filter = colorMatrix; 300 | 301 | // update crop metadata 302 | item.setMetadata(metadata); 303 | 304 | // call 305 | editor.filepondCallbackBridge.onconfirm(data, createItemAPI(item)); 306 | 307 | // used in instant edit mode 308 | if (!handleEditorResponse) return; 309 | editor.onclose = function() { 310 | handleEditorResponse(true); 311 | editor.onclose = null; 312 | }; 313 | }; 314 | 315 | editor.oncancel = function() { 316 | // call 317 | editor.filepondCallbackBridge.oncancel(createItemAPI(item)); 318 | 319 | // used in instant edit mode 320 | if (!handleEditorResponse) return; 321 | editor.onclose = function() { 322 | handleEditorResponse(false); 323 | editor.onclose = null; 324 | }; 325 | }; 326 | 327 | editor.open(file, imageParameters); 328 | }; 329 | 330 | /** 331 | * Image Preview related 332 | */ 333 | 334 | // create the image edit plugin, but only do so if the item is an image 335 | var didLoadItem = function didLoadItem(_ref6) { 336 | var root = _ref6.root, 337 | props = _ref6.props; 338 | 339 | if (!query('GET_IMAGE_EDIT_ALLOW_EDIT')) return; 340 | var id = props.id; 341 | 342 | // try to access item 343 | var item = query('GET_ITEM', id); 344 | if (!item) return; 345 | 346 | // get the file object 347 | var file = item.file; 348 | 349 | // exit if this is not an image 350 | if (!isPreviewableImage(file)) return; 351 | 352 | // handle interactions 353 | root.ref.handleEdit = function(e) { 354 | e.stopPropagation(); 355 | root.dispatch('EDIT_ITEM', { id: id }); 356 | }; 357 | 358 | if (canShowImagePreview) { 359 | // add edit button to preview 360 | var buttonView = view.createChildView(fileActionButton, { 361 | label: 'edit', 362 | icon: query('GET_IMAGE_EDIT_ICON_EDIT'), 363 | opacity: 0 364 | }); 365 | 366 | // edit item classname 367 | buttonView.element.classList.add('filepond--action-edit-item'); 368 | buttonView.element.dataset.align = query( 369 | 'GET_STYLE_IMAGE_EDIT_BUTTON_EDIT_ITEM_POSITION' 370 | ); 371 | buttonView.on('click', root.ref.handleEdit); 372 | 373 | root.ref.buttonEditItem = view.appendChildView(buttonView); 374 | } else { 375 | // view is file info 376 | var filenameElement = view.element.querySelector( 377 | '.filepond--file-info-main' 378 | ); 379 | var editButton = document.createElement('button'); 380 | editButton.className = 'filepond--action-edit-item-alt'; 381 | editButton.innerHTML = 382 | query('GET_IMAGE_EDIT_ICON_EDIT') + 'edit'; 383 | editButton.addEventListener('click', root.ref.handleEdit); 384 | filenameElement.appendChild(editButton); 385 | 386 | root.ref.editButton = editButton; 387 | } 388 | }; 389 | 390 | view.registerDestroyer(function(_ref7) { 391 | var root = _ref7.root; 392 | if (root.ref.buttonEditItem) { 393 | root.ref.buttonEditItem.off('click', root.ref.handleEdit); 394 | } 395 | if (root.ref.editButton) { 396 | root.ref.editButton.removeEventListener('click', root.ref.handleEdit); 397 | } 398 | }); 399 | 400 | var routes = { 401 | EDIT_ITEM: openEditor, 402 | DID_LOAD_ITEM: didLoadItem 403 | }; 404 | 405 | if (canShowImagePreview) { 406 | // view is file 407 | var didPreviewUpdate = function didPreviewUpdate(_ref8) { 408 | var root = _ref8.root; 409 | if (!root.ref.buttonEditItem) return; 410 | root.ref.buttonEditItem.opacity = 1; 411 | }; 412 | 413 | routes.DID_IMAGE_PREVIEW_SHOW = didPreviewUpdate; 414 | } else { 415 | } 416 | 417 | // start writing 418 | view.registerWriter(createRoute(routes)); 419 | }); 420 | 421 | // Expose plugin options 422 | return { 423 | options: { 424 | // enable or disable image editing 425 | allowImageEdit: [true, Type.BOOLEAN], 426 | 427 | // location of processing button 428 | styleImageEditButtonEditItemPosition: ['bottom center', Type.STRING], 429 | 430 | // open editor when image is dropped 431 | imageEditInstantEdit: [false, Type.BOOLEAN], 432 | 433 | // allow editing 434 | imageEditAllowEdit: [true, Type.BOOLEAN], 435 | 436 | // the icon to use for the edit button 437 | imageEditIconEdit: [ 438 | '', 439 | Type.STRING 440 | ], 441 | 442 | // editor object 443 | imageEditEditor: [null, Type.OBJECT] 444 | } 445 | }; 446 | }; 447 | 448 | // fire pluginloaded event if running in browser, this allows registering the plugin when using async script tags 449 | var isBrowser = 450 | typeof window !== 'undefined' && typeof window.document !== 'undefined'; 451 | if (isBrowser) { 452 | document.dispatchEvent( 453 | new CustomEvent('FilePond:pluginloaded', { detail: plugin }) 454 | ); 455 | } 456 | 457 | return plugin; 458 | }); 459 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | const isPreviewableImage = file => /^image/.test(file.type); 2 | 3 | /** 4 | * Image Edit Proxy Plugin 5 | */ 6 | const plugin = _ => { 7 | const { addFilter, utils, views } = _; 8 | const { Type, createRoute, createItemAPI = item => item } = utils; 9 | const { fileActionButton } = views; 10 | 11 | addFilter( 12 | 'SHOULD_REMOVE_ON_REVERT', 13 | (shouldRemove, { item, query }) => 14 | new Promise(resolve => { 15 | const { file } = item; 16 | 17 | // if this file is editable it shouldn't be removed immidiately even when instant uploading 18 | const canEdit = 19 | query('GET_ALLOW_IMAGE_EDIT') && 20 | query('GET_IMAGE_EDIT_ALLOW_EDIT') && 21 | isPreviewableImage(file); 22 | 23 | // if the file cannot be edited it should be removed on revert 24 | resolve(!canEdit); 25 | }) 26 | ); 27 | 28 | // open editor when loading a new item 29 | addFilter( 30 | 'DID_LOAD_ITEM', 31 | (item, { query, dispatch }) => 32 | new Promise((resolve, reject) => { 33 | // if is temp or local file 34 | if (item.origin > 1) { 35 | resolve(item); 36 | return; 37 | } 38 | 39 | // get file reference 40 | const { file } = item; 41 | if ( 42 | !query('GET_ALLOW_IMAGE_EDIT') || 43 | !query('GET_IMAGE_EDIT_INSTANT_EDIT') 44 | ) { 45 | resolve(item); 46 | return; 47 | } 48 | 49 | // exit if this is not an image 50 | if (!isPreviewableImage(file)) { 51 | resolve(item); 52 | return; 53 | } 54 | 55 | const createEditorResponseHandler = ( 56 | item, 57 | resolve, 58 | reject 59 | ) => userDidConfirm => { 60 | // remove item 61 | editRequestQueue.shift(); 62 | 63 | // handle item 64 | if (userDidConfirm) { 65 | resolve(item); 66 | } else { 67 | reject(item); 68 | } 69 | 70 | // TODO: Fix, should not be needed to kick the internal loop in case no processes are running 71 | dispatch('KICK'); 72 | 73 | // handle next item! 74 | requestEdit(); 75 | }; 76 | 77 | const requestEdit = () => { 78 | if (!editRequestQueue.length) return; 79 | 80 | const { item, resolve, reject } = editRequestQueue[0]; 81 | 82 | dispatch('EDIT_ITEM', { 83 | id: item.id, 84 | handleEditorResponse: createEditorResponseHandler( 85 | item, 86 | resolve, 87 | reject 88 | ), 89 | }); 90 | }; 91 | 92 | queueEditRequest({ item, resolve, reject }); 93 | 94 | if (editRequestQueue.length === 1) { 95 | requestEdit(); 96 | } 97 | }) 98 | ); 99 | 100 | // extend item methods 101 | addFilter('DID_CREATE_ITEM', (item, { query, dispatch }) => { 102 | item.extend('edit', () => { 103 | dispatch('EDIT_ITEM', { id: item.id }); 104 | }); 105 | }); 106 | 107 | const editRequestQueue = []; 108 | const queueEditRequest = editRequest => { 109 | editRequestQueue.push(editRequest); 110 | return editRequest; 111 | }; 112 | 113 | // called for each view that is created right after the 'create' method 114 | addFilter('CREATE_VIEW', viewAPI => { 115 | // get reference to created view 116 | const { is, view, query } = viewAPI; 117 | 118 | if (!query('GET_ALLOW_IMAGE_EDIT')) return; 119 | 120 | const canShowImagePreview = query('GET_ALLOW_IMAGE_PREVIEW'); 121 | 122 | // only run for either the file or the file info panel 123 | const shouldExtendView = 124 | (is('file-info') && !canShowImagePreview) || 125 | (is('file') && canShowImagePreview); 126 | 127 | if (!shouldExtendView) return; 128 | 129 | // no editor defined, then exit 130 | const editor = query('GET_IMAGE_EDIT_EDITOR'); 131 | if (!editor) return; 132 | 133 | // set default FilePond options and add bridge once 134 | if (!editor.filepondCallbackBridge) { 135 | editor.outputData = true; 136 | editor.outputFile = false; 137 | editor.filepondCallbackBridge = { 138 | onconfirm: editor.onconfirm || (() => {}), 139 | oncancel: editor.oncancel || (() => {}), 140 | }; 141 | } 142 | 143 | // opens the editor, if it does not already exist, it creates the editor 144 | const openEditor = ({ root, props, action }) => { 145 | const { id } = props; 146 | const { handleEditorResponse } = action; 147 | 148 | // update editor props that could have changed 149 | editor.cropAspectRatio = 150 | root.query('GET_IMAGE_CROP_ASPECT_RATIO') || 151 | editor.cropAspectRatio; 152 | editor.outputCanvasBackgroundColor = 153 | root.query('GET_IMAGE_TRANSFORM_CANVAS_BACKGROUND_COLOR') || 154 | editor.outputCanvasBackgroundColor; 155 | 156 | // get item 157 | const item = root.query('GET_ITEM', id); 158 | if (!item) return; 159 | 160 | // file to open 161 | const file = item.file; 162 | 163 | // crop data to pass to editor 164 | const crop = item.getMetadata('crop'); 165 | const cropDefault = { 166 | center: { 167 | x: 0.5, 168 | y: 0.5, 169 | }, 170 | flip: { 171 | horizontal: false, 172 | vertical: false, 173 | }, 174 | zoom: 1, 175 | rotation: 0, 176 | aspectRatio: null, 177 | }; 178 | 179 | // size data to pass to editor 180 | const resize = item.getMetadata('resize'); 181 | 182 | // filter and color data to pass to editor 183 | const filter = item.getMetadata('filter') || null; 184 | const filters = item.getMetadata('filters') || null; 185 | const colors = item.getMetadata('colors') || null; 186 | const markup = item.getMetadata('markup') || null; 187 | 188 | // build parameters object 189 | const imageParameters = { 190 | crop: crop || cropDefault, 191 | size: resize 192 | ? { 193 | upscale: resize.upscale, 194 | mode: resize.mode, 195 | width: resize.size.width, 196 | height: resize.size.height, 197 | } 198 | : null, 199 | filter: filters 200 | ? filters.id || filters.matrix 201 | : root.query('GET_ALLOW_IMAGE_FILTER') && 202 | root.query('GET_IMAGE_FILTER_COLOR_MATRIX') && 203 | !colors 204 | ? filter 205 | : null, 206 | color: colors, 207 | markup, 208 | }; 209 | 210 | editor.onconfirm = ({ data }) => { 211 | const { crop, size, filter, color, colorMatrix, markup } = data; 212 | 213 | // create new metadata object 214 | const metadata = {}; 215 | 216 | // append crop data 217 | if (crop) { 218 | metadata.crop = crop; 219 | } 220 | 221 | // append size data 222 | if (size) { 223 | const initialSize = (item.getMetadata('resize') || {}).size; 224 | const targetSize = { 225 | width: size.width, 226 | height: size.height, 227 | }; 228 | 229 | if ( 230 | !(targetSize.width && targetSize.height) && 231 | initialSize 232 | ) { 233 | targetSize.width = initialSize.width; 234 | targetSize.height = initialSize.height; 235 | } 236 | 237 | if (targetSize.width || targetSize.height) { 238 | metadata.resize = { 239 | upscale: size.upscale, 240 | mode: size.mode, 241 | size: targetSize, 242 | }; 243 | } 244 | } 245 | 246 | if (markup) { 247 | metadata.markup = markup; 248 | } 249 | 250 | // set filters and colors so we can restore them when re-editing the image 251 | metadata.colors = color; 252 | metadata.filters = filter; 253 | 254 | // set merged color matrix to use in preview plugin 255 | metadata.filter = colorMatrix; 256 | 257 | // update crop metadata 258 | item.setMetadata(metadata); 259 | 260 | // call 261 | editor.filepondCallbackBridge.onconfirm( 262 | data, 263 | createItemAPI(item) 264 | ); 265 | 266 | // used in instant edit mode 267 | if (!handleEditorResponse) return; 268 | 269 | editor.onclose = () => { 270 | handleEditorResponse(true); 271 | editor.onclose = null; 272 | }; 273 | }; 274 | 275 | editor.oncancel = () => { 276 | // call 277 | editor.filepondCallbackBridge.oncancel(createItemAPI(item)); 278 | 279 | // used in instant edit mode 280 | if (!handleEditorResponse) return; 281 | editor.onclose = () => { 282 | handleEditorResponse(false); 283 | editor.onclose = null; 284 | }; 285 | }; 286 | 287 | editor.open(file, imageParameters); 288 | }; 289 | 290 | /** 291 | * Image Preview related 292 | */ 293 | 294 | // create the image edit plugin, but only do so if the item is an image 295 | const didLoadItem = ({ root, props }) => { 296 | if (!query('GET_IMAGE_EDIT_ALLOW_EDIT')) return; 297 | 298 | const { id } = props; 299 | 300 | // try to access item 301 | const item = query('GET_ITEM', id); 302 | if (!item) return; 303 | 304 | // get the file object 305 | const file = item.file; 306 | 307 | // exit if this is not an image 308 | if (!isPreviewableImage(file)) return; 309 | 310 | // handle interactions 311 | root.ref.handleEdit = e => { 312 | e.stopPropagation(); 313 | root.dispatch('EDIT_ITEM', { id }); 314 | }; 315 | 316 | if (canShowImagePreview) { 317 | // add edit button to preview 318 | const buttonView = view.createChildView(fileActionButton, { 319 | label: 'edit', 320 | icon: query('GET_IMAGE_EDIT_ICON_EDIT'), 321 | opacity: 0, 322 | }); 323 | 324 | // edit item classname 325 | buttonView.element.classList.add('filepond--action-edit-item'); 326 | buttonView.element.dataset.align = query( 327 | 'GET_STYLE_IMAGE_EDIT_BUTTON_EDIT_ITEM_POSITION' 328 | ); 329 | buttonView.on('click', root.ref.handleEdit); 330 | 331 | root.ref.buttonEditItem = view.appendChildView(buttonView); 332 | } else { 333 | // view is file info 334 | const filenameElement = view.element.querySelector( 335 | '.filepond--file-info-main' 336 | ); 337 | const editButton = document.createElement('button'); 338 | editButton.className = 'filepond--action-edit-item-alt'; 339 | editButton.innerHTML = 340 | query('GET_IMAGE_EDIT_ICON_EDIT') + 'edit'; 341 | editButton.addEventListener('click', root.ref.handleEdit); 342 | filenameElement.appendChild(editButton); 343 | 344 | root.ref.editButton = editButton; 345 | } 346 | }; 347 | 348 | view.registerDestroyer(({ root }) => { 349 | if (root.ref.buttonEditItem) { 350 | root.ref.buttonEditItem.off('click', root.ref.handleEdit); 351 | } 352 | if (root.ref.editButton) { 353 | root.ref.editButton.removeEventListener( 354 | 'click', 355 | root.ref.handleEdit 356 | ); 357 | } 358 | }); 359 | 360 | const routes = { 361 | EDIT_ITEM: openEditor, 362 | DID_LOAD_ITEM: didLoadItem, 363 | }; 364 | 365 | if (canShowImagePreview) { 366 | // view is file 367 | const didPreviewUpdate = ({ root }) => { 368 | if (!root.ref.buttonEditItem) return; 369 | root.ref.buttonEditItem.opacity = 1; 370 | }; 371 | 372 | routes.DID_IMAGE_PREVIEW_SHOW = didPreviewUpdate; 373 | } else { 374 | } 375 | 376 | // start writing 377 | view.registerWriter(createRoute(routes)); 378 | }); 379 | 380 | // Expose plugin options 381 | return { 382 | options: { 383 | // enable or disable image editing 384 | allowImageEdit: [true, Type.BOOLEAN], 385 | 386 | // location of processing button 387 | styleImageEditButtonEditItemPosition: [ 388 | 'bottom center', 389 | Type.STRING, 390 | ], 391 | 392 | // open editor when image is dropped 393 | imageEditInstantEdit: [false, Type.BOOLEAN], 394 | 395 | // allow editing 396 | imageEditAllowEdit: [true, Type.BOOLEAN], 397 | 398 | // the icon to use for the edit button 399 | imageEditIconEdit: [ 400 | '', 401 | Type.STRING, 402 | ], 403 | 404 | // editor object 405 | imageEditEditor: [null, Type.OBJECT], 406 | }, 407 | }; 408 | }; 409 | 410 | // fire pluginloaded event if running in browser, this allows registering the plugin when using async script tags 411 | const isBrowser = 412 | typeof window !== 'undefined' && typeof window.document !== 'undefined'; 413 | if (isBrowser) { 414 | document.dispatchEvent( 415 | new CustomEvent('FilePond:pluginloaded', { detail: plugin }) 416 | ); 417 | } 418 | 419 | export default plugin; 420 | --------------------------------------------------------------------------------