From e35da25763957791fa9d5123f549db6751eccfc8 Mon Sep 17 00:00:00 2001 From: Rik Schennink Date: Fri, 22 Mar 2019 16:18:04 +0100 Subject: [PATCH] initial commit --- .babelrc | 14 + .gitattributes | 0 .github/ISSUE_TEMPLATE.md | 0 .gitignore | 3 +- CHANGELOG.md | 6 + LICENSE | 0 README.md | 7 + banner-cli.js | 8 + banner.js | 8 + index.html | 24 + jest.config.js | 6 + jest.stubs.js | 31 + package-lock.json | 6305 +++++++++++++++++ package.json | 45 +- rollup.config.js | 27 + rollup.scripts.js | 52 + src/css/assistant.scss | 11 + src/css/browser.scss | 16 + src/css/drip.scss | 36 + src/css/drop-label.scss | 50 + src/css/file-action-button.scss | 65 + src/css/file-info.scss | 44 + src/css/file-status.scss | 37 + src/css/file-wrapper.scss | 21 + src/css/file.scss | 171 + src/css/hopper.scss | 15 + src/css/item-order.scss | 15 + src/css/item.scss | 51 + src/css/list-scroller.scss | 46 + src/css/list.scss | 17 + src/css/modifiers.scss | 79 + src/css/panel-root.scss | 4 + src/css/panel.scss | 106 + src/css/progress-indicator.scss | 28 + src/css/root-order.scss | 19 + src/css/root.scss | 61 + src/js/app/actions.js | 911 +++ src/js/app/enum/FileOrigin.js | 5 + src/js/app/enum/InteractionMethod.js | 7 + src/js/app/enum/ItemStatus.js | 11 + src/js/app/enum/Key.js | 4 + src/js/app/enum/Status.js | 7 + src/js/app/enum/Type.js | 11 + src/js/app/frame/createPainter.js | 54 + src/js/app/frame/createRoute.js | 9 + src/js/app/frame/createStore.js | 91 + src/js/app/frame/createView.js | 325 + src/js/app/frame/index.js | 6 + src/js/app/frame/mixins/animations.js | 74 + src/js/app/frame/mixins/apis.js | 7 + src/js/app/frame/mixins/index.js | 11 + src/js/app/frame/mixins/listeners.js | 45 + src/js/app/frame/mixins/styles.js | 199 + src/js/app/frame/mixins/utils/addGetSet.js | 25 + src/js/app/frame/utils/AxisEnum.js | 5 + src/js/app/frame/utils/addEvent.js | 3 + src/js/app/frame/utils/animators/easing.js | 2 + src/js/app/frame/utils/animators/spring.js | 125 + src/js/app/frame/utils/animators/tween.js | 82 + src/js/app/frame/utils/appendChild.js | 7 + src/js/app/frame/utils/appendChildView.js | 10 + src/js/app/frame/utils/createAnimator.js | 27 + src/js/app/frame/utils/createElement.js | 28 + src/js/app/frame/utils/getViewRect.js | 72 + src/js/app/frame/utils/removeChildView.js | 11 + src/js/app/frame/utils/removeEvent.js | 3 + src/js/app/frame/utils/updateRect.js | 25 + src/js/app/index.js | 597 ++ src/js/app/options.js | 170 + src/js/app/queries.js | 77 + src/js/app/utils/convertTo.js | 39 + src/js/app/utils/createFetchFunction.js | 81 + src/js/app/utils/createFileLoader.js | 144 + src/js/app/utils/createFileProcessor.js | 189 + src/js/app/utils/createFileStub.js | 29 + src/js/app/utils/createHopper.js | 74 + src/js/app/utils/createInitialState.js | 16 + src/js/app/utils/createItem.js | 430 ++ src/js/app/utils/createItemAPI.js | 24 + src/js/app/utils/createOption.js | 12 + src/js/app/utils/createOptionAPI.js | 17 + src/js/app/utils/createOptionActions.js | 21 + src/js/app/utils/createOptionQueries.js | 11 + src/js/app/utils/createOptions.js | 15 + src/js/app/utils/createPaster.js | 63 + .../createPerceivedPerformanceUpdater.js | 39 + src/js/app/utils/createProcessorFunction.js | 79 + src/js/app/utils/createRevertFunction.js | 58 + src/js/app/utils/createServerAPI.js | 74 + src/js/app/utils/dnd.js | 269 + src/js/app/utils/dynamicLabel.js | 3 + src/js/app/utils/fetchLocal.js | 62 + src/js/app/utils/getActiveItems.js | 1 + src/js/app/utils/getItemById.js | 9 + src/js/app/utils/getItemByQuery.js | 23 + src/js/app/utils/getItemIndexByPosition.js | 66 + src/js/app/utils/getItemIndexByQuery.js | 17 + src/js/app/utils/getType.js | 28 + src/js/app/utils/getValueByType.js | 32 + src/js/app/utils/hasRoomForItem.js | 23 + src/js/app/utils/insertItem.js | 24 + src/js/app/utils/isAPI.js | 13 + src/js/app/utils/mergeOptionObject.js | 8 + src/js/app/utils/on.js | 39 + src/js/app/utils/removeReleasedItems.js | 9 + src/js/app/utils/requestDataTransferItems.js | 165 + src/js/app/utils/toServerAPI.js | 2 + src/js/app/view/assistant.js | 123 + src/js/app/view/blob.js | 16 + src/js/app/view/browser.js | 136 + src/js/app/view/drip.js | 70 + src/js/app/view/dropLabel.js | 74 + src/js/app/view/file.js | 366 + src/js/app/view/fileActionButton.js | 48 + src/js/app/view/fileInfo.js | 73 + src/js/app/view/fileStatus.js | 100 + src/js/app/view/fileWrapper.js | 75 + src/js/app/view/item.js | 133 + src/js/app/view/list.js | 312 + src/js/app/view/listScroller.js | 67 + src/js/app/view/panel.js | 100 + src/js/app/view/progressIndicator.js | 94 + src/js/app/view/root.js | 588 ++ src/js/createApp.js | 7 + src/js/createAppAPI.js | 10 + src/js/createAppAtElement.js | 96 + src/js/createAppObject.js | 24 + src/js/createAppPlugin.js | 75 + src/js/filter.js | 40 + src/js/index.js | 206 + src/js/tests/addFile.test.js | 40 + src/js/tests/callbacks.test.js | 113 + src/js/tests/createInstance.test.js | 35 + src/js/tests/removeFile.test.js | 53 + src/js/tests/setFiles.test.js | 92 + src/js/utils/arrayInsert.js | 1 + src/js/utils/arrayRemove.js | 1 + src/js/utils/arrayReverse.js | 1 + src/js/utils/attr.js | 6 + src/js/utils/attrToggle.js | 8 + src/js/utils/capitalizeFirstLetter.js | 2 + src/js/utils/composeObject.js | 3 + src/js/utils/copyFile.js | 2 + src/js/utils/copyObjectPropertiesToObject.js | 11 + src/js/utils/createBlob.js | 15 + src/js/utils/createDefaultResponse.js | 12 + src/js/utils/createElement.js | 3 + src/js/utils/createObject.js | 10 + src/js/utils/createResponse.js | 6 + src/js/utils/createWorker.js | 34 + src/js/utils/debounce.js | 27 + src/js/utils/deepCloneObject.js | 13 + src/js/utils/defineProperty.js | 7 + src/js/utils/describeArc.js | 19 + src/js/utils/forEachDelayed.js | 10 + src/js/utils/forin.js | 9 + src/js/utils/formatFilename.js | 1 + src/js/utils/fromCamels.js | 5 + src/js/utils/getAttributesAsObject.js | 82 + .../utils/getBase64DataFromBase64DataURI.js | 7 + src/js/utils/getBlobBuilder.js | 7 + src/js/utils/getBlobFromBase64DataURI.js | 10 + .../getBlobFromByteStringWithMimeType.js | 12 + .../utils/getByteStringFromBase64DataURI.js | 5 + src/js/utils/getDateString.js | 9 + src/js/utils/getDecimalSeparator.js | 4 + src/js/utils/getDomainFromURL.js | 10 + src/js/utils/getExtensionFromFilename.js | 1 + src/js/utils/getFileFromBase64DataURI.js | 11 + src/js/utils/getFileFromBlob.js | 33 + src/js/utils/getFileInfoFromHeaders.js | 54 + src/js/utils/getFilenameFromURL.js | 6 + src/js/utils/getFilenameWithoutExtension.js | 2 + src/js/utils/getMimeTypeFromBase64DataURI.js | 3 + src/js/utils/getNonNumeric.js | 1 + .../utils/getNumericAspectRatioFromString.js | 12 + src/js/utils/getParameters.js | 6 + src/js/utils/getRandomNumber.js | 2 + src/js/utils/getRootNode.js | 2 + src/js/utils/getThousandsSeparator.js | 14 + src/js/utils/getUniqueId.js | 4 + src/js/utils/guesstimateExtension.js | 39 + src/js/utils/guesstimateMimeType.js | 23 + src/js/utils/hasQueryString.js | 1 + src/js/utils/insertAfter.js | 6 + src/js/utils/insertBefore.js | 1 + src/js/utils/isArray.js | 1 + src/js/utils/isBase64DataURI.js | 4 + src/js/utils/isBoolean.js | 1 + src/js/utils/isDefined.js | 1 + src/js/utils/isEmpty.js | 1 + src/js/utils/isExternalURL.js | 4 + src/js/utils/isFile.js | 1 + src/js/utils/isFunction.js | 1 + src/js/utils/isInt.js | 3 + src/js/utils/isNode.js | 1 + src/js/utils/isNull.js | 1 + src/js/utils/isNumber.js | 1 + src/js/utils/isObject.js | 1 + src/js/utils/isString.js | 1 + src/js/utils/leftPad.js | 2 + src/js/utils/limit.js | 1 + src/js/utils/loadImage.js | 11 + src/js/utils/lowerCaseFirstLetter.js | 2 + src/js/utils/percentageArc.js | 19 + src/js/utils/polarToCartesian.js | 7 + src/js/utils/renameFile.js | 6 + src/js/utils/replaceInString.js | 7 + src/js/utils/resetFileInput.js | 30 + src/js/utils/sendRequest.js | 129 + src/js/utils/text.js | 9 + src/js/utils/toArray.js | 17 + src/js/utils/toBoolean.js | 2 + src/js/utils/toBytes.js | 27 + src/js/utils/toCamels.js | 4 + src/js/utils/toFloat.js | 2 + src/js/utils/toFunctionReference.js | 12 + src/js/utils/toInt.js | 2 + src/js/utils/toNaturalFileSize.js | 34 + src/js/utils/toNumber.js | 7 + src/js/utils/toPercentage.js | 1 + src/js/utils/toString.js | 1 + src/js/utils/trim.js | 1 + 223 files changed, 16769 insertions(+), 7 deletions(-) create mode 100644 .babelrc mode change 100644 => 100755 .gitattributes mode change 100644 => 100755 .github/ISSUE_TEMPLATE.md mode change 100644 => 100755 .gitignore mode change 100644 => 100755 CHANGELOG.md mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md create mode 100644 banner-cli.js create mode 100644 banner.js create mode 100644 index.html create mode 100644 jest.config.js create mode 100644 jest.stubs.js create mode 100644 package-lock.json mode change 100644 => 100755 package.json create mode 100644 rollup.config.js create mode 100644 rollup.scripts.js create mode 100644 src/css/assistant.scss create mode 100644 src/css/browser.scss create mode 100644 src/css/drip.scss create mode 100644 src/css/drop-label.scss create mode 100644 src/css/file-action-button.scss create mode 100644 src/css/file-info.scss create mode 100644 src/css/file-status.scss create mode 100644 src/css/file-wrapper.scss create mode 100644 src/css/file.scss create mode 100644 src/css/hopper.scss create mode 100644 src/css/item-order.scss create mode 100644 src/css/item.scss create mode 100644 src/css/list-scroller.scss create mode 100644 src/css/list.scss create mode 100644 src/css/modifiers.scss create mode 100644 src/css/panel-root.scss create mode 100644 src/css/panel.scss create mode 100644 src/css/progress-indicator.scss create mode 100644 src/css/root-order.scss create mode 100644 src/css/root.scss create mode 100644 src/js/app/actions.js create mode 100644 src/js/app/enum/FileOrigin.js create mode 100644 src/js/app/enum/InteractionMethod.js create mode 100644 src/js/app/enum/ItemStatus.js create mode 100644 src/js/app/enum/Key.js create mode 100644 src/js/app/enum/Status.js create mode 100644 src/js/app/enum/Type.js create mode 100644 src/js/app/frame/createPainter.js create mode 100644 src/js/app/frame/createRoute.js create mode 100644 src/js/app/frame/createStore.js create mode 100644 src/js/app/frame/createView.js create mode 100644 src/js/app/frame/index.js create mode 100644 src/js/app/frame/mixins/animations.js create mode 100644 src/js/app/frame/mixins/apis.js create mode 100644 src/js/app/frame/mixins/index.js create mode 100644 src/js/app/frame/mixins/listeners.js create mode 100644 src/js/app/frame/mixins/styles.js create mode 100644 src/js/app/frame/mixins/utils/addGetSet.js create mode 100644 src/js/app/frame/utils/AxisEnum.js create mode 100644 src/js/app/frame/utils/addEvent.js create mode 100644 src/js/app/frame/utils/animators/easing.js create mode 100644 src/js/app/frame/utils/animators/spring.js create mode 100644 src/js/app/frame/utils/animators/tween.js create mode 100644 src/js/app/frame/utils/appendChild.js create mode 100644 src/js/app/frame/utils/appendChildView.js create mode 100644 src/js/app/frame/utils/createAnimator.js create mode 100644 src/js/app/frame/utils/createElement.js create mode 100644 src/js/app/frame/utils/getViewRect.js create mode 100644 src/js/app/frame/utils/removeChildView.js create mode 100644 src/js/app/frame/utils/removeEvent.js create mode 100644 src/js/app/frame/utils/updateRect.js create mode 100644 src/js/app/index.js create mode 100644 src/js/app/options.js create mode 100644 src/js/app/queries.js create mode 100644 src/js/app/utils/convertTo.js create mode 100644 src/js/app/utils/createFetchFunction.js create mode 100644 src/js/app/utils/createFileLoader.js create mode 100644 src/js/app/utils/createFileProcessor.js create mode 100644 src/js/app/utils/createFileStub.js create mode 100644 src/js/app/utils/createHopper.js create mode 100644 src/js/app/utils/createInitialState.js create mode 100644 src/js/app/utils/createItem.js create mode 100644 src/js/app/utils/createItemAPI.js create mode 100644 src/js/app/utils/createOption.js create mode 100644 src/js/app/utils/createOptionAPI.js create mode 100644 src/js/app/utils/createOptionActions.js create mode 100644 src/js/app/utils/createOptionQueries.js create mode 100644 src/js/app/utils/createOptions.js create mode 100644 src/js/app/utils/createPaster.js create mode 100644 src/js/app/utils/createPerceivedPerformanceUpdater.js create mode 100644 src/js/app/utils/createProcessorFunction.js create mode 100644 src/js/app/utils/createRevertFunction.js create mode 100644 src/js/app/utils/createServerAPI.js create mode 100644 src/js/app/utils/dnd.js create mode 100644 src/js/app/utils/dynamicLabel.js create mode 100644 src/js/app/utils/fetchLocal.js create mode 100644 src/js/app/utils/getActiveItems.js create mode 100644 src/js/app/utils/getItemById.js create mode 100644 src/js/app/utils/getItemByQuery.js create mode 100644 src/js/app/utils/getItemIndexByPosition.js create mode 100644 src/js/app/utils/getItemIndexByQuery.js create mode 100644 src/js/app/utils/getType.js create mode 100644 src/js/app/utils/getValueByType.js create mode 100644 src/js/app/utils/hasRoomForItem.js create mode 100644 src/js/app/utils/insertItem.js create mode 100644 src/js/app/utils/isAPI.js create mode 100644 src/js/app/utils/mergeOptionObject.js create mode 100644 src/js/app/utils/on.js create mode 100644 src/js/app/utils/removeReleasedItems.js create mode 100644 src/js/app/utils/requestDataTransferItems.js create mode 100644 src/js/app/utils/toServerAPI.js create mode 100644 src/js/app/view/assistant.js create mode 100644 src/js/app/view/blob.js create mode 100644 src/js/app/view/browser.js create mode 100644 src/js/app/view/drip.js create mode 100644 src/js/app/view/dropLabel.js create mode 100644 src/js/app/view/file.js create mode 100644 src/js/app/view/fileActionButton.js create mode 100644 src/js/app/view/fileInfo.js create mode 100644 src/js/app/view/fileStatus.js create mode 100644 src/js/app/view/fileWrapper.js create mode 100644 src/js/app/view/item.js create mode 100644 src/js/app/view/list.js create mode 100644 src/js/app/view/listScroller.js create mode 100644 src/js/app/view/panel.js create mode 100644 src/js/app/view/progressIndicator.js create mode 100644 src/js/app/view/root.js create mode 100644 src/js/createApp.js create mode 100644 src/js/createAppAPI.js create mode 100644 src/js/createAppAtElement.js create mode 100644 src/js/createAppObject.js create mode 100644 src/js/createAppPlugin.js create mode 100644 src/js/filter.js create mode 100644 src/js/index.js create mode 100644 src/js/tests/addFile.test.js create mode 100644 src/js/tests/callbacks.test.js create mode 100644 src/js/tests/createInstance.test.js create mode 100644 src/js/tests/removeFile.test.js create mode 100644 src/js/tests/setFiles.test.js create mode 100644 src/js/utils/arrayInsert.js create mode 100644 src/js/utils/arrayRemove.js create mode 100644 src/js/utils/arrayReverse.js create mode 100644 src/js/utils/attr.js create mode 100644 src/js/utils/attrToggle.js create mode 100644 src/js/utils/capitalizeFirstLetter.js create mode 100644 src/js/utils/composeObject.js create mode 100644 src/js/utils/copyFile.js create mode 100644 src/js/utils/copyObjectPropertiesToObject.js create mode 100644 src/js/utils/createBlob.js create mode 100644 src/js/utils/createDefaultResponse.js create mode 100644 src/js/utils/createElement.js create mode 100644 src/js/utils/createObject.js create mode 100644 src/js/utils/createResponse.js create mode 100644 src/js/utils/createWorker.js create mode 100644 src/js/utils/debounce.js create mode 100644 src/js/utils/deepCloneObject.js create mode 100644 src/js/utils/defineProperty.js create mode 100644 src/js/utils/describeArc.js create mode 100644 src/js/utils/forEachDelayed.js create mode 100644 src/js/utils/forin.js create mode 100644 src/js/utils/formatFilename.js create mode 100644 src/js/utils/fromCamels.js create mode 100644 src/js/utils/getAttributesAsObject.js create mode 100644 src/js/utils/getBase64DataFromBase64DataURI.js create mode 100644 src/js/utils/getBlobBuilder.js create mode 100644 src/js/utils/getBlobFromBase64DataURI.js create mode 100644 src/js/utils/getBlobFromByteStringWithMimeType.js create mode 100644 src/js/utils/getByteStringFromBase64DataURI.js create mode 100644 src/js/utils/getDateString.js create mode 100644 src/js/utils/getDecimalSeparator.js create mode 100644 src/js/utils/getDomainFromURL.js create mode 100644 src/js/utils/getExtensionFromFilename.js create mode 100644 src/js/utils/getFileFromBase64DataURI.js create mode 100644 src/js/utils/getFileFromBlob.js create mode 100644 src/js/utils/getFileInfoFromHeaders.js create mode 100644 src/js/utils/getFilenameFromURL.js create mode 100644 src/js/utils/getFilenameWithoutExtension.js create mode 100644 src/js/utils/getMimeTypeFromBase64DataURI.js create mode 100644 src/js/utils/getNonNumeric.js create mode 100644 src/js/utils/getNumericAspectRatioFromString.js create mode 100644 src/js/utils/getParameters.js create mode 100644 src/js/utils/getRandomNumber.js create mode 100644 src/js/utils/getRootNode.js create mode 100644 src/js/utils/getThousandsSeparator.js create mode 100644 src/js/utils/getUniqueId.js create mode 100644 src/js/utils/guesstimateExtension.js create mode 100644 src/js/utils/guesstimateMimeType.js create mode 100644 src/js/utils/hasQueryString.js create mode 100644 src/js/utils/insertAfter.js create mode 100644 src/js/utils/insertBefore.js create mode 100644 src/js/utils/isArray.js create mode 100644 src/js/utils/isBase64DataURI.js create mode 100644 src/js/utils/isBoolean.js create mode 100644 src/js/utils/isDefined.js create mode 100644 src/js/utils/isEmpty.js create mode 100644 src/js/utils/isExternalURL.js create mode 100644 src/js/utils/isFile.js create mode 100644 src/js/utils/isFunction.js create mode 100644 src/js/utils/isInt.js create mode 100644 src/js/utils/isNode.js create mode 100644 src/js/utils/isNull.js create mode 100644 src/js/utils/isNumber.js create mode 100644 src/js/utils/isObject.js create mode 100644 src/js/utils/isString.js create mode 100644 src/js/utils/leftPad.js create mode 100644 src/js/utils/limit.js create mode 100644 src/js/utils/loadImage.js create mode 100644 src/js/utils/lowerCaseFirstLetter.js create mode 100644 src/js/utils/percentageArc.js create mode 100644 src/js/utils/polarToCartesian.js create mode 100644 src/js/utils/renameFile.js create mode 100644 src/js/utils/replaceInString.js create mode 100644 src/js/utils/resetFileInput.js create mode 100644 src/js/utils/sendRequest.js create mode 100644 src/js/utils/text.js create mode 100644 src/js/utils/toArray.js create mode 100644 src/js/utils/toBoolean.js create mode 100644 src/js/utils/toBytes.js create mode 100644 src/js/utils/toCamels.js create mode 100644 src/js/utils/toFloat.js create mode 100644 src/js/utils/toFunctionReference.js create mode 100644 src/js/utils/toInt.js create mode 100644 src/js/utils/toNaturalFileSize.js create mode 100644 src/js/utils/toNumber.js create mode 100644 src/js/utils/toPercentage.js create mode 100644 src/js/utils/toString.js create mode 100644 src/js/utils/trim.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..bd1a1efb --- /dev/null +++ b/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + ["@babel/preset-env", { + "exclude": ["transform-typeof-symbol"], + "modules": false + }] + ], + "plugins": [ + ["@babel/plugin-proposal-object-rest-spread", { + "loose": true, + "useBuiltIns": true + }] + ] +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes old mode 100644 new mode 100755 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 5c96d0d9..67fe8e3f --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -npm-debug.log \ No newline at end of file +npm-debug.log +node_modules/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md old mode 100644 new mode 100755 index f3436798..fc971c61 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 4.3.4 + +- Add source code +- Add build scripts + + ## 4.3.3 - Fix issue where aborting a file load while the file was being prepared (for instance, encoded) did not work. diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 6ea465ec..3c4915ce --- a/README.md +++ b/README.md @@ -120,6 +120,13 @@ Or get it from a CDN: [Getting started with FilePond](https://pqina.nl/filepond/docs/patterns/getting-started/) +## Build + +``` +npm run build +``` + + ## Publications * [Smooth file uploading with React and FilePond](https://itnext.io/uploading-files-with-react-and-filepond-f8a798308557) diff --git a/banner-cli.js b/banner-cli.js new file mode 100644 index 00000000..2f990bd4 --- /dev/null +++ b/banner-cli.js @@ -0,0 +1,8 @@ +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); +}); \ No newline at end of file diff --git a/banner.js b/banner.js new file mode 100644 index 00000000..2c0c3381 --- /dev/null +++ b/banner.js @@ -0,0 +1,8 @@ +module.exports = ({ id, version, license, homepage }) => `/*! + * ${ id } ${ version } + * Licensed under ${ license }, https://opensource.org/licenses/${ license }/ + * Please visit ${ homepage } for details. + */ + +/* eslint-disable */ +`; \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..b1802130 --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + FilePond Demo + + + + + + + + + + + + \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..9b1de8f2 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + bail: true, + verbose: true, + setupFiles: ['/jest.stubs.js'], + roots: ['/js'] +}; \ No newline at end of file diff --git a/jest.stubs.js b/jest.stubs.js new file mode 100644 index 00000000..acc50ca7 --- /dev/null +++ b/jest.stubs.js @@ -0,0 +1,31 @@ +const uuid = require('uuid/v4'); + +window.URL.createObjectURL = (blob) => { + return `blob:${serializeURL(location.origin)}/${uuid()}`; +} + +window.URL.revokeObjectURL = (url) => {} + +// var _createObjectURL = window.URL.createObjectURL; +// Object.defineProperty(window.URL, 'createObjectURL', { +// set: function (value) { +// console.trace('set createObjectURL') +// _createObjectURL = value; +// }, +// get: function () { +// console.trace('get createObjectURL') +// return _createObjectURL; +// } +// }) + +// var _URL = window.URL; +// Object.defineProperty(window, 'URL', { +// set: function (value) { +// console.trace('set URL') +// _URL = value; +// }, +// get: function () { +// console.trace('get URL') +// return _URL; +// } +// }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..55d99600 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6305 @@ +{ + "name": "filepond", + "version": "4.3.4", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.0.tgz", + "integrity": "sha512-Dzl7U0/T69DFOTwqz/FJdnOSWS57NpjNfCwMKHABr589Lg8uX1RrlBIJ7L5Dubt/xkLsx0xH5EBFzlBVes1ayA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helpers": "^7.4.0", + "@babel/parser": "^7.4.0", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.0", + "@babel/types": "^7.4.0", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-call-delegate": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.0.tgz", + "integrity": "sha512-SdqDfbVdNQCBp3WhK2mNdDvHd3BD6qbmIc43CAyjnsfCmgHMeqgDcM3BzY2lchi7HBJGJ2CVdynLWbezaE4mmQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.4.0", + "@babel/traverse": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/helper-define-map": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.0.tgz", + "integrity": "sha512-wAhQ9HdnLIywERVcSvX40CEJwKdAa1ID4neI9NXQPDOHwwA+57DqwLiPEVy2AIyWzAk0CQ8qx4awO0VUURwLtA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.4.0", + "lodash": "^4.17.11" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.0.tgz", + "integrity": "sha512-/NErCuoe/et17IlAQFKWM24qtyYYie7sFIrW/tIQXpck6vAu2hhtYYsKLBWQV+BQZMbcIYPU/QMYuTufrY4aQw==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz", + "integrity": "sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/template": "^7.2.2", + "@babel/types": "^7.2.2", + "lodash": "^4.17.10" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", + "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.0.tgz", + "integrity": "sha512-PVwCVnWWAgnal+kJ+ZSAphzyl58XrFeSKSAJRiqg5QToTsjL+Xu1f9+RJ+d+Q0aPhPfBGaYfkox66k86thxNSg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "dev": true, + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.2.tgz", + "integrity": "sha512-gQR1eQeroDzFBikhrCccm5Gs2xBjZ57DNjGbqTaHo911IpmSxflOQWMAHPw/TXk8L3isv7s9lYzUkexOeTQUYg==", + "dev": true, + "requires": { + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.2.tgz", + "integrity": "sha512-9fJTDipQFvlfSVdD/JBtkiY0br9BtfvW2R8wo6CX/Ej2eMuV0gWPk1M67Mt3eggQvBqYW1FCEk8BN7WvGm/g5g==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.0.tgz", + "integrity": "sha512-uTNi8pPYyUH2eWHyYWWSYJKwKg34hhgl4/dbejEjL+64OhbHjTX7wEVWMQl82tEmdDsGeu77+s8HHLS627h6OQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.0.tgz", + "integrity": "sha512-h/KjEZ3nK9wv1P1FSNb9G079jXrNYR0Ko+7XkOx85+gM24iZbPn0rh4vCftk+5QKY7y1uByFataBTmX7irEF1w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.5.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.0.tgz", + "integrity": "sha512-EeaFdCeUULM+GPFEsf7pFcNSxM7hYjoj5fiYbyuiXobW4JhFnjAv9OWzNwHyHcKoPNpAfeRDuW6VyaXEDUBa7g==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.0.tgz", + "integrity": "sha512-AWyt3k+fBXQqt2qb9r97tn3iBwFpiv9xdAiG+Gr2HpAZpuayvbL55yWrsV3MyHvXk/4vmSiedhDRl1YI2Iy5nQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.11" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.0.tgz", + "integrity": "sha512-XGg1Mhbw4LDmrO9rSTNe+uI79tQPdGs0YASlxgweYRLZqo/EQktjaOV4tchL/UZbM0F+/94uOipmdNGoaGOEYg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.4.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.0.tgz", + "integrity": "sha512-HySkoatyYTY3ZwLI8GGvkRWCFrjAGXUHur5sMecmCIdIharnlcWWivOqDJI76vvmVZfzwb6G08NREsrY96RhGQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz", + "integrity": "sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.0.tgz", + "integrity": "sha512-vWdfCEYLlYSxbsKj5lGtzA49K3KANtb8qCPQ1em07txJzsBwY+cKJzBHizj5fl3CCx7vt+WPdgDLTHmydkbQSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz", + "integrity": "sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.0.tgz", + "integrity": "sha512-iWKAooAkipG7g1IY0eah7SumzfnIT3WNhT4uYB2kIsvHnNSB6MDYVa5qyICSwaTBDBY2c4SnJ3JtEa6ltJd6Jw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.0.tgz", + "integrity": "sha512-gjPdHmqiNhVoBqus5qK60mWPp1CmYWp/tkh11mvb0rrys01HycEGD7NvvSoKXlWEfSM9TcL36CpsK8ElsADptQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.4.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.2.tgz", + "integrity": "sha512-NsAuliSwkL3WO2dzWTOL1oZJHm0TM8ZY8ZSxk2ANyKkt5SQlToGA4pzctmq1BEjoacurdwZ3xp2dCQWJkME0gQ==", + "dev": true, + "requires": { + "regexp-tree": "^0.1.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.0.tgz", + "integrity": "sha512-6ZKNgMQmQmrEX/ncuCwnnw1yVGoaOW5KpxNhoWI7pCQdA0uZ0HqHGqenCUIENAnxRjy2WwNQ30gfGdIgqJXXqw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.0.tgz", + "integrity": "sha512-Xqv6d1X+doyiuCGDoVJFtlZx0onAX0tnc3dY8w71pv/O0dODAbusVv2Ale3cGOwfiyi895ivOBhYa9DhAM8dUA==", + "dev": true, + "requires": { + "@babel/helper-call-delegate": "^7.4.0", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.0.tgz", + "integrity": "sha512-SZ+CgL4F0wm4npojPU6swo/cK4FcbLgxLd4cWpHaNXY/NJ2dpahODCqBbAwb2rDmVszVb3SSjnk9/vik3AYdBw==", + "dev": true, + "requires": { + "regenerator-transform": "^0.13.4" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz", + "integrity": "sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz", + "integrity": "sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } + }, + "@babel/preset-env": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.2.tgz", + "integrity": "sha512-OEz6VOZaI9LW08CWVS3d9g/0jZA6YCn1gsKIy/fut7yZCJti5Lm1/Hi+uo/U+ODm7g4I6gULrCP+/+laT8xAsA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.4.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.0", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.4.0", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.4.0", + "@babel/plugin-transform-classes": "^7.4.0", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.4.0", + "@babel/plugin-transform-dotall-regex": "^7.2.0", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.4.0", + "@babel/plugin-transform-function-name": "^7.2.0", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.4.0", + "@babel/plugin-transform-modules-systemjs": "^7.4.0", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.2", + "@babel/plugin-transform-new-target": "^7.4.0", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.4.0", + "@babel/plugin-transform-regenerator": "^7.4.0", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.2.0", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.2.0", + "@babel/types": "^7.4.0", + "browserslist": "^4.4.2", + "core-js-compat": "^3.0.0", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.3.0" + } + }, + "@babel/template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/traverse": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.0.tgz", + "integrity": "sha512-/DtIHKfyg2bBKnIN+BItaIlEg5pjAnzHOIQe5w+rHAw/rg9g0V7T4rqPX8BJPfW11kt3koyjAnTNwCzb28Y1PA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "11.11.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.5.tgz", + "integrity": "sha512-pz6wNe/XwyesgfVX7P6B0hY3TnTAYXk6KSTLdpQfbuq3be+hnMoCuFzE+yLTskPdBwmNiGRL2TAsnF09aRugvQ==", + "dev": true + }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async-each": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.2.tgz", + "integrity": "sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg==", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "autoprefixer": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.5.0.tgz", + "integrity": "sha512-hMKcyHsZn5+qL6AUeP3c8OyuteZ4VaUlg+fWbyl8z7PqsKHF/Bf8/px3K6AT8aMzDkBo8Bc11245MM+itDBOxQ==", + "dev": true, + "requires": { + "browserslist": "^4.4.2", + "caniuse-lite": "^1.0.30000947", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.14", + "postcss-value-parser": "^3.3.1" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.0.tgz", + "integrity": "sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw==", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browserslist": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.5.2.tgz", + "integrity": "sha512-zmJVLiKLrzko0iszd/V4SsjTaomFeoVzQGYYOYgRgsbh7WNh95RgDB0CmBdFWYs/3MyFSt69NypjL/h3iaddKQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000951", + "electron-to-chromium": "^1.3.116", + "node-releases": "^1.1.11" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.0.0.tgz", + "integrity": "sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30000951", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000951.tgz", + "integrity": "sha512-eRhP+nQ6YUkIcNQ6hnvdhMkdc7n3zadog0KXNRxAZTT2kHjUb1yGn71OrPhSn8MOvlX97g5CR97kGVj8fMsXWg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.4.tgz", + "integrity": "sha512-ZVuFiB9IGOHqu+Jh7B7fSTmzsfDmUtC6yqd/V72UPQeXbNHONbMV0chOXtLXjsP3NvdUsHTNMg02NtXPDaSmNQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.0.tgz", + "integrity": "sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "commenting": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/commenting/-/commenting-1.0.5.tgz", + "integrity": "sha512-U7qGbcDLSNpOcV3RQRKHp7hFpy9WUmfawbkPdS4R2RhrSu4dOF85QQpx/Zjcv7uLF6tWSUKEKUIkxknPCrVjwg==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.0.tgz", + "integrity": "sha512-WBmxlgH2122EzEJ6GH8o9L/FeoUKxxxZ6q6VUxoTlsE4EvbTWKJb447eyVxTEuq0LpXjlq/kCB2qgBvsYRkLvQ==", + "dev": true + }, + "core-js-compat": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.0.0.tgz", + "integrity": "sha512-W/Ppz34uUme3LmXWjMgFlYyGnbo1hd9JvA0LNQ4EmieqVjg2GPYbj3H6tcdP2QGPGWdRKUqZVbVKLNIFVs/HiA==", + "dev": true, + "requires": { + "browserslist": "^4.5.1", + "core-js": "3.0.0", + "core-js-pure": "3.0.0", + "semver": "^5.6.0" + } + }, + "core-js-pure": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.0.0.tgz", + "integrity": "sha512-yPiS3fQd842RZDgo/TAKGgS0f3p2nxssF1H65DIZvZv0Od5CygP8puHXn3IQiM/39VAvgCbdaMQpresrbGgt9g==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.1.0.tgz", + "integrity": "sha512-kCNPvthka8gvLtzAxQXvWo4FxqRB+ftRZyPZNuab5ngvM9Y7yw7hbEysglptLgpkGX9nAOKTBVkHUAe8xtYR6Q==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "lodash.get": "^4.4.2", + "parse-json": "^4.0.0" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-select": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", + "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^2.1.2", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-tree": { + "version": "1.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", + "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + }, + "css-unit-converter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", + "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=", + "dev": true + }, + "css-url-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz", + "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=", + "dev": true + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "dev": true, + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", + "dev": true + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true + }, + "csso": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", + "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.29" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", + "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + } + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "dependency-graph": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.8.0.tgz", + "integrity": "sha512-DCvzSq2UiMsuLnj/9AL484ummEgLtZIcRS7YvtO38QnpX3vqh9nJ8P+zhu8Ja+SmLrBHO2iDbva20jq38qvBkQ==", + "dev": true + }, + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "electron-to-chromium": { + "version": "1.3.119", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.119.tgz", + "integrity": "sha512-3mtqcAWa4HgG+Djh/oNXlPH0cOH6MmtwxN1nHSaReb9P0Vn51qYPqYwLeoSuAX9loU1wrOBhFbiX3CkeIxPfgg==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estree-walker": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.0.tgz", + "integrity": "sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-glob": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.6.tgz", + "integrity": "sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w==", + "dev": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + } + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "requires": { + "globule": "^1.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + }, + "globby": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.1.0.tgz", + "integrity": "sha512-VtYjhHr7ncls724Of5W6Kaahz0ag7dB4G62/2HsN+xEKG6SrPzM1AJMerGxQTwJGnN9reeyxdvXbuZYpfssCvg==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.1", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", + "dev": true + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jest-worker": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.4.0.tgz", + "integrity": "sha512-BH9X/klG9vxwoO99ZBUbZFfV8qO0XNZ5SIiCyYK2zOuJBl6YJVAeNIQjcoOVNu4HGEHeYEKsUWws8kSlSbZ9YQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "dev": true + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.hasin": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.hasin/-/lodash.hasin-4.5.2.tgz", + "integrity": "sha1-+R41I3jSHvcJC552h8LKNcW01So=", + "dev": true + }, + "lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=", + "dev": true + }, + "lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", + "dev": true + }, + "lodash.omitby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.omitby/-/lodash.omitby-4.6.0.tgz", + "integrity": "sha1-XBX/R1StVVAWtTwEExHo8HkgR5E=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "magic-string": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "dev": true + }, + "mem": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.2.0.tgz", + "integrity": "sha512-5fJxa68urlY0Ir8ijatKa3eRz5lwXnRCTvo9+TbTGAuTFJOwpGcY0X05moBd0nW45965Njt4CDI2GFQoG8DvqA==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "merge2": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", + "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + }, + "mimic-fn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.0.0.tgz", + "integrity": "sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "moment": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", + "integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nan": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.1.tgz", + "integrity": "sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "node-releases": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.11.tgz", + "integrity": "sha512-8v1j5KfP+s5WOTa1spNUAOfreajQPN12JXbRR0oDE+YrJBQCXBnNqUDj27EKpPLOoSiU3tKi3xGPB+JaOdUEQQ==", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "node-sass": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "dev": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash.assign": "^4.2.0", + "lodash.clonedeep": "^4.3.2", + "lodash.mergewith": "^4.6.0", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.10.0", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "^2.2.4", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.1.0.tgz", + "integrity": "sha512-H2RyIJ7+A3rjkwKC2l5GGtU4H1vkxKCAGsWasNVd0Set+6i4znxbWy6/j16YDPJDWxhsgZiKAstMEP8wCdSpjA==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz", + "integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-calc": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", + "integrity": "sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==", + "dev": true, + "requires": { + "css-unit-converter": "^1.1.1", + "postcss": "^7.0.5", + "postcss-selector-parser": "^5.0.0-rc.4", + "postcss-value-parser": "^3.3.1" + } + }, + "postcss-cli": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-6.1.2.tgz", + "integrity": "sha512-jIWfIkqt8cTThSpH8DBaNxHlBf99OKSem2RseRpfVPqWayxHKQB0IWdS/IF5XSGeFU5QslSDTdVHnw6qggXGkA==", + "dev": true, + "requires": { + "chalk": "^2.1.0", + "chokidar": "^2.0.0", + "dependency-graph": "^0.8.0", + "fs-extra": "^7.0.0", + "get-stdin": "^6.0.0", + "globby": "^9.0.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "postcss-reporter": "^6.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "yargs": "^12.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz", + "integrity": "sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ==", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-load-config": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.0.0.tgz", + "integrity": "sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ==", + "dev": true, + "requires": { + "cosmiconfig": "^4.0.0", + "import-cwd": "^2.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", + "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", + "dev": true, + "requires": { + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0", + "require-from-string": "^2.0.1" + } + } + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "dev": true, + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-reporter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", + "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "lodash": "^4.17.11", + "log-symbols": "^2.2.0", + "postcss": "^7.0.7" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "dev": true, + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "prettier": { + "version": "1.16.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", + "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz", + "integrity": "sha512-SbA/iNrBUf6Pv2zU8Ekv1Qbhv92yxL4hiDa2siuxs4KKn4oOoMDHXjAf7+Nz9qinUQ46B1LcWEi/PhJfPWpZWQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-transform": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.4.tgz", + "integrity": "sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A==", + "dev": true, + "requires": { + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp-tree": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.5.tgz", + "integrity": "sha512-nUmxvfJyAODw+0B13hj8CFVAxhe7fDEAgJgaotBu3nnR+IgGgZq59YedJP5VYTlkEfqjuK6TuRpnymKdatLZfQ==", + "dev": true + }, + "regexpu-core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", + "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.0.2", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "dev": true + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.7.0.tgz", + "integrity": "sha512-hjuWSCgoQsFSTsmsNP4AH1l1kfkFqW82gW00V9nL81Zr3JtnKn3rvxh18jUAAEMb7qNoHj21PR5SqbK2mhBgMg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "@types/node": "^11.9.5", + "acorn": "^6.1.1" + } + }, + "rollup-plugin-babel": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.3.2.tgz", + "integrity": "sha512-KfnizE258L/4enADKX61ozfwGHoqYauvoofghFJBhFnpH9Sb9dNPpWg8QHOaAfVASUYV8w0mCx430i9z0LJoJg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "rollup-pluginutils": "^2.3.0" + } + }, + "rollup-plugin-commonjs": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.1.tgz", + "integrity": "sha512-X0A/Cp/t+zbONFinBhiTZrfuUaVwRIp4xsbKq/2ohA2CDULa/7ONSJTelqxon+Vds2R2t2qJTqJQucKUC8GKkw==", + "dev": true, + "requires": { + "estree-walker": "^0.5.2", + "magic-string": "^0.25.1", + "resolve": "^1.10.0", + "rollup-pluginutils": "^2.3.3" + }, + "dependencies": { + "estree-walker": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", + "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", + "dev": true + } + } + }, + "rollup-plugin-license": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-license/-/rollup-plugin-license-0.8.1.tgz", + "integrity": "sha512-UeDVawklIv/rb4/M8AqHHtt03L+gDNfCVjQJqgUaQKiNTmUk+peAzkaLYeC6kfQm7i77kGYEzkbPZ0Dd9NEbCQ==", + "dev": true, + "requires": { + "commenting": "1.0.5", + "lodash": "4.17.11", + "magic-string": "0.25.1", + "mkdirp": "0.5.1", + "moment": "2.23.0" + }, + "dependencies": { + "magic-string": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", + "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.1" + } + } + } + }, + "rollup-plugin-node-resolve": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.1.tgz", + "integrity": "sha512-fSS7YDuCe0gYqKsr5OvxMloeZYUSgN43Ypi1WeRZzQcWtHgFayV5tUSPYpxuaioIIWaBXl6NrVk0T2/sKwueLg==", + "dev": true, + "requires": { + "builtin-modules": "^3.0.0", + "is-module": "^1.0.0", + "resolve": "^1.10.0" + } + }, + "rollup-plugin-prettier": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-prettier/-/rollup-plugin-prettier-0.6.0.tgz", + "integrity": "sha512-BgfyZ1biKcAaRNzfUyG/CeI5dFn+WsygK7kYjuXM6aL4m3t/53ZpJI2ZSMAeaKj6w2dj3CkOYbT0gulwh2SvKA==", + "dev": true, + "requires": { + "diff": "4.0.1", + "lodash.hasin": "4.5.2", + "lodash.isempty": "4.4.0", + "lodash.isnil": "4.0.0", + "lodash.omitby": "4.6.0", + "magic-string": "0.25.1", + "prettier": "^1.0.0" + }, + "dependencies": { + "magic-string": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", + "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.1" + } + } + } + }, + "rollup-plugin-terser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz", + "integrity": "sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "jest-worker": "^24.0.0", + "serialize-javascript": "^1.6.1", + "terser": "^3.14.1" + } + }, + "rollup-pluginutils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.5.0.tgz", + "integrity": "sha512-9Muh1H+XB5f5ONmKMayUoTYR1EZwHbwJJ9oZLrKT5yuTf/RLIQ5mYIGsrERquVucJmjmaAW0Y7+6Qo1Ep+5w3Q==", + "dev": true, + "requires": { + "estree-walker": "^0.6.0", + "micromatch": "^3.1.10" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^7.0.0" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "serialize-javascript": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", + "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + } + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.11.tgz", + "integrity": "sha512-//sajEx/fGL3iw6fltKMdPvy8kL3kJ2O3iuYlRoT3k9Kb4BjOoZ+BZzaNHeuaruSt+Kf3Zk9tnfAQg9/AJqUVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", + "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.2.0.tgz", + "integrity": "sha512-xBfxJxfk4UeVN8asec9jNxHiv3UAMv/ujwBWGYvQhhMb2u3YTGKkiybPcLFDLq7GLLWE9wa73e0/m8L5nTzQbw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.28", + "css-url-regex": "^1.1.0", + "csso": "^3.5.1", + "js-yaml": "^3.12.0", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "terser": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", + "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.10" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "requires": { + "glob": "^7.1.2" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", + "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", + "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", + "dev": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vendors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz", + "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "^3.0.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + } + } + } + } +} diff --git a/package.json b/package.json old mode 100644 new mode 100755 index bd1513ee..5af54def --- a/package.json +++ b/package.json @@ -1,8 +1,13 @@ { "name": "filepond", - "version": "4.3.3", + "version": "4.3.4", "description": "FilePond, Where files go to stretch their bits.", - "homepage": "https://pqina.nl/filepond", + "license": "MIT", + "author": { + "name": "PQINA", + "url": "https://pqina.nl/" + }, + "homepage": "https://pqina.nl/filepond/", "repository": "pqina/filepond", "main": "dist/filepond.js", "browser": "dist/filepond.js", @@ -18,9 +23,37 @@ "image", "preview" ], - "license": "MIT", - "author": { - "name": "PQINA", - "url": "https://pqina.nl" + "browserslist": [ + "last 3 versions", + "edge >= 12", + "ie >= 10" + ], + "files": [ + "dist" + ], + "scripts": { + "start": "npx rollup -c -w", + "build": "npm run scripts | npm run styles", + "scripts": "npx rollup -c", + "styles": "npm run styles:pretty && npm run styles:nano", + "styles:pretty": "cat src/css/* | npx node-sass | npx postcss --no-map --use autoprefixer | npx prettier --single-quote --parser css | node banner-cli.js FilePond > dist/filepond.css", + "styles:nano": "cat src/css/* | npx node-sass | npx postcss --no-map --use autoprefixer --use cssnano | node banner-cli.js FilePond > dist/filepond.min.css" + }, + "devDependencies": { + "@babel/core": "^7.4.0", + "@babel/plugin-proposal-object-rest-spread": "^7.4.0", + "@babel/preset-env": "^7.4.2", + "autoprefixer": "^9.5.0", + "cssnano": "^4.1.10", + "node-sass": "^4.11.0", + "postcss-cli": "^6.1.2", + "prettier": "^1.16.4", + "rollup": "^1.7.0", + "rollup-plugin-babel": "^4.3.2", + "rollup-plugin-commonjs": "^9.2.1", + "rollup-plugin-license": "^0.8.1", + "rollup-plugin-node-resolve": "^4.0.1", + "rollup-plugin-prettier": "^0.6.0", + "rollup-plugin-terser": "^4.0.4" } } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 00000000..32bcc98b --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,27 @@ +import * as pkg from './package.json'; +import build from './rollup.scripts'; + +export default build( + { + id: 'FilePond', + ...pkg + }, + [ + { + format: 'umd', + transpile: true + }, + { + format: 'umd', + transpile: true, + minify: true + }, + { + format: 'es' + }, + { + format: 'es', + minify: true + } + ] +); \ No newline at end of file diff --git a/rollup.scripts.js b/rollup.scripts.js new file mode 100644 index 00000000..28c8a46b --- /dev/null +++ b/rollup.scripts.js @@ -0,0 +1,52 @@ +import babel from 'rollup-plugin-babel'; +import license from 'rollup-plugin-license'; +import { terser } from 'rollup-plugin-terser'; +import prettier from 'rollup-plugin-prettier'; +const banner = require('./banner'); + +const createBuild = (options) => { + const { format, id, name, minify = false, transpile = false } = options; + + // get filename + const filename = ['dist/', name]; + if (format === 'es') { + filename.push('.esm'); + } + if (minify) { + filename.push('.min'); + } + filename.push('.js'); + + // collect plugins + const plugins = []; + if (transpile) { + plugins.push(babel({ + exclude: ['node_modules/**'] + })) + } + if (minify) { + plugins.push(terser()) + } + else { + plugins.push(prettier({ + singleQuote: true, + parser: 'babel' + })) + } + plugins.push(license({banner: banner(options)})) + + // return Rollup config + return { + input: 'src/js/index.js', + output: [ + { + format, + name: id, + file: filename.join('') + } + ], + plugins + } +}; + +export default (metadata, configs) => configs.map(config => createBuild({ ...metadata, ...config })); \ No newline at end of file diff --git a/src/css/assistant.scss b/src/css/assistant.scss new file mode 100644 index 00000000..0481ee92 --- /dev/null +++ b/src/css/assistant.scss @@ -0,0 +1,11 @@ +.filepond--assistant { + position: absolute; + overflow: hidden; + height: 1px; + width: 1px; + padding: 0; + border: 0; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + white-space: nowrap; +} diff --git a/src/css/browser.scss b/src/css/browser.scss new file mode 100644 index 00000000..771f9c40 --- /dev/null +++ b/src/css/browser.scss @@ -0,0 +1,16 @@ +/* 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 +} diff --git a/src/css/drip.scss b/src/css/drip.scss new file mode 100644 index 00000000..7262682d --- /dev/null +++ b/src/css/drip.scss @@ -0,0 +1,36 @@ +.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 { + $blobSize: 8em; + + position: absolute; + transform-origin: center center; + top: 0; + left: 0; + width: $blobSize; + height: $blobSize; + margin-left: -$blobSize * 0.5; + margin-top: -$blobSize * 0.5; + background: #292625; + border-radius: 50%; + + // will be animated + will-change: transform, opacity; +} diff --git a/src/css/drop-label.scss b/src/css/drop-label.scss new file mode 100644 index 00000000..fe75ee73 --- /dev/null +++ b/src/css/drop-label.scss @@ -0,0 +1,50 @@ +.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 + 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: .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; + text-decoration-skip-ink: auto; + text-decoration-color: #a7a4a4; + cursor: pointer; +} + +.filepond--root[data-disabled] { + .filepond--drop-label label { + opacity:.5; + } +} \ No newline at end of file diff --git a/src/css/file-action-button.scss b/src/css/file-action-button.scss new file mode 100644 index 00000000..bd3fafd4 --- /dev/null +++ b/src/css/file-action-button.scss @@ -0,0 +1,65 @@ +/* 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; + + // scale SVG to fill button + svg { + width: 100%; + height: 100%; + } + + // bigger touch area + &::after { + position:absolute; + left:-.75em; + right:-.75em; + top:-.75em; + bottom:-.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; + + &:hover, + &:focus { + box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.9); + } + + &[disabled] { + color: rgba(255,255,255,.5); + background-color: rgba(0, 0, 0, 0.25); + } +} diff --git a/src/css/file-info.scss b/src/css/file-info.scss new file mode 100644 index 00000000..e9060a24 --- /dev/null +++ b/src/css/file-info.scss @@ -0,0 +1,44 @@ +.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; + user-select: none; + + // no margins on children + * { + margin: 0; + } + + // we don't want to have these overrules so these selectors are a bit more specific + .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-sub { + font-size: 0.625em; + opacity: 0.5; + transition: opacity .25s ease-in-out; + white-space: nowrap; + } + + .filepond--file-info-sub:empty { + display: none; + } +} diff --git a/src/css/file-status.scss b/src/css/file-status.scss new file mode 100644 index 00000000..9691038a --- /dev/null +++ b/src/css/file-status.scss @@ -0,0 +1,37 @@ +.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; + user-select: none; + + // no margins on children + * { + margin: 0; + white-space: nowrap; + } + + // font sizes + .filepond--file-status-main { + font-size: 0.75em; + line-height: 1.2; + } + + .filepond--file-status-sub { + font-size: 0.625em; + opacity: 0.5; + transition: opacity .25s ease-in-out; + } +} diff --git a/src/css/file-wrapper.scss b/src/css/file-wrapper.scss new file mode 100644 index 00000000..2fa02c49 --- /dev/null +++ b/src/css/file-wrapper.scss @@ -0,0 +1,21 @@ +/* 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 + > legend { + position: absolute; + overflow: hidden; + height: 1px; + width: 1px; + padding: 0; + border: 0; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + white-space: nowrap; + } +} \ No newline at end of file diff --git a/src/css/file.scss b/src/css/file.scss new file mode 100644 index 00000000..ad6e5377 --- /dev/null +++ b/src/css/file.scss @@ -0,0 +1,171 @@ +.filepond--file { + $item-spacing-horizontal: 0.5625em; + $item-spacing-vertical: 0.5625em; + + position: static; + display: flex; + height: 100%; + align-items: flex-start; + + padding: $item-spacing-vertical $item-spacing-horizontal; + + color: #fff; + border-radius: 0.5em; + + // control positions + .filepond--file-status { + margin-left: auto; + margin-right: 2.25em; + } + + .filepond--processing-complete-indicator { + pointer-events: none; + user-select: none; + z-index: 3; + } + + .filepond--processing-complete-indicator, + .filepond--progress-indicator, + .filepond--file-action-button { + position: absolute; + } + + // .filepond--file-action-button + [data-align*='left'] { + left: $item-spacing-horizontal; + } + + [data-align*='right'] { + right: $item-spacing-horizontal; + } + + [data-align*='center'] { + left: calc(50% - .8125em); // .8125 is half of button width + } + + [data-align*='bottom'] { + bottom: $item-spacing-vertical * 2; + } + + [data-align='center'] { + top: calc(50% - .8125em); + } + + .filepond--progress-indicator { + margin-top: .1875em; + + &[data-align*='right'] { + margin-right: .1875em; + } + + &[data-align*='left'] { + margin-left: .1875em; + } + } +} + +// make sure text does not overlap +[data-filepond-item-state='cancelled'], +[data-filepond-item-state*='invalid'], +[data-filepond-item-state*='error'] { + .filepond--file-info { + margin-right: 2.25em; + } +} + + +[data-filepond-item-state='processing-complete'] { + + // busy state + .filepond--action-revert-item-processing svg { + animation: fall 0.5s 0.125s linear both; + } + + // hide details by default, only show when can revert + .filepond--file-info-sub, + .filepond--file-status-sub { + opacity:0; + } + + .filepond--action-revert-item-processing ~ .filepond--file-info .filepond--file-info-sub, + .filepond--action-revert-item-processing ~ .filepond--file-status .filepond--file-status-sub { + opacity:.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'], +[data-filepond-item-state*='error'] { + .filepond--panel, + .filepond--file-wrapper + { + animation: shake 0.65s linear both; + } +} + +// spins progress indicator when file is marked as busy +[data-filepond-item-state*='busy'] { + .filepond--progress-indicator svg { + animation: spin 1s linear infinite; + } +} + +/** + * States + */ +@keyframes spin { + 0% { + transform: rotateZ(0deg); + } + + 100% { + transform: rotateZ(360deg); + } +} + +@keyframes shake { + 10%, + 90% { + transform: translateX(-0.0625em); + } + + 20%, + 80% { + transform: translateX(0.125em); + } + + 30%, + 50%, + 70% { + transform: translateX(-0.25em); + } + + 40%, + 60% { + transform: translateX(0.25em); + } +} + +@keyframes fall { + 0% { + opacity: 0; + transform: scale(0.5); + animation-timing-function: ease-out; + } + + 70% { + opacity: 1; + transform: scale(1.1); + animation-timing-function: ease-in-out; + } + + 100% { + transform: scale(1); + animation-timing-function: ease-out; + } +} diff --git a/src/css/hopper.scss b/src/css/hopper.scss new file mode 100644 index 00000000..8498b8a0 --- /dev/null +++ b/src/css/hopper.scss @@ -0,0 +1,15 @@ +// 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; +} \ No newline at end of file diff --git a/src/css/item-order.scss b/src/css/item-order.scss new file mode 100644 index 00000000..3fec099b --- /dev/null +++ b/src/css/item-order.scss @@ -0,0 +1,15 @@ +.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; +} diff --git a/src/css/item.scss b/src/css/item.scss new file mode 100644 index 00000000..317dacd2 --- /dev/null +++ b/src/css/item.scss @@ -0,0 +1,51 @@ +.filepond--item { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1; + + padding: 0; + margin: .25em; + + will-change: transform, opacity; + + // item children order + > .filepond--panel { + z-index: -1; + + // has a slight shadow + .filepond--panel-bottom { + box-shadow: 0 0.0625em 0.125em -0.0625em rgba(0, 0, 0, 0.25); + } + } + +} + +// states +$color-default: #64605e; +$color-success: #369763; +$color-error: #c44e47; + +.filepond--item-panel { + background-color: $color-default; +} + +[data-filepond-item-state='processing-complete'] { + .filepond--item-panel { + background-color: $color-success; + } +} + +[data-filepond-item-state*='invalid'], +[data-filepond-item-state*='error'] { + .filepond--item-panel { + background-color: $color-error; + } +} + +// style of item panel +.filepond--item-panel { + border-radius: 0.5em; + transition: background-color 0.25s; +} diff --git a/src/css/list-scroller.scss b/src/css/list-scroller.scss new file mode 100644 index 00000000..45a528ac --- /dev/null +++ b/src/css/list-scroller.scss @@ -0,0 +1,46 @@ +// 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; + } + + overflow-y: scroll; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + mask: linear-gradient(to bottom, #000 calc(100% - .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; +} diff --git a/src/css/list.scss b/src/css/list.scss new file mode 100644 index 00000000..efa640d6 --- /dev/null +++ b/src/css/list.scss @@ -0,0 +1,17 @@ +/* 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: .75em; + right: .75em; +} diff --git a/src/css/modifiers.scss b/src/css/modifiers.scss new file mode 100644 index 00000000..8d55d97c --- /dev/null +++ b/src/css/modifiers.scss @@ -0,0 +1,79 @@ +.filepond--root { + + &[data-style-panel-layout~='integrated'] { + width:100%; + height:100%; + max-width: none; + margin:0; + } + + &[data-style-panel-layout~='circle'], + &[data-style-panel-layout~='integrated'] { + + .filepond--panel-root { + border-radius:0; + > * { + display:none; + } + } + + .filepond--drop-label { + bottom: 0; + height: auto; + display: flex; + justify-content: center; + align-items: center; + z-index: 7; + } + + // we're only loading one item, this makes the intro animation a bit nicer + .filepond--item-panel { display:none; } + } + + &[data-style-panel-layout~='compact'], + &[data-style-panel-layout~='integrated'] { + + .filepond--list-scroller { + overflow: hidden; + height: 100%; + margin-top: 0; + margin-bottom: 0; + } + + .filepond--list { + left: 0; + right: 0; + height: 100%; + } + + .filepond--item { + margin: 0; + } + + .filepond--file-wrapper { + height: 100%; + } + + } + + &[data-style-panel-layout~='circle'] { + + border-radius: 99999rem; + overflow: hidden; + + > .filepond--panel { + border-radius: inherit; + + > * { + display:none; + } + } + + // circle cuts of this info, so best to hide it + .filepond--file-info { display: none; } + .filepond--file-status { display: none; } + + } + +} + diff --git a/src/css/panel-root.scss b/src/css/panel-root.scss new file mode 100644 index 00000000..41758866 --- /dev/null +++ b/src/css/panel-root.scss @@ -0,0 +1,4 @@ +.filepond--panel-root { + border-radius: 0.5em; + background-color: #f1f0ef; +} diff --git a/src/css/panel.scss b/src/css/panel.scss new file mode 100644 index 00000000..c250f16d --- /dev/null +++ b/src/css/panel.scss @@ -0,0 +1,106 @@ +.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 + 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 + &::after { + content: ''; + position: absolute; + height: 2px; + left: 0; + right: 0; + bottom: -1px; + background-color: inherit; + } +} + +.filepond--panel-center, +.filepond--panel-bottom { + will-change: transform; + backface-visibility: hidden; + transform-origin: left top; + 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 + &::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 + &:not([style]) { + visibility: hidden; + } +} diff --git a/src/css/progress-indicator.scss b/src/css/progress-indicator.scss new file mode 100644 index 00000000..7b80ab77 --- /dev/null +++ b/src/css/progress-indicator.scss @@ -0,0 +1,28 @@ +.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; +} diff --git a/src/css/root-order.scss b/src/css/root-order.scss new file mode 100644 index 00000000..770f48c3 --- /dev/null +++ b/src/css/root-order.scss @@ -0,0 +1,19 @@ +.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; +} diff --git a/src/css/root.scss b/src/css/root.scss new file mode 100644 index 00000000..92989d34 --- /dev/null +++ b/src/css/root.scss @@ -0,0 +1,61 @@ +.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 + * { + font-size: inherit; + box-sizing: inherit; + line-height: inherit; + } + + // block everything + &[data-disabled] { + pointer-events: none; + } +} + +/** + * Root element children layout + */ +.filepond--root { + + .filepond--drop-label { + min-height: 4.75em; + } + + .filepond--list-scroller { + margin-top:1em; + margin-bottom:1em; + } + +} diff --git a/src/js/app/actions.js b/src/js/app/actions.js new file mode 100644 index 00000000..7417ed4e --- /dev/null +++ b/src/js/app/actions.js @@ -0,0 +1,911 @@ +import { isEmpty } from '../utils/isEmpty'; +import { forin } from '../utils/forin'; +import { fromCamels } from '../utils/fromCamels'; +import { hasRoomForItem } from './utils/hasRoomForItem'; +import { insertItem } from './utils/insertItem'; +import { createFileLoader } from './utils/createFileLoader'; +import { createFetchFunction } from './utils/createFetchFunction'; +import { createProcessorFunction } from './utils/createProcessorFunction'; +import { createRevertFunction } from './utils/createRevertFunction'; +import { createFileProcessor } from './utils/createFileProcessor'; +import { createItem } from './utils/createItem'; +import { ItemStatus } from './enum/ItemStatus'; +import { FileOrigin } from './enum/FileOrigin'; +import { getItemById } from './utils/getItemById'; +import { getItemByQuery } from './utils/getItemByQuery'; +import { InteractionMethod } from './enum/InteractionMethod'; +import { applyFilterChain, applyFilters } from '../filter'; +import { createItemAPI } from './utils/createItemAPI'; +import { createResponse } from '../utils/createResponse'; +import { fetchLocal } from './utils/fetchLocal'; +import { isExternalURL } from '../utils/isExternalURL'; +import { isString } from '../utils/isString'; +import { isFile } from '../utils/isFile'; +import { dynamicLabel } from './utils/dynamicLabel'; +import { getActiveItems } from './utils/getActiveItems'; +import { isFunction } from '../utils/isFunction'; + +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 = () => { } +} = {}) => { + const item = getItemByQuery(state.items, query); + if (!item) { + failure({ + error: createResponse( + 'error', + 0, + 'Item not found' + ), + file: null + }); + return; + } + itemHandler(item, success, failure); +}; + +export const actions = (dispatch, query, state) => ({ + /** + * Aborts all ongoing processes + */ + ABORT_ALL: () => { + getActiveItems(state.items).forEach(item => { + 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 }); + } + }); + + // 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, change }) => { + + 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 }) + .then(shouldPrepareOutput => { + if (!shouldPrepareOutput) { + return; + } + dispatch('REQUEST_PREPARE_OUTPUT', { + query: id, + item, + ready: (file) => { + dispatch('DID_PREPARE_OUTPUT', { id, file }); + } + }, true); + }); + + return; + } + + // 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 immidiately + 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(); + } + + }, + + SORT: ({ compare }) => { + sortItems(state, compare); + }, + + 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: source.options || {} + }); + } + )); + + Promise.all(promises) + .then(success) + .catch(failure) + }, + + /** + * @param source + * @param index + * @param interactionMethod + */ + ADD_ITEM: ({ + source, + index = -1, + interactionMethod, + success = () => { }, + failure = () => { }, + options = {} + }) => { + + // if no source supplied + if (isEmpty(source)) { + failure({ + error: createResponse( + 'error', + 0, + 'No source' + ), + file: null + }); + return; + } + + // filter out invalid file items, used to filter dropped directory contents + if (isFile(source) && state.options.ignoredFiles.includes(source.name.toLowerCase())) { + // fail silently + return; + } + + // test if there's still room in the list of files + if (!hasRoomForItem(state)) { + + // if multiple allowed, we can't replace + // or if only a single item is allowed but we're not allowed to replace it we exit + if ( + state.options.allowMultiple || + (!state.options.allowMultiple && !state.options.allowReplace) + ) { + const error = createResponse( + 'warning', + 0, + 'Max files' + ); + + dispatch('DID_THROW_MAX_FILES', { + source, + error + }); + + failure({ error, file: null }); + + return; + } + + // let's replace the item + // id of first item we're about to remove + const item = getActiveItems(state.items)[0]; + + // if has been processed remove it from the server as well + if (item.status === ItemStatus.PROCESSING_COMPLETE || item.status === ItemStatus.PROCESSING_REVERT_ERROR) { + + const forceRevert = query('GET_FORCE_REVERT'); + item.revert(createRevertFunction(state.options.server.url, state.options.server.revert), forceRevert) + .then(() => { + + if (!forceRevert) return; + + // try to add now + dispatch('ADD_ITEM', { source, index, interactionMethod, success, failure, options }); + }) + .catch(() => {}) // no need to handle this catch state for now + + if (forceRevert) return; + } + + // remove first item as it will be replaced by this item + dispatch('REMOVE_ITEM', { query: item.id }); + } + + // where did the file originate + const origin = + options.type === 'local' + ? FileOrigin.LOCAL + : options.type === 'limbo' + ? FileOrigin.LIMBO + : FileOrigin.INPUT; + + // create a new blank item + const item = createItem( + // where did this file come from + origin, + + // an input file never has a server file reference + origin === FileOrigin.INPUT ? null : source, + + // file mock data, if defined + options.file + ); + + // set initial meta data + Object.keys(options.metadata || {}).forEach(key => { + item.setMetadata(key, options.metadata[key]); + }); + + // created the item, let plugins add methods + applyFilters('DID_CREATE_ITEM', item, { query, dispatch }); + + // where to insert new items + const itemInsertLocation = query('GET_ITEM_INSERT_LOCATION'); + + // adjust index if is not allowed to pick location + if (!state.options.itemInsertLocationFreedom) { + index = itemInsertLocation === 'before' ? -1 : state.items.length; + } + + // add item to list + insertItem(state.items, item, index); + + // sort items in list + if (isFunction(itemInsertLocation) && source) { + sortItems(state, itemInsertLocation); + } + + // get a quick reference to the item id + const id = item.id; + + // observe item events + item.on('load-init', () => { + dispatch('DID_START_ITEM_LOAD', { id }); + }); + + item.on('load-meta', () => { + dispatch('DID_UPDATE_ITEM_META', { id }); + }); + + item.on('load-progress', progress => { + dispatch('DID_UPDATE_ITEM_LOAD_PROGRESS', { id, progress }); + }); + + item.on('load-request-error', error => { + + const mainStatus = dynamicLabel(state.options.labelFileLoadError)(error); + + // is client error, no way to recover + if (error.code >= 400 && error.code < 500) { + dispatch('DID_THROW_ITEM_INVALID', { + id, + error, + status: { + main: mainStatus, + sub: `${error.code} (${error.body})` + } + }); + + // reject the file so can be dealt with through API + failure({ error, file: createItemAPI(item) }); + return; + } + + // is possible server error, so might be possible to retry + dispatch('DID_THROW_ITEM_LOAD_ERROR', { + id, + error, + status: { + main: mainStatus, + sub: state.options.labelTapToRetry + } + }); + }); + + item.on('load-file-error', error => { + dispatch('DID_THROW_ITEM_INVALID', { + id, + error: error.status, + status: error.status + }); + }); + + item.on('load-abort', () => { + dispatch('REMOVE_ITEM', { query: id }); + }); + + item.on('load-skip', () => { + + dispatch('COMPLETE_LOAD_ITEM', { + query: id, + item, + data: { + source, + success + } + }); + + }); + + item.on('load', () => { + + const handleAdd = (shouldAdd) => { + + // no should not add this file + if (!shouldAdd) { + dispatch('REMOVE_ITEM', { + query: id + }); + return; + } + + // now interested in metadata updates + item.on('metadata-update', change => { + dispatch('DID_UPDATE_ITEM_METADATA', { id, change }); + }); + + // let plugins decide if the output data should be prepared at this point + // means we'll do this and wait for idle state + applyFilterChain('SHOULD_PREPARE_OUTPUT', false, { item, query }) + .then(shouldPrepareOutput => { + + const loadComplete = () => { + + dispatch('COMPLETE_LOAD_ITEM', { + query: id, + item, + data:{ + source, + success + } + }); + + listUpdated(dispatch, state); + + } + + // exit + if (shouldPrepareOutput) { + + // wait for idle state and then run PREPARE_OUTPUT + dispatch('REQUEST_PREPARE_OUTPUT', { + query: id, + item, + ready: (file) => { + dispatch('DID_PREPARE_OUTPUT', { id, file }); + loadComplete(); + } + }, true) + + return; + } + + loadComplete(); + + }); + + } + + // item loaded, allow plugins to + // - read data (quickly) + // - add metadata + applyFilterChain('DID_LOAD_ITEM', item, { query, dispatch }) + .then(() => { + optionalPromise(query('GET_BEFORE_ADD_FILE'), createItemAPI(item)) + .then(handleAdd); + }).catch(() => { + handleAdd(false); + }) + }); + + item.on('process-start', () => { + dispatch('DID_START_ITEM_PROCESSING', { id }); + }); + + item.on('process-progress', progress => { + dispatch('DID_UPDATE_ITEM_PROCESS_PROGRESS', { id, progress }); + }); + + item.on('process-error', error => { + dispatch('DID_THROW_ITEM_PROCESSING_ERROR', { + id, + error, + status: { + main: dynamicLabel(state.options.labelFileProcessingError)(error), + sub: state.options.labelTapToRetry + } + }); + }); + + item.on('process-revert-error', error => { + dispatch('DID_THROW_ITEM_PROCESSING_REVERT_ERROR', { + id, + error, + status: { + main: dynamicLabel(state.options.labelFileProcessingRevertError)(error), + sub: state.options.labelTapToRetry + } + }); + }); + + item.on('process-complete', serverFileReference => { + dispatch('DID_COMPLETE_ITEM_PROCESSING', { + id, + error: null, + serverFileReference + }); + }); + + item.on('process-abort', () => { + dispatch('DID_ABORT_ITEM_PROCESSING', { id }); + }); + + item.on('process-revert', () => { + dispatch('DID_REVERT_ITEM_PROCESSING', { id }); + }); + + // let view know the item has been inserted + dispatch('DID_ADD_ITEM', { id, index, interactionMethod }); + + listUpdated(dispatch, state); + + // start loading the source + const { url, load, restore, fetch } = state.options.server || {}; + + item.load( + source, + + // this creates a function that loads the file based on the type of file (string, base64, blob, file) and location of file (local, remote, limbo) + createFileLoader( + origin === FileOrigin.INPUT + // input + ? isString(source) && isExternalURL(source) + ? createFetchFunction(url, fetch) // remote url + : fetchLocal // local url + // limbo or local + : origin === FileOrigin.LIMBO + ? createFetchFunction(url, restore) // limbo + : createFetchFunction(url, load) // local + + ), + + // called when the file is loaded so it can be piped through the filters + (file, success, error) => { + // let's process the file + applyFilterChain('LOAD_FILE', file, { query }) + .then(success) + .catch(error); + } + ); + }, + + REQUEST_PREPARE_OUTPUT: ({ item, ready }) => { + + // don't handle archived items, an item could have been archived (load aborted) while waiting to be prepared + if (item.archived) return; + + // allow plugins to alter the file data + applyFilterChain('PREPARE_OUTPUT', item.file, { query, item }) + .then((result) => { + applyFilterChain('COMPLETE_PREPARE_OUTPUT', result, { query, item }) + .then(result => { + + // don't handle archived items, an item could have been archived (load aborted) while being prepared + if (item.archived) return; + + // we done! + ready(result); + }); + }); + + }, + + COMPLETE_LOAD_ITEM: ({item, data}) => { + + const { + success, + source, + } = data; + + // sort items in list + const itemInsertLocation = query('GET_ITEM_INSERT_LOCATION'); + if (isFunction(itemInsertLocation) && source) { + sortItems(state, itemInsertLocation); + } + + // let interface know the item has loaded + dispatch('DID_LOAD_ITEM', { + id: item.id, + error: null, + serverFileReference: item.origin === FileOrigin.INPUT ? null : source + }); + + // item has been successfully loaded and added to the + // list of items so can now be safely returned for use + success(createItemAPI(item)); + + // if this is a local server file we need to show a different state + if (item.origin === FileOrigin.LOCAL) { + dispatch('DID_LOAD_LOCAL_ITEM', { id: item.id }); + return; + } + + // if is a temp server file we prevent async upload call here (as the file is already on the server) + if (item.origin === FileOrigin.LIMBO) { + dispatch('DID_COMPLETE_ITEM_PROCESSING', { + id: item.id, + error: null, + serverFileReference: source + }); + return; + } + + // id we are allowed to upload the file immidiately, lets do it + if (query('IS_ASYNC') && state.options.instantUpload) { + dispatch('REQUEST_ITEM_PROCESSING', { query: item.id }); + } + + }, + + RETRY_ITEM_LOAD: getItemByQueryFromState(state, item => { + // try loading the source one more time + item.retryLoad(); + }), + + REQUEST_ITEM_PROCESSING: getItemByQueryFromState(state, (item, success, failure) => { + + // cannot be queued (or is already queued) + const itemCanBeQueuedForProcessing = + // waiting for something + (item.status === ItemStatus.IDLE) || + // processing went wrong earlier + (item.status === ItemStatus.PROCESSING_ERROR); + + // not ready to be processed + if (!itemCanBeQueuedForProcessing) { + + const process = () => { + setTimeout(() => { + dispatch('REQUEST_ITEM_PROCESSING', { query: item, success, failure }); + }, 32); + } + + // if already done processing or tried to revert but didn't work, try again + if (item.status === ItemStatus.PROCESSING_COMPLETE || item.status === ItemStatus.PROCESSING_REVERT_ERROR) { + item.revert(createRevertFunction(state.options.server.url,state.options.server.revert), query('GET_FORCE_REVERT')) + .then(process) + .catch(() => {}) // don't continue with processing if something went wrong + } + + else if (item.status === ItemStatus.PROCESSING) { + item.abortProcessing() + .then(process); + } + + return; + } + + // already queued for processing + if (item.status === ItemStatus.PROCESSING_QUEUED) return; + + item.requestProcessing(); + + dispatch('DID_REQUEST_ITEM_PROCESSING', { id: item.id }); + + dispatch('PROCESS_ITEM', { query: item, success, failure }, true); + }), + + PROCESS_ITEM: getItemByQueryFromState(state, (item, success, failure) => { + + const maxParallelUploads = query('GET_MAX_PARALLEL_UPLOADS'); + const totalCurrentUploads = query('GET_ITEMS_BY_STATUS', ItemStatus.PROCESSING).length; + + // queue and wait till queue is freed up + if (totalCurrentUploads === maxParallelUploads) { + + // queue for later processing + state.processingQueue.push({ + item, + success, + failure + }); + + // stop it! + return; + }; + + // if was not queued or is already processing exit here + if (item.status === ItemStatus.PROCESSING) return; + + const processNext = () => { + + // process queueud items + const queued = state.processingQueue.shift(); + + // process queued item + if (queued) { + dispatch('PROCESS_ITEM', { query: queued.item, success: queued.success, failure: queued.failure }, true); + } + } + + // we done function + item.onOnce('process-complete', () => { + success(createItemAPI(item)); + processNext(); + + // All items processed? No errors? + const allItemsProcessed = query('GET_ITEMS_BY_STATUS', ItemStatus.PROCESSING_COMPLETE).length === state.items.length; + if (allItemsProcessed) { + dispatch('DID_COMPLETE_ITEM_PROCESSING_ALL'); + } + }); + + // we error function + item.onOnce('process-error', error => { + failure({ error, file: createItemAPI(item) }); + processNext(); + }); + + // start file processing + item.process( + createFileProcessor( + createProcessorFunction( + state.options.server.url, + state.options.server.process, + state.options.name + ) + ), + // called when the file is about to be processed so it can be piped through the transform filters + (file, success, error) => { + + // allow plugins to alter the file data + applyFilterChain('PREPARE_OUTPUT', file, { query, item }) + .then(file => { + dispatch('DID_PREPARE_OUTPUT', { id: item.id, file }); + + success(file); + }) + .catch(error); + } + ); + }), + + RETRY_ITEM_PROCESSING: getItemByQueryFromState(state, item => { + dispatch('REQUEST_ITEM_PROCESSING', { query: item }); + }), + + REQUEST_REMOVE_ITEM: getItemByQueryFromState(state, (item) => { + optionalPromise(query('GET_BEFORE_REMOVE_FILE'), createItemAPI(item)) + .then((shouldRemove) => { + if (!shouldRemove) { + return; + } + dispatch('REMOVE_ITEM', { query: item }); + }); + }), + + RELEASE_ITEM: getItemByQueryFromState(state, (item) => { + item.release(); + }), + + REMOVE_ITEM: getItemByQueryFromState(state, (item, success) => { + + const removeFromView = () => { + + // get id reference + const id = item.id; + + // archive the item, this does not remove it from the list + getItemById(state.items, id).archive(); + + // tell the view the item has been removed + dispatch('DID_REMOVE_ITEM', { id, item }); + + // now the list has been modified + listUpdated(dispatch, state); + + // correctly removed + success(createItemAPI(item)); + } + + // if this is a local file and the server.remove function has been configured, send source there so dev can remove file from server + const server = state.options.server; + if (item.origin === FileOrigin.LOCAL && + server && isFunction(server.remove)) { + + dispatch('DID_START_ITEM_REMOVE', { id: item.id }); + + server.remove( + item.source, + () => removeFromView(), + (status) => { + dispatch('DID_THROW_ITEM_REMOVE_ERROR', { + id: item.id, + error: createResponse('error', 0, status, null), + status: { + main: dynamicLabel(state.options.labelFileRemoveError)(status), + sub: state.options.labelTapToRetry + } + }); + } + ); + } + else { + removeFromView(); + } + + }), + + ABORT_ITEM_LOAD: getItemByQueryFromState(state, item => { + item.abortLoad(); + }), + + ABORT_ITEM_PROCESSING: getItemByQueryFromState(state, item => { + + // test if is already processed + if (item.serverId) { + dispatch('REVERT_ITEM_PROCESSING', { id: item.id }); + return; + } + + // abort + item.abortProcessing() + .then(() => { + const shouldRemove = state.options.instantUpload; + if (shouldRemove) { + dispatch('REMOVE_ITEM', { query: item.id }); + } + }); + }), + + REQUEST_REVERT_ITEM_PROCESSING: getItemByQueryFromState(state, item => { + + // not instant uploading, revert immidiately + if (!state.options.instantUpload) { + dispatch('REVERT_ITEM_PROCESSING', { query: item }); + return; + } + + // if we're instant uploading the file will also be removed if we revert, + // so if a before remove file hook is defined we need to run it now + const handleRevert = (shouldRevert) => { + if (!shouldRevert) return; + dispatch('REVERT_ITEM_PROCESSING', { query: item }); + } + + const fn = query('GET_BEFORE_REMOVE_FILE'); + if (!fn) { + return handleRevert(true); + } + + const requestRemoveResult = fn(createItemAPI(item)); + if (requestRemoveResult == null) { // undefined or null + return handleRevert(true); + } + + if (typeof requestRemoveResult === 'boolean') { + return handleRevert(requestRemoveResult); + } + + if (typeof requestRemoveResult.then === 'function') { + requestRemoveResult.then(handleRevert); + } + + }), + + REVERT_ITEM_PROCESSING: getItemByQueryFromState(state, item => { + item.revert(createRevertFunction(state.options.server.url, state.options.server.revert), query('GET_FORCE_REVERT')) + .then(() => { + const shouldRemove = state.options.instantUpload || isMockItem(item); + if (shouldRemove) { + dispatch('REMOVE_ITEM', { query: item.id }); + } + }).catch(() => {}) + }), + + SET_OPTIONS: ({ options }) => { + forin(options, (key, value) => { + dispatch(`SET_${fromCamels(key, '_').toUpperCase()}`, { value }); + }); + } +}); diff --git a/src/js/app/enum/FileOrigin.js b/src/js/app/enum/FileOrigin.js new file mode 100644 index 00000000..580140d2 --- /dev/null +++ b/src/js/app/enum/FileOrigin.js @@ -0,0 +1,5 @@ +export const FileOrigin = { + INPUT:1, + LIMBO:2, + LOCAL:3 +}; \ No newline at end of file diff --git a/src/js/app/enum/InteractionMethod.js b/src/js/app/enum/InteractionMethod.js new file mode 100644 index 00000000..b2df76c3 --- /dev/null +++ b/src/js/app/enum/InteractionMethod.js @@ -0,0 +1,7 @@ +export const InteractionMethod = { + API: 1, + DROP: 2, + BROWSE: 3, + PASTE: 4, + NONE: 5 +}; diff --git a/src/js/app/enum/ItemStatus.js b/src/js/app/enum/ItemStatus.js new file mode 100644 index 00000000..3fe440be --- /dev/null +++ b/src/js/app/enum/ItemStatus.js @@ -0,0 +1,11 @@ +export 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 +}; diff --git a/src/js/app/enum/Key.js b/src/js/app/enum/Key.js new file mode 100644 index 00000000..4695246f --- /dev/null +++ b/src/js/app/enum/Key.js @@ -0,0 +1,4 @@ +export const Key = { + ENTER: 13, + SPACE: 32 +}; diff --git a/src/js/app/enum/Status.js b/src/js/app/enum/Status.js new file mode 100644 index 00000000..460695b8 --- /dev/null +++ b/src/js/app/enum/Status.js @@ -0,0 +1,7 @@ +export 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 +}; \ No newline at end of file diff --git a/src/js/app/enum/Type.js b/src/js/app/enum/Type.js new file mode 100644 index 00000000..d7b1c897 --- /dev/null +++ b/src/js/app/enum/Type.js @@ -0,0 +1,11 @@ +export const Type = { + BOOLEAN: 'boolean', + INT: 'int', + STRING: 'string', + ARRAY: 'array', + OBJECT: 'object', + FUNCTION: 'function', + ACTION: 'action', + SERVER_API: 'serverapi', + REGEX: 'regex' +}; diff --git a/src/js/app/frame/createPainter.js b/src/js/app/frame/createPainter.js new file mode 100644 index 00000000..ab044551 --- /dev/null +++ b/src/js/app/frame/createPainter.js @@ -0,0 +1,54 @@ +export 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 frame = null; + + const tick = ts => { + // queue next tick + frame = window.requestAnimationFrame(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)); + }; + + tick(performance.now()); + + return { + pause: () => { + window.cancelAnimationFrame(frame); + } + }; +}; diff --git a/src/js/app/frame/createRoute.js b/src/js/app/frame/createRoute.js new file mode 100644 index 00000000..8fe9413b --- /dev/null +++ b/src/js/app/frame/createRoute.js @@ -0,0 +1,9 @@ +export 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 }); + }; +}; diff --git a/src/js/app/frame/createStore.js b/src/js/app/frame/createStore.js new file mode 100644 index 00000000..582d0fee --- /dev/null +++ b/src/js/app/frame/createStore.js @@ -0,0 +1,91 @@ +export 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 + if (isBlocking) { + 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; +}; diff --git a/src/js/app/frame/createView.js b/src/js/app/frame/createView.js new file mode 100644 index 00000000..654d076a --- /dev/null +++ b/src/js/app/frame/createView.js @@ -0,0 +1,325 @@ +import { createObject } from '../../utils/createObject'; +import { createElement } from './utils/createElement'; +import { appendChild } from './utils/appendChild'; +import { appendChildView } from './utils/appendChildView'; +import { removeChildView } from './utils/removeChildView'; +import { getViewRect } from './utils/getViewRect'; +import { Mixins } from './mixins/index'; +import { updateRect } from './utils/updateRect'; + +export 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 = element.children.length; // 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, props); + }; diff --git a/src/js/app/frame/index.js b/src/js/app/frame/index.js new file mode 100644 index 00000000..23f4ac79 --- /dev/null +++ b/src/js/app/frame/index.js @@ -0,0 +1,6 @@ +import { createStore } from './createStore'; +import { createView } from './createView'; +import { createPainter } from './createPainter'; +import { createRoute } from './createRoute'; + +export { createRoute, createPainter, createStore, createView }; \ No newline at end of file diff --git a/src/js/app/frame/mixins/animations.js b/src/js/app/frame/mixins/animations.js new file mode 100644 index 00000000..6ac0831a --- /dev/null +++ b/src/js/app/frame/mixins/animations.js @@ -0,0 +1,74 @@ +import { createAnimator } from '../utils/createAnimator'; +import { forin } from '../../../utils/forin'; +import { addGetSet } from './utils/addGetSet'; +import { isDefined } from '../../../utils/isDefined'; + +// add to state, +// add getters and setters to internal and external api (if not set) +// setup animators + +export const animations = ({ + mixinConfig, + viewProps, + viewInternalAPI, + viewExternalAPI, + viewState +}) => { + // 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 resting = true; + animations.forEach(animation => { + if (!animation.resting) { + resting = false; + } + animation.interpolate(ts); + }); + return resting; + }, + destroy: () => {} + }; +}; diff --git a/src/js/app/frame/mixins/apis.js b/src/js/app/frame/mixins/apis.js new file mode 100644 index 00000000..53ee5cf8 --- /dev/null +++ b/src/js/app/frame/mixins/apis.js @@ -0,0 +1,7 @@ +import { addGetSet } from './utils/addGetSet'; + +// add to external api and link to props + +export const apis = ({ mixinConfig, viewProps, viewExternalAPI }) => { + addGetSet(mixinConfig, viewExternalAPI, viewProps); +}; diff --git a/src/js/app/frame/mixins/index.js b/src/js/app/frame/mixins/index.js new file mode 100644 index 00000000..d6b68319 --- /dev/null +++ b/src/js/app/frame/mixins/index.js @@ -0,0 +1,11 @@ +import { animations } from './animations'; +import { listeners } from './listeners'; +import { apis } from './apis'; +import { styles } from './styles'; + +export const Mixins = { + styles, + listeners, + animations, + apis +}; diff --git a/src/js/app/frame/mixins/listeners.js b/src/js/app/frame/mixins/listeners.js new file mode 100644 index 00000000..d24aba7c --- /dev/null +++ b/src/js/app/frame/mixins/listeners.js @@ -0,0 +1,45 @@ +import { addEvent } from '../utils/addEvent'; +import { removeEvent } from '../utils/removeEvent'; + +// mixin +export 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); + }); + } + }; +}; diff --git a/src/js/app/frame/mixins/styles.js b/src/js/app/frame/mixins/styles.js new file mode 100644 index 00000000..f19af619 --- /dev/null +++ b/src/js/app/frame/mixins/styles.js @@ -0,0 +1,199 @@ +import { getViewRect } from '../utils/getViewRect'; +import { addGetSet } from './utils/addGetSet'; +import { isDefined } from '../../../utils/isDefined'; + +// 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 +}; + +export 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.setAttribute('style', styles); + // store current styles so we can compare them to new styles later on + // _not_ getting the style attribute is faster + element.elementCurrentStyle = styles; + } +}; \ No newline at end of file diff --git a/src/js/app/frame/mixins/utils/addGetSet.js b/src/js/app/frame/mixins/utils/addGetSet.js new file mode 100644 index 00000000..3819f8a8 --- /dev/null +++ b/src/js/app/frame/mixins/utils/addGetSet.js @@ -0,0 +1,25 @@ +export 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 + }; + }); + }); +}; diff --git a/src/js/app/frame/utils/AxisEnum.js b/src/js/app/frame/utils/AxisEnum.js new file mode 100644 index 00000000..0cd9c40b --- /dev/null +++ b/src/js/app/frame/utils/AxisEnum.js @@ -0,0 +1,5 @@ +export const AxisEnum = { + X: 0, + Y: 1, + Z: 2 +}; diff --git a/src/js/app/frame/utils/addEvent.js b/src/js/app/frame/utils/addEvent.js new file mode 100644 index 00000000..7eb55ffc --- /dev/null +++ b/src/js/app/frame/utils/addEvent.js @@ -0,0 +1,3 @@ +export const addEvent = element => (type, fn) => { + element.addEventListener(type, fn); +}; diff --git a/src/js/app/frame/utils/animators/easing.js b/src/js/app/frame/utils/animators/easing.js new file mode 100644 index 00000000..99cbbbbc --- /dev/null +++ b/src/js/app/frame/utils/animators/easing.js @@ -0,0 +1,2 @@ +export const easeLinear = t => t; +export const easeInOutQuad = t => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t); diff --git a/src/js/app/frame/utils/animators/spring.js b/src/js/app/frame/utils/animators/spring.js new file mode 100644 index 00000000..e7d8dfc2 --- /dev/null +++ b/src/js/app/frame/utils/animators/spring.js @@ -0,0 +1,125 @@ +import { createObject } from '../../../../utils/createObject'; +import { isNumber } from '../../../../utils/isNumber'; + +/** + * 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 + */ +export 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 = () => { + + // 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)) { + 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; + }; diff --git a/src/js/app/frame/utils/animators/tween.js b/src/js/app/frame/utils/animators/tween.js new file mode 100644 index 00000000..3f622696 --- /dev/null +++ b/src/js/app/frame/utils/animators/tween.js @@ -0,0 +1,82 @@ +import { createObject } from '../../../../utils/createObject'; +import { easeInOutQuad } from './easing'; + +export 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 => { + if (resting || target === null) { + return; + } + + if (start === null) { + start = ts; + } + + if (ts - start < delay) { + return; + } + + t = ts - start - delay; + + if (t < duration) { + p = t / duration; + api.onupdate( + (t >= 0 ? easing(reverse ? 1 - p : p) : 0) * target + ); + } else { + t = 1; + p = reverse ? 0 : 1; + api.onupdate(p * target); + api.oncomplete(p * target); + resting = true; + } + }; + + // 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; + }; diff --git a/src/js/app/frame/utils/appendChild.js b/src/js/app/frame/utils/appendChild.js new file mode 100644 index 00000000..71e0f349 --- /dev/null +++ b/src/js/app/frame/utils/appendChild.js @@ -0,0 +1,7 @@ +export const appendChild = parent => (child, index) => { + if (typeof index !== 'undefined' && parent.children[index]) { + parent.insertBefore(child, parent.children[index]); + } else { + parent.appendChild(child); + } +}; diff --git a/src/js/app/frame/utils/appendChildView.js b/src/js/app/frame/utils/appendChildView.js new file mode 100644 index 00000000..7f44e944 --- /dev/null +++ b/src/js/app/frame/utils/appendChildView.js @@ -0,0 +1,10 @@ +export const appendChildView = (parent, childViews) => (view, index) => { + + if (typeof index !== 'undefined') { + childViews.splice(index, 0, view); + } else { + childViews.push(view); + } + + return view; +}; diff --git a/src/js/app/frame/utils/createAnimator.js b/src/js/app/frame/utils/createAnimator.js new file mode 100644 index 00000000..6d3d1d4e --- /dev/null +++ b/src/js/app/frame/utils/createAnimator.js @@ -0,0 +1,27 @@ +import { spring } from './animators/spring'; +import { tween } from './animators/tween'; + +const animator = { + spring, + tween +}; + +/* + { type: 'spring', stiffness: .5, damping: .75, mass: 10 }; + { translation: { type: 'spring', ... }, ... } + { translation: { x: { type: 'spring', ... } } } +*/ +export 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; +}; diff --git a/src/js/app/frame/utils/createElement.js b/src/js/app/frame/utils/createElement.js new file mode 100644 index 00000000..75497219 --- /dev/null +++ b/src/js/app/frame/utils/createElement.js @@ -0,0 +1,28 @@ +import { forin } from '../../../utils/forin'; +import { attr } from '../../../utils/attr'; + +const ns = 'http://www.w3.org/2000/svg'; +const svgElements = ['svg', 'path']; // only svg elements used + +const isSVGElement = tag => svgElements.includes(tag); + +export 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; +}; diff --git a/src/js/app/frame/utils/getViewRect.js b/src/js/app/frame/utils/getViewRect.js new file mode 100644 index 00000000..2bb5d360 --- /dev/null +++ b/src/js/app/frame/utils/getViewRect.js @@ -0,0 +1,72 @@ +export 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; +}; diff --git a/src/js/app/frame/utils/removeChildView.js b/src/js/app/frame/utils/removeChildView.js new file mode 100644 index 00000000..d90c17b3 --- /dev/null +++ b/src/js/app/frame/utils/removeChildView.js @@ -0,0 +1,11 @@ +export 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; +}; diff --git a/src/js/app/frame/utils/removeEvent.js b/src/js/app/frame/utils/removeEvent.js new file mode 100644 index 00000000..e122d38a --- /dev/null +++ b/src/js/app/frame/utils/removeEvent.js @@ -0,0 +1,3 @@ +export const removeEvent = element => (type, fn) => { + element.removeEventListener(type, fn); +}; diff --git a/src/js/app/frame/utils/updateRect.js b/src/js/app/frame/utils/updateRect.js new file mode 100644 index 00000000..8f1f4cf3 --- /dev/null +++ b/src/js/app/frame/utils/updateRect.js @@ -0,0 +1,25 @@ +export 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; +}; diff --git a/src/js/app/index.js b/src/js/app/index.js new file mode 100644 index 00000000..7d79e4cf --- /dev/null +++ b/src/js/app/index.js @@ -0,0 +1,597 @@ +import { createStore } from './frame/index'; +import { insertBefore } from '../utils/insertBefore'; +import { insertAfter } from '../utils/insertAfter'; +import { createInitialState } from './utils/createInitialState'; +import { createObject } from '../utils/createObject'; +import { createOptionAPI } from './utils/createOptionAPI'; +import { createOptionActions } from './utils/createOptionActions'; +import { createOptionQueries } from './utils/createOptionQueries'; +import { InteractionMethod } from './enum/InteractionMethod'; +import { getUniqueId } from '../utils/getUniqueId'; +import { on } from './utils/on'; +import { isArray } from '../utils/isArray'; +import { isNumber } from '../utils/isNumber'; +import { createItemAPI } from './utils/createItemAPI'; +import { removeReleasedItems } from './utils/removeReleasedItems'; +import { ItemStatus } from './enum/ItemStatus'; + +// defaults +import { getOptions } from './options'; +import { queries } from './queries'; +import { actions } from './actions'; + +// view +import { root } from './view/root'; + +// creates the app +export const createApp = (initialOptions = {}) => { + // let element + let originalElement = null; + + // get default options + const defaultOptions = getOptions(); + + // create the data store, this will contain all our app info + const store = createStore( + // initial state (should be serializable) + createInitialState(defaultOptions), + + // queries + [queries, createOptionQueries(defaultOptions)], + + // action handlers + [actions, createOptionActions(defaultOptions)] + ); + + // set initial options + store.dispatch('SET_OPTIONS', { options: initialOptions }); + + // kick thread if visibility changes + const visibilityHandler = () => { + if (document.hidden) return; + store.dispatch('KICK'); + } + document.addEventListener('visibilitychange', visibilityHandler); + + // re-render on window resize start and finish + let resizeDoneTimer = null; + let isResizing = false; + let isResizingHorizontally = false; + let initialWindowWidth = null; + let currentWindowWidth = null; + const resizeHandler = () => { + if (!isResizing) { + isResizing = true; + } + clearTimeout(resizeDoneTimer); + resizeDoneTimer = setTimeout(() => { + isResizing = false; + initialWindowWidth = null; + currentWindowWidth = null; + if (isResizingHorizontally) { + isResizingHorizontally = false; + store.dispatch('DID_STOP_RESIZE'); + } + }, 500); + }; + window.addEventListener('resize', resizeHandler); + + // render initial view + const view = root(store, { id: getUniqueId() }); + + // + // PRIVATE API ------------------------------------------------------------------------------------- + // + let isResting = false; + let isHidden = false; + + const readWriteApi = { + // necessary for update loop + + /** + * Reads from dom (never call manually) + * @private + */ + _read: () => { + + // test if we're resizing horizontally + // TODO: see if we can optimize this by measuring root rect + if (isResizing) { + + currentWindowWidth = window.innerWidth; + if (!initialWindowWidth) { + initialWindowWidth = currentWindowWidth; + } + + if (!isResizingHorizontally && currentWindowWidth !== initialWindowWidth) { + store.dispatch('DID_START_RESIZE'); + isResizingHorizontally = true; + } + } + + // if resting, no need to read as numbers will still all be correct + if (isResting) return; + + // read view data + view._read(); + + // if root is hidden + isHidden = view.rect.element.hidden; + }, + + /** + * Writes to dom (never call manually) + * @private + */ + _write: ts => { + + // don't do anything while hidden + if (isHidden) return; + + // get all actions from store + const actions = store + .processActionQueue() + + // filter out set actions (these will automatically trigger DID_SET) + .filter(action => !/^SET_/.test(action.type)); + + // if was idling and no actions stop here + if (isResting && !actions.length) return; + + // some actions might trigger events + routeActionsToEvents(actions); + + // update the view + isResting = view._write(ts, actions, isResizingHorizontally); + + // will clean up all archived items + removeReleasedItems(store.query('GET_ITEMS')); + + // now idling + if (isResting) { + store.processDispatchQueue(); + } + + } + }; + + // + // EXPOSE EVENTS ------------------------------------------------------------------------------------- + // + const createEvent = name => data => { + // create default event + const event = { + type: name + }; + + // no data to add + if (!data) { + return event; + } + + // copy relevant props + if (data.hasOwnProperty('error')) { + event.error = data.error ? { ...data.error } : null; + } + + if (data.status) { + event.status = { ...data.status }; + } + + if (data.file) { + event.output = data.file; + } + + // only source is available, else add item if possible + if (data.source) { + event.file = data.source; + } else if (data.item || data.id) { + const item = data.item + ? data.item + : store.query('GET_ITEM', data.id); + event.file = item ? createItemAPI(item) : null; + } + + // map all items in a possible items array + if (data.items) { + event.items = data.items.map(createItemAPI); + } + + // if this is a progress event add the progress amount + if (/progress/.test(name)) { + event.progress = data.progress; + } + + return event; + }; + + const eventRoutes = { + DID_DESTROY: createEvent('destroy'), + + DID_INIT: createEvent('init'), + + DID_THROW_MAX_FILES: createEvent('warning'), + + DID_START_ITEM_LOAD: createEvent('addfilestart'), + DID_UPDATE_ITEM_LOAD_PROGRESS: createEvent('addfileprogress'), + DID_LOAD_ITEM: createEvent('addfile'), + + DID_THROW_ITEM_INVALID: [ + createEvent('error'), + createEvent('addfile') + ], + + DID_THROW_ITEM_LOAD_ERROR: [ + createEvent('error'), + createEvent('addfile') + ], + + DID_PREPARE_OUTPUT: createEvent('preparefile'), + + DID_START_ITEM_PROCESSING: createEvent('processfilestart'), + DID_UPDATE_ITEM_PROCESS_PROGRESS: createEvent('processfileprogress'), + DID_ABORT_ITEM_PROCESSING: createEvent('processfileabort'), + DID_COMPLETE_ITEM_PROCESSING: createEvent('processfile'), + DID_COMPLETE_ITEM_PROCESSING_ALL: createEvent('processfiles'), + DID_REVERT_ITEM_PROCESSING: createEvent('processfilerevert'), + + DID_THROW_ITEM_PROCESSING_ERROR: [ + createEvent('error'), + createEvent('processfile') + ], + + DID_REMOVE_ITEM: createEvent('removefile'), + + DID_UPDATE_ITEMS: createEvent('updatefiles'), + + DID_ACTIVATE_ITEM: createEvent('activatefile') + }; + + const exposeEvent = event => { + + // create event object to be dispatched + const detail = { pond: exports, ...event }; + delete detail.type; + view.element.dispatchEvent( + new CustomEvent(`FilePond:${event.type}`, { + // event info + detail, + + // event behaviour + bubbles: true, + cancelable: true, + composed: true // triggers listeners outside of shadow root + }) + ); + + // event object to params used for `on()` event handlers and callbacks `oninit()` + const params = []; + + // if is possible error event, make it the first param + if (event.hasOwnProperty('error')) { + params.push(event.error); + } + + // file is always section + if (event.hasOwnProperty('file')) { + params.push(event.file); + } + + // append other props + const filtered = ['type', 'error', 'file']; + Object.keys(event) + .filter(key => !filtered.includes(key)) + .forEach(key => params.push(event[key])); + + // on(type, () => { }) + exports.fire(event.type, ...params); + + // oninit = () => {} + const handler = store.query(`GET_ON${event.type.toUpperCase()}`); + if (handler) { + handler(...params); + } + }; + + const routeActionsToEvents = actions => { + if (!actions.length) { + return; + } + + actions.forEach(action => { + if (!eventRoutes[action.type]) { + return; + } + const routes = eventRoutes[action.type]; + (Array.isArray(routes) ? routes : [routes]).forEach(route => { + setTimeout(() => { + exposeEvent(route(action.data)); + }, 0); + }); + }); + }; + + // + // PUBLIC API ------------------------------------------------------------------------------------- + // + const setOptions = options => store.dispatch('SET_OPTIONS', { options }); + + const getFile = query => store.query('GET_ACTIVE_ITEM', query); + + const addFile = (source, options = {}) => + new Promise((resolve, reject) => { + store.dispatch('ADD_ITEM', { + interactionMethod: InteractionMethod.API, + source, + index: options.index, + success: resolve, + failure: reject, + options + }); + }); + + const removeFile = query => { + // request item removal + store.dispatch('REMOVE_ITEM', { query }); + + // see if item has been removed + return store.query('GET_ACTIVE_ITEM', query) === null; + }; + + const addFiles = (...args) => + new Promise((resolve, reject) => { + + const sources = []; + const options = {}; + + // user passed a sources array + if (isArray(args[0])) { + sources.push.apply(sources, args[0]); + Object.assign(options, args[1] || {}); + } + else { + // user passed sources as arguments, last one might be options object + const lastArgument = args[args.length - 1]; + if ( + typeof lastArgument === 'object' && + !(lastArgument instanceof Blob) + ) { + Object.assign(options, args.pop()); + } + + // add rest to sources + sources.push(...args); + } + + store.dispatch('ADD_ITEMS', { + items: sources, + index: options.index, + interactionMethod: InteractionMethod.API, + success: resolve, + failure: reject + }); + + }); + + const getFiles = () => store.query('GET_ACTIVE_ITEMS'); + + const processFile = query => + new Promise((resolve, reject) => { + store.dispatch('REQUEST_ITEM_PROCESSING', { + query, + success: (item) => { + resolve(item) + }, + failure: (error) => { + reject(error) + }, + }); + }); + + const processFiles = (...args) => { + const queries = Array.isArray(args[0]) ? args[0] : args; + if (!queries.length) { + const files = getFiles().filter(item => + item.status !== ItemStatus.PROCESSING && + item.status !== ItemStatus.PROCESSING_COMPLETE && + item.status !== ItemStatus.PROCESSING_REVERT_ERROR + ); + return Promise.all(files.map(processFile)); + } + return Promise.all(queries.map(processFile)); + }; + + const removeFiles = (...args) => { + const queries = Array.isArray(args[0]) ? args[0] : args; + const files = getFiles(); + + if (!queries.length) { + return Promise.all(files.map(removeFile)); + } + + // when removing by index the indexes shift after each file removal so we need to convert indexes to ids + const mappedQueries = queries.map(query => + isNumber(query) ? files[query] ? files[query].id : null : query + ).filter(query => query); + + return mappedQueries.map(removeFile); + }; + + const exports = { + // supports events + ...on(), + + // inject private api methods + ...readWriteApi, + + // inject all getters and setters + ...createOptionAPI(store, defaultOptions), + + /** + * Override options defined in options object + * @param options + */ + setOptions, + + /** + * Load the given file + * @param source - the source of the file (either a File, base64 data uri or url) + * @param options - object, { index: 0 } + */ + addFile, + + /** + * Load the given files + * @param sources - the sources of the files to load + * @param options - object, { index: 0 } + */ + addFiles, + + /** + * Returns the file objects matching the given query + * @param query { string, number, null } + */ + getFile, + + /** + * Upload file with given name + * @param query { string, number, null } + */ + processFile, + + /** + * Removes a file by its name + * @param query { string, number, null } + */ + removeFile, + + /** + * Returns all files (wrapped in public api) + */ + getFiles, + + /** + * Starts uploading all files + */ + processFiles, + + /** + * Clears all files from the files list + */ + removeFiles, + + /** + * Sort list of files + */ + sort: (compare) => store.dispatch('SORT', { compare }), + + /** + * Browse the file system for a file + */ + browse: () => { + // needs to be trigger directly as user action needs to be traceable (is not traceable in requestAnimationFrame) + var input = view.element.querySelector('input[type=file]'); + if (input) { + input.click(); + } + }, + + /** + * Destroys the app + */ + destroy: () => { + // request destruction + exports.fire('destroy', view.element); + + // stop active processes (file uploads, fetches, stuff like that) + // loop over items and depending on states call abort for ongoing processes + store.dispatch('ABORT_ALL'); + + // destroy view + view._destroy(); + + // stop listening to resize + window.removeEventListener('resize', resizeHandler); + + // stop listening to the visiblitychange event + document.addEventListener('visibilitychange', visibilityHandler); + + // dispatch destroy + store.dispatch('DID_DESTROY'); + }, + + /** + * Inserts the plugin before the target element + */ + insertBefore: element => insertBefore(view.element, element), + + /** + * Inserts the plugin after the target element + */ + insertAfter: element => insertAfter(view.element, element), + + /** + * Appends the plugin to the target element + */ + appendTo: element => element.appendChild(view.element), + + /** + * Replaces an element with the app + */ + replaceElement: element => { + // insert the app before the element + insertBefore(view.element, element); + + // remove the original element + element.parentNode.removeChild(element); + + // remember original element + originalElement = element; + }, + + /** + * Restores the original element + */ + restoreElement: () => { + if (!originalElement) { + return; // no element to restore + } + + // restore original element + insertAfter(originalElement, view.element); + + // remove our element + view.element.parentNode.removeChild(view.element); + + // remove reference + originalElement = null; + }, + + /** + * Returns true if the app root is attached to given element + * @param element + */ + isAttachedTo: element => + view.element === element || originalElement === element, + + /** + * Returns the root element + */ + element: { + get: () => view.element + }, + + /** + * Returns the current pond status + */ + status: { + get: () => store.query('GET_STATUS') + } + }; + + // Done! + store.dispatch('DID_INIT'); + + // create actual api object + return createObject(exports); +}; diff --git a/src/js/app/options.js b/src/js/app/options.js new file mode 100644 index 00000000..9675e1cf --- /dev/null +++ b/src/js/app/options.js @@ -0,0 +1,170 @@ +import { getDecimalSeparator } from '../utils/getDecimalSeparator'; +import { getThousandsSeparator } from '../utils/getThousandsSeparator'; +import { Type } from './enum/Type'; +import { applyFilters } from './../filter'; +import { forin } from '../utils/forin'; +import { isString } from '../utils/isString'; +import { createServerAPI } from './utils/createServerAPI'; +import { getValueByType } from './utils/getValueByType'; + +export const extendDefaultOptions = additionalOptions => + Object.assign(defaultOptions, additionalOptions); + +export const getOptions = () => ({ ...defaultOptions }); + +export 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 +export 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/*" + + // 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 + + // 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 immidiately on drop + maxParallelUploads: [2, Type.INT], // Maximum files to upload in parallel + + // The server api end points to use for uploading (see docs) + server: [null, Type.SERVER_API], + + // Labels and status messages + labelDecimalSeparator: [getDecimalSeparator(), Type.STRING], // Default is locale separator + labelThousandsSeparator: [getThousandsSeparator(), Type.STRING], // Default is locale separator + + labelIdle: ['Drag & Drop your files or Browse', 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: [ + '', + Type.STRING + ], + iconProcess: [ + '', + Type.STRING + ], + iconRetry: [ + '', + Type.STRING + ], + iconUndo: [ + '', + Type.STRING + ], + iconDone: [ + '', + Type.STRING + ], + + // event handlers + oninit: [null, Type.FUNCTION], + onwarning: [null, Type.FUNCTION], + onerror: [null, Type.FUNCTION], + onactivatefile: [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], + + // hooks + beforeAddFile: [null, Type.FUNCTION], + beforeRemoveFile: [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], + + // custom initial files array + files: [[], Type.ARRAY] +}; diff --git a/src/js/app/queries.js b/src/js/app/queries.js new file mode 100644 index 00000000..03f9ea7c --- /dev/null +++ b/src/js/app/queries.js @@ -0,0 +1,77 @@ +import { isObject } from '../utils/isObject'; +import { isFunction } from '../utils/isFunction'; +import { getItemByQuery } from './utils/getItemByQuery'; +import { getNumericAspectRatioFromString } from '../utils/getNumericAspectRatioFromString'; +import { getActiveItems } from './utils/getActiveItems'; +import { Status } from './enum/Status'; +import { ItemStatus } from './enum/ItemStatus'; + +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); + +export 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: query => getActiveItems(state.items), + + GET_ITEMS: query => 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, + + IS_ASYNC: () => + isObject(state.options.server) && + (isObject(state.options.server.process) || + isFunction(state.options.server.process)) +}); diff --git a/src/js/app/utils/convertTo.js b/src/js/app/utils/convertTo.js new file mode 100644 index 00000000..57d94c09 --- /dev/null +++ b/src/js/app/utils/convertTo.js @@ -0,0 +1,39 @@ +import { toArray } from '../../utils/toArray'; +import { toBoolean } from '../../utils/toBoolean'; +import { toInt } from '../../utils/toInt'; +import { toFloat } from '../../utils/toFloat'; +import { toBytes } from '../../utils/toBytes'; +import { toString } from '../../utils/toString'; +import { isFunction } from '../../utils/isFunction'; +import { toFunctionReference } from '../../utils/toFunctionReference'; +import { toServerAPI } from './toServerAPI'; +import { getType } from './getType'; + +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), + float: toFloat, + bytes: toBytes, + string: value => isFunction(value) ? value : toString(value), + serverapi: toServerAPI, + object: value => { + try { + return JSON.parse(replaceSingleQuotes(value)) + } + catch(e) { + return null; + } + }, + function: value => toFunctionReference(value) +}; + +export const convertTo = (value, type) => conversionTable[type](value); diff --git a/src/js/app/utils/createFetchFunction.js b/src/js/app/utils/createFetchFunction.js new file mode 100644 index 00000000..e80adc69 --- /dev/null +++ b/src/js/app/utils/createFetchFunction.js @@ -0,0 +1,81 @@ +import { sendRequest } from '../../utils/sendRequest'; +import { createResponse } from '../../utils/createResponse'; +import { createTimeoutResponse } from '../../utils/createDefaultResponse'; +import { getFileInfoFromHeaders } from '../../utils/getFileInfoFromHeaders'; +import { getFilenameFromURL } from '../../utils/getFilenameFromURL'; +import { getFileFromBlob } from '../../utils/getFileFromBlob'; +import { isString } from '../../utils/isString'; + +export 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, 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, + 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; + }; +}; diff --git a/src/js/app/utils/createFileLoader.js b/src/js/app/utils/createFileLoader.js new file mode 100644 index 00000000..fc38c154 --- /dev/null +++ b/src/js/app/utils/createFileLoader.js @@ -0,0 +1,144 @@ +import { isBase64DataURI } from '../../utils/isBase64DataURI'; +import { getFilenameFromURL } from '../../utils/getFilenameFromURL'; +import { getFileFromBase64DataURI } from '../../utils/getFileFromBase64DataURI'; +import { getFileFromBlob } from '../../utils/getFileFromBlob'; +import { getFileInfoFromHeaders } from '../../utils/getFileInfoFromHeaders'; +import { on } from '../utils/on'; + +export 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) { + return; + } + 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, + getFilenameFromURL(url) + ); + } + + api.fire( + 'load', + response instanceof Blob ? response : response.body + ); + }, + 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; +}; diff --git a/src/js/app/utils/createFileProcessor.js b/src/js/app/utils/createFileProcessor.js new file mode 100644 index 00000000..e9c03d94 --- /dev/null +++ b/src/js/app/utils/createFileProcessor.js @@ -0,0 +1,189 @@ +import { createPerceivedPerformanceUpdater } from './createPerceivedPerformanceUpdater'; +import { getRandomNumber } from '../../utils/getRandomNumber'; +import { on } from '../utils/on'; +import { isObject } from '../../utils/isObject'; + +export const createFileProcessor = processFn => { + const state = { + complete: false, + perceivedProgress: 0, + perceivedPerformanceUpdater: null, + progress: null, + timestamp: null, + perceivedDuration: 0, + duration: 0, + request: null, + response: null + }; + + 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 + getRandomNumber(750, 1500) + ); + + // 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) + // 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 (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); + } + ); + }; + + const abort = () => { + + // no request running, can't abort + if (!state.request) return; + + // stop updater + state.perceivedPerformanceUpdater.clear(); + + // abort actual request + 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 = () => + state.progress + ? Math.min(state.progress, state.perceivedProgress) + : null; + const getDuration = () => Math.min(state.duration, state.perceivedDuration); + + const api = { + ...on(), + process, // start processing file + abort, // abort active process request + getProgress, + getDuration, + reset + }; + + return api; +}; diff --git a/src/js/app/utils/createFileStub.js b/src/js/app/utils/createFileStub.js new file mode 100644 index 00000000..f64a1159 --- /dev/null +++ b/src/js/app/utils/createFileStub.js @@ -0,0 +1,29 @@ +import { getMimeTypeFromBase64DataURI } from '../../utils/getMimeTypeFromBase64DataURI'; +import { getFilenameFromURL } from '../../utils/getFilenameFromURL'; +import { isBase64DataURI } from '../../utils/isBase64DataURI'; +import { isString } from '../../utils/isString'; +import { getDateString } from '../../utils/getDateString'; + +export 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] + }; +}; diff --git a/src/js/app/utils/createHopper.js b/src/js/app/utils/createHopper.js new file mode 100644 index 00000000..a76e1400 --- /dev/null +++ b/src/js/app/utils/createHopper.js @@ -0,0 +1,74 @@ +import { createDragNDropClient } from '../utils/dnd'; + +export const createHopper = (scope, validateItems, options) => { + // is now hopper scope + scope.classList.add('filepond--hopper'); + + // shortcuts + const { catchesDropsOnPage, requiresDropOnElement } = options; + + // create a dnd client + const client = createDragNDropClient( + scope, + catchesDropsOnPage ? document.documentElement : scope, + requiresDropOnElement + ); + + // current client state + let lastState = ''; + let currentState = ''; + + // determines if a file may be dropped + client.allowdrop = items => { + // TODO: if we can, throw error to indicate the items cannot by dropped + + return validateItems(items); + }; + + client.ondrop = (position, items) => { + + if (!validateItems(items)) { + api.ondragend(position); + return; + } + + currentState = 'drag-drop'; + + api.onload(items, position); + }; + + client.ondrag = position => { + api.ondrag(position); + }; + + client.onenter = position => { + currentState = 'drag-over'; + + api.ondragstart(position); + }; + + client.onexit = position => { + currentState = 'drag-exit'; + + api.ondragend(position); + }; + + const api = { + updateHopperState: () => { + if (lastState !== currentState) { + scope.dataset.hopperState = currentState; + lastState = currentState; + } + }, + onload: () => {}, + ondragstart: () => {}, + ondrag: () => {}, + ondragend: () => {}, + destroy: () => { + // destroy client + client.destroy(); + } + }; + + return api; +}; diff --git a/src/js/app/utils/createInitialState.js b/src/js/app/utils/createInitialState.js new file mode 100644 index 00000000..7be29ce8 --- /dev/null +++ b/src/js/app/utils/createInitialState.js @@ -0,0 +1,16 @@ +import { createOptions } from './createOptions'; + +export const createInitialState = options => ({ + + // model + items: [], + + // timeout used for calling update items + listUpdateTimeout: null, + + // queue of items waiting to be processed + processingQueue: [], + + // options + options: createOptions(options) +}); diff --git a/src/js/app/utils/createItem.js b/src/js/app/utils/createItem.js new file mode 100644 index 00000000..361aa381 --- /dev/null +++ b/src/js/app/utils/createItem.js @@ -0,0 +1,430 @@ +import { getUniqueId } from '../../utils/getUniqueId'; +import { getFilenameWithoutExtension } from '../../utils/getFilenameWithoutExtension'; +import { getExtensionFromFilename } from '../../utils/getExtensionFromFilename'; +import { ItemStatus } from '../enum/ItemStatus'; +import { on } from './on'; +import { createFileStub } from './createFileStub'; +import { createObject } from '../../utils/createObject'; +import { FileOrigin } from '../../app/enum/FileOrigin'; +import { isObject } from '../../utils/isObject'; +import { isFile } from '../../utils/isFile'; +import { deepCloneObject } from '../../utils/deepCloneObject'; + +export 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, + + // removed from view + released: false, + + // original source + source: null, + + // file model reference + file, + + // id of file on server + serverFileReference, + + // 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) 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; + + // file stub is already there + if (state.file) { + fire('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) => { + + // 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.serverFileReference = serverFileReference; + + }); + + processor.on('load-perceived', serverFileReference => { + // no longer required + state.activeProcessor = null; + + // need this id to be able to rever the upload + 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 = result => {}; + + // start processing the file + onprocess(state.file, success, error); + + // set as active processor + state.activeProcessor = processor; + }; + + const requestProcessing = () => { + setStatus(ItemStatus.PROCESSING_QUEUED); + } + + const abortProcessing = () => new Promise((resolve) => { + + if (!state.activeProcessor) { + + 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) => { + + // cannot revert without a server id for this process + if (state.serverFileReference === null) { + resolve(); + return; + } + + // revert the upload (fire and forget) + revertFileUpload( + state.serverFileReference, + () => { + + // reset file server id as now it's no available on the server + state.serverFileReference = 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; + + if (silent) return; + + fire('metadata-update', { + key: root, + value: metadata[root] + }); + } + + const getMetadata = (key) => deepCloneObject(key ? metadata[key] : metadata); + + const api = { + id: { get: () => id }, + origin: { get:() => origin }, + serverId: { get: () => state.serverFileReference }, + 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 }, + + 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(), + + release: () => state.released = true, + released: { get: () => state.released }, + + archive: () => state.archived = true, + archived: { get: () => state.archived } + }; + + // create it here instead of returning it instantly so we can extend it later + const itemAPI = createObject(api); + + return itemAPI; + +}; diff --git a/src/js/app/utils/createItemAPI.js b/src/js/app/utils/createItemAPI.js new file mode 100644 index 00000000..0bc90136 --- /dev/null +++ b/src/js/app/utils/createItemAPI.js @@ -0,0 +1,24 @@ +import { copyObjectPropertiesToObject } from '../../utils/copyObjectPropertiesToObject'; + +const PRIVATE = [ + 'fire', + 'process', + 'revert', + 'load', + 'on', + 'off', + 'onOnce', + 'retryLoad', + 'extend', + 'archive', + 'archived', + 'release', + 'released', + 'requestProcessing' +]; + +export const createItemAPI = item => { + const api = {}; + copyObjectPropertiesToObject(item, api, PRIVATE); + return api; +}; \ No newline at end of file diff --git a/src/js/app/utils/createOption.js b/src/js/app/utils/createOption.js new file mode 100644 index 00000000..1eca1306 --- /dev/null +++ b/src/js/app/utils/createOption.js @@ -0,0 +1,12 @@ +import { getValueByType } from './getValueByType'; + +export const createOption = (defaultValue, valueType) => { + let currentValue = defaultValue; + return { + enumerable: true, + get: () => currentValue, + set: newValue => { + currentValue = getValueByType(newValue, defaultValue, valueType); + } + }; +}; diff --git a/src/js/app/utils/createOptionAPI.js b/src/js/app/utils/createOptionAPI.js new file mode 100644 index 00000000..9d96ef9a --- /dev/null +++ b/src/js/app/utils/createOptionAPI.js @@ -0,0 +1,17 @@ +import { fromCamels } from '../../utils/fromCamels'; +import { forin } from '../../utils/forin'; + +export 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; +}; diff --git a/src/js/app/utils/createOptionActions.js b/src/js/app/utils/createOptionActions.js new file mode 100644 index 00000000..9ac8af7e --- /dev/null +++ b/src/js/app/utils/createOptionActions.js @@ -0,0 +1,21 @@ +import { fromCamels } from '../../utils/fromCamels'; +import { forin } from '../../utils/forin'; + +export 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; +}; diff --git a/src/js/app/utils/createOptionQueries.js b/src/js/app/utils/createOptionQueries.js new file mode 100644 index 00000000..48c669e1 --- /dev/null +++ b/src/js/app/utils/createOptionQueries.js @@ -0,0 +1,11 @@ +import { fromCamels } from '../../utils/fromCamels'; +import { forin } from '../../utils/forin'; + +export const createOptionQueries = options => state => { + const obj = {}; + forin(options, key => { + obj[`GET_${fromCamels(key, '_').toUpperCase()}`] = action => + state.options[key]; + }); + return obj; +}; diff --git a/src/js/app/utils/createOptions.js b/src/js/app/utils/createOptions.js new file mode 100644 index 00000000..c2cd383e --- /dev/null +++ b/src/js/app/utils/createOptions.js @@ -0,0 +1,15 @@ +import { createObject } from '../../utils/createObject'; +import { createOption } from './createOption'; +import { forin } from '../../utils/forin'; + +export const createOptions = options => { + const obj = {}; + forin(options, prop => { + const optionDefinition = options[prop]; + obj[prop] = createOption( + optionDefinition[0], + optionDefinition[1] + ); + }); + return createObject(obj); +}; diff --git a/src/js/app/utils/createPaster.js b/src/js/app/utils/createPaster.js new file mode 100644 index 00000000..e86b4c08 --- /dev/null +++ b/src/js/app/utils/createPaster.js @@ -0,0 +1,63 @@ +import { arrayRemove } from '../../utils/arrayRemove'; +import { InteractionMethod } from '../enum/InteractionMethod'; +import { requestDataTransferItems } from './requestDataTransferItems'; + +let listening = false; +const listeners = []; + +const handlePaste = e => { + requestDataTransferItems(e.clipboardData).then(files => { + // no files received + if (!files.length) { + return; + } + + // notify listeners of received files + listeners.forEach(listener => listener(files)); + }); +}; + +const listen = cb => { + // can't add twice + if (listeners.includes(cb)) { + return; + } + + // add initial listener + listeners.push(cb); + + // setup paste listener for entire page + if (listening) { + return; + } + + listening = true; + document.addEventListener('paste', handlePaste); +}; + +const unlisten = listener => { + arrayRemove(listeners, listeners.indexOf(listener)); + + // clean up + if (listeners.length === 0) { + document.removeEventListener('paste', handlePaste); + listening = false; + } +}; + +export const createPaster = () => { + const cb = files => { + api.onload(files); + }; + + const api = { + destroy: () => { + unlisten(cb); + }, + onload: () => {} + }; + + listen(cb); + + return api; +}; diff --git a/src/js/app/utils/createPerceivedPerformanceUpdater.js b/src/js/app/utils/createPerceivedPerformanceUpdater.js new file mode 100644 index 00000000..fab457c2 --- /dev/null +++ b/src/js/app/utils/createPerceivedPerformanceUpdater.js @@ -0,0 +1,39 @@ +import { getRandomNumber } from '../../utils/getRandomNumber'; + +export 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) { + cb(1); + return; + } + + cb(progress); + + timeout = setTimeout(tick, delay); + }; + + tick(); + + return { + clear: () => { + clearTimeout(timeout); + } + }; +}; diff --git a/src/js/app/utils/createProcessorFunction.js b/src/js/app/utils/createProcessorFunction.js new file mode 100644 index 00000000..c83d10b1 --- /dev/null +++ b/src/js/app/utils/createProcessorFunction.js @@ -0,0 +1,79 @@ +import { sendRequest } from '../../utils/sendRequest'; +import { createResponse } from '../../utils/createResponse'; +import { createTimeoutResponse } from '../../utils/createDefaultResponse'; +import { isString } from '../../utils/isString'; +import { isObject } from '../../utils/isObject'; + +/* +function signature: + (file, metadata, load, error, progress, abort) => { + return { + abort:() => {} + } +} +*/ +export const createProcessorFunction = (apiUrl = '', action, name) => { + // custom handler (should also handle file, load, error, progress and abort) + if (typeof action === 'function') { + return (...params) => action(name, ...params); + } + + // no action supplied + if (!action || !isString(action.url)) { + return null; + } + + // internal handler + return (file, metadata, load, error, progress, abort) => { + + // set onload hanlder + const ondata = action.ondata || (fd => fd); + const onload = action.onload || (res => res); + const onerror = action.onerror || (res => null); + + // no file received + if (!file) return; + + // 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), apiUrl + action.url, action); + 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; + }; +}; diff --git a/src/js/app/utils/createRevertFunction.js b/src/js/app/utils/createRevertFunction.js new file mode 100644 index 00000000..225588ea --- /dev/null +++ b/src/js/app/utils/createRevertFunction.js @@ -0,0 +1,58 @@ +import { sendRequest } from '../../utils/sendRequest'; +import { createResponse } from '../../utils/createResponse'; +import { createTimeoutResponse } from '../../utils/createDefaultResponse'; +import { isString } from '../../utils/isString'; + +/* + function signature: + (uniqueFileId, load, error) => { } + */ +export 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; + }; +}; diff --git a/src/js/app/utils/createServerAPI.js b/src/js/app/utils/createServerAPI.js new file mode 100644 index 00000000..8cc959d1 --- /dev/null +++ b/src/js/app/utils/createServerAPI.js @@ -0,0 +1,74 @@ +import { isString } from '../../utils/isString'; +import { toBoolean } from '../../utils/toBoolean'; +import { forin } from '../../utils/forin'; + +const methods = { + process: 'POST', + revert: 'DELETE', + fetch: 'GET', + restore: 'GET', + load: 'GET' +}; + +export const createServerAPI = outline => { + const api = {}; + + api.url = isString(outline) ? outline : outline.url || ''; + api.timeout = outline.timeout ? parseInt(outline.timeout, 10) : 0; + + forin(methods, key => { + api[key] = createAction(key, outline[key], methods[key], api.timeout); + }); + + // special treatment for remove + api.remove = outline.remove || null; + + return api; +}; + +const createAction = (name, outline, method, timeout) => { + // 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' ? `?${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; +}; diff --git a/src/js/app/utils/dnd.js b/src/js/app/utils/dnd.js new file mode 100644 index 00000000..6283295f --- /dev/null +++ b/src/js/app/utils/dnd.js @@ -0,0 +1,269 @@ +import { forin } from '../../utils/forin'; +import { getRootNode } from '../../utils/getRootNode'; +import { requestDataTransferItems } from './requestDataTransferItems'; + +const dragNDropObservers = []; + +const eventPosition = e => ({ + pageLeft: e.pageX, + pageTop: e.pageY, + scopeLeft: e.offsetX || e.layerX, + scopeTop: e.offsetY || e.layerY +}); + +export const createDragNDropClient = ( + element, + scopeToObserve, + filterElement +) => { + const observer = getDragNDropObserver(scopeToObserve); + + const client = { + element, + filterElement, + state: null, + ondrop: () => {}, + onenter: () => {}, + ondrag: () => {}, + onexit: () => {}, + onload: () => {}, + allowdrop: () => {} + }; + + client.destroy = observer.addListener(client); + + return client; +}; + +const getDragNDropObserver = element => { + // see if already exists, if so, return + const observer = dragNDropObservers.find(item => item.element === element); + if (observer) { + return observer; + } + + // create new observer, does not yet exist for this element + const newObserver = createDragNDropObserver(element); + dragNDropObservers.push(newObserver); + return newObserver; +}; + +const createDragNDropObserver = element => { + const clients = []; + + const routes = { + dragenter, + dragover, + dragleave, + drop + }; + + const handlers = {}; + + forin(routes, (event, createHandler) => { + handlers[event] = createHandler(element, clients); + element.addEventListener(event, handlers[event], false); + }); + + const observer = { + element, + addListener: client => { + // add as client + clients.push(client); + + // return removeListener function + return () => { + // remove client + clients.splice(clients.indexOf(client), 1); + + // if no more clients, clean up observer + if (clients.length === 0) { + dragNDropObservers.splice( + dragNDropObservers.indexOf(observer), + 1 + ); + + forin(routes, event => { + element.removeEventListener( + event, + handlers[event], + false + ); + }); + } + }; + } + }; + + return observer; +}; + +const elementFromPoint = (root, point) => { + if (!('elementFromPoint' in root)) { + root = document; + } + return root.elementFromPoint(point.x, point.y); +} + +const isEventTarget = (e, target) => { + // get root + const root = getRootNode(target); + + // get element at position + // if root is not actual shadow DOM and does not have elementFromPoint method, use the one on document + const elementAtPosition = elementFromPoint(root,{ + x: e.pageX - window.pageXOffset, + y: e.pageY - window.pageYOffset + }); + + // test if target is the element or if one of its children is + return elementAtPosition === target || target.contains(elementAtPosition); +}; + +let initialTarget = null; + +const setDropEffect = (dataTransfer, effect) => { + // is in try catch as IE11 will throw error if not + try { + dataTransfer.dropEffect = effect; + } catch (e) {} +}; + +const dragenter = (root, clients) => e => { + e.preventDefault(); + + initialTarget = e.target; + + clients.forEach(client => { + const { element, onenter } = client; + + if (isEventTarget(e, element)) { + client.state = 'enter'; + + // fire enter event + onenter(eventPosition(e)); + } + }); +}; + +const dragover = (root, clients) => e => { + e.preventDefault(); + + const dataTransfer = e.dataTransfer; + + requestDataTransferItems(dataTransfer).then(items => { + + let overDropTarget = false; + + clients.some(client => { + const { + filterElement, + element, + onenter, + onexit, + ondrag, + allowdrop + } = client; + + // by default we can drop + setDropEffect(dataTransfer, 'copy'); + + // allow transfer of these items + const allowsTransfer = allowdrop(items); + + // only used when can be dropped on page + if (!allowsTransfer) { + setDropEffect(dataTransfer, 'none'); + return; + } + + // targetting this client + if (isEventTarget(e, element)) { + + overDropTarget = true; + + // had no previous state, means we are entering this client + if (client.state === null) { + client.state = 'enter'; + onenter(eventPosition(e)); + return; + } + + // now over element (no matter if it allows the drop or not) + client.state = 'over'; + + // needs to allow transfer + if (filterElement && !allowsTransfer) { + setDropEffect(dataTransfer, 'none'); + return; + } + + // dragging + ondrag(eventPosition(e)); + } else { + + // should be over an element to drop + if (filterElement && !overDropTarget) { + setDropEffect(dataTransfer, 'none'); + } + + // might have just left this client? + if (client.state) { + client.state = null; + onexit(eventPosition(e)); + } + } + }); + + }); + +}; + +const drop = (root, clients) => e => { + e.preventDefault(); + + const dataTransfer = e.dataTransfer; + + requestDataTransferItems(dataTransfer).then(items => { + clients.forEach(client => { + const { + filterElement, + element, + ondrop, + onexit, + allowdrop + } = client; + + client.state = null; + + const allowsTransfer = allowdrop(items); + + // no transfer for this client + if (!allowsTransfer) { + onexit(eventPosition(e)); + return; + } + + // if we're filtering on element we need to be over the element to drop + if (filterElement && !isEventTarget(e, element)) { + return; + } + + ondrop(eventPosition(e), items); + }); + }); +}; + +const dragleave = (root, clients) => e => { + if (initialTarget !== e.target) { + return; + } + + clients.forEach(client => { + const { onexit } = client; + + client.state = null; + + onexit(eventPosition(e)); + }); +}; diff --git a/src/js/app/utils/dynamicLabel.js b/src/js/app/utils/dynamicLabel.js new file mode 100644 index 00000000..ee861d10 --- /dev/null +++ b/src/js/app/utils/dynamicLabel.js @@ -0,0 +1,3 @@ +import { isFunction } from '../../utils/isFunction'; + +export const dynamicLabel = (label) => (...params) => isFunction(label) ? label(...params) : label; \ No newline at end of file diff --git a/src/js/app/utils/fetchLocal.js b/src/js/app/utils/fetchLocal.js new file mode 100644 index 00000000..a90b2bda --- /dev/null +++ b/src/js/app/utils/fetchLocal.js @@ -0,0 +1,62 @@ +import { sendRequest } from '../../utils/sendRequest'; +import { createResponse } from '../../utils/createResponse'; +import { createTimeoutResponse } from '../../utils/createDefaultResponse'; +import { getFileFromBlob } from '../../utils/getFileFromBlob'; +import { getFileInfoFromHeaders } from '../../utils/getFileInfoFromHeaders'; +import { getFilenameFromURL } from '../../utils/getFilenameFromURL'; + +export const fetchLocal = (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; +}; diff --git a/src/js/app/utils/getActiveItems.js b/src/js/app/utils/getActiveItems.js new file mode 100644 index 00000000..d417f10e --- /dev/null +++ b/src/js/app/utils/getActiveItems.js @@ -0,0 +1 @@ +export const getActiveItems = (items) => items.filter(item => !item.archived); \ No newline at end of file diff --git a/src/js/app/utils/getItemById.js b/src/js/app/utils/getItemById.js new file mode 100644 index 00000000..1346140b --- /dev/null +++ b/src/js/app/utils/getItemById.js @@ -0,0 +1,9 @@ +import { getItemIndexByQuery } from './getItemIndexByQuery'; + +export const getItemById = (items, itemId) => { + const index = getItemIndexByQuery(items, itemId); + if (index < 0) { + return; + } + return items[index] || null; +}; diff --git a/src/js/app/utils/getItemByQuery.js b/src/js/app/utils/getItemByQuery.js new file mode 100644 index 00000000..2a2b6780 --- /dev/null +++ b/src/js/app/utils/getItemByQuery.js @@ -0,0 +1,23 @@ +import { isEmpty } from '../../utils/isEmpty'; +import { isString } from '../../utils/isString'; +import { isInt } from '../../utils/isInt'; + +export 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; +}; diff --git a/src/js/app/utils/getItemIndexByPosition.js b/src/js/app/utils/getItemIndexByPosition.js new file mode 100644 index 00000000..b8ab998d --- /dev/null +++ b/src/js/app/utils/getItemIndexByPosition.js @@ -0,0 +1,66 @@ +export const getItemIndexByPosition = (view, positionInView) => { + + if (!positionInView) return; + + const horizontalSpace = view.rect.element.width; + const children = view.childViews; + const l = children.length; + let last = null; + + // -1, don't move items to accomodate (either add to top or bottom) + if (l === 0 || positionInView.top < children[0].rect.element.top) return -1; + + // let's get the item width + const item = children[0]; + const itemRect = item.rect.element; + const itemHorizontalMargin = itemRect.marginLeft + itemRect.marginRight; + const itemWidth = itemRect.width + itemHorizontalMargin; + const itemsPerRow = Math.round(horizontalSpace / itemWidth); + + // stack + if (itemsPerRow === 1) { + for (let index=0; index itemTop) { + if (positionInView.left < itemRight) { + return index; + } + else if (index !== l - 1) { + last = index; + } + else { + last = null; + } + } + + } + + if (last !== null) { + return last; + } + + return l; +}; \ No newline at end of file diff --git a/src/js/app/utils/getItemIndexByQuery.js b/src/js/app/utils/getItemIndexByQuery.js new file mode 100644 index 00000000..b10f669b --- /dev/null +++ b/src/js/app/utils/getItemIndexByQuery.js @@ -0,0 +1,17 @@ +import { isEmpty } from '../../utils/isEmpty'; +import { isString } from '../../utils/isString'; + +export 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); +}; diff --git a/src/js/app/utils/getType.js b/src/js/app/utils/getType.js new file mode 100644 index 00000000..3217c850 --- /dev/null +++ b/src/js/app/utils/getType.js @@ -0,0 +1,28 @@ +import { isArray } from '../../utils/isArray'; +import { isInt } from '../../utils/isInt'; +import { isNull } from '../../utils/isNull'; +import { isAPI } from './isAPI'; + +export 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; +}; diff --git a/src/js/app/utils/getValueByType.js b/src/js/app/utils/getValueByType.js new file mode 100644 index 00000000..ac1905d8 --- /dev/null +++ b/src/js/app/utils/getValueByType.js @@ -0,0 +1,32 @@ + +import { convertTo } from './convertTo'; +import { getType } from './getType'; + +export 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; +} \ No newline at end of file diff --git a/src/js/app/utils/hasRoomForItem.js b/src/js/app/utils/hasRoomForItem.js new file mode 100644 index 00000000..7c7254ed --- /dev/null +++ b/src/js/app/utils/hasRoomForItem.js @@ -0,0 +1,23 @@ +import { getActiveItems } from './getActiveItems'; +export 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; +}; diff --git a/src/js/app/utils/insertItem.js b/src/js/app/utils/insertItem.js new file mode 100644 index 00000000..e5e0f068 --- /dev/null +++ b/src/js/app/utils/insertItem.js @@ -0,0 +1,24 @@ +import { isEmpty } from '../../utils/isEmpty'; +import { limit } from '../../utils/limit'; +import { arrayInsert } from '../../utils/arrayInsert'; + +export 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; +}; diff --git a/src/js/app/utils/isAPI.js b/src/js/app/utils/isAPI.js new file mode 100644 index 00000000..6304276f --- /dev/null +++ b/src/js/app/utils/isAPI.js @@ -0,0 +1,13 @@ +import { isObject } from '../../utils/isObject'; +import { isString } from '../../utils/isString'; + +export const isAPI = value => { + return ( + isObject(value) && + isString(value.url) && + isObject(value.process) && + isObject(value.revert) && + isObject(value.restore) && + isObject(value.fetch) + ); +}; diff --git a/src/js/app/utils/mergeOptionObject.js b/src/js/app/utils/mergeOptionObject.js new file mode 100644 index 00000000..570dbe67 --- /dev/null +++ b/src/js/app/utils/mergeOptionObject.js @@ -0,0 +1,8 @@ +import { forin } from '../../utils/forin'; +export const mergeOptionObject = (originalObject, newObject) => { + const obj = {}; + forin(originalObject, key => { + obj[key] = newObject[key] || originalObject[key]; + }); + return obj; +}; diff --git a/src/js/app/utils/on.js b/src/js/app/utils/on.js new file mode 100644 index 00000000..2727f11e --- /dev/null +++ b/src/js/app/utils/on.js @@ -0,0 +1,39 @@ +import { arrayRemove } from '../../utils/arrayRemove'; + +export const on = () => { + const listeners = []; + const off = (event, cb) => { + arrayRemove( + listeners, + listeners.findIndex( + listener => + listener.event === event && (listener.cb === cb || !cb) + ) + ); + }; + return { + fire: (event, ...args) => { + listeners + .filter(listener => listener.event === event) + .map(listener => listener.cb) + .forEach(cb => { + setTimeout(() => { + cb(...args); + }, 0); + }); + }, + on: (event, cb) => { + listeners.push({ event, cb }); + }, + onOnce: (event, cb) => { + listeners.push({ + event, + cb: (...args) => { + off(event, cb); + cb(...args); + } + }); + }, + off + }; +}; diff --git a/src/js/app/utils/removeReleasedItems.js b/src/js/app/utils/removeReleasedItems.js new file mode 100644 index 00000000..28dec1e3 --- /dev/null +++ b/src/js/app/utils/removeReleasedItems.js @@ -0,0 +1,9 @@ +import { arrayRemove } from '../../utils/arrayRemove'; + +export const removeReleasedItems = (items) => { + items.forEach((item, index) => { + if (item.released) { + arrayRemove(items, index); + } + }); +}; \ No newline at end of file diff --git a/src/js/app/utils/requestDataTransferItems.js b/src/js/app/utils/requestDataTransferItems.js new file mode 100644 index 00000000..240ca523 --- /dev/null +++ b/src/js/app/utils/requestDataTransferItems.js @@ -0,0 +1,165 @@ +import { guesstimateMimeType } from '../../utils/guesstimateMimeType'; +import { getExtensionFromFilename } from '../../utils/getExtensionFromFilename'; + +export const requestDataTransferItems = dataTransfer => + new Promise((resolve, reject) => { + // try to get links from transfer, if found, we'll exit immidiately + // as only one link can be dragged at once + const links = getLinks(dataTransfer); + if (links.length) { + resolve(links); + return; + } + + // try to get files from the transfer + getFiles(dataTransfer).then(resolve); + }); + +/** + * Extracts files from a DataTransfer object + */ +const getFiles = dataTransfer => + new Promise((resolve, reject) => { + // get the transfer items as promises + const promisedFiles = (dataTransfer.items + ? Array.from(dataTransfer.items) + : [] + ) + + // only keep file system items (files and directories) + .filter(item => isFileSystemItem(item)) + + // map each item to promise + .map(item => getFilesFromItem(item)); + + // if is empty, see if we can extract some info from the files property as a fallback + if (!promisedFiles.length) { + // TODO: test for directories (should not be allowed) + // Use FileReader, problem is that the files property gets lost in the process + + resolve(dataTransfer.files ? Array.from(dataTransfer.files) : []); + return; + } + + // done! + Promise.all(promisedFiles).then(returendFileGroups => { + // flatten groups + const files = []; + returendFileGroups.forEach(group => { + files.push.apply(files, group); + }); + + // done (filter out empty files)! + resolve(files.filter(file => file)); + }); + }); + +const isFileSystemItem = item => { + if (isEntry(item)) { + const entry = getAsEntry(item); + if (entry) { + return entry.isFile || entry.isDirectory; + } + } + return item.kind === 'file'; +}; + +const getFilesFromItem = item => + new Promise((resolve, reject) => { + if (isDirectoryEntry(item)) { + getFilesInDirectory(getAsEntry(item)).then(resolve); + return; + } + + resolve([item.getAsFile()]); + }); + +const getFilesInDirectory = entry => + new Promise((resolve, reject) => { + const files = []; + + // the total entries to read + let totalFilesFound = 0; + + // the recursive function + const readEntries = dirEntry => { + const directoryReader = dirEntry.createReader(); + directoryReader.readEntries(entries => { + entries.forEach(entry => { + + // recursively read more directories + if (entry.isDirectory) { + readEntries(entry); + } else { + // read as file + totalFilesFound++; + entry.file(file => { + + files.push(correctMissingFileType(file)); + + if (totalFilesFound === files.length) { + resolve(files); + } + }); + } + }); + }); + }; + + // go! + readEntries(entry); + }); + +const correctMissingFileType = (file) => { + if (file.type.length) return file; + const date = file.lastModifiedDate; + const name = file.name; + file = file.slice(0, file.size, guesstimateMimeType(getExtensionFromFilename(file.name))); + file.name = name; + file.lastModifiedDate = date; + return file; +} + +const isDirectoryEntry = item => + isEntry(item) && (getAsEntry(item) || {}).isDirectory; + +const isEntry = item => 'webkitGetAsEntry' in item; + +const getAsEntry = item => item.webkitGetAsEntry(); + +/** + * Extracts links from a DataTransfer object + */ +const getLinks = dataTransfer => { + let links = []; + try { + // look in meta data property + links = getLinksFromTransferMetaData(dataTransfer); + if (links.length) { + return links; + } + links = getLinksFromTransferURLData(dataTransfer); + } catch (e) { + // nope nope nope (probably IE trouble) + } + return links; +}; + +const getLinksFromTransferURLData = dataTransfer => { + let data = dataTransfer.getData('url'); + if (typeof data === 'string' && data.length) { + return [data]; + } + return []; +}; + +const getLinksFromTransferMetaData = dataTransfer => { + let data = dataTransfer.getData('text/html'); + if (typeof data === 'string' && data.length) { + const matches = data.match(/src\s*=\s*"(.+?)"/); + if (matches) { + return [matches[1]]; + } + } + return []; +}; diff --git a/src/js/app/utils/toServerAPI.js b/src/js/app/utils/toServerAPI.js new file mode 100644 index 00000000..38c8db36 --- /dev/null +++ b/src/js/app/utils/toServerAPI.js @@ -0,0 +1,2 @@ +import { createServerAPI } from './createServerAPI'; +export const toServerAPI = value => createServerAPI(value); diff --git a/src/js/app/view/assistant.js b/src/js/app/view/assistant.js new file mode 100644 index 00000000..b81c7d0e --- /dev/null +++ b/src/js/app/view/assistant.js @@ -0,0 +1,123 @@ +import { createView, createRoute } from '../frame/index'; +import { attr } from '../../utils/attr'; + +/** + * Creates the file view + */ +const create = ({ root, props }) => { + root.element.id = `filepond--assistant-${props.id}`; + attr(root.element, 'role', 'status'); + attr(root.element, 'aria-live', 'polite'); + attr(root.element, 'aria-relevant', 'additions'); +}; + +let addFilesNotificationTimeout = null; +let notificationClearTimeout = null; + +const filenames = []; + +const assist = (root, message) => { + root.element.textContent = message; +}; + +const clear = root => { + root.element.textContent = ''; +}; + +const listModified = (root, filename, label) => { + const total = root.query('GET_TOTAL_ITEMS'); + assist( + root, + `${label} ${filename}, ${total} ${ + total === 1 + ? root.query('GET_LABEL_FILE_COUNT_SINGULAR') + : root.query('GET_LABEL_FILE_COUNT_PLURAL') + }` + ); + + // clear group after set amount of time so the status is not read twice + clearTimeout(notificationClearTimeout); + notificationClearTimeout = setTimeout(() => { + clear(root); + }, 1500); +}; + +const isUsingFilePond = root => + root.element.parentNode.contains(document.activeElement); + +const itemAdded = ({ root, action }) => { + if (!isUsingFilePond(root)) { + return; + } + + root.element.textContent = ''; + const item = root.query('GET_ITEM', action.id); + filenames.push(item.filename); + + clearTimeout(addFilesNotificationTimeout); + addFilesNotificationTimeout = setTimeout(() => { + listModified( + root, + filenames.join(', '), + root.query('GET_LABEL_FILE_ADDED') + ); + filenames.length = 0; + }, 750); +}; + +const itemRemoved = ({ root, action }) => { + if (!isUsingFilePond(root)) { + return; + } + + const item = action.item; + listModified(root, item.filename, root.query('GET_LABEL_FILE_REMOVED')); +}; + +const itemProcessed = ({ root, action }) => { + // will also notify the user when FilePond is not being used, as the user might be occupied with other activities while uploading a file + + const item = root.query('GET_ITEM', action.id); + const filename = item.filename; + const label = root.query('GET_LABEL_FILE_PROCESSING_COMPLETE'); + + assist(root, `${filename} ${label}`); +}; + +const itemProcessedUndo = ({ root, action }) => { + const item = root.query('GET_ITEM', action.id); + const filename = item.filename; + const label = root.query('GET_LABEL_FILE_PROCESSING_ABORTED'); + + assist(root, `${filename} ${label}`); +}; + +const itemError = ({ root, action }) => { + const item = root.query('GET_ITEM', action.id); + const filename = item.filename; + + // will also notify the user when FilePond is not being used, as the user might be occupied with other activities while uploading a file + + assist(root, `${action.status.main} ${filename} ${action.status.sub}`); +}; + +export const assistant = createView({ + create, + ignoreRect: true, + ignoreRectUpdate: true, + write: createRoute({ + DID_LOAD_ITEM: itemAdded, + DID_REMOVE_ITEM: itemRemoved, + DID_COMPLETE_ITEM_PROCESSING: itemProcessed, + + DID_ABORT_ITEM_PROCESSING: itemProcessedUndo, + DID_REVERT_ITEM_PROCESSING: itemProcessedUndo, + + DID_THROW_ITEM_REMOVE_ERROR: itemError, + DID_THROW_ITEM_LOAD_ERROR: itemError, + DID_THROW_ITEM_INVALID: itemError, + DID_THROW_ITEM_PROCESSING_ERROR: itemError + }), + tag: 'span', + name: 'assistant' +}); diff --git a/src/js/app/view/blob.js b/src/js/app/view/blob.js new file mode 100644 index 00000000..64ee71a4 --- /dev/null +++ b/src/js/app/view/blob.js @@ -0,0 +1,16 @@ +import { createView } from '../frame/index'; + +export const blob = createView({ + name: 'drip-blob', + ignoreRect: true, + mixins: { + styles: ['translateX', 'translateY', 'scaleX', 'scaleY', 'opacity'], + animations: { + scaleX: 'spring', + scaleY: 'spring', + translateX: 'spring', + translateY: 'spring', + opacity: { type: 'tween', duration: 250 } + } + } +}); diff --git a/src/js/app/view/browser.js b/src/js/app/view/browser.js new file mode 100644 index 00000000..b1478497 --- /dev/null +++ b/src/js/app/view/browser.js @@ -0,0 +1,136 @@ +import { createView, createRoute } from '../frame/index'; +import { attrToggle } from '../../utils/attrToggle'; +import { resetFileInput } from '../../utils/resetFileInput'; +import { attr } from '../../utils/attr'; + +const create = ({ root, props }) => { + + // set id so can be referenced from outside labels + root.element.id = `filepond--browser-${props.id}`; + + // set name of element (is removed when a value is set) + attr(root.element, 'name', root.query('GET_NAME')); + + // we have to link this element to the status element + attr(root.element, 'aria-controls', `filepond--assistant-${props.id}`); + + // set label, we use labelled by as otherwise the screenreader does not read the "browse" text in the label (as it has tabindex: 0) + attr(root.element, 'aria-labelledby', `filepond--drop-label-${props.id}`); + + // handle changes to the input field + root.ref.handleChange = e => { + if (!root.element.value) { + return; + } + + // extract files + const files = Array.from(root.element.files); + + // we add a little delay so the OS file select window can move out of the way before we add our file + setTimeout(() => { + // load files + props.onload(files); + + // reset input, it's just for exposing a method to drop files, should not retain any state + resetFileInput(root.element); + }, 250); + } + root.element.addEventListener('change', root.ref.handleChange); +}; + +const setAcceptedFileTypes = ({ root, action }) => { + attrToggle( + root.element, + 'accept', + !!action.value, + action.value ? action.value.join(',') : '' + ); +}; + +const toggleAllowMultiple = ({ root, action }) => { + attrToggle(root.element, 'multiple', action.value); +}; + +const toggleDisabled = ({ root, action }) => { + const isDisabled = root.query('GET_DISABLED'); + const doesAllowBrowse = root.query('GET_ALLOW_BROWSE'); + const disableField = isDisabled || !doesAllowBrowse; + attrToggle(root.element, 'disabled', disableField); +} + +const toggleRequired = ({ root, action }) => { + // want to remove required, always possible + if (!action.value) { + attrToggle(root.element, 'required', false); + } + // if want to make required, only possible when zero items + else if (root.query('GET_TOTAL_ITEMS') === 0) { + attrToggle(root.element, 'required', true); + } +}; + +const setCaptureMethod = ({ root, action }) => { + attrToggle( + root.element, + 'capture', + !!action.value, + action.value === true ? '' : action.value + ); +}; + +const updateRequiredStatus = ({ root }) => { + const { element } = root; + // always remove the required attribute when more than zero items + if (root.query('GET_TOTAL_ITEMS') > 0) { + attrToggle(element, 'required', false); + attrToggle(element, 'name', false); + } + else { + + // add name attribute + attrToggle(element, 'name', true, root.query('GET_NAME')); + + // remove any validation messages + const shouldCheckValidity = root.query('GET_CHECK_VALIDITY'); + if (shouldCheckValidity) { + element.setCustomValidity(''); + } + + // we only add required if the field has been deemed required + if (root.query('GET_REQUIRED')) { + attrToggle(element, 'required', true); + } + } +}; + +const updateFieldValidityStatus = ({ root }) => { + const shouldCheckValidity = root.query('GET_CHECK_VALIDITY'); + if (!shouldCheckValidity) return; + root.element.setCustomValidity(root.query('GET_LABEL_INVALID_FIELD')); +} + +export const browser = createView({ + tag: 'input', + name: 'browser', + ignoreRect: true, + ignoreRectUpdate: true, + attributes: { + type: 'file' + }, + create, + destroy: ({ root }) => { + root.element.removeEventListener('change', root.ref.handleChange); + }, + write: createRoute({ + DID_LOAD_ITEM: updateRequiredStatus, + DID_REMOVE_ITEM: updateRequiredStatus, + DID_THROW_ITEM_INVALID: updateFieldValidityStatus, + + DID_SET_DISABLED: toggleDisabled, + DID_SET_ALLOW_BROWSE: toggleDisabled, + DID_SET_ALLOW_MULTIPLE: toggleAllowMultiple, + DID_SET_ACCEPTED_FILE_TYPES: setAcceptedFileTypes, + DID_SET_CAPTURE_METHOD: setCaptureMethod, + DID_SET_REQUIRED: toggleRequired + }) +}); diff --git a/src/js/app/view/drip.js b/src/js/app/view/drip.js new file mode 100644 index 00000000..1468c419 --- /dev/null +++ b/src/js/app/view/drip.js @@ -0,0 +1,70 @@ +import { createView, createRoute } from '../frame/index'; +import { blob } from './blob'; + +const addBlob = ({ root }) => { + const centerX = root.rect.element.width * 0.5; + const centerY = root.rect.element.height * 0.5; + + root.ref.blob = root.appendChildView( + root.createChildView(blob, { + opacity: 0, + scaleX: 2.5, + scaleY: 2.5, + translateX: centerX, + translateY: centerY + }) + ); +}; + +const moveBlob = ({ root, action }) => { + if (!root.ref.blob) { + addBlob({ root }); + return; + } + + root.ref.blob.translateX = action.position.scopeLeft; + root.ref.blob.translateY = action.position.scopeTop; + root.ref.blob.scaleX = 1; + root.ref.blob.scaleY = 1; + root.ref.blob.opacity = 1; +}; + +const hideBlob = ({ root }) => { + if (!root.ref.blob) { + return; + } + root.ref.blob.opacity = 0; +}; + +const explodeBlob = ({ root }) => { + if (!root.ref.blob) { + return; + } + root.ref.blob.scaleX = 2.5; + root.ref.blob.scaleY = 2.5; + root.ref.blob.opacity = 0; +}; + +const write = ({ root, props, actions }) => { + route({ root, props, actions }); + + const { blob } = root.ref; + + if (actions.length === 0 && blob && blob.opacity === 0) { + root.removeChildView(blob); + root.ref.blob = null; + } +}; + +const route = createRoute({ + DID_DRAG: moveBlob, + DID_DROP: explodeBlob, + DID_END_DRAG: hideBlob +}); + +export const drip = createView({ + ignoreRect: true, + ignoreRectUpdate: true, + name: 'drip', + write +}); diff --git a/src/js/app/view/dropLabel.js b/src/js/app/view/dropLabel.js new file mode 100644 index 00000000..6a1d4124 --- /dev/null +++ b/src/js/app/view/dropLabel.js @@ -0,0 +1,74 @@ +import { createView, createRoute } from '../frame/index'; +import { attr } from '../../utils/attr'; +import { createElement } from '../../utils/createElement'; +import { Key } from '../enum/Key'; + +const create = ({ root, props }) => { + + // create the label and link it to the file browser + const label = createElement('label'); + attr(label, 'for', `filepond--browser-${props.id}`); + + // use for labeling file input (aria-labelledby on file input) + attr(label, 'id', `filepond--drop-label-${props.id}`); + + // hide the label from screenreaders, the input element has an aria-label + attr(label, 'aria-hidden', 'true'); + + // handle keys + label.addEventListener('keydown', e => { + const isActivationKey = e.keyCode === Key.ENTER || e.keyCode === Key.SPACE; + if (!isActivationKey) return; + // stops from triggering the element a second time + e.preventDefault(); + + // click link (will then in turn activate file input) + root.ref.label.click(); + }); + + root.element.addEventListener('click', e => { + + const isLabelClick = e.target === label || label.contains(e.target); + + // don't want to click twice + if (isLabelClick) return; + + // click link (will then in turn activate file input) + root.ref.label.click(); + }); + + // update + updateLabelValue(label, props.caption); + + // add! + root.appendChild(label); + root.ref.label = label; +}; + +const updateLabelValue = (label, value) => { + label.innerHTML = value; + const clickable = label.querySelector('.filepond--label-action'); + if (clickable) { + attr(clickable, 'tabindex', '0'); + } + return value; +}; + +export const dropLabel = createView({ + name: 'drop-label', + ignoreRect: true, + create, + write: createRoute({ + DID_SET_LABEL_IDLE: ({ root, action }) => { + updateLabelValue(root.ref.label, action.value); + } + }), + mixins: { + styles: ['opacity', 'translateX', 'translateY'], + animations: { + opacity: { type: 'tween', duration: 150 }, + translateX: 'spring', + translateY: 'spring' + } + } +}); diff --git a/src/js/app/view/file.js b/src/js/app/view/file.js new file mode 100644 index 00000000..d71dd4ed --- /dev/null +++ b/src/js/app/view/file.js @@ -0,0 +1,366 @@ +import { createView, createRoute } from '../frame/index'; +import { progressIndicator } from './progressIndicator'; +import { fileActionButton } from './fileActionButton'; +import { fileInfo } from './fileInfo'; +import { fileStatus } from './fileStatus'; +import { forin } from '../../utils/forin'; +import { applyFilters } from '../../filter'; + +/** + * Button definitions for the file view + */ + +const Buttons = { + AbortItemLoad: { + label: 'GET_LABEL_BUTTON_ABORT_ITEM_LOAD', + action: 'ABORT_ITEM_LOAD', + className: 'filepond--action-abort-item-load', + align: 'LOAD_INDICATOR_POSITION' // right + }, + RetryItemLoad: { + label: 'GET_LABEL_BUTTON_RETRY_ITEM_LOAD', + action: 'RETRY_ITEM_LOAD', + icon: 'GET_ICON_RETRY', + className: 'filepond--action-retry-item-load', + align: 'BUTTON_PROCESS_ITEM_POSITION' // right + }, + RemoveItem: { + label: 'GET_LABEL_BUTTON_REMOVE_ITEM', + action: 'REQUEST_REMOVE_ITEM', + icon: 'GET_ICON_REMOVE', + className: 'filepond--action-remove-item', + align: 'BUTTON_REMOVE_ITEM_POSITION' // left + }, + ProcessItem: { + label: 'GET_LABEL_BUTTON_PROCESS_ITEM', + action: 'REQUEST_ITEM_PROCESSING', + icon: 'GET_ICON_PROCESS', + className: 'filepond--action-process-item', + align: 'BUTTON_PROCESS_ITEM_POSITION' // right + }, + AbortItemProcessing: { + label: 'GET_LABEL_BUTTON_ABORT_ITEM_PROCESSING', + action: 'ABORT_ITEM_PROCESSING', + className: 'filepond--action-abort-item-processing', + align: 'BUTTON_PROCESS_ITEM_POSITION' // right + }, + RetryItemProcessing: { + label: 'GET_LABEL_BUTTON_RETRY_ITEM_PROCESSING', + action: 'RETRY_ITEM_PROCESSING', + icon: 'GET_ICON_RETRY', + className: 'filepond--action-retry-item-processing', + align: 'BUTTON_PROCESS_ITEM_POSITION' // right + }, + RevertItemProcessing: { + label: 'GET_LABEL_BUTTON_UNDO_ITEM_PROCESSING', + action: 'REQUEST_REVERT_ITEM_PROCESSING', + icon: 'GET_ICON_UNDO', + className: 'filepond--action-revert-item-processing', + align: 'BUTTON_PROCESS_ITEM_POSITION' // right + } +}; + +// make a list of buttons, we can then remove buttons from this list if they're disabled +const ButtonKeys = []; +forin(Buttons, key => { + ButtonKeys.push(key); +}); + +const calculateFileInfoOffset = root => + root.ref.buttonRemoveItem.rect.element.width + + root.ref.buttonRemoveItem.rect.element.left; + +// Force on full pixels so text stays crips +const calculateFileVerticalCenterOffset = root => Math.floor(root.ref.buttonRemoveItem.rect.element.height / 4) +const calculateFileHorizontalCenterOffset = root => Math.floor(root.ref.buttonRemoveItem.rect.element.left / 2); + +const getLoadIndicatorAlignment = root => root.query('GET_STYLE_LOAD_INDICATOR_POSITION'); +const getProcessIndicatorAlignment = root => root.query('GET_STYLE_PROGRESS_INDICATOR_POSITION'); +const getRemoveIndicatorAligment = root => root.query('GET_STYLE_BUTTON_REMOVE_ITEM_POSITION'); + +const DefaultStyle = { + buttonAbortItemLoad: { opacity: 0 }, + buttonRetryItemLoad: { opacity: 0 }, + buttonRemoveItem: { opacity: 0 }, + buttonProcessItem: { opacity: 0 }, + buttonAbortItemProcessing: { opacity: 0 }, + buttonRetryItemProcessing: { opacity: 0 }, + buttonRevertItemProcessing: { opacity: 0 }, + loadProgressIndicator: { opacity: 0, align: getLoadIndicatorAlignment }, + processProgressIndicator: { opacity: 0, align: getProcessIndicatorAlignment }, + processingCompleteIndicator: { opacity: 0, scaleX:.75, scaleY:.75 }, + info: { translateX: 0, translateY: 0, opacity: 0 }, + status: { translateX: 0, translateY: 0, opacity: 0 } +}; + +const IdleStyle = { + buttonRemoveItem: { opacity: 1 }, + buttonProcessItem: { opacity: 1 }, + info: { translateX: calculateFileInfoOffset }, + status: { translateX: calculateFileInfoOffset } +}; + +const ProcessingStyle = { + buttonAbortItemProcessing: { opacity: 1 }, + processProgressIndicator: { opacity: 1 }, + status: { opacity: 1 } +}; + +const StyleMap = { + DID_THROW_ITEM_INVALID: { + buttonRemoveItem: { opacity: 1 }, + info: { translateX: calculateFileInfoOffset }, + status: { translateX: calculateFileInfoOffset, opacity: 1 } + }, + + DID_START_ITEM_LOAD: { + buttonAbortItemLoad: { opacity: 1 }, + loadProgressIndicator: { opacity: 1 }, + status: { opacity: 1 } + }, + DID_THROW_ITEM_LOAD_ERROR: { + buttonRetryItemLoad: { opacity: 1 }, + buttonRemoveItem: { opacity: 1 }, + info: { translateX: calculateFileInfoOffset }, + status: { opacity: 1 } + }, + + DID_START_ITEM_REMOVE: { + processProgressIndicator: { opacity: 1, align: getRemoveIndicatorAligment }, + info: { translateX: calculateFileInfoOffset }, + status: { opacity: 0 } + }, + + DID_THROW_ITEM_REMOVE_ERROR: { + processProgressIndicator: { opacity: 0, align: getRemoveIndicatorAligment }, + buttonRemoveItem: { opacity: 1 }, + info: { translateX: calculateFileInfoOffset }, + status: { opacity: 1, translateX: calculateFileInfoOffset } + }, + + DID_LOAD_ITEM: IdleStyle, + DID_LOAD_LOCAL_ITEM: { + buttonRemoveItem: { opacity: 1 }, + info: { translateX: calculateFileInfoOffset }, + status: { translateX: calculateFileInfoOffset } + }, + DID_START_ITEM_PROCESSING: ProcessingStyle, + DID_REQUEST_ITEM_PROCESSING: ProcessingStyle, + DID_UPDATE_ITEM_PROCESS_PROGRESS: ProcessingStyle, + DID_COMPLETE_ITEM_PROCESSING: { + buttonRevertItemProcessing: { opacity: 1 }, + info: { opacity: 1 }, + status: { opacity: 1 } + }, + DID_THROW_ITEM_PROCESSING_ERROR: { + buttonRemoveItem: { opacity: 1 }, + buttonRetryItemProcessing: { opacity: 1 }, + status: { opacity: 1 }, + info: { translateX: calculateFileInfoOffset } + }, + DID_THROW_ITEM_PROCESSING_REVERT_ERROR: { + buttonRevertItemProcessing: { opacity: 1 }, + status: { opacity: 1 }, + info: { opacity: 1 } + }, + DID_ABORT_ITEM_PROCESSING: { + buttonRemoveItem: { opacity: 1 }, + buttonProcessItem: { opacity: 1 }, + info: { translateX: calculateFileInfoOffset }, + status: { opacity: 1 } + }, + DID_REVERT_ITEM_PROCESSING: IdleStyle +}; + + +// complete indicator view +const processingCompleteIndicatorView = createView({ + create: ({ root }) => { + root.element.innerHTML = root.query('GET_ICON_DONE'); + }, + name: 'processing-complete-indicator', + ignoreRect: true, + mixins: { + styles: ['scaleX', 'scaleY', 'opacity'], + animations: { + scaleX: 'spring', + scaleY: 'spring', + opacity: { type: 'tween', duration: 250 } + } + } +}) + + +/** + * Creates the file view + */ +const create = ({ root, props }) => { + + const { id } = props; + + // allow reverting upload + const allowRevert = root.query('GET_ALLOW_REVERT'); + + // is instant uploading, need this to determine the icon of the undo button + const instantUpload = root.query('GET_INSTANT_UPLOAD'); + + // is async set up + const isAsync = root.query('IS_ASYNC'); + + // enabled buttons array + const enabledButtons = isAsync + ? ButtonKeys.concat() + : ButtonKeys.filter(key => !/Process/.test(key)); + + // remove last button (revert) if not allowed + if (isAsync && !allowRevert) { + enabledButtons.splice(-1, 1); + const map = StyleMap['DID_COMPLETE_ITEM_PROCESSING']; + map.info.translateX = calculateFileHorizontalCenterOffset; + map.info.translateY = calculateFileVerticalCenterOffset; + map.status.translateY = calculateFileVerticalCenterOffset; + map.processingCompleteIndicator = { opacity: 1, scaleX: 1, scaleY: 1 } + } + + // update icon and label for revert button when instant uploading + if (instantUpload && allowRevert) { + Buttons['RevertItemProcessing'].label = 'GET_LABEL_BUTTON_REMOVE_ITEM'; + Buttons['RevertItemProcessing'].icon = 'GET_ICON_REMOVE'; + } + + // create the button views + forin(Buttons, (key, definition) => { + + // create button + const buttonView = root.createChildView(fileActionButton, { + label: root.query(definition.label), + icon: root.query(definition.icon), + opacity: 0 + }); + + // should be appended? + if (enabledButtons.includes(key)) { + root.appendChildView(buttonView); + } + + // add position attribute + buttonView.element.dataset.align = root.query(`GET_STYLE_${definition.align}`); + + // add class + buttonView.element.classList.add(definition.className); + + // handle interactions + buttonView.on('click', e => { + e.stopPropagation(); + root.dispatch(definition.action, { query: id }); + }); + + // set reference + root.ref[`button${key}`] = buttonView; + }); + + // create file info view + root.ref.info = root.appendChildView( + root.createChildView(fileInfo, { id }) + ); + + // create file status view + root.ref.status = root.appendChildView( + root.createChildView(fileStatus, { id }) + ); + + // checkmark + root.ref.processingCompleteIndicator = root.appendChildView(root.createChildView(processingCompleteIndicatorView)); + root.ref.processingCompleteIndicator.element.dataset.align = root.query(`GET_STYLE_BUTTON_PROCESS_ITEM_POSITION`); + + // add progress indicators + const loadIndicatorView = root.appendChildView(root.createChildView(progressIndicator, { + opacity: 0, + align: root.query(`GET_STYLE_LOAD_INDICATOR_POSITION`) + })); + loadIndicatorView.element.classList.add('filepond--load-indicator'); + root.ref.loadProgressIndicator = loadIndicatorView; + + const progressIndicatorView = root.appendChildView(root.createChildView(progressIndicator, { + opacity: 0, + align: root.query(`GET_STYLE_PROGRESS_INDICATOR_POSITION`) + })); + progressIndicatorView.element.classList.add('filepond--process-indicator'); + root.ref.processProgressIndicator = progressIndicatorView; +}; + +const write = ({ root, actions, props }) => { + + // route actions + route({ root, actions, props }); + + // select last state change action + let action = actions.concat() + .filter(action => /^DID_/.test(action.type)) + .reverse() + .find(action => StyleMap[action.type]); + + // no need to set same state twice + if (!action || (action && action.type === root.ref.currentAction)) { + return; + } + + // set current state + root.ref.currentAction = action.type; + const newStyles = StyleMap[root.ref.currentAction]; + + forin(DefaultStyle, (name, defaultStyles) => { + // get reference to control + const control = root.ref[name]; + + // loop over all styles for this control + forin(defaultStyles, (key, defaultValue) => { + const value = + newStyles[name] && typeof newStyles[name][key] !== 'undefined' + ? newStyles[name][key] + : defaultValue; + control[key] = typeof value === 'function' ? value(root) : value; + }); + }); +}; + +const route = createRoute({ + DID_SET_LABEL_BUTTON_ABORT_ITEM_PROCESSING: ({ root, action }) => { + root.ref.buttonAbortItemProcessing.label = action.value; + }, + DID_SET_LABEL_BUTTON_ABORT_ITEM_LOAD: ({ root, action }) => { + root.ref.buttonAbortItemLoad.label = action.value; + }, + DID_SET_LABEL_BUTTON_ABORT_ITEM_REMOVAL: ({ root, action }) => { + root.ref.buttonAbortItemRemoval.label = action.value; + }, + DID_REQUEST_ITEM_PROCESSING: ({ root }) => { + root.ref.processProgressIndicator.spin = true; + root.ref.processProgressIndicator.progress = 0; + }, + DID_START_ITEM_LOAD: ({ root }) => { + root.ref.loadProgressIndicator.spin = true; + root.ref.loadProgressIndicator.progress = 0; + }, + DID_START_ITEM_REMOVE: ({ root }) => { + root.ref.processProgressIndicator.spin = true; + root.ref.processProgressIndicator.progress = 0; + }, + DID_UPDATE_ITEM_LOAD_PROGRESS: ({ root, action }) => { + root.ref.loadProgressIndicator.spin = false; + root.ref.loadProgressIndicator.progress = action.progress; + }, + DID_UPDATE_ITEM_PROCESS_PROGRESS: ({ root, action }) => { + root.ref.processProgressIndicator.spin = false; + root.ref.processProgressIndicator.progress = action.progress; + } +}); + +export const file = createView({ + create, + write, + didCreateView: root => { + applyFilters('CREATE_VIEW', { ...root, view: root }); + }, + name: 'file' +}); diff --git a/src/js/app/view/fileActionButton.js b/src/js/app/view/fileActionButton.js new file mode 100644 index 00000000..01d990f6 --- /dev/null +++ b/src/js/app/view/fileActionButton.js @@ -0,0 +1,48 @@ +import { createView } from '../frame/index'; +import { attr } from '../../utils/attr'; + +const create = ({ root, props }) => { + root.element.title = props.label; + root.element.innerHTML = props.icon || ''; + + props.isDisabled = false; +}; + +const write = ({ root, props }) => { + + const { isDisabled } = props; + const shouldDisable = root.query('GET_DISABLED') || props.opacity === 0; + + if (shouldDisable && !isDisabled) { + props.isDisabled = true; + attr(root.element, 'disabled', 'disabled'); + } + else if (!shouldDisable && isDisabled) { + props.isDisabled = false; + root.element.removeAttribute('disabled'); + } +}; + +export const fileActionButton = createView({ + tag: 'button', + attributes: { + type: 'button' + }, + ignoreRect: true, + ignoreRectUpdate: true, + name: 'file-action-button', + mixins: { + apis: ['label'], + styles: ['translateX', 'translateY', 'scaleX', 'scaleY', 'opacity'], + animations: { + scaleX: 'spring', + scaleY: 'spring', + translateX: 'spring', + translateY: 'spring', + opacity: { type: 'tween', duration: 250 } + }, + listeners: true + }, + create, + write +}); diff --git a/src/js/app/view/fileInfo.js b/src/js/app/view/fileInfo.js new file mode 100644 index 00000000..79551de8 --- /dev/null +++ b/src/js/app/view/fileInfo.js @@ -0,0 +1,73 @@ +import { createView, createRoute } from '../frame/index'; +import { toNaturalFileSize } from '../../utils/toNaturalFileSize'; +import { text } from '../../utils/text'; +import { formatFilename } from '../../utils/formatFilename'; +import { isInt } from '../../utils/isInt'; +import { createElement } from '../../utils/createElement'; +import { attr } from '../../utils/attr'; +import { applyFilters } from '../../filter'; + +const create = ({ root, props }) => { + // filename + const fileName = createElement('span'); + fileName.className = 'filepond--file-info-main'; + // hide for screenreaders + // the file is contained in a fieldset with legend that contains the filename + // no need to read it twice + attr(fileName, 'aria-hidden', 'true'); + root.appendChild(fileName); + root.ref.fileName = fileName; + + // filesize + const fileSize = createElement('span'); + fileSize.className = 'filepond--file-info-sub'; + root.appendChild(fileSize); + root.ref.fileSize = fileSize; + + // set initial values + text(fileSize, root.query('GET_LABEL_FILE_WAITING_FOR_SIZE')); + text(fileName, formatFilename(root.query('GET_ITEM_NAME', props.id))); +}; + +const updateFile = ({ root, props }) => { + text( + root.ref.fileSize, + toNaturalFileSize(root.query('GET_ITEM_SIZE', props.id)) + ); + text( + root.ref.fileName, + formatFilename(root.query('GET_ITEM_NAME', props.id)) + ); +}; + +const updateFileSizeOnError = ({ root, props }) => { + // if size is available don't fallback to unknown size message + if (isInt(root.query('GET_ITEM_SIZE', props.id))) { + return; + } + + text(root.ref.fileSize, root.query('GET_LABEL_FILE_SIZE_NOT_AVAILABLE')); +}; + +export const fileInfo = createView({ + name: 'file-info', + ignoreRect: true, + ignoreRectUpdate: true, + write: createRoute({ + DID_LOAD_ITEM: updateFile, + DID_UPDATE_ITEM_META: updateFile, + DID_THROW_ITEM_LOAD_ERROR: updateFileSizeOnError, + DID_THROW_ITEM_INVALID: updateFileSizeOnError + }), + didCreateView: root => { + applyFilters('CREATE_VIEW', { ...root, view: root }); + }, + create, + mixins: { + styles: ['translateX', 'translateY'], + animations: { + translateX: 'spring', + translateY: 'spring' + } + } +}); diff --git a/src/js/app/view/fileStatus.js b/src/js/app/view/fileStatus.js new file mode 100644 index 00000000..45e62380 --- /dev/null +++ b/src/js/app/view/fileStatus.js @@ -0,0 +1,100 @@ +import { createView, createRoute } from '../frame/index'; +import { toPercentage } from '../../utils/toPercentage'; +import { text } from '../../utils/text'; +import { createElement } from '../../utils/createElement'; +import { applyFilters } from '../../filter'; + +const create = ({ root, props }) => { + // main status + const main = createElement('span'); + main.className = 'filepond--file-status-main'; + root.appendChild(main); + root.ref.main = main; + + // sub status + const sub = createElement('span'); + sub.className = 'filepond--file-status-sub'; + root.appendChild(sub); + root.ref.sub = sub; + + didSetItemLoadProgress({ root, action: { progress: null } }); +}; + +const didSetItemLoadProgress = ({ root, action }) => { + const title = + action.progress === null + ? root.query('GET_LABEL_FILE_LOADING') + : `${root.query('GET_LABEL_FILE_LOADING')} ${toPercentage( + action.progress + )}%`; + text(root.ref.main, title); + text(root.ref.sub, root.query('GET_LABEL_TAP_TO_CANCEL')); +}; + +const didSetItemProcessProgress = ({ root, action }) => { + const title = + action.progress === null + ? root.query('GET_LABEL_FILE_PROCESSING') + : `${root.query('GET_LABEL_FILE_PROCESSING')} ${toPercentage( + action.progress + )}%`; + text(root.ref.main, title); + text(root.ref.sub, root.query('GET_LABEL_TAP_TO_CANCEL')); +}; + +const didRequestItemProcessing = ({ root }) => { + text(root.ref.main, root.query('GET_LABEL_FILE_PROCESSING')); + text(root.ref.sub, root.query('GET_LABEL_TAP_TO_CANCEL')); +}; + +const didAbortItemProcessing = ({ root }) => { + text(root.ref.main, root.query('GET_LABEL_FILE_PROCESSING_ABORTED')); + text(root.ref.sub, root.query('GET_LABEL_TAP_TO_RETRY')); +}; + +const didCompleteItemProcessing = ({ root }) => { + text(root.ref.main, root.query('GET_LABEL_FILE_PROCESSING_COMPLETE')); + text(root.ref.sub, root.query('GET_LABEL_TAP_TO_UNDO')); +}; + +const clear = ({ root }) => { + text(root.ref.main, ''); + text(root.ref.sub, ''); +}; + +const error = ({ root, action }) => { + text(root.ref.main, action.status.main); + text(root.ref.sub, action.status.sub); +}; + +export const fileStatus = createView({ + name: 'file-status', + ignoreRect: true, + ignoreRectUpdate: true, + write: createRoute({ + DID_LOAD_ITEM: clear, + DID_REVERT_ITEM_PROCESSING: clear, + DID_REQUEST_ITEM_PROCESSING: didRequestItemProcessing, + DID_ABORT_ITEM_PROCESSING: didAbortItemProcessing, + DID_COMPLETE_ITEM_PROCESSING: didCompleteItemProcessing, + DID_UPDATE_ITEM_PROCESS_PROGRESS: didSetItemProcessProgress, + DID_UPDATE_ITEM_LOAD_PROGRESS: didSetItemLoadProgress, + DID_THROW_ITEM_LOAD_ERROR: error, + DID_THROW_ITEM_INVALID: error, + DID_THROW_ITEM_PROCESSING_ERROR: error, + DID_THROW_ITEM_PROCESSING_REVERT_ERROR: error, + DID_THROW_ITEM_REMOVE_ERROR: error + }), + didCreateView: root => { + applyFilters('CREATE_VIEW', { ...root, view: root }); + }, + create, + mixins: { + styles: ['translateX', 'translateY', 'opacity'], + animations: { + opacity: { type: 'tween', duration: 250 }, + translateX: 'spring', + translateY: 'spring' + } + } +}); diff --git a/src/js/app/view/fileWrapper.js b/src/js/app/view/fileWrapper.js new file mode 100644 index 00000000..9394966e --- /dev/null +++ b/src/js/app/view/fileWrapper.js @@ -0,0 +1,75 @@ +import { createView, createRoute } from '../frame/index'; +import { formatFilename } from '../../utils/formatFilename'; +import { createElement } from '../../utils/createElement'; +import { text } from '../../utils/text'; +import { file } from './file'; +import { applyFilters } from '../../filter'; + +/** + * Creates the file view + */ +const create = ({ root, props }) => { + + // filename + root.ref.fileName = createElement('legend'); + root.appendChild(root.ref.fileName); + + // file view + root.ref.file = root.appendChildView( + root.createChildView(file, { id: props.id }) + ); + + // create data container + const dataContainer = createElement('input'); + dataContainer.type = 'hidden'; + dataContainer.name = root.query('GET_NAME'); + dataContainer.disabled = root.query('GET_DISABLED'); + root.ref.data = dataContainer; + root.appendChild(dataContainer); +}; + +const didSetDisabled = ({ root }) => { + root.ref.data.disabled = root.query('GET_DISABLED'); +} + +/** + * Data storage + */ +const didLoadItem = ({ root, action, props }) => { + root.ref.data.value = action.serverFileReference; + + // updates the legend of the fieldset so screenreaders can better group buttons + text( + root.ref.fileName, + formatFilename(root.query('GET_ITEM_NAME', props.id)) + ); +}; + +const didRemoveItem = ({ root }) => { + root.ref.data.removeAttribute('value'); +}; + +const didCompleteItemProcessing = ({ root, action }) => { + root.ref.data.value = action.serverFileReference; +}; + +const didRevertItemProcessing = ({ root, action }) => { + root.ref.data.removeAttribute('value'); +}; + +export const fileWrapper = createView({ + create, + ignoreRect: true, + write: createRoute({ + DID_SET_DISABLED: didSetDisabled, + DID_LOAD_ITEM: didLoadItem, + DID_REMOVE_ITEM: didRemoveItem, + DID_COMPLETE_ITEM_PROCESSING: didCompleteItemProcessing, + DID_REVERT_ITEM_PROCESSING: didRevertItemProcessing + }), + didCreateView: root => { + applyFilters('CREATE_VIEW', { ...root, view: root }); + }, + tag: 'fieldset', + name: 'file-wrapper' +}); diff --git a/src/js/app/view/item.js b/src/js/app/view/item.js new file mode 100644 index 00000000..13387720 --- /dev/null +++ b/src/js/app/view/item.js @@ -0,0 +1,133 @@ +import { createView, createRoute } from '../frame/index'; +import { fileWrapper } from './fileWrapper'; +import { panel } from './panel'; + +const ITEM_TRANSLATE_SPRING = { + type: 'spring', + stiffness: 0.75, + damping: 0.45, + mass: 10 +} + +const ITEM_SCALE_SPRING = 'spring'; + +/** + * Creates the file view + */ +const create = ({ root, props }) => { + + // select + root.ref.handleClick = () => root.dispatch('DID_ACTIVATE_ITEM', { id: props.id }); + + // set id + root.element.id = `filepond--item-${props.id}`; + root.element.addEventListener('click', root.ref.handleClick); + + // file view + root.ref.container = root.appendChildView( + root.createChildView(fileWrapper, { id: props.id }) + ); + + // file panel + root.ref.panel = root.appendChildView( + root.createChildView(panel, { name: 'item-panel' }) + ); + + // default start height + root.ref.panel.height = 0; + + // by default not marked for removal + props.markedForRemoval = false; + +}; + +const StateMap = { + DID_START_ITEM_LOAD: 'busy', + DID_UPDATE_ITEM_LOAD_PROGRESS: 'loading', + DID_THROW_ITEM_INVALID: 'load-invalid', + DID_THROW_ITEM_LOAD_ERROR: 'load-error', + DID_LOAD_ITEM: 'idle', + DID_THROW_ITEM_REMOVE_ERROR: 'remove-error', + DID_START_ITEM_REMOVE: 'busy', + DID_START_ITEM_PROCESSING: 'busy', + DID_REQUEST_ITEM_PROCESSING: 'busy', + DID_UPDATE_ITEM_PROCESS_PROGRESS: 'processing', + DID_COMPLETE_ITEM_PROCESSING: 'processing-complete', + DID_THROW_ITEM_PROCESSING_ERROR: 'processing-error', + DID_THROW_ITEM_PROCESSING_REVERT_ERROR: 'processing-revert-error', + DID_ABORT_ITEM_PROCESSING: 'cancelled', + DID_REVERT_ITEM_PROCESSING: 'idle' +}; + +const route = createRoute({ + DID_UPDATE_PANEL_HEIGHT: ({ root, action }) => { + const { height } = action; + root.height = height; + } +}); + +const write = ({ root, actions, props, shouldOptimize }) => { + + // route actions + const aspectRatio = root.query('GET_ITEM_PANEL_ASPECT_RATIO') || root.query('GET_PANEL_ASPECT_RATIO'); + if (!aspectRatio) { + route({ root, actions, props }); + if (!root.height) { + root.height = root.ref.container.rect.element.height; + } + } + else if (!shouldOptimize) { + root.height = root.rect.element.width * aspectRatio; + } + + // sync panel height with item height + if (shouldOptimize) { + root.ref.panel.height = null; + } + + root.ref.panel.height = root.height; + + // select last state change action + let action = actions.concat() + .filter(action => /^DID_/.test(action.type)) + .reverse() + .find(action => StateMap[action.type]); + + // no need to set same state twice + if (!action || (action && action.type === props.currentState)) return; + + // set current state + props.currentState = action.type; + + // set state + root.element.dataset.filepondItemState = StateMap[props.currentState] || ''; +}; + +export const item = createView({ + create, + write, + destroy: ({ root, props }) => { + root.element.removeEventListener('click', root.ref.handleClick); + root.dispatch('RELEASE_ITEM', { query: props.id }); + }, + tag: 'li', + name: 'item', + mixins: { + apis: ['id', 'interactionMethod', 'markedForRemoval', 'spawnDate'], + styles: [ + 'translateX', + 'translateY', + 'scaleX', + 'scaleY', + 'opacity', + 'height' + ], + animations: { + scaleX: ITEM_SCALE_SPRING, + scaleY: ITEM_SCALE_SPRING, + translateX: ITEM_TRANSLATE_SPRING, + translateY: ITEM_TRANSLATE_SPRING, + opacity: { type: 'tween', duration: 150 } + } + } +}); diff --git a/src/js/app/view/list.js b/src/js/app/view/list.js new file mode 100644 index 00000000..97b36563 --- /dev/null +++ b/src/js/app/view/list.js @@ -0,0 +1,312 @@ +import { createView, createRoute } from '../frame/index'; +import { InteractionMethod } from '../enum/InteractionMethod'; +import { item } from './item'; +import { attr } from '../../utils/attr'; +import { getItemIndexByPosition } from '../utils/getItemIndexByPosition'; + +const create = ({ root, props }) => { + // need to set role to list as otherwise it won't be read as a list by VoiceOver + attr(root.element, 'role', 'list'); + + root.ref.lastItemSpanwDate = Date.now(); +}; + +/** + * Inserts a new item + * @param root + * @param action + */ +const addItemView = ({ root, action }) => { + const { id, index, interactionMethod } = action; + + root.ref.addIndex = index; + + const now = Date.now(); + let spawnDate = now; + let opacity = 1; + + if (interactionMethod !== InteractionMethod.NONE) { + opacity = 0; + const cooldown = root.query('GET_ITEM_INSERT_INTERVAL'); + const dist = now - root.ref.lastItemSpanwDate; + spawnDate = dist < cooldown ? now + (cooldown - dist) : now; + } + + root.ref.lastItemSpanwDate = spawnDate; + + root.appendChildView( + root.createChildView( + // view type + item, + + // props + { + spawnDate, + id, + opacity, + interactionMethod + } + ), + index + ); +}; + +const moveItem = (item, x, y, vx = 0, vy = 1) => { + + item.translateX = x; + item.translateY = y; + + if (Date.now() > item.spawnDate) { + + // reveal element + if (item.opacity === 0) { + introItemView(item, x, y, vx, vy); + } + + // make sure is default scale every frame + item.scaleX = 1; + item.scaleY = 1; + item.opacity = 1; + + } +} + +const introItemView = (item, x, y, vx, vy) => { + + if (item.interactionMethod === InteractionMethod.NONE) { + item.translateX = null; + item.translateX = x; + item.translateY = null; + item.translateY = y; + } + + else if (item.interactionMethod === InteractionMethod.DROP) { + + item.translateX = null; + item.translateX = x - (vx * 20); + + item.translateY = null; + item.translateY = y - (vy * 10); + + item.scaleX = .8; + item.scaleY = .8; + } + + else if (item.interactionMethod === InteractionMethod.BROWSE) { + item.translateY = null; + item.translateY = y - 30; + } + + else if (item.interactionMethod === InteractionMethod.API) { + item.translateX = null; + item.translateX = x - 30; + item.translateY = null; + } + +} + +/** + * Removes an existing item + * @param root + * @param action + */ +const removeItemView = ({ root, action }) => { + const { id } = action; + + // get the view matching the given id + const view = root.childViews.find(child => child.id === id); + + // if no view found, exit + if (!view) { + return; + } + + // animate view out of view + view.scaleX = 0.9; + view.scaleY = 0.9; + view.opacity = 0; + + // mark for removal + view.markedForRemoval = true; +}; + +/** + * Setup action routes + */ +const route = createRoute({ + DID_ADD_ITEM: addItemView, + DID_REMOVE_ITEM: removeItemView +}); + + + +/** + * Write to view + * @param root + * @param actions + * @param props + */ +const write = ({ root, props, actions, shouldOptimize }) => { + + // route actions + route({ root, props, actions }); + + const { dragCoordinates } = props; + + // get index + const dragIndex = dragCoordinates ? getItemIndexByPosition(root, dragCoordinates) : null; + + // available space on horizontal axis + const horizontalSpace = root.rect.element.width; + + // only draw children that have dimensions + const visibleChildren = root.childViews.filter(child => child.rect.outer.height); + + // sort based on current active items + const children = root.query('GET_ACTIVE_ITEMS').map(item => visibleChildren.find(child => child.id === item.id)).filter(item => item); + + // add index is used to reserve the dropped/added item index till the actual item is rendered + const addIndex = root.ref.addIndex || null; + + // add index no longer needed till possibly next draw + root.ref.addIndex = null; + + let dragIndexOffset = 0; + let removeIndexOffset = 0; + let addIndexOffset = 0; + + if (children.length === 0) return; + + const childRect = children[0].rect.element; + const itemVerticalMargin = childRect.marginTop + childRect.marginBottom; + const itemHorizontalMargin = childRect.marginLeft + childRect.marginRight; + const itemWidth = childRect.width + itemHorizontalMargin; + const itemHeight = childRect.height + itemVerticalMargin; + const itemsPerRow = Math.round(horizontalSpace / itemWidth); + + // stack + if (itemsPerRow === 1) { + + let offsetY = 0; + let dragOffset = 0; + + children.forEach((child, index) => { + + if (dragIndex) { + let dist = index - dragIndex; + if (dist === -2) { + dragOffset = -itemVerticalMargin * .25; + } + else if (dist === -1) { + dragOffset = -itemVerticalMargin * .75; + } + else if (dist === 0) { + dragOffset = itemVerticalMargin * .75; + } + else if (dist === 1) { + dragOffset = itemVerticalMargin * .25; + } + else { + dragOffset = 0; + } + } + + if (shouldOptimize) { + child.translateX = null; + child.translateY = null; + } + + if (!child.markedForRemoval) { + moveItem(child, 0, offsetY + dragOffset); + } + + let itemHeight = child.rect.element.height + itemVerticalMargin; + + let visualHeight = itemHeight * (child.markedForRemoval ? child.opacity : 1); + + offsetY += visualHeight; + + }); + } + // grid + else { + + let prevX = 0; + let prevY = 0; + + children.forEach((child, index) => { + + if (index === dragIndex) { + dragIndexOffset = 1; + } + + if (index === addIndex) { + addIndexOffset += 1; + } + + if (child.markedForRemoval && child.opacity < .5) { + removeIndexOffset -= 1; + } + + const visualIndex = index + addIndexOffset + dragIndexOffset + removeIndexOffset; + + const indexX = (visualIndex % itemsPerRow); + const indexY = Math.floor(visualIndex / itemsPerRow); + + const offsetX = indexX * itemWidth; + const offsetY = indexY * itemHeight; + + const vectorX = Math.sign(offsetX - prevX); + const vectorY = Math.sign(offsetY - prevY); + + prevX = offsetX; + prevY = offsetY; + + if (child.markedForRemoval) return; + + if (shouldOptimize) { + child.translateX = null; + child.translateY = null; + } + + moveItem(child, offsetX, offsetY, vectorX, vectorY); + }); + } + +}; + +/** + * Filters actions that are meant specifically for a certain child of the list + * @param child + * @param actions + */ +const filterSetItemActions = (child, actions) => + actions.filter(action => { + + // if action has an id, filter out actions that don't have this child id + if (action.data && action.data.id) { + return child.id === action.data.id; + } + + // allow all other actions + return true; + }); + +export const list = createView({ + create, + write, + tag: 'ul', + name: 'list', + didWriteView: ({ root }) => { + root.childViews + .filter(view => view.markedForRemoval && view.opacity === 0 && view.resting) + .forEach(view => { + view._destroy(); + root.removeChildView(view); + }); + }, + filterFrameActionsForChild: filterSetItemActions, + mixins: { + apis: ['dragCoordinates'] + } +}); diff --git a/src/js/app/view/listScroller.js b/src/js/app/view/listScroller.js new file mode 100644 index 00000000..31bde3e5 --- /dev/null +++ b/src/js/app/view/listScroller.js @@ -0,0 +1,67 @@ +import { createView, createRoute } from '../frame/index'; +import { list } from './list'; + +const create = ({ root, props }) => { + root.ref.list = root.appendChildView(root.createChildView(list)); + props.dragCoordinates = null; + props.overflowing = false; +}; + +const storeDragCoordinates = ({ root, props, action }) => { + if (!root.query('GET_ITEM_INSERT_LOCATION_FREEDOM')) return; + props.dragCoordinates = { + left: action.position.scopeLeft - root.ref.list.rect.element.left, + top: action.position.scopeTop - (root.rect.outer.top + root.rect.element.marginTop + root.rect.element.scrollTop) + }; +}; + +const clearDragCoordinates = ({ props }) => { + props.dragCoordinates = null; +}; + +const route = createRoute({ + DID_DRAG: storeDragCoordinates, + DID_END_DRAG: clearDragCoordinates +}); + +const write = ({ root, props, actions }) => { + + // route actions + route({ root, props, actions }); + + // current drag position + root.ref.list.dragCoordinates = props.dragCoordinates; + + // if currently overflowing but no longer received overflow + if (props.overflowing && !props.overflow) { + props.overflowing = false; + + // reset overflow state + root.element.dataset.state = ''; + root.height = null; + } + + // if is not overflowing currently but does receive overflow value + if (props.overflow) { + const newHeight = Math.round(props.overflow); + if (newHeight !== root.height) { + props.overflowing = true; + root.element.dataset.state = 'overflow'; + root.height = newHeight; + } + } + +}; + +export const listScroller = createView({ + create, + write, + name: 'list-scroller', + mixins: { + apis: ['overflow' , 'dragCoordinates'], + styles: ['height', 'translateY'], + animations: { + translateY: 'spring' + } + } +}); diff --git a/src/js/app/view/panel.js b/src/js/app/view/panel.js new file mode 100644 index 00000000..a9b1ffb0 --- /dev/null +++ b/src/js/app/view/panel.js @@ -0,0 +1,100 @@ +import { createView } from '../frame/index'; +import { isBoolean } from '../../utils/isBoolean'; + +const PANEL_SPRING_PROPS = { type: 'spring', damping: 0.6, mass: 7 }; + +const create = ({ root, props }) => { + [ + { + name: 'top' + }, + { + name: 'center', + props: { + translateY: null, + scaleY: null + }, + mixins: { + animations: { + scaleY: PANEL_SPRING_PROPS + }, + styles: ['translateY', 'scaleY'] + } + }, + { + name: 'bottom', + props: { + translateY: null + }, + mixins: { + animations: { + translateY: PANEL_SPRING_PROPS + }, + styles: ['translateY'] + } + } + ].forEach(section => { + createSection(root, section, props.name); + }); + + root.element.classList.add(`filepond--${props.name}`); + + root.ref.scalable = null; +}; + +const createSection = (root, section, className) => { + + const viewConstructor = createView({ + name: `panel-${section.name} filepond--${className}`, + mixins: section.mixins, + ignoreRectUpdate: true + }); + + const view = root.createChildView(viewConstructor, section.props); + + root.ref[section.name] = root.appendChildView(view); +}; + +const write = ({ root, props }) => { + + // update scalable state + if (root.ref.scalable === null || props.scalable !== root.ref.scalable) { + root.ref.scalable = isBoolean(props.scalable) + ? props.scalable + : true; + root.element.dataset.scalable = root.ref.scalable; + } + + // no height, can't set + if (!props.height) { + return; + } + + // get child rects + const topRect = root.ref.top.rect.element; + const bottomRect = root.ref.bottom.rect.element; + + // make sure height never is smaller than bottom and top seciton heights combined (will probably never happen, but who knows) + const height = Math.max(topRect.height + bottomRect.height, props.height); + + // offset center part + root.ref.center.translateY = topRect.height; + + // scale center part + // use math ceil to prevent transparent lines because of rounding errors + root.ref.center.scaleY = + (height - topRect.height - bottomRect.height) / 100; + + // offset bottom part + root.ref.bottom.translateY = height - bottomRect.height; +}; + +export const panel = createView({ + name: 'panel', + write, + create, + ignoreRect: true, + mixins: { + apis: ['height', 'scalable'] + } +}); diff --git a/src/js/app/view/progressIndicator.js b/src/js/app/view/progressIndicator.js new file mode 100644 index 00000000..6de61166 --- /dev/null +++ b/src/js/app/view/progressIndicator.js @@ -0,0 +1,94 @@ +import { createView } from '../frame/index'; +import { createElement } from '../frame/utils/createElement'; +import { attr } from '../../utils/attr'; +import { percentageArc } from '../../utils/percentageArc'; + +const create = ({ root, props }) => { + // start at 0 + props.spin = false; + props.progress = 0; + props.opacity = 0; + + // svg + const svg = createElement('svg'); + root.ref.path = createElement('path', { + 'stroke-width': 2, + 'stroke-linecap': 'round' + }); + svg.appendChild(root.ref.path); + + root.ref.svg = svg; + + root.appendChild(svg); +}; + +const write = ({ root, props }) => { + + if (props.opacity === 0) { + return; + } + + if (props.align) { + root.element.dataset.align = props.align; + } + + // get width of stroke + const ringStrokeWidth = parseInt(attr(root.ref.path, 'stroke-width'), 10); + + // calculate size of ring + const size = root.rect.element.width * 0.5; + + // ring state + let ringFrom = 0; + let ringTo = 0; + + // now in busy mode + if (props.spin) { + ringFrom = 0; + ringTo = 0.5; + } else { + ringFrom = 0; + ringTo = props.progress; + } + + // get arc path + const coordinates = percentageArc( + size, + size, + size - ringStrokeWidth, + ringFrom, + ringTo + ); + + // update progress bar + attr(root.ref.path, 'd', coordinates); + + // hide while contains 0 value + attr( + root.ref.path, + 'stroke-opacity', + props.spin || props.progress > 0 ? 1 : 0 + ); +}; + +export const progressIndicator = createView({ + tag: 'div', + name: 'progress-indicator', + ignoreRectUpdate: true, + ignoreRect: true, + create, + write, + mixins: { + apis: ['progress', 'spin', 'align'], + styles: ['opacity'], + animations: { + opacity: { type: 'tween', duration: 500 }, + progress: { + type: 'spring', + stiffness: 0.95, + damping: 0.65, + mass: 10 + } + } + } +}); diff --git a/src/js/app/view/root.js b/src/js/app/view/root.js new file mode 100644 index 00000000..d0b0433c --- /dev/null +++ b/src/js/app/view/root.js @@ -0,0 +1,588 @@ +import { createView, createRoute } from '../frame/index'; +import { applyFilters } from '../../filter'; +import { listScroller } from './listScroller'; +import { panel } from './panel'; +import { browser } from './browser'; +import { dropLabel } from './dropLabel'; +import { drip } from './drip'; +import { createHopper } from '../utils/createHopper'; +import { createPaster } from '../utils/createPaster'; +import { InteractionMethod } from '../enum/InteractionMethod'; +import { getItemIndexByPosition } from '../utils/getItemIndexByPosition'; +import { isInt } from '../../utils/isInt'; +import { isEmpty } from '../../utils/isEmpty'; +import { assistant } from './assistant'; +import { toCamels } from '../../utils/toCamels'; +import { createElement } from '../../utils/createElement'; +import { createResponse } from '../../utils/createResponse'; +import { debounce } from '../../utils/debounce'; + +const MAX_FILES_LIMIT = 1000000; + +const create = ({ root, props }) => { + + // Add id + const id = root.query('GET_ID'); + if (id) { + root.element.id = id; + } + + // Add className + const className = root.query('GET_CLASS_NAME'); + if (className) { + className.split(' ').forEach(name => { + root.element.classList.add(name); + }); + } + + // Field label + root.ref.label = root.appendChildView( + root.createChildView(dropLabel, { + ...props, + translateY: null, + caption: root.query('GET_LABEL_IDLE') + }) + ); + + // List of items + root.ref.list = root.appendChildView( + root.createChildView(listScroller, { translateY: null }) + ); + + // Background panel + root.ref.panel = root.appendChildView( + root.createChildView(panel, { name: 'panel-root' }) + ); + + // Assistant notifies assistive tech when content changes + root.ref.assistant = root.appendChildView( + root.createChildView(assistant, { ...props }) + ); + + // Measure (tests if fixed height was set) + // DOCTYPE needs to be set for this to work + root.ref.measure = createElement('div'); + root.ref.measure.style.height = '100%'; + root.element.appendChild(root.ref.measure); + + // information on the root height or fixed height status + root.ref.bounds = null; + + // apply initial style properties + root.query('GET_STYLES').filter(style => !isEmpty(style.value)) + .map(({ name, value }) => { + root.element.dataset[name] = value; + }); + + // determine if width changed + root.ref.widthPrevious = null; + root.ref.widthUpdated = debounce(() => { + root.dispatch('DID_RESIZE_ROOT'); + }, 250); + + // history of updates + root.ref.updateHistory = []; +}; + +const write = ({ root, props, actions }) => { + + if (root.rect.element.width !== root.ref.widthPrevious) { + root.ref.widthPrevious = root.rect.element.width; + root.ref.widthUpdated(); + } + + // get box bounds, we do this only once + let bounds = root.ref.bounds; + if (!bounds) { + bounds = root.ref.bounds = calculateRootBoundingBoxHeight(root); + + // destroy measure element + root.element.removeChild(root.ref.measure); + root.ref.measure = null; + } + + // route actions + route({ root, props, actions }); + + // apply style properties + actions + .filter(action => /^DID_SET_STYLE_/.test(action.type)) + .filter(action => !isEmpty(action.data.value)) + .map(({ type, data }) => { + const name = toCamels(type.substr(8).toLowerCase(), '_'); + root.element.dataset[name] = data.value; + root.invalidateLayout(); + }); + + // get quick references to various high level parts of the upload tool + const { hopper, label, list, panel } = root.ref; + + // sets correct state to hopper scope + if (hopper) { + hopper.updateHopperState(); + } + + // bool to indicate if we're full or not + const aspectRatio = root.query('GET_PANEL_ASPECT_RATIO'); + const isMultiItem = root.query('GET_ALLOW_MULTIPLE'); + const totalItems = root.query('GET_TOTAL_ITEMS'); + const maxItems = isMultiItem ? root.query('GET_MAX_FILES') || MAX_FILES_LIMIT : 1; + const atMaxCapacity = totalItems === maxItems; + + // action used to add item + const addAction = actions.find(action => action.type === 'DID_ADD_ITEM'); + + // if reached max capacity and we've just reached it + if (atMaxCapacity && addAction) { + + // get interaction type + const interactionMethod = addAction.data.interactionMethod; + + // hide label + label.opacity = 0; + + if (isMultiItem) { + label.translateY = -40; + } + else { + if (interactionMethod === InteractionMethod.API) { + label.translateX = 40; + } + else if (interactionMethod === InteractionMethod.BROWSE) { + label.translateY = 40; + } + else { + label.translateY = 30; + } + } + + } + else if (!atMaxCapacity) { + label.opacity = 1; + label.translateX = 0; + label.translateY = 0; + } + + const listItemMargin = calculateListItemMargin(root); + + const listHeight = calculateListHeight(root); + + const labelHeight = label.rect.element.height; + const currentLabelHeight = !isMultiItem || atMaxCapacity ? 0 : labelHeight; + + const listMarginTop = atMaxCapacity ? list.rect.element.marginTop : 0; + const listMarginBottom = totalItems === 0 ? 0 : list.rect.element.marginBottom; + + const visualHeight = currentLabelHeight + listMarginTop + listHeight.visual + listMarginBottom; + const boundsHeight = currentLabelHeight + listMarginTop + listHeight.bounds + listMarginBottom; + + // link list to label bottom position + list.translateY = Math.max(0, currentLabelHeight - list.rect.element.marginTop) - listItemMargin.top; + + if (aspectRatio) { // fixed aspect ratio + + // calculate height based on width + const width = root.rect.element.width; + const height = width * aspectRatio; + + // remember this width + const history = root.ref.updateHistory; + history.push(width); + + const MAX_BOUNCES = 2; + if (history.length > MAX_BOUNCES * 2) { + const l = history.length; + const bottom = l - 10; + let bounces = 0; + for (let i=l;i>=bottom;i--) { + + if (history[i] === history[i-2]) { + bounces++; + } + + if (bounces >= MAX_BOUNCES) { + // dont adjust height + return; + } + } + } + + + // fix height of panel so it adheres to aspect ratio + panel.scalable = false; + panel.height = height; + + // available height for list + const listAvailableHeight = + // the height of the panel minus the label height + height - currentLabelHeight + // the room we leave open between the end of the list and the panel bottom + - (listMarginBottom - listItemMargin.bottom) + // if we're full we need to leave some room between the top of the panel and the list + - (atMaxCapacity ? listMarginTop : 0); + + if (listHeight.visual > listAvailableHeight) { + list.overflow = listAvailableHeight; + } + else { + list.overflow = null; + } + + // set container bounds (so pushes siblings downwards) + root.height = height; + } + else if (bounds.fixedHeight) { // fixed height + + // fix height of panel + panel.scalable = false; + + // available height for list + const listAvailableHeight = + // the height of the panel minus the label height + bounds.fixedHeight - currentLabelHeight + // the room we leave open between the end of the list and the panel bottom + - (listMarginBottom - listItemMargin.bottom) + // if we're full we need to leave some room between the top of the panel and the list + - (atMaxCapacity ? listMarginTop : 0); + + // set list height + if (listHeight.visual > listAvailableHeight) { + list.overflow = listAvailableHeight; + } + else { + list.overflow = null; + } + + // no need to set container bounds as these are handles by CSS fixed height + + } + else if (bounds.cappedHeight) { // max-height + + // not a fixed height panel + const isCappedHeight = visualHeight >= bounds.cappedHeight; + const panelHeight = Math.min(bounds.cappedHeight, visualHeight); + panel.scalable = true; + panel.height = isCappedHeight ? panelHeight : panelHeight - listItemMargin.top - listItemMargin.bottom; + + // available height for list + const listAvailableHeight = + // the height of the panel minus the label height + panelHeight - currentLabelHeight + // the room we leave open between the end of the list and the panel bottom + - (listMarginBottom - listItemMargin.bottom) + // if we're full we need to leave some room between the top of the panel and the list + - (atMaxCapacity ? listMarginTop : 0); + + // set list height (if is overflowing) + if (visualHeight > bounds.cappedHeight && listHeight.visual > listAvailableHeight) { + list.overflow = listAvailableHeight; + } + else { + list.overflow = null; + } + + // set container bounds (so pushes siblings downwards) + root.height = Math.min( + bounds.cappedHeight, + boundsHeight - listItemMargin.top - listItemMargin.bottom + ); + + } + else { // flexible height + + // not a fixed height panel + const itemMargin = totalItems > 0 ? listItemMargin.top + listItemMargin.bottom : 0; + panel.scalable = true; + panel.height = Math.max(labelHeight, visualHeight - itemMargin); + + // set container bounds (so pushes siblings downwards) + root.height = Math.max(labelHeight, boundsHeight - itemMargin); + } +}; + +const calculateListItemMargin = (root) => { + const item = root.ref.list.childViews[0].childViews[0]; + return item ? { + top: item.rect.element.marginTop, + bottom: item.rect.element.marginBottom + } : { + top: 0, + bottom: 0 + } +} + +const calculateListHeight = (root) => { + + let visual = 0; + let bounds = 0; + + // get file list reference + const scrollList = root.ref.list; + const itemList = scrollList.childViews[0]; + const children = itemList.childViews; + + // no children, done! + if (children.length === 0) return { visual, bounds }; + + const horizontalSpace = itemList.rect.element.width; + const dragIndex = getItemIndexByPosition(itemList, scrollList.dragCoordinates); + + const childRect = children[0].rect.element; + + const itemVerticalMargin = childRect.marginTop + childRect.marginBottom; + const itemHorizontalMargin = childRect.marginLeft + childRect.marginRight; + + const itemWidth = childRect.width + itemHorizontalMargin; + const itemHeight = childRect.height + itemVerticalMargin; + + const newItem = (typeof dragIndex !== 'undefined' && dragIndex >= 0 ? 1 : 0); + const removedItem = children.find(child => child.markedForRemoval && child.opacity < .45) ? -1 : 0; + const verticalItemCount = children.length + newItem + removedItem; + const itemsPerRow = Math.round(horizontalSpace / itemWidth); + + // stack + if (itemsPerRow === 1) { + children.forEach(item => { + const height = item.rect.element.height + itemVerticalMargin; + bounds += height; + visual += height * item.opacity; + }); + } + // grid + else { + bounds = Math.ceil(verticalItemCount / itemsPerRow) * itemHeight; + visual = bounds; + } + + return { visual, bounds } +} + +const calculateRootBoundingBoxHeight = (root) => { + + const height = root.ref.measureHeight || null; + const cappedHeight = parseInt(root.style.maxHeight, 10) || null; + const fixedHeight = height === 0 ? null : height; + + return { + cappedHeight, + fixedHeight + }; + +}; + +const exceedsMaxFiles = (root, items) => { + + const allowReplace = root.query('GET_ALLOW_REPLACE'); + const allowMultiple = root.query('GET_ALLOW_MULTIPLE'); + const totalItems = root.query('GET_TOTAL_ITEMS'); + let maxItems = root.query('GET_MAX_FILES'); + + // total amount of items being dragged + const totalBrowseItems = items.length; + + // if does not allow multiple items and dragging more than one item + if (!allowMultiple && totalBrowseItems > 1) { + return true; + } + + // limit max items to one if not allowed to drop multiple items + maxItems = allowMultiple + ? maxItems + : allowReplace ? maxItems : 1; + + // no more room? + const hasMaxItems = isInt(maxItems); + if (hasMaxItems && totalItems + totalBrowseItems > maxItems) { + root.dispatch('DID_THROW_MAX_FILES', { + source: items, + error: createResponse( + 'warning', + 0, + 'Max files' + ) + }); + return true; + } + + return false; +} + +const getDragIndex = (list, position) => { + const itemList = list.childViews[0]; + return getItemIndexByPosition(itemList, { + left: position.scopeLeft - itemList.rect.element.left, + top: position.scopeTop - (list.rect.outer.top + list.rect.element.marginTop + list.rect.element.scrollTop) + }); +} + +/** + * Enable or disable file drop functionality + */ +const toggleDrop = (root) => { + const isAllowed = root.query('GET_ALLOW_DROP'); + const isDisabled = root.query('GET_DISABLED'); + const enabled = isAllowed && !isDisabled; + if (enabled && !root.ref.hopper) { + const hopper = createHopper( + root.element, + items => { + + // these files don't fit so stop here + if (exceedsMaxFiles(root, items)) return false; + + // all items should be validated by all filters as valid + const dropValidation = root.query('GET_DROP_VALIDATION'); + return dropValidation ? items.every(item => + applyFilters('ALLOW_HOPPER_ITEM', item, { query: root.query }) + .every(result => result === true) + ) : true; + }, + { + catchesDropsOnPage: root.query('GET_DROP_ON_PAGE'), + requiresDropOnElement: root.query('GET_DROP_ON_ELEMENT') + } + ); + + hopper.onload = (items, position) => { + + root.dispatch('ADD_ITEMS', { + items, + index: getDragIndex(root.ref.list, position), + interactionMethod: InteractionMethod.DROP + }); + + root.dispatch('DID_DROP', { position }); + + root.dispatch('DID_END_DRAG', { position }); + }; + + hopper.ondragstart = position => { + root.dispatch('DID_START_DRAG', { position }); + }; + + hopper.ondrag = debounce(position => { + root.dispatch('DID_DRAG', { position }); + }); + + hopper.ondragend = position => { + root.dispatch('DID_END_DRAG', { position }); + }; + + root.ref.hopper = hopper; + + root.ref.drip = root.appendChildView(root.createChildView(drip)); + } + else if (!enabled && root.ref.hopper) { + root.ref.hopper.destroy(); + root.ref.hopper = null; + root.removeChildView(root.ref.drip); + } +}; + +/** + * Enable or disable browse functionality + */ +const toggleBrowse = (root, props) => { + const isAllowed = root.query('GET_ALLOW_BROWSE'); + const isDisabled = root.query('GET_DISABLED'); + const enabled = isAllowed && !isDisabled; + if (enabled && !root.ref.browser) { + root.ref.browser = root.appendChildView( + root.createChildView(browser, { + ...props, + onload: items => { + + // these files don't fit so stop here + if (exceedsMaxFiles(root, items)) return false; + + // add items! + root.dispatch('ADD_ITEMS', { + items, + index: -1, + interactionMethod: InteractionMethod.BROWSE + }); + + } + }), + 0 + ); + } + else if (!enabled && root.ref.browser) { + root.removeChildView(root.ref.browser); + } +} + +/** + * Enable or disable paste functionality + */ +const togglePaste = (root) => { + const isAllowed = root.query('GET_ALLOW_PASTE'); + const isDisabled = root.query('GET_DISABLED'); + const enabled = isAllowed && !isDisabled; + if (enabled && !root.ref.paster) { + root.ref.paster = createPaster(); + root.ref.paster.onload = items => { + root.dispatch('ADD_ITEMS', { + items, + index: getDragIndex(root.ref.list, position), + interactionMethod: InteractionMethod.PASTE + }); + }; + } + else if (!enabled && root.ref.paster) { + root.ref.paster.destroy(); + root.ref.paster = null; + } +} + +/** + * Route actions + */ +const route = createRoute({ + DID_SET_ALLOW_BROWSE: ({ root, props }) => { + toggleBrowse(root, props); + }, + DID_SET_ALLOW_DROP: ({ root }) => { + toggleDrop(root); + }, + DID_SET_ALLOW_PASTE: ({ root }) => { + togglePaste(root); + }, + DID_SET_DISABLED: ({ root, props }) => { + + toggleDrop(root); + togglePaste(root); + toggleBrowse(root, props); + + const isDisabled = root.query('GET_DISABLED'); + if (isDisabled) { + root.element.dataset.disabled = 'disabled' + } + else { + delete root.element.dataset.disabled; + } + + } +}); + +export const root = createView({ + name: 'root', + read: ({ root }) => { + if (root.ref.measure) { + root.ref.measureHeight = root.ref.measure.offsetHeight; + } + }, + create, + write, + destroy: ({ root }) => { + if (root.ref.paster) { + root.ref.paster.destroy(); + } + if (root.ref.hopper) { + root.ref.hopper.destroy(); + } + }, + mixins: { + styles: ['height'] + } +}); diff --git a/src/js/createApp.js b/src/js/createApp.js new file mode 100644 index 00000000..42bb27da --- /dev/null +++ b/src/js/createApp.js @@ -0,0 +1,7 @@ +import { isNode } from './utils/isNode'; +import { createAppObject } from './createAppObject'; +import { createAppAtElement } from './createAppAtElement'; + +// if an element is passed, we create the instance at that element, if not, we just create an up object +export const createApp = (...args) => + isNode(args[0]) ? createAppAtElement(...args) : createAppObject(...args); diff --git a/src/js/createAppAPI.js b/src/js/createAppAPI.js new file mode 100644 index 00000000..c1111406 --- /dev/null +++ b/src/js/createAppAPI.js @@ -0,0 +1,10 @@ +import { copyObjectPropertiesToObject } from './utils/copyObjectPropertiesToObject'; +const PRIVATE_METHODS = ['fire', '_read', '_write']; + +export const createAppAPI = app => { + const api = {}; + + copyObjectPropertiesToObject(app, api, PRIVATE_METHODS); + + return api; +}; diff --git a/src/js/createAppAtElement.js b/src/js/createAppAtElement.js new file mode 100644 index 00000000..e5e20ede --- /dev/null +++ b/src/js/createAppAtElement.js @@ -0,0 +1,96 @@ +import { getAttributesAsObject } from './utils/getAttributesAsObject'; +import { createAppObject } from './createAppObject'; +import { applyFilters } from './filter'; +import { isObject } from './utils/isObject'; + +export const createAppAtElement = (element, options = {}) => { + + // how attributes of the input element are mapped to the options for the plugin + const attributeMapping = { + // translate to other name + '^class$': 'className', + '^multiple$': 'allowMultiple', + '^capture$': 'captureMethod', + + // group under single property + '^server': { + group: 'server', + mapping: { + '^process': { + group: 'process' + }, + '^revert': { + group: 'revert' + }, + '^fetch': { + group: 'fetch' + }, + '^restore': { + group: 'restore' + }, + '^load': { + group: 'load' + } + } + }, + + // don't include in object + '^type$': false, + '^files$': false + }; + + // add additional option translators + applyFilters('SET_ATTRIBUTE_TO_OPTION_MAP', attributeMapping); + + // create final options object by setting options object and then overriding options supplied on element + const mergedOptions = { + ...options + }; + + const attributeOptions = getAttributesAsObject( + element.nodeName === 'FIELDSET' + ? element.querySelector('input[type=file]') + : element, + attributeMapping + ); + + // merge with options object + Object.keys(attributeOptions).forEach(key => { + if (isObject(attributeOptions[key])) { + if (!isObject(mergedOptions[key])) { + mergedOptions[key] = {}; + } + Object.assign(mergedOptions[key], attributeOptions[key]); + } + else { + mergedOptions[key] = attributeOptions[key]; + } + }); + + // if parent is a fieldset, get files from parent by selecting all input fields that are not file upload fields + // these will then be automatically set to the initial files + mergedOptions.files = (options.files || []).concat( + Array.from(element.querySelectorAll('input:not([type=file])')).map(input => ({ + source: input.value, + options: { + type: input.dataset.type + } + })) + ); + + // build plugin + const app = createAppObject(mergedOptions); + + // add already selected files + if (element.files) { + Array.from(element.files).forEach(file => { + app.addFile(file); + }); + } + + // replace the target element + app.replaceElement(element); + + // expose + return app; +}; diff --git a/src/js/createAppObject.js b/src/js/createAppObject.js new file mode 100644 index 00000000..a2d8c895 --- /dev/null +++ b/src/js/createAppObject.js @@ -0,0 +1,24 @@ +import { createApp } from './app/index'; +import { getOptions } from './app/options'; +import { forin } from './utils/forin'; +import { Type } from './app/enum/Type'; + +export const createAppObject = (customOptions = {}) => { + // default options + const defaultOptions = {}; + forin(getOptions(), (key, value) => { + defaultOptions[key] = value[0]; + }); + + // set app options + const app = createApp({ + // default options + ...defaultOptions, + + // custom options + ...customOptions + }); + + // return the plugin instance + return app; +}; diff --git a/src/js/createAppPlugin.js b/src/js/createAppPlugin.js new file mode 100644 index 00000000..5812ca9d --- /dev/null +++ b/src/js/createAppPlugin.js @@ -0,0 +1,75 @@ +import { Type } from './app/enum/Type'; +import { addFilter } from './filter'; +import { applyFilterChain } from './filter'; +import { forin } from './utils/forin'; +import { replaceInString } from './utils/replaceInString'; +import { toNaturalFileSize } from './utils/toNaturalFileSize'; +import { isString } from './utils/isString'; +import { guesstimateMimeType } from './utils/guesstimateMimeType'; +import { getExtensionFromFilename } from './utils/getExtensionFromFilename'; +import { getFileFromBlob } from './utils/getFileFromBlob'; +import { getFilenameFromURL } from './utils/getFilenameFromURL'; +import { getFilenameWithoutExtension } from './utils/getFilenameWithoutExtension'; +import { createRoute } from './app/frame/createRoute'; +import { createWorker } from './utils/createWorker'; +import { createView } from './app/frame/createView'; +import { loadImage } from './utils/loadImage'; +import { copyFile } from './utils/copyFile'; +import { renameFile } from './utils/renameFile'; +import { extendDefaultOptions } from './app/options'; +import { fileActionButton } from './app/view/fileActionButton'; +import { isFile } from './utils/isFile'; +import { createBlob } from './utils/createBlob'; +import { text } from './utils/text'; +import { getNumericAspectRatioFromString } from './utils/getNumericAspectRatioFromString'; +import { createItemAPI } from './app/utils/createItemAPI'; + +// already registered plugins (can't register twice) +const registeredPlugins = []; + +// pass utils to plugin +export const createAppPlugin = plugin => { + + // already registered + if (registeredPlugins.includes(plugin)) { + return; + } + + // remember this plugin + registeredPlugins.push(plugin); + + // setup! + const pluginOutline = plugin({ + addFilter, + utils: { + Type, + forin, + isString, + isFile, + toNaturalFileSize, + replaceInString, + getExtensionFromFilename, + getFilenameWithoutExtension, + guesstimateMimeType, + getFileFromBlob, + getFilenameFromURL, + createRoute, + createWorker, + createView, + createItemAPI, + loadImage, + copyFile, + renameFile, + createBlob, + applyFilterChain, + text, + getNumericAspectRatioFromString + }, + views: { + fileActionButton + } + }); + + // add plugin options to default options + extendDefaultOptions(pluginOutline.options); +}; diff --git a/src/js/filter.js b/src/js/filter.js new file mode 100644 index 00000000..e99bf15d --- /dev/null +++ b/src/js/filter.js @@ -0,0 +1,40 @@ +// all registered filters +const filters = []; + +// loops over matching filters and passes options to each filter, returning the mapped results +export 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)); + }); + +export const applyFilters = (key, value, utils) => + filters.filter(f => f.key === key).map(f => f.cb(value, utils)); + +// adds a new filter to the list +export const addFilter = (key, cb) => filters.push({ key, cb }); diff --git a/src/js/index.js b/src/js/index.js new file mode 100644 index 00000000..814fe813 --- /dev/null +++ b/src/js/index.js @@ -0,0 +1,206 @@ +import { createApp } from './createApp'; +import { createPainter } from './app/frame/index'; +import { createAppAPI } from './createAppAPI'; +import { createAppPlugin } from './createAppPlugin'; +import { getOptions as getDefaultOptions, setOptions as setDefaultOptions } from './app/options'; +import { isObject } from './utils/isObject'; +import { forin } from './utils/forin'; +import { ItemStatus } from './app/enum/ItemStatus'; +import { Status as StatusEnum } from './app/enum/Status'; +import { FileOrigin as FileOriginEnum } from './app/enum/FileOrigin'; + +// feature detection used by supported() method +const isOperaMini = () => Object.prototype.toString.call(window.operamini) === '[object OperaMini]'; +const hasPromises = () => 'Promise' in window; +const hasBlobSlice = () => 'slice' in Blob.prototype; +const hasCreateObjectURL = () => 'URL' in window && 'createObjectURL' in window.URL; +const hasVisibility = () => 'visibilityState' in document; +const hasTiming = () => 'performance' in window; // iOS 8.x +const isBrowser = () => typeof window !== 'undefined' && typeof window.document !== 'undefined'; + +export const supported = (() => { + + // Runs immidiately and then remembers result for subsequent calls + const isSupported = + + // Has to be a browser + isBrowser() && + + // Can't run on Opera Mini due to lack of everything + !isOperaMini() && + + // Require these APIs to feature detect a modern browser + hasVisibility() && + hasPromises() && + hasBlobSlice() && + hasCreateObjectURL() && + hasTiming(); + + return () => isSupported; +})(); + + +/** + * Plugin internal state (over all instances) + */ +const state = { + // active app instances, used to redraw the apps and to find the later + apps: [] +}; + +// plugin name +const name = 'filepond'; + +/** + * Public Plugin methods + */ +const fn = () => {}; +export let Status = {}; +export let FileStatus = {}; +export let FileOrigin = {}; +export let OptionTypes = {}; +export let create = fn; +export let destroy = fn; +export let parse = fn; +export let find = fn; +export let registerPlugin = fn; +export let getOptions = fn; +export let setOptions = fn; + +// if not supported, no API +if (supported()) { + + // start painter and fire load event + createPainter( + () => { + state.apps.forEach(app => app._read()); + }, + (ts) => { + state.apps.forEach(app => app._write(ts)); + } + ); + + // fire loaded event so we know when FilePond is available + const dispatch = () => { + // let others know we have area ready + document.dispatchEvent( + new CustomEvent('FilePond:loaded', { + detail: { + supported, + create, + destroy, + parse, + find, + registerPlugin, + setOptions + } + }) + ); + + // clean up event + document.removeEventListener('DOMContentLoaded', dispatch); + }; + + if (document.readyState !== 'loading') { + // move to back of execution queue, FilePond should have been exported by then + setTimeout(() => dispatch(), 0); + } else { + document.addEventListener('DOMContentLoaded', dispatch); + } + + // updates the OptionTypes object based on the current options + const updateOptionTypes = () => forin(getDefaultOptions(), (key, value) => { + OptionTypes[key] = value[1]; + }); + + Status = { ...StatusEnum }; + FileOrigin = { ...FileOriginEnum }; + FileStatus = { ...ItemStatus }; + + OptionTypes = {}; + updateOptionTypes(); + + + // create method, creates apps and adds them to the app array + create = (...args) => { + const app = createApp(...args); + app.on('destroy', destroy); + state.apps.push(app); + return createAppAPI(app); + }; + + // destroys apps and removes them from the app array + destroy = hook => { + // returns true if the app was destroyed successfully + const indexToRemove = state.apps.findIndex(app => app.isAttachedTo(hook)); + if (indexToRemove >= 0) { + + // remove from apps + const app = state.apps.splice(indexToRemove, 1)[0]; + + // restore original dom element + app.restoreElement(); + + return true; + } + + return false; + }; + + // parses the given context for plugins (does not include the context element itself) + parse = context => { + // get all possible hooks + const matchedHooks = Array.from(context.querySelectorAll(`.${name}`)); + + // filter out already active hooks + const newHooks = matchedHooks.filter( + newHook => !state.apps.find(app => app.isAttachedTo(newHook)) + ); + + // create new instance for each hook + return newHooks.map(hook => create(hook)); + }; + + // returns an app based on the given element hook + find = hook => { + const app = state.apps.find(app => app.isAttachedTo(hook)); + if (!app) { + return null; + } + return createAppAPI(app); + }; + + // adds a plugin extension + registerPlugin = (...plugins) => { + + // register plugins + plugins.forEach(createAppPlugin); + + // update OptionTypes, each plugin might have extended the default options + updateOptionTypes(); + } + + getOptions = () => { + const opts = {}; + forin(getDefaultOptions(), (key, value) => { + opts[key] = value[0]; + }); + return opts; + } + + setOptions = opts => { + + if (isObject(opts)) { + // update existing plugins + state.apps.forEach(app => { + app.setOptions(opts); + }); + + // override defaults + setDefaultOptions(opts); + } + + // return new options + return getOptions(); + }; +} \ No newline at end of file diff --git a/src/js/tests/addFile.test.js b/src/js/tests/addFile.test.js new file mode 100644 index 00000000..ffb5245f --- /dev/null +++ b/src/js/tests/addFile.test.js @@ -0,0 +1,40 @@ +import { create } from '../index.js'; + +describe('adding files', () => { + + let pond = null; + + beforeEach(() => { + if (pond) { + pond.destroy(); + } + pond = create(); + + // enables draw loop, else it seems that filepond is hidden + Object.defineProperty(pond.element, 'offsetParent', { + get: jest.fn(() => 1) + }); + }); + + test('add file', done => { + const data = new File(['Hello World!'], 'dummy.txt', {type: 'text/plain', lastModified: new Date()}) + pond.addFile(data).then(item => { + done(); + }); + }); + + test('add blob', done => { + const data = new Blob(['Hello World!'], {type: 'text/plain'}); + pond.addFile(data).then(item => { + done(); + }); + }); + + test('add base64 string', done => { + const data = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=='; + pond.addFile(data).then(item => { + done(); + }); + }); + +}); \ No newline at end of file diff --git a/src/js/tests/callbacks.test.js b/src/js/tests/callbacks.test.js new file mode 100644 index 00000000..7985cba4 --- /dev/null +++ b/src/js/tests/callbacks.test.js @@ -0,0 +1,113 @@ +import { create, OptionTypes } from '../index.js'; + +describe('adding files', () => { + + const data = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=='; + let pond = null; + + const createPond = (options) => { + if (pond) { + pond.destroy(); + } + + pond = create({...options, server: { + process: (fieldName, file, metadata, load, error, progress, abort) => { + let p = 0; + const interval = setInterval(() => { + p+=.01; + progress(true, p, 1); + }, 50); + const timeout = setTimeout(() => { + clearInterval(interval); + progress(true, 1, 1); + load(Date.now()); + }, 750); + return {abort:() => {clearTimeout(timeout);abort();}} + } + }}); + + // enables draw loop, else it seems that filepond is hidden and it won't run + Object.defineProperty(pond.element, 'offsetParent', { + get: jest.fn(() => 1) + }); + } + + test('oninit', done => { + createPond({ + oninit: () => { + done(); + } + }); + }); + + test('onaddfilestart', done => { + createPond(); + pond.onaddfilestart = () => { + done(); + } + pond.addFile(data); + }); + + test('onaddfileprogress', done => { + createPond(); + pond.onaddfilestart = () => { + done(); + } + pond.addFile(data); + }); + + test('onaddfile', done => { + createPond(); + pond.onaddfilestart = () => { + done(); + } + pond.addFile(data); + }); + + test('onremovefile', done => { + createPond(); + pond.files = [data]; + pond.onremovefile = () => { + done(); + } + pond.removeFile(); + }); + + test('onprocessfilestart', done => { + createPond(); + pond.onprocessfilestart = () => { + done(); + } + pond.files = [data]; + }); + + test('onprocessfileprogress', done => { + createPond(); + pond.onprocessfileprogress = () => { + done(); + } + pond.files = [data]; + }); + + test('onprocessfileabort', done => { + createPond(); + pond.onprocessfileabort = () => { + done(); + } + pond.files = [data]; + + pond.getFile().abortProcessing(); + }); + + // test('onprocessfilerevert', done => { + // }); + + test('onprocessfile', done => { + createPond(); + pond.onprocessfile = () => { + done(); + } + pond.files = [data]; + }); + +}); \ No newline at end of file diff --git a/src/js/tests/createInstance.test.js b/src/js/tests/createInstance.test.js new file mode 100644 index 00000000..caee2549 --- /dev/null +++ b/src/js/tests/createInstance.test.js @@ -0,0 +1,35 @@ +import { create } from '../index.js'; + +describe('create instance', () => { + + test('without parameters', () => { + expect(create()).toBeDefined(); + }); + + test('with options object only', () => { + expect(create({ + instantUpload: false + }).instantUpload).toBe(false); + }); + + test('with element only', () => { + const form = document.createElement('form'); + const input = document.createElement('input'); + input.type = 'file'; + form.appendChild(input); + expect(create(input).element.parentNode).toBe(form); + }); + + test('with element and options object', () => { + const form = document.createElement('form'); + const input = document.createElement('input'); + input.type = 'file'; + input.dataset.dropOnPage = false; + form.appendChild(input); + const pond = create(input, { + dropOnPage: true + }); + expect(pond.dropOnPage).toBe(false); + }); + +}); \ No newline at end of file diff --git a/src/js/tests/removeFile.test.js b/src/js/tests/removeFile.test.js new file mode 100644 index 00000000..85a79e57 --- /dev/null +++ b/src/js/tests/removeFile.test.js @@ -0,0 +1,53 @@ +import { create } from '../index.js'; + +describe('removing files', () => { + + let pond = null; + + const DUMMY_FILE = new File(['Hello World!'], 'text_file_a.txt', {type: 'text/plain', lastModified: new Date()}); + + beforeEach(() => { + + if (pond) { + pond.destroy(); + } + + pond = create({ + server: { + process: (fieldName, file, metadata, load, error, progress, abort) => { + let p = 0; + const interval = setInterval(() => { + p+=.01; + progress(true, p, 1); + }, 50); + setTimeout(() => { + clearInterval(interval); + progress(true, 1, 1); + load(Date.now()); + }, 750); + } + } + }); + + Object.defineProperty(pond.element, 'offsetParent', { + get: jest.fn(() => 1) + }); + + }); + + test('remove file object', () => { + pond.files = [DUMMY_FILE]; + pond.removeFile(); + expect(pond.getFiles().length).toBe(0); + }); + + test('process and then remove file object', () => { + pond.files = [DUMMY_FILE]; + pond.processFile().then(() => { + pond.removeFile(); + expect(pond.getFiles().length).toBe(0); + done(); + }); + }); + +}); \ No newline at end of file diff --git a/src/js/tests/setFiles.test.js b/src/js/tests/setFiles.test.js new file mode 100644 index 00000000..6859fd65 --- /dev/null +++ b/src/js/tests/setFiles.test.js @@ -0,0 +1,92 @@ +import { create } from '../index.js'; + +const next = (cb) => { + setTimeout(() => { + cb() + }, 20); +} + +describe('setting the files property', () => { + + let pond = null; + + const TEXT_DATAURI_A = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=='; + const TEXT_FILE_A = new File(['Hello World!'], 'text_file_a.txt', {type: 'text/plain', lastModified: new Date()}); + const TEXT_FILE_B = new File(['Hello World!'], 'text_file_b.txt', {type: 'text/plain', lastModified: new Date()}); + + beforeEach(() => { + if (pond) { + pond.destroy(); + } + pond = create({ + allowMultiple: true + }); + + // enables draw loop, else it seems that filepond is hidden + Object.defineProperty(pond.element, 'offsetParent', { + get: jest.fn(() => 1) + }); + }); + + test('set single file object', () => { + pond.files = [TEXT_FILE_A]; + expect(pond.getFiles().length).toBe(1); + }); + + test('remove single file object', () => { + pond.files = [TEXT_FILE_A]; + pond.files = []; + expect(pond.getFiles().length).toBe(0); + }); + + test('replace single file object', () => { + pond.files = [TEXT_FILE_A]; + pond.files = [TEXT_FILE_B]; + expect(pond.getFile().filename).toBe('text_file_b.txt'); + }); + + test('re-add own file object', (done) => { + + // set data uri + pond.files = [TEXT_DATAURI_A]; + + // set marker + pond.getFile().setMetadata('marker', 'hello'); + + next(() => { + + // update files array with created file object from input file + pond.files = [pond.getFile().file]; + + // expect FilePond to find that this file already exists in array + expect(pond.getFile().getMetadata('marker')).toBe('hello'); + + done(); + + }); + + }); + + test('replace file in list of multiple files', (done) => { + + // set data uri + pond.files = [TEXT_DATAURI_A, TEXT_FILE_A]; + + // set marker + pond.getFile().setMetadata('marker', 'hello'); + + next(() => { + + // update files array with created file object from input file + pond.files = [TEXT_FILE_B, pond.getFile().file]; + + // expect FilePond to find that this file already exists in array + expect(pond.getFile(1).getMetadata('marker')).toBe('hello'); + + done(); + + }); + + }) + +}); \ No newline at end of file diff --git a/src/js/utils/arrayInsert.js b/src/js/utils/arrayInsert.js new file mode 100644 index 00000000..f4fd8092 --- /dev/null +++ b/src/js/utils/arrayInsert.js @@ -0,0 +1 @@ +export const arrayInsert = (arr, index, item) => arr.splice(index, 0, item); diff --git a/src/js/utils/arrayRemove.js b/src/js/utils/arrayRemove.js new file mode 100644 index 00000000..be71036e --- /dev/null +++ b/src/js/utils/arrayRemove.js @@ -0,0 +1 @@ +export const arrayRemove = (arr, index) => arr.splice(index, 1); diff --git a/src/js/utils/arrayReverse.js b/src/js/utils/arrayReverse.js new file mode 100644 index 00000000..7d7b399f --- /dev/null +++ b/src/js/utils/arrayReverse.js @@ -0,0 +1 @@ +export const arrayReverse = arr => arr.reverse(); diff --git a/src/js/utils/attr.js b/src/js/utils/attr.js new file mode 100644 index 00000000..60abda39 --- /dev/null +++ b/src/js/utils/attr.js @@ -0,0 +1,6 @@ +export const attr = (node, name, value = null) => { + if (value === null) { + return node.getAttribute(name) || node.hasAttribute(name); + } + node.setAttribute(name, value); +}; diff --git a/src/js/utils/attrToggle.js b/src/js/utils/attrToggle.js new file mode 100644 index 00000000..acdaba51 --- /dev/null +++ b/src/js/utils/attrToggle.js @@ -0,0 +1,8 @@ +import { attr } from './attr'; +export const attrToggle = (element, name, state, enabledValue = '') => { + if (state) { + attr(element, name, enabledValue); + } else { + element.removeAttribute(name); + } +}; diff --git a/src/js/utils/capitalizeFirstLetter.js b/src/js/utils/capitalizeFirstLetter.js new file mode 100644 index 00000000..a1bbeec2 --- /dev/null +++ b/src/js/utils/capitalizeFirstLetter.js @@ -0,0 +1,2 @@ +export const capitalizeFirstLetter = string => + string.charAt(0).toUpperCase() + string.slice(1); diff --git a/src/js/utils/composeObject.js b/src/js/utils/composeObject.js new file mode 100644 index 00000000..f270d170 --- /dev/null +++ b/src/js/utils/composeObject.js @@ -0,0 +1,3 @@ +export const composeObject = (...objects) => { + return Object.assign({}, ...objects); +}; diff --git a/src/js/utils/copyFile.js b/src/js/utils/copyFile.js new file mode 100644 index 00000000..fbd940ea --- /dev/null +++ b/src/js/utils/copyFile.js @@ -0,0 +1,2 @@ +import { renameFile } from './renameFile'; +export const copyFile = file => renameFile(file, file.name); diff --git a/src/js/utils/copyObjectPropertiesToObject.js b/src/js/utils/copyObjectPropertiesToObject.js new file mode 100644 index 00000000..39a65add --- /dev/null +++ b/src/js/utils/copyObjectPropertiesToObject.js @@ -0,0 +1,11 @@ +export const copyObjectPropertiesToObject = (src, target, excluded) => { + Object.getOwnPropertyNames(src) + .filter(property => !excluded.includes(property)) + .forEach(key => + Object.defineProperty( + target, + key, + Object.getOwnPropertyDescriptor(src, key) + ) + ); +}; diff --git a/src/js/utils/createBlob.js b/src/js/utils/createBlob.js new file mode 100644 index 00000000..7434a71b --- /dev/null +++ b/src/js/utils/createBlob.js @@ -0,0 +1,15 @@ +import { getBlobBuilder } from './getBlobBuilder'; + +export 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 + }); +}; diff --git a/src/js/utils/createDefaultResponse.js b/src/js/utils/createDefaultResponse.js new file mode 100644 index 00000000..a69b3f6a --- /dev/null +++ b/src/js/utils/createDefaultResponse.js @@ -0,0 +1,12 @@ +import { createResponse } from './createResponse'; + +export const createTimeoutResponse = (cb) => (xhr) => { + cb( + createResponse( + 'error', + 0, + 'Timeout', + xhr.getAllResponseHeaders() + ) + ) +} \ No newline at end of file diff --git a/src/js/utils/createElement.js b/src/js/utils/createElement.js new file mode 100644 index 00000000..80b0fecc --- /dev/null +++ b/src/js/utils/createElement.js @@ -0,0 +1,3 @@ +export const createElement = tagName => { + return document.createElement(tagName); +}; diff --git a/src/js/utils/createObject.js b/src/js/utils/createObject.js new file mode 100644 index 00000000..6f09869f --- /dev/null +++ b/src/js/utils/createObject.js @@ -0,0 +1,10 @@ +import { defineProperty } from './defineProperty'; +import { forin } from './forin'; + +export const createObject = definition => { + const obj = {}; + forin(definition, property => { + defineProperty(obj, property, definition[property]); + }); + return obj; +}; diff --git a/src/js/utils/createResponse.js b/src/js/utils/createResponse.js new file mode 100644 index 00000000..57fc588a --- /dev/null +++ b/src/js/utils/createResponse.js @@ -0,0 +1,6 @@ +export const createResponse = (type, code, body, headers) => ({ + type, + code, + body, + headers +}); \ No newline at end of file diff --git a/src/js/utils/createWorker.js b/src/js/utils/createWorker.js new file mode 100644 index 00000000..27575961 --- /dev/null +++ b/src/js/utils/createWorker.js @@ -0,0 +1,34 @@ +import { getUniqueId } from './getUniqueId'; + +export const createWorker = fn => { + const workerBlob = new Blob(['(', fn.toString(), ')()'], { + type: 'application/javascript' + }); + const workerURL = URL.createObjectURL(workerBlob); + const worker = new Worker(workerURL); + + return { + transfer: (message, cb) => {}, + post: (message, cb, transferList) => { + const id = getUniqueId(); + + worker.onmessage = e => { + if (e.data.id === id) { + cb(e.data.message); + } + }; + + worker.postMessage( + { + id, + message + }, + transferList + ); + }, + terminate: () => { + worker.terminate(); + URL.revokeObjectURL(workerURL); + } + }; +}; diff --git a/src/js/utils/debounce.js b/src/js/utils/debounce.js new file mode 100644 index 00000000..75805672 --- /dev/null +++ b/src/js/utils/debounce.js @@ -0,0 +1,27 @@ +export const debounce = (func, interval = 16, immidiateOnly = true) => { + let last = Date.now(); + let timeout = null; + + return (...args) => { + clearTimeout(timeout); + + const dist = Date.now() - last; + + const fn = () => { + last = Date.now(); + func(...args); + }; + + if (dist < interval) { + // we need to delay by the difference between interval and dist + // for example: if distance is 10 ms and interval is 16 ms, + // we need to wait an additional 6ms before calling the function) + if (!immidiateOnly) { + timeout = setTimeout(fn, interval - dist); + } + } else { + // go! + fn(); + } + }; +}; diff --git a/src/js/utils/deepCloneObject.js b/src/js/utils/deepCloneObject.js new file mode 100644 index 00000000..6aa55206 --- /dev/null +++ b/src/js/utils/deepCloneObject.js @@ -0,0 +1,13 @@ +import { isObject } from './isObject'; +import { isArray } from './isArray'; + +export 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; +} \ No newline at end of file diff --git a/src/js/utils/defineProperty.js b/src/js/utils/defineProperty.js new file mode 100644 index 00000000..6645e115 --- /dev/null +++ b/src/js/utils/defineProperty.js @@ -0,0 +1,7 @@ +export const defineProperty = (obj, property, definition) => { + if (typeof definition === 'function') { + obj[property] = definition; + return; + } + Object.defineProperty(obj, property, { ...definition }); +}; diff --git a/src/js/utils/describeArc.js b/src/js/utils/describeArc.js new file mode 100644 index 00000000..fd2beda7 --- /dev/null +++ b/src/js/utils/describeArc.js @@ -0,0 +1,19 @@ +import { polarToCartesian } from './polarToCartesian'; + +export const describeArc = (x, y, radius, startAngle, endAngle, arcSweep) => { + const start = polarToCartesian(x, y, radius, endAngle); + const end = polarToCartesian(x, y, radius, startAngle); + return [ + 'M', + start.x, + start.y, + 'A', + radius, + radius, + 0, + arcSweep, + 0, + end.x, + end.y + ].join(' '); +}; diff --git a/src/js/utils/forEachDelayed.js b/src/js/utils/forEachDelayed.js new file mode 100644 index 00000000..17ab4e99 --- /dev/null +++ b/src/js/utils/forEachDelayed.js @@ -0,0 +1,10 @@ +export const forEachDelayed = (items, cb, delay = 75) => + items.map( + (item, index) => + new Promise((resolve, reject) => { + setTimeout(() => { + cb(item); + resolve(); + }, delay * index); + }) + ); diff --git a/src/js/utils/forin.js b/src/js/utils/forin.js new file mode 100644 index 00000000..9e451a83 --- /dev/null +++ b/src/js/utils/forin.js @@ -0,0 +1,9 @@ +export const forin = (obj, cb) => { + for (const key in obj) { + if (!obj.hasOwnProperty(key)) { + continue; + } + + cb(key, obj[key]); + } +}; diff --git a/src/js/utils/formatFilename.js b/src/js/utils/formatFilename.js new file mode 100644 index 00000000..01d25dd5 --- /dev/null +++ b/src/js/utils/formatFilename.js @@ -0,0 +1 @@ +export const formatFilename = name => name; diff --git a/src/js/utils/fromCamels.js b/src/js/utils/fromCamels.js new file mode 100644 index 00000000..e8379a5d --- /dev/null +++ b/src/js/utils/fromCamels.js @@ -0,0 +1,5 @@ +export const fromCamels = (string, separator = '-') => + string + .split(/(?=[A-Z])/) + .map(part => part.toLowerCase()) + .join(separator); diff --git a/src/js/utils/getAttributesAsObject.js b/src/js/utils/getAttributesAsObject.js new file mode 100644 index 00000000..96e2736e --- /dev/null +++ b/src/js/utils/getAttributesAsObject.js @@ -0,0 +1,82 @@ +import { toCamels } from './toCamels'; +import { attr } from './attr'; +import { forin } from './forin'; +import { lowerCaseFirstLetter } from './lowerCaseFirstLetter'; +import { isString } from './isString'; +import { isObject } from './isObject'; + +const attributeNameToPropertyName = attributeName => + toCamels(attributeName.replace(/^data-/, '')); + +const mapObject = (object, propertyMap) => { + // remove unwanted + forin(propertyMap, (selector, mapping) => { + forin(object, (property, value) => { + // create regexp shortcut + const selectorRegExp = new RegExp(selector); + + // tests if + const matches = selectorRegExp.test(property); + + // no match, skip + if (!matches) { + return; + } + + // if there's a mapping, the original property is always removed + delete object[property]; + + // should only remove, we done! + if (mapping === false) { + return; + } + + // move value to new property + if (isString(mapping)) { + object[mapping] = value + return; + } + + // move to group + const group = mapping.group; + if (isObject(mapping) && !object[group]) { + object[group] = {}; + } + + object[group][ + lowerCaseFirstLetter(property.replace(selectorRegExp, '')) + ] = value; + }); + + // do submapping + if (mapping.mapping) { + mapObject(object[mapping.group], mapping.mapping); + } + }); +}; + +export const getAttributesAsObject = (node, attributeMapping = {}) => { + // turn attributes into object + const attributes = []; + forin(node.attributes, index => { + attributes.push(node.attributes[index]); + }); + + const output = attributes + .filter(attribute => attribute.name) + .reduce((obj, attribute) => { + + const value = attr( + node, + attribute.name + ); + + obj[attributeNameToPropertyName(attribute.name)] = value === attribute.name ? true : value; + return obj; + }, {}); + + // do mapping of object properties + mapObject(output, attributeMapping); + + return output; +}; \ No newline at end of file diff --git a/src/js/utils/getBase64DataFromBase64DataURI.js b/src/js/utils/getBase64DataFromBase64DataURI.js new file mode 100644 index 00000000..e2fef8f2 --- /dev/null +++ b/src/js/utils/getBase64DataFromBase64DataURI.js @@ -0,0 +1,7 @@ +export 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, ''); +}; diff --git a/src/js/utils/getBlobBuilder.js b/src/js/utils/getBlobBuilder.js new file mode 100644 index 00000000..bb90a32d --- /dev/null +++ b/src/js/utils/getBlobBuilder.js @@ -0,0 +1,7 @@ +export const getBlobBuilder = () => { + return (window.BlobBuilder = + window.BlobBuilder || + window.WebKitBlobBuilder || + window.MozBlobBuilder || + window.MSBlobBuilder); +}; diff --git a/src/js/utils/getBlobFromBase64DataURI.js b/src/js/utils/getBlobFromBase64DataURI.js new file mode 100644 index 00000000..4abe5cd3 --- /dev/null +++ b/src/js/utils/getBlobFromBase64DataURI.js @@ -0,0 +1,10 @@ +import { getBlobFromByteStringWithMimeType } from './getBlobFromByteStringWithMimeType'; +import { getMimeTypeFromBase64DataURI } from './getMimeTypeFromBase64DataURI'; +import { getByteStringFromBase64DataURI } from './getByteStringFromBase64DataURI'; + +export const getBlobFromBase64DataURI = dataURI => { + const mimeType = getMimeTypeFromBase64DataURI(dataURI); + const byteString = getByteStringFromBase64DataURI(dataURI); + + return getBlobFromByteStringWithMimeType(byteString, mimeType); +}; diff --git a/src/js/utils/getBlobFromByteStringWithMimeType.js b/src/js/utils/getBlobFromByteStringWithMimeType.js new file mode 100644 index 00000000..22356671 --- /dev/null +++ b/src/js/utils/getBlobFromByteStringWithMimeType.js @@ -0,0 +1,12 @@ +import { createBlob } from './createBlob'; + +export 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); +}; diff --git a/src/js/utils/getByteStringFromBase64DataURI.js b/src/js/utils/getByteStringFromBase64DataURI.js new file mode 100644 index 00000000..eefdde56 --- /dev/null +++ b/src/js/utils/getByteStringFromBase64DataURI.js @@ -0,0 +1,5 @@ +import { getBase64DataFromBase64DataURI } from './getBase64DataFromBase64DataURI'; + +export const getByteStringFromBase64DataURI = dataURI => { + return atob(getBase64DataFromBase64DataURI(dataURI)); +}; diff --git a/src/js/utils/getDateString.js b/src/js/utils/getDateString.js new file mode 100644 index 00000000..7c877a06 --- /dev/null +++ b/src/js/utils/getDateString.js @@ -0,0 +1,9 @@ +import { leftPad } from './leftPad'; +export 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')}`; diff --git a/src/js/utils/getDecimalSeparator.js b/src/js/utils/getDecimalSeparator.js new file mode 100644 index 00000000..3d47d8ef --- /dev/null +++ b/src/js/utils/getDecimalSeparator.js @@ -0,0 +1,4 @@ +import { getNonNumeric } from './getNonNumeric'; + +export const getDecimalSeparator = () => + getNonNumeric((1.1).toLocaleString())[0]; diff --git a/src/js/utils/getDomainFromURL.js b/src/js/utils/getDomainFromURL.js new file mode 100644 index 00000000..6bac6547 --- /dev/null +++ b/src/js/utils/getDomainFromURL.js @@ -0,0 +1,10 @@ +export const getDomainFromURL = url => { + if (url.indexOf('//') === 0) { + url = location.protocol + url; + } + return url + .toLowerCase() + .replace('blob:', '') + .replace(/([a-z])?:\/\//, '$1') + .split('/')[0]; +}; diff --git a/src/js/utils/getExtensionFromFilename.js b/src/js/utils/getExtensionFromFilename.js new file mode 100644 index 00000000..fe4702a1 --- /dev/null +++ b/src/js/utils/getExtensionFromFilename.js @@ -0,0 +1 @@ +export const getExtensionFromFilename = name => name.split('.').pop(); diff --git a/src/js/utils/getFileFromBase64DataURI.js b/src/js/utils/getFileFromBase64DataURI.js new file mode 100644 index 00000000..9c0b0092 --- /dev/null +++ b/src/js/utils/getFileFromBase64DataURI.js @@ -0,0 +1,11 @@ +import { getFileFromBlob } from './getFileFromBlob'; +import { getBlobFromBase64DataURI } from './getBlobFromBase64DataURI'; + +export const getFileFromBase64DataURI = (dataURI, filename, extension) => { + return getFileFromBlob( + getBlobFromBase64DataURI(dataURI), + filename, + null, + extension + ); +}; diff --git a/src/js/utils/getFileFromBlob.js b/src/js/utils/getFileFromBlob.js new file mode 100644 index 00000000..7a024c64 --- /dev/null +++ b/src/js/utils/getFileFromBlob.js @@ -0,0 +1,33 @@ +import { getExtensionFromFilename } from './getExtensionFromFilename'; +import { guesstimateExtension } from './guesstimateExtension'; +import { isString } from './isString'; +import { getDateString } from './getDateString'; + +export 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(); + + // 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; +}; diff --git a/src/js/utils/getFileInfoFromHeaders.js b/src/js/utils/getFileInfoFromHeaders.js new file mode 100644 index 00000000..864b4789 --- /dev/null +++ b/src/js/utils/getFileInfoFromHeaders.js @@ -0,0 +1,54 @@ +const getFileNameFromHeader = header => { + const matches = header.match(/(?:filename="(.+)")|(?:filename=(.+))/) || []; + return matches[1] || matches[2]; +} + +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; +} + +export 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; +} \ No newline at end of file diff --git a/src/js/utils/getFilenameFromURL.js b/src/js/utils/getFilenameFromURL.js new file mode 100644 index 00000000..caa4d414 --- /dev/null +++ b/src/js/utils/getFilenameFromURL.js @@ -0,0 +1,6 @@ +export const getFilenameFromURL = url => + url + .split('/') + .pop() + .split('?') + .shift(); diff --git a/src/js/utils/getFilenameWithoutExtension.js b/src/js/utils/getFilenameWithoutExtension.js new file mode 100644 index 00000000..2e7b5984 --- /dev/null +++ b/src/js/utils/getFilenameWithoutExtension.js @@ -0,0 +1,2 @@ +export const getFilenameWithoutExtension = name => + name.substr(0, name.lastIndexOf('.')) || name; diff --git a/src/js/utils/getMimeTypeFromBase64DataURI.js b/src/js/utils/getMimeTypeFromBase64DataURI.js new file mode 100644 index 00000000..7e0f4a69 --- /dev/null +++ b/src/js/utils/getMimeTypeFromBase64DataURI.js @@ -0,0 +1,3 @@ +export const getMimeTypeFromBase64DataURI = dataURI => { + return (/^data:(.+);/.exec(dataURI) || [])[1] || null; +}; diff --git a/src/js/utils/getNonNumeric.js b/src/js/utils/getNonNumeric.js new file mode 100644 index 00000000..875713e4 --- /dev/null +++ b/src/js/utils/getNonNumeric.js @@ -0,0 +1 @@ +export const getNonNumeric = str => /[^0-9]+/.exec(str); diff --git a/src/js/utils/getNumericAspectRatioFromString.js b/src/js/utils/getNumericAspectRatioFromString.js new file mode 100644 index 00000000..ffc886f7 --- /dev/null +++ b/src/js/utils/getNumericAspectRatioFromString.js @@ -0,0 +1,12 @@ +import { isEmpty } from './isEmpty'; + +export const getNumericAspectRatioFromString = aspectRatio => { + if (isEmpty(aspectRatio)) { + return aspectRatio; + } + if(/:/.test(aspectRatio)) { + const parts = aspectRatio.split(':'); + return parts[1] / parts[0]; + } + return parseFloat(aspectRatio); +}; \ No newline at end of file diff --git a/src/js/utils/getParameters.js b/src/js/utils/getParameters.js new file mode 100644 index 00000000..76ae174f --- /dev/null +++ b/src/js/utils/getParameters.js @@ -0,0 +1,6 @@ +export const getParameters = (args, filters) => { + return Object.keys(filters).reduce((acc, name) => { + acc[name] = args.find(arg => typeof arg === filters[name]); + return acc; + }, {}); +}; diff --git a/src/js/utils/getRandomNumber.js b/src/js/utils/getRandomNumber.js new file mode 100644 index 00000000..47c976a2 --- /dev/null +++ b/src/js/utils/getRandomNumber.js @@ -0,0 +1,2 @@ +export const getRandomNumber = (min = 0, max = 1) => + min + Math.random() * (max - min); diff --git a/src/js/utils/getRootNode.js b/src/js/utils/getRootNode.js new file mode 100644 index 00000000..5c540458 --- /dev/null +++ b/src/js/utils/getRootNode.js @@ -0,0 +1,2 @@ +export const getRootNode = element => + 'getRootNode' in element ? element.getRootNode() : document; diff --git a/src/js/utils/getThousandsSeparator.js b/src/js/utils/getThousandsSeparator.js new file mode 100644 index 00000000..41b184a8 --- /dev/null +++ b/src/js/utils/getThousandsSeparator.js @@ -0,0 +1,14 @@ +import { getNonNumeric } from './getNonNumeric'; +import { getDecimalSeparator } from './getDecimalSeparator'; + +export 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 === '.' ? ',' : '.'; +}; diff --git a/src/js/utils/getUniqueId.js b/src/js/utils/getUniqueId.js new file mode 100644 index 00000000..aafa4bbe --- /dev/null +++ b/src/js/utils/getUniqueId.js @@ -0,0 +1,4 @@ +export const getUniqueId = () => + Math.random() + .toString(36) + .substr(2, 9); diff --git a/src/js/utils/guesstimateExtension.js b/src/js/utils/guesstimateExtension.js new file mode 100644 index 00000000..2f14ccfb --- /dev/null +++ b/src/js/utils/guesstimateExtension.js @@ -0,0 +1,39 @@ +export 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 ''; +}; diff --git a/src/js/utils/guesstimateMimeType.js b/src/js/utils/guesstimateMimeType.js new file mode 100644 index 00000000..fe4d1b64 --- /dev/null +++ b/src/js/utils/guesstimateMimeType.js @@ -0,0 +1,23 @@ +const images = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'tiff']; +const text = ['css', 'csv', 'html', 'txt']; +const apps = ['rtf', 'pdf', 'json']; +const map = { + zip: 'zip|compressed', + epub: 'application/epub+zip' +}; + +export const guesstimateMimeType = (extension = '') => { + extension = extension.toLowerCase(); + if (images.includes(extension)) { + return ( + 'image/' + + (extension === 'jpg' + ? 'jpeg' + : extension === 'svg' ? 'svg+xml' : extension) + ); + } + if (text.includes(extension)) { + return 'text/' + extension; + } + return map[extension] || null; +}; diff --git a/src/js/utils/hasQueryString.js b/src/js/utils/hasQueryString.js new file mode 100644 index 00000000..0c6a7ed6 --- /dev/null +++ b/src/js/utils/hasQueryString.js @@ -0,0 +1 @@ +export const hasQueryString = url => /[?&]/.test(url); diff --git a/src/js/utils/insertAfter.js b/src/js/utils/insertAfter.js new file mode 100644 index 00000000..f838e697 --- /dev/null +++ b/src/js/utils/insertAfter.js @@ -0,0 +1,6 @@ +export const insertAfter = (newNode, referenceNode) => { + return referenceNode.parentNode.insertBefore( + newNode, + referenceNode.nextSibling + ); +}; diff --git a/src/js/utils/insertBefore.js b/src/js/utils/insertBefore.js new file mode 100644 index 00000000..104e6a11 --- /dev/null +++ b/src/js/utils/insertBefore.js @@ -0,0 +1 @@ +export const insertBefore = (newNode, referenceNode) => referenceNode.parentNode.insertBefore(newNode, referenceNode); diff --git a/src/js/utils/isArray.js b/src/js/utils/isArray.js new file mode 100644 index 00000000..e4f49780 --- /dev/null +++ b/src/js/utils/isArray.js @@ -0,0 +1 @@ +export const isArray = value => Array.isArray(value); diff --git a/src/js/utils/isBase64DataURI.js b/src/js/utils/isBase64DataURI.js new file mode 100644 index 00000000..faa3c459 --- /dev/null +++ b/src/js/utils/isBase64DataURI.js @@ -0,0 +1,4 @@ +export const isBase64DataURI = str => + /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*)\s*$/i.test( + str + ); diff --git a/src/js/utils/isBoolean.js b/src/js/utils/isBoolean.js new file mode 100644 index 00000000..479517cf --- /dev/null +++ b/src/js/utils/isBoolean.js @@ -0,0 +1 @@ +export const isBoolean = value => typeof value === 'boolean'; diff --git a/src/js/utils/isDefined.js b/src/js/utils/isDefined.js new file mode 100644 index 00000000..42378c21 --- /dev/null +++ b/src/js/utils/isDefined.js @@ -0,0 +1 @@ +export const isDefined = value => value != null; diff --git a/src/js/utils/isEmpty.js b/src/js/utils/isEmpty.js new file mode 100644 index 00000000..4de436c3 --- /dev/null +++ b/src/js/utils/isEmpty.js @@ -0,0 +1 @@ +export const isEmpty = value => value == null; diff --git a/src/js/utils/isExternalURL.js b/src/js/utils/isExternalURL.js new file mode 100644 index 00000000..c5ba57f8 --- /dev/null +++ b/src/js/utils/isExternalURL.js @@ -0,0 +1,4 @@ +import { getDomainFromURL } from './getDomainFromURL'; +export const isExternalURL = url => + (url.indexOf(':') > -1 || url.indexOf('//') > -1) && + getDomainFromURL(location.href) !== getDomainFromURL(url); diff --git a/src/js/utils/isFile.js b/src/js/utils/isFile.js new file mode 100644 index 00000000..42b31092 --- /dev/null +++ b/src/js/utils/isFile.js @@ -0,0 +1 @@ +export const isFile = value => value instanceof File || (value instanceof Blob && value.name); diff --git a/src/js/utils/isFunction.js b/src/js/utils/isFunction.js new file mode 100644 index 00000000..26245c2a --- /dev/null +++ b/src/js/utils/isFunction.js @@ -0,0 +1 @@ +export const isFunction = value => typeof value === 'function'; diff --git a/src/js/utils/isInt.js b/src/js/utils/isInt.js new file mode 100644 index 00000000..3a2841b2 --- /dev/null +++ b/src/js/utils/isInt.js @@ -0,0 +1,3 @@ +import { isNumber } from './isNumber'; +export const isInt = value => + isNumber(value) && isFinite(value) && Math.floor(value) === value; diff --git a/src/js/utils/isNode.js b/src/js/utils/isNode.js new file mode 100644 index 00000000..7aa3c1b2 --- /dev/null +++ b/src/js/utils/isNode.js @@ -0,0 +1 @@ +export const isNode = value => value instanceof HTMLElement; diff --git a/src/js/utils/isNull.js b/src/js/utils/isNull.js new file mode 100644 index 00000000..9899dd40 --- /dev/null +++ b/src/js/utils/isNull.js @@ -0,0 +1 @@ +export const isNull = value => value === null; diff --git a/src/js/utils/isNumber.js b/src/js/utils/isNumber.js new file mode 100644 index 00000000..2f814cb1 --- /dev/null +++ b/src/js/utils/isNumber.js @@ -0,0 +1 @@ +export const isNumber = value => typeof value === 'number'; diff --git a/src/js/utils/isObject.js b/src/js/utils/isObject.js new file mode 100644 index 00000000..80b9301f --- /dev/null +++ b/src/js/utils/isObject.js @@ -0,0 +1 @@ +export const isObject = value => typeof value === 'object' && value !== null; \ No newline at end of file diff --git a/src/js/utils/isString.js b/src/js/utils/isString.js new file mode 100644 index 00000000..2e3a1892 --- /dev/null +++ b/src/js/utils/isString.js @@ -0,0 +1 @@ +export const isString = value => typeof value === 'string'; diff --git a/src/js/utils/leftPad.js b/src/js/utils/leftPad.js new file mode 100644 index 00000000..9ba8d875 --- /dev/null +++ b/src/js/utils/leftPad.js @@ -0,0 +1,2 @@ +export const leftPad = (value, padding = '') => + (padding + value).slice(-padding.length); diff --git a/src/js/utils/limit.js b/src/js/utils/limit.js new file mode 100644 index 00000000..73e84d66 --- /dev/null +++ b/src/js/utils/limit.js @@ -0,0 +1 @@ +export const limit = (value, min, max) => Math.max(Math.min(max, value), min); diff --git a/src/js/utils/loadImage.js b/src/js/utils/loadImage.js new file mode 100644 index 00000000..7fd3ceb2 --- /dev/null +++ b/src/js/utils/loadImage.js @@ -0,0 +1,11 @@ +export const loadImage = (url) => + new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + resolve(img); + }; + img.onerror = e => { + reject(e); + }; + img.src = url; + }); diff --git a/src/js/utils/lowerCaseFirstLetter.js b/src/js/utils/lowerCaseFirstLetter.js new file mode 100644 index 00000000..037c0608 --- /dev/null +++ b/src/js/utils/lowerCaseFirstLetter.js @@ -0,0 +1,2 @@ +export const lowerCaseFirstLetter = string => + string.charAt(0).toLowerCase() + string.slice(1); diff --git a/src/js/utils/percentageArc.js b/src/js/utils/percentageArc.js new file mode 100644 index 00000000..4ecfaf2d --- /dev/null +++ b/src/js/utils/percentageArc.js @@ -0,0 +1,19 @@ +import { describeArc } from './describeArc'; + +export const percentageArc = (x, y, radius, from, to) => { + let arcSweep = 1; + if (to > from && to - from <= 0.5) { + arcSweep = 0; + } + if (from > to && from - to >= 0.5) { + arcSweep = 0; + } + return describeArc( + x, + y, + radius, + Math.min(0.9999, from) * 360, + Math.min(0.9999, to) * 360, + arcSweep + ); +}; diff --git a/src/js/utils/polarToCartesian.js b/src/js/utils/polarToCartesian.js new file mode 100644 index 00000000..86351d9f --- /dev/null +++ b/src/js/utils/polarToCartesian.js @@ -0,0 +1,7 @@ +export const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => { + const angleInRadians = (angleInDegrees % 360 - 90) * Math.PI / 180.0; + return { + x: centerX + radius * Math.cos(angleInRadians), + y: centerY + radius * Math.sin(angleInRadians) + }; +}; diff --git a/src/js/utils/renameFile.js b/src/js/utils/renameFile.js new file mode 100644 index 00000000..3adf98d7 --- /dev/null +++ b/src/js/utils/renameFile.js @@ -0,0 +1,6 @@ +export const renameFile = (file, name) => { + const renamedFile = file.slice(0, file.size, file.type); + renamedFile.lastModifiedDate = file.lastModifiedDate; + renamedFile.name = name; + return renamedFile; +}; diff --git a/src/js/utils/replaceInString.js b/src/js/utils/replaceInString.js new file mode 100644 index 00000000..af7282d7 --- /dev/null +++ b/src/js/utils/replaceInString.js @@ -0,0 +1,7 @@ +/** + * Replaces placeholders in given string with replacements + * @param string - "Foo {bar}"" + * @param replacements - { "bar": 10 } + */ +export const replaceInString = (string, replacements) => + string.replace(/(?:{([a-zA-Z]+)})/g, (match, group) => replacements[group]); diff --git a/src/js/utils/resetFileInput.js b/src/js/utils/resetFileInput.js new file mode 100644 index 00000000..8ab1ba65 --- /dev/null +++ b/src/js/utils/resetFileInput.js @@ -0,0 +1,30 @@ +import { createElement } from './createElement'; + +export const resetFileInput = input => { + // no value, no need to reset + if (!input || input.value === '') { + return; + } + + try { + // for modern browsers + input.value = ''; + } catch (err) {} + + // for IE10 + if (input.value) { + // quickly append input to temp form and reset form + const form = createElement('form'); + const parentNode = input.parentNode; + const ref = input.nextSibling; + form.appendChild(input); + form.reset(); + + // re-inject input where it originally was + if (ref) { + parentNode.insertBefore(input, ref); + } else { + parentNode.appendChild(input); + } + } +}; diff --git a/src/js/utils/sendRequest.js b/src/js/utils/sendRequest.js new file mode 100644 index 00000000..1bb1b011 --- /dev/null +++ b/src/js/utils/sendRequest.js @@ -0,0 +1,129 @@ +import { isInt } from './isInt'; + +const isGet = method => /GET|HEAD/.test(method); + +export 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 => { + xhr.setRequestHeader(key, options.headers[key]); + }); + + // 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; +}; \ No newline at end of file diff --git a/src/js/utils/text.js b/src/js/utils/text.js new file mode 100644 index 00000000..e322f097 --- /dev/null +++ b/src/js/utils/text.js @@ -0,0 +1,9 @@ +export const text = (node, value) => { + let textNode = node.childNodes[0]; + if (!textNode) { + textNode = document.createTextNode(value); + node.appendChild(textNode); + } else if (value !== textNode.nodeValue) { + textNode.nodeValue = value; + } +}; diff --git a/src/js/utils/toArray.js b/src/js/utils/toArray.js new file mode 100644 index 00000000..dd76942d --- /dev/null +++ b/src/js/utils/toArray.js @@ -0,0 +1,17 @@ +import { isArray } from './isArray'; +import { isEmpty } from './isEmpty'; +import { trim } from './trim'; +import { toString } from './toString'; + +export const toArray = (value, splitter = ',') => { + if (isEmpty(value)) { + return []; + } + if (isArray(value)) { + return value; + } + return toString(value) + .split(splitter) + .map(trim) + .filter(str => str.length); +}; diff --git a/src/js/utils/toBoolean.js b/src/js/utils/toBoolean.js new file mode 100644 index 00000000..d7f1c227 --- /dev/null +++ b/src/js/utils/toBoolean.js @@ -0,0 +1,2 @@ +import { isBoolean } from './isBoolean'; +export const toBoolean = value => (isBoolean(value) ? value : value === 'true'); diff --git a/src/js/utils/toBytes.js b/src/js/utils/toBytes.js new file mode 100644 index 00000000..07e4dcaa --- /dev/null +++ b/src/js/utils/toBytes.js @@ -0,0 +1,27 @@ +import { toString } from './toString'; +import { isInt } from './isInt'; +import { toInt } from './toInt'; + +export const toBytes = value => { + // 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) * 1000 * 1000; + } + + // if is value in kilobytes + if (/KB/i.test(naturalFileSize)) { + naturalFileSize = naturalFileSize.replace(/KB$i/, '').trim(); + return toInt(naturalFileSize) * 1000; + } + + return toInt(naturalFileSize); +}; diff --git a/src/js/utils/toCamels.js b/src/js/utils/toCamels.js new file mode 100644 index 00000000..22ca6617 --- /dev/null +++ b/src/js/utils/toCamels.js @@ -0,0 +1,4 @@ +export const toCamels = (string, separator = '-') => + string.replace(new RegExp(`${separator}.`, 'g'), sub => + sub.charAt(1).toUpperCase() + ); diff --git a/src/js/utils/toFloat.js b/src/js/utils/toFloat.js new file mode 100644 index 00000000..a90b3e31 --- /dev/null +++ b/src/js/utils/toFloat.js @@ -0,0 +1,2 @@ +import { toNumber } from './toNumber'; +export const toFloat = value => parseFloat(toNumber(value)); diff --git a/src/js/utils/toFunctionReference.js b/src/js/utils/toFunctionReference.js new file mode 100644 index 00000000..98f2d987 --- /dev/null +++ b/src/js/utils/toFunctionReference.js @@ -0,0 +1,12 @@ +export 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; +}; diff --git a/src/js/utils/toInt.js b/src/js/utils/toInt.js new file mode 100644 index 00000000..71021a01 --- /dev/null +++ b/src/js/utils/toInt.js @@ -0,0 +1,2 @@ +import { toNumber } from './toNumber'; +export const toInt = value => parseInt(toNumber(value), 10); diff --git a/src/js/utils/toNaturalFileSize.js b/src/js/utils/toNaturalFileSize.js new file mode 100644 index 00000000..9be4daab --- /dev/null +++ b/src/js/utils/toNaturalFileSize.js @@ -0,0 +1,34 @@ +export const toNaturalFileSize = (bytes, decimalSeparator = '.') => { + // nope, no negative byte sizes + bytes = Math.round(Math.abs(bytes)); + + // just bytes + if (bytes < 1000) { + return `${bytes} bytes`; + } + + // kilobytes + if (bytes < MB) { + return `${Math.floor(bytes / KB)} KB`; + } + + // megabytes + if (bytes < GB) { + return `${removeDecimalsWhenZero(bytes / MB, 1, decimalSeparator)} MB`; + } + + // gigabytes + return `${removeDecimalsWhenZero(bytes / GB, 2, decimalSeparator)} GB`; +}; + +const KB = 1000; +const MB = 1000000; +const GB = 1000000000; + +const removeDecimalsWhenZero = (value, decimalCount, separator) => { + return value + .toFixed(decimalCount) + .split('.') + .filter(part => part !== '0') + .join(separator); +}; diff --git a/src/js/utils/toNumber.js b/src/js/utils/toNumber.js new file mode 100644 index 00000000..11389eaf --- /dev/null +++ b/src/js/utils/toNumber.js @@ -0,0 +1,7 @@ +import { isNumber } from './isNumber'; +import { isString } from './isString'; +import { toString } from './toString'; +export const toNumber = value => + isNumber(value) + ? value + : isString(value) ? toString(value).replace(/[a-z]+/gi, '') : 0; diff --git a/src/js/utils/toPercentage.js b/src/js/utils/toPercentage.js new file mode 100644 index 00000000..fe8314c2 --- /dev/null +++ b/src/js/utils/toPercentage.js @@ -0,0 +1 @@ +export const toPercentage = value => Math.round(value * 100); diff --git a/src/js/utils/toString.js b/src/js/utils/toString.js new file mode 100644 index 00000000..b79eab4c --- /dev/null +++ b/src/js/utils/toString.js @@ -0,0 +1 @@ +export const toString = value => '' + value; diff --git a/src/js/utils/trim.js b/src/js/utils/trim.js new file mode 100644 index 00000000..d60642ee --- /dev/null +++ b/src/js/utils/trim.js @@ -0,0 +1 @@ +export const trim = str => str.trim();