Showing preview only (1,369K chars total). Download the full file or copy to clipboard to get everything.
Repository: pqina/filepond
Branch: master
Commit: 990dca310c45
Files: 298
Total size: 1.3 MB
Directory structure:
gitextract_hbxyat0y/
├── .babelrc
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── banner-cli.js
├── banner.js
├── dist/
│ ├── filepond.css
│ ├── filepond.esm.js
│ └── filepond.js
├── index.html
├── jest.config.js
├── jest.stubs.js
├── locale/
│ ├── am-et.js
│ ├── ar-ar.js
│ ├── az-az.js
│ ├── ca-ca.js
│ ├── cs-cz.js
│ ├── cy-cy.js
│ ├── da-dk.js
│ ├── de-de.js
│ ├── el-el.js
│ ├── en-en.js
│ ├── es-es.js
│ ├── et-ee.js
│ ├── fa_ir.js
│ ├── fi-fi.js
│ ├── fr-fr.js
│ ├── he-he.js
│ ├── hr-hr.js
│ ├── hu-hu.js
│ ├── id-id.js
│ ├── it-it.js
│ ├── ja-ja.js
│ ├── km-km.js
│ ├── ko-kr.js
│ ├── ku-ckb.js
│ ├── kur-ckb.js
│ ├── lt-lt.js
│ ├── lus-lus.js
│ ├── lv-lv.js
│ ├── nl-nl.js
│ ├── no_nb.js
│ ├── pl-pl.js
│ ├── pt-br.js
│ ├── pt-pt.js
│ ├── ro-ro.js
│ ├── ru-ru.js
│ ├── sk-sk.js
│ ├── sl-si.js
│ ├── sr-rs.js
│ ├── sv_se.js
│ ├── tr-tr.js
│ ├── uk-ua.js
│ ├── ur-ur.js
│ ├── vi-vi.js
│ ├── zh-cn.js
│ ├── zh-hk.js
│ └── zh-tw.js
├── package.json
├── rollup.config.js
├── rollup.scripts.js
├── src/
│ ├── css/
│ │ ├── assistant.css
│ │ ├── browser.css
│ │ ├── data.css
│ │ ├── drip.css
│ │ ├── drop-label.css
│ │ ├── file-action-button.css
│ │ ├── file-info.css
│ │ ├── file-status.css
│ │ ├── file-wrapper.css
│ │ ├── file.css
│ │ ├── hopper.css
│ │ ├── item-order.css
│ │ ├── item.css
│ │ ├── list-scroller.css
│ │ ├── list.css
│ │ ├── modifiers.css
│ │ ├── panel-root.css
│ │ ├── panel.css
│ │ ├── progress-indicator.css
│ │ ├── root-order.css
│ │ ├── root.css
│ │ └── styles.css
│ └── js/
│ ├── __tests__/
│ │ ├── addFile.test.js
│ │ ├── callbacks.test.js
│ │ ├── contentDisposition.test.js
│ │ ├── createInstance.test.js
│ │ ├── removeFile.test.js
│ │ ├── revertUploadOnRemove.test.js
│ │ ├── server.test.js
│ │ ├── setFiles.test.js
│ │ └── windowMatchMedia.mock
│ ├── app/
│ │ ├── actions.js
│ │ ├── enum/
│ │ │ ├── ChunkStatus.js
│ │ │ ├── FileOrigin.js
│ │ │ ├── InteractionMethod.js
│ │ │ ├── ItemStatus.js
│ │ │ ├── Key.js
│ │ │ ├── Status.js
│ │ │ └── Type.js
│ │ ├── frame/
│ │ │ ├── createPainter.js
│ │ │ ├── createRoute.js
│ │ │ ├── createStore.js
│ │ │ ├── createView.js
│ │ │ ├── index.js
│ │ │ ├── mixins/
│ │ │ │ ├── animations.js
│ │ │ │ ├── apis.js
│ │ │ │ ├── index.js
│ │ │ │ ├── listeners.js
│ │ │ │ ├── styles.js
│ │ │ │ └── utils/
│ │ │ │ └── addGetSet.js
│ │ │ └── utils/
│ │ │ ├── AxisEnum.js
│ │ │ ├── addEvent.js
│ │ │ ├── animators/
│ │ │ │ ├── easing.js
│ │ │ │ ├── spring.js
│ │ │ │ └── tween.js
│ │ │ ├── appendChild.js
│ │ │ ├── appendChildView.js
│ │ │ ├── createAnimator.js
│ │ │ ├── createElement.js
│ │ │ ├── getChildCount.js
│ │ │ ├── getViewRect.js
│ │ │ ├── removeChildView.js
│ │ │ ├── removeEvent.js
│ │ │ └── updateRect.js
│ │ ├── index.js
│ │ ├── options.js
│ │ ├── queries.js
│ │ ├── utils/
│ │ │ ├── buildURL.js
│ │ │ ├── convertTo.js
│ │ │ ├── createDragHelper.js
│ │ │ ├── createFetchFunction.js
│ │ │ ├── createFileLoader.js
│ │ │ ├── createFileProcessor.js
│ │ │ ├── createFileProcessorFunction.js
│ │ │ ├── createFileStub.js
│ │ │ ├── createHopper.js
│ │ │ ├── createInitialState.js
│ │ │ ├── createItem.js
│ │ │ ├── createItemAPI.js
│ │ │ ├── createOption.js
│ │ │ ├── createOptionAPI.js
│ │ │ ├── createOptionActions.js
│ │ │ ├── createOptionQueries.js
│ │ │ ├── createOptions.js
│ │ │ ├── createPaster.js
│ │ │ ├── createPerceivedPerformanceUpdater.js
│ │ │ ├── createProcessorFunction.js
│ │ │ ├── createRevertFunction.js
│ │ │ ├── createServerAPI.js
│ │ │ ├── dnd.js
│ │ │ ├── dropAreaDimensions.js
│ │ │ ├── dynamicLabel.js
│ │ │ ├── fetchBlob.js
│ │ │ ├── getActiveItems.js
│ │ │ ├── getItemById.js
│ │ │ ├── getItemByQuery.js
│ │ │ ├── getItemIndexByPosition.js
│ │ │ ├── getItemIndexByQuery.js
│ │ │ ├── getItemsPerRow.js
│ │ │ ├── getType.js
│ │ │ ├── getValueByType.js
│ │ │ ├── hasRoomForItem.js
│ │ │ ├── insertItem.js
│ │ │ ├── isAPI.js
│ │ │ ├── mergeOptionObject.js
│ │ │ ├── on.js
│ │ │ ├── processFileChunked.js
│ │ │ ├── removeReleasedItems.js
│ │ │ ├── requestDataTransferItems.js
│ │ │ └── toServerAPI.js
│ │ └── view/
│ │ ├── assistant.js
│ │ ├── blob.js
│ │ ├── browser.js
│ │ ├── data.js
│ │ ├── drip.js
│ │ ├── dropLabel.js
│ │ ├── file.js
│ │ ├── fileActionButton.js
│ │ ├── fileInfo.js
│ │ ├── fileStatus.js
│ │ ├── fileWrapper.js
│ │ ├── item.js
│ │ ├── list.js
│ │ ├── listScroller.js
│ │ ├── panel.js
│ │ ├── progressIndicator.js
│ │ └── root.js
│ ├── createApp.js
│ ├── createAppAPI.js
│ ├── createAppAtElement.js
│ ├── createAppObject.js
│ ├── createAppPlugin.js
│ ├── filter.js
│ ├── index.js
│ └── utils/
│ ├── arrayInsert.js
│ ├── arrayRemove.js
│ ├── arrayReverse.js
│ ├── attr.js
│ ├── attrToggle.js
│ ├── canUpdateFileInput.js
│ ├── capitalizeFirstLetter.js
│ ├── composeObject.js
│ ├── copyFile.js
│ ├── copyObjectPropertiesToObject.js
│ ├── createBlob.js
│ ├── createDefaultResponse.js
│ ├── createElement.js
│ ├── createObject.js
│ ├── createResponse.js
│ ├── createWorker.js
│ ├── debounce.js
│ ├── deepCloneObject.js
│ ├── defineProperty.js
│ ├── describeArc.js
│ ├── forEachDelayed.js
│ ├── forin.js
│ ├── formatFilename.js
│ ├── fromCamels.js
│ ├── getAttributesAsObject.js
│ ├── getBase64DataFromBase64DataURI.js
│ ├── getBlobBuilder.js
│ ├── getBlobFromBase64DataURI.js
│ ├── getBlobFromByteStringWithMimeType.js
│ ├── getByteStringFromBase64DataURI.js
│ ├── getDateString.js
│ ├── getDecimalSeparator.js
│ ├── getDomainFromURL.js
│ ├── getExtensionFromFilename.js
│ ├── getFileFromBase64DataURI.js
│ ├── getFileFromBlob.js
│ ├── getFileInfoFromHeaders.js
│ ├── getFilenameFromURL.js
│ ├── getFilenameWithoutExtension.js
│ ├── getMimeTypeFromBase64DataURI.js
│ ├── getNonNumeric.js
│ ├── getNumericAspectRatioFromString.js
│ ├── getParameters.js
│ ├── getRandomNumber.js
│ ├── getRootNode.js
│ ├── getThousandsSeparator.js
│ ├── getUniqueId.js
│ ├── guesstimateExtension.js
│ ├── guesstimateMimeType.js
│ ├── hasQueryString.js
│ ├── insertAfter.js
│ ├── insertBefore.js
│ ├── isArray.js
│ ├── isBase64DataURI.js
│ ├── isBoolean.js
│ ├── isBrowser.js
│ ├── isDefined.js
│ ├── isEmpty.js
│ ├── isExternalURL.js
│ ├── isFile.js
│ ├── isFunction.js
│ ├── isIOS.js
│ ├── isInt.js
│ ├── isNode.js
│ ├── isNull.js
│ ├── isNumber.js
│ ├── isObject.js
│ ├── isString.js
│ ├── leftPad.js
│ ├── limit.js
│ ├── loadImage.js
│ ├── lowerCaseFirstLetter.js
│ ├── percentageArc.js
│ ├── polarToCartesian.js
│ ├── renameFile.js
│ ├── replaceInString.js
│ ├── resetFileInput.js
│ ├── sendRequest.js
│ ├── setInputFiles.js
│ ├── text.js
│ ├── toArray.js
│ ├── toBoolean.js
│ ├── toBytes.js
│ ├── toCamels.js
│ ├── toFloat.js
│ ├── toFunctionReference.js
│ ├── toInt.js
│ ├── toNaturalFileSize.js
│ ├── toNumber.js
│ ├── toPercentage.js
│ ├── toString.js
│ └── trim.js
└── types/
├── index.d.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"retainLines": true,
"presets": [
["@babel/preset-env", {
"exclude": ["transform-typeof-symbol"],
"modules": false
}]
],
"plugins": [
["@babel/plugin-proposal-object-rest-spread", {
"loose": true,
"useBuiltIns": true
}],
["@babel/plugin-transform-template-literals", {
"loose": true
}]
],
"env": {
"test": {
"presets": [
["@babel/preset-env", {
"targets": {
"node": "current"
}
}]
]
}
}
}
================================================
FILE: .gitattributes
================================================
dist/* linguist-vendored=false
================================================
FILE: .github/FUNDING.yml
================================================
custom: ['https://www.buymeacoffee.com/rikschennink']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
title: "[Bug] "
description: Report an issue with FilePond
labels: bug
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: checkboxes
attributes:
label: Have you updated FilePond and its plugins?
description: Please update both FilePond and its plugins to the most recent version.
options:
- label: I have updated FilePond and its plugins
required: true
- type: textarea
attributes:
label: Describe the bug
description: A concise description of what the bug is.
placeholder: Bug description
validations:
required: true
- type: textarea
attributes:
label: Reproduction
description: Provide clear steps to reproduce the bug. Please take the time to create a reproduction of the bug by [forking this project on codesandbox.io](https://codesandbox.io/s/filepond-plain-javascript-24i1m)
placeholder: Reproduction
validations:
required: true
- type: textarea
attributes:
label: Environment
description: |
examples:
- **Device**: Samsung Galaxy s20, iPhone 12 Mini, Macbook Pro (2020), etc.
- **OS**: Android 8, iOS 12, Windows 10, etc.
- **Browser**: Firefox 93, Chrome 94, etc.
value: |
- Device:
- OS:
- Browser:
render: markdown
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Stack Overflow
url: https://stackoverflow.com/questions/tagged/filepond
about: Ask questions on Stack Overflow and tag with "filepond"
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
title: "[Feature] "
description: Suggest an idea for this project
labels: new feature
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature request you want to submit.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe.
description: A clear and concise description of what the problem is you want to solve.
placeholder: I'm always frustrated when...
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
================================================
FILE: .gitignore
================================================
npm-debug.log
node_modules/
coverage/
.idea
.vscode/
upload/
test*.html
_TODO.md
_RELEASE.md
================================================
FILE: .nvmrc
================================================
v10.15.3
================================================
FILE: .prettierignore
================================================
dist/*
================================================
FILE: .prettierrc
================================================
{
"svelteSortOrder": "scripts-markup-styles",
"trailingComma": "es5",
"tabWidth": 4,
"printWidth": 100,
"singleQuote": true,
"semi": true
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## 4.32.12
- Link attribution banner to [filepond.com](https://filepond.com)
## 4.32.11
- Merge fix file processing aborted and moving to next file. #1065
## 4.32.10
- Added various new locales.
- Merge fix for drop offset mismatch when scrolling.
## 4.32.9
- Added various new locales.
## 4.32.8
- Merged multiple PRs with various fixes.
## 4.32.7
- Fix issue where `checkValidity` would not reset field validity status after removing an invalid file.
## 4.32.6
- Fix `processfiles` event missing from types.`
## 4.32.5
- Attempt to fix item dragging issue on Android.
## 4.32.4
- Make credits link more visible to prevent authors from leaving it in place accidentally.
## 4.32.3
- Add nofollow to credits link.
## 4.32.2
- Fix accessibility issue with data fieldset missing a legend.
## 4.32.1
- Fix types.
## 4.32.0
- Add `onload` property to chunk patch request, receives `xhr` object, chunk index, and total chunks. #649
- Fix role attribute in voice over assistant to improve WCAG compatibility #988
## 4.31.4
- Fix issue where images pasted in contenteditable elements would be handled by FilePond.
## 4.31.3
- Fix issue where hidden input fields in `filepond--data` fieldset would stay disabled after enabling FilePond. #1001
## 4.31.2
- Remove `aria-hidden` from drop label as suggested by Lighthouse.
- Fix `tabIndex` attribute on credits link not set correctly, now no longer included in tab flow.
## 4.31.1
- Fix issue with dragging file items sometimes not working.
## 4.31.0
- Add support for editing mock files if `item.source` is url.
## 4.30.6
- Fix issue where using a number as source for a local file would throw a `url.split` error.
## 4.30.5
- Fix file field value assignment #905
## 4.30.4
- Fix a bug where the selected file is not replaced #841
- Fix a bug where the onwarning event is not triggered #839
- Add more translations
## 4.30.3
- Fix accidental push.
## 4.30.2
- Remove accidental log statement.
## 4.30.1
- Prioritize server prop before other props when passed to setOptions #606
## 4.30.0
- Add `labelFileSizeBytes`, `labelFileSizeKilobytes`, `labelFileSizeMegabytes`, `labelFileSizeGigabytes` #763
## 4.29.1
- Revert chunked uploads #757
## 4.29.0
- Enable rejecting images with an error message using the internal `DID_LOAD_ITEM` hook.
## 4.28.2
- Fix issue where local server files could not be re-uploaded after editing and did not trigger remove of source file.
## 4.28.1
- Fix CSS error.
## 4.28.0
- Add `storeAsFile` property, if set to `true` FilePond will attempt to store the file objects in file input elements allowing file submit along with parent form (no need for `server` property). **This only works if the browser [supports the DataTransfer constructor](https://caniuse.com/mdn-api_datatransfer_datatransfer), this is the case on Firefox, Chrome, Chromium powered browsers and Safari version 14.1 and higher.**
- Switch to PostCSS for style output.
## 4.27.3
- Fix issue with file.js component leaking state causing `allowRemove` to impact other instances of FilePond. #713
## 4.27.2
- Fix issue with fetch and `HEAD` no setting server id to hidden input element.
## 4.27.1
- Fix issue with `allowMinimumUploadDuration` set to `false` throwing error.
## 4.27.0
- Add `allowMinimumUploadDuration` set to `false` to prevent a minimum upload time of 750ms.
## 4.26.2
- `setMetadata` internal `silent` bool now does fire internal `DID_UPDATE_ITEM_METADATA` but doesn't trigger upload or file prepare logic. This fixes an issue with the new image editor and file poster plugins.
## 4.26.1
- Add metdata change info to internal `SHOULD_PREPARE_OUTPUT` call
## 4.26.0
- Fix problem with rendering 0 items per row. #676
- The `headers` property of the `server.process` end point can now be a function.
## 4.25.3
- Fix issue with `chunkRetryDelays`. #671
## 4.25.2
- Fix issue with fixed with file items not row wrapping correctly. #653
- Fix file info label when remove button positioned to the right. #620
## 4.25.1
- Renamed `beforePrepareOutput` hook to `beforePrepareFile`.
## 4.25.0
- Add `beforePrepareOutput` hook to intercept and prevent preparing a new output file.
## 4.24.0
- Add action info to internal `SHOULD_PREPARE_OUTPUT` call
- Moved edit button CSS to FilePond
## 4.23.1
- When files are dropped in a folder the file type cannot always be determined, this fix prevents FilePond from creating a Blob if it can't guesstimate the file type.
## 4.23.0
- Add "powered by" footer and [credits prop](https://pqina.nl/filepond/docs/patterns/api/filepond-instance/#disabling-credits).
## 4.22.1
- Fix problem with locale files.
## 4.22.0
- Add internal filter for plugins to manipulate dropped files before adding them to the files list.
## 4.21.1
- Fix calculation of file size when `fileSizeBase` is set to 1024.
## 4.21.0
- Add `fileSizeBase` use to adjust the way files sizes are displayed. Default is `1000`.
## 4.20.1
- Add `allowRemove` option so it actually works.
## 4.20.0
- Add `allowRemove`, set to `false` to disable remove button.
- Improve TypeScript definitions.
- Fix issue where `removeFiles` would not remove all files.
## 4.19.2
- Fix problem with locale files not ending up on npm.
## 4.19.1
- Fix issue where removal of a server file was requested when setting new files to the `files` property.
## 4.19.0
- Add locale folder, can now import different locales.
- Improve `supports` method, now correctly detects MacOS Safari 8.
- Improve type definitions file.
- Fix issue with `removeFiles({ revert: false })` not working.
- Fix issue where content pasted in a textarea would be interpreted as a file.
## 4.18.0
- Add fallback for `fetch` when loading remote URLs, if no custom fetch supplied, will use default request.
- Add TypeScript dynamic label types.
- Fix issue where order of files wasn't correct when setting initial files.
## 4.17.1
- Fix issue where reorder event was fired on each drag interaction, now only fires when order changes.
## 4.17.0
- Add `allowProcess`, set to `false` to remove processing button and related abort / retry processing controls.
- Fix issue where hidden inputs didn't reflect visual order of files in list.
## 4.16.0
- Add `allowSyncAcceptAttribute`, set to `false` to prevent FilePond from setting the file input field `accept` attribute to the value of the `acceptedFileTypes`.
## 4.15.1
- Fix issue with abort being called even when not supplied.
## 4.15.0
- Add support for reording items in grid layout. Thanks @jwsinner ❤︎
## 4.14.0
- Add `oninitfile`, called when file is first initialised, can be used to immediately set metadata.
## 4.13.7
- Fix backwards compatibility problem with `4.13.5` and `4.13.6` where removeFile would revert upload.
- Add `{ revert: true }` as parameter to `removeFile` and `removeFiles` methods. Where in the previous two fix versions reverting was added to be done automatically this new parameter now needs be set to revert the upload.
## 4.13.6
- Fix problem where revert wasn't called for user added files.
## 4.13.5
- Fix trigger of revert handler to `removeFile` API.
- Fix problem where circular layout wouldn't work on latest Safari.
## 4.13.4
- Fix issue where FilePond internal event mechanism would be in slowmotion mode when running in an inactive tab because of `setTimeout` use.
## 4.13.3
- Fix issue where FilePond would excessivly pause in between processing files while running in an inactive tab.
## 4.13.2
- Fix issue where FilePond running in an inactive tab would be very slow to pick up new files.
## 4.13.1
- Fix issue where HEAD fetch request would try turn response into zero byte file.
## 4.13.0
- Fix issue where hidden file fields were not in the correct order when files were sorted either automatically or manually.
- Clean up accidental log statement left in 4.12.2 release.
## 4.12.2
- Fix issue with re-enabling FilePond field from disabled state not applying the appropriate fields to the browse input.
## 4.12.1
- Fix issue where browse button wasn't clickable when `styleLayoutMode` was set to `compact`.
## 4.12.0
- Add `styleButtonRemoveItemAlign` to align remove button to the left side of the file item.
- Fix issue where list of files could not be scrolled when FilePond was disabled.
## 4.11.0
- Add `relativePath` property to file item.
- Add `onreorderfiles` callback.
- Fix issue where unkown type was `"null"` instead of an empty string.
- Fix issue where `onactivatefile` was fired on drag end.
## 4.10.0
- Copy webkitDirectory property to file object.
## 4.9.5
- Fix issue with error format in TypeScript types.
## 4.9.4
- Fix problem with API querystring containing multiple questionmarks.
## 4.9.3
- Fix problem where ending the class attribute on a space would throw an error.
## 4.9.2
- Add `grab` cursor to items so there's and indicator that items are grabbable.
## 4.9.1
- Fix issue where Chrome on Android would launch pull-to-refresh when trying to drag a file item.
## 4.9.0
- Add drag to reorder file items, enable by setting `allowReorder` to `true`.
- Only works in single column mode (for now).
- It also works when the list of files is showing a scrollbar, but dragging + scrolling isn't working correctly at the moment.
- Limited to [browsers supporting Pointer events](https://caniuse.com/#feat=pointer).
- Add `moveFile(query, index)` method. Use to move a file to a different index in the file items array.
## 4.8.2
- Fix problem with 4.8.1 fix not working with SSR.
## 4.8.1
- Fix IE issue where adding markup would not work.
## 4.8.0
- Add `prepareFile` and `prepareFiles` methods to the FilePond instance, use to request output files of the current items in the files list.
## 4.7.4
- UTF-8 encode request headers to prevent issues with weird characters.
## 4.7.3
- Switch from `setAttribute` to `cssText` for layout changes resulting in better performance and CSP compatibility. [#400](https://github.com/pqina/filepond/pull/400)
## 4.7.2
- Fix issue where iOS 10 would throw an error when calling `delete` on a dataset property
- Fix issue with `onwarning` being called on incorrect element
## 4.7.1
- Fix problem where directories with over 100 files weren't read correctly.
## 4.7.0
- Add support for [chunked uploads](https://pqina.nl/filepond/docs/patterns/api/server/#process-chunks). Thanks to Ryan Olson (@ams-ryanolson) Arctic Media for donating the funds to build this.
## 4.6.1
- Add missing Blob type to TypeScript server config.
## 4.6.0
- Add TypeScript declarations.
## 4.5.2
- If `Blob` has `name` attribute use name attribute instead of URL for file name.
## 4.5.1
- Fix issue where drag-drop from Firefox download list would not add file to drop area
## 4.5.0
- Add option to set server end point headers on a generic level so they're applied to all end points using `server.headers`.
## 4.4.13
- Fix problem with CSS overriding image preview markup text size.
## 4.4.12
- Fix memory leak.
## 4.4.11
- Fix problem with abort statement in file loader logic.
## 4.4.10
- Fix issue where Promise returned by `addFile` would not be rejected if file failed to load.
## 4.4.9
- Fix security issues with dependencies.
## 4.4.8
- Fix issue where multiple calls to `setMetadata` would result in multiple successive calls to prepare file.
- Fix issue where drop area aspect ratio would not update correctly on resize.
## 4.4.7
- Fix issue where pasting a file would throw an error.
- Fix issue where ignored files would be counted as files when dropping a folder.
## 4.4.6
- Fix issue where `processFiles` would re-process `local` server images.
## 4.4.5
- Fix issue where FilePond event loop would freeze when tab was inactive.
## 4.4.4
- Fix issue where FilePond would not render when hidden, resulting in missing input elements and events not firing.
## 4.4.3
- Fix issue where processing the queue didn't work correctly when files were removed while being in the queue.
## 4.4.2
- Fix issue where UTF-8 encoded filename was not parsed correctly.
## 4.4.1
- Fix issue where `Content-Disposition` header filename was not parsed correctly.
## 4.4.0
- Fix issue where `addFile` did not respect `itemInsertLocation` setting.
- Add the `beforeDropFile` hook which can be used to validate a dropped item before it's added, make sure `dropValidation` is set to `true` as well.
## 4.3.9
- Fix problem where enabling FilePond after being `disabled` would not allow browsing for files.
## 4.3.8
- Improve accessibility of buttons by moving label from `title` to inner hidden `<span>`.
## 4.3.7
- Attempt #2 at fixing the issue of release `4.3.6`.
## 4.3.6
- Fix problem where the `abortAll` call triggered when destroying FilePond would inadvertently trigger the `server.remove` end point for each local file.
## 4.3.5
- Fix issue where changing the `stylePanelAspectRatio` would not update the container size.
## 4.3.4
- Add source code.
- Add build scripts.
- Fix `onremovefile` callback not receiving an error object similar to `onaddfile`.
## 4.3.3
- Fix issue where aborting a file load while the file was being prepared (for instance, encoded) did not work.
## 4.3.2
- Fix issue where 0 byte files would not upload to the server.
## 4.3.1
- Add `status` property to the FilePond instance, use this property to determine the current FilePond status (`EMPTY`, `IDLE`, `ERROR`, `BUSY`, or `READY`).
## 4.3.0
- Fix problem where `addFiles` would not correctly map passed options to files.
- Fix problem where upload error would prevent processing of other files.
- Fix problem where the field would not "exist" if it had no value. Now if FilePond is empty the internal file input element is given the name attribute, this is removed when a file is added (as the name is then present on the file's hidden input element).
- Add `onprocessfiles` which is called when all files have been processed.
- Add `onactivatefile` which is called when a user clicks or taps on a file item.
## 4.2.0
- Add `disabled` property, can be set as an attribute on the file input or as a property in the FilePond options object.
- Add catching clicks on the entire pond label element to make it easier to click the label.
## 4.1.4
- Only hide preview images when resizing the window horizontally, fixes problem with resize events on iOS.
## 4.1.3
- Improve the way that FilePond resumes drawing when a tab retains focus.
## 4.1.2
- Fix problem where `onaddfile` callback parameters were reversed when file validation plugins prevented file load
## 4.1.1
- Fix problem where error shake animation would mess up preview image.
## 4.1.0
- Add `itemInsertLocationFreedom` property, set to `false` to stop user from picking the location in the file list where the file is added.
## 4.0.2
- Fix problem with undefine `ItemStatus` object in `processFiles` method
## 4.0.1
- Fix problem where window resize handler was removed incorrectly resulting in an error.
## 4.0.0
Multiple improvements, small fixes and new features. As updating will result in animation speed changes, changes to the way files are added to the files list, and will require an update of the image preview plugin, the version has been bumped to 4.0.0
- Add grid layout feature. Assign a fixed width to a filepond item and FilePond will render the items in rows.
The code below will render a list view on small viewports, a 50/50 grid on medium viewports, and a 33/33/33 grid on wide viewports. The `.5em` in each calc statement is equivalent to the combined left and right margin of each filepond item.
```css
@media (min-width: 30em) {
.filepond--item {
width: calc(50% - 0.5em);
}
}
@media (min-width: 50em) {
.filepond--item {
width: calc(33.33% - 0.5em);
}
}
```
- Add `styleItemPanelAspectRatio` to control the item panel aspect ratio and render item panels in a fixed size.
- Add `sort` method on FilePond instance for sorting FilePond files.
- Add `itemInsertLocation` property to set default insert location of files or sort method.
- Add `itemInsertInterval` to control the small delay between adding items to the files list.
- Improve drag and drop performance.
- Improve file insert logic and performance.
- Improve rendering of file previews will now scale correctly when window is resized.
- Improve handling of dropped directories on Firefox, file type was missing, now guestimates file type based on file extension.
- Small tweaks and changes to file animation durations and intros.
- Fixed drag coordinates being slightly out of place.
- Multiple small fixes and code improvements.
## 3.9.0
- Add `checkValidity` which is set to `false` by default. If it's set to `true`, FilePond will set the contents of the `labelInvalidField` property as the field custom validity message if it contains invalid files (files that for instance exceed max file size or fail other tests).
## 3.8.2
- Fix problem where remove server error message was passed directly to client without label. Set `labelFileRemoveError` to a string to change default error, set it to a function to show custom server error. `{ labelFileRemoveError: serverError => serverError }`
## 3.8.1
- Expose `dispatch` call to plugin item extensions.
## 3.8.0
- Add `forceRevert` option, set to `true` to force a revert action to finish before continuing.
## 3.7.7
- Improve `onlistupdate` event so it can be better synced with adapter components.
## 3.7.6
- Switched browse text underline to `text-decoration-skip-ink: auto` instead of `text-decoration-skip: ink` to prevent eslint warnings.
## 3.7.5
- Fix problem where calling `processFiles` without arguments would re-process already processed files.
## 3.7.4
- Fix problem where subsequent calls to `processFile` would not automatically revert an uploaded file or abort an active upload.
## 3.7.3
- Fix problem where upload complete indicator would not show when image preview was active.
## 3.7.2
- Expose `createItemAPI` to plugins.
## 3.7.1
- Fix problem where URLs would immidiately be in processed state.
## 3.7.0
- Add `maxParallelUploads` option to limit the amount of files being uploaded in parallel.
- Add option to only fetch file head when downloading remote URLs. File is downloaded to the server and server sends a unique file id to the client. Set `server.fetch.method` to `'HEAD'` the server needs to repond with custom header `X-Content-Transfer-Id` and a unique id. See [handle_fetch_remote_file](https://github.com/pqina/filepond-server-php/blob/master/index.php#L91) in FilePond PHP Server for an example implementation.
## 3.6.0
- Add support for uploading transform plugin variants.
- Add `server.process.ondata` which allows adding entries to the formdata before it's sent to the server.
## 3.5.1
- Fix problem where `processFile` and `processFiles` would reprocess already processed files.
## 3.5.0
- Add `beforeAddFile` hook, this can be used to quickly validate files before they're being added.
## 3.4.0
- Add `server.remove` property, this property can be optionally set to a method to call when the remove button is tapped on a `local` file. This allows removing files from the server. Please note that allowing clients to remove files from the server is a potential security risk and requires extra caution.
By default the property is `null`. The advise is to not use this method and only make changes to the server after the parent form has been submitted. The form POST will contain all the loaded file names and relevant file data, it should be enough to determine the files to remove and the files to keep.
## 3.3.3
- Fix filename matching of content-disposition header when the filename is not wrapped in quotes.
- Fix problem where special characters in filename prevented a file from being added
## 3.3.2
- Fix problem where revert call would revert wrong file item.
## 3.3.1
- Fix problem where exceeding the max file limit would not throw an error
## 3.3.0
- Add feature to silently update metadata so it doesn't trigger an update.
## 3.2.5
- Fix issue where items would be removed before item sub views were all in rest state.
## 3.2.4
- Fix problem where server side rendering would not work correctly.
## 3.2.3
- Fix problem where `beforeRemoveFile` hook was not called when in `instantUpload` mode and reverting an upload.
## 3.2.2
- Add preparations for queueing file processing.
- Improve guards against errors when items are removed.
- Improve alignment of drop label.
## 3.2.1
- Group updateitems callback for better compatibility with React.
## 3.2.0
- Add global scoped property for painter so multiple libraries can subscribe to read and write DOM operations. This is mostly in preparation for a standalone version of the Image Editor plugin.
## 3.1.6
- Fix problem where remove callback would no longer work.
## 3.1.5
- Fix problem with WebWorkers not working correctly on Edge and IE.
## 3.1.4
- Fix syntax error [#147](https://github.com/pqina/filepond/pull/147)
## 3.1.3
- Fix additional problem with quick file removals.
## 3.1.2
- Fix problem where remove call would throw error depending on the state of the upload.
- Fix problem where clicking on abort before upload had started would not cancel upload.
Please note that this update will require installing new versions of the following plugins:
- File Validate Size
## 3.1.1
- Fix problem where panel overflow would render incorrectly.
## 3.1.0
- Improve diffing when updating the `files` property.
- Add `onupdatefiles` callback that is triggered when a file is added or removed to a pond instance.
## 3.0.4
- Fix problem where feature detection would throw error on iOS 8.x
## 3.0.3
- Fix problem with XMLHttpRequest timeout on Internet Explorer 11.
- Fix problem with custom properties on element on Internet Explorer 11.
## 3.0.2
- Fix problem with label not being clickable while in integrated layout mode.
## 3.0.1
- Fix problem where timeout would incorrectly trigger for uploads.
## 3.0.0
- Small internal flow changes to facilitate integration with the Image Editor plugin
- Improve performance
- Improve file loader so it now supports `blob` URLs
- Add `stylePanelLayout` setting to set layout mode for panel
- Add `stylePanelAspectRatio` setting to fix aspect ratio of panel
- Add `styleButtonRemoveItemPosition` to control remove button position on image preview
- Add `styleButtonProcessItemPosition` to control item processing position on image preview
- Add `styleLoadIndicatorPosition` to control load indicator position on image preview
- Add `styleProgressIndicatorPosition` to control process indicator position on image preview
- Add method to automatically update data when file metadata is updated
- Fix animation rest state detection
Please note that this update will require installing new versions of the following plugins:
- File Encode
- Image Crop
- Image Preview
- Image Transform
## 2.3.1
- Fix improved browser environment detection.
## 2.3.0
- Improve browser environment detection [#123](https://github.com/pqina/filepond/pull/123).
- Add `beforeRemoveFile` callback to allow user confirmation before actual file removal.
## 2.2.1
- Fix another problem where list overflow would not render correctly.
## 2.2.0
- Fix problem where `maxFiles` was not enforced when dropping a set of files, each file was added in sequence till `maxFiles` was reached while the set as a whole should've been invalidated at once.
## 2.1.3
- Fix problem where max-height of filepond root would not be respected by file list.
## 2.1.2
- Cleaned up some stray babelHelpers.
- Fix bug in render engine style method, should result in less unnecessary redraws.
## 2.1.1
- Fix problem where the drop indicator would render at the wrong location.
- Fix problem where calling `removeFile` directly after `processFile` was resolved would throw an error.
## 2.1.0
- Labels can now be set as functions, these functions will receive context information, this is useful to customize both he load error and processing error labels based on server response.
## 2.0.1
- Add additional utilities to plugin API.
## 2.0.0
- Automatically replace undo button counterclockwise arrow icon with remove button icon when `instantUpload` is set to `true`.
## 1.8.8
- Add `metadata` handling to `addFile` method.
## 1.8.7
- Fix problem where setting `allowRevert` to `false` would hide the remove button.
## 1.8.6
- Fix problem where adding dataURIs would throw an error
## 1.8.5
- Fix casting of input attributes without value to correct boolean
## 1.8.4
- Fix problem where response timeout would throw an error
- Improve handling of returned value by processing onload function
## 1.8.3
- Cleaning up some stray code
## 1.8.2
- Fix problem where element options did not override page level options
## 1.8.1
- Handle `Blob` with `name` property same as actual `File` object
- Attempt to fix a problem where `elementFromPoint` could not be found in Angular component
## 1.8.0
- Add view filter to file info view
- Add option to [mock server files](https://pqina.nl/filepond/docs/patterns/api/filepond-object/#creating-a-filepond-instance)
- Add option to [set initial file metadata](https://pqina.nl/filepond/docs/patterns/api/filepond-object/#creating-a-filepond-instance) for server files
- Bugfixes
## 1.7.4
- Fix handling of `Content-Disposition` header to better extract the filename
## 1.7.3
- Events are now fired asynchronous, this allows internal processes to finish up
## 1.7.2
- Fix broken links in README
## 1.7.1
- Add view filter to file status view
- Fix problem where attribute object value was not read correctly
## 1.7.0
- Add `onerror` callback to server configuration to allow custom parsing of error response
## 1.6.2
- Fix problem where restored temp file would not be removed correctly
## 1.6.1
- Add `FileOrigin` enum to FilePond object
## 1.6.0
- Add `fileOrigin` property to file item
## 1.5.4
- Update README with links to new plugins and adapters
## 1.5.3
- Accidentally skip over this version number
## 1.5.2
- Remove max-width on file status view
## 1.5.1
- Prevent text wrapping for file size label
## 1.5.0
- Add `onload` method to server configuration
## 1.4.1
- Fix progress indicator getting stuck on subsequent uploads
## 1.4.0
- Add `allowRevert` option to disable revert button
## 1.3.0
- Add `dropValidation` option to enable pre-validating of dropped items
## 1.2.11
- Improve timing of CPU heavy operations (like file encoding) when a file is added to FilePond
## 1.2.10
- Fix `removeFiles` method. Did not correctly remove files when called with empty arguments or an array of indexes
## 1.2.9
- Fix bug where `processFiles` only worked when receiving parameters
## 1.2.8
- Tiny improvements so can be used when server side rendering
## 1.2.7
- Improve loading indicator state
- Add Angular adapter reference to README
## 1.2.6
- Add `onprocessfile` callback to options object
## 1.2.5
- Fix id attribute not being available on FilePond root
- Fix FilePond not rendering correctly when initially hidden
- Improve render performance
## 1.2.4
- Fix `setOptions` method not correctly converting value types
## 1.2.3
- Update the `OptionTypes` property when a plugin is registered
## 1.2.2
- Fix `setOptions` returning an incorrectly formatted options object
## 1.2.1
- Add README link to backlog on WIP
- Fix problem where `destroy` would not remove FilePond
- Switch license from GPL to MIT
- Add jQuery to adapters list
- Fix error message animation
- Improve FilePond event parameters
## 1.1.0
- Accidentally skip version 1.1.0
## 1.0.8
- Fix problem where plugins could be registered twice
- Improve `files` property, now compares existing files against new files and updates accordingly
## 1.0.7
- Hide center panel before scaling panel view
## 1.0.6
- Improve style possibilities for panel views and enforce internal panel layout properties
## 1.0.5
- Fix problem where loading indicators would not spin for certain requests
- Various improvements to README
## 1.0.4
- Fix render bugs
- Improve panel view layout
## 1.0.3
- Fix processing complete state file item color
## 1.0.2
- Add support for client side file manipulation
- Add support for file metadata
- Improve performance
## 1.0.1
- Add correct banners to library files
## 1.0.0
- Initial release
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Pqina
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# [<img src="https://github.com/pqina/filepond-github-assets/blob/master/logo.svg" height="44" alt="FilePond"/>](https://pqina.nl/filepond/)
A JavaScript library that can upload anything you throw at it, optimizes images for faster uploads, and offers a great, accessible, silky smooth user experience.
[](https://github.com/pqina/filepond/blob/master/LICENSE)
[](https://www.npmjs.com/package/filepond)

[](https://bundlephobia.com/package/filepond)
[](https://discord.gg/KTyymsrTrX)
FilePond adapters are available for **[React](https://github.com/pqina/react-filepond)**, **[Vue](https://github.com/pqina/vue-filepond)**, **[Angular](https://github.com/pqina/ngx-filepond)**, **[Svelte](https://github.com/pqina/svelte-filepond)**, and **[jQuery](https://github.com/pqina/jquery-filepond)**
[FilePond v5 Alpha version now available for testing](https://github.com/pqina/filepond/tree/v5)
[Documentation](https://pqina.nl/filepond/docs) • [Discord](https://discord.gg/KTyymsrTrX) • [Examples](#live-demos)
---
[<img src="https://github.com/pqina/filepond-github-assets/blob/master/header.svg" alt="FilePond"/>](https://www.buymeacoffee.com/rikschennink/)
[Buy me a Coffee](https://www.buymeacoffee.com/rikschennink/) • [Use FilePond with Pintura](https://pqina.nl/pintura/?ref=github-filepond) • [Dev updates](https://rik.schenn.ink)
---
### Core Features
- Accepts **directories**, **files**, blobs, local URLs, **remote URLs** and Data URIs.
- **Drop files**, select on filesystem, **copy and paste files**, or add files using the API.
- **Async uploads** with AJAX, supports **chunk uploads**, can encode files as base64 data and send along form post.
- **Accessible**, tested with AT software like VoiceOver and JAWS, **navigable by Keyboard**.
- **Image optimization**, automatic image resizing, **cropping**, filtering, and **fixes EXIF orientation**.
- **Responsive**, automatically scales to available space, is functional on both **mobile and desktop devices**.
[Learn more about FilePond](https://pqina.nl/filepond/)
[<img src="https://github.com/pqina/filepond-github-assets/blob/master/filepond-animation-01.gif?raw=true" width="370" alt=""/>](https://pqina.nl/filepond/)
---
### Also need Image Editing?
**Pintura the modern JavaScript Image Editor** is what you're looking for. Pintura supports setting **crop aspect ratios**, **resizing**, **rotating**, **cropping**, and **flipping** images. Above all, it integrates beautifully with FilePond.
[Learn more about Pintura](https://pqina.nl/pintura/?ref=github-filepond)
[<img src="https://github.com/pqina/filepond-github-assets/blob/master/filepond_pintura.gif?raw=true" width="600" alt=""/>](https://pqina.nl/pintura/?ref=github-filepond)
---
### Live Demos
- [React](https://stackblitz.com/github/pqina/pintura-example-react?file=src%2FExampleFilePond.js)
- [Angular](https://stackblitz.com/github/pqina/pintura-example-angular?file=src%2Fapp%2Ffilepond-example%2Ffilepond-example.component.ts)
- [Svelte](https://stackblitz.com/github/pqina/pintura-example-svelte?file=src%2FApp.svelte%3AL152)
- [Vue](https://stackblitz.com/github/pqina/pintura-example-vue-3?file=src%2FExampleFilePond.vue)
### Plugins
- [File encode](https://github.com/pqina/filepond-plugin-file-encode)
- [File rename](https://github.com/pqina/filepond-plugin-file-rename)
- [File size validation](https://github.com/pqina/filepond-plugin-file-validate-size)
- [File type validation](https://github.com/pqina/filepond-plugin-file-validate-type)
- [File metadata](https://github.com/pqina/filepond-plugin-file-metadata)
- [File poster](https://github.com/pqina/filepond-plugin-file-poster)
- [Image editor](https://github.com/pqina/filepond-plugin-image-edit)
- [Image size validation](https://github.com/pqina/filepond-plugin-image-validate-size)
- [Image preview](https://github.com/pqina/filepond-plugin-image-preview)
- [Image crop](https://github.com/pqina/filepond-plugin-image-crop)
- [Image filter](https://github.com/pqina/filepond-plugin-image-filter)
- [Image resize](https://github.com/pqina/filepond-plugin-image-resize)
- [Image transform](https://github.com/pqina/filepond-plugin-image-transform)
- [Image EXIF orientation](https://github.com/pqina/filepond-plugin-image-exif-orientation)
- [Image overlay](https://github.com/nielsboogaard/filepond-plugin-image-overlay) ([nielsboogaard/filepond-plugin-image-overlay](https://github.com/nielsboogaard/filepond-plugin-image-overlay))
- [Media preview](https://github.com/nielsboogaard/filepond-plugin-media-preview) ([nielsboogaard/filepond-plugin-media-preview](https://github.com/nielsboogaard/filepond-plugin-media-preview))
- [Media preview + PDF preview](https://github.com/ErnestBrandi/filepond-plugin-media-preview) ([ErnestBrandi/filepond-plugin-media-preview](https://github.com/ErnestBrandi/filepond-plugin-media-preview))
- [Get file](https://github.com/nielsboogaard/filepond-plugin-get-file) ([nielsboogaard/filepond-plugin-get-file](https://github.com/nielsboogaard/filepond-plugin-get-file))
- [Zip Directory Uploads](https://github.com/tzsk/filepond-plugin-zipper) ([tzsk/filepond-plugin-zipper](https://github.com/tzsk/filepond-plugin-zipper))
- [PDF Preview](https://github.com/Adri-Glez/filepond-plugin-pdf-preview) ([Adri-Glez/filepond-plugin-pdf-preview](https://github.com/Adri-Glez/filepond-plugin-pdf-preview))
- [PDF Convert](https://github.com/alexandreDavid/filepond-plugin-pdf-convert) ([alexandreDavid/filepond-plugin-pdf-convert](https://github.com/alexandreDavid/filepond-plugin-pdf-convert))
### Adapters
- [React](https://github.com/pqina/react-filepond)
- [Vue](https://github.com/pqina/vue-filepond)
- [Svelte](https://github.com/pqina/svelte-filepond)
- [jQuery](https://github.com/pqina/jquery-filepond)
- [Angular](https://github.com/pqina/ngx-filepond)
- [Angular 1](https://github.com/johnnyasantoss/angularjs-filepond) ([johnnyasantoss/angularjs-filepond](https://github.com/johnnyasantoss/angularjs-filepond))
- [Blazor](https://github.com/soenneker/soenneker.blazor.filepond) ([soenneker/soenneker.blazor.filepond](https://github.com/soenneker/soenneker.blazor.filepond))
- [Ember](https://github.com/alexdiliberto/ember-filepond) ([alexdiliberto/ember-filepond](https://github.com/alexdiliberto/ember-filepond))
### Backend
- [PHP](https://github.com/pqina/filepond-boilerplate-php)
- [Django](https://github.com/ImperialCollegeLondon/django-drf-filepond) ([ImperialCollegeLondon/django-drf-filepond](https://github.com/ImperialCollegeLondon/django-drf-filepond))
- [Laravel](https://github.com/Sopamo/laravel-filepond) ([Sopamo/laravel-filepond](https://github.com/Sopamo/laravel-filepond))
- [Laravel](https://github.com/Albert221/laravel-filepond) ([Albert221/laravel-filepond](https://github.com/Albert221/laravel-filepond))
- [SilverStripe](https://github.com/lekoala/silverstripe-filepond) ([lekoala/silverstripe-filepond](https://github.com/lekoala/silverstripe-filepond))
- [Ruby on Rails](https://github.com/Code-With-Rails/filepond-rails) ([Code-With-Rails/filepond-rails](https://github.com/Code-With-Rails/filepond-rails))
## Quick Start
Install using npm:
```bash
npm install filepond
```
Then import in your project:
```js
import * as FilePond from 'filepond';
// Create a multi file upload component
const pond = FilePond.create({
multiple: true,
name: 'filepond',
});
// Add it to the DOM
document.body.appendChild(pond.element);
```
Or get it from a CDN:
```html
<!DOCTYPE html>
<html>
<head>
<title>FilePond from CDN</title>
<!-- Filepond stylesheet -->
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet" />
</head>
<body>
<!-- We'll transform this input into a pond -->
<input type="file" class="filepond" />
<!-- Load FilePond library -->
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
<!-- Turn all file input elements into ponds -->
<script>
FilePond.parse(document.body);
</script>
</body>
</html>
```
[Getting started with FilePond](https://pqina.nl/filepond/docs/patterns/getting-started/)
## Internationalization
The [locale folder](./locale/) contains different language files, PR's are welcome, you can use locale files like this:
```js
import pt_BR from 'filepond/locale/pt-br.js';
FilePond.setOptions(pt_BR);
```
## Contributing
At the moment test coverage is not great, it's around 65%. To accept pull requests the tests need to be better, any help to improve them is very much appreciated.
Tests are based on Jest and can be run with `npm run test`
To build the library run `npm run build`
## Publications
- [Using FilePond with NodeJS](https://www.infoworld.com/article/3627248/how-to-use-filepond-with-nodejs.html)
- [Applying Watermarks to Images with FilePond](https://pqina.nl/blog/applying-watermarks-to-images-with-filepond/)
- [Generating Image Thumbnails in the Browser using JavaScript and FilePond](https://dev.to/pqina/generating-image-thumbnails-in-the-browser-using-javascript-and-filepond-10b8)
- [How to upload files with Vue and FilePond](https://dev.to/pqina/how-to-upload-files-with-vue-and-filepond-1m02)
- [Smooth file uploading with React and FilePond](https://itnext.io/uploading-files-with-react-and-filepond-f8a798308557)
- [5 interesting technical challenges I faced while building FilePond](https://itnext.io/filepond-frontend-trickery-a3073c934c77)
- [Image uploads with Laravel and FilePond](https://devdojo.com/episode/image-uploads-with-laravel-and-filepond)
- [Integrating FilePond with Ember](https://alexdiliberto.com/ember-filepond/)
- [FilePond launch day post-mortem](https://pqina.nl/blog/filepond-launch-day-post-mortem)
- [FilePond on ProductHunt](https://www.producthunt.com/posts/filepond-js)
### Browser Compatibility
FilePond is compatible with a wide range of desktop and mobile browsers, the oldest explicitly supported browser is IE11, for best cross browser support add [FilePond Polyfill](https://github.com/pqina/filepond-polyfill) and [Babel polyfill](https://babeljs.io/docs/en/babel-polyfill) to your project.
FilePond uses [BrowserStack](https://www.browserstack.com/) for compatibility testing.
[<img src="https://github.com/pqina/filepond-github-assets/blob/master/browserstack-logo.svg" height="32" alt="BrowserStack"/>](https://www.browserstack.com/)
## License
**Please don't remove or change the disclaimers in the source files**
MIT License
Copyright (c) 2020 PQINA | [Rik Schennink](mailto:rik@pqina.nl)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: banner-cli.js
================================================
const banner = require('./banner');
const pkg = require('./package.json');
const args = process.argv.slice(2);
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(data) {
process.stdout.write(banner({ id: args[0], ...pkg }) + data);
});
================================================
FILE: banner.js
================================================
module.exports = ({ id, version, license, homepage }) => `/*!
* ${ id } ${ version }
* Licensed under ${ license }, https://opensource.org/licenses/${ license }/
* Please visit ${ homepage } for details.
*/
/* eslint-disable */
`;
================================================
FILE: dist/filepond.css
================================================
/*!
* FilePond 4.32.12
* Licensed under MIT, https://opensource.org/licenses/MIT/
* Please visit https://pqina.nl/filepond/ for details.
*/
/* eslint-disable */
.filepond--assistant {
position: absolute;
overflow: hidden;
height: 1px;
width: 1px;
padding: 0;
border: 0;
clip: rect(1px, 1px, 1px, 1px);
-webkit-clip-path: inset(50%);
clip-path: inset(50%);
white-space: nowrap;
}
/* Hard to override styles */
.filepond--browser.filepond--browser {
/* is positioned absolute so it is focusable for form validation errors */
position: absolute;
margin: 0;
padding: 0;
/* is positioned ~behind drop label */
left: 1em;
top: 1.75em;
width: calc(100% - 2em);
/* hide visually */
opacity: 0;
font-size: 0; /* removes text cursor in Internet Explorer 11 */
}
.filepond--data {
position: absolute;
width: 0;
height: 0;
padding: 0;
margin: 0;
border: none;
visibility: hidden;
pointer-events: none;
contain: strict;
}
.filepond--drip {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.1;
/* can't interact with this element */
pointer-events: none;
/* inherit border radius from parent (needed for drip-blob cut of) */
border-radius: 0.5em;
/* this seems to prevent Chrome from redrawing this layer constantly */
background: rgba(0, 0, 0, 0.01);
}
.filepond--drip-blob {
position: absolute;
-webkit-transform-origin: center center;
transform-origin: center center;
top: 0;
left: 0;
width: 8em;
height: 8em;
margin-left: -4em;
margin-top: -4em;
background: #292625;
border-radius: 50%;
/* will be animated */
will-change: transform, opacity;
}
.filepond--drop-label {
position: absolute;
left: 0;
right: 0;
top: 0;
margin: 0;
color: #4f4f4f;
/* center contents */
display: flex;
justify-content: center;
align-items: center;
/* fixes IE11 centering problems (is overruled by label min-height) */
height: 0px;
/* dont allow selection */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
/* will be animated */
will-change: transform, opacity;
}
/* Hard to override styles on purpose */
.filepond--drop-label.filepond--drop-label label {
display: block;
margin: 0;
padding: 0.5em; /* use padding instead of margin so click area is not impacted */
}
.filepond--drop-label label {
cursor: default;
font-size: 0.875em;
font-weight: normal;
text-align: center;
line-height: 1.5;
}
.filepond--label-action {
text-decoration: underline;
-webkit-text-decoration-skip: ink;
text-decoration-skip-ink: auto;
-webkit-text-decoration-color: #a7a4a4;
text-decoration-color: #a7a4a4;
cursor: pointer;
}
.filepond--root[data-disabled] .filepond--drop-label label {
opacity: 0.5;
}
/* Hard to override styles */
.filepond--file-action-button.filepond--file-action-button {
font-size: 1em;
width: 1.625em;
height: 1.625em;
font-family: inherit;
line-height: inherit;
margin: 0;
padding: 0;
border: none;
outline: none;
will-change: transform, opacity;
/* hidden label */
}
.filepond--file-action-button.filepond--file-action-button span {
position: absolute;
overflow: hidden;
height: 1px;
width: 1px;
padding: 0;
border: 0;
clip: rect(1px, 1px, 1px, 1px);
-webkit-clip-path: inset(50%);
clip-path: inset(50%);
white-space: nowrap;
}
.filepond--file-action-button.filepond--file-action-button {
/* scale SVG to fill button */
}
.filepond--file-action-button.filepond--file-action-button svg {
width: 100%;
height: 100%;
}
.filepond--file-action-button.filepond--file-action-button {
/* bigger touch area */
}
.filepond--file-action-button.filepond--file-action-button::after {
position: absolute;
left: -0.75em;
right: -0.75em;
top: -0.75em;
bottom: -0.75em;
content: '';
}
/* Soft styles */
.filepond--file-action-button {
/* use default arrow cursor */
cursor: auto;
/* reset default button styles */
color: #fff;
/* set default look n feel */
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
background-image: none;
/* we animate box shadow on focus */
/* it's only slightly slower than animating */
/* a pseudo-element with transforms and renders */
/* a lot better on chrome */
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
transition: box-shadow 0.25s ease-in;
}
.filepond--file-action-button:hover,
.filepond--file-action-button:focus {
box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.9);
}
.filepond--file-action-button[disabled] {
color: rgba(255, 255, 255, 0.5);
background-color: rgba(0, 0, 0, 0.25);
}
.filepond--file-action-button[hidden] {
display: none;
}
/* edit button */
.filepond--action-edit-item.filepond--action-edit-item {
width: 2em;
height: 2em;
padding: 0.1875em;
}
.filepond--action-edit-item.filepond--action-edit-item[data-align*='center'] {
margin-left: -0.1875em;
}
.filepond--action-edit-item.filepond--action-edit-item[data-align*='bottom'] {
margin-bottom: -0.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 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--file-info {
position: static;
display: flex;
flex-direction: column;
align-items: flex-start;
flex: 1;
margin: 0 0.5em 0 0;
min-width: 0;
/* will be animated */
will-change: transform, opacity;
/* can't do anything with this info */
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
/* no margins on children */
}
.filepond--file-info * {
margin: 0;
}
.filepond--file-info {
/* we don't want to have these overrules so these selectors are a bit more specific */
}
.filepond--file-info .filepond--file-info-main {
font-size: 0.75em;
line-height: 1.2;
/* we want ellipsis if this bar gets too wide */
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
}
.filepond--file-info .filepond--file-info-sub {
font-size: 0.625em;
opacity: 0.5;
transition: opacity 0.25s ease-in-out;
white-space: nowrap;
}
.filepond--file-info .filepond--file-info-sub:empty {
display: none;
}
.filepond--file-status {
position: static;
display: flex;
flex-direction: column;
align-items: flex-end;
flex-grow: 0;
flex-shrink: 0;
margin: 0;
min-width: 2.25em;
text-align: right;
/* will be animated */
will-change: transform, opacity;
/* can't do anything with this info */
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
/* no margins on children */
}
.filepond--file-status * {
margin: 0;
white-space: nowrap;
}
.filepond--file-status {
/* font sizes */
}
.filepond--file-status .filepond--file-status-main {
font-size: 0.75em;
line-height: 1.2;
}
.filepond--file-status .filepond--file-status-sub {
font-size: 0.625em;
opacity: 0.5;
transition: opacity 0.25s ease-in-out;
}
/* Hard to override styles */
.filepond--file-wrapper.filepond--file-wrapper {
border: none;
margin: 0;
padding: 0;
min-width: 0;
height: 100%;
/* hide legend for visual users */
}
.filepond--file-wrapper.filepond--file-wrapper > legend {
position: absolute;
overflow: hidden;
height: 1px;
width: 1px;
padding: 0;
border: 0;
clip: rect(1px, 1px, 1px, 1px);
-webkit-clip-path: inset(50%);
clip-path: inset(50%);
white-space: nowrap;
}
.filepond--file {
position: static;
display: flex;
height: 100%;
align-items: flex-start;
padding: 0.5625em 0.5625em;
color: #fff;
border-radius: 0.5em;
/* control positions */
}
.filepond--file .filepond--file-status {
margin-left: auto;
margin-right: 2.25em;
}
.filepond--file .filepond--processing-complete-indicator {
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
z-index: 3;
}
.filepond--file .filepond--processing-complete-indicator,
.filepond--file .filepond--progress-indicator,
.filepond--file .filepond--file-action-button {
position: absolute;
}
.filepond--file {
/* .filepond--file-action-button */
}
.filepond--file [data-align*='left'] {
left: 0.5625em;
}
.filepond--file [data-align*='right'] {
right: 0.5625em;
}
.filepond--file [data-align*='center'] {
left: calc(50% - 0.8125em); /* .8125 is half of button width */
}
.filepond--file [data-align*='bottom'] {
bottom: 1.125em;
}
.filepond--file [data-align='center'] {
top: calc(50% - 0.8125em);
}
.filepond--file .filepond--progress-indicator {
margin-top: 0.1875em;
}
.filepond--file .filepond--progress-indicator[data-align*='right'] {
margin-right: 0.1875em;
}
.filepond--file .filepond--progress-indicator[data-align*='left'] {
margin-left: 0.1875em;
}
/* make sure text does not overlap */
[data-filepond-item-state='cancelled'] .filepond--file-info,
[data-filepond-item-state*='invalid'] .filepond--file-info,
[data-filepond-item-state*='error'] .filepond--file-info {
margin-right: 2.25em;
}
[data-filepond-item-state~='processing'] .filepond--file-status-sub {
opacity: 0;
}
[data-filepond-item-state~='processing']
.filepond--action-abort-item-processing
~ .filepond--file-status
.filepond--file-status-sub {
opacity: 0.5;
}
[data-filepond-item-state='processing-error'] .filepond--file-status-sub {
opacity: 0;
}
[data-filepond-item-state='processing-error']
.filepond--action-retry-item-processing
~ .filepond--file-status
.filepond--file-status-sub {
opacity: 0.5;
}
[data-filepond-item-state='processing-complete'] {
/* busy state */
}
[data-filepond-item-state='processing-complete'] .filepond--action-revert-item-processing svg {
-webkit-animation: fall 0.5s 0.125s linear both;
animation: fall 0.5s 0.125s linear both;
}
[data-filepond-item-state='processing-complete'] {
/* hide details by default, only show when can revert */
}
[data-filepond-item-state='processing-complete'] .filepond--file-status-sub {
opacity: 0.5;
}
[data-filepond-item-state='processing-complete']
.filepond--processing-complete-indicator:not([style*='hidden'])
~ .filepond--file-status
.filepond--file-status-sub {
opacity: 0;
}
[data-filepond-item-state='processing-complete'] .filepond--file-info-sub {
opacity: 0;
}
[data-filepond-item-state='processing-complete']
.filepond--action-revert-item-processing
~ .filepond--file-info
.filepond--file-info-sub {
opacity: 0.5;
}
/* file state can be invalid or error, both are visually similar but */
/* having them as separate states might be useful */
[data-filepond-item-state*='invalid'] .filepond--panel,
[data-filepond-item-state*='invalid'] .filepond--file-wrapper,
[data-filepond-item-state*='error'] .filepond--panel,
[data-filepond-item-state*='error'] .filepond--file-wrapper {
-webkit-animation: shake 0.65s linear both;
animation: shake 0.65s linear both;
}
/* spins progress indicator when file is marked as busy */
[data-filepond-item-state*='busy'] .filepond--progress-indicator svg {
-webkit-animation: spin 1s linear infinite;
animation: spin 1s linear infinite;
}
/**
* States
*/
@-webkit-keyframes spin {
0% {
-webkit-transform: rotateZ(0deg);
transform: rotateZ(0deg);
}
100% {
-webkit-transform: rotateZ(360deg);
transform: rotateZ(360deg);
}
}
@keyframes spin {
0% {
-webkit-transform: rotateZ(0deg);
transform: rotateZ(0deg);
}
100% {
-webkit-transform: rotateZ(360deg);
transform: rotateZ(360deg);
}
}
@-webkit-keyframes shake {
10%,
90% {
-webkit-transform: translateX(-0.0625em);
transform: translateX(-0.0625em);
}
20%,
80% {
-webkit-transform: translateX(0.125em);
transform: translateX(0.125em);
}
30%,
50%,
70% {
-webkit-transform: translateX(-0.25em);
transform: translateX(-0.25em);
}
40%,
60% {
-webkit-transform: translateX(0.25em);
transform: translateX(0.25em);
}
}
@keyframes shake {
10%,
90% {
-webkit-transform: translateX(-0.0625em);
transform: translateX(-0.0625em);
}
20%,
80% {
-webkit-transform: translateX(0.125em);
transform: translateX(0.125em);
}
30%,
50%,
70% {
-webkit-transform: translateX(-0.25em);
transform: translateX(-0.25em);
}
40%,
60% {
-webkit-transform: translateX(0.25em);
transform: translateX(0.25em);
}
}
@-webkit-keyframes fall {
0% {
opacity: 0;
-webkit-transform: scale(0.5);
transform: scale(0.5);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
70% {
opacity: 1;
-webkit-transform: scale(1.1);
transform: scale(1.1);
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
}
@keyframes fall {
0% {
opacity: 0;
-webkit-transform: scale(0.5);
transform: scale(0.5);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
70% {
opacity: 1;
-webkit-transform: scale(1.1);
transform: scale(1.1);
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
}
/* ignore all other interaction elements while dragging a file */
.filepond--hopper[data-hopper-state='drag-over'] > * {
pointer-events: none;
}
/* capture all hit tests using a hidden layer, this speeds up the event flow */
.filepond--hopper[data-hopper-state='drag-over']::after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 100;
}
.filepond--progress-indicator {
z-index: 103;
}
.filepond--file-action-button {
z-index: 102;
}
.filepond--file-status {
z-index: 101;
}
.filepond--file-info {
z-index: 100;
}
.filepond--item {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 1;
padding: 0;
margin: 0.25em;
will-change: transform, opacity;
touch-action: auto;
/* item children order */
}
.filepond--item > .filepond--panel {
z-index: -1;
}
/* has a slight shadow */
.filepond--item > .filepond--panel .filepond--panel-bottom {
box-shadow: 0 0.0625em 0.125em -0.0625em rgba(0, 0, 0, 0.25);
}
.filepond--item {
/* drag related */
}
.filepond--item > .filepond--file-wrapper,
.filepond--item > .filepond--panel {
transition: opacity 0.15s ease-out;
}
.filepond--item[data-drag-state] {
cursor: -webkit-grab;
cursor: grab;
}
.filepond--item[data-drag-state] > .filepond--panel {
transition: box-shadow 0.125s ease-in-out;
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
}
.filepond--item[data-drag-state='drag'] {
cursor: -webkit-grabbing;
cursor: grabbing;
}
.filepond--item[data-drag-state='drag'] > .filepond--panel {
box-shadow: 0 0.125em 0.3125em rgba(0, 0, 0, 0.325);
}
.filepond--item[data-drag-state]:not([data-drag-state='idle']) {
z-index: 2;
}
/* states */
.filepond--item-panel {
background-color: #64605e;
}
[data-filepond-item-state='processing-complete'] .filepond--item-panel {
background-color: #369763;
}
[data-filepond-item-state*='invalid'] .filepond--item-panel,
[data-filepond-item-state*='error'] .filepond--item-panel {
background-color: #c44e47;
}
/* style of item panel */
.filepond--item-panel {
border-radius: 0.5em;
transition: background-color 0.25s;
}
/* normal mode */
.filepond--list-scroller {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: 0;
will-change: transform;
}
/* scroll mode */
.filepond--list-scroller[data-state='overflow'] .filepond--list {
bottom: 0;
right: 0;
}
.filepond--list-scroller[data-state='overflow'] {
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
-webkit-mask: linear-gradient(to bottom, #000 calc(100% - 0.5em), transparent 100%);
mask: linear-gradient(to bottom, #000 calc(100% - 0.5em), transparent 100%);
}
/* style scrollbar */
.filepond--list-scroller::-webkit-scrollbar {
background: transparent;
}
.filepond--list-scroller::-webkit-scrollbar:vertical {
width: 1em;
}
.filepond--list-scroller::-webkit-scrollbar:horizontal {
height: 0;
}
.filepond--list-scroller::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.3);
border-radius: 99999px;
border: 0.3125em solid transparent;
background-clip: content-box;
}
/* hard to overide styles on purpose */
.filepond--list.filepond--list {
position: absolute;
top: 0;
margin: 0;
padding: 0;
list-style-type: none;
/* prevents endless paint calls on filepond--list-scroller */
will-change: transform;
}
/* used for padding so allowed to be restyled */
.filepond--list {
left: 0.75em;
right: 0.75em;
}
.filepond--root[data-style-panel-layout~='integrated'] {
width: 100%;
height: 100%;
max-width: none;
margin: 0;
}
.filepond--root[data-style-panel-layout~='circle'] .filepond--panel-root,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--panel-root {
border-radius: 0;
}
.filepond--root[data-style-panel-layout~='circle'] .filepond--panel-root > *,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--panel-root > * {
display: none;
}
.filepond--root[data-style-panel-layout~='circle'] .filepond--drop-label,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--drop-label {
bottom: 0;
height: auto;
display: flex;
justify-content: center;
align-items: center;
z-index: 7;
}
.filepond--root[data-style-panel-layout~='circle'],
.filepond--root[data-style-panel-layout~='integrated'] {
/* we're only loading one item, this makes the intro animation a bit nicer */
}
.filepond--root[data-style-panel-layout~='circle'] .filepond--item-panel,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--item-panel {
display: none;
}
.filepond--root[data-style-panel-layout~='compact'] .filepond--list-scroller,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--list-scroller {
overflow: hidden;
height: 100%;
margin-top: 0;
margin-bottom: 0;
}
.filepond--root[data-style-panel-layout~='compact'] .filepond--list,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--list {
left: 0;
right: 0;
height: 100%;
}
.filepond--root[data-style-panel-layout~='compact'] .filepond--item,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--item {
margin: 0;
}
.filepond--root[data-style-panel-layout~='compact'] .filepond--file-wrapper,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--file-wrapper {
height: 100%;
}
.filepond--root[data-style-panel-layout~='compact'] .filepond--drop-label,
.filepond--root[data-style-panel-layout~='integrated'] .filepond--drop-label {
z-index: 7;
}
.filepond--root[data-style-panel-layout~='circle'] {
border-radius: 99999rem;
overflow: hidden;
}
.filepond--root[data-style-panel-layout~='circle'] > .filepond--panel {
border-radius: inherit;
}
.filepond--root[data-style-panel-layout~='circle'] > .filepond--panel > * {
display: none;
}
.filepond--root[data-style-panel-layout~='circle'] {
/* circle cuts of this info, so best to hide it */
}
.filepond--root[data-style-panel-layout~='circle'] .filepond--file-info {
display: none;
}
.filepond--root[data-style-panel-layout~='circle'] .filepond--file-status {
display: none;
}
.filepond--root[data-style-panel-layout~='circle'] .filepond--action-edit-item {
opacity: 1 !important;
visibility: visible !important;
}
/* dirfty way to fix circular overflow issue on safari 11+ */
@media not all and (min-resolution: 0.001dpcm) {
@supports (-webkit-appearance: none) and (stroke-color: transparent) {
.filepond--root[data-style-panel-layout~='circle'] {
will-change: transform;
}
}
}
.filepond--panel-root {
border-radius: 0.5em;
background-color: #f1f0ef;
}
.filepond--panel {
position: absolute;
left: 0;
top: 0;
right: 0;
margin: 0;
/* defaults to 100% height (fixed height mode) this fixes problem with panel height in IE11 */
height: 100% !important;
/* no interaction possible with panel */
pointer-events: none;
}
.filepond-panel:not([data-scalable='false']) {
height: auto !important;
}
.filepond--panel[data-scalable='false'] > div {
display: none;
}
.filepond--panel[data-scalable='true'] {
/* this seems to fix Chrome performance issues */
/* - when box-shadow is enabled */
/* - when multiple ponds are active on the same page */
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
/* prevent borders and backgrounds */
background-color: transparent !important;
border: none !important;
}
.filepond--panel-top,
.filepond--panel-bottom,
.filepond--panel-center {
position: absolute;
left: 0;
top: 0;
right: 0;
margin: 0;
padding: 0;
}
.filepond--panel-top,
.filepond--panel-bottom {
height: 0.5em;
}
.filepond--panel-top {
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
border-bottom: none !important;
/* fixes tiny transparant line between top and center panel */
}
.filepond--panel-top::after {
content: '';
position: absolute;
height: 2px;
left: 0;
right: 0;
bottom: -1px;
background-color: inherit;
}
.filepond--panel-center,
.filepond--panel-bottom {
will-change: transform;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform-origin: left top;
transform-origin: left top;
-webkit-transform: translate3d(0, 0.5em, 0);
transform: translate3d(0, 0.5em, 0);
}
.filepond--panel-bottom {
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
border-top: none !important;
/* fixes tiny transparant line between bottom and center of panel */
}
.filepond--panel-bottom::before {
content: '';
position: absolute;
height: 2px;
left: 0;
right: 0;
top: -1px;
background-color: inherit;
}
.filepond--panel-center {
/* the center panel is scaled using scale3d to fit the correct height */
/* we use 100px instead of 1px as scaling 1px to a huge height is really laggy on chrome */
height: 100px !important;
border-top: none !important;
border-bottom: none !important;
border-radius: 0 !important;
/* hide if not transformed, prevents a little flash when the panel is at 100px height while attached for first time */
}
.filepond--panel-center:not([style]) {
visibility: hidden;
}
.filepond--progress-indicator {
position: static;
width: 1.25em;
height: 1.25em;
color: #fff;
/* can't have margins */
margin: 0;
/* no interaction possible with progress indicator */
pointer-events: none;
/* will be animated */
will-change: transform, opacity;
}
.filepond--progress-indicator svg {
width: 100%;
height: 100%;
vertical-align: top;
transform-box: fill-box; /* should center the animation correctly when zoomed in */
}
.filepond--progress-indicator path {
fill: none;
stroke: currentColor;
}
.filepond--list-scroller {
z-index: 6;
}
.filepond--drop-label {
z-index: 5;
}
.filepond--drip {
z-index: 3;
}
.filepond--root > .filepond--panel {
z-index: 2;
}
.filepond--browser {
z-index: 1;
}
.filepond--root {
/* layout*/
box-sizing: border-box;
position: relative;
margin-bottom: 1em;
/* base font size for whole component */
font-size: 1rem;
/* base line height */
line-height: normal;
/* up uses default system font family */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
/* will increase font weight a bit on Safari */
font-weight: 450;
/* default text alignment */
text-align: left;
/* better text rendering on Safari */
text-rendering: optimizeLegibility;
/* text direction is ltr for now */
direction: ltr;
/* optimize rendering */
/* https://developer.mozilla.org/en-US/docs/Web/CSS/contain */
contain: layout style size;
/* correct box sizing, line-height and positioning on child elements */
}
.filepond--root * {
box-sizing: inherit;
line-height: inherit;
}
.filepond--root *:not(text) {
font-size: inherit;
}
.filepond--root {
/* block everything */
}
.filepond--root[data-disabled] {
pointer-events: none;
}
.filepond--root[data-disabled] .filepond--list-scroller {
pointer-events: all;
}
.filepond--root[data-disabled] .filepond--list {
pointer-events: none;
}
/**
* Root element children layout
*/
.filepond--root .filepond--drop-label {
min-height: 4.75em;
}
.filepond--root .filepond--list-scroller {
margin-top: 1em;
margin-bottom: 1em;
}
.filepond--root .filepond--credits {
position: absolute;
right: 0;
opacity: 0.4;
line-height: 0.85;
font-size: 11px;
color: inherit;
text-decoration: none;
z-index: 3;
bottom: -14px;
}
.filepond--root .filepond--credits[style] {
top: 0;
bottom: auto;
margin-top: 14px;
}
================================================
FILE: dist/filepond.esm.js
================================================
/*!
* FilePond 4.32.12
* Licensed under MIT, https://opensource.org/licenses/MIT/
* Please visit https://pqina.nl/filepond/ for details.
*/
/* eslint-disable */
const isNode = value => value instanceof HTMLElement;
const createStore = (initialState, queries = [], actions = []) => {
// internal state
const state = {
...initialState,
};
// contains all actions for next frame, is clear when actions are requested
const actionQueue = [];
const dispatchQueue = [];
// returns a duplicate of the current state
const getState = () => ({ ...state });
// returns a duplicate of the actions array and clears the actions array
const processActionQueue = () => {
// create copy of actions queue
const queue = [...actionQueue];
// clear actions queue (we don't want no double actions)
actionQueue.length = 0;
return queue;
};
// processes actions that might block the main UI thread
const processDispatchQueue = () => {
// create copy of actions queue
const queue = [...dispatchQueue];
// clear actions queue (we don't want no double actions)
dispatchQueue.length = 0;
// now dispatch these actions
queue.forEach(({ type, data }) => {
dispatch(type, data);
});
};
// adds a new action, calls its handler and
const dispatch = (type, data, isBlocking) => {
// is blocking action (should never block if document is hidden)
if (isBlocking && !document.hidden) {
dispatchQueue.push({ type, data });
return;
}
// if this action has a handler, handle the action
if (actionHandlers[type]) {
actionHandlers[type](data);
}
// now add action
actionQueue.push({
type,
data,
});
};
const query = (str, ...args) => (queryHandles[str] ? queryHandles[str](...args) : null);
const api = {
getState,
processActionQueue,
processDispatchQueue,
dispatch,
query,
};
let queryHandles = {};
queries.forEach(query => {
queryHandles = {
...query(state),
...queryHandles,
};
});
let actionHandlers = {};
actions.forEach(action => {
actionHandlers = {
...action(dispatch, query, state),
...actionHandlers,
};
});
return api;
};
const defineProperty = (obj, property, definition) => {
if (typeof definition === 'function') {
obj[property] = definition;
return;
}
Object.defineProperty(obj, property, { ...definition });
};
const forin = (obj, cb) => {
for (const key in obj) {
if (!obj.hasOwnProperty(key)) {
continue;
}
cb(key, obj[key]);
}
};
const createObject = definition => {
const obj = {};
forin(definition, property => {
defineProperty(obj, property, definition[property]);
});
return obj;
};
const attr = (node, name, value = null) => {
if (value === null) {
return node.getAttribute(name) || node.hasAttribute(name);
}
node.setAttribute(name, value);
};
const ns = 'http://www.w3.org/2000/svg';
const svgElements = ['svg', 'path']; // only svg elements used
const isSVGElement = tag => svgElements.includes(tag);
const createElement = (tag, className, attributes = {}) => {
if (typeof className === 'object') {
attributes = className;
className = null;
}
const element = isSVGElement(tag)
? document.createElementNS(ns, tag)
: document.createElement(tag);
if (className) {
if (isSVGElement(tag)) {
attr(element, 'class', className);
} else {
element.className = className;
}
}
forin(attributes, (name, value) => {
attr(element, name, value);
});
return element;
};
const appendChild = parent => (child, index) => {
if (typeof index !== 'undefined' && parent.children[index]) {
parent.insertBefore(child, parent.children[index]);
} else {
parent.appendChild(child);
}
};
const appendChildView = (parent, childViews) => (view, index) => {
if (typeof index !== 'undefined') {
childViews.splice(index, 0, view);
} else {
childViews.push(view);
}
return view;
};
const removeChildView = (parent, childViews) => view => {
// remove from child views
childViews.splice(childViews.indexOf(view), 1);
// remove the element
if (view.element.parentNode) {
parent.removeChild(view.element);
}
return view;
};
const IS_BROWSER = (() =>
typeof window !== 'undefined' && typeof window.document !== 'undefined')();
const isBrowser = () => IS_BROWSER;
const testElement = isBrowser() ? createElement('svg') : {};
const getChildCount =
'children' in testElement ? el => el.children.length : el => el.childNodes.length;
const getViewRect = (elementRect, childViews, offset, scale) => {
const left = offset[0] || elementRect.left;
const top = offset[1] || elementRect.top;
const right = left + elementRect.width;
const bottom = top + elementRect.height * (scale[1] || 1);
const rect = {
// the rectangle of the element itself
element: {
...elementRect,
},
// the rectangle of the element expanded to contain its children, does not include any margins
inner: {
left: elementRect.left,
top: elementRect.top,
right: elementRect.right,
bottom: elementRect.bottom,
},
// the rectangle of the element expanded to contain its children including own margin and child margins
// margins will be added after we've recalculated the size
outer: {
left,
top,
right,
bottom,
},
};
// expand rect to fit all child rectangles
childViews
.filter(childView => !childView.isRectIgnored())
.map(childView => childView.rect)
.forEach(childViewRect => {
expandRect(rect.inner, { ...childViewRect.inner });
expandRect(rect.outer, { ...childViewRect.outer });
});
// calculate inner width and height
calculateRectSize(rect.inner);
// append additional margin (top and left margins are included in top and left automatically)
rect.outer.bottom += rect.element.marginBottom;
rect.outer.right += rect.element.marginRight;
// calculate outer width and height
calculateRectSize(rect.outer);
return rect;
};
const expandRect = (parent, child) => {
// adjust for parent offset
child.top += parent.top;
child.right += parent.left;
child.bottom += parent.top;
child.left += parent.left;
if (child.bottom > parent.bottom) {
parent.bottom = child.bottom;
}
if (child.right > parent.right) {
parent.right = child.right;
}
};
const calculateRectSize = rect => {
rect.width = rect.right - rect.left;
rect.height = rect.bottom - rect.top;
};
const isNumber = value => typeof value === 'number';
/**
* Determines if position is at destination
* @param position
* @param destination
* @param velocity
* @param errorMargin
* @returns {boolean}
*/
const thereYet = (position, destination, velocity, errorMargin = 0.001) => {
return Math.abs(position - destination) < errorMargin && Math.abs(velocity) < errorMargin;
};
/**
* Spring animation
*/
const spring =
// default options
({ stiffness = 0.5, damping = 0.75, mass = 10 } = {}) =>
// method definition
{
let target = null;
let position = null;
let velocity = 0;
let resting = false;
// updates spring state
const interpolate = (ts, skipToEndState) => {
// in rest, don't animate
if (resting) return;
// need at least a target or position to do springy things
if (!(isNumber(target) && isNumber(position))) {
resting = true;
velocity = 0;
return;
}
// calculate spring force
const f = -(position - target) * stiffness;
// update velocity by adding force based on mass
velocity += f / mass;
// update position by adding velocity
position += velocity;
// slow down based on amount of damping
velocity *= damping;
// we've arrived if we're near target and our velocity is near zero
if (thereYet(position, target, velocity) || skipToEndState) {
position = target;
velocity = 0;
resting = true;
// we done
api.onupdate(position);
api.oncomplete(position);
} else {
// progress update
api.onupdate(position);
}
};
/**
* Set new target value
* @param value
*/
const setTarget = value => {
// if currently has no position, set target and position to this value
if (isNumber(value) && !isNumber(position)) {
position = value;
}
// next target value will not be animated to
if (target === null) {
target = value;
position = value;
}
// let start moving to target
target = value;
// already at target
if (position === target || typeof target === 'undefined') {
// now resting as target is current position, stop moving
resting = true;
velocity = 0;
// done!
api.onupdate(position);
api.oncomplete(position);
return;
}
resting = false;
};
// need 'api' to call onupdate callback
const api = createObject({
interpolate,
target: {
set: setTarget,
get: () => target,
},
resting: {
get: () => resting,
},
onupdate: value => {},
oncomplete: value => {},
});
return api;
};
const easeLinear = t => t;
const easeInOutQuad = t => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);
const tween =
// default values
({ duration = 500, easing = easeInOutQuad, delay = 0 } = {}) =>
// method definition
{
let start = null;
let t;
let p;
let resting = true;
let reverse = false;
let target = null;
const interpolate = (ts, skipToEndState) => {
if (resting || target === null) return;
if (start === null) {
start = ts;
}
if (ts - start < delay) return;
t = ts - start - delay;
if (t >= duration || skipToEndState) {
t = 1;
p = reverse ? 0 : 1;
api.onupdate(p * target);
api.oncomplete(p * target);
resting = true;
} else {
p = t / duration;
api.onupdate((t >= 0 ? easing(reverse ? 1 - p : p) : 0) * target);
}
};
// need 'api' to call onupdate callback
const api = createObject({
interpolate,
target: {
get: () => (reverse ? 0 : target),
set: value => {
// is initial value
if (target === null) {
target = value;
api.onupdate(value);
api.oncomplete(value);
return;
}
// want to tween to a smaller value and have a current value
if (value < target) {
target = 1;
reverse = true;
} else {
// not tweening to a smaller value
reverse = false;
target = value;
}
// let's go!
resting = false;
start = null;
},
},
resting: {
get: () => resting,
},
onupdate: value => {},
oncomplete: value => {},
});
return api;
};
const animator = {
spring,
tween,
};
/*
{ type: 'spring', stiffness: .5, damping: .75, mass: 10 };
{ translation: { type: 'spring', ... }, ... }
{ translation: { x: { type: 'spring', ... } } }
*/
const createAnimator = (definition, category, property) => {
// default is single definition
// we check if transform is set, if so, we check if property is set
const def =
definition[category] && typeof definition[category][property] === 'object'
? definition[category][property]
: definition[category] || definition;
const type = typeof def === 'string' ? def : def.type;
const props = typeof def === 'object' ? { ...def } : {};
return animator[type] ? animator[type](props) : null;
};
const addGetSet = (keys, obj, props, overwrite = false) => {
obj = Array.isArray(obj) ? obj : [obj];
obj.forEach(o => {
keys.forEach(key => {
let name = key;
let getter = () => props[key];
let setter = value => (props[key] = value);
if (typeof key === 'object') {
name = key.key;
getter = key.getter || getter;
setter = key.setter || setter;
}
if (o[name] && !overwrite) {
return;
}
o[name] = {
get: getter,
set: setter,
};
});
});
};
// add to state,
// add getters and setters to internal and external api (if not set)
// setup animators
const animations = ({ mixinConfig, viewProps, viewInternalAPI, viewExternalAPI }) => {
// initial properties
const initialProps = { ...viewProps };
// list of all active animations
const animations = [];
// setup animators
forin(mixinConfig, (property, animation) => {
const animator = createAnimator(animation);
if (!animator) {
return;
}
// when the animator updates, update the view state value
animator.onupdate = value => {
viewProps[property] = value;
};
// set animator target
animator.target = initialProps[property];
// when value is set, set the animator target value
const prop = {
key: property,
setter: value => {
// if already at target, we done!
if (animator.target === value) {
return;
}
animator.target = value;
},
getter: () => viewProps[property],
};
// add getters and setters
addGetSet([prop], [viewInternalAPI, viewExternalAPI], viewProps, true);
// add it to the list for easy updating from the _write method
animations.push(animator);
});
// expose internal write api
return {
write: ts => {
let skipToEndState = document.hidden;
let resting = true;
animations.forEach(animation => {
if (!animation.resting) resting = false;
animation.interpolate(ts, skipToEndState);
});
return resting;
},
destroy: () => {},
};
};
const addEvent = element => (type, fn) => {
element.addEventListener(type, fn);
};
const removeEvent = element => (type, fn) => {
element.removeEventListener(type, fn);
};
// mixin
const listeners = ({
mixinConfig,
viewProps,
viewInternalAPI,
viewExternalAPI,
viewState,
view,
}) => {
const events = [];
const add = addEvent(view.element);
const remove = removeEvent(view.element);
viewExternalAPI.on = (type, fn) => {
events.push({
type,
fn,
});
add(type, fn);
};
viewExternalAPI.off = (type, fn) => {
events.splice(events.findIndex(event => event.type === type && event.fn === fn), 1);
remove(type, fn);
};
return {
write: () => {
// not busy
return true;
},
destroy: () => {
events.forEach(event => {
remove(event.type, event.fn);
});
},
};
};
// add to external api and link to props
const apis = ({ mixinConfig, viewProps, viewExternalAPI }) => {
addGetSet(mixinConfig, viewExternalAPI, viewProps);
};
const isDefined = value => value != null;
// add to state,
// add getters and setters to internal and external api (if not set)
// set initial state based on props in viewProps
// apply as transforms each frame
const defaults = {
opacity: 1,
scaleX: 1,
scaleY: 1,
translateX: 0,
translateY: 0,
rotateX: 0,
rotateY: 0,
rotateZ: 0,
originX: 0,
originY: 0,
};
const styles = ({ mixinConfig, viewProps, viewInternalAPI, viewExternalAPI, view }) => {
// initial props
const initialProps = { ...viewProps };
// current props
const currentProps = {};
// we will add those properties to the external API and link them to the viewState
addGetSet(mixinConfig, [viewInternalAPI, viewExternalAPI], viewProps);
// override rect on internal and external rect getter so it takes in account transforms
const getOffset = () => [viewProps['translateX'] || 0, viewProps['translateY'] || 0];
const getScale = () => [viewProps['scaleX'] || 0, viewProps['scaleY'] || 0];
const getRect = () =>
view.rect ? getViewRect(view.rect, view.childViews, getOffset(), getScale()) : null;
viewInternalAPI.rect = { get: getRect };
viewExternalAPI.rect = { get: getRect };
// apply view props
mixinConfig.forEach(key => {
viewProps[key] =
typeof initialProps[key] === 'undefined' ? defaults[key] : initialProps[key];
});
// expose api
return {
write: () => {
// see if props have changed
if (!propsHaveChanged(currentProps, viewProps)) {
return;
}
// moves element to correct position on screen
applyStyles(view.element, viewProps);
// store new transforms
Object.assign(currentProps, { ...viewProps });
// no longer busy
return true;
},
destroy: () => {},
};
};
const propsHaveChanged = (currentProps, newProps) => {
// different amount of keys
if (Object.keys(currentProps).length !== Object.keys(newProps).length) {
return true;
}
// lets analyze the individual props
for (const prop in newProps) {
if (newProps[prop] !== currentProps[prop]) {
return true;
}
}
return false;
};
const applyStyles = (
element,
{
opacity,
perspective,
translateX,
translateY,
scaleX,
scaleY,
rotateX,
rotateY,
rotateZ,
originX,
originY,
width,
height,
}
) => {
let transforms = '';
let styles = '';
// handle transform origin
if (isDefined(originX) || isDefined(originY)) {
styles += `transform-origin: ${originX || 0}px ${originY || 0}px;`;
}
// transform order is relevant
// 0. perspective
if (isDefined(perspective)) {
transforms += `perspective(${perspective}px) `;
}
// 1. translate
if (isDefined(translateX) || isDefined(translateY)) {
transforms += `translate3d(${translateX || 0}px, ${translateY || 0}px, 0) `;
}
// 2. scale
if (isDefined(scaleX) || isDefined(scaleY)) {
transforms += `scale3d(${isDefined(scaleX) ? scaleX : 1}, ${
isDefined(scaleY) ? scaleY : 1
}, 1) `;
}
// 3. rotate
if (isDefined(rotateZ)) {
transforms += `rotateZ(${rotateZ}rad) `;
}
if (isDefined(rotateX)) {
transforms += `rotateX(${rotateX}rad) `;
}
if (isDefined(rotateY)) {
transforms += `rotateY(${rotateY}rad) `;
}
// add transforms
if (transforms.length) {
styles += `transform:${transforms};`;
}
// add opacity
if (isDefined(opacity)) {
styles += `opacity:${opacity};`;
// if we reach zero, we make the element inaccessible
if (opacity === 0) {
styles += `visibility:hidden;`;
}
// if we're below 100% opacity this element can't be clicked
if (opacity < 1) {
styles += `pointer-events:none;`;
}
}
// add height
if (isDefined(height)) {
styles += `height:${height}px;`;
}
// add width
if (isDefined(width)) {
styles += `width:${width}px;`;
}
// apply styles
const elementCurrentStyle = element.elementCurrentStyle || '';
// if new styles does not match current styles, lets update!
if (styles.length !== elementCurrentStyle.length || styles !== elementCurrentStyle) {
element.style.cssText = styles;
// store current styles so we can compare them to new styles later on
// _not_ getting the style value is faster
element.elementCurrentStyle = styles;
}
};
const Mixins = {
styles,
listeners,
animations,
apis,
};
const updateRect = (rect = {}, element = {}, style = {}) => {
if (!element.layoutCalculated) {
rect.paddingTop = parseInt(style.paddingTop, 10) || 0;
rect.marginTop = parseInt(style.marginTop, 10) || 0;
rect.marginRight = parseInt(style.marginRight, 10) || 0;
rect.marginBottom = parseInt(style.marginBottom, 10) || 0;
rect.marginLeft = parseInt(style.marginLeft, 10) || 0;
element.layoutCalculated = true;
}
rect.left = element.offsetLeft || 0;
rect.top = element.offsetTop || 0;
rect.width = element.offsetWidth || 0;
rect.height = element.offsetHeight || 0;
rect.right = rect.left + rect.width;
rect.bottom = rect.top + rect.height;
rect.scrollTop = element.scrollTop;
rect.hidden = element.offsetParent === null;
return rect;
};
const createView =
// default view definition
({
// element definition
tag = 'div',
name = null,
attributes = {},
// view interaction
read = () => {},
write = () => {},
create = () => {},
destroy = () => {},
// hooks
filterFrameActionsForChild = (child, actions) => actions,
didCreateView = () => {},
didWriteView = () => {},
// rect related
ignoreRect = false,
ignoreRectUpdate = false,
// mixins
mixins = [],
} = {}) => (
// each view requires reference to store
store,
// specific properties for this view
props = {}
) => {
// root element should not be changed
const element = createElement(tag, `filepond--${name}`, attributes);
// style reference should also not be changed
const style = window.getComputedStyle(element, null);
// element rectangle
const rect = updateRect();
let frameRect = null;
// rest state
let isResting = false;
// pretty self explanatory
const childViews = [];
// loaded mixins
const activeMixins = [];
// references to created children
const ref = {};
// state used for each instance
const state = {};
// list of writers that will be called to update this view
const writers = [
write, // default writer
];
const readers = [
read, // default reader
];
const destroyers = [
destroy, // default destroy
];
// core view methods
const getElement = () => element;
const getChildViews = () => childViews.concat();
const getReference = () => ref;
const createChildView = store => (view, props) => view(store, props);
const getRect = () => {
if (frameRect) {
return frameRect;
}
frameRect = getViewRect(rect, childViews, [0, 0], [1, 1]);
return frameRect;
};
const getStyle = () => style;
/**
* Read data from DOM
* @private
*/
const _read = () => {
frameRect = null;
// read child views
childViews.forEach(child => child._read());
const shouldUpdate = !(ignoreRectUpdate && rect.width && rect.height);
if (shouldUpdate) {
updateRect(rect, element, style);
}
// readers
const api = { root: internalAPI, props, rect };
readers.forEach(reader => reader(api));
};
/**
* Write data to DOM
* @private
*/
const _write = (ts, frameActions, shouldOptimize) => {
// if no actions, we assume that the view is resting
let resting = frameActions.length === 0;
// writers
writers.forEach(writer => {
const writerResting = writer({
props,
root: internalAPI,
actions: frameActions,
timestamp: ts,
shouldOptimize,
});
if (writerResting === false) {
resting = false;
}
});
// run mixins
activeMixins.forEach(mixin => {
// if one of the mixins is still busy after write operation, we are not resting
const mixinResting = mixin.write(ts);
if (mixinResting === false) {
resting = false;
}
});
// updates child views that are currently attached to the DOM
childViews
.filter(child => !!child.element.parentNode)
.forEach(child => {
// if a child view is not resting, we are not resting
const childResting = child._write(
ts,
filterFrameActionsForChild(child, frameActions),
shouldOptimize
);
if (!childResting) {
resting = false;
}
});
// append new elements to DOM and update those
childViews
//.filter(child => !child.element.parentNode)
.forEach((child, index) => {
// skip
if (child.element.parentNode) {
return;
}
// append to DOM
internalAPI.appendChild(child.element, index);
// call read (need to know the size of these elements)
child._read();
// re-call write
child._write(
ts,
filterFrameActionsForChild(child, frameActions),
shouldOptimize
);
// we just added somthing to the dom, no rest
resting = false;
});
// update resting state
isResting = resting;
didWriteView({
props,
root: internalAPI,
actions: frameActions,
timestamp: ts,
});
// let parent know if we are resting
return resting;
};
const _destroy = () => {
activeMixins.forEach(mixin => mixin.destroy());
destroyers.forEach(destroyer => {
destroyer({ root: internalAPI, props });
});
childViews.forEach(child => child._destroy());
};
// sharedAPI
const sharedAPIDefinition = {
element: {
get: getElement,
},
style: {
get: getStyle,
},
childViews: {
get: getChildViews,
},
};
// private API definition
const internalAPIDefinition = {
...sharedAPIDefinition,
rect: {
get: getRect,
},
// access to custom children references
ref: {
get: getReference,
},
// dom modifiers
is: needle => name === needle,
appendChild: appendChild(element),
createChildView: createChildView(store),
linkView: view => {
childViews.push(view);
return view;
},
unlinkView: view => {
childViews.splice(childViews.indexOf(view), 1);
},
appendChildView: appendChildView(element, childViews),
removeChildView: removeChildView(element, childViews),
registerWriter: writer => writers.push(writer),
registerReader: reader => readers.push(reader),
registerDestroyer: destroyer => destroyers.push(destroyer),
invalidateLayout: () => (element.layoutCalculated = false),
// access to data store
dispatch: store.dispatch,
query: store.query,
};
// public view API methods
const externalAPIDefinition = {
element: {
get: getElement,
},
childViews: {
get: getChildViews,
},
rect: {
get: getRect,
},
resting: {
get: () => isResting,
},
isRectIgnored: () => ignoreRect,
_read,
_write,
_destroy,
};
// mixin API methods
const mixinAPIDefinition = {
...sharedAPIDefinition,
rect: {
get: () => rect,
},
};
// add mixin functionality
Object.keys(mixins)
.sort((a, b) => {
// move styles to the back of the mixin list (so adjustments of other mixins are applied to the props correctly)
if (a === 'styles') {
return 1;
} else if (b === 'styles') {
return -1;
}
return 0;
})
.forEach(key => {
const mixinAPI = Mixins[key]({
mixinConfig: mixins[key],
viewProps: props,
viewState: state,
viewInternalAPI: internalAPIDefinition,
viewExternalAPI: externalAPIDefinition,
view: createObject(mixinAPIDefinition),
});
if (mixinAPI) {
activeMixins.push(mixinAPI);
}
});
// construct private api
const internalAPI = createObject(internalAPIDefinition);
// create the view
create({
root: internalAPI,
props,
});
// append created child views to root node
const childCount = getChildCount(element); // need to know the current child count so appending happens in correct order
childViews.forEach((child, index) => {
internalAPI.appendChild(child.element, childCount + index);
});
// call did create
didCreateView(internalAPI);
// expose public api
return createObject(externalAPIDefinition);
};
const createPainter = (read, write, fps = 60) => {
const name = '__framePainter';
// set global painter
if (window[name]) {
window[name].readers.push(read);
window[name].writers.push(write);
return;
}
window[name] = {
readers: [read],
writers: [write],
};
const painter = window[name];
const interval = 1000 / fps;
let last = null;
let id = null;
let requestTick = null;
let cancelTick = null;
const setTimerType = () => {
if (document.hidden) {
requestTick = () => window.setTimeout(() => tick(performance.now()), interval);
cancelTick = () => window.clearTimeout(id);
} else {
requestTick = () => window.requestAnimationFrame(tick);
cancelTick = () => window.cancelAnimationFrame(id);
}
};
document.addEventListener('visibilitychange', () => {
if (cancelTick) cancelTick();
setTimerType();
tick(performance.now());
});
const tick = ts => {
// queue next tick
id = requestTick(tick);
// limit fps
if (!last) {
last = ts;
}
const delta = ts - last;
if (delta <= interval) {
// skip frame
return;
}
// align next frame
last = ts - (delta % interval);
// update view
painter.readers.forEach(read => read());
painter.writers.forEach(write => write(ts));
};
setTimerType();
tick(performance.now());
return {
pause: () => {
cancelTick(id);
},
};
};
const createRoute = (routes, fn) => ({ root, props, actions = [], timestamp, shouldOptimize }) => {
actions
.filter(action => routes[action.type])
.forEach(action =>
routes[action.type]({ root, props, action: action.data, timestamp, shouldOptimize })
);
if (fn) {
fn({ root, props, actions, timestamp, shouldOptimize });
}
};
const insertBefore = (newNode, referenceNode) =>
referenceNode.parentNode.insertBefore(newNode, referenceNode);
const insertAfter = (newNode, referenceNode) => {
return referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
};
const isArray = value => Array.isArray(value);
const isEmpty = value => value == null;
const trim = str => str.trim();
const toString = value => '' + value;
const toArray = (value, splitter = ',') => {
if (isEmpty(value)) {
return [];
}
if (isArray(value)) {
return value;
}
return toString(value)
.split(splitter)
.map(trim)
.filter(str => str.length);
};
const isBoolean = value => typeof value === 'boolean';
const toBoolean = value => (isBoolean(value) ? value : value === 'true');
const isString = value => typeof value === 'string';
const toNumber = value =>
isNumber(value) ? value : isString(value) ? toString(value).replace(/[a-z]+/gi, '') : 0;
const toInt = value => parseInt(toNumber(value), 10);
const toFloat = value => parseFloat(toNumber(value));
const isInt = value => isNumber(value) && isFinite(value) && Math.floor(value) === value;
const toBytes = (value, base = 1000) => {
// is in bytes
if (isInt(value)) {
return value;
}
// is natural file size
let naturalFileSize = toString(value).trim();
// if is value in megabytes
if (/MB$/i.test(naturalFileSize)) {
naturalFileSize = naturalFileSize.replace(/MB$i/, '').trim();
return toInt(naturalFileSize) * base * base;
}
// if is value in kilobytes
if (/KB/i.test(naturalFileSize)) {
naturalFileSize = naturalFileSize.replace(/KB$i/, '').trim();
return toInt(naturalFileSize) * base;
}
return toInt(naturalFileSize);
};
const isFunction = value => typeof value === 'function';
const toFunctionReference = string => {
let ref = self;
let levels = string.split('.');
let level = null;
while ((level = levels.shift())) {
ref = ref[level];
if (!ref) {
return null;
}
}
return ref;
};
const methods = {
process: 'POST',
patch: 'PATCH',
revert: 'DELETE',
fetch: 'GET',
restore: 'GET',
load: 'GET',
};
const createServerAPI = outline => {
const api = {};
api.url = isString(outline) ? outline : outline.url || '';
api.timeout = outline.timeout ? parseInt(outline.timeout, 10) : 0;
api.headers = outline.headers ? outline.headers : {};
forin(methods, key => {
api[key] = createAction(key, outline[key], methods[key], api.timeout, api.headers);
});
// remove process if no url or process on outline
api.process = outline.process || isString(outline) || outline.url ? api.process : null;
// special treatment for remove
api.remove = outline.remove || null;
// remove generic headers from api object
delete api.headers;
return api;
};
const createAction = (name, outline, method, timeout, headers) => {
// is explicitely set to null so disable
if (outline === null) {
return null;
}
// if is custom function, done! Dev handles everything.
if (typeof outline === 'function') {
return outline;
}
// build action object
const action = {
url: method === 'GET' || method === 'PATCH' ? `?${name}=` : '',
method,
headers,
withCredentials: false,
timeout,
onload: null,
ondata: null,
onerror: null,
};
// is a single url
if (isString(outline)) {
action.url = outline;
return action;
}
// overwrite
Object.assign(action, outline);
// see if should reformat headers;
if (isString(action.headers)) {
const parts = action.headers.split(/:(.+)/);
action.headers = {
header: parts[0],
value: parts[1],
};
}
// if is bool withCredentials
action.withCredentials = toBoolean(action.withCredentials);
return action;
};
const toServerAPI = value => createServerAPI(value);
const isNull = value => value === null;
const isObject = value => typeof value === 'object' && value !== null;
const isAPI = value => {
return (
isObject(value) &&
isString(value.url) &&
isObject(value.process) &&
isObject(value.revert) &&
isObject(value.restore) &&
isObject(value.fetch)
);
};
const getType = value => {
if (isArray(value)) {
return 'array';
}
if (isNull(value)) {
return 'null';
}
if (isInt(value)) {
return 'int';
}
if (/^[0-9]+ ?(?:GB|MB|KB)$/gi.test(value)) {
return 'bytes';
}
if (isAPI(value)) {
return 'api';
}
return typeof value;
};
const replaceSingleQuotes = str =>
str
.replace(/{\s*'/g, '{"')
.replace(/'\s*}/g, '"}')
.replace(/'\s*:/g, '":')
.replace(/:\s*'/g, ':"')
.replace(/,\s*'/g, ',"')
.replace(/'\s*,/g, '",');
const conversionTable = {
array: toArray,
boolean: toBoolean,
int: value => (getType(value) === 'bytes' ? toBytes(value) : toInt(value)),
number: toFloat,
float: toFloat,
bytes: toBytes,
string: value => (isFunction(value) ? value : toString(value)),
function: value => toFunctionReference(value),
serverapi: toServerAPI,
object: value => {
try {
return JSON.parse(replaceSingleQuotes(value));
} catch (e) {
return null;
}
},
};
const convertTo = (value, type) => conversionTable[type](value);
const getValueByType = (newValue, defaultValue, valueType) => {
// can always assign default value
if (newValue === defaultValue) {
return newValue;
}
// get the type of the new value
let newValueType = getType(newValue);
// is valid type?
if (newValueType !== valueType) {
// is string input, let's attempt to convert
const convertedValue = convertTo(newValue, valueType);
// what is the type now
newValueType = getType(convertedValue);
// no valid conversions found
if (convertedValue === null) {
throw `Trying to assign value with incorrect type to "${option}", allowed type: "${valueType}"`;
} else {
newValue = convertedValue;
}
}
// assign new value
return newValue;
};
const createOption = (defaultValue, valueType) => {
let currentValue = defaultValue;
return {
enumerable: true,
get: () => currentValue,
set: newValue => {
currentValue = getValueByType(newValue, defaultValue, valueType);
},
};
};
const createOptions = options => {
const obj = {};
forin(options, prop => {
const optionDefinition = options[prop];
obj[prop] = createOption(optionDefinition[0], optionDefinition[1]);
});
return createObject(obj);
};
const createInitialState = options => ({
// model
items: [],
// timeout used for calling update items
listUpdateTimeout: null,
// timeout used for stacking metadata updates
itemUpdateTimeout: null,
// queue of items waiting to be processed
processingQueue: [],
// options
options: createOptions(options),
});
const fromCamels = (string, separator = '-') =>
string
.split(/(?=[A-Z])/)
.map(part => part.toLowerCase())
.join(separator);
const createOptionAPI = (store, options) => {
const obj = {};
forin(options, key => {
obj[key] = {
get: () => store.getState().options[key],
set: value => {
store.dispatch(`SET_${fromCamels(key, '_').toUpperCase()}`, {
value,
});
},
};
});
return obj;
};
const createOptionActions = options => (dispatch, query, state) => {
const obj = {};
forin(options, key => {
const name = fromCamels(key, '_').toUpperCase();
obj[`SET_${name}`] = action => {
try {
state.options[key] = action.value;
} catch (e) {
// nope, failed
}
// we successfully set the value of this option
dispatch(`DID_SET_${name}`, { value: state.options[key] });
};
});
return obj;
};
const createOptionQueries = options => state => {
const obj = {};
forin(options, key => {
obj[`GET_${fromCamels(key, '_').toUpperCase()}`] = action => state.options[key];
});
return obj;
};
const InteractionMethod = {
API: 1,
DROP: 2,
BROWSE: 3,
PASTE: 4,
NONE: 5,
};
const getUniqueId = () =>
Math.random()
.toString(36)
.substring(2, 11);
const arrayRemove = (arr, index) => arr.splice(index, 1);
const run = (cb, sync) => {
if (sync) {
cb();
} else if (document.hidden) {
Promise.resolve(1).then(cb);
} else {
setTimeout(cb, 0);
}
};
const on = () => {
const listeners = [];
const off = (event, cb) => {
arrayRemove(
listeners,
listeners.findIndex(listener => listener.event === event && (listener.cb === cb || !cb))
);
};
const fire = (event, args, sync) => {
listeners
.filter(listener => listener.event === event)
.map(listener => listener.cb)
.forEach(cb => run(() => cb(...args), sync));
};
return {
fireSync: (event, ...args) => {
fire(event, args, true);
},
fire: (event, ...args) => {
fire(event, args, false);
},
on: (event, cb) => {
listeners.push({ event, cb });
},
onOnce: (event, cb) => {
listeners.push({
event,
cb: (...args) => {
off(event, cb);
cb(...args);
},
});
},
off,
};
};
const copyObjectPropertiesToObject = (src, target, excluded) => {
Object.getOwnPropertyNames(src)
.filter(property => !excluded.includes(property))
.forEach(key =>
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(src, key))
);
};
const PRIVATE = [
'fire',
'process',
'revert',
'load',
'on',
'off',
'onOnce',
'retryLoad',
'extend',
'archive',
'archived',
'release',
'released',
'requestProcessing',
'freeze',
];
const createItemAPI = item => {
const api = {};
copyObjectPropertiesToObject(item, api, PRIVATE);
return api;
};
const removeReleasedItems = items => {
items.forEach((item, index) => {
if (item.released) {
arrayRemove(items, index);
}
});
};
const ItemStatus = {
INIT: 1,
IDLE: 2,
PROCESSING_QUEUED: 9,
PROCESSING: 3,
PROCESSING_COMPLETE: 5,
PROCESSING_ERROR: 6,
PROCESSING_REVERT_ERROR: 10,
LOADING: 7,
LOAD_ERROR: 8,
};
const FileOrigin = {
INPUT: 1,
LIMBO: 2,
LOCAL: 3,
};
const getNonNumeric = str => /[^0-9]+/.exec(str);
const getDecimalSeparator = () => getNonNumeric((1.1).toLocaleString())[0];
const getThousandsSeparator = () => {
// Added for browsers that do not return the thousands separator (happend on native browser Android 4.4.4)
// We check against the normal toString output and if they're the same return a comma when decimal separator is a dot
const decimalSeparator = getDecimalSeparator();
const thousandsStringWithSeparator = (1000.0).toLocaleString();
const thousandsStringWithoutSeparator = (1000.0).toString();
if (thousandsStringWithSeparator !== thousandsStringWithoutSeparator) {
return getNonNumeric(thousandsStringWithSeparator)[0];
}
return decimalSeparator === '.' ? ',' : '.';
};
const Type = {
BOOLEAN: 'boolean',
INT: 'int',
NUMBER: 'number',
STRING: 'string',
ARRAY: 'array',
OBJECT: 'object',
FUNCTION: 'function',
ACTION: 'action',
SERVER_API: 'serverapi',
REGEX: 'regex',
};
// all registered filters
const filters = [];
// loops over matching filters and passes options to each filter, returning the mapped results
const applyFilterChain = (key, value, utils) =>
new Promise((resolve, reject) => {
// find matching filters for this key
const matchingFilters = filters.filter(f => f.key === key).map(f => f.cb);
// resolve now
if (matchingFilters.length === 0) {
resolve(value);
return;
}
// first filter to kick things of
const initialFilter = matchingFilters.shift();
// chain filters
matchingFilters
.reduce(
// loop over promises passing value to next promise
(current, next) => current.then(value => next(value, utils)),
// call initial filter, will return a promise
initialFilter(value, utils)
// all executed
)
.then(value => resolve(value))
.catch(error => reject(error));
});
const applyFilters = (key, value, utils) =>
filters.filter(f => f.key === key).map(f => f.cb(value, utils));
// adds a new filter to the list
const addFilter = (key, cb) => filters.push({ key, cb });
const extendDefaultOptions = additionalOptions => Object.assign(defaultOptions, additionalOptions);
const getOptions = () => ({ ...defaultOptions });
const setOptions = opts => {
forin(opts, (key, value) => {
// key does not exist, so this option cannot be set
if (!defaultOptions[key]) {
return;
}
defaultOptions[key][0] = getValueByType(
value,
defaultOptions[key][0],
defaultOptions[key][1]
);
});
};
// default options on app
const defaultOptions = {
// the id to add to the root element
id: [null, Type.STRING],
// input field name to use
name: ['filepond', Type.STRING],
// disable the field
disabled: [false, Type.BOOLEAN],
// classname to put on wrapper
className: [null, Type.STRING],
// is the field required
required: [false, Type.BOOLEAN],
// Allow media capture when value is set
captureMethod: [null, Type.STRING],
// - "camera", "microphone" or "camcorder",
// - Does not work with multiple on apple devices
// - If set, acceptedFileTypes must be made to match with media wildcard "image/*", "audio/*" or "video/*"
// sync `acceptedFileTypes` property with `accept` attribute
allowSyncAcceptAttribute: [true, Type.BOOLEAN],
// Feature toggles
allowDrop: [true, Type.BOOLEAN], // Allow dropping of files
allowBrowse: [true, Type.BOOLEAN], // Allow browsing the file system
allowPaste: [true, Type.BOOLEAN], // Allow pasting files
allowMultiple: [false, Type.BOOLEAN], // Allow multiple files (disabled by default, as multiple attribute is also required on input to allow multiple)
allowReplace: [true, Type.BOOLEAN], // Allow dropping a file on other file to replace it (only works when multiple is set to false)
allowRevert: [true, Type.BOOLEAN], // Allows user to revert file upload
allowRemove: [true, Type.BOOLEAN], // Allow user to remove a file
allowProcess: [true, Type.BOOLEAN], // Allows user to process a file, when set to false, this removes the file upload button
allowReorder: [false, Type.BOOLEAN], // Allow reordering of files
allowDirectoriesOnly: [false, Type.BOOLEAN], // Allow only selecting directories with browse (no support for filtering dnd at this point)
// Try store file if `server` not set
storeAsFile: [false, Type.BOOLEAN],
// Revert mode
forceRevert: [false, Type.BOOLEAN], // Set to 'force' to require the file to be reverted before removal
// Input requirements
maxFiles: [null, Type.INT], // Max number of files
checkValidity: [false, Type.BOOLEAN], // Enables custom validity messages
// Where to put file
itemInsertLocationFreedom: [true, Type.BOOLEAN], // Set to false to always add items to begin or end of list
itemInsertLocation: ['before', Type.STRING], // Default index in list to add items that have been dropped at the top of the list
itemInsertInterval: [75, Type.INT],
// Drag 'n Drop related
dropOnPage: [false, Type.BOOLEAN], // Allow dropping of files anywhere on page (prevents browser from opening file if dropped outside of Up)
dropOnElement: [true, Type.BOOLEAN], // Drop needs to happen on element (set to false to also load drops outside of Up)
dropValidation: [false, Type.BOOLEAN], // Enable or disable validating files on drop
ignoredFiles: [['.ds_store', 'thumbs.db', 'desktop.ini'], Type.ARRAY],
// Upload related
instantUpload: [true, Type.BOOLEAN], // Should upload files immediately on drop
maxParallelUploads: [2, Type.INT], // Maximum files to upload in parallel
allowMinimumUploadDuration: [true, Type.BOOLEAN], // if true uploads take at least 750 ms, this ensures the user sees the upload progress giving trust the upload actually happened
// Chunks
chunkUploads: [false, Type.BOOLEAN], // Enable chunked uploads
chunkForce: [false, Type.BOOLEAN], // Force use of chunk uploads even for files smaller than chunk size
chunkSize: [5000000, Type.INT], // Size of chunks (5MB default)
chunkRetryDelays: [[500, 1000, 3000], Type.ARRAY], // Amount of times to retry upload of a chunk when it fails
// The server api end points to use for uploading (see docs)
server: [null, Type.SERVER_API],
// File size calculations, can set to 1024, this is only used for display, properties use file size base 1000
fileSizeBase: [1000, Type.INT],
// Labels and status messages
labelFileSizeBytes: ['bytes', Type.STRING],
labelFileSizeKilobytes: ['KB', Type.STRING],
labelFileSizeMegabytes: ['MB', Type.STRING],
labelFileSizeGigabytes: ['GB', Type.STRING],
labelDecimalSeparator: [getDecimalSeparator(), Type.STRING], // Default is locale separator
labelThousandsSeparator: [getThousandsSeparator(), Type.STRING], // Default is locale separator
labelIdle: [
'Drag & Drop your files or <span class="filepond--label-action">Browse</span>',
Type.STRING,
],
labelInvalidField: ['Field contains invalid files', Type.STRING],
labelFileWaitingForSize: ['Waiting for size', Type.STRING],
labelFileSizeNotAvailable: ['Size not available', Type.STRING],
labelFileCountSingular: ['file in list', Type.STRING],
labelFileCountPlural: ['files in list', Type.STRING],
labelFileLoading: ['Loading', Type.STRING],
labelFileAdded: ['Added', Type.STRING], // assistive only
labelFileLoadError: ['Error during load', Type.STRING],
labelFileRemoved: ['Removed', Type.STRING], // assistive only
labelFileRemoveError: ['Error during remove', Type.STRING],
labelFileProcessing: ['Uploading', Type.STRING],
labelFileProcessingComplete: ['Upload complete', Type.STRING],
labelFileProcessingAborted: ['Upload cancelled', Type.STRING],
labelFileProcessingError: ['Error during upload', Type.STRING],
labelFileProcessingRevertError: ['Error during revert', Type.STRING],
labelTapToCancel: ['tap to cancel', Type.STRING],
labelTapToRetry: ['tap to retry', Type.STRING],
labelTapToUndo: ['tap to undo', Type.STRING],
labelButtonRemoveItem: ['Remove', Type.STRING],
labelButtonAbortItemLoad: ['Abort', Type.STRING],
labelButtonRetryItemLoad: ['Retry', Type.STRING],
labelButtonAbortItemProcessing: ['Cancel', Type.STRING],
labelButtonUndoItemProcessing: ['Undo', Type.STRING],
labelButtonRetryItemProcessing: ['Retry', Type.STRING],
labelButtonProcessItem: ['Upload', Type.STRING],
// make sure width and height plus viewpox are even numbers so icons are nicely centered
iconRemove: [
'<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg"><path d="M11.586 13l-2.293 2.293a1 1 0 0 0 1.414 1.414L13 14.414l2.293 2.293a1 1 0 0 0 1.414-1.414L14.414 13l2.293-2.293a1 1 0 0 0-1.414-1.414L13 11.586l-2.293-2.293a1 1 0 0 0-1.414 1.414L11.586 13z" fill="currentColor" fill-rule="nonzero"/></svg>',
Type.STRING,
],
iconProcess: [
'<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg"><path d="M14 10.414v3.585a1 1 0 0 1-2 0v-3.585l-1.293 1.293a1 1 0 0 1-1.414-1.415l3-3a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1-1.414 1.415L14 10.414zM9 18a1 1 0 0 1 0-2h8a1 1 0 0 1 0 2H9z" fill="currentColor" fill-rule="evenodd"/></svg>',
Type.STRING,
],
iconRetry: [
'<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg"><path d="M10.81 9.185l-.038.02A4.997 4.997 0 0 0 8 13.683a5 5 0 0 0 5 5 5 5 0 0 0 5-5 1 1 0 0 1 2 0A7 7 0 1 1 9.722 7.496l-.842-.21a.999.999 0 1 1 .484-1.94l3.23.806c.535.133.86.675.73 1.21l-.804 3.233a.997.997 0 0 1-1.21.73.997.997 0 0 1-.73-1.21l.23-.928v-.002z" fill="currentColor" fill-rule="nonzero"/></svg>',
Type.STRING,
],
iconUndo: [
'<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg"><path d="M9.185 10.81l.02-.038A4.997 4.997 0 0 1 13.683 8a5 5 0 0 1 5 5 5 5 0 0 1-5 5 1 1 0 0 0 0 2A7 7 0 1 0 7.496 9.722l-.21-.842a.999.999 0 1 0-1.94.484l.806 3.23c.133.535.675.86 1.21.73l3.233-.803a.997.997 0 0 0 .73-1.21.997.997 0 0 0-1.21-.73l-.928.23-.002-.001z" fill="currentColor" fill-rule="nonzero"/></svg>',
Type.STRING,
],
iconDone: [
'<svg width="26" height="26" viewBox="0 0 26 26" xmlns="http://www.w3.org/2000/svg"><path d="M18.293 9.293a1 1 0 0 1 1.414 1.414l-7.002 7a1 1 0 0 1-1.414 0l-3.998-4a1 1 0 1 1 1.414-1.414L12 15.586l6.294-6.293z" fill="currentColor" fill-rule="nonzero"/></svg>',
Type.STRING,
],
// event handlers
oninit: [null, Type.FUNCTION],
onwarning: [null, Type.FUNCTION],
onerror: [null, Type.FUNCTION],
onactivatefile: [null, Type.FUNCTION],
oninitfile: [null, Type.FUNCTION],
onaddfilestart: [null, Type.FUNCTION],
onaddfileprogress: [null, Type.FUNCTION],
onaddfile: [null, Type.FUNCTION],
onprocessfilestart: [null, Type.FUNCTION],
onprocessfileprogress: [null, Type.FUNCTION],
onprocessfileabort: [null, Type.FUNCTION],
onprocessfilerevert: [null, Type.FUNCTION],
onprocessfile: [null, Type.FUNCTION],
onprocessfiles: [null, Type.FUNCTION],
onremovefile: [null, Type.FUNCTION],
onpreparefile: [null, Type.FUNCTION],
onupdatefiles: [null, Type.FUNCTION],
onreorderfiles: [null, Type.FUNCTION],
// hooks
beforeDropFile: [null, Type.FUNCTION],
beforeAddFile: [null, Type.FUNCTION],
beforeRemoveFile: [null, Type.FUNCTION],
beforePrepareFile: [null, Type.FUNCTION],
// styles
stylePanelLayout: [null, Type.STRING], // null 'integrated', 'compact', 'circle'
stylePanelAspectRatio: [null, Type.STRING], // null or '3:2' or 1
styleItemPanelAspectRatio: [null, Type.STRING],
styleButtonRemoveItemPosition: ['left', Type.STRING],
styleButtonProcessItemPosition: ['right', Type.STRING],
styleLoadIndicatorPosition: ['right', Type.STRING],
styleProgressIndicatorPosition: ['right', Type.STRING],
styleButtonRemoveItemAlign: [false, Type.BOOLEAN],
// custom initial files array
files: [[], Type.ARRAY],
// show support by displaying credits
credits: [['https://filepond.com', 'Powered by FilePond'], Type.ARRAY],
};
const getItemByQuery = (items, query) => {
// just return first index
if (isEmpty(query)) {
return items[0] || null;
}
// query is index
if (isInt(query)) {
return items[query] || null;
}
// if query is item, get the id
if (typeof query === 'object') {
query = query.id;
}
// assume query is a string and return item by id
return items.find(item => item.id === query) || null;
};
const getNumericAspectRatioFromString = aspectRatio => {
if (isEmpty(aspectRatio)) {
return aspectRatio;
}
if (/:/.test(aspectRatio)) {
const parts = aspectRatio.split(':');
return parts[1] / parts[0];
}
return parseFloat(aspectRatio);
};
const getActiveItems = items => items.filter(item => !item.archived);
const Status = {
EMPTY: 0,
IDLE: 1, // waiting
ERROR: 2, // a file is in error state
BUSY: 3, // busy processing or loading
READY: 4, // all files uploaded
};
let res = null;
const canUpdateFileInput = () => {
if (res === null) {
try {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(new File(['hello world'], 'This_Works.txt'));
const el = document.createElement('input');
el.setAttribute('type', 'file');
el.files = dataTransfer.files;
res = el.files.length === 1;
} catch (err) {
res = false;
}
}
return res;
};
const ITEM_ERROR = [
ItemStatus.LOAD_ERROR,
ItemStatus.PROCESSING_ERROR,
ItemStatus.PROCESSING_REVERT_ERROR,
];
const ITEM_BUSY = [
ItemStatus.LOADING,
ItemStatus.PROCESSING,
ItemStatus.PROCESSING_QUEUED,
ItemStatus.INIT,
];
const ITEM_READY = [ItemStatus.PROCESSING_COMPLETE];
const isItemInErrorState = item => ITEM_ERROR.includes(item.status);
const isItemInBusyState = item => ITEM_BUSY.includes(item.status);
const isItemInReadyState = item => ITEM_READY.includes(item.status);
const isAsync = state =>
isObject(state.options.server) &&
(isObject(state.options.server.process) || isFunction(state.options.server.process));
const queries = state => ({
GET_STATUS: () => {
const items = getActiveItems(state.items);
const { EMPTY, ERROR, BUSY, IDLE, READY } = Status;
if (items.length === 0) return EMPTY;
if (items.some(isItemInErrorState)) return ERROR;
if (items.some(isItemInBusyState)) return BUSY;
if (items.some(isItemInReadyState)) return READY;
return IDLE;
},
GET_ITEM: query => getItemByQuery(state.items, query),
GET_ACTIVE_ITEM: query => getItemByQuery(getActiveItems(state.items), query),
GET_ACTIVE_ITEMS: () => getActiveItems(state.items),
GET_ITEMS: () => state.items,
GET_ITEM_NAME: query => {
const item = getItemByQuery(state.items, query);
return item ? item.filename : null;
},
GET_ITEM_SIZE: query => {
const item = getItemByQuery(state.items, query);
return item ? item.fileSize : null;
},
GET_STYLES: () =>
Object.keys(state.options)
.filter(key => /^style/.test(key))
.map(option => ({
name: option,
value: state.options[option],
})),
GET_PANEL_ASPECT_RATIO: () => {
const isShapeCircle = /circle/.test(state.options.stylePanelLayout);
const aspectRatio = isShapeCircle
? 1
: getNumericAspectRatioFromString(state.options.stylePanelAspectRatio);
return aspectRatio;
},
GET_ITEM_PANEL_ASPECT_RATIO: () => state.options.styleItemPanelAspectRatio,
GET_ITEMS_BY_STATUS: status =>
getActiveItems(state.items).filter(item => item.status === status),
GET_TOTAL_ITEMS: () => getActiveItems(state.items).length,
SHOULD_UPDATE_FILE_INPUT: () =>
state.options.storeAsFile && canUpdateFileInput() && !isAsync(state),
IS_ASYNC: () => isAsync(state),
GET_FILE_SIZE_LABELS: query => ({
labelBytes: query('GET_LABEL_FILE_SIZE_BYTES') || undefined,
labelKilobytes: query('GET_LABEL_FILE_SIZE_KILOBYTES') || undefined,
labelMegabytes: query('GET_LABEL_FILE_SIZE_MEGABYTES') || undefined,
labelGigabytes: query('GET_LABEL_FILE_SIZE_GIGABYTES') || undefined,
}),
});
const hasRoomForItem = state => {
const count = getActiveItems(state.items).length;
// if cannot have multiple items, to add one item it should currently not contain items
if (!state.options.allowMultiple) {
return count === 0;
}
// if allows multiple items, we check if a max item count has been set, if not, there's no limit
const maxFileCount = state.options.maxFiles;
if (maxFileCount === null) {
return true;
}
// we check if the current count is smaller than the max count, if so, another file can still be added
if (count < maxFileCount) {
return true;
}
// no more room for another file
return false;
};
const limit = (value, min, max) => Math.max(Math.min(max, value), min);
const arrayInsert = (arr, index, item) => arr.splice(index, 0, item);
const insertItem = (items, item, index) => {
if (isEmpty(item)) {
return null;
}
// if index is undefined, append
if (typeof index === 'undefined') {
items.push(item);
return item;
}
// limit the index to the size of the items array
index = limit(index, 0, items.length);
// add item to array
arrayInsert(items, index, item);
// expose
return item;
};
const isBase64DataURI = str =>
/^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*)\s*$/i.test(
str
);
const getFilenameFromURL = url =>
`${url}`
.split('/')
.pop()
.split('?')
.shift();
const getExtensionFromFilename = name => name.split('.').pop();
const guesstimateExtension = type => {
// if no extension supplied, exit here
if (typeof type !== 'string') {
return '';
}
// get subtype
const subtype = type.split('/').pop();
// is svg subtype
if (/svg/.test(subtype)) {
return 'svg';
}
if (/zip|compressed/.test(subtype)) {
return 'zip';
}
if (/plain/.test(subtype)) {
return 'txt';
}
if (/msword/.test(subtype)) {
return 'doc';
}
// if is valid subtype
if (/[a-z]+/.test(subtype)) {
// always use jpg extension
if (subtype === 'jpeg') {
return 'jpg';
}
// return subtype
return subtype;
}
return '';
};
const leftPad = (value, padding = '') => (padding + value).slice(-padding.length);
const getDateString = (date = new Date()) =>
`${date.getFullYear()}-${leftPad(date.getMonth() + 1, '00')}-${leftPad(
date.getDate(),
'00'
)}_${leftPad(date.getHours(), '00')}-${leftPad(date.getMinutes(), '00')}-${leftPad(
date.getSeconds(),
'00'
)}`;
const getFileFromBlob = (blob, filename, type = null, extension = null) => {
const file =
typeof type === 'string'
? blob.slice(0, blob.size, type)
: blob.slice(0, blob.size, blob.type);
file.lastModifiedDate = new Date();
// copy relative path
if (blob._relativePath) file._relativePath = blob._relativePath;
// if blob has name property, use as filename if no filename supplied
if (!isString(filename)) {
filename = getDateString();
}
// if filename supplied but no extension and filename has extension
if (filename && extension === null && getExtensionFromFilename(filename)) {
file.name = filename;
} else {
extension = extension || guesstimateExtension(file.type);
file.name = filename + (extension ? '.' + extension : '');
}
return file;
};
const getBlobBuilder = () => {
return (window.BlobBuilder =
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder);
};
const createBlob = (arrayBuffer, mimeType) => {
const BB = getBlobBuilder();
if (BB) {
const bb = new BB();
bb.append(arrayBuffer);
return bb.getBlob(mimeType);
}
return new Blob([arrayBuffer], {
type: mimeType,
});
};
const getBlobFromByteStringWithMimeType = (byteString, mimeType) => {
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return createBlob(ab, mimeType);
};
const getMimeTypeFromBase64DataURI = dataURI => {
return (/^data:(.+);/.exec(dataURI) || [])[1] || null;
};
const getBase64DataFromBase64DataURI = dataURI => {
// get data part of string (remove data:image/jpeg...,)
const data = dataURI.split(',')[1];
// remove any whitespace as that causes InvalidCharacterError in IE
return data.replace(/\s/g, '');
};
const getByteStringFromBase64DataURI = dataURI => {
return atob(getBase64DataFromBase64DataURI(dataURI));
};
const getBlobFromBase64DataURI = dataURI => {
const mimeType = getMimeTypeFromBase64DataURI(dataURI);
const byteString = getByteStringFromBase64DataURI(dataURI);
return getBlobFromByteStringWithMimeType(byteString, mimeType);
};
const getFileFromBase64DataURI = (dataURI, filename, extension) => {
return getFileFromBlob(getBlobFromBase64DataURI(dataURI), filename, null, extension);
};
const getFileNameFromHeader = header => {
// test if is content disposition header, if not exit
if (!/^content-disposition:/i.test(header)) return null;
// get filename parts
const matches = header
.split(/filename=|filename\*=.+''/)
.splice(1)
.map(name => name.trim().replace(/^["']|[;"']{0,2}$/g, ''))
.filter(name => name.length);
return matches.length ? decodeURI(matches[matches.length - 1]) : null;
};
const getFileSizeFromHeader = header => {
if (/content-length:/i.test(header)) {
const size = header.match(/[0-9]+/)[0];
return size ? parseInt(size, 10) : null;
}
return null;
};
const getTranfserIdFromHeader = header => {
if (/x-content-transfer-id:/i.test(header)) {
const id = (header.split(':')[1] || '').trim();
return id || null;
}
return null;
};
const getFileInfoFromHeaders = headers => {
const info = {
source: null,
name: null,
size: null,
};
const rows = headers.split('\n');
for (let header of rows) {
const name = getFileNameFromHeader(header);
if (name) {
info.name = name;
continue;
}
const size = getFileSizeFromHeader(header);
if (size) {
info.size = size;
continue;
}
const source = getTranfserIdFromHeader(header);
if (source) {
info.source = source;
continue;
}
}
return info;
};
const createFileLoader = fetchFn => {
const state = {
source: null,
complete: false,
progress: 0,
size: null,
timestamp: null,
duration: 0,
request: null,
};
const getProgress = () => state.progress;
const abort = () => {
if (state.request && state.request.abort) {
state.request.abort();
}
};
// load source
const load = () => {
// get quick reference
const source = state.source;
api.fire('init', source);
// Load Files
if (source instanceof File) {
api.fire('load', source);
} else if (source instanceof Blob) {
// Load blobs, set default name to current date
api.fire('load', getFileFromBlob(source, source.name));
} else if (isBase64DataURI(source)) {
// Load base 64, set default name to current date
api.fire('load', getFileFromBase64DataURI(source));
} else {
// Deal as if is external URL, let's load it!
loadURL(source);
}
};
// loads a url
const loadURL = url => {
// is remote url and no fetch method supplied
if (!fetchFn) {
api.fire('error', {
type: 'error',
body: "Can't load URL",
code: 400,
});
return;
}
// set request start
state.timestamp = Date.now();
// load file
state.request = fetchFn(
url,
response => {
// update duration
state.duration = Date.now() - state.timestamp;
// done!
state.complete = true;
// turn blob response into a file
if (response instanceof Blob) {
response = getFileFromBlob(response, response.name || getFilenameFromURL(url));
}
api.fire(
'load',
// if has received blob, we go with blob, if no response, we return null
response instanceof Blob ? response : response ? response.body : null
);
},
error => {
api.fire(
'error',
typeof error === 'string'
? {
type: 'error',
code: 0,
body: error,
}
: error
);
},
(computable, current, total) => {
// collected some meta data already
if (total) {
state.size = total;
}
// update duration
state.duration = Date.now() - state.timestamp;
// if we can't compute progress, we're not going to fire progress events
if (!computable) {
state.progress = null;
return;
}
// update progress percentage
state.progress = current / total;
// expose
api.fire('progress', state.progress);
},
() => {
api.fire('abort');
},
response => {
const fileinfo = getFileInfoFromHeaders(
typeof response === 'string' ? response : response.headers
);
api.fire('meta', {
size: state.size || fileinfo.size,
filename: fileinfo.name,
source: fileinfo.source,
});
}
);
};
const api = {
...on(),
setSource: source => (state.source = source),
getProgress, // file load progress
abort, // abort file load
load, // start load
};
return api;
};
const isGet = method => /GET|HEAD/.test(method);
const sendRequest = (data, url, options) => {
const api = {
onheaders: () => {},
onprogress: () => {},
onload: () => {},
ontimeout: () => {},
onerror: () => {},
onabort: () => {},
abort: () => {
aborted = true;
xhr.abort();
},
};
// timeout identifier, only used when timeout is defined
let aborted = false;
let headersReceived = false;
// set default options
options = {
method: 'POST',
headers: {},
withCredentials: false,
...options,
};
// encode url
url = encodeURI(url);
// if method is GET, add any received data to url
if (isGet(options.method) && data) {
url = `${url}${encodeURIComponent(typeof data === 'string' ? data : JSON.stringify(data))}`;
}
// create request
const xhr = new XMLHttpRequest();
// progress of load
const process = isGet(options.method) ? xhr : xhr.upload;
process.onprogress = e => {
// no progress event when aborted ( onprogress is called once after abort() )
if (aborted) {
return;
}
api.onprogress(e.lengthComputable, e.loaded, e.total);
};
// tries to get header info to the app as fast as possible
xhr.onreadystatechange = () => {
// not interesting in these states ('unsent' and 'openend' as they don't give us any additional info)
if (xhr.readyState < 2) {
return;
}
// no server response
if (xhr.readyState === 4 && xhr.status === 0) {
return;
}
if (headersReceived) {
return;
}
headersReceived = true;
// we've probably received some useful data in response headers
api.onheaders(xhr);
};
// load successful
xhr.onload = () => {
// is classified as valid response
if (xhr.status >= 200 && xhr.status < 300) {
api.onload(xhr);
} else {
api.onerror(xhr);
}
};
// error during load
xhr.onerror = () => api.onerror(xhr);
// request aborted
xhr.onabort = () => {
aborted = true;
api.onabort();
};
// request timeout
xhr.ontimeout = () => api.ontimeout(xhr);
// open up open up!
xhr.open(options.method, url, true);
// set timeout if defined (do it after open so IE11 plays ball)
if (isInt(options.timeout)) {
xhr.timeout = options.timeout;
}
// add headers
Object.keys(options.headers).forEach(key => {
const value = unescape(encodeURIComponent(options.headers[key]));
xhr.setRequestHeader(key, value);
});
// set type of response
if (options.responseType) {
xhr.responseType = options.responseType;
}
// set credentials
if (options.withCredentials) {
xhr.withCredentials = true;
}
// let's send our data
xhr.send(data);
return api;
};
const createResponse = (type, code, body, headers) => ({
type,
code,
body,
headers,
});
const createTimeoutResponse = cb => xhr => {
cb(createResponse('error', 0, 'Timeout', xhr.getAllResponseHeaders()));
};
const hasQS = str => /\?/.test(str);
const buildURL = (...parts) => {
let url = '';
parts.forEach(part => {
url += hasQS(url) && hasQS(part) ? part.replace(/\?/, '&') : part;
});
return url;
};
const createFetchFunction = (apiUrl = '', action) => {
// custom handler (should also handle file, load, error, progress and abort)
if (typeof action === 'function') {
return action;
}
// no action supplied
if (!action || !isString(action.url)) {
return null;
}
// set onload hanlder
const onload = action.onload || (res => res);
const onerror = action.onerror || (res => null);
// internal handler
return (url, load, error, progress, abort, headers) => {
// do local or remote request based on if the url is external
const request = sendRequest(url, buildURL(apiUrl, action.url), {
...action,
responseType: 'blob',
});
request.onload = xhr => {
// get headers
const headers = xhr.getAllResponseHeaders();
// get filename
const filename = getFileInfoFromHeaders(headers).name || getFilenameFromURL(url);
// create response
load(
createResponse(
'load',
xhr.status,
action.method === 'HEAD'
? null
: getFileFromBlob(onload(xhr.response), filename),
headers
)
);
};
request.onerror = xhr => {
error(
createResponse(
'error',
xhr.status,
onerror(xhr.response) || xhr.statusText,
xhr.getAllResponseHeaders()
)
);
};
request.onheaders = xhr => {
headers(createResponse('headers', xhr.status, null, xhr.getAllResponseHeaders()));
};
request.ontimeout = createTimeoutResponse(error);
request.onprogress = progress;
request.onabort = abort;
// should return request
return request;
};
};
const ChunkStatus = {
QUEUED: 0,
COMPLETE: 1,
PROCESSING: 2,
ERROR: 3,
WAITING: 4,
};
/*
function signature:
(file, metadata, load, error, progress, abort, transfer, options) => {
return {
abort:() => {}
}
}
*/
// apiUrl, action, name, file, metadata, load, error, progress, abort, transfer, options
const processFileChunked = (
apiUrl,
action,
name,
file,
metadata,
load,
error,
progress,
abort,
transfer,
options
) => {
// all chunks
const chunks = [];
const { chunkTransferId, chunkServer, chunkSize, chunkRetryDelays } = options;
// default state
const state = {
serverId: chunkTransferId,
aborted: false,
};
// set onload handlers
const ondata = action.ondata || (fd => fd);
const onload =
action.onload ||
((xhr, method) =>
method === 'HEAD' ? xhr.getResponseHeader('Upload-Offset') : xhr.response);
const onerror = action.onerror || (res => null);
// create server hook
const requestTransferId = cb => {
const formData = new FormData();
// add metadata under same name
if (isObject(metadata)) formData.append(name, JSON.stringify(metadata));
const headers =
typeof action.headers === 'function'
? action.headers(file, metadata)
: {
...action.headers,
'Upload-Length': file.size,
};
const requestParams = {
...action,
headers,
};
// send request object
const request = sendRequest(ondata(formData), buildURL(apiUrl, action.url), requestParams);
request.onload = xhr => cb(onload(xhr, requestParams.method));
request.onerror = xhr =>
error(
createResponse(
'error',
xhr.status,
onerror(xhr.response) || xhr.statusText,
xhr.getAllResponseHeaders()
)
);
request.ontimeout = createTimeoutResponse(error);
};
const requestTransferOffset = cb => {
const requestUrl = buildURL(apiUrl, chunkServer.url, state.serverId);
const headers =
typeof action.headers === 'function'
? action.headers(state.serverId)
: {
...action.headers,
};
const requestParams = {
headers,
method: 'HEAD',
};
const request = sendRequest(null, requestUrl, requestParams);
request.onload = xhr => cb(onload(xhr, requestParams.method));
request.onerror = xhr =>
error(
createResponse(
'error',
xhr.status,
onerror(xhr.response) || xhr.statusText,
xhr.getAllResponseHeaders()
)
);
request.ontimeout = createTimeoutResponse(error);
};
// create chunks
const lastChunkIndex = Math.floor(file.size / chunkSize);
for (let i = 0; i <= lastChunkIndex; i++) {
const offset = i * chunkSize;
const data = file.slice(offset, offset + chunkSize, 'application/offset+octet-stream');
chunks[i] = {
index: i,
size: data.size,
offset,
data,
file,
progress: 0,
retries: [...chunkRetryDelays],
status: ChunkStatus.QUEUED,
error: null,
request: null,
timeout: null,
};
}
const completeProcessingChunks = () => load(state.serverId);
const canProcessChunk = chunk =>
chunk.status === ChunkStatus.QUEUED || chunk.status === ChunkStatus.ERROR;
const processChunk = chunk => {
// processing is paused, wait here
if (state.aborted) return;
// get next chunk to process
chunk = chunk || chunks.find(canProcessChunk);
// no more chunks to process
if (!chunk) {
// all done?
if (chunks.every(chunk => chunk.status === ChunkStatus.COMPLETE)) {
completeProcessingChunks();
}
// no chunk to handle
return;
}
// now processing this chunk
chunk.status = ChunkStatus.PROCESSING;
chunk.progress = null;
// allow parsing of formdata
const ondata = chunkServer.ondata || (fd => fd);
const onerror = chunkServer.onerror || (res => null);
const onload = chunkServer.onload || (() => {});
// send request object
const requestUrl = buildURL(apiUrl, chunkServer.url, state.serverId);
const headers =
typeof chunkServer.headers === 'function'
? chunkServer.headers(chunk)
: {
...chunkServer.headers,
'Content-Type': 'application/offset+octet-stream',
'Upload-Offset': chunk.offset,
'Upload-Length': file.size,
'Upload-Name': file.name,
};
const request = (chunk.request = sendRequest(ondata(chunk.data), requestUrl, {
...chunkServer,
headers,
}));
request.onload = xhr => {
// allow hooking into request result
onload(xhr, chunk.index, chunks.length);
// done!
chunk.status = ChunkStatus.COMPLETE;
// remove request reference
chunk.request = null;
// start processing more chunks
processChunks();
};
request.onprogress = (lengthComputable, loaded, total) => {
chunk.progress = lengthComputable ? loaded : null;
updateTotalProgress();
};
request.onerror = xhr => {
chunk.status = ChunkStatus.ERROR;
chunk.request = null;
chunk.error = onerror(xhr.response) || xhr.statusText;
if (!retryProcessChunk(chunk)) {
error(
createResponse(
'error',
xhr.status,
onerror(xhr.response) || xhr.statusText,
xhr.getAllResponseHeaders()
)
);
}
};
request.ontimeout = xhr => {
chunk.status = ChunkStatus.ERROR;
chunk.request = null;
if (!retryProcessChunk(chunk)) {
createTimeoutResponse(error)(xhr);
}
};
request.onabort = () => {
chunk.status = ChunkStatus.QUEUED;
chunk.request = null;
abort();
};
};
const retryProcessChunk = chunk => {
// no more retries left
if (chunk.retries.length === 0) return false;
// new retry
chunk.status = ChunkStatus.WAITING;
clearTimeout(chunk.timeout);
chunk.timeout = setTimeout(() => {
processChunk(chunk);
}, chunk.retries.shift());
// we're going to retry
return true;
};
const updateTotalProgress = () => {
// calculate total progress fraction
const totalBytesTransfered = chunks.reduce((p, chunk) => {
if (p === null || chunk.progress === null) return null;
return p + chunk.progress;
}, 0);
// can't compute progress
if (totalBytesTransfered === null) return progress(false, 0, 0);
// calculate progress values
const totalSize = chunks.reduce((total, chunk) => total + chunk.size, 0);
// can update progress indicator
progress(true, totalBytesTransfered, totalSize);
};
// process new chunks
const processChunks = () => {
const totalProcessing = chunks.filter(chunk => chunk.status === ChunkStatus.PROCESSING)
.length;
if (totalProcessing >= 1) return;
processChunk();
};
const abortChunks = () => {
chunks.forEach(chunk => {
clearTimeout(chunk.timeout);
if (chunk.request) {
chunk.request.abort();
}
});
};
// let's go!
if (!state.serverId) {
requestTransferId(serverId => {
// stop here if aborted, might have happened in between request and callback
if (state.aborted) return;
// pass back to item so we can use it if something goes wrong
transfer(serverId);
// store internally
state.serverId = serverId;
processChunks();
});
} else {
requestTransferOffset(offset => {
// stop here if aborted, might have happened in between request and callback
if (state.aborted) return;
// mark chunks with lower offset as complete
chunks
.filter(chunk => chunk.offset < offset)
.forEach(chunk => {
chunk.status = ChunkStatus.COMPLETE;
chunk.progress = chunk.size;
});
// continue processing
processChunks();
});
}
return {
abort: () => {
state.aborted = true;
abortChunks();
},
};
};
/*
function signature:
(file, metadata, load, error, progress, abort) => {
return {
abort:() => {}
}
}
*/
const createFileProcessorFunction = (apiUrl, action, name, options) => (
file,
metadata,
load,
error,
progress,
abort,
transfer
) => {
// no file received
if (!file) return;
// if was passed a file, and we can chunk it, exit here
const canChunkUpload = options.chunkUploads;
const shouldChunkUpload = canChunkUpload && file.size > options.chunkSize;
const willChunkUpload = canChunkUpload && (shouldChunkUpload || options.chunkForce);
if (file instanceof Blob && willChunkUpload)
return processFileChunked(
apiUrl,
action,
name,
file,
metadata,
load,
error,
progress,
abort,
transfer,
options
);
// set handlers
const ondata = action.ondata || (fd => fd);
const onload = action.onload || (res => res);
const onerror = action.onerror || (res => null);
const headers =
typeof action.headers === 'function'
? action.headers(file, metadata) || {}
: {
...action.headers,
};
const requestParams = {
...action,
headers,
};
// create formdata object
var formData = new FormData();
// add metadata under same name
if (isObject(metadata)) {
formData.append(name, JSON.stringify(metadata));
}
// Turn into an array of objects so no matter what the input, we can handle it the same way
(file instanceof Blob ? [{ name: null, file }] : file).forEach(item => {
formData.append(
name,
item.file,
item.name === null ? item.file.name : `${item.name}${item.file.name}`
);
});
// send request object
const request = sendRequest(ondata(formData), buildURL(apiUrl, action.url), requestParams);
request.onload = xhr => {
load(createResponse('load', xhr.status, onload(xhr.response), xhr.getAllResponseHeaders()));
};
request.onerror = xhr => {
error(
createResponse(
'error',
xhr.status,
onerror(xhr.response) || xhr.statusText,
xhr.getAllResponseHeaders()
)
);
};
request.ontimeout = createTimeoutResponse(error);
request.onprogress = progress;
request.onabort = abort;
// should return request
return request;
};
const createProcessorFunction = (apiUrl = '', action, name, options) => {
// custom handler (should also handle file, load, error, progress and abort)
if (typeof action === 'function') return (...params) => action(name, ...params, options);
// no action supplied
if (!action || !isString(action.url)) return null;
// internal handler
return createFileProcessorFunction(apiUrl, action, name, options);
};
/*
function signature:
(uniqueFileId, load, error) => { }
*/
const createRevertFunction = (apiUrl = '', action) => {
// is custom implementation
if (typeof action === 'function') {
return action;
}
// no action supplied, return stub function, interface will work, but file won't be removed
if (!action || !isString(action.url)) {
return (uniqueFileId, load) => load();
}
// set onload hanlder
const onload = action.onload || (res => res);
const onerror = action.onerror || (res => null);
// internal implementation
return (uniqueFileId, load, error) => {
const request = sendRequest(
uniqueFileId,
apiUrl + action.url,
action // contains method, headers and withCredentials properties
);
request.onload = xhr => {
load(
createResponse(
'load',
xhr.status,
onload(xhr.response),
xhr.getAllResponseHeaders()
)
);
};
request.onerror = xhr => {
error(
createResponse(
'error',
xhr.status,
onerror(xhr.response) || xhr.statusText,
xhr.getAllResponseHeaders()
)
);
};
request.ontimeout = createTimeoutResponse(error);
return request;
};
};
const getRandomNumber = (min = 0, max = 1) => min + Math.random() * (max - min);
const createPerceivedPerformanceUpdater = (
cb,
duration = 1000,
offset = 0,
tickMin = 25,
tickMax = 250
) => {
let timeout = null;
const start = Date.now();
const tick = () => {
let runtime = Date.now() - start;
let delay = getRandomNumber(tickMin, tickMax);
if (runtime + delay > duration) {
delay = runtime + delay - duration;
}
let progress = runtime / duration;
if (progress >= 1 || document.hidden) {
cb(1);
return;
}
cb(progress);
timeout = setTimeout(tick, delay);
};
if (duration > 0) tick();
return {
clear: () => {
clearTimeout(timeout);
},
};
};
const createFileProcessor = (processFn, options) => {
const state = {
complete: false,
perceivedProgress: 0,
perceivedPerformanceUpdater: null,
progress: null,
timestamp: null,
perceivedDuration: 0,
duration: 0,
request: null,
response: null,
};
const { allowMinimumUploadDuration } = options;
const process = (file, metadata) => {
const progressFn = () => {
// we've not yet started the real download, stop here
// the request might not go through, for instance, there might be some server trouble
// if state.progress is null, the server does not allow computing progress and we show the spinner instead
if (state.duration === 0 || state.progress === null) return;
// as we're now processing, fire the progress event
api.fire('progress', api.getProgress());
};
const completeFn = () => {
state.complete = true;
api.fire('load-perceived', state.response.body);
};
// let's start processing
api.fire('start');
// set request start
state.timestamp = Date.now();
// create perceived performance progress indicator
state.perceivedPerformanceUpdater = createPerceivedPerformanceUpdater(
progress => {
state.perceivedProgress = progress;
state.perceivedDuration = Date.now() - state.timestamp;
progressFn();
// if fake progress is done, and a response has been received,
// and we've not yet called the complete method
if (state.response && state.perceivedProgress === 1 && !state.complete) {
// we done!
completeFn();
}
},
// random delay as in a list of files you start noticing
// files uploading at the exact same speed
allowMinimumUploadDuration ? getRandomNumber(750, 1500) : 0
);
// remember request so we can abort it later
state.request = processFn(
// the file to process
file,
// the metadata to send along
metadata,
// callbacks (load, error, progress, abort, transfer)
// load expects the body to be a server id if
// you want to make use of revert
response => {
// we put the response in state so we can access
// it outside of this method
state.response = isObject(response)
? response
: {
type: 'load',
code: 200,
body: `${response}`,
headers: {},
};
// update duration
state.duration = Date.now() - state.timestamp;
// force progress to 1 as we're now done
state.progress = 1;
// actual load is done let's share results
api.fire('load', state.response.body);
// we are really done
// if perceived progress is 1 ( wait for perceived progress to complete )
// or if server does not support progress ( null )
if (
!allowMinimumUploadDuration ||
(allowMinimumUploadDuration && state.perceivedProgress === 1)
) {
completeFn();
}
},
// error is expected to be an object with type, code, body
error => {
// cancel updater
state.perceivedPerformanceUpdater.clear();
// update others about this error
api.fire(
'error',
isObject(error)
? error
: {
type: 'error',
code: 0,
body: `${error}`,
}
);
},
// actual processing progress
(computable, current, total) => {
// update actual duration
state.duration = Date.now() - state.timestamp;
// update actual progress
state.progress = computable ? current / total : null;
progressFn();
},
// abort does not expect a value
() => {
// stop updater
state.perceivedPerformanceUpdater.clear();
// fire the abort event so we can switch visuals
api.fire('abort', state.response ? state.response.body : null);
},
// register the id for this transfer
transferId => {
api.fire('transfer', transferId);
}
);
};
const abort = () => {
// no request running, can't abort
if (!state.request) return;
// stop updater
state.perceivedPerformanceUpdater.clear();
// abort actual request
if (state.request.abort) state.request.abort();
// if has response object, we've completed the request
state.complete = true;
};
const reset = () => {
abort();
state.complete = false;
state.perceivedProgress = 0;
state.progress = 0;
state.timestamp = null;
state.perceivedDuration = 0;
state.duration = 0;
state.request = null;
state.response = null;
};
const getProgress = allowMinimumUploadDuration
? () => (state.progress ? Math.min(state.progress, state.perceivedProgress) : null)
: () => state.progress || null;
const getDuration = allowMinimumUploadDuration
? () => Math.min(state.duration, state.perceivedDuration)
: () => state.duration;
const api = {
...on(),
process, // start processing file
abort, // abort active process request
getProgress,
getDuration,
reset,
};
return api;
};
const getFilenameWithoutExtension = name => name.substring(0, name.lastIndexOf('.')) || name;
const createFileStub = source => {
let data = [source.name, source.size, source.type];
// is blob or base64, then we need to set the name
if (source instanceof Blob || isBase64DataURI(source)) {
data[0] = source.name || getDateString();
} else if (isBase64DataURI(source)) {
// if is base64 data uri we need to determine the average size and type
data[1] = source.length;
data[2] = getMimeTypeFromBase64DataURI(source);
} else if (isString(source)) {
// url
data[0] = getFilenameFromURL(source);
data[1] = 0;
data[2] = 'application/octet-stream';
}
return {
name: data[0],
size: data[1],
type: data[2],
};
};
const isFile = value => !!(value instanceof File || (value instanceof Blob && value.name));
const deepCloneObject = src => {
if (!isObject(src)) return src;
const target = isArray(src) ? [] : {};
for (const key in src) {
if (!src.hasOwnProperty(key)) continue;
const v = src[key];
target[key] = v && isObject(v) ? deepCloneObject(v) : v;
}
return target;
};
const createItem = (origin = null, serverFileReference = null, file = null) => {
// unique id for this item, is used to identify the item across views
const id = getUniqueId();
/**
* Internal item state
*/
const state = {
// is archived
archived: false,
// if is frozen, no longer fires events
frozen: false,
// removed from view
released: false,
// original source
source: null,
// file model reference
file,
// id of file on server
serverFileReference,
// id of file transfer on server
transferId: null,
// is aborted
processingAborted: false,
// current item status
status: serverFileReference ? ItemStatus.PROCESSING_COMPLETE : ItemStatus.INIT,
// active processes
activeLoader: null,
activeProcessor: null,
};
// callback used when abort processing is called to link back to the resolve method
let abortProcessingRequestComplete = null;
/**
* Externally added item metadata
*/
const metadata = {};
// item data
const setStatus = status => (state.status = status);
// fire event unless the item has been archived
const fire = (event, ...params) => {
if (state.released || state.frozen) return;
api.fire(event, ...params);
};
// file data
const getFileExtension = () => getExtensionFromFilename(state.file.name);
const getFileType = () => state.file.type;
const getFileSize = () => state.file.size;
const getFile = () => state.file;
//
// logic to load a file
//
const load = (source, loader, onload) => {
// remember the original item source
state.source = source;
// source is known
api.fireSync('init');
// file stub is already there
if (state.file) {
api.fireSync('load-skip');
return;
}
// set a stub file object while loading the actual data
state.file = createFileStub(source);
// starts loading
loader.on('init', () => {
fire('load-init');
});
// we'eve received a size indication, let's update the stub
loader.on('meta', meta => {
// set size of file stub
state.file.size = meta.size;
// set name of file stub
state.file.filename = meta.filename;
// if has received source, we done
if (meta.source) {
origin = FileOrigin.LIMBO;
state.serverFileReference = meta.source;
state.status = ItemStatus.PROCESSING_COMPLETE;
}
// size has been updated
fire('load-meta');
});
// the file is now loading we need to update the progress indicators
loader.on('progress', progress => {
setStatus(ItemStatus.LOADING);
fire('load-progress', progress);
});
// an error was thrown while loading the file, we need to switch to error state
loader.on('error', error => {
setStatus(ItemStatus.LOAD_ERROR);
fire('load-request-error', error);
});
// user or another process aborted the file load (cannot retry)
loader.on('abort', () => {
setStatus(ItemStatus.INIT);
fire('load-abort');
});
// done loading
loader.on('load', file => {
// as we've now loaded the file the loader is no longer required
state.activeLoader = null;
// called when file has loaded succesfully
const success = result => {
// set (possibly) transformed file
state.file = isFile(result) ? result : state.file;
// file received
if (origin === FileOrigin.LIMBO && state.serverFileReference) {
setStatus(ItemStatus.PROCESSING_COMPLETE);
} else {
setStatus(ItemStatus.IDLE);
}
fire('load');
};
const error = result => {
// set original file
state.file = file;
fire('load-meta');
setStatus(ItemStatus.LOAD_ERROR);
fire('load-file-error', result);
};
// if we already have a server file reference, we don't need to call the onload method
if (state.serverFileReference) {
success(file);
return;
}
// no server id, let's give this file the full treatment
onload(file, success, error);
});
// set loader source data
loader.setSource(source);
// set as active loader
state.activeLoader = loader;
// load the source data
loader.load();
};
const retryLoad = () => {
if (!state.activeLoader) {
return;
}
state.activeLoader.load();
};
const abortLoad = () => {
if (state.activeLoader) {
state.activeLoader.abort();
return;
}
setStatus(ItemStatus.INIT);
fire('load-abort');
};
//
// logic to process a file
//
const process = (processor, onprocess) => {
// processing was aborted
if (state.processingAborted) {
state.processingAborted = false;
return;
}
// now processing
setStatus(ItemStatus.PROCESSING);
// reset abort callback
abortProcessingRequestComplete = null;
// if no file loaded we'll wait for the load event
if (!(state.file instanceof Blob)) {
api.on('load', () => {
process(processor, onprocess);
});
return;
}
// setup processor
processor.on('load', serverFileReference => {
// need this id to be able to revert the upload
state.transferId = null;
state.serverFileReference = serverFileReference;
});
// register transfer id
processor.on('transfer', transferId => {
// need this id to be able to revert the upload
state.transferId = transferId;
});
processor.on('load-perceived', serverFileReference => {
// no longer required
state.activeProcessor = null;
// need this id to be able to rever the upload
state.transferId = null;
state.serverFileReference = serverFileReference;
setStatus(ItemStatus.PROCESSING_COMPLETE);
fire('process-complete', serverFileReference);
});
processor.on('start', () => {
fire('process-start');
});
processor.on('error', error => {
state.activeProcessor = null;
setStatus(ItemStatus.PROCESSING_ERROR);
fire('process-error', error);
});
processor.on('abort', serverFileReference => {
state.activeProcessor = null;
// if file was uploaded but processing was cancelled during perceived processor time store file reference
state.serverFileReference = serverFileReference;
setStatus(ItemStatus.IDLE);
fire('process-abort');
// has timeout so doesn't interfere with remove action
if (abortProcessingRequestComplete) {
abortProcessingRequestComplete();
}
});
processor.on('progress', progress => {
fire('process-progress', progress);
});
// when successfully transformed
const success = file => {
// if was archived in the mean time, don't process
if (state.archived) return;
// process file!
processor.process(file, { ...metadata });
};
// something went wrong during transform phase
const error = console.error;
// start processing the file
onprocess(state.file, success, error);
// set as active processor
state.activeProcessor = processor;
};
const requestProcessing = () => {
state.processingAborted = false;
setStatus(ItemStatus.PROCESSING_QUEUED);
};
const abortProcessing = () =>
new Promise(resolve => {
if (!state.activeProcessor) {
state.processingAborted = true;
setStatus(ItemStatus.IDLE);
fire('process-abort');
resolve();
return;
}
abortProcessingRequestComplete = () => {
resolve();
};
state.activeProcessor.abort();
});
//
// logic to revert a processed file
//
const revert = (revertFileUpload, forceRevert) =>
new Promise((resolve, reject) => {
// a completed upload will have a serverFileReference, a failed chunked upload where
// getting a serverId succeeded but >=0 chunks have been uploaded will have transferId set
const serverTransferId =
state.serverFileReference !== null ? state.serverFileReference : state.transferId;
// cannot revert without a server id for this process
if (serverTransferId === null) {
resolve();
return;
}
// revert the upload (fire and forget)
revertFileUpload(
serverTransferId,
() => {
// reset file server id and transfer id as now it's not available on the server
state.serverFileReference = null;
state.transferId = null;
resolve();
},
error => {
// don't set error state when reverting is optional, it will always resolve
if (!forceRevert) {
resolve();
return;
}
// oh no errors
setStatus(ItemStatus.PROCESSING_REVERT_ERROR);
fire('process-revert-error');
reject(error);
}
);
// fire event
setStatus(ItemStatus.IDLE);
fire('process-revert');
});
// exposed methods
const setMetadata = (key, value, silent) => {
const keys = key.split('.');
const root = keys[0];
const last = keys.pop();
let data = metadata;
keys.forEach(key => (data = data[key]));
// compare old value against new value, if they're the same, we're not updating
if (JSON.stringify(data[last]) === JSON.stringify(value)) return;
// update value
data[last] = value;
// fire update
fire('metadata-update', {
key: root,
value: metadata[root],
silent,
});
};
const getMetadata = key => deepCloneObject(key ? metadata[key] : metadata);
const api = {
id: { get: () => id },
origin: { get: () => origin, set: value => (origin = value) },
serverId: { get: () => state.serverFileReference },
transferId: { get: () => state.transferId },
status: { get: () => state.status },
filename: { get: () => state.file.name },
filenameWithoutExtension: { get: () => getFilenameWithoutExtension(state.file.name) },
fileExtension: { get: getFileExtension },
fileType: { get: getFileType },
fileSize: { get: getFileSize },
file: { get: getFile },
relativePath: { get: () => state.file._relativePath },
source: { get: () => state.source },
getMetadata,
setMetadata: (key, value, silent) => {
if (isObject(key)) {
const data = key;
Object.keys(data).forEach(key => {
setMetadata(key, data[key], value);
});
return key;
}
setMetadata(key, value, silent);
return value;
},
extend: (name, handler) => (itemAPI[name] = handler),
abortLoad,
retryLoad,
requestProcessing,
abortProcessing,
load,
process,
revert,
...on(),
freeze: () => (state.frozen = true),
release: () => (state.released = true),
released: { get: () => state.released },
archive: () => (state.archived = true),
archived: { get: () => state.archived },
// replace source and file object
setFile: file => (state.file = file),
};
// create it here instead of returning it instantly so we can extend it later
const itemAPI = createObject(api);
return itemAPI;
};
const getItemIndexByQuery = (items, query) => {
// just return first index
if (isEmpty(query)) {
return 0;
}
// invalid queries
if (!isString(query)) {
return -1;
}
// return item by id (or -1 if not found)
return items.findIndex(item => item.id === query);
};
const getItemById = (items, itemId) => {
const index = getItemIndexByQuery(items, itemId);
if (index < 0) {
return;
}
return items[index] || null;
};
const fetchBlob = (url, load, error, progress, abort, headers) => {
const request = sendRequest(null, url, {
method: 'GET',
responseType: 'blob',
});
request.onload = xhr => {
// get headers
const headers = xhr.getAllResponseHeaders();
// get filename
const filename = getFileInfoFromHeaders(headers).name || getFilenameFromURL(url);
// create response
load(createResponse('load', xhr.status, getFileFromBlob(xhr.response, filename), headers));
};
request.onerror = xhr => {
error(createResponse('error', xhr.status, xhr.statusText, xhr.getAllResponseHeaders()));
};
request.onheaders = xhr => {
headers(createResponse('headers', xhr.status, null, xhr.getAllResponseHeaders()));
};
request.ontimeout = createTimeoutResponse(error);
request.onprogress = progress;
request.onabort = abort;
// should return request
return request;
};
const getDomainFromURL = url => {
if (url.indexOf('//') === 0) {
url = location.protocol + url;
}
return url
.toLowerCase()
.replace('blob:', '')
.replace(/([a-z])?:\/\//, '$1')
.split('/')[0];
};
const isExternalURL = url =>
(url.indexOf(':') > -1 || url.indexOf('//') > -1) &&
getDomainFromURL(location.href) !== getDomainFromURL(url);
const dynamicLabel = label => (...params) => (isFunction(label) ? label(...params) : label);
const isMockItem = item => !isFile(item.file);
const listUpdated = (dispatch, state) => {
clearTimeout(state.listUpdateTimeout);
state.listUpdateTimeout = setTimeout(() => {
dispatch('DID_UPDATE_ITEMS', { items: getActiveItems(state.items) });
}, 0);
};
const optionalPromise = (fn, ...params) =>
new Promise(resolve => {
if (!fn) {
return resolve(true);
}
const result = fn(...params);
if (result == null) {
return resolve(true);
}
if (typeof result === 'boolean') {
return resolve(result);
}
if (typeof result.then === 'function') {
result.then(resolve);
}
});
const sortItems = (state, compare) => {
state.items.sort((a, b) => compare(createItemAPI(a), createItemAPI(b)));
};
// returns item based on state
const getItemByQueryFromState = (state, itemHandler) => ({
query,
success = () => {},
failure = () => {},
...options
} = {}) => {
const item = getItemByQuery(state.items, query);
if (!item) {
failure({
error: createResponse('error', 0, 'Item not found'),
file: null,
});
return;
}
itemHandler(item, success, failure, options || {});
};
const actions = (dispatch, query, state) => ({
/**
* Aborts all ongoing processes
*/
ABORT_ALL: () => {
getActiveItems(state.items).forEach(item => {
item.freeze();
item.abortLoad();
item.abortProcessing();
});
},
/**
* Sets initial files
*/
DID_SET_FILES: ({ value = [] }) => {
// map values to file objects
const files = value.map(file => ({
source: file.source ? file.source : file,
options: file.options,
}));
// loop over files, if file is in list, leave it be, if not, remove
// test if items should be moved
let activeItems = getActiveItems(state.items);
activeItems.forEach(item => {
// if item not is in new value, remove
if (!files.find(file => file.source === item.source || file.source === item.file)) {
dispatch('REMOVE_ITEM', { query: item, remove: false });
}
});
// add new files
activeItems = getActiveItems(state.items);
files.forEach((file, index) => {
// if file is already in list
if (activeItems.find(item => item.source === file.source || item.file === file.source))
return;
// not in list, add
dispatch('ADD_ITEM', {
...file,
interactionMethod: InteractionMethod.NONE,
index,
});
});
},
DID_UPDATE_ITEM_METADATA: ({ id, action, change }) => {
// don't do anything
if (change.silent) return;
// if is called multiple times in close succession we combined all calls together to save resources
clearTimeout(state.itemUpdateTimeout);
state.itemUpdateTimeout = setTimeout(() => {
const item = getItemById(state.items, id);
// only revert and attempt to upload when we're uploading to a server
if (!query('IS_ASYNC')) {
// should we update the output data
applyFilterChain('SHOULD_PREPARE_OUTPUT', false, {
item,
query,
action,
change,
}).then(shouldPrepareOutput => {
// plugins determined the output data should be prepared (or not), can be adjusted with beforePrepareOutput hook
const beforePrepareFile = query('GET_BEFORE_PREPARE_FILE');
if (beforePrepareFile)
shouldPrepareOutput = beforePrepareFile(item, shouldPrepareOutput);
if (!shouldPrepareOutput) return;
dispatch(
'REQUEST_PREPARE_OUTPUT',
{
query: id,
item,
success: file => {
dispatch('DID_PREPARE_OUTPUT', { id, file });
},
},
true
);
});
return;
}
// if is local item we need to enable upload button so change can be propagated to server
if (item.origin === FileOrigin.LOCAL) {
dispatch('DID_LOAD_ITEM', {
id: item.id,
error: null,
serverFileReference: item.source,
});
}
// for async scenarios
const upload = () => {
// we push this forward a bit so the interface is updated correctly
setTimeout(() => {
dispatch('REQUEST_ITEM_PROCESSING', { query: id });
}, 32);
};
const revert = doUpload => {
item.revert(
createRevertFunction(state.options.server.url, state.options.server.revert),
query('GET_FORCE_REVERT')
)
.then(doUpload ? upload : () => {})
.catch(() => {});
};
const abort = doUpload => {
item.abortProcessing().then(doUpload ? upload : () => {});
};
// if we should re-upload the file immediately
if (item.status === ItemStatus.PROCESSING_COMPLETE) {
return revert(state.options.instantUpload);
}
// if currently uploading, cancel upload
if (item.status === ItemStatus.PROCESSING) {
return abort(state.options.instantUpload);
}
if (state.options.instantUpload) {
upload();
}
}, 0);
},
MOVE_ITEM: ({ query, index }) => {
const item = getItemByQuery(state.items, query);
if (!item) return;
const currentIndex = state.items.indexOf(item);
index = limit(index, 0, state.items.length - 1);
if (currentIndex === index) return;
state.items.splice(index, 0, state.items.splice(currentIndex, 1)[0]);
},
SORT: ({ compare }) => {
sortItems(state, compare);
dispatch('DID_SORT_ITEMS', {
items: query('GET_ACTIVE_ITEMS'),
});
},
ADD_ITEMS: ({ items, index, interactionMethod, success = () => {}, failure = () => {} }) => {
let currentIndex = index;
if (index === -1 || typeof index === 'undefined') {
const insertLocation = query('GET_ITEM_INSERT_LOCATION');
const totalItems = query('GET_TOTAL_ITEMS');
currentIndex = insertLocation === 'before' ? 0 : totalItems;
}
const ignoredFiles = query('GET_IGNORED_FILES');
const isValidFile = source =>
isFile(source) ? !ignoredFiles.includes(source.name.toLowerCase()) : !isEmpty(source);
const validItems = items.filter(isValidFile);
const promises = validItems.map(
source =>
new Promise((resolve, reject) => {
dispatch('ADD_ITEM', {
interactionMethod,
source: source.source || source,
success: resolve,
failure: reject,
index: currentIndex++,
options:
gitextract_hbxyat0y/
├── .babelrc
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── ISSUE_TEMPLATE/
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── banner-cli.js
├── banner.js
├── dist/
│ ├── filepond.css
│ ├── filepond.esm.js
│ └── filepond.js
├── index.html
├── jest.config.js
├── jest.stubs.js
├── locale/
│ ├── am-et.js
│ ├── ar-ar.js
│ ├── az-az.js
│ ├── ca-ca.js
│ ├── cs-cz.js
│ ├── cy-cy.js
│ ├── da-dk.js
│ ├── de-de.js
│ ├── el-el.js
│ ├── en-en.js
│ ├── es-es.js
│ ├── et-ee.js
│ ├── fa_ir.js
│ ├── fi-fi.js
│ ├── fr-fr.js
│ ├── he-he.js
│ ├── hr-hr.js
│ ├── hu-hu.js
│ ├── id-id.js
│ ├── it-it.js
│ ├── ja-ja.js
│ ├── km-km.js
│ ├── ko-kr.js
│ ├── ku-ckb.js
│ ├── kur-ckb.js
│ ├── lt-lt.js
│ ├── lus-lus.js
│ ├── lv-lv.js
│ ├── nl-nl.js
│ ├── no_nb.js
│ ├── pl-pl.js
│ ├── pt-br.js
│ ├── pt-pt.js
│ ├── ro-ro.js
│ ├── ru-ru.js
│ ├── sk-sk.js
│ ├── sl-si.js
│ ├── sr-rs.js
│ ├── sv_se.js
│ ├── tr-tr.js
│ ├── uk-ua.js
│ ├── ur-ur.js
│ ├── vi-vi.js
│ ├── zh-cn.js
│ ├── zh-hk.js
│ └── zh-tw.js
├── package.json
├── rollup.config.js
├── rollup.scripts.js
├── src/
│ ├── css/
│ │ ├── assistant.css
│ │ ├── browser.css
│ │ ├── data.css
│ │ ├── drip.css
│ │ ├── drop-label.css
│ │ ├── file-action-button.css
│ │ ├── file-info.css
│ │ ├── file-status.css
│ │ ├── file-wrapper.css
│ │ ├── file.css
│ │ ├── hopper.css
│ │ ├── item-order.css
│ │ ├── item.css
│ │ ├── list-scroller.css
│ │ ├── list.css
│ │ ├── modifiers.css
│ │ ├── panel-root.css
│ │ ├── panel.css
│ │ ├── progress-indicator.css
│ │ ├── root-order.css
│ │ ├── root.css
│ │ └── styles.css
│ └── js/
│ ├── __tests__/
│ │ ├── addFile.test.js
│ │ ├── callbacks.test.js
│ │ ├── contentDisposition.test.js
│ │ ├── createInstance.test.js
│ │ ├── removeFile.test.js
│ │ ├── revertUploadOnRemove.test.js
│ │ ├── server.test.js
│ │ ├── setFiles.test.js
│ │ └── windowMatchMedia.mock
│ ├── app/
│ │ ├── actions.js
│ │ ├── enum/
│ │ │ ├── ChunkStatus.js
│ │ │ ├── FileOrigin.js
│ │ │ ├── InteractionMethod.js
│ │ │ ├── ItemStatus.js
│ │ │ ├── Key.js
│ │ │ ├── Status.js
│ │ │ └── Type.js
│ │ ├── frame/
│ │ │ ├── createPainter.js
│ │ │ ├── createRoute.js
│ │ │ ├── createStore.js
│ │ │ ├── createView.js
│ │ │ ├── index.js
│ │ │ ├── mixins/
│ │ │ │ ├── animations.js
│ │ │ │ ├── apis.js
│ │ │ │ ├── index.js
│ │ │ │ ├── listeners.js
│ │ │ │ ├── styles.js
│ │ │ │ └── utils/
│ │ │ │ └── addGetSet.js
│ │ │ └── utils/
│ │ │ ├── AxisEnum.js
│ │ │ ├── addEvent.js
│ │ │ ├── animators/
│ │ │ │ ├── easing.js
│ │ │ │ ├── spring.js
│ │ │ │ └── tween.js
│ │ │ ├── appendChild.js
│ │ │ ├── appendChildView.js
│ │ │ ├── createAnimator.js
│ │ │ ├── createElement.js
│ │ │ ├── getChildCount.js
│ │ │ ├── getViewRect.js
│ │ │ ├── removeChildView.js
│ │ │ ├── removeEvent.js
│ │ │ └── updateRect.js
│ │ ├── index.js
│ │ ├── options.js
│ │ ├── queries.js
│ │ ├── utils/
│ │ │ ├── buildURL.js
│ │ │ ├── convertTo.js
│ │ │ ├── createDragHelper.js
│ │ │ ├── createFetchFunction.js
│ │ │ ├── createFileLoader.js
│ │ │ ├── createFileProcessor.js
│ │ │ ├── createFileProcessorFunction.js
│ │ │ ├── createFileStub.js
│ │ │ ├── createHopper.js
│ │ │ ├── createInitialState.js
│ │ │ ├── createItem.js
│ │ │ ├── createItemAPI.js
│ │ │ ├── createOption.js
│ │ │ ├── createOptionAPI.js
│ │ │ ├── createOptionActions.js
│ │ │ ├── createOptionQueries.js
│ │ │ ├── createOptions.js
│ │ │ ├── createPaster.js
│ │ │ ├── createPerceivedPerformanceUpdater.js
│ │ │ ├── createProcessorFunction.js
│ │ │ ├── createRevertFunction.js
│ │ │ ├── createServerAPI.js
│ │ │ ├── dnd.js
│ │ │ ├── dropAreaDimensions.js
│ │ │ ├── dynamicLabel.js
│ │ │ ├── fetchBlob.js
│ │ │ ├── getActiveItems.js
│ │ │ ├── getItemById.js
│ │ │ ├── getItemByQuery.js
│ │ │ ├── getItemIndexByPosition.js
│ │ │ ├── getItemIndexByQuery.js
│ │ │ ├── getItemsPerRow.js
│ │ │ ├── getType.js
│ │ │ ├── getValueByType.js
│ │ │ ├── hasRoomForItem.js
│ │ │ ├── insertItem.js
│ │ │ ├── isAPI.js
│ │ │ ├── mergeOptionObject.js
│ │ │ ├── on.js
│ │ │ ├── processFileChunked.js
│ │ │ ├── removeReleasedItems.js
│ │ │ ├── requestDataTransferItems.js
│ │ │ └── toServerAPI.js
│ │ └── view/
│ │ ├── assistant.js
│ │ ├── blob.js
│ │ ├── browser.js
│ │ ├── data.js
│ │ ├── drip.js
│ │ ├── dropLabel.js
│ │ ├── file.js
│ │ ├── fileActionButton.js
│ │ ├── fileInfo.js
│ │ ├── fileStatus.js
│ │ ├── fileWrapper.js
│ │ ├── item.js
│ │ ├── list.js
│ │ ├── listScroller.js
│ │ ├── panel.js
│ │ ├── progressIndicator.js
│ │ └── root.js
│ ├── createApp.js
│ ├── createAppAPI.js
│ ├── createAppAtElement.js
│ ├── createAppObject.js
│ ├── createAppPlugin.js
│ ├── filter.js
│ ├── index.js
│ └── utils/
│ ├── arrayInsert.js
│ ├── arrayRemove.js
│ ├── arrayReverse.js
│ ├── attr.js
│ ├── attrToggle.js
│ ├── canUpdateFileInput.js
│ ├── capitalizeFirstLetter.js
│ ├── composeObject.js
│ ├── copyFile.js
│ ├── copyObjectPropertiesToObject.js
│ ├── createBlob.js
│ ├── createDefaultResponse.js
│ ├── createElement.js
│ ├── createObject.js
│ ├── createResponse.js
│ ├── createWorker.js
│ ├── debounce.js
│ ├── deepCloneObject.js
│ ├── defineProperty.js
│ ├── describeArc.js
│ ├── forEachDelayed.js
│ ├── forin.js
│ ├── formatFilename.js
│ ├── fromCamels.js
│ ├── getAttributesAsObject.js
│ ├── getBase64DataFromBase64DataURI.js
│ ├── getBlobBuilder.js
│ ├── getBlobFromBase64DataURI.js
│ ├── getBlobFromByteStringWithMimeType.js
│ ├── getByteStringFromBase64DataURI.js
│ ├── getDateString.js
│ ├── getDecimalSeparator.js
│ ├── getDomainFromURL.js
│ ├── getExtensionFromFilename.js
│ ├── getFileFromBase64DataURI.js
│ ├── getFileFromBlob.js
│ ├── getFileInfoFromHeaders.js
│ ├── getFilenameFromURL.js
│ ├── getFilenameWithoutExtension.js
│ ├── getMimeTypeFromBase64DataURI.js
│ ├── getNonNumeric.js
│ ├── getNumericAspectRatioFromString.js
│ ├── getParameters.js
│ ├── getRandomNumber.js
│ ├── getRootNode.js
│ ├── getThousandsSeparator.js
│ ├── getUniqueId.js
│ ├── guesstimateExtension.js
│ ├── guesstimateMimeType.js
│ ├── hasQueryString.js
│ ├── insertAfter.js
│ ├── insertBefore.js
│ ├── isArray.js
│ ├── isBase64DataURI.js
│ ├── isBoolean.js
│ ├── isBrowser.js
│ ├── isDefined.js
│ ├── isEmpty.js
│ ├── isExternalURL.js
│ ├── isFile.js
│ ├── isFunction.js
│ ├── isIOS.js
│ ├── isInt.js
│ ├── isNode.js
│ ├── isNull.js
│ ├── isNumber.js
│ ├── isObject.js
│ ├── isString.js
│ ├── leftPad.js
│ ├── limit.js
│ ├── loadImage.js
│ ├── lowerCaseFirstLetter.js
│ ├── percentageArc.js
│ ├── polarToCartesian.js
│ ├── renameFile.js
│ ├── replaceInString.js
│ ├── resetFileInput.js
│ ├── sendRequest.js
│ ├── setInputFiles.js
│ ├── text.js
│ ├── toArray.js
│ ├── toBoolean.js
│ ├── toBytes.js
│ ├── toCamels.js
│ ├── toFloat.js
│ ├── toFunctionReference.js
│ ├── toInt.js
│ ├── toNaturalFileSize.js
│ ├── toNumber.js
│ ├── toPercentage.js
│ ├── toString.js
│ └── trim.js
└── types/
├── index.d.ts
└── tsconfig.json
SYMBOL INDEX (148 symbols across 11 files)
FILE: dist/filepond.esm.js
constant IS_BROWSER (line 187) | const IS_BROWSER = (() =>
constant PRIVATE (line 1662) | const PRIVATE = [
constant ITEM_ERROR (line 2038) | const ITEM_ERROR = [
constant ITEM_BUSY (line 2043) | const ITEM_BUSY = [
constant ITEM_READY (line 2049) | const ITEM_READY = [ItemStatus.PROCESSING_COMPLETE];
constant PANEL_SPRING_PROPS (line 5882) | const PANEL_SPRING_PROPS = { type: 'spring', damping: 0.6, mass: 7 };
constant ITEM_TRANSLATE_SPRING (line 5986) | const ITEM_TRANSLATE_SPRING = {
constant ITEM_SCALE_SPRING (line 5993) | const ITEM_SCALE_SPRING = 'spring';
method getHeight (line 6292) | get getHeight() {
method setHeight (line 6295) | set setHeight(val) {
method getWidth (line 6298) | get getWidth() {
method setWidth (line 6301) | set setWidth(val) {
constant MAX_FILES_LIMIT (line 8010) | const MAX_FILES_LIMIT = 1000000;
constant PRIVATE_METHODS (line 9446) | const PRIVATE_METHODS = ['fire', '_read', '_write'];
FILE: dist/filepond.js
function _typeof (line 1881) | function _typeof(obj) {
function _jsx (line 1902) | function _jsx(type, props, key, children) {
function _asyncIterator (line 1950) | function _asyncIterator(iterable) {
function _AwaitValue (line 1968) | function _AwaitValue(value) {
function _AsyncGenerator (line 1972) | function _AsyncGenerator(gen) {
function _wrapAsyncGenerator (line 2072) | function _wrapAsyncGenerator(fn) {
function _awaitAsyncGenerator (line 2078) | function _awaitAsyncGenerator(value) {
function _asyncGeneratorDelegate (line 2082) | function _asyncGeneratorDelegate(inner, awaitWrap) {
function asyncGeneratorStep (line 2132) | function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, ar...
function _asyncToGenerator (line 2148) | function _asyncToGenerator(fn) {
function _classCallCheck (line 2168) | function _classCallCheck(instance, Constructor) {
function _defineProperties (line 2174) | function _defineProperties(target, props) {
function _createClass (line 2184) | function _createClass(Constructor, protoProps, staticProps) {
function _defineEnumerableProperties (line 2190) | function _defineEnumerableProperties(obj, descs) {
function _defaults (line 2213) | function _defaults(obj, defaults) {
function _defineProperty (line 2228) | function _defineProperty(obj, key, value) {
function _extends (line 2243) | function _extends() {
function _objectSpread (line 2263) | function _objectSpread(target) {
function ownKeys (line 2284) | function ownKeys(object, enumerableOnly) {
function _objectSpread2 (line 2299) | function _objectSpread2(target) {
function _inherits (line 2323) | function _inherits(subClass, superClass) {
function _inheritsLoose (line 2338) | function _inheritsLoose(subClass, superClass) {
function _getPrototypeOf (line 2344) | function _getPrototypeOf(o) {
function _setPrototypeOf (line 2353) | function _setPrototypeOf(o, p) {
function isNativeReflectConstruct (line 2364) | function isNativeReflectConstruct() {
function _construct (line 2377) | function _construct(Parent, args, Class) {
function _isNativeFunction (line 2394) | function _isNativeFunction(fn) {
function _wrapNativeSuper (line 2398) | function _wrapNativeSuper(Class) {
function _instanceof (line 2432) | function _instanceof(left, right) {
function _interopRequireDefault (line 2440) | function _interopRequireDefault(obj) {
function _interopRequireWildcard (line 2448) | function _interopRequireWildcard(obj) {
function _newArrowCheck (line 2476) | function _newArrowCheck(innerThis, boundThis) {
function _objectDestructuringEmpty (line 2482) | function _objectDestructuringEmpty(obj) {
function _objectWithoutPropertiesLoose (line 2486) | function _objectWithoutPropertiesLoose(source, excluded) {
function _objectWithoutProperties (line 2501) | function _objectWithoutProperties(source, excluded) {
function _assertThisInitialized (line 2522) | function _assertThisInitialized(self) {
function _possibleConstructorReturn (line 2530) | function _possibleConstructorReturn(self, call) {
function _superPropBase (line 2538) | function _superPropBase(object, property) {
function _get (line 2547) | function _get(target, property, receiver) {
function set (line 2568) | function set(target, property, value, receiver) {
function _set (line 2608) | function _set(target, property, value, receiver, isStrict) {
function _taggedTemplateLiteral (line 2618) | function _taggedTemplateLiteral(strings, raw) {
function _taggedTemplateLiteralLoose (line 2632) | function _taggedTemplateLiteralLoose(strings, raw) {
function _temporalRef (line 2641) | function _temporalRef(val, name) {
function _readOnlyError (line 2649) | function _readOnlyError(name) {
function _classNameTDZError (line 2653) | function _classNameTDZError(name) {
function _slicedToArray (line 2659) | function _slicedToArray(arr, i) {
function _slicedToArrayLoose (line 2663) | function _slicedToArrayLoose(arr, i) {
function _toArray (line 2667) | function _toArray(arr) {
function _toConsumableArray (line 2671) | function _toConsumableArray(arr) {
function _arrayWithoutHoles (line 2675) | function _arrayWithoutHoles(arr) {
function _arrayWithHoles (line 2683) | function _arrayWithHoles(arr) {
function _iterableToArray (line 2687) | function _iterableToArray(iter) {
function _iterableToArrayLimit (line 2695) | function _iterableToArrayLimit(arr, i) {
function _iterableToArrayLimitLoose (line 2721) | function _iterableToArrayLimitLoose(arr, i) {
function _nonIterableSpread (line 2733) | function _nonIterableSpread() {
function _nonIterableRest (line 2737) | function _nonIterableRest() {
function _skipFirstGeneratorNext (line 2741) | function _skipFirstGeneratorNext(fn) {
function _toPrimitive (line 2749) | function _toPrimitive(input, hint) {
function _toPropertyKey (line 2762) | function _toPropertyKey(arg) {
function _initializerWarningHelper (line 2768) | function _initializerWarningHelper(descriptor, context) {
function _initializerDefineProperty (line 2777) | function _initializerDefineProperty(target, property, descriptor, contex...
function _applyDecoratedDescriptor (line 2787) | function _applyDecoratedDescriptor(target, property, decorators, descrip...
function _classPrivateFieldLooseKey (line 2821) | function _classPrivateFieldLooseKey(name) {
function _classPrivateFieldLooseBase (line 2825) | function _classPrivateFieldLooseBase(receiver, privateKey) {
function _classPrivateFieldGet (line 2833) | function _classPrivateFieldGet(receiver, privateMap) {
function _classPrivateFieldSet (line 2847) | function _classPrivateFieldSet(receiver, privateMap, value) {
function _classPrivateFieldDestructureSet (line 2867) | function _classPrivateFieldDestructureSet(receiver, privateMap) {
function _classStaticPrivateFieldSpecGet (line 2893) | function _classStaticPrivateFieldSpecGet(receiver, classConstructor, des...
function _classStaticPrivateFieldSpecSet (line 2901) | function _classStaticPrivateFieldSpecSet(receiver, classConstructor, des...
function _classStaticPrivateMethodGet (line 2914) | function _classStaticPrivateMethodGet(receiver, classConstructor, method) {
function _classStaticPrivateMethodSet (line 2922) | function _classStaticPrivateMethodSet() {
function _decorate (line 2926) | function _decorate(decorators, factory, superClass, mixins) {
function _getDecoratorsApi (line 2946) | function _getDecoratorsApi() {
function _createElementDescriptor (line 3261) | function _createElementDescriptor(def) {
function _coalesceGetterSetter (line 3304) | function _coalesceGetterSetter(element, other) {
function _coalesceClassElements (line 3312) | function _coalesceClassElements(elements) {
function _hasDecorators (line 3360) | function _hasDecorators(element) {
function _isDataDescriptor (line 3364) | function _isDataDescriptor(desc) {
function _optionalCallableProperty (line 3368) | function _optionalCallableProperty(obj, name) {
function _classPrivateMethodGet (line 3378) | function _classPrivateMethodGet(receiver, privateSet, fn) {
function _classPrivateMethodSet (line 3386) | function _classPrivateMethodSet() {
function _wrapRegExp (line 3390) | function _wrapRegExp(re, groups) {
method getHeight (line 8876) | get getHeight() {
method setHeight (line 8879) | set setHeight(val) {
method getWidth (line 8882) | get getWidth() {
method setWidth (line 8885) | set setWidth(val) {
FILE: src/js/app/queries.js
constant ITEM_ERROR (line 10) | const ITEM_ERROR = [
constant ITEM_BUSY (line 15) | const ITEM_BUSY = [
constant ITEM_READY (line 21) | const ITEM_READY = [ItemStatus.PROCESSING_COMPLETE];
FILE: src/js/app/utils/createItemAPI.js
constant PRIVATE (line 3) | const PRIVATE = [
FILE: src/js/app/utils/dropAreaDimensions.js
method getHeight (line 4) | get getHeight() {
method setHeight (line 7) | set setHeight(val) {
method getWidth (line 10) | get getWidth() {
method setWidth (line 13) | set setWidth(val) {
FILE: src/js/app/view/item.js
constant ITEM_TRANSLATE_SPRING (line 6) | const ITEM_TRANSLATE_SPRING = {
constant ITEM_SCALE_SPRING (line 13) | const ITEM_SCALE_SPRING = 'spring';
FILE: src/js/app/view/panel.js
constant PANEL_SPRING_PROPS (line 4) | const PANEL_SPRING_PROPS = { type: 'spring', damping: 0.6, mass: 7 };
FILE: src/js/app/view/root.js
constant MAX_FILES_LIMIT (line 23) | const MAX_FILES_LIMIT = 1000000;
FILE: src/js/createAppAPI.js
constant PRIVATE_METHODS (line 2) | const PRIVATE_METHODS = ['fire', '_read', '_write'];
FILE: src/js/utils/isBrowser.js
constant IS_BROWSER (line 1) | const IS_BROWSER = (() => typeof window !== 'undefined' && typeof window...
FILE: types/index.d.ts
type FileStatus (line 9) | enum FileStatus {
type Status (line 21) | enum Status {
type FileOrigin (line 29) | enum FileOrigin {
type ActualFileObject (line 39) | type ActualFileObject = Blob & { readonly lastModified: number; readonly...
class FilePondFile (line 44) | class FilePondFile {
class File (line 88) | class File extends FilePondFile {}
type ServerUrl (line 90) | interface ServerUrl {
type ProgressServerConfigFunction (line 114) | type ProgressServerConfigFunction = (
type ProcessServerChunkTransferOptions (line 127) | interface ProcessServerChunkTransferOptions {
type ProcessServerConfigFunction (line 148) | type ProcessServerConfigFunction = (
type RevertServerConfigFunction (line 179) | type RevertServerConfigFunction = (
type RestoreServerConfigFunction (line 188) | type RestoreServerConfigFunction = (
type LoadServerConfigFunction (line 209) | type LoadServerConfigFunction = (
type FetchServerConfigFunction (line 229) | type FetchServerConfigFunction = (
type RemoveServerConfigFunction (line 249) | type RemoveServerConfigFunction = (
type FilePondInitialFile (line 258) | interface FilePondInitialFile {
type FilePondServerConfigProps (line 275) | interface FilePondServerConfigProps {
type FilePondDragDropProps (line 337) | interface FilePondDragDropProps {
type FilePondLabelProps (line 362) | interface FilePondLabelProps {
type FilePondSvgIconProps (line 534) | interface FilePondSvgIconProps {
type FilePondErrorDescription (line 562) | interface FilePondErrorDescription {
type FilePondCallbackProps (line 568) | interface FilePondCallbackProps {
type FilePondHookProps (line 617) | interface FilePondHookProps {
type FilePondStyleProps (line 638) | interface FilePondStyleProps {
type CaptureAttribute (line 691) | type CaptureAttribute = 'camera' | 'microphone' | 'camcorder';
type FilePondBaseProps (line 693) | interface FilePondBaseProps {
type FilePondOptionProps (line 840) | interface FilePondOptionProps
type FilePondOptions (line 850) | interface FilePondOptions
type FilePondEventPrefixed (line 860) | type FilePondEventPrefixed =
type FilePondEvent (line 877) | type FilePondEvent =
type RemoveFileOptions (line 894) | interface RemoveFileOptions {
type FilePond (line 899) | interface FilePond extends Required<FilePondOptions> {}
class FilePond (line 901) | class FilePond {
Condensed preview — 298 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,378K chars).
[
{
"path": ".babelrc",
"chars": 663,
"preview": "{\n \"retainLines\": true,\n \"presets\": [\n [\"@babel/preset-env\", {\n \"exclude\": [\"transform-typeof-sy"
},
{
"path": ".gitattributes",
"chars": 31,
"preview": "dist/* linguist-vendored=false\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 54,
"preview": "custom: ['https://www.buymeacoffee.com/rikschennink']\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1733,
"preview": "name: Bug report\ntitle: \"[Bug] \"\ndescription: Report an issue with FilePond\nlabels: bug\nbody:\n - type: markdown\n att"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 196,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Stack Overflow\n url: https://stackoverflow.com/questions/tagged/"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 1246,
"preview": "name: Feature request\ntitle: \"[Feature] \"\ndescription: Suggest an idea for this project\nlabels: new feature\nbody:\n - ty"
},
{
"path": ".gitignore",
"chars": 92,
"preview": "npm-debug.log\nnode_modules/\ncoverage/\n.idea\n.vscode/\nupload/\ntest*.html\n_TODO.md\n_RELEASE.md"
},
{
"path": ".nvmrc",
"chars": 8,
"preview": "v10.15.3"
},
{
"path": ".prettierignore",
"chars": 6,
"preview": "dist/*"
},
{
"path": ".prettierrc",
"chars": 164,
"preview": "{\n \"svelteSortOrder\": \"scripts-markup-styles\",\n \"trailingComma\": \"es5\",\n \"tabWidth\": 4,\n \"printWidth\": 100,\n"
},
{
"path": "CHANGELOG.md",
"chars": 29128,
"preview": "# Changelog\n\n## 4.32.12\n\n- Link attribution banner to [filepond.com](https://filepond.com)\n\n## 4.32.11\n\n- Merge fix "
},
{
"path": "LICENSE",
"chars": 1062,
"preview": "MIT License\n\nCopyright (c) 2019 Pqina\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
},
{
"path": "README.md",
"chars": 12010,
"preview": "# [<img src=\"https://github.com/pqina/filepond-github-assets/blob/master/logo.svg\" height=\"44\" alt=\"FilePond\"/>](https:/"
},
{
"path": "banner-cli.js",
"chars": 281,
"preview": "const banner = require('./banner');\nconst pkg = require('./package.json');\nconst args = process.argv.slice(2);\nprocess.s"
},
{
"path": "banner.js",
"chars": 235,
"preview": "module.exports = ({ id, version, license, homepage }) => `/*!\n * ${ id } ${ version }\n * Licensed under ${ license }, ht"
},
{
"path": "dist/filepond.css",
"chars": 26774,
"preview": "/*!\n * FilePond 4.32.12\n * Licensed under MIT, https://opensource.org/licenses/MIT/\n * Please visit https://pqina.nl/fil"
},
{
"path": "dist/filepond.esm.js",
"chars": 296174,
"preview": "/*!\n * FilePond 4.32.12\n * Licensed under MIT, https://opensource.org/licenses/MIT/\n * Please visit https://pqina.nl/fil"
},
{
"path": "dist/filepond.js",
"chars": 439131,
"preview": "/*!\n * FilePond 4.32.12\n * Licensed under MIT, https://opensource.org/licenses/MIT/\n * Please visit https://pqina.nl/fil"
},
{
"path": "index.html",
"chars": 1769,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"widt"
},
{
"path": "jest.config.js",
"chars": 133,
"preview": "module.exports = {\n bail: true,\n verbose: true,\n setupFiles: ['<rootDir>/jest.stubs.js'],\n roots: ['<rootDir"
},
{
"path": "jest.stubs.js",
"chars": 298,
"preview": "const uuid = require('uuid/v4');\n\n// needed because jest doesn't have CSS on window\nif (!window.CSS) window.CSS = {};\nwi"
},
{
"path": "locale/am-et.js",
"chars": 2145,
"preview": "export default {\n labelIdle: 'ፋይሎች ስበው እዚህ ጋር ይልቀቁት ወይም ፋይሉን <span class=\"filepond--label-action\"> ይምረጡ </span>',\n "
},
{
"path": "locale/ar-ar.js",
"chars": 2089,
"preview": "export default {\n labelIdle: 'اسحب و ادرج ملفاتك أو <span class=\"filepond--label-action\"> تصفح </span>',\n labelInv"
},
{
"path": "locale/az-az.js",
"chars": 2258,
"preview": "export default {\n labelIdle: 'Faylınızı Sürüşdürün & Buraxın ya da <span class=\"filepond--label-action\"> Seçin </span"
},
{
"path": "locale/ca-ca.js",
"chars": 2262,
"preview": "export default {\n labelIdle: `Arrossega i deixa anar els teus fitxers o <span class=\"filepond--label-action\"> Navega "
},
{
"path": "locale/cs-cz.js",
"chars": 2280,
"preview": "export default {\n labelIdle: 'Přetáhněte soubor sem (drag&drop) nebo <span class=\"filepond--label-action\"> Vyhledat <"
},
{
"path": "locale/cy-cy.js",
"chars": 2322,
"preview": "export default {\n labelIdle: 'Llusgwch a Gollyngwch eich ffeiliau neu ddefnyddiwch y <span class=\"filepond--label-ac"
},
{
"path": "locale/da-dk.js",
"chars": 2192,
"preview": "export default {\n labelIdle:\n 'Træk & slip filer eller <span class = \"filepond - label-action\"> Gennemse </spa"
},
{
"path": "locale/de-de.js",
"chars": 2209,
"preview": "export default {\n labelIdle: 'Dateien ablegen oder <span class=\"filepond--label-action\"> auswählen </span>',\n labe"
},
{
"path": "locale/el-el.js",
"chars": 2453,
"preview": "export default {\n labelIdle: 'Σύρετε τα αρχεία σας στο πλαίσιο ή <span class=\"filepond--label-action\"> Επιλέξτε </spa"
},
{
"path": "locale/en-en.js",
"chars": 2130,
"preview": "export default {\n labelIdle: 'Drag & Drop your files or <span class=\"filepond--label-action\"> Browse </span>',\n la"
},
{
"path": "locale/es-es.js",
"chars": 2314,
"preview": "export default {\n labelIdle: 'Arrastra y suelta tus archivos o <span class = \"filepond--label-action\"> Examina <span>"
},
{
"path": "locale/et-ee.js",
"chars": 2268,
"preview": "export default {\n labelIdle: 'Lohista oma failid siia või <span class=\"filepond--label-action\"> Sirvi </span>',\n l"
},
{
"path": "locale/fa_ir.js",
"chars": 2181,
"preview": "export default {\n labelIdle: 'فایل را اینجا بکشید و رها کنید، یا <span class=\"filepond--label-action\"> جستجو کنید </s"
},
{
"path": "locale/fi-fi.js",
"chars": 2250,
"preview": "export default {\n labelIdle: 'Vedä ja pudota tiedostoja tai <span class=\"filepond--label-action\"> Selaa </span>',\n "
},
{
"path": "locale/fr-fr.js",
"chars": 2383,
"preview": "export default {\n labelIdle: 'Faites glisser vos fichiers ou <span class = \"filepond--label-action\"> Parcourir </span"
},
{
"path": "locale/he-he.js",
"chars": 2245,
"preview": "export default {\n labelIdle: 'גרור ושחרר את הקבצים כאן או <span class=\"filepond--label-action\"> לחץ כאן לבחירה </span"
},
{
"path": "locale/hr-hr.js",
"chars": 2294,
"preview": "export default {\n labelIdle: 'Ovdje \"ispusti\" datoteku ili <span class=\"filepond--label-action\"> Pretraži </span>',\n "
},
{
"path": "locale/hu-hu.js",
"chars": 2332,
"preview": "export default {\n labelIdle: 'Mozgasd ide a fájlt a feltöltéshez, vagy <span class=\"filepond--label-action\"> tallózás"
},
{
"path": "locale/id-id.js",
"chars": 2316,
"preview": "export default {\n labelIdle: 'Seret & Jatuhkan berkas Anda atau <span class=\"filepond--label-action\">Jelajahi</span>'"
},
{
"path": "locale/it-it.js",
"chars": 2377,
"preview": "export default {\n labelIdle: 'Trascina e rilascia i tuoi file oppure <span class=\"filepond--label-action\"> Sfoglia <s"
},
{
"path": "locale/ja-ja.js",
"chars": 4335,
"preview": "export default {\n labelIdle: 'ファイルをドラッグ&ドロップ又は<span class=\"filepond--label-action\">ファイル選択</span>',\n labelInvalidFi"
},
{
"path": "locale/km-km.js",
"chars": 2184,
"preview": "export default {\n labelIdle: 'ទាញ&ដាក់ហ្វាល់ឯកសាររបស់អ្នក ឬ <span class=\"filepond--label-action\"> ស្វែងរក </span>',\n "
},
{
"path": "locale/ko-kr.js",
"chars": 1891,
"preview": "export default {\n labelIdle: '파일을 드래그 하거나 <span class=\"filepond--label-action\"> 찾아보기 </span>',\n labelInvalidField:"
},
{
"path": "locale/ku-ckb.js",
"chars": 2218,
"preview": "export default {\n labelIdle: 'پەڕگەکان فڕێ بدە ئێرە بۆ بارکردن یان <span class=\"filepond--label-action\"> هەڵبژێرە </s"
},
{
"path": "locale/kur-ckb.js",
"chars": 2396,
"preview": "export default {\n labelIdle:\n 'فایلەکانت ڕابکێشە یان <span class=\"filepond--label-action\"> دیاری بکە </span>',\n "
},
{
"path": "locale/lt-lt.js",
"chars": 2257,
"preview": "export default {\n labelIdle: 'Įdėkite failus čia arba <span class=\"filepond--label-action\"> Ieškokite </span>',\n l"
},
{
"path": "locale/lus-lus.js",
"chars": 2296,
"preview": "export default {\n labelIdle: 'I file hnûklût rawh, emaw <span class=\"filepond--label-action\"> Zawnna </span>',\n la"
},
{
"path": "locale/lv-lv.js",
"chars": 2376,
"preview": "export default {\n labelIdle: 'Ievelciet savus failus vai <span class=\"filepond--label-action\"> pārlūkojiet šeit </spa"
},
{
"path": "locale/nl-nl.js",
"chars": 2276,
"preview": "export default {\n labelIdle: 'Drag & Drop je bestanden of <span class=\"filepond--label-action\"> Bladeren </span>',\n "
},
{
"path": "locale/no_nb.js",
"chars": 2243,
"preview": "export default {\n labelIdle: 'Dra og slipp filene dine, eller <span class=\"filepond--label-action\"> Bla gjennom... </"
},
{
"path": "locale/pl-pl.js",
"chars": 2212,
"preview": "export default {\n labelIdle: 'Przeciągnij i upuść lub <span class=\"filepond--label-action\">wybierz</span> pliki',\n "
},
{
"path": "locale/pt-br.js",
"chars": 2302,
"preview": "export default {\n labelIdle: 'Arraste e solte os arquivos ou <span class=\"filepond--label-action\"> Clique aqui </span"
},
{
"path": "locale/pt-pt.js",
"chars": 2330,
"preview": "export default {\n labelIdle: 'Arraste & Largue os ficheiros ou <span class=\"filepond--label-action\"> Seleccione </spa"
},
{
"path": "locale/ro-ro.js",
"chars": 2350,
"preview": "export default {\n labelIdle: 'Trage și plasează fișiere sau <span class=\"filepond--label-action\"> Caută-le </span>',\n"
},
{
"path": "locale/ru-ru.js",
"chars": 2316,
"preview": "export default {\n labelIdle: 'Перетащите файлы или <span class=\"filepond--label-action\"> выберите </span>',\n label"
},
{
"path": "locale/sk-sk.js",
"chars": 2273,
"preview": "export default {\n labelIdle: 'Natiahnúť súbor (drag&drop) alebo <span class=\"filepond--label-action\"> Vyhľadať </span"
},
{
"path": "locale/sl-si.js",
"chars": 2274,
"preview": "export default {\n labelIdle: 'Povleci in spusti ali <span class=\"filepond--label-action\">izberi</span>',\n labelInv"
},
{
"path": "locale/sr-rs.js",
"chars": 2285,
"preview": "export default {\n labelIdle: 'Овде \"испустите\" датотеку или <span class=\"filepond--label-action\"> Претражи </span>',\n"
},
{
"path": "locale/sv_se.js",
"chars": 2272,
"preview": "export default {\n labelIdle: 'Drag och släpp dina filer eller <span class=\"filepond--label-action\"> Bläddra </span>',"
},
{
"path": "locale/tr-tr.js",
"chars": 2235,
"preview": "export default {\n labelIdle: 'Dosyanızı Sürükleyin & Bırakın ya da <span class=\"filepond--label-action\"> Seçin </span"
},
{
"path": "locale/uk-ua.js",
"chars": 2356,
"preview": "export default {\n labelIdle: 'Перетягніть файли або <span class=\"filepond--label-action\"> виберіть </span>',\n labe"
},
{
"path": "locale/ur-ur.js",
"chars": 2256,
"preview": "export default {\n labelIdle: 'اپنی فائلز ڈریگ کریں یا <span class=\"filepond--label-action\"> براؤز کریں </span>',\n "
},
{
"path": "locale/vi-vi.js",
"chars": 2196,
"preview": "export default {\n labelIdle: 'Kéo thả tệp của bạn hoặc <span class=\"filepond--label-action\"> Tìm kiếm </span>',\n l"
},
{
"path": "locale/zh-cn.js",
"chars": 1711,
"preview": "export default {\n labelIdle: '拖放文件,或者 <span class=\"filepond--label-action\"> 浏览 </span>',\n labelInvalidField: '字段包含"
},
{
"path": "locale/zh-hk.js",
"chars": 1718,
"preview": "export default {\n labelIdle: '拖放檔案,或者 <span class=\"filepond--label-action\"> 瀏覽 </span>',\n labelInvalidField: '不支援此"
},
{
"path": "locale/zh-tw.js",
"chars": 1718,
"preview": "export default {\n labelIdle: '拖放檔案,或者 <span class=\"filepond--label-action\"> 瀏覽 </span>',\n labelInvalidField: '不支援此"
},
{
"path": "package.json",
"chars": 2360,
"preview": "{\n \"name\": \"filepond\",\n \"version\": \"4.32.12\",\n \"description\": \"FilePond, Where files go to stretch their bits.\""
},
{
"path": "rollup.config.js",
"chars": 317,
"preview": "import * as pkg from './package.json';\nimport build from './rollup.scripts';\n\nexport default build(\n\t{\n\t\tid: 'FilePond',"
},
{
"path": "rollup.scripts.js",
"chars": 1086,
"preview": "import babel from 'rollup-plugin-babel';\nimport license from 'rollup-plugin-license';\nimport { terser } from 'rollup-plu"
},
{
"path": "src/css/assistant.css",
"chars": 223,
"preview": ".filepond--assistant {\n position: absolute;\n overflow: hidden;\n height: 1px;\n width: 1px;\n padding: 0;\n "
},
{
"path": "src/css/browser.css",
"chars": 419,
"preview": "/* Hard to override styles */\n.filepond--browser.filepond--browser {\n /* is positioned absolute so it is focusable fo"
},
{
"path": "src/css/data.css",
"chars": 193,
"preview": ".filepond--data {\n position: absolute;\n width: 0;\n height: 0;\n padding: 0;\n margin: 0;\n border: none;\n"
},
{
"path": "src/css/drip.css",
"chars": 727,
"preview": ".filepond--drip {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n overflow: hidden;\n "
},
{
"path": "src/css/drop-label.css",
"chars": 1056,
"preview": ".filepond--drop-label {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n margin: 0;\n color: #4f4f4f"
},
{
"path": "src/css/file-action-button.css",
"chars": 2364,
"preview": "/* Hard to override styles */\n.filepond--file-action-button.filepond--file-action-button {\n font-size: 1em;\n width"
},
{
"path": "src/css/file-info.css",
"chars": 993,
"preview": ".filepond--file-info {\n position: static;\n display: flex;\n flex-direction: column;\n align-items: flex-start;"
},
{
"path": "src/css/file-status.css",
"chars": 735,
"preview": ".filepond--file-status {\n position: static;\n display: flex;\n flex-direction: column;\n align-items: flex-end;"
},
{
"path": "src/css/file-wrapper.css",
"chars": 463,
"preview": "/* Hard to override styles */\n.filepond--file-wrapper.filepond--file-wrapper {\n border: none;\n margin: 0;\n padd"
},
{
"path": "src/css/file.css",
"chars": 3887,
"preview": ".filepond--file {\n position: static;\n display: flex;\n height: 100%;\n align-items: flex-start;\n\n padding: "
},
{
"path": "src/css/hopper.css",
"chars": 403,
"preview": "/* ignore all other interaction elements while dragging a file */\n.filepond--hopper[data-hopper-state='drag-over'] > * {"
},
{
"path": "src/css/item-order.css",
"chars": 195,
"preview": ".filepond--progress-indicator {\n z-index: 103;\n}\n\n.filepond--file-action-button {\n z-index: 102;\n}\n\n.filepond--fil"
},
{
"path": "src/css/item.css",
"chars": 1480,
"preview": ".filepond--item {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n z-index: 1;\n\n padding: 0;\n ma"
},
{
"path": "src/css/list-scroller.css",
"chars": 908,
"preview": "/* normal mode */\n.filepond--list-scroller {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n margin: "
},
{
"path": "src/css/list.css",
"chars": 371,
"preview": "/* hard to overide styles on purpose */\n.filepond--list.filepond--list {\n position: absolute;\n top: 0;\n margin:"
},
{
"path": "src/css/modifiers.css",
"chars": 2220,
"preview": ".filepond--root {\n &[data-style-panel-layout~='integrated'] {\n width: 100%;\n height: 100%;\n max-"
},
{
"path": "src/css/panel-root.css",
"chars": 83,
"preview": ".filepond--panel-root {\n border-radius: 0.5em;\n background-color: #f1f0ef;\n}\n"
},
{
"path": "src/css/panel.css",
"chars": 2561,
"preview": ".filepond--panel {\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n margin: 0;\n\n /* defaults to 100"
},
{
"path": "src/css/progress-indicator.css",
"chars": 577,
"preview": ".filepond--progress-indicator {\n position: static;\n width: 1.25em;\n height: 1.25em;\n\n color: #fff;\n\n /* c"
},
{
"path": "src/css/root-order.css",
"chars": 221,
"preview": ".filepond--list-scroller {\n z-index: 6;\n}\n\n.filepond--drop-label {\n z-index: 5;\n}\n\n.filepond--drip {\n z-index: "
},
{
"path": "src/css/root.css",
"chars": 1869,
"preview": ".filepond--root {\n /* layout*/\n box-sizing: border-box;\n position: relative;\n margin-bottom: 1em;\n\n /* ba"
},
{
"path": "src/css/styles.css",
"chars": 517,
"preview": "@import 'assistant.css';\n@import 'browser.css';\n@import 'data.css';\n@import 'drip.css';\n@import 'drop-label.css';\n@impor"
},
{
"path": "src/js/__tests__/addFile.test.js",
"chars": 1062,
"preview": "import './windowMatchMedia.mock';\nimport { create } from '../index.js';\n\ndescribe('adding files', () => {\n let pond ="
},
{
"path": "src/js/__tests__/callbacks.test.js",
"chars": 3760,
"preview": "import mockConsole from \"jest-mock-console\";\n\nimport './windowMatchMedia.mock';\nimport { create, OptionTypes, FileStatus"
},
{
"path": "src/js/__tests__/contentDisposition.test.js",
"chars": 1847,
"preview": "import { getFileNameFromHeader } from '../utils/getFileInfoFromHeaders';\n\ndescribe('parse filename', () => {\n [\n "
},
{
"path": "src/js/__tests__/createInstance.test.js",
"chars": 1056,
"preview": "import './windowMatchMedia.mock';\nimport { create } from '../index.js';\n\ndescribe('create instance', () => {\n test('w"
},
{
"path": "src/js/__tests__/removeFile.test.js",
"chars": 3081,
"preview": "import './windowMatchMedia.mock';\nimport { create, isSupported } from '../index.js';\n\nconst server = {\n process: (fie"
},
{
"path": "src/js/__tests__/revertUploadOnRemove.test.js",
"chars": 5069,
"preview": "import \"./windowMatchMedia.mock\";\nimport { create } from '../index.js';\nimport { actions } from '../app/actions.js';\n\n/*"
},
{
"path": "src/js/__tests__/server.test.js",
"chars": 2352,
"preview": "import './windowMatchMedia.mock';\nimport { create } from '../index.js';\n\ndescribe('setting server property', () => {\n "
},
{
"path": "src/js/__tests__/setFiles.test.js",
"chars": 2499,
"preview": "import './windowMatchMedia.mock';\nimport { create } from '../index.js';\n\nconst next = cb => {\n setTimeout(() => {\n "
},
{
"path": "src/js/__tests__/windowMatchMedia.mock",
"chars": 639,
"preview": "// Mocking window.matchMedia as described in the jest docks\n// since this is not defined in JSDOM and causes the tests\n/"
},
{
"path": "src/js/app/actions.js",
"chars": 37512,
"preview": "import { isEmpty } from '../utils/isEmpty';\nimport { forin } from '../utils/forin';\nimport { fromCamels } from '../utils"
},
{
"path": "src/js/app/enum/ChunkStatus.js",
"chars": 96,
"preview": "export const ChunkStatus = {\n\tQUEUED: 0,\n\tCOMPLETE: 1,\n\tPROCESSING: 2,\n\tERROR: 3,\n\tWAITING: 4\n};"
},
{
"path": "src/js/app/enum/FileOrigin.js",
"chars": 68,
"preview": "export const FileOrigin = {\n INPUT:1,\n LIMBO:2,\n LOCAL:3\n};"
},
{
"path": "src/js/app/enum/InteractionMethod.js",
"chars": 104,
"preview": "export const InteractionMethod = {\n API: 1,\n DROP: 2,\n BROWSE: 3,\n PASTE: 4,\n NONE: 5\n};\n"
},
{
"path": "src/js/app/enum/ItemStatus.js",
"chars": 222,
"preview": "export const ItemStatus = {\n INIT: 1,\n IDLE: 2,\n PROCESSING_QUEUED: 9,\n PROCESSING: 3,\n PROCESSING_COMPLE"
},
{
"path": "src/js/app/enum/Key.js",
"chars": 53,
"preview": "export const Key = {\n ENTER: 13,\n SPACE: 32\n};\n"
},
{
"path": "src/js/app/enum/Status.js",
"chars": 184,
"preview": "export const Status = {\n EMPTY: 0,\n IDLE: 1, // waiting\n ERROR: 2, // a file is in error state\n BUSY: 3, // "
},
{
"path": "src/js/app/enum/Type.js",
"chars": 247,
"preview": "export const Type = {\n BOOLEAN: 'boolean',\n INT: 'int',\n NUMBER: 'number',\n STRING: 'string',\n ARRAY: 'ar"
},
{
"path": "src/js/app/frame/createPainter.js",
"chars": 1663,
"preview": "export const createPainter = (read, write, fps = 60) => {\n\n const name = '__framePainter';\n\n // set global painter"
},
{
"path": "src/js/app/frame/createRoute.js",
"chars": 381,
"preview": "export const createRoute = (routes, fn) => ({ root, props, actions = [], timestamp, shouldOptimize }) => {\n actions.f"
},
{
"path": "src/js/app/frame/createStore.js",
"chars": 2284,
"preview": "export const createStore = (initialState, queries = [], actions = []) => {\n // internal state\n const state = {\n "
},
{
"path": "src/js/app/frame/createView.js",
"chars": 10894,
"preview": "import { createObject } from '../../utils/createObject';\nimport { createElement } from './utils/createElement';\nimport {"
},
{
"path": "src/js/app/frame/index.js",
"chars": 246,
"preview": "import { createStore } from './createStore';\nimport { createView } from './createView';\nimport { createPainter } from '."
},
{
"path": "src/js/app/frame/mixins/animations.js",
"chars": 1961,
"preview": "import { createAnimator } from '../utils/createAnimator';\nimport { forin } from '../../../utils/forin';\nimport { addGetS"
},
{
"path": "src/js/app/frame/mixins/apis.js",
"chars": 220,
"preview": "import { addGetSet } from './utils/addGetSet';\n\n// add to external api and link to props\n\nexport const apis = ({ mixinCo"
},
{
"path": "src/js/app/frame/mixins/index.js",
"chars": 230,
"preview": "import { animations } from './animations';\nimport { listeners } from './listeners';\nimport { apis } from './apis';\nimpor"
},
{
"path": "src/js/app/frame/mixins/listeners.js",
"chars": 936,
"preview": "import { addEvent } from '../utils/addEvent';\nimport { removeEvent } from '../utils/removeEvent';\n\n// mixin\nexport const"
},
{
"path": "src/js/app/frame/mixins/styles.js",
"chars": 4998,
"preview": "import { getViewRect } from '../utils/getViewRect';\nimport { addGetSet } from './utils/addGetSet';\nimport { isDefined } "
},
{
"path": "src/js/app/frame/mixins/utils/addGetSet.js",
"chars": 676,
"preview": "export const addGetSet = (keys, obj, props, overwrite = false) => {\n obj = Array.isArray(obj) ? obj : [obj];\n obj."
},
{
"path": "src/js/app/frame/utils/AxisEnum.js",
"chars": 58,
"preview": "export const AxisEnum = {\n X: 0,\n Y: 1,\n Z: 2\n};\n"
},
{
"path": "src/js/app/frame/utils/addEvent.js",
"chars": 94,
"preview": "export const addEvent = element => (type, fn) => {\n element.addEventListener(type, fn);\n};\n"
},
{
"path": "src/js/app/frame/utils/animators/easing.js",
"chars": 114,
"preview": "export const easeLinear = t => t;\nexport const easeInOutQuad = t => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t);\n"
},
{
"path": "src/js/app/frame/utils/animators/spring.js",
"chars": 3686,
"preview": "import { createObject } from '../../../../utils/createObject';\nimport { isNumber } from '../../../../utils/isNumber';\n\n/"
},
{
"path": "src/js/app/frame/utils/animators/tween.js",
"chars": 2549,
"preview": "import { createObject } from '../../../../utils/createObject';\nimport { easeInOutQuad } from './easing';\n\nexport const t"
},
{
"path": "src/js/app/frame/utils/appendChild.js",
"chars": 240,
"preview": "export const appendChild = parent => (child, index) => {\n if (typeof index !== 'undefined' && parent.children[index])"
},
{
"path": "src/js/app/frame/utils/appendChildView.js",
"chars": 237,
"preview": "export const appendChildView = (parent, childViews) => (view, index) => {\n \n if (typeof index !== 'undefined') {\n "
},
{
"path": "src/js/app/frame/utils/createAnimator.js",
"chars": 857,
"preview": "import { spring } from './animators/spring';\nimport { tween } from './animators/tween';\n\nconst animator = {\n spring,\n"
},
{
"path": "src/js/app/frame/utils/createElement.js",
"chars": 831,
"preview": "import { forin } from '../../../utils/forin';\nimport { attr } from '../../../utils/attr';\n\nconst ns = 'http://www.w3.org"
},
{
"path": "src/js/app/frame/utils/getChildCount.js",
"chars": 275,
"preview": "import { createElement } from './createElement';\nimport { isBrowser } from '../../../utils/isBrowser';\nconst testElement"
},
{
"path": "src/js/app/frame/utils/getViewRect.js",
"chars": 2165,
"preview": "export const getViewRect = (elementRect, childViews, offset, scale) => {\n const left = offset[0] || elementRect.left;"
},
{
"path": "src/js/app/frame/utils/removeChildView.js",
"chars": 279,
"preview": "export const removeChildView = (parent, childViews) => view => {\n // remove from child views\n childViews.splice(ch"
},
{
"path": "src/js/app/frame/utils/removeEvent.js",
"chars": 100,
"preview": "export const removeEvent = element => (type, fn) => {\n element.removeEventListener(type, fn);\n};\n"
},
{
"path": "src/js/app/frame/utils/updateRect.js",
"chars": 838,
"preview": "export const updateRect = (rect = {}, element = {}, style = {}) => {\n\n if (!element.layoutCalculated) {\n rect."
},
{
"path": "src/js/app/index.js",
"chars": 19945,
"preview": "import { createStore } from './frame/index';\nimport { insertBefore } from '../utils/insertBefore';\nimport { insertAfter "
},
{
"path": "src/js/app/options.js",
"chars": 10711,
"preview": "import { getDecimalSeparator } from '../utils/getDecimalSeparator';\nimport { getThousandsSeparator } from '../utils/getT"
},
{
"path": "src/js/app/queries.js",
"chars": 3339,
"preview": "import { isObject } from '../utils/isObject';\nimport { isFunction } from '../utils/isFunction';\nimport { getItemByQuery "
},
{
"path": "src/js/app/utils/buildURL.js",
"chars": 223,
"preview": "const hasQS = str => /\\?/.test(str);\nexport const buildURL = (...parts) => {\n let url = '';\n parts.forEach(part =>"
},
{
"path": "src/js/app/utils/convertTo.js",
"chars": 1296,
"preview": "import { toArray } from '../../utils/toArray';\nimport { toBoolean } from '../../utils/toBoolean';\nimport { toInt } from "
},
{
"path": "src/js/app/utils/createDragHelper.js",
"chars": 309,
"preview": "export const createDragHelper = (items) => {\n const itemIds = items.map(item => item.id);\n let prevIndex = undefin"
},
{
"path": "src/js/app/utils/createFetchFunction.js",
"chars": 2598,
"preview": "import { sendRequest } from '../../utils/sendRequest';\nimport { createResponse } from '../../utils/createResponse';\nimpo"
},
{
"path": "src/js/app/utils/createFileLoader.js",
"chars": 4533,
"preview": "import { isBase64DataURI } from '../../utils/isBase64DataURI';\nimport { getFilenameFromURL } from '../../utils/getFilena"
},
{
"path": "src/js/app/utils/createFileProcessor.js",
"chars": 6509,
"preview": "import { createPerceivedPerformanceUpdater } from './createPerceivedPerformanceUpdater';\nimport { getRandomNumber } from"
},
{
"path": "src/js/app/utils/createFileProcessorFunction.js",
"chars": 2929,
"preview": "import { sendRequest } from '../../utils/sendRequest';\nimport { createResponse } from '../../utils/createResponse';\nimpo"
},
{
"path": "src/js/app/utils/createFileStub.js",
"chars": 1067,
"preview": "import { getMimeTypeFromBase64DataURI } from '../../utils/getMimeTypeFromBase64DataURI';\nimport { getFilenameFromURL } f"
},
{
"path": "src/js/app/utils/createHopper.js",
"chars": 1844,
"preview": "import { createDragNDropClient } from '../utils/dnd';\n\nexport const createHopper = (scope, validateItems, options) => {\n"
},
{
"path": "src/js/app/utils/createInitialState.js",
"chars": 414,
"preview": "import { createOptions } from './createOptions';\n\nexport const createInitialState = options => ({\n\n // model\n item"
},
{
"path": "src/js/app/utils/createItem.js",
"chars": 13634,
"preview": "import { getUniqueId } from '../../utils/getUniqueId';\nimport { getFilenameWithoutExtension } from '../../utils/getFilen"
},
{
"path": "src/js/app/utils/createItemAPI.js",
"chars": 462,
"preview": "import { copyObjectPropertiesToObject } from '../../utils/copyObjectPropertiesToObject';\n\nconst PRIVATE = [\n 'fire',\n"
},
{
"path": "src/js/app/utils/createOption.js",
"chars": 345,
"preview": "import { getValueByType } from './getValueByType';\n\nexport const createOption = (defaultValue, valueType) => {\n let c"
},
{
"path": "src/js/app/utils/createOptionAPI.js",
"chars": 477,
"preview": "import { fromCamels } from '../../utils/fromCamels';\nimport { forin } from '../../utils/forin';\n\nexport const createOpti"
},
{
"path": "src/js/app/utils/createOptionActions.js",
"chars": 640,
"preview": "import { fromCamels } from '../../utils/fromCamels';\nimport { forin } from '../../utils/forin';\n\nexport const createOpti"
},
{
"path": "src/js/app/utils/createOptionQueries.js",
"chars": 330,
"preview": "import { fromCamels } from '../../utils/fromCamels';\nimport { forin } from '../../utils/forin';\n\nexport const createOpti"
},
{
"path": "src/js/app/utils/createOptions.js",
"chars": 438,
"preview": "import { createObject } from '../../utils/createObject';\nimport { createOption } from './createOption';\nimport { forin }"
},
{
"path": "src/js/app/utils/createPaster.js",
"chars": 2082,
"preview": "import { arrayRemove } from '../../utils/arrayRemove';\nimport { requestDataTransferItems } from './requestDataTransferIt"
},
{
"path": "src/js/app/utils/createPerceivedPerformanceUpdater.js",
"chars": 820,
"preview": "import { getRandomNumber } from '../../utils/getRandomNumber';\n\nexport const createPerceivedPerformanceUpdater = (\n c"
},
{
"path": "src/js/app/utils/createProcessorFunction.js",
"chars": 568,
"preview": "import { isString } from '../../utils/isString';\nimport { createFileProcessorFunction } from './createFileProcessorFunct"
},
{
"path": "src/js/app/utils/createRevertFunction.js",
"chars": 1738,
"preview": "import { sendRequest } from '../../utils/sendRequest';\nimport { createResponse } from '../../utils/createResponse';\nimpo"
},
{
"path": "src/js/app/utils/createServerAPI.js",
"chars": 2084,
"preview": "import { isString } from '../../utils/isString';\nimport { toBoolean } from '../../utils/toBoolean';\nimport { forin } fro"
},
{
"path": "src/js/app/utils/dnd.js",
"chars": 6859,
"preview": "import { forin } from '../../utils/forin';\nimport { getRootNode } from '../../utils/getRootNode';\nimport { requestDataTr"
},
{
"path": "src/js/app/utils/dropAreaDimensions.js",
"chars": 552,
"preview": "export const dropAreaDimensions = {\n height: 0,\n width: 0,\n get getHeight() {\n return this.height;\n }"
},
{
"path": "src/js/app/utils/dynamicLabel.js",
"chars": 153,
"preview": "import { isFunction } from '../../utils/isFunction';\n\nexport const dynamicLabel = (label) => (...params) => isFunction(l"
},
{
"path": "src/js/app/utils/fetchBlob.js",
"chars": 1684,
"preview": "import { sendRequest } from '../../utils/sendRequest';\nimport { createResponse } from '../../utils/createResponse';\nimpo"
},
{
"path": "src/js/app/utils/getActiveItems.js",
"chars": 78,
"preview": "export const getActiveItems = (items) => items.filter(item => !item.archived);"
},
{
"path": "src/js/app/utils/getItemById.js",
"chars": 243,
"preview": "import { getItemIndexByQuery } from './getItemIndexByQuery';\n\nexport const getItemById = (items, itemId) => {\n const "
},
{
"path": "src/js/app/utils/getItemByQuery.js",
"chars": 549,
"preview": "import { isEmpty } from '../../utils/isEmpty';\nimport { isInt } from '../../utils/isInt';\n\nexport const getItemByQuery ="
},
{
"path": "src/js/app/utils/getItemIndexByPosition.js",
"chars": 2059,
"preview": "import getItemsPerRow from './getItemsPerRow';\n\nexport const getItemIndexByPosition = (view, children, positionInView) ="
},
{
"path": "src/js/app/utils/getItemIndexByQuery.js",
"chars": 415,
"preview": "import { isEmpty } from '../../utils/isEmpty';\nimport { isString } from '../../utils/isString';\n\nexport const getItemInd"
},
{
"path": "src/js/app/utils/getItemsPerRow.js",
"chars": 223,
"preview": "export default (horizontalSpace, itemWidth) => {\n // add one pixel leeway, when using percentages for item width tota"
},
{
"path": "src/js/app/utils/getType.js",
"chars": 535,
"preview": "import { isArray } from '../../utils/isArray';\nimport { isInt } from '../../utils/isInt';\nimport { isNull } from '../../"
},
{
"path": "src/js/app/utils/getValueByType.js",
"chars": 902,
"preview": "import { convertTo } from './convertTo';\nimport { getType } from './getType';\n\nexport const getValueByType = (newValue, "
},
{
"path": "src/js/app/utils/hasRoomForItem.js",
"chars": 750,
"preview": "import { getActiveItems } from './getActiveItems';\nexport const hasRoomForItem = state => {\n const count = getActiveI"
},
{
"path": "src/js/app/utils/insertItem.js",
"chars": 577,
"preview": "import { isEmpty } from '../../utils/isEmpty';\nimport { limit } from '../../utils/limit';\nimport { arrayInsert } from '."
},
{
"path": "src/js/app/utils/isAPI.js",
"chars": 346,
"preview": "import { isObject } from '../../utils/isObject';\nimport { isString } from '../../utils/isString';\n\nexport const isAPI = "
},
{
"path": "src/js/app/utils/mergeOptionObject.js",
"chars": 249,
"preview": "import { forin } from '../../utils/forin';\nexport const mergeOptionObject = (originalObject, newObject) => {\n const o"
},
{
"path": "src/js/app/utils/on.js",
"chars": 1269,
"preview": "import { arrayRemove } from '../../utils/arrayRemove';\n\nconst run = (cb, sync) => {\n if (sync) {\n cb();\n }\n"
},
{
"path": "src/js/app/utils/processFileChunked.js",
"chars": 9671,
"preview": "import { sendRequest } from '../../utils/sendRequest';\nimport { createResponse } from '../../utils/createResponse';\nimpo"
},
{
"path": "src/js/app/utils/removeReleasedItems.js",
"chars": 229,
"preview": "import { arrayRemove } from '../../utils/arrayRemove';\n\nexport const removeReleasedItems = (items) => {\n items.forEac"
},
{
"path": "src/js/app/utils/requestDataTransferItems.js",
"chars": 6424,
"preview": "import { guesstimateMimeType } from '../../utils/guesstimateMimeType';\nimport { getExtensionFromFilename } from '../../u"
},
{
"path": "src/js/app/utils/toServerAPI.js",
"chars": 113,
"preview": "import { createServerAPI } from './createServerAPI';\nexport const toServerAPI = value => createServerAPI(value);\n"
},
{
"path": "src/js/app/view/assistant.js",
"chars": 3516,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { attr } from '../../utils/attr';\n\n/**\n * Creates the f"
},
{
"path": "src/js/app/view/blob.js",
"chars": 439,
"preview": "import { createView } from '../frame/index';\n\nexport const blob = createView({\n name: 'drip-blob',\n ignoreRect: tr"
},
{
"path": "src/js/app/view/browser.js",
"chars": 5755,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { attrToggle } from '../../utils/attrToggle';\nimport { "
},
{
"path": "src/js/app/view/data.js",
"chars": 3252,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { createElement } from '../../utils/createElement';\nimp"
},
{
"path": "src/js/app/view/drip.js",
"chars": 1572,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { blob } from './blob';\n\nconst addBlob = ({ root }) => "
},
{
"path": "src/js/app/view/dropLabel.js",
"chars": 2361,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { attr } from '../../utils/attr';\nimport { createElemen"
},
{
"path": "src/js/app/view/file.js",
"chars": 15713,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { progressIndicator } from './progressIndicator';\nimpor"
},
{
"path": "src/js/app/view/fileActionButton.js",
"chars": 1243,
"preview": "import { createView } from '../frame/index';\nimport { attr } from '../../utils/attr';\n\nconst create = ({ root, props }) "
},
{
"path": "src/js/app/view/fileInfo.js",
"chars": 2489,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { toNaturalFileSize } from '../../utils/toNaturalFileSi"
},
{
"path": "src/js/app/view/fileStatus.js",
"chars": 3348,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { toPercentage } from '../../utils/toPercentage';\nimpor"
},
{
"path": "src/js/app/view/fileWrapper.js",
"chars": 1214,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { formatFilename } from '../../utils/formatFilename';\ni"
},
{
"path": "src/js/app/view/item.js",
"chars": 7197,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { fileWrapper } from './fileWrapper';\nimport { panel } "
},
{
"path": "src/js/app/view/list.js",
"chars": 12407,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { InteractionMethod } from '../enum/InteractionMethod';"
},
{
"path": "src/js/app/view/listScroller.js",
"chars": 1888,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { list } from './list';\n\nconst create = ({ root, props "
},
{
"path": "src/js/app/view/panel.js",
"chars": 2750,
"preview": "import { createView } from '../frame/index';\nimport { isBoolean } from '../../utils/isBoolean';\n\nconst PANEL_SPRING_PROP"
},
{
"path": "src/js/app/view/progressIndicator.js",
"chars": 1999,
"preview": "import { createView } from '../frame/index';\nimport { createElement } from '../frame/utils/createElement';\nimport { attr"
},
{
"path": "src/js/app/view/root.js",
"chars": 22761,
"preview": "import { createView, createRoute } from '../frame/index';\nimport { applyFilterChain, applyFilters } from '../../filter';"
},
{
"path": "src/js/createApp.js",
"chars": 372,
"preview": "import { isNode } from './utils/isNode';\nimport { createAppObject } from './createAppObject';\nimport { createAppAtElemen"
},
{
"path": "src/js/createAppAPI.js",
"chars": 272,
"preview": "import { copyObjectPropertiesToObject } from './utils/copyObjectPropertiesToObject';\nconst PRIVATE_METHODS = ['fire', '_"
},
{
"path": "src/js/createAppAtElement.js",
"chars": 2916,
"preview": "import { getAttributesAsObject } from './utils/getAttributesAsObject';\nimport { createAppObject } from './createAppObjec"
}
]
// ... and 98 more files (download for full content)
About this extraction
This page contains the full source code of the pqina/filepond GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 298 files (1.3 MB), approximately 297.5k tokens, and a symbol index with 148 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.