FilePondPluginImageEditor.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /*!
  2. * Pintura Image Editor 8.13.1
  3. * (c) 2018-2021 PQINA Inc. - All Rights Reserved
  4. * License: https://pqina.nl/pintura/license/
  5. */
  6. /* eslint-disable */
  7. var FilePondPluginImageEditor = (function () {
  8. 'use strict';
  9. var isFile = (v) => v instanceof File;
  10. var isImage = (file) => /^image/.test(file.type);
  11. var isString = (v) => typeof v === 'string';
  12. let isSafari = null;
  13. var isSafari$1 = () => {
  14. if (isSafari === null)
  15. isSafari = isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
  16. return isSafari;
  17. };
  18. var getImageElementSize = (imageElement) => new Promise((resolve, reject) => {
  19. let shouldAutoRemove = false;
  20. // test if image is attached to DOM, if not attached, attach so measurement is correct on Safari
  21. if (!imageElement.parentNode && isSafari$1()) {
  22. shouldAutoRemove = true;
  23. // has width 0 and height 0 to prevent rendering very big SVGs (without width and height) that will for one frame overflow the window and show a scrollbar
  24. imageElement.style.cssText = `position:absolute;visibility:hidden;pointer-events:none;left:0;top:0;width:0;height:0;`;
  25. document.body.appendChild(imageElement);
  26. }
  27. // start testing size
  28. const measure = () => {
  29. const width = imageElement.naturalWidth;
  30. const height = imageElement.naturalHeight;
  31. const hasSize = width && height;
  32. if (!hasSize)
  33. return;
  34. // clean up image if was attached for measuring
  35. if (shouldAutoRemove)
  36. imageElement.parentNode.removeChild(imageElement);
  37. clearInterval(intervalId);
  38. resolve({ width, height });
  39. };
  40. imageElement.onerror = (err) => {
  41. clearInterval(intervalId);
  42. reject(err);
  43. };
  44. const intervalId = setInterval(measure, 1);
  45. measure();
  46. });
  47. var getImageSize = async (image) => {
  48. // the image element we'll use to load the image
  49. let imageElement = image;
  50. // if is not an image element, it must be a valid image source
  51. if (!imageElement.src) {
  52. imageElement = new Image();
  53. imageElement.src = isString(image) ? image : URL.createObjectURL(image);
  54. }
  55. let size;
  56. try {
  57. size = await getImageElementSize(imageElement);
  58. }
  59. finally {
  60. isFile(image) && URL.revokeObjectURL(imageElement.src);
  61. }
  62. return size;
  63. };
  64. var isBlob = (v) => v instanceof Blob && !(v instanceof File);
  65. var noop = (...args) => { };
  66. let result$3 = null;
  67. var isBrowser = () => {
  68. if (result$3 === null)
  69. result$3 = typeof window !== 'undefined' && typeof window.document !== 'undefined';
  70. return result$3;
  71. };
  72. var h = (name, attributes, children = []) => {
  73. const el = document.createElement(name);
  74. // @ts-ignore
  75. const descriptors = Object.getOwnPropertyDescriptors(el.__proto__);
  76. for (const key in attributes) {
  77. if (key === 'style') {
  78. el.style.cssText = attributes[key];
  79. }
  80. else if ((descriptors[key] && descriptors[key].set) ||
  81. /textContent|innerHTML/.test(key) ||
  82. typeof attributes[key] === 'function') {
  83. el[key] = attributes[key];
  84. }
  85. else {
  86. el.setAttribute(key, attributes[key]);
  87. }
  88. }
  89. children.forEach((child) => el.appendChild(child));
  90. return el;
  91. };
  92. var releaseCanvas = (canvas) => {
  93. canvas.width = 1;
  94. canvas.height = 1;
  95. const ctx = canvas.getContext('2d');
  96. ctx && ctx.clearRect(0, 0, 1, 1);
  97. };
  98. let result$2 = null;
  99. var supportsWebGL2 = () => {
  100. if (result$2 === null) {
  101. if ('WebGL2RenderingContext' in window) {
  102. let canvas;
  103. try {
  104. canvas = h('canvas');
  105. result$2 = !!canvas.getContext('webgl2');
  106. }
  107. catch (err) {
  108. result$2 = false;
  109. }
  110. canvas && releaseCanvas(canvas);
  111. }
  112. else {
  113. result$2 = false;
  114. }
  115. }
  116. return result$2;
  117. };
  118. var getWebGLContext = (canvas, attrs) => {
  119. if (supportsWebGL2())
  120. return canvas.getContext('webgl2', attrs);
  121. return (canvas.getContext('webgl', attrs) ||
  122. canvas.getContext('experimental-webgl', attrs));
  123. };
  124. let result$1 = null;
  125. var supportsWebGL = () => {
  126. if (result$1 === null) {
  127. const canvas = h('canvas');
  128. result$1 = !!getWebGLContext(canvas);
  129. releaseCanvas(canvas);
  130. }
  131. return result$1;
  132. };
  133. const isOperaMini = () => Object.prototype.toString.call(window['operamini']) === '[object OperaMini]';
  134. const hasPromises = () => 'Promise' in window;
  135. const hasCreateObjectURL = () => 'URL' in window && 'createObjectURL' in window.URL;
  136. const hasVisibility = () => 'visibilityState' in document;
  137. const hasTiming = () => 'performance' in window; // iOS 8.x
  138. const hasFileConstructor = () => 'File' in window; // excludes IE11
  139. let result = null;
  140. var isModernBrowser = () => {
  141. if (result === null)
  142. result =
  143. isBrowser() &&
  144. // Can't run on Opera Mini due to lack of everything
  145. !isOperaMini() &&
  146. // Require these APIs to feature detect a modern browser
  147. hasVisibility() &&
  148. hasPromises() &&
  149. hasFileConstructor() &&
  150. hasCreateObjectURL() &&
  151. hasTiming();
  152. return result;
  153. };
  154. /**
  155. * Image Edit Proxy Plugin
  156. */
  157. const plugin = (_) => {
  158. const { addFilter, utils, views } = _;
  159. const { Type, createRoute } = utils;
  160. const { fileActionButton } = views;
  161. const getEditorSafe = (editor) => (editor === null ? {} : editor);
  162. addFilter('SHOULD_REMOVE_ON_REVERT', (shouldRemove, { item, query }) => new Promise((resolve) => {
  163. const { file } = item;
  164. // if this file is editable it shouldn't be removed immidiately even when instant uploading
  165. const canEdit = query('GET_ALLOW_IMAGE_EDITOR') &&
  166. query('GET_IMAGE_EDITOR_ALLOW_EDIT') &&
  167. query('GET_IMAGE_EDITOR_SUPPORT_EDIT') &&
  168. query('GET_IMAGE_EDITOR_SUPPORT_IMAGE')(file);
  169. // if the file cannot be edited it should be removed on revert
  170. resolve(!canEdit);
  171. }));
  172. // open editor when loading a new item
  173. addFilter('DID_LOAD_ITEM', (item, { query, dispatch }) => new Promise((resolve, reject) => {
  174. // if is temp or local file
  175. if (item.origin > 1) {
  176. resolve(item);
  177. return;
  178. }
  179. // get file reference
  180. const { file } = item;
  181. if (!query('GET_ALLOW_IMAGE_EDITOR') ||
  182. !query('GET_IMAGE_EDITOR_INSTANT_EDIT' ))
  183. return resolve(item);
  184. // exit if this is not an image
  185. if (!query('GET_IMAGE_EDITOR_SUPPORT_IMAGE')(file))
  186. return resolve(item);
  187. // request editing of a file
  188. const requestEdit = () => {
  189. if (!editRequestQueue.length)
  190. return;
  191. const { item, resolve, reject } = editRequestQueue[0];
  192. dispatch('EDIT_ITEM', {
  193. id: item.id,
  194. handleEditorResponse: createEditorResponseHandler(item, resolve, reject),
  195. });
  196. };
  197. // is called when the user confirms editing the file
  198. const createEditorResponseHandler = (item, resolve, reject) => (userDidConfirm) => {
  199. // remove item
  200. editRequestQueue.shift();
  201. // handle item
  202. if (userDidConfirm) {
  203. resolve(item);
  204. }
  205. else {
  206. reject(item);
  207. }
  208. // TODO: Fix, should not be needed to kick the internal loop in case no processes are running
  209. dispatch('KICK');
  210. // handle next item!
  211. requestEdit();
  212. };
  213. queueEditRequest({ item, resolve, reject });
  214. if (editRequestQueue.length === 1) {
  215. requestEdit();
  216. }
  217. }));
  218. // extend item methods
  219. addFilter('DID_CREATE_ITEM', (item, { query, dispatch }) => {
  220. item.extend('edit', () => {
  221. dispatch('EDIT_ITEM', { id: item.id });
  222. });
  223. });
  224. const editRequestQueue = [];
  225. const queueEditRequest = (editRequest) => {
  226. editRequestQueue.push(editRequest);
  227. return editRequest;
  228. };
  229. const couldTransformFile = (query) => {
  230. // no editor defined, then exit
  231. const { imageProcessor, imageReader, imageWriter } = getEditorSafe(query('GET_IMAGE_EDITOR'));
  232. return (query('GET_IMAGE_EDITOR_WRITE_IMAGE') &&
  233. query('GET_IMAGE_EDITOR_SUPPORT_WRITE_IMAGE') &&
  234. imageProcessor &&
  235. imageReader &&
  236. imageWriter);
  237. };
  238. // called for each view that is created right after the 'create' method
  239. addFilter('CREATE_VIEW', (viewAPI) => {
  240. // get reference to created view
  241. const { is, view, query } = viewAPI;
  242. if (!query('GET_ALLOW_IMAGE_EDITOR'))
  243. return;
  244. if (!query('GET_IMAGE_EDITOR_SUPPORT_WRITE_IMAGE'))
  245. return;
  246. const supportsFilePoster = query('GET_ALLOW_FILE_POSTER');
  247. // only run for either the file or the file info panel
  248. const shouldExtendView = (is('file-info') && !supportsFilePoster) || (is('file') && supportsFilePoster);
  249. if (!shouldExtendView)
  250. return;
  251. // no editor defined, then exit
  252. const { createEditor, imageProcessor, imageReader, imageWriter, editorOptions, legacyDataToImageState, imageState: imageBaseState, } = getEditorSafe(query('GET_IMAGE_EDITOR'));
  253. if (!imageReader || !imageWriter || !editorOptions || !editorOptions.locale)
  254. return;
  255. // remove default image reader and writer if set
  256. delete editorOptions.imageReader;
  257. delete editorOptions.imageWriter;
  258. const [createImageReader, imageReaderOptions] = imageReader;
  259. const [createImageWriter = noop, imageWriterOptions] = imageWriter;
  260. // tests if file item has poster
  261. const getItemByProps = (props) => {
  262. const { id } = props;
  263. const item = query('GET_ITEM', id);
  264. return item;
  265. };
  266. const hasPoster = (root, props) => {
  267. if (!query('GET_ALLOW_FILE_POSTER'))
  268. return false;
  269. const item = getItemByProps(props);
  270. if (!item)
  271. return;
  272. // test if is filtered
  273. if (!query('GET_FILE_POSTER_FILTER_ITEM')(item))
  274. return false;
  275. const poster = item.getMetadata('poster');
  276. return !!poster;
  277. };
  278. // generate preview
  279. const getPosterTargetSize = (root, targetSize) => {
  280. const posterHeight = root.query('GET_FILE_POSTER_HEIGHT');
  281. const maxPosterHeight = root.query('GET_FILE_POSTER_MAX_HEIGHT');
  282. if (posterHeight) {
  283. targetSize.width = posterHeight * 2;
  284. targetSize.height = posterHeight * 2;
  285. }
  286. else if (maxPosterHeight) {
  287. targetSize.width = maxPosterHeight * 2;
  288. targetSize.height = maxPosterHeight * 2;
  289. }
  290. return targetSize;
  291. };
  292. const createEditorOptions = (root) => {
  293. const targetSize = getPosterTargetSize(root, {
  294. width: 512,
  295. height: 512,
  296. });
  297. return {
  298. ...editorOptions,
  299. imageReader: createImageReader(imageReaderOptions),
  300. imageWriter: createImageWriter({
  301. // poster size
  302. targetSize,
  303. // can optionally overwrite poster size
  304. ...(imageWriterOptions || {}),
  305. }),
  306. };
  307. };
  308. const createImagePoster = ({ root, props }) => {
  309. // need image processor to create image poster
  310. if (!imageProcessor)
  311. return;
  312. const item = getItemByProps(props);
  313. if (!item)
  314. return;
  315. const file = item.file;
  316. const imageState = item.getMetadata('imageState');
  317. const options = {
  318. ...createEditorOptions(root),
  319. imageState: {
  320. ...imageBaseState,
  321. ...imageState,
  322. },
  323. };
  324. imageProcessor(file, options).then(({ dest }) => {
  325. item.setMetadata('poster', URL.createObjectURL(dest), true);
  326. });
  327. };
  328. // opens the editor, if it does not already exist, it creates the editor
  329. const openImageEditor = ({ root, props, action }) => {
  330. const { handleEditorResponse } = action;
  331. // get item
  332. const item = getItemByProps(props);
  333. // file to open
  334. const file = item.file;
  335. // open the editor (sets editor properties and imageState property)
  336. const editor = createEditor({
  337. ...createEditorOptions(root),
  338. src: file,
  339. });
  340. // when the image has loaded, update the editor
  341. editor.on('load', ({ size }) => {
  342. // get current image edit state
  343. let imageState = item.getMetadata('imageState');
  344. imageState = legacyDataToImageState
  345. ? legacyDataToImageState(editor, size, imageState)
  346. : imageState;
  347. // update editor view based on image edit state
  348. editor.imageState = {
  349. ...imageBaseState,
  350. ...imageState,
  351. };
  352. });
  353. editor.on('process', ({ imageState, dest }) => {
  354. // if already has post URL, try to revoke
  355. const poster = item.getMetadata('poster');
  356. poster && URL.revokeObjectURL(item.getMetadata('poster'));
  357. // store state, two seperate actions because we want to trigger preparefile when setting `imageState`
  358. item.setMetadata('poster', URL.createObjectURL(dest));
  359. item.setMetadata('imageState', imageState);
  360. // used in instant edit mode
  361. if (!handleEditorResponse)
  362. return;
  363. handleEditorResponse(true);
  364. });
  365. editor.on('close', () => {
  366. // used in instant edit mode
  367. if (!handleEditorResponse)
  368. return;
  369. handleEditorResponse(false);
  370. });
  371. };
  372. //#region view
  373. const didLoadItem = ({ root, props }) => {
  374. const { id } = props;
  375. // try to access item
  376. const item = query('GET_ITEM', id);
  377. if (!item)
  378. return;
  379. // get the file object
  380. const file = item.file;
  381. // exit if this is not an image
  382. if (!query('GET_IMAGE_EDITOR_SUPPORT_IMAGE')(file))
  383. return;
  384. if (query('GET_ALLOW_FILE_POSTER') && !item.getMetadata('poster')) {
  385. root.dispatch('REQUEST_CREATE_IMAGE_POSTER', { id });
  386. }
  387. if (!query('GET_IMAGE_EDITOR_ALLOW_EDIT') || !query('GET_IMAGE_EDITOR_SUPPORT_EDIT'))
  388. return;
  389. // handle interactions
  390. root.ref.handleEdit = (e) => {
  391. e.stopPropagation();
  392. root.dispatch('EDIT_ITEM', { id });
  393. };
  394. updateEditButton(root, props);
  395. };
  396. const updateEditButton = (root, props) => {
  397. root.ref.buttonEditItem && root.removeChildView(root.ref.buttonEditItem);
  398. if (root.ref.editButton && root.ref.editButton.parentNode) {
  399. root.ref.editButton.parentNode.removeChild(root.ref.editButton);
  400. }
  401. if (hasPoster(root, props)) {
  402. // add edit button to preview
  403. const buttonView = view.createChildView(fileActionButton, {
  404. label: 'edit',
  405. icon: query('GET_IMAGE_EDITOR_ICON_EDIT'),
  406. opacity: 0,
  407. });
  408. // edit item classname
  409. buttonView.element.classList.add('filepond--action-edit-item');
  410. buttonView.element.dataset.align = query('GET_STYLE_IMAGE_EDITOR_BUTTON_EDIT_ITEM_POSITION');
  411. buttonView.on('click', root.ref.handleEdit);
  412. root.ref.buttonEditItem = view.appendChildView(buttonView);
  413. }
  414. else {
  415. // view is file info
  416. const filenameElement = view.element.querySelector('.filepond--file-info-main');
  417. const editButton = document.createElement('button');
  418. editButton.className = 'filepond--action-edit-item-alt';
  419. editButton.innerHTML = query('GET_IMAGE_EDITOR_ICON_EDIT') + '<span>edit</span>';
  420. editButton.addEventListener('click', root.ref.handleEdit);
  421. filenameElement.appendChild(editButton);
  422. root.ref.editButton = editButton;
  423. }
  424. };
  425. const didUpdateItemMetadata = ({ root, props, action }) => {
  426. if (!/poster/.test(action.change.key))
  427. return;
  428. if (!query('GET_IMAGE_EDITOR_ALLOW_EDIT') || !query('GET_IMAGE_EDITOR_SUPPORT_EDIT'))
  429. return;
  430. updateEditButton(root, props);
  431. };
  432. view.registerDestroyer(({ root }) => {
  433. if (root.ref.buttonEditItem) {
  434. root.ref.buttonEditItem.off('click', root.ref.handleEdit);
  435. }
  436. if (root.ref.editButton) {
  437. root.ref.editButton.removeEventListener('click', root.ref.handleEdit);
  438. }
  439. });
  440. const routes = {
  441. EDIT_ITEM: openImageEditor,
  442. DID_LOAD_ITEM: didLoadItem,
  443. DID_UPDATE_ITEM_METADATA: didUpdateItemMetadata,
  444. DID_REMOVE_ITEM: ({ props }) => {
  445. const { id } = props;
  446. const item = query('GET_ITEM', id);
  447. if (!item)
  448. return;
  449. const poster = item.getMetadata('poster');
  450. poster && URL.revokeObjectURL(poster);
  451. },
  452. REQUEST_CREATE_IMAGE_POSTER: createImagePoster,
  453. DID_FILE_POSTER_LOAD: undefined,
  454. };
  455. if (supportsFilePoster) {
  456. // view is file
  457. const didPosterUpdate = ({ root }) => {
  458. if (!root.ref.buttonEditItem)
  459. return;
  460. root.ref.buttonEditItem.opacity = 1;
  461. };
  462. routes.DID_FILE_POSTER_LOAD = didPosterUpdate;
  463. }
  464. //#endregion
  465. // start writing
  466. view.registerWriter(createRoute(routes));
  467. });
  468. //#region write image
  469. addFilter('SHOULD_PREPARE_OUTPUT', (shouldPrepareOutput, { query, change, item }) => new Promise((resolve) => {
  470. if (!query('GET_IMAGE_EDITOR_SUPPORT_IMAGE')(item.file))
  471. return resolve(false);
  472. if (change && !/imageState/.test(change.key))
  473. return resolve(false);
  474. resolve(!query('IS_ASYNC'));
  475. }));
  476. const shouldTransformFile = (query, file, item) => new Promise((resolve) => {
  477. // invalid item
  478. if (!couldTransformFile(query) ||
  479. item.archived ||
  480. (!isFile(file) && !isBlob(file)) ||
  481. !query('GET_IMAGE_EDITOR_SUPPORT_IMAGE')(file)) {
  482. return resolve(false);
  483. }
  484. // if size can't be read this browser doesn't support image
  485. getImageSize(file)
  486. .then(() => {
  487. const fn = query('GET_IMAGE_TRANSFORM_IMAGE_FILTER');
  488. if (fn) {
  489. const filterResult = fn(file);
  490. if (typeof filterResult === 'boolean') {
  491. return resolve(filterResult);
  492. }
  493. if (typeof filterResult.then === 'function') {
  494. return filterResult.then(resolve);
  495. }
  496. }
  497. resolve(true);
  498. })
  499. .catch(() => {
  500. resolve(false);
  501. });
  502. });
  503. // subscribe to file transformations
  504. addFilter('PREPARE_OUTPUT', (file, { query, item }) => {
  505. const writeOutputImage = (file) => new Promise((resolve, reject) => {
  506. const imageState = item.getMetadata('imageState');
  507. // no editor defined, then exit
  508. const { imageProcessor, imageReader, imageWriter, editorOptions, imageState: imageBaseState, } = getEditorSafe(query('GET_IMAGE_EDITOR'));
  509. if (!imageProcessor || !imageReader || !imageWriter || !editorOptions)
  510. return;
  511. const [createImageReader, imageReaderOptions] = imageReader;
  512. const [createImageWriter = noop, imageWriterOptions] = imageWriter;
  513. imageProcessor(file, {
  514. ...editorOptions,
  515. imageReader: createImageReader(imageReaderOptions),
  516. imageWriter: createImageWriter(imageWriterOptions),
  517. imageState: {
  518. ...imageBaseState,
  519. ...imageState,
  520. },
  521. })
  522. .then(resolve)
  523. .catch(reject);
  524. });
  525. return new Promise((resolve) => {
  526. shouldTransformFile(query, file, item).then((shouldWrite) => {
  527. if (!shouldWrite)
  528. return resolve(file);
  529. writeOutputImage(file).then((res) => {
  530. const afterFn = query('GET_IMAGE_EDITOR_AFTER_WRITE_IMAGE');
  531. if (afterFn)
  532. return afterFn(res).then(resolve);
  533. // @ts-ignore
  534. resolve(res.dest);
  535. });
  536. });
  537. });
  538. });
  539. //#endregion
  540. // Expose plugin options
  541. return {
  542. options: {
  543. // enable or disable image editing
  544. allowImageEditor: [true, Type.BOOLEAN],
  545. // open editor when image is dropped
  546. imageEditorInstantEdit: [false, Type.BOOLEAN],
  547. // allow editing
  548. imageEditorAllowEdit: [true, Type.BOOLEAN],
  549. // cannot edit if no WebGL or is <=IE11
  550. imageEditorSupportEdit: [
  551. isBrowser() && isModernBrowser() && supportsWebGL(),
  552. Type.BOOLEAN,
  553. ],
  554. // receives file and should return true if can edit
  555. imageEditorSupportImage: [isImage, Type.FUNCTION],
  556. // cannot write if is <= IE11
  557. imageEditorSupportWriteImage: [isModernBrowser(), Type.BOOLEAN],
  558. // should output image
  559. imageEditorWriteImage: [true, Type.BOOLEAN],
  560. // receives written image and can return single or more images
  561. imageEditorAfterWriteImage: [undefined, Type.FUNCTION],
  562. // editor object
  563. imageEditor: [null, Type.OBJECT],
  564. // the icon to use for the edit button
  565. imageEditorIconEdit: [
  566. '<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>',
  567. Type.STRING,
  568. ],
  569. // location of processing button
  570. styleImageEditorButtonEditItemPosition: ['bottom center', Type.STRING],
  571. },
  572. };
  573. };
  574. // fire pluginloaded event if running in browser, this allows registering the plugin when using async script tags
  575. if (isBrowser())
  576. document.dispatchEvent(new CustomEvent('FilePond:pluginloaded', { detail: plugin }));
  577. return plugin;
  578. }());
  579. export { FilePondPluginImageEditor };