diff --git a/.pylintrc b/.pylintrc
index 39e142048d..190f88bab4 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -18,6 +18,7 @@ ignore-patterns=
ignore-paths=^dash/dcc/.*$,
^dash/html/.*$,
^dash/dash_table/.*$,
+ ^dash/dash_uploader/.*$,
^.*/node_modules/.*$
# Python code to execute, usually for sys.path manipulation such as
diff --git a/MANIFEST.in b/MANIFEST.in
index 457204bf7d..b8d4910df3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -8,6 +8,7 @@ include dash/deps/*.map
include dash/dcc/*
include dash/html/*
include dash/dash_table/*
+include dash/dash_uploader/_build/*
include dash/dash-renderer/build/*.js
include dash/dash-renderer/build/*.map
include dash/labextension/dist/dash-jupyterlab.tgz
diff --git a/components/dash-uploader/.babelrc b/components/dash-uploader/.babelrc
new file mode 100644
index 0000000000..f41ba72959
--- /dev/null
+++ b/components/dash-uploader/.babelrc
@@ -0,0 +1,44 @@
+{
+ "presets": [
+ "@babel/preset-env",
+ "@babel/preset-react"
+ ],
+ "env": {
+ "production": {
+ "plugins": [
+ "@babel/plugin-proposal-object-rest-spread",
+ "styled-jsx/babel",
+ [
+ "@babel/plugin-proposal-class-properties",
+ {
+ "loose": true
+ }
+ ]
+ ]
+ },
+ "development": {
+ "plugins": [
+ "@babel/plugin-proposal-object-rest-spread",
+ "styled-jsx/babel",
+ [
+ "@babel/plugin-proposal-class-properties",
+ {
+ "loose": true
+ }
+ ]
+ ]
+ },
+ "test": {
+ "plugins": [
+ "@babel/plugin-proposal-object-rest-spread",
+ "styled-jsx/babel-test",
+ [
+ "@babel/plugin-proposal-class-properties",
+ {
+ "loose": true
+ }
+ ]
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/dash-uploader/.eslintignore b/components/dash-uploader/.eslintignore
new file mode 100644
index 0000000000..4a8b0edf10
--- /dev/null
+++ b/components/dash-uploader/.eslintignore
@@ -0,0 +1,2 @@
+*.css
+registerServiceWorker.js
\ No newline at end of file
diff --git a/components/dash-uploader/.eslintrc b/components/dash-uploader/.eslintrc
new file mode 100644
index 0000000000..208c8d7151
--- /dev/null
+++ b/components/dash-uploader/.eslintrc
@@ -0,0 +1,121 @@
+{
+ "extends": ["eslint:recommended", "prettier"],
+ "parser": "babel-eslint",
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module",
+ "ecmaFeatures": {
+ "arrowFunctions": true,
+ "blockBindings": true,
+ "classes": true,
+ "defaultParams": true,
+ "destructuring": true,
+ "forOf": true,
+ "generators": true,
+ "modules": true,
+ "templateStrings": true,
+ "jsx": true
+ }
+ },
+ "env": {
+ "browser": true,
+ "es6": true,
+ "jasmine": true,
+ "jest": true,
+ "node": true
+ },
+ "globals": {
+ "jest": true
+ },
+ "plugins": [
+ "react",
+ "import"
+ ],
+ "rules": {
+ "accessor-pairs": ["error"],
+ "block-scoped-var": ["error"],
+ "consistent-return": ["error"],
+ "curly": ["error", "all"],
+ "default-case": ["error"],
+ "dot-location": ["off"],
+ "dot-notation": ["error"],
+ "eqeqeq": ["error"],
+ "guard-for-in": ["off"],
+ "import/named": ["off"],
+ "import/no-duplicates": ["error"],
+ "import/no-named-as-default": ["error"],
+ "new-cap": ["error"],
+ "no-alert": [1],
+ "no-caller": ["error"],
+ "no-case-declarations": ["error"],
+ "no-console": ["off"],
+ "no-div-regex": ["error"],
+ "no-dupe-keys": ["error"],
+ "no-else-return": ["error"],
+ "no-empty-pattern": ["error"],
+ "no-eq-null": ["error"],
+ "no-eval": ["error"],
+ "no-extend-native": ["error"],
+ "no-extra-bind": ["error"],
+ "no-extra-boolean-cast": ["error"],
+ "no-inline-comments": ["error"],
+ "no-implicit-coercion": ["error"],
+ "no-implied-eval": ["error"],
+ "no-inner-declarations": ["off"],
+ "no-invalid-this": ["error"],
+ "no-iterator": ["error"],
+ "no-labels": ["error"],
+ "no-lone-blocks": ["error"],
+ "no-loop-func": ["error"],
+ "no-multi-str": ["error"],
+ "no-native-reassign": ["error"],
+ "no-new": ["error"],
+ "no-new-func": ["error"],
+ "no-new-wrappers": ["error"],
+ "no-param-reassign": ["error"],
+ "no-process-env": ["warn"],
+ "no-proto": ["error"],
+ "no-redeclare": ["error"],
+ "no-return-assign": ["error"],
+ "no-script-url": ["error"],
+ "no-self-compare": ["error"],
+ "no-sequences": ["error"],
+ "no-shadow": ["off"],
+ "no-throw-literal": ["error"],
+ "no-undefined": ["error"],
+ "no-unused-expressions": ["error"],
+ "no-use-before-define": ["error", "nofunc"],
+ "no-useless-call": ["error"],
+ "no-useless-concat": ["error"],
+ "no-with": ["error"],
+ "prefer-const": ["error"],
+ "radix": ["error"],
+ "react/jsx-no-duplicate-props": ["error"],
+ "react/jsx-no-undef": ["error"],
+ "react/jsx-uses-react": ["error"],
+ "react/jsx-uses-vars": ["error"],
+ "react/no-did-update-set-state": ["error"],
+ "react/no-direct-mutation-state": ["error"],
+ "react/no-is-mounted": ["error"],
+ "react/no-unknown-property": ["error"],
+ "react/prefer-es6-class": ["error", "always"],
+ "react/prop-types": "error",
+ "valid-jsdoc": ["off"],
+ "yoda": ["error"],
+ "spaced-comment": ["error", "always", {
+ "block": {
+ "exceptions": ["*"]
+ }
+ }],
+ "no-unused-vars": ["error", {
+ "args": "after-used",
+ "argsIgnorePattern": "^_",
+ "caughtErrorsIgnorePattern": "^e$"
+ }],
+ "no-magic-numbers": ["error", {
+ "ignoreArrayIndexes": true,
+ "ignore": [-1, 0, 1, 2, 3, 100, 10, 0.5]
+ }],
+ "no-underscore-dangle": ["off"]
+ }
+}
diff --git a/components/dash-uploader/.gitignore b/components/dash-uploader/.gitignore
new file mode 100644
index 0000000000..9ef3e9d907
--- /dev/null
+++ b/components/dash-uploader/.gitignore
@@ -0,0 +1,285 @@
+# Created by .ignore support plugin (hsz.mobi)
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# next.js build output
+.next
+
+# nuxt.js build output
+.nuxt
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+### SublimeText template
+# Cache files for Sublime Text
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+
+# Workspace files are user-specific
+*.sublime-workspace
+
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
+# *.sublime-project
+
+# SFTP configuration file
+sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+Package Control.merged-ca-bundle
+Package Control.user-ca-bundle
+oscrypto-ca-bundle.crt
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
+
+# automatically created temporary files
+inst/
+
+.vscode
+
+# Do not track the build files
+dash_uploader/_build
+
+# Do not track local /tmp
+tmp/
\ No newline at end of file
diff --git a/components/dash-uploader/.npmignore b/components/dash-uploader/.npmignore
new file mode 100644
index 0000000000..69227a1441
--- /dev/null
+++ b/components/dash-uploader/.npmignore
@@ -0,0 +1,25 @@
+# dependencies
+/node_modules
+
+# testing
+/coverage
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Development folders and files
+public
+src
+scripts
+config
+.travis.yml
+CHANGELOG.md
+README.md
diff --git a/components/dash-uploader/.prettierrc b/components/dash-uploader/.prettierrc
new file mode 100644
index 0000000000..eb81a49a94
--- /dev/null
+++ b/components/dash-uploader/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "tabWidth": 4,
+ "singleQuote": true,
+ "bracketSpacing": false,
+ "trailingComma": "es5"
+}
diff --git a/components/dash-uploader/.pylintrc b/components/dash-uploader/.pylintrc
new file mode 100644
index 0000000000..7540ce0b51
--- /dev/null
+++ b/components/dash-uploader/.pylintrc
@@ -0,0 +1,556 @@
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code
+extension-pkg-whitelist=
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Add files or directories matching the regex patterns to the blacklist. The
+# regex matches against base names, not paths.
+ignore-patterns=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Use multiple processes to speed up Pylint.
+jobs=1
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Specify a configuration file.
+#rcfile=
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages
+suggestion-mode=yes
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=print-statement,
+ parameter-unpacking,
+ unpacking-in-except,
+ old-raise-syntax,
+ backtick,
+ long-suffix,
+ old-ne-operator,
+ old-octal-literal,
+ import-star-module-level,
+ non-ascii-bytes-literal,
+ raw-checker-failed,
+ bad-inline-option,
+ locally-disabled,
+ locally-enabled,
+ file-ignored,
+ suppressed-message,
+ useless-suppression,
+ deprecated-pragma,
+ apply-builtin,
+ basestring-builtin,
+ buffer-builtin,
+ cmp-builtin,
+ coerce-builtin,
+ execfile-builtin,
+ file-builtin,
+ long-builtin,
+ raw_input-builtin,
+ reduce-builtin,
+ standarderror-builtin,
+ unicode-builtin,
+ xrange-builtin,
+ coerce-method,
+ delslice-method,
+ getslice-method,
+ setslice-method,
+ no-absolute-import,
+ old-division,
+ dict-iter-method,
+ dict-view-method,
+ next-method-called,
+ metaclass-assignment,
+ indexing-exception,
+ raising-string,
+ reload-builtin,
+ oct-method,
+ hex-method,
+ nonzero-method,
+ cmp-method,
+ input-builtin,
+ round-builtin,
+ intern-builtin,
+ unichr-builtin,
+ map-builtin-not-iterating,
+ zip-builtin-not-iterating,
+ range-builtin-not-iterating,
+ filter-builtin-not-iterating,
+ using-cmp-argument,
+ eq-without-hash,
+ div-method,
+ idiv-method,
+ rdiv-method,
+ exception-message-attribute,
+ invalid-str-codec,
+ sys-max-int,
+ bad-python3-import,
+ deprecated-string-function,
+ deprecated-str-translate-call,
+ deprecated-itertools-function,
+ deprecated-types-field,
+ next-method-defined,
+ dict-items-not-iterating,
+ dict-keys-not-iterating,
+ dict-values-not-iterating,
+ no-member,
+ missing-docstring,
+ invalid-name,
+ redefined-builtin,
+ wrong-import-order,
+ too-many-arguments,
+ too-many-locals,
+ consider-using-enumerate,
+ len-as-condition,
+ too-many-branches,
+ too-many-statements,
+ blacklisted-name,
+ line-too-long,
+ bare-except,
+ duplicate-code,
+ too-many-function-args,
+ attribute-defined-outside-init,
+ broad-except
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=c-extension-no-member
+
+
+[REPORTS]
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio).You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Activate the evaluation score.
+score=yes
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=optparse.Values,sys.exit
+
+
+[BASIC]
+
+# Naming style matching correct argument names
+argument-naming-style=snake_case
+
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style
+#argument-rgx=
+
+# Naming style matching correct attribute names
+attr-naming-style=snake_case
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style
+#attr-rgx=
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,
+ bar,
+ baz,
+ toto,
+ tutu,
+ tata
+
+# Naming style matching correct class attribute names
+class-attribute-naming-style=any
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style
+#class-attribute-rgx=
+
+# Naming style matching correct class names
+class-naming-style=PascalCase
+
+# Regular expression matching correct class names. Overrides class-naming-style
+#class-rgx=
+
+# Naming style matching correct constant names
+const-naming-style=UPPER_CASE
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style
+#const-rgx=
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming style matching correct function names
+function-naming-style=snake_case
+
+# Regular expression matching correct function names. Overrides function-
+# naming-style
+#function-rgx=
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,
+ j,
+ k,
+ ex,
+ Run,
+ _
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# Naming style matching correct inline iteration names
+inlinevar-naming-style=any
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style
+#inlinevar-rgx=
+
+# Naming style matching correct method names
+method-naming-style=snake_case
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style
+#method-rgx=
+
+# Naming style matching correct module names
+module-naming-style=snake_case
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style
+#module-rgx=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+property-classes=abc.abstractproperty
+
+# Naming style matching correct variable names
+variable-naming-style=snake_case
+
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style
+#variable-rgx=
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )??$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+# Maximum number of characters on a single line.
+max-line-length=100
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# List of optional constructs for which whitespace checking is disabled. `dict-
+# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
+# `trailing-comma` allows a space between comma and closing bracket: (a, ).
+# `empty-line` allows space-only lines.
+no-space-check=trailing-comma,
+ dict-separator
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,
+ XXX,
+
+
+[SIMILARITIES]
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[SPELLING]
+
+# Limits count of emitted suggestions for spelling mistakes
+max-spelling-suggestions=4
+
+# Spelling dictionary name. Available dictionaries: none. To make it working
+# install python-enchant package.
+spelling-dict=
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to indicated private dictionary in
+# --spelling-private-dict-file option instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis. It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+ _cb
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+ __new__,
+ setUp
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+ _fields,
+ _replace,
+ _source,
+ _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in a if statement
+max-bool-expr=5
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[IMPORTS]
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=optparse,tkinter.tix
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/components/dash-uploader/LICENSE.txt b/components/dash-uploader/LICENSE.txt
new file mode 100644
index 0000000000..873b87bd4d
--- /dev/null
+++ b/components/dash-uploader/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Niko Pasanen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/components/dash-uploader/MANIFEST.in b/components/dash-uploader/MANIFEST.in
new file mode 100644
index 0000000000..ef359bea06
--- /dev/null
+++ b/components/dash-uploader/MANIFEST.in
@@ -0,0 +1,8 @@
+include dash_uploader/_build/dash_uploader.min.js
+include dash_uploader/_build/dash_uploader.min.js.map
+include dash_uploader/_build/metadata.json
+include dash_uploader/_build/package-info.json
+include README.md
+include docs/README-PyPi.md
+include LICENSE
+include package.json
diff --git a/components/dash-uploader/README.md b/components/dash-uploader/README.md
new file mode 100644
index 0000000000..29b042e316
--- /dev/null
+++ b/components/dash-uploader/README.md
@@ -0,0 +1,165 @@
+   
+
+
+
+# 📤 dash-uploader
+
+The alternative upload component for [Dash](https://dash.plotly.com/) applications.
+
+
+## Table of contents
+- [Short summary](#short-summary)
+- [dash-uploader vs. dcc.Upload](#dash-uploader-vs-dccupload)
+- [Installing](#installing)
+- [Quickstart](#quickstart)
+ - [Simple example](#simple-example)
+ - [Example with callback](#example-with-callback-and-other-options)
+- [Contributing](#contributing)
+- [Documentation](#documentation)
+- [Changelog](#changelog)
+- [Credits](#credits)
+
+## Short summary
+ 💾 Data file size has no limits. (Except the hard disk size)
+ ☎ Call easily a callback after uploading is finished.
+ 📤 Upload files using [resumable.js](https://github.com/23/resumable.js)
+ 📦 All JS and CSS bundled with the package. No need for network calls for CSS/JS.
+ ✅ Works with Dash 1.1.0.+ & Python 3.6+. (Possibly with other versions, too)
+
+
+### dash-uploader vs. [dcc.Upload](https://dash.plotly.com/dash-core-components/upload)
+
+
+
+
+
+| | dash-uploader | [dcc.Upload](https://dash.plotly.com/dash-core-components/upload) |
+| --------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| Underlying technology | [resumable.js](http://www.resumablejs.com/) | HTML5 API |
+| File size | Unlimited | max ~150-200Mb ([link](https://community.plotly.com/t/dash-upload-component-decoding-large-files/8033/11)) |
+| Uploads to | Hard disk (server side) | First to browser memory (user side) Then, to server using callbacks. |
+| Data type | Uploaded as file; no need to parse at server side. | Uploaded as byte64 encoded string -> Needs parsing |
+| See upload progress? | Progressbar out of the box | No upload indicators out of the box. Generic loading indicator possible. [Progressbar not possible](https://community.plotly.com/t/upload-after-confirmation-and-progress-bar/7172). |
+
+# Installing
+```
+pip install dash-uploader
+```
+
+# Quickstart
+
+Full documentation [here](docs/dash-uploader.md)
+>⚠️**Security note**: The Upload component allows POST requests and uploads of arbitrary files to the server harddisk and one should take this into account (with user token checking etc.) if used as part of a public website! For this you can utilize the `http_request_handler` argument of the [du.configure_upload](https://github.com/np-8/dash-uploader/blob/master/docs/dash-uploader.md#duconfigure_upload). (New in version 0.5.0)
+
+## Simple example
+
+```python
+import dash
+import dash_html_components as html
+import dash_uploader as du
+
+app = dash.Dash(__name__)
+
+# 1) configure the upload folder
+du.configure_upload(app, r"C:\tmp\Uploads")
+
+# 2) Use the Upload component
+app.layout = html.Div([
+ du.Upload(),
+])
+
+if __name__ == '__main__':
+ app.run_server(debug=True)
+
+```
+
+## Example with callback (and other options)
+- **New in version 0.3.0:** New short callback syntax using `@du.callback`.
+- **New in version 0.2.0:** The configure_upload accepts additional parameter `use_upload_id`, which is a boolean flag (Defaults to True). When True, the uploaded files are put into subfolders `/`. This way different users can be forced to upload to different folders.
+
+```python
+from pathlib import Path
+import uuid
+
+import dash_uploader as du
+import dash
+import dash_html_components as html
+from dash.dependencies import Input, Output, State
+
+app = dash.Dash(__name__)
+
+UPLOAD_FOLDER_ROOT = r"C:\tmp\Uploads"
+du.configure_upload(app, UPLOAD_FOLDER_ROOT)
+
+def get_upload_component(id):
+ return du.Upload(
+ id=id,
+ max_file_size=1800, # 1800 Mb
+ filetypes=['csv', 'zip'],
+ upload_id=uuid.uuid1(), # Unique session id
+ )
+
+
+def get_app_layout():
+
+ return html.Div(
+ [
+ html.H1('Demo'),
+ html.Div(
+ [
+ get_upload_component(id='dash-uploader'),
+ html.Div(id='callback-output'),
+ ],
+ style={ # wrapper div style
+ 'textAlign': 'center',
+ 'width': '600px',
+ 'padding': '10px',
+ 'display': 'inline-block'
+ }),
+ ],
+ style={
+ 'textAlign': 'center',
+ },
+ )
+
+
+# get_app_layout is a function
+# This way we can use unique session id's as upload_id's
+app.layout = get_app_layout
+
+
+@du.callback(
+ output=Output('callback-output', 'children'),
+ id='dash-uploader',
+)
+def get_a_list(filenames):
+ return html.Ul([html.Li(filenames)])
+
+
+if __name__ == '__main__':
+ app.run_server(debug=True)
+
+```
+
+
+## Contributing
+
+
+| What? | How? |
+| :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 🐞 Found a bug? | 🎟 File an Issue |
+| 🙋♂️ Need help? | ❔ Ask a question on StackOverflow 📧 Use this thread on community.plotly.com |
+| 💡 Want to submit a feature request? | 🎭 Discuss about it on community.plotly.com 🎫 File an Issue (feature request) |
+| 🧙 Want to write code? | 🔥 Here's how you get started! |
+## Documentation
+- See: [Documentation](docs/dash-uploader.md) and [Developer documentation](docs/CONTRIBUTING.md) .
+
+## Changelog
+
+- See: [Changelog](./docs/CHANGELOG.md)
+## Credits
+- History: This package is based on the React 16 compatible version [dash-resumable-upload](https://github.com/westonkjones/dash-resumable-upload) (0.0.4) by [Weston Jones](https://github.com/westonkjones/) which in turn is based on [dash-resumable-upload](https://github.com/rmarren1/dash-resumable-upload) (0.0.3) by [Ryan Marren](https://github.com/rmarren1)
+- The package boilerplate is taken from the [dash-component-boilerplate](https://github.com/plotly/dash-component-boilerplate).
+- The uploading JS function utilizes the [resumable.js](http://www.resumablejs.com/) (1.1.0).
+- The JS component is created using [React](https://github.com/facebook/react) (17.0.x)
+- The CSS styling is mostly from [Bootstrap](https://getbootstrap.com/) 4.
diff --git a/components/dash-uploader/_validate_init.py b/components/dash-uploader/_validate_init.py
new file mode 100644
index 0000000000..f98bccdde9
--- /dev/null
+++ b/components/dash-uploader/_validate_init.py
@@ -0,0 +1,66 @@
+"""
+DO NOT MODIFY
+This file is used to validate your publish settings.
+"""
+from __future__ import print_function
+
+import os
+import sys
+import importlib
+
+
+components_package = "dash_uploader"
+
+components_lib = importlib.import_module(components_package)
+
+missing_dist_msg = "Warning {} was not found in `{}.__init__.{}`!!!"
+missing_manifest_msg = """
+Warning {} was not found in `MANIFEST.in`!
+It will not be included in the build!
+"""
+
+with open("MANIFEST.in", "r") as f:
+ manifest = f.read()
+
+
+def check_dist(dist, filename):
+ # Support the dev bundle.
+ if filename.endswith("dev.js"):
+ return True
+
+ return any(
+ filename in x
+ for d in dist
+ for x in (
+ [d.get("relative_package_path")]
+ if not isinstance(d.get("relative_package_path"), list)
+ else d.get("relative_package_path")
+ )
+ )
+
+
+def check_manifest(filename):
+ return filename in manifest
+
+
+def check_file(dist, filename):
+ if not check_dist(dist, filename):
+ print(
+ missing_dist_msg.format(filename, components_package, "_js_dist"),
+ file=sys.stderr,
+ )
+ if not check_manifest(filename):
+ print(missing_manifest_msg.format(filename), file=sys.stderr)
+
+
+for cur, _, files in os.walk(components_package):
+ for f in files:
+
+ if f.endswith("js"):
+ # noinspection PyProtectedMember
+ check_file(components_lib._js_dist, f)
+ elif f.endswith("css"):
+ # noinspection PyProtectedMember
+ check_file(components_lib._css_dist, f)
+ elif not f.endswith("py"):
+ check_manifest(f)
diff --git a/components/dash-uploader/assets/style.css b/components/dash-uploader/assets/style.css
new file mode 100644
index 0000000000..b3960fd0e0
--- /dev/null
+++ b/components/dash-uploader/assets/style.css
@@ -0,0 +1,417 @@
+/* From: https://codepen.io/chriddyp/pen/bWLwgP.css */
+
+/* Table of contents
+––––––––––––––––––––––––––––––––––––––––––––––––––
+- Plotly.js
+- Grid
+- Base Styles
+- Typography
+- Links
+- Buttons
+- Forms
+- Lists
+- Code
+- Tables
+- Spacing
+- Utilities
+- Clearing
+- Media Queries
+*/
+
+/* PLotly.js
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+/* plotly.js's modebar's z-index is 1001 by default
+ * https://github.com/plotly/plotly.js/blob/7e4d8ab164258f6bd48be56589dacd9bdd7fded2/src/css/_modebar.scss#L5
+ * In case a dropdown is above the graph, the dropdown's options
+ * will be rendered below the modebar
+ * Increase the select option's z-index
+ */
+
+/* This was actually not quite right -
+ dropdowns were overlapping each other (edited October 26)
+
+.Select {
+ z-index: 1002;
+}*/
+
+
+/* Grid
+–––––––––––––––––––––––––––––––––––––––––––––––––– */
+.container {
+ position: relative;
+ width: 100%;
+ max-width: 960px;
+ margin: 0 auto;
+ padding: 0 20px;
+ box-sizing: border-box; }
+ .column,
+ .columns {
+ width: 100%;
+ float: left;
+ box-sizing: border-box; }
+
+ /* For devices larger than 400px */
+ @media (min-width: 400px) {
+ .container {
+ width: 85%;
+ padding: 0; }
+ }
+
+ /* For devices larger than 550px */
+ @media (min-width: 550px) {
+ .container {
+ width: 80%; }
+ .column,
+ .columns {
+ margin-left: 4%; }
+ .column:first-child,
+ .columns:first-child {
+ margin-left: 0; }
+
+ .one.column,
+ .one.columns { width: 4.66666666667%; }
+ .two.columns { width: 13.3333333333%; }
+ .three.columns { width: 22%; }
+ .four.columns { width: 30.6666666667%; }
+ .five.columns { width: 39.3333333333%; }
+ .six.columns { width: 48%; }
+ .seven.columns { width: 56.6666666667%; }
+ .eight.columns { width: 65.3333333333%; }
+ .nine.columns { width: 74.0%; }
+ .ten.columns { width: 82.6666666667%; }
+ .eleven.columns { width: 91.3333333333%; }
+ .twelve.columns { width: 100%; margin-left: 0; }
+
+ .one-third.column { width: 30.6666666667%; }
+ .two-thirds.column { width: 65.3333333333%; }
+
+ .one-half.column { width: 48%; }
+
+ /* Offsets */
+ .offset-by-one.column,
+ .offset-by-one.columns { margin-left: 8.66666666667%; }
+ .offset-by-two.column,
+ .offset-by-two.columns { margin-left: 17.3333333333%; }
+ .offset-by-three.column,
+ .offset-by-three.columns { margin-left: 26%; }
+ .offset-by-four.column,
+ .offset-by-four.columns { margin-left: 34.6666666667%; }
+ .offset-by-five.column,
+ .offset-by-five.columns { margin-left: 43.3333333333%; }
+ .offset-by-six.column,
+ .offset-by-six.columns { margin-left: 52%; }
+ .offset-by-seven.column,
+ .offset-by-seven.columns { margin-left: 60.6666666667%; }
+ .offset-by-eight.column,
+ .offset-by-eight.columns { margin-left: 69.3333333333%; }
+ .offset-by-nine.column,
+ .offset-by-nine.columns { margin-left: 78.0%; }
+ .offset-by-ten.column,
+ .offset-by-ten.columns { margin-left: 86.6666666667%; }
+ .offset-by-eleven.column,
+ .offset-by-eleven.columns { margin-left: 95.3333333333%; }
+
+ .offset-by-one-third.column,
+ .offset-by-one-third.columns { margin-left: 34.6666666667%; }
+ .offset-by-two-thirds.column,
+ .offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
+
+ .offset-by-one-half.column,
+ .offset-by-one-half.columns { margin-left: 52%; }
+
+ }
+
+
+ /* Base Styles
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ /* NOTE
+ html is set to 62.5% so that all the REM measurements throughout Skeleton
+ are based on 10px sizing. So basically 1.5rem = 15px :) */
+ html {
+ font-size: 62.5%; }
+ body {
+ font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
+ line-height: 1.6;
+ font-weight: 400;
+ font-family: "Open Sans", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color: rgb(50, 50, 50); }
+
+
+ /* Typography
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ h1, h2, h3, h4, h5, h6 {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-weight: 300; }
+ h1 { font-size: 4.5rem; line-height: 1.2; letter-spacing: -.1rem; margin-bottom: 2rem; }
+ h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; margin-bottom: 1.8rem; margin-top: 1.8rem;}
+ h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; margin-bottom: 1.5rem; margin-top: 1.5rem;}
+ h4 { font-size: 2.6rem; line-height: 1.35; letter-spacing: -.08rem; margin-bottom: 1.2rem; margin-top: 1.2rem;}
+ h5 { font-size: 2.2rem; line-height: 1.5; letter-spacing: -.05rem; margin-bottom: 0.6rem; margin-top: 0.6rem;}
+ h6 { font-size: 2.0rem; line-height: 1.6; letter-spacing: 0; margin-bottom: 0.75rem; margin-top: 0.75rem;}
+
+ p {
+ margin-top: 0; }
+
+
+ /* Blockquotes
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ blockquote {
+ border-left: 4px lightgrey solid;
+ padding-left: 1rem;
+ margin-top: 2rem;
+ margin-bottom: 2rem;
+ margin-left: 0rem;
+ }
+
+
+ /* Links
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ a {
+ color: #1EAEDB;
+ text-decoration: underline;
+ cursor: pointer;}
+ a:hover {
+ color: #0FA0CE; }
+
+
+ /* Buttons
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ .button,
+ button,
+ input[type="submit"],
+ input[type="reset"],
+ input[type="button"] {
+ display: inline-block;
+ height: 38px;
+ padding: 0 30px;
+ color: #555;
+ text-align: center;
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 38px;
+ letter-spacing: .1rem;
+ text-transform: uppercase;
+ text-decoration: none;
+ white-space: nowrap;
+ background-color: transparent;
+ border-radius: 4px;
+ border: 1px solid #bbb;
+ cursor: pointer;
+ box-sizing: border-box; }
+ .button:hover,
+ button:hover,
+ input[type="submit"]:hover,
+ input[type="reset"]:hover,
+ input[type="button"]:hover,
+ .button:focus,
+ button:focus,
+ input[type="submit"]:focus,
+ input[type="reset"]:focus,
+ input[type="button"]:focus {
+ color: #333;
+ border-color: #888;
+ outline: 0; }
+ .button.button-primary,
+ button.button-primary,
+ input[type="submit"].button-primary,
+ input[type="reset"].button-primary,
+ input[type="button"].button-primary {
+ color: #FFF;
+ background-color: #33C3F0;
+ border-color: #33C3F0; }
+ .button.button-primary:hover,
+ button.button-primary:hover,
+ input[type="submit"].button-primary:hover,
+ input[type="reset"].button-primary:hover,
+ input[type="button"].button-primary:hover,
+ .button.button-primary:focus,
+ button.button-primary:focus,
+ input[type="submit"].button-primary:focus,
+ input[type="reset"].button-primary:focus,
+ input[type="button"].button-primary:focus {
+ color: #FFF;
+ background-color: #1EAEDB;
+ border-color: #1EAEDB; }
+
+
+ /* Forms
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ input[type="email"],
+ input[type="number"],
+ input[type="search"],
+ input[type="text"],
+ input[type="tel"],
+ input[type="url"],
+ input[type="password"],
+ textarea,
+ select {
+ height: 38px;
+ padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
+ background-color: #fff;
+ border: 1px solid #D1D1D1;
+ border-radius: 4px;
+ box-shadow: none;
+ box-sizing: border-box;
+ font-family: inherit;
+ font-size: inherit; /*https://stackoverflow.com/questions/6080413/why-doesnt-input-inherit-the-font-from-body*/}
+ /* Removes awkward default styles on some inputs for iOS */
+ input[type="email"],
+ input[type="number"],
+ input[type="search"],
+ input[type="text"],
+ input[type="tel"],
+ input[type="url"],
+ input[type="password"],
+ textarea {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none; }
+ textarea {
+ min-height: 65px;
+ padding-top: 6px;
+ padding-bottom: 6px; }
+ input[type="email"]:focus,
+ input[type="number"]:focus,
+ input[type="search"]:focus,
+ input[type="text"]:focus,
+ input[type="tel"]:focus,
+ input[type="url"]:focus,
+ input[type="password"]:focus,
+ textarea:focus,
+ select:focus {
+ border: 1px solid #33C3F0;
+ outline: 0; }
+ label,
+ legend {
+ display: block;
+ margin-bottom: 0px; }
+ fieldset {
+ padding: 0;
+ border-width: 0; }
+ input[type="checkbox"],
+ input[type="radio"] {
+ display: inline; }
+ label > .label-body {
+ display: inline-block;
+ margin-left: .5rem;
+ font-weight: normal; }
+
+
+ /* Lists
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ ul {
+ list-style: circle inside; }
+ ol {
+ list-style: decimal inside; }
+ ol, ul {
+ padding-left: 0;
+ margin-top: 0; }
+ ul ul,
+ ul ol,
+ ol ol,
+ ol ul {
+ margin: 1.5rem 0 1.5rem 3rem;
+ font-size: 90%; }
+ li {
+ margin-bottom: 1rem; }
+
+
+ /* Tables
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ table {
+ border-collapse: collapse;
+ }
+ th:not(.CalendarDay),
+ td:not(.CalendarDay) {
+ padding: 12px 15px;
+ text-align: left;
+ border-bottom: 1px solid #E1E1E1; }
+ th:first-child:not(.CalendarDay),
+ td:first-child:not(.CalendarDay) {
+ padding-left: 0; }
+ th:last-child:not(.CalendarDay),
+ td:last-child:not(.CalendarDay) {
+ padding-right: 0; }
+
+
+ /* Spacing
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ button,
+ .button {
+ margin-bottom: 0rem; }
+ input,
+ textarea,
+ select,
+ fieldset {
+ margin-bottom: 0rem; }
+ pre,
+ dl,
+ figure,
+ table,
+ form {
+ margin-bottom: 0rem; }
+ p,
+ ul,
+ ol {
+ margin-bottom: 0.75rem; }
+
+ /* Utilities
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ .u-full-width {
+ width: 100%;
+ box-sizing: border-box; }
+ .u-max-full-width {
+ max-width: 100%;
+ box-sizing: border-box; }
+ .u-pull-right {
+ float: right; }
+ .u-pull-left {
+ float: left; }
+
+
+ /* Misc
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ hr {
+ margin-top: 3rem;
+ margin-bottom: 3.5rem;
+ border-width: 0;
+ border-top: 1px solid #E1E1E1; }
+
+
+ /* Clearing
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+
+ /* Self Clearing Goodness */
+ .container:after,
+ .row:after,
+ .u-cf {
+ content: "";
+ display: table;
+ clear: both; }
+
+
+ /* Media Queries
+ –––––––––––––––––––––––––––––––––––––––––––––––––– */
+ /*
+ Note: The best way to structure the use of media queries is to create the queries
+ near the relevant code. For example, if you wanted to change the styles for buttons
+ on small devices, paste the mobile query code up in the buttons section and style it
+ there.
+ */
+
+
+ /* Larger than mobile */
+ @media (min-width: 400px) {}
+
+ /* Larger than phablet (also point when grid becomes active) */
+ @media (min-width: 550px) {}
+
+ /* Larger than tablet */
+ @media (min-width: 750px) {}
+
+ /* Larger than desktop */
+ @media (min-width: 1000px) {}
+
+ /* Larger than Desktop HD */
+ @media (min-width: 1200px) {}
\ No newline at end of file
diff --git a/components/dash-uploader/dash_uploader/__init__.py b/components/dash-uploader/dash_uploader/__init__.py
new file mode 100644
index 0000000000..a40c3a092e
--- /dev/null
+++ b/components/dash-uploader/dash_uploader/__init__.py
@@ -0,0 +1,52 @@
+import os as _os
+import sys as _sys
+import json
+
+import dash as _dash
+
+from dash_uploader.configure_upload import configure_upload
+from dash_uploader.callbacks import callback
+from dash_uploader.httprequesthandler import HttpRequestHandler
+from dash_uploader.upload import Upload
+
+# noinspection PyUnresolvedReferences
+from ._build._imports_ import * # noqa: F403,F401
+from ._build._imports_ import __all__ as build_all
+
+# Defines all exposed APIs of this package.
+__all__ = ["configure_upload", "callback", "HttpRequestHandler", "Upload"]
+
+if not hasattr(_dash, "development"):
+ print(
+ "Dash was not successfully imported. "
+ "Make sure you don't have a file "
+ 'named \n"dash.py" in your current directory.',
+ file=_sys.stderr,
+ )
+ _sys.exit(1)
+
+_basepath = _os.path.dirname(__file__)
+_filepath = _os.path.abspath(_os.path.join(_basepath, "_build", "package-info.json"))
+with open(_filepath) as f:
+ package = json.load(f)
+
+package_name = package["name"].replace(" ", "_").replace("-", "_")
+__version__ = package["version"]
+
+_current_path = _os.path.dirname(_os.path.abspath(__file__))
+
+_this_module = _sys.modules[__name__]
+_js_dist = [
+ {"relative_package_path": "_build/dash_uploader.min.js", "namespace": package_name},
+ {
+ "relative_package_path": "_build/dash_uploader.min.js.map",
+ "namespace": package_name,
+ "dynamic": True,
+ },
+]
+
+_css_dist = []
+
+for _component in build_all:
+ setattr(locals()[_component], "_js_dist", _js_dist)
+ setattr(locals()[_component], "_css_dist", _css_dist)
diff --git a/components/dash-uploader/dash_uploader/callbacks.py b/components/dash-uploader/dash_uploader/callbacks.py
new file mode 100644
index 0000000000..0001b514d7
--- /dev/null
+++ b/components/dash-uploader/dash_uploader/callbacks.py
@@ -0,0 +1,111 @@
+from packaging import version
+from pathlib import Path
+
+from dash import __version__ as dashversion
+from dash.dependencies import Input, State
+
+import dash_uploader.settings as settings
+
+
+def compare_dash_version(req_version="1.12"):
+ """Compare the version of dash.
+ Will return True if current dash version is greater than
+ the argument "req_version".
+ This is a private method, and should not be exposed to users.
+ """
+ cur_version = version.parse(dashversion)
+ return cur_version >= version.parse(req_version)
+
+
+def create_dash_callback(callback, settings): # pylint: disable=redefined-outer-name
+ """Wrap the dash callback with the du.settings.
+ This function could be used as a wrapper. It will add the
+ configurations of dash-uploader to the callback.
+ This is a private method, and should not be exposed to users.
+ """
+
+ def wrapper(iscompleted, filenames, upload_id):
+ if not iscompleted:
+ return
+
+ out = []
+ if filenames is not None:
+ if upload_id:
+ root_folder = Path(settings.UPLOAD_FOLDER_ROOT) / upload_id
+ else:
+ root_folder = Path(settings.UPLOAD_FOLDER_ROOT)
+
+ for filename in filenames:
+ file = root_folder / filename
+ out.append(str(file))
+
+ return callback(out)
+
+ return wrapper
+
+
+def callback(
+ output,
+ id="dash-uploader",
+):
+ """
+ Add a callback to dash application.
+ This callback fires when upload is completed.
+ Note: Must be called after du.configure_upload!
+
+ Parameters
+ ----------
+ output: dash Ouput
+ The output dash component
+ id: str
+ The id of the du.Upload component.
+
+ Example
+ -------
+ @du.callback(
+ output=Output('callback-output', 'children'),
+ id='dash-uploader',
+ )
+ def get_a_list(filenames):
+ return html.Ul([html.Li(filenames)])
+
+
+ """
+
+ def add_callback(function):
+ """
+ Parameters
+ ---------
+ function: callable
+ Function that receivers one argument,
+ filenames and returns one argument,
+ a dash component. The filenames is either
+ None or list of str containing the uploaded
+ file(s).
+ output: dash.dependencies.Output
+ The dash output. For example:
+ Output('callback-output', 'children')
+
+ """
+ dash_callback = create_dash_callback(
+ function,
+ settings,
+ )
+
+ if not hasattr(settings, "app"):
+ raise Exception(
+ "The du.configure_upload must be called before the @du.callback can be used! Please, configure the dash-uploader."
+ )
+
+ kwargs = dict()
+ if compare_dash_version("1.12"):
+ kwargs["prevent_initial_call"] = True
+ dash_callback = settings.app.callback(
+ output,
+ [Input(id, "isCompleted")],
+ [State(id, "fileNames"), State(id, "upload_id")],
+ **kwargs
+ )(dash_callback)
+ return function
+
+ return add_callback
diff --git a/components/dash-uploader/dash_uploader/configure_upload.py b/components/dash-uploader/dash_uploader/configure_upload.py
new file mode 100644
index 0000000000..620ada41bf
--- /dev/null
+++ b/components/dash-uploader/dash_uploader/configure_upload.py
@@ -0,0 +1,110 @@
+import logging
+
+import dash_uploader.settings as settings
+from dash_uploader.upload import update_upload_api
+from dash_uploader.httprequesthandler import HttpRequestHandler
+
+
+logger = logging.getLogger("dash_uploader")
+
+
+def configure_upload(
+ app, folder, use_upload_id=True, upload_api=None, http_request_handler=None
+):
+ r"""
+ Configure the upload APIs for dash app.
+ This function is required to be called before using du.callback.
+
+ Parameters
+ ---------
+ app: dash.Dash
+ The application instance
+ folder: str
+ The folder where to upload files.
+ Can be relative ("uploads") or
+ absolute (r"C:\tmp\my_uploads").
+ If the folder does not exist, it will
+ be created automatically.
+ use_upload_id: bool
+ Determines if the uploads are put into
+ folders defined by a "upload id" (upload_id).
+ If True, uploads will be put into `folder`//;
+ that is, every user (for example with different
+ session id) will use their own folder. If False,
+ all files from all sessions are uploaded into
+ same folder (not recommended).
+ upload_api: None or str
+ The upload api endpoint to use; the url that is used
+ internally for the upload component POST and GET HTTP
+ requests. For example: "/API/dash-uploader"
+ http_request_handler: None or class
+ Used for custom configuration on the Http POST and GET requests.
+ This can be used to add validation for the HTTP requests (Important
+ if your site is public!). If None, dash_uploader.HttpRequestHandler is used.
+ If you provide a class, use a subclass of HttpRequestHandler.
+ See the documentation of dash_uploader.HttpRequestHandler for
+ more details.
+ """
+ settings.UPLOAD_FOLDER_ROOT = folder
+ settings.app = app
+
+ if upload_api is None:
+ upload_api = settings.upload_api
+ else:
+ # Set the upload api since du.Upload components
+ # that are created after du.configure_upload
+ # need to be able to read the api endpoint.
+ settings.upload_api = upload_api
+
+ # Needed if using a proxy
+ settings.requests_pathname_prefix = app.config.get("requests_pathname_prefix", "/")
+ settings.routes_pathname_prefix = app.config.get("routes_pathname_prefix", "/")
+
+ upload_api = update_upload_api(settings.routes_pathname_prefix, upload_api)
+
+ if http_request_handler is None:
+ http_request_handler = HttpRequestHandler
+
+ decorate_server(
+ app.server,
+ folder,
+ upload_api,
+ http_request_handler=http_request_handler,
+ use_upload_id=use_upload_id,
+ )
+
+
+def decorate_server(
+ server,
+ temp_base,
+ upload_api,
+ http_request_handler,
+ use_upload_id=True,
+):
+ """
+ Parameters
+ ----------
+ server: flask.Flask
+ The flask server instance
+ temp_base: str
+ The upload root folder
+ upload_api: str
+ The upload api endpoint to use; the url that is used
+ internally for the upload component POST and GET HTTP
+ requests.
+ use_upload_id: bool
+ Determines if the uploads are put into
+ folders defined by a "upload id" (upload_id).
+ If True, uploads will be put into `folder`//;
+ that is, every user (for example with different
+ session id) will use their own folder. If False,
+ all files from all sessions are uploaded into
+ same folder (not recommended).
+ """
+
+ handler = http_request_handler(
+ server, upload_folder=temp_base, use_upload_id=use_upload_id
+ )
+
+ server.add_url_rule(upload_api, None, handler.get, methods=["GET"])
+ server.add_url_rule(upload_api, None, handler.post, methods=["POST"])
diff --git a/components/dash-uploader/dash_uploader/httprequesthandler.py b/components/dash-uploader/dash_uploader/httprequesthandler.py
new file mode 100644
index 0000000000..9b00137bca
--- /dev/null
+++ b/components/dash-uploader/dash_uploader/httprequesthandler.py
@@ -0,0 +1,198 @@
+import logging
+import os
+import shutil
+import time
+import traceback
+
+from flask import request
+from flask import abort
+
+logger = logging.getLogger(__name__)
+
+
+def get_chunk_name(uploaded_filename, chunk_number):
+ return uploaded_filename + "_part_%03d" % chunk_number
+
+
+class BaseHttpRequestHandler:
+ def __init__(self, server, upload_folder, use_upload_id):
+ """
+ Parameters
+ ----------
+ server: flask.Flask
+ The flask server instance
+ upload_folder: str
+ The folder to use for uploads
+ use_upload_id: bool
+ Determines if the uploads are put into
+ folders defined by a "upload id" (upload_id).
+ If True, uploads will be put into `folder`//;
+ that is, every user (for example with different
+ session id) will use their own folder. If False,
+ all files from all sessions are uploaded into
+ same folder (not recommended).
+
+ """
+ self.server = server
+ self.upload_folder = upload_folder
+ self.use_upload_id = use_upload_id
+
+ def post(self):
+ try:
+ return self._post()
+ except Exception:
+ logger.error(traceback.format_exc())
+
+ def _post(self):
+ resumableTotalChunks = request.form.get("resumableTotalChunks", type=int)
+ resumableChunkNumber = request.form.get(
+ "resumableChunkNumber", default=1, type=int
+ )
+ resumableFilename = request.form.get(
+ "resumableFilename", default="error", type=str
+ )
+ resumableIdentifier = request.form.get(
+ "resumableIdentifier", default="error", type=str
+ )
+ upload_id = request.form.get("upload_id", default="", type=str)
+
+ # get the chunk data
+ chunk_data = request.files["file"]
+
+ # make our temp directory
+ temp_root = self.get_temp_root(upload_id)
+ temp_dir = os.path.join(temp_root, resumableIdentifier)
+ if not os.path.isdir(temp_dir):
+ os.makedirs(temp_dir)
+
+ # save the chunk data
+ chunk_name = get_chunk_name(resumableFilename, resumableChunkNumber)
+ chunk_file = os.path.join(temp_dir, chunk_name)
+
+ # make a lock file
+ lock_file_path = os.path.join(
+ temp_dir, ".lock_{:d}".format(resumableChunkNumber)
+ )
+
+ with open(lock_file_path, "a"):
+ os.utime(lock_file_path, None)
+ chunk_data.save(chunk_file)
+ os.unlink(lock_file_path)
+
+ # check if the upload is complete
+ chunk_paths = [
+ os.path.join(temp_dir, get_chunk_name(resumableFilename, x))
+ for x in range(1, resumableTotalChunks + 1)
+ ]
+ upload_complete = all([os.path.exists(p) for p in chunk_paths])
+
+ # combine all the chunks to create the final file
+ if upload_complete:
+
+ # Make sure all files are finished writing
+ # but do not wait forever..
+ tried = 0
+ while any(
+ [
+ os.path.isfile(os.path.join(temp_dir, ".lock_{:d}".format(chunk)))
+ for chunk in range(1, resumableTotalChunks + 1)
+ ]
+ ):
+ tried += 1
+ if tried >= 5:
+ logger.error("Error uploading files with temp_dir: %s.", temp_dir)
+ raise Exception("Error uploading files with temp_dir: " + temp_dir)
+ time.sleep(1)
+
+ # Make sure some other chunk didn't trigger file reconstruction
+ target_file_name = os.path.join(temp_root, resumableFilename)
+ if os.path.exists(target_file_name):
+ logger.info("File %s exists already. Overwriting..", target_file_name)
+ os.unlink(target_file_name)
+
+ with open(target_file_name, "ab") as target_file:
+ for p in chunk_paths:
+ with open(p, "rb") as stored_chunk_file:
+ target_file.write(stored_chunk_file.read())
+ self.server.logger.debug("File saved to: %s", target_file_name)
+ shutil.rmtree(temp_dir)
+
+ return resumableFilename
+
+ def get(self):
+ try:
+ return self._get()
+ except Exception:
+ logger.error(traceback.format_exc())
+
+ def _get(self):
+ # resumable.js uses a GET request to check if it uploaded the file already.
+ # https://github.com/23/resumable.js#handling-get-or-test-requests
+ # TODO: Since testChunks is set to false, this seems to be permanently disabled.
+ # Should this be removed altogether?
+
+ resumableIdentifier = request.args.get("resumableIdentifier", type=str)
+ resumableFilename = request.args.get("resumableFilename", type=str)
+ resumableChunkNumber = request.args.get("resumableChunkNumber", type=int)
+
+ upload_id = request.args.get("upload_id", default="", type=str)
+
+ if not (resumableIdentifier and resumableFilename and resumableChunkNumber):
+ # Parameters are missing or invalid
+ abort(500, "Parameter error")
+
+ # chunk folder path based on the parameters
+ temp_dir = os.path.join(self.get_temp_root(upload_id), resumableIdentifier)
+
+ # chunk path based on the parameters
+ chunk_file = os.path.join(
+ temp_dir, get_chunk_name(resumableFilename, resumableChunkNumber)
+ )
+ self.server.logger.debug("Getting chunk: %s", chunk_file)
+
+ if os.path.isfile(chunk_file):
+ # Let resumable.js know this chunk already exists
+ return "OK"
+ else:
+ # Let resumable.js know this chunk does not exists
+ # and needs to be uploaded
+ abort(404, "Not found")
+
+ def get_temp_root(self, upload_id):
+ return (
+ os.path.join(self.upload_folder, upload_id)
+ if self.use_upload_id
+ else self.upload_folder
+ )
+
+
+class HttpRequestHandler(BaseHttpRequestHandler):
+ # You may use the flask.request
+ # and flask.session inside the methods of this
+ # class when needed.
+ def __init__(self, *args, **kwargs): # pylint: disable=useless-super-delegation
+ super().__init__(*args, **kwargs)
+
+ def post_before(self):
+ pass
+
+ def post(self):
+ self.post_before()
+ returnvalue = super().post()
+ self.post_after()
+ return returnvalue
+
+ def post_after(self):
+ pass
+
+ def get_before(self):
+ pass
+
+ def get(self):
+ self.get_before()
+ returnvalue = super().get()
+ self.get_after()
+ return returnvalue
+
+ def get_after(self):
+ pass
diff --git a/components/dash-uploader/dash_uploader/settings.py b/components/dash-uploader/dash_uploader/settings.py
new file mode 100644
index 0000000000..019300a1f2
--- /dev/null
+++ b/components/dash-uploader/dash_uploader/settings.py
@@ -0,0 +1,20 @@
+# The default upload api endpoint
+# The du.configure_upload can change this
+upload_api = "/API/resumable"
+
+# Needed if using a proxy; when dash.Dash is used
+# with a `requests_pathname_prefix`.
+# The front-end will prefix this string to the requests
+# that are made to the proxy server
+requests_pathname_prefix = "/"
+
+# From dash source code:
+# Note that `requests_pathname_prefix` is the prefix for the AJAX calls that
+# originate from the client (the web browser) and `routes_pathname_prefix` is
+# the prefix for the API routes on the backend (this flask server).
+# `url_base_pathname` will set `requests_pathname_prefix` and
+# `routes_pathname_prefix` to the same value.
+# If you need these to be different values then you should set
+# `requests_pathname_prefix` and `routes_pathname_prefix`,
+# not `url_base_pathname`.
+routes_pathname_prefix = "/"
diff --git a/components/dash-uploader/dash_uploader/upload.py b/components/dash-uploader/dash_uploader/upload.py
new file mode 100644
index 0000000000..53807d7dbf
--- /dev/null
+++ b/components/dash-uploader/dash_uploader/upload.py
@@ -0,0 +1,171 @@
+import uuid
+
+from dash_uploader._build.Upload_ReactComponent import Upload_ReactComponent
+import dash_uploader.settings as settings
+
+DEFAULT_STYLE = {
+ "width": "100%",
+ # min-height and line-height should be the same to make
+ # the centering work.
+ "minHeight": "100px",
+ "lineHeight": "100px",
+ "textAlign": "center",
+ "borderWidth": "1px",
+ "borderStyle": "dashed",
+ "borderRadius": "7px",
+}
+
+
+def update_upload_api(requests_pathname_prefix, upload_api):
+ """Path join for the API path name.
+ This is a private method, and should not be exposed to users.
+ """
+ if requests_pathname_prefix == "/":
+ return upload_api
+ return "/".join(
+ [
+ requests_pathname_prefix.rstrip("/"),
+ upload_api.lstrip("/"),
+ ]
+ )
+
+
+def combine(overiding_dict, base_dict):
+ """Combining two dictionaries without modifying them.
+ This is a private method, and should not be exposed to users.
+ """
+ if overiding_dict is None:
+ return dict(base_dict)
+ return {**base_dict, **overiding_dict}
+
+
+# Implemented as function, but still uppercase.
+# This is because subclassing the Dash-auto-generated
+# "Upload from Upload.py" will give some errors
+def Upload(
+ id="dash-uploader",
+ text="Drag and Drop Here to upload!",
+ text_completed="Uploaded: ",
+ text_disabled="The uploader is disabled.",
+ cancel_button=True,
+ pause_button=False,
+ disabled=False,
+ filetypes=None,
+ max_file_size=1024,
+ chunk_size=1,
+ default_style=None,
+ upload_id=None,
+ max_files=1,
+):
+ """
+ du.Upload component
+
+ Parameters
+ ----------
+ text: str
+ The text to show in the upload "Drag
+ and Drop" area. Optional.
+ text_completed: str
+ The text to show in the upload area
+ after upload has completed successfully before
+ the name of the uploaded file. For example, if user
+ uploaded "data.zip" and `text_completed` is
+ "Ready! ", then user would see text "Ready!
+ data.zip".
+ text_disabled: str
+ The text to show in the upload area when the
+ the component is disabled.
+ cancel_button: bool
+ If True, shows a cancel button.
+ pause_button: bool
+ If True, shows a pause button.
+ disabled: bool
+ If True, the file is not allowed to be uploaded.
+ filetypes: list of str or None
+ The filetypes that can be uploaded.
+ For example ['zip', 'rar'].
+ Note that this just checks the extension of the
+ filename, and user might still upload any kind
+ of file (by renaming)!
+ By default, all filetypes are accepted.
+ max_file_size: numeric
+ The maximum file size in Megabytes. Optional.
+ chunk_size: numeric
+ The chunk size in Megabytes. Optional.
+ default_style: None or dict
+ Inline CSS styling for the main div element.
+ If None, use the default style of the component.
+ If dict, will use the union on the given dict
+ and the default style. (you may override
+ part of the style by giving a dictionary)
+ More styling options through the CSS classes.
+ upload_id: None or str
+ The upload id, created with uuid.uuid1() or uuid.uuid4(),
+ for example. If none, creates random session id with
+ uuid.uuid1().
+ max_files: int (default: 1)
+ EXPERIMENTAL feature. Read below. For bulletproof
+ implementation, force usage of zip files and keep
+ max_files = 1.
+
+ The number of files that can be added to
+ the upload field simultaneously.
+
+ Notes:
+ (1) If even a single file which is not supported file
+ type, is added to the upload queue, upload process of
+ all files will be permanently interrupted.
+ (2) Use reasonably small amount in "max_files".
+ (3) When uploading two folders with Chrome, there is
+ a bug in resumable.js which makes only one of the
+ folders to be uploaded. See:
+ https://github.com/23/resumable.js/issues/416
+ (4) When uploading folders, note that the subdirectories
+ are NOT created -> All files in the folders will
+ be uploaded to the single upload folder.
+
+ Returns
+ -------
+ Upload: dash component
+ Initiated Dash component for app.layout.
+ """
+
+ # Handle styling
+ default_style = combine(default_style, DEFAULT_STYLE)
+ disabled_style = combine({"opacity": "0.5"}, default_style)
+ upload_style = combine({"lineHeight": "0px"}, default_style)
+
+ if upload_id is None:
+ upload_id = uuid.uuid1()
+
+ service = update_upload_api(settings.requests_pathname_prefix, settings.upload_api)
+
+ arguments = dict(
+ id=id,
+ isCompleted=False,
+ # Have not tested if using many files
+ # is reliable -> Do not allow
+ maxFiles=max_files,
+ maxFileSize=max_file_size * 1024 * 1024,
+ chunkSize=chunk_size * 1024 * 1024,
+ textLabel=text,
+ service=service,
+ startButton=False,
+ disabled=disabled,
+ # Not tested so default to one.
+ simultaneousUploads=1,
+ completedMessage=text_completed,
+ disabledMessage=text_disabled,
+ cancelButton=cancel_button,
+ pauseButton=pause_button,
+ defaultStyle=default_style,
+ disabledStyle=disabled_style,
+ uploadingStyle=upload_style,
+ completeStyle=default_style,
+ upload_id=str(upload_id),
+ )
+
+ if filetypes:
+ arguments["filetypes"] = filetypes
+
+ return Upload_ReactComponent(**arguments)
diff --git a/components/dash-uploader/dash_uploader/utils.py b/components/dash-uploader/dash_uploader/utils.py
new file mode 100644
index 0000000000..84f8d09b9a
--- /dev/null
+++ b/components/dash-uploader/dash_uploader/utils.py
@@ -0,0 +1,19 @@
+import packaging
+import pkg_resources
+
+## Dash version
+dash_version_str = pkg_resources.get_distribution("dash").version
+dash_version = packaging.version.parse(dash_version_str)
+
+
+def dash_version_is_greater_or_equal_to(version):
+ """
+ Check if the installed version of Dash is
+ greater or equal than some version.
+
+ Parameters
+ ----------
+ version: str
+ The version string. E.g. '1.14.0'
+ """
+ return dash_version >= packaging.version.parse(version)
diff --git a/components/dash-uploader/devscripts/post_build.py b/components/dash-uploader/devscripts/post_build.py
new file mode 100644
index 0000000000..d461817680
--- /dev/null
+++ b/components/dash-uploader/devscripts/post_build.py
@@ -0,0 +1,20 @@
+from pathlib import Path
+import shutil
+
+root = Path(__file__).resolve().parent.parent
+
+components = root / "src" / "lib" / "components"
+
+component_names = [x.name[:-9] for x in components.glob("*.react.js")]
+
+folder_from = root / "dash_uploader"
+folder_to = folder_from / "_build"
+
+filenames = ["metadata.json", "package-info.json", "_imports_.py"] + [
+ x + ".py" for x in component_names
+]
+
+for filename in filenames:
+ shutil.move(folder_from / filename, folder_to / filename)
+
+Path(folder_to / "__init__.py").touch(exist_ok=True)
diff --git a/components/dash-uploader/docs/CHANGELOG.md b/components/dash-uploader/docs/CHANGELOG.md
new file mode 100644
index 0000000000..c4e79cb2ef
--- /dev/null
+++ b/components/dash-uploader/docs/CHANGELOG.md
@@ -0,0 +1,75 @@
+# Changelog
+
+## v.0.6.0 (2021-09-19)
+### Added
+- New `chunk_size`, `disabled` and `text_disabled` parameters for `du.Upload`. [Issue 41](https://github.com/np-8/dash-uploader/issues/41)
+
+### Changed
+- Added the `prevent_initial_call=True` for all `du.callback`s. For [Dash >= 1.12.0](https://community.plotly.com/t/dash-v1-12-0-release-pattern-matching-callbacks-fixes-shape-drawing-new-datatable-conditional-formatting-options-prevent-initial-call-and-more/38867).
+
+### Fixed
+- Changing the parameter `disableDragAndDrop` by callbacks does not take effects. [PR 42](https://github.com/np-8/dash-uploader/pull/42)
+
+## v.0.5.0 (2021-04-25)
+### Added
+- [`du.HttpRequestHandler`](./dash-uploader.md#duhttprequesthandler) which allows for custom HTTP POST and GET request handling. For example, custom validation logic is now possible! Used through `http_request_handler` parameter of [`du.configure_upload`](./dash-uploader.md#duconfigure_upload).
+### Changed
+- **Backwards incompatible changes**: Changed the CSS classes of the component to be `dash-uploader-default`, `dash-uploader-uploading`, .. etc. instead of `resumable-default`, `resumable-uploading`.
+
+## v.0.4.2 (2021-02-20)
+- Fixed some width related CSS issues in mobile mode. See: [#19](https://github.com/np-8/dash-uploader/issues/19)
+
+## v.0.4.1 (2020-10-27)
+### Fixed
+- max_files parameter to du.Upload did not have effect (Related [issue](https://github.com/np-8/dash-uploader/issues/12))
+
+## v.0.4.0 (2020-10-27)
+### Fixed
+- Now dash-uploader works with `url_base_pathname` set in `app = dash.Dash(__name__, server=server, url_base_pathname='/somebase/')` . (Related [issue](https://github.com/np-8/dash-uploader/issues/15))
+### Other
+- Javascript updates (includes security updates)
+
+## v.0.3.1 (2020-08-04)
+### Fixed
+- Importing `dash-uploader` with `dash` versions `<1.11.0` was not possible. (Related [issue](https://github.com/np-8/dash-uploader/issues/9))
+### Security
+- Javascript package security updates.
+
+## v.0.3.0 (2020-06-06)
+### Added
+- New [`@du.callback`](dash-uploader.md#ducallback) decorator for simple callback creation.
+- Experimental `max_files` parameter for `du.Upload`.
+- Support for proxies; i.e. If app is running on `http://server.com/myapp`, and dash application is configured using `requests_pathname_prefix=myapp`, this is handled automatically by the Upload component. Fixes [#3](https://github.com/np-8/dash-uploader/issues/3).
+### Fixed
+- Uploading file with same name multiple times is now possible.
+## v.0.2.4 (2020-06-05)
+### Added
+- Possibility to determine the uploader component API endpoint using the `upload_api` argument of the `configure_upload` function.
+
+## v.0.2.0 (2020-05-25)
+### Added
+- Upload folder for each file defined with a upload id (`upload_id`), which may be defined by the user.
+### Fixed
+- Uploading file with similar name now overwrites the old file (previously, file chunks were uploaded, but never merged.)
+- Removed potential cause of infinite wait
+
+## v.0.1.2 (2020-05-22)
+### Added
+- Progressbar
+### Changed
+- Loosened `dash` requirements; `dash~=0.11.0` -> `dash>=1.1.0`.
+- `activeStyle` replaced with `uploadingStyle`.
+
+
+## v.0.1.1 (2020-05-18)
+### Fixed
+- Callback will now fired even multiple files are uploaded in a row. (Related [Issue](https://github.com/np-8/dash-uploader/issues/1))
+
+## v.0.1.0 (2020-04-06)
+- Initial release based on the [dash-resume-upload](https://github.com/westonkjones/dash-uploader) (0.0.4).
+
+### Changed
+- Restarted project basing on the [dash-component-boilerplate](https://github.com/plotly/dash-component-boilerplate)
+- Hiding "Pause" and "Cancel" buttons when not uploading
+### Added
+- Clean, documented python interface for `Upload`
diff --git a/components/dash-uploader/docs/CONTRIBUTING.md b/components/dash-uploader/docs/CONTRIBUTING.md
new file mode 100644
index 0000000000..392c2fa6e8
--- /dev/null
+++ b/components/dash-uploader/docs/CONTRIBUTING.md
@@ -0,0 +1,172 @@
+# CONTRIBUTING
+
+## TABLE OF CONTENTS
+- [1. How to improve this package?](#1-how-to-improve-this-package)
+- [2. Setting up development environment](#2-setting-up-development-environment)
+- [3. Package structure](#3-package-structure)
+ - [3.1 Highlights of package structure](#31-highlights-of-package-structure)
+ - [3.2 Other (Package structure)](#32-other-package-structure)
+- [4. Developing](#4-developing)
+ - [4.1 Developing the Python code](#41-developing-the-python-code)
+- [5. Testing](#5-testing)
+ - [5.1 Manually](#51-manually)
+ - [5.2 With pytest (automatic tests)](#52-with-pytest-automatic-tests)
+ - [5.3 Testing React components without python](#53--testing-react-components-without-python)
+- [6. Creating new version to pip](#6-creating-new-version-to-pip)
+- [7. How does dash-uploader work internally?](#7--how-does-dash-uploader-work-internally)
+- [8. More help?](#8--more-help)
+## 1. How to improve this package?
+
+Maybe you already have an idea. If not, see if there are any open [issues](https://github.com/np-8/dash-uploader/issues) that need help.
+## 2. Setting up development environment
+- Clone this repository. Change current directory to project root.
+- Install [npm and Node.js](https://nodejs.org) for building JS.
+- Install the JS dependencies by running `npm install` on the project root. This will create `node_modules` directory.
+- Create python virtual environment and activate it
+- `pip install` this package in editable state with the `[dev]` flag.
+```
+python -m pip install -e [dev]
+```
+
+
+## 3. Package structure
+
+### 3.1 Highlights of package structure
+```
+dash_uploader/
+ * python source code of this package
+ __init__.py
+ _build/
+ * Auto-generated python & JS code
+ * Do not edit these by hand!
+ _imports_.py
+ .py <-- for each component
+ dash_uploader.min.js
+ dash_uploader.min.js.map
+ metadata.json
+ package-info.json
+
+devscripts/
+ * used during "npm run build"
+
+src/
+ demo/
+ * Example JS demo. Just for testing React code
+ with "npm start"
+ lib/
+ * React components (The JS/React source code)
+
+package.json
+ * Defines JS dependencies
+ * Defines npm scripts
+
+usage.py
+ * Example file
+ * Run with `python usage.py`
+```
+### 3.2 Other (Package structure)
+```
+assets/
+ * Assets just for the demo (usage.py)
+index.html
+ * Needed for testing (with npm run)
+inst/
+ * Some kind of intermediate storage for JS files
+ (before copying to dash_uploader)
+ * Automatically generated with "npm run build"
+node_modules/
+ * JS dependencies
+ * Automatically created by "npm install"
+venv/
+ * python dependencies (virtual environment)
+ * Created with "python -m venv venv"
+```
+## 4. Developing
+
+### 4.1 Developing the Python code
+
+- Edit the non-auto-generated files in `dash_uploader`
+- The used code formatter is [black](https://github.com/psf/black).
+### 4.2 Developing the React/JS code
+- Edit the react.js files in `src/lib/components/`
+
+
+#### 4.2.1 Building: React.js -> JS & Python
+If you edited the JS files, you need to build them. You also need to build the JS files the first time you try to use the cloned package.
+
+Run in the project root
+```
+npm run build
+```
+This will create all the auto-generated (JS, json, python) files into the `dash_uploader/_build` folder.
+
+
+
+## 5. Testing
+
+### 5.1 Manually
+
+You can test the code manually by running the demo page
+1. Run `python usage.py`
+2. Visit http://127.0.0.1:8050/ in your web browser
+
+### 5.2 With pytest (automatic tests)
+
+*If testing for the first time, install the testing requirements with*
+```
+python -m pip install -r tests/requirements.txt
+```
+
+You can test the code automatically by running
+
+```
+python -m pytest
+```
+
+- Make sure you have built the JS first with `npm run build`
+- The `app` defined in the `usage.py` will be available to the tests. See the tests in the `tests/test_usage.py` to get a grasp on how it works. You could also add other `app`s available to the tests in similar manner.
+- More about testing Dash components [here](https://dash.plotly.com/testing).
+- If you get an error similar to
+```
+selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 90
+Current browser version is 93.0.4577.82 with binary path C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
+```
+then, run
+
+```
+python -m pip install --upgrade --force-reinstall chromedriver-binary-auto
+```
+
+### 5.3 Testing React components without python
+*This is WIP; probably needs some fixing*
+- Before creating the "python/Dash" versions, it is possible to test the component(s) by
+- Editing the content of `src/demo/index.js`, if you wish.
+- Then, running
+```
+npm start
+```
+- Then, go to url `http://127.0.0.1:55555`.
+- The url can be changed in the package.json -> scripts -> start, by changing the `host` argument to the [`webpack-serve`](https://www.npmjs.com/package/webpack-serve).
+- **Note**: There is not handler for POST requests in the demo! (the Upload component will not work without a POST handler)
+
+## 6. Creating new version to pip
+
+*only applicable to people with access to the PyPI package*
+- Update version in `package.json`
+- Create new `dash_uploader-x.x.x.tar.gz` with
+```
+python .\setup.py sdist
+```
+- Upload to pip with
+```
+twine upload .\dist\dash_uploader-x.x.x.tar.gz
+```
+## 7. How does dash-uploader work internally?
+
+Here is a diagram that tries to explain how dash-uploader works under the hood. If you find a place for improvement, please [submit a PR](https://github.com/np-8/dash-uploader/issues).
+
+
+[](https://raw.githubusercontent.com/np-8/dash-uploader/master/docs/how-dash-uploader-works.svg)
+
+## 8. More help?
+Read also the automatically generated README text at [README-COOKIECUTTER.md](README-COOKIECUTTER.md).
\ No newline at end of file
diff --git a/components/dash-uploader/docs/README-COOKIECUTTER.md b/components/dash-uploader/docs/README-COOKIECUTTER.md
new file mode 100644
index 0000000000..b0b1fc84ca
--- /dev/null
+++ b/components/dash-uploader/docs/README-COOKIECUTTER.md
@@ -0,0 +1,61 @@
+
+This readme is part of the automatically generated README.md file (from the cookiecutter template)
+
+### Write your component code in `src/lib/components/Upload_ReactComponent.react.js`.
+
+- The demo app is in `src/demo` and you will import your example component code into your demo app.
+- Test your code in a Python environment:
+ 1. Build your code
+ ```
+ $ npm run build
+ ```
+ 2. Run and modify the `usage.py` sample dash app:
+ ```
+ $ python usage.py
+ ```
+- Write tests for your component.
+ - A sample test is available in `tests/test_usage.py`, it will load `usage.py` and you can then automate interactions with selenium.
+ - Run the tests with `$ pytest tests`.
+ - The Dash team uses these types of integration tests extensively. Browse the Dash component code on GitHub for more examples of testing (e.g. https://github.com/plotly/dash-core-components)
+- Add custom styles to your component by putting your custom CSS files into your distribution folder (`dash_uploader`).
+ - Make sure that they are referenced in `MANIFEST.in` so that they get properly included when you're ready to publish your component.
+ - Make sure the stylesheets are added to the `_css_dist` dict in `dash_uploader/__init__.py` so dash will serve them automatically when the component suite is requested.
+- [Review your code](./review_checklist.md)
+
+### Create a production build and publish:
+
+1. Build your code:
+ ```
+ $ npm run build
+ ```
+2. Create a Python tarball
+ ```
+ $ python setup.py sdist
+ ```
+ This distribution tarball will get generated in the `dist/` folder
+
+3. Test your tarball by copying it into a new environment and installing it locally:
+ ```
+ $ pip install dash_uploader-0.0.1.tar.gz
+ ```
+
+4. If it works, then you can publish the component to NPM and PyPI:
+ 1. Publish on PyPI
+ ```
+ $ twine upload dist/*
+ ```
+ 2. Cleanup the dist folder (optional)
+ ```
+ $ rm -rf dist
+ ```
+ 3. Publish on NPM (Optional if chosen False in `publish_on_npm`)
+ ```
+ $ npm publish
+ ```
+ _Publishing your component to NPM will make the JavaScript bundles available on the unpkg CDN. By default, Dash serves the component library's CSS and JS locally, but if you choose to publish the package to NPM you can set `serve_locally` to `False` and you may see faster load times._
+
+5. Share your component with the community! https://community.plotly.com/c/dash
+ 1. Publish this repository to GitHub
+ 2. Tag your GitHub repository with the plotly-dash tag so that it appears here: https://github.com/topics/plotly-dash
+ 3. Create a post in the Dash community forum: https://community.plotly.com/c/dash
+
diff --git a/components/dash-uploader/docs/README-PyPi.md b/components/dash-uploader/docs/README-PyPi.md
new file mode 100644
index 0000000000..5324582847
--- /dev/null
+++ b/components/dash-uploader/docs/README-PyPi.md
@@ -0,0 +1,51 @@
+
+   
+
+
+
+# 📤 dash-uploader
+The upload package for [Dash](https://dash.plotly.com/) applications using large data files.
+
+### 🏠 Homepage & Documentation
+[https://github.com/np-8/dash-uploader](https://github.com/np-8/dash-uploader)
+
+
+## Short summary
+ 💾 Data file size has no limits. (Except the hard disk size)
+ ☎ Call easily a callback after uploading is finished.
+ 📦 Upload files using [resumable.js](https://github.com/23/resumable.js)
+
+ ✅ Works with Dash 1.1.0.+ & Python 3.6+. (Possibly with other versions, too)
+
+
+
+## Installing
+```
+pip install dash-uploader
+```
+
+## Usage
+
+
+### Simple example
+
+```python
+import dash
+import dash_html_components as html
+import dash_uploader as du
+
+app = dash.Dash(__name__)
+
+# 1) configure the upload folder
+du.configure_upload(app, r"C:\tmp\Uploads")
+
+# 2) Use the Upload component
+app.layout = html.Div([
+ du.Upload(),
+])
+
+if __name__ == '__main__':
+ app.run_server(debug=True)
+
+```
+
diff --git a/components/dash-uploader/docs/dash-uploader.md b/components/dash-uploader/docs/dash-uploader.md
new file mode 100644
index 0000000000..c022dc4ed6
--- /dev/null
+++ b/components/dash-uploader/docs/dash-uploader.md
@@ -0,0 +1,351 @@
+
+
+# dash-uploader
+To use the Upload component you need to two things
+- Configure dash-uploader with [`du.configure_upload`](#duconfigure_upload)
+- Create the upload component with [`du.Upload`](#duupload)
+
+Typically you also would like to define [callbacks](#3-callbacks) (functions that are called automatically when upload finishes).
+
+> ⚠️**Security note**: The Upload component allows uploads of arbitrary files to the server harddisk and one should take this into account (with user token checking etc.) if used as part of a public website! Particularly, the `configure_upload` opens a route for `POST` HTTP requests. Use the [`http_request_handler`](#http_request_handler--none-or-subclass-of--duhttprequesthandler) argument for defining your custom validation logic.
+
+
+# Table of contents
+
+[1 Configuring dash-uploader](#1-configuring-dash-uploader)
+- [`du.configure_upload`](#duconfigure_upload)
+
+[2 Creating Upload components](#2-creating-upload-components)
+- [`du.Upload`](#duupload)
+
+[3 Callbacks](#3-callbacks)
+- [`@du.callback`](#ducallback)
+- [`@app.callback`](#appcallback)
+
+[4 Custom handling of HTTP Requests](#4-custom-handling-of-http-requests)
+- [`du.HttpRequestHandler`](#duhttprequesthandler)
+
+[5 How dash-uploader works internally?](#5-how-dash-uploader-works-internally)
+
+-----
+
+## 1 Configuring dash-uploader
+
+You need to configure the dash uploader after you created your dash application instance (`app`) and before you create any Upload components
+
+**Example**
+```python
+import dash_uploader as du
+
+du.configure_upload(app, r'C:\tmp\uploads')
+```
+
+### `du.configure_upload`
+
+```python
+configure_upload(app, folder, use_upload_id=True, upload_api=None, http_request_handler=None)
+```
+
+#### app: dash.Dash
+The application instance. Usually created using
+```python
+app = dash.Dash(__name__)
+```
+
+#### folder: str
+The folder where to upload files. Can be relative
+(`"uploads"`) or absolute (`r"C:\tmp\my_uploads"`).
+If the folder does not exist, it will be created
+automatically.
+
+#### use_upload_id: bool (Default: True)
+Determines if the uploads are put into
+folders defined by a "upload id". To define an upload id, use the `upload_id` parameter of the `du.Upload` component. In typical use case, upload id's are unique for different users. In that case, you must use a callable as the `app.layout`.
+
+If True, uploads will be put into `//`;
+that is, every user (for example with different
+session id) will use their own folder. If False,
+all files from all sessions are uploaded into
+same folder (not recommended).
+
+#### upload_api: None or str
+The upload api endpoint to use; the url that is used
+internally for the upload component POST and GET HTTP
+requests. For example, using `upload_api="/API/dash-uploader"` would create api endpoint and use address
+
+```
+http://[]/API/dash-uploader
+```
+for the communication between the front-end and the server. The `requests_pathname_prefix` is added automatically, if the dash `app` instance has `requests_pathname_prefix`. (used with proxies)
+
+#### http_request_handler: None or subclass of du.HttpRequestHandler
+*New in version **0.5.0***
+
+Used for custom configuration on the HTTP POST and GET requests. This can be used to add validation for the HTTP requests (⚠️Important
+if your site is public!). If None, dash_uploader.HttpRequestHandler is used.
+If you provide a class, use a subclass of `du.HttpRequestHandler`.
+See the documentation of [`@du.HttpRequestHandler`](#duhttprequesthandler) for
+more details.
+
+
+## 2 Creating Upload components
+### `du.Upload`
+Below are the arguments for the `du.Upload` component and their default values.
+```python
+Upload(
+ id='dash-uploader',
+ text='Drag and Drop Here to upload!',
+ text_completed='Uploaded: ',
+ text_disabled='The uploader is disabled.',
+ cancel_button=True,
+ pause_button=False,
+ disabled=False,
+ filetypes=None,
+ max_file_size=1024,
+ chunk_size=1,
+ default_style=None,
+ upload_id=None,
+ max_files=1,
+)
+```
+
+#### id: str (default: 'dash-uploader')
+The html id for the component. This is needed when defining callbacks. Note that ids must be unique in a dash application.
+
+#### text: str (Default: ''Drag and Drop Here to upload!')
+The text to be shown in the upload "Drag
+and Drop" area. Optional.
+
+#### text_completed: str
+The text to show in the upload area
+after upload has completed succesfully before
+the name of the uploaded file.
+
+For example, if user
+uploaded "data.zip" and `text_completed` is
+"Ready! ", then user would see text "Ready!
+data.zip".
+
+#### text_disabled: str
+*New in version **[0.6.0]***
+
+The text to show in the upload area when the
+the component is disabled.
+
+#### cancel_button: bool
+If True, shows a cancel button.
+
+#### pause_button: bool
+If True, shows a pause button.
+
+#### disabled: bool
+*New in version **[0.6.0]***
+
+If True, the file is not allowed to be uploaded.
+
+#### filetypes: list of str or None
+The filetypes that can be uploaded.
+For example `['zip', 'rar']`.
+Note that this just checks the extension of the
+filename, and user might still upload any kind
+of file (by renaming)!
+By default, all filetypes are accepted.
+
+#### max_file_size: numeric
+The maximum file size in Megabytes. Default: 1024 (=1Gb).
+
+#### chunk_size: numeric
+*New in version **[0.6.0]***
+
+The chunk size in Megabytes. Optional. Default: 1 (=1Mb).
+
+#### default_style: None or dict
+Inline CSS styling for the main div element.
+If None, use the default style of the component.
+If dict, will use the union on the given dict
+and the default style. (you may override
+part of the style by giving a dictionary)
+More styling options through the CSS classes.
+
+#### upload_id: None or str
+The upload id, created with `uuid.uuid1()` or uuid.uuid4(), for example. If `None`, creates random session id with `uuid.uuid1()`. Defines a subfolder where the files are to be uploaded.
+
+Only used, if `use_upload_id` parameter is set to `True` in [`du.configure_upload`](#duconfigure_upload).
+
+#### max_files: int (default: 1)
+>⚠️ **Experimental** feature. Read below. For bulletproof
+implementation, force usage of zip files and keep
+max_files = 1.
+
+The number of files that can be added to
+the upload field simultaneously.
+
+**Notes**:
+**(1)** If even a single file which is not supported file
+ type (i.e. missing in `filetypes`), is added to the upload queue, upload process of all files will be permanently interrupted.
+**(2)** Use reasonably small number in `max_files`.
+**(3)** When uploading two (or more) folders with Chrome, there is
+ a bug in resumable.js which makes only one of the
+ folders to be uploaded. See:
+ https://github.com/23/resumable.js/issues/416
+**(4)** When uploading folders, note that the subdirectories
+ are **not** created -> All files in the folders will
+ be uploaded to the single upload folder.
+
+
+
+## 3 Callbacks
+
+Callbacks can be defined using two different approaches
+- `@du.callback`: short notation, for typical use cases
+- `@app.callback`: needs more verbose code. In case you need more control than when using the `@du.callback`.
+
+
+In the following example it is assumed that the `du.Upload` component id is `dash-uploader`, i.e. the component was created with:
+
+```
+du.Upload(
+ id='dash-uploader',
+)
+```
+
+### `@du.callback`
+
+*New in version **0.3.0***
+
+Easiest way to call a simple callback after uploading would be something like:
+
+```python
+@du.callback(
+ output=Output('callback-output', 'children'),
+ id='dash-uploader',
+)
+def get_a_list(filenames):
+ return html.Ul([html.Li(filenames)])
+```
+
+The syntax is
+```python
+@du.callback(
+ output=