From 644b1b134f58f8fb5bc204576a97504f67ad4481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Fredrik=20Ki=C3=A6r?= Date: Tue, 18 Aug 2020 12:35:37 +0200 Subject: [PATCH 1/8] Add support for calculating CSP hashes of inline scripts --- CHANGELOG.md | 3 ++ dash/dash.py | 38 ++++++++++++++++++++++ requires-testing.txt | 1 + tests/integration/test_csp.py | 59 +++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 tests/integration/test_csp.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fbc913ce2..2854ece58b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). ## [UNRELEASED] +### Added +- [#1371](https://github.com/plotly/dash/pull/1371) You can now get [CSP `script-src` hashes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) of all added inline scripts by calling `app.csp_hashes()` (both Dash internal inline scripts, and those added with `app.clientside_callback`) . + ### Fixed - [#1384](https://github.com/plotly/dash/pull/1384) Fixed a bug introduced by [#1180](https://github.com/plotly/dash/pull/1180) breaking use of `prevent_initial_call` as a positional arg in callback definitions diff --git a/dash/dash.py b/dash/dash.py index c17ddbe271..4720c83f4f 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -10,6 +10,8 @@ import re import logging import mimetypes +import hashlib +import base64 from functools import wraps from future.moves.urllib.parse import urlparse @@ -1128,6 +1130,42 @@ def _serve_default_favicon(): pkgutil.get_data("dash", "favicon.ico"), content_type="image/x-icon" ) + def csp_hashes(self, hash_algorithm="sha256"): + """Calculates CSP hashes (sha + base64) of all inline scripts, such that + one of the biggest benefits of CSP (disallowing general inline scripts) + can be utilized together with Dash clientside callbacks (inline scripts). + + Calculate these hashes after all inline callbacks are defined, + and add them to your CSP headers before starting the server, for example + with the flask-talisman package from PyPI: + + flask_talisman.Talisman(app.server, content_security_policy={ + "default-src": "'self'", + "script-src": ["'self'"] + app.csp_hashes() + }) + + :param hash_algorithm: One of the recognized CSP hash algorithms ('sha256', 'sha384', 'sha512'). + :return: List of CSP hash strings of all inline scripts. + """ + + HASH_ALGORITHMS = ["sha256", "sha384", "sha512"] + if hash_algorithm not in HASH_ALGORITHMS: + raise ValueError( + "Possible CSP hash algorithms: " + ", ".join(HASH_ALGORITHMS) + ) + + method = getattr(hashlib, hash_algorithm) + + return [ + "'{hash_algorithm}-{base64_hash}'".format( + hash_algorithm=hash_algorithm, + base64_hash=base64.b64encode( + method(script.encode("utf-8")).digest() + ).decode("utf-8"), + ) + for script in self._inline_scripts + [self.renderer] + ] + def get_asset_url(self, path): asset = get_asset_path( self.config.requests_pathname_prefix, diff --git a/requires-testing.txt b/requires-testing.txt index 1662b98057..1b14e7d6e9 100644 --- a/requires-testing.txt +++ b/requires-testing.txt @@ -10,3 +10,4 @@ cryptography==3.0 requests[security]==2.21.0 beautifulsoup4==4.8.2 waitress==1.4.3 +flask-talisman==0.7.0 diff --git a/tests/integration/test_csp.py b/tests/integration/test_csp.py new file mode 100644 index 0000000000..a6e3df6fe7 --- /dev/null +++ b/tests/integration/test_csp.py @@ -0,0 +1,59 @@ +import contextlib + +import pytest +import flask_talisman +from selenium.common.exceptions import NoSuchElementException + +import dash +import dash_core_components as dcc +import dash_html_components as html +from dash.dependencies import Input, Output + + +@contextlib.contextmanager +def does_not_raise(): + yield + + +@pytest.mark.parametrize( + "add_hashes, hash_algorithm, expectation", + [ + (False, None, pytest.raises(NoSuchElementException)), + (True, "sha256", does_not_raise()), + (True, "sha384", does_not_raise()), + (True, "sha512", does_not_raise()), + (True, "sha999", pytest.raises(ValueError)), + ], +) +def test_csp_hashes_inline_scripts(dash_duo, add_hashes, hash_algorithm, expectation): + app = dash.Dash(__name__) + + app.layout = html.Div( + [dcc.Input(id="input_element", type="text"), html.Div(id="output_element")] + ) + + app.clientside_callback( + """ + function(input) { + return input; + } + """, + Output("output_element", "children"), + [Input("input_element", "value")], + ) + + with expectation: + csp = { + "default-src": "'self'", + "script-src": ["'self'"] + + (app.csp_hashes(hash_algorithm) if add_hashes else []), + } + + flask_talisman.Talisman( + app.server, content_security_policy=csp, force_https=False + ) + + dash_duo.start_server(app) + + dash_duo.find_element("#input_element").send_keys("xyz") + assert dash_duo.wait_for_element("#output_element").text == "xyz" From d87335b099fa8f9a4e854a8cbd815dda8b48e099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Rivet?= Date: Wed, 2 Sep 2020 20:43:00 -0400 Subject: [PATCH 2/8] Issue 1350 - Multi-input callback with sync event handling (#1385) --- CHANGELOG.md | 3 + dash-renderer/src/APIController.react.js | 8 +- dash-renderer/src/AppContainer.react.js | 2 - dash-renderer/src/StoreObserver.ts | 1 + .../components/core/DocumentTitle.react.js | 51 ---- dash-renderer/src/observers/documentTitle.ts | 58 +++++ .../src/observers/requestedCallbacks.ts | 85 +++++-- dash-renderer/src/store.ts | 2 + dash-renderer/src/utils/wait.ts | 8 + .../callbacks/test_basic_callback.py | 28 ++- .../callbacks/test_callback_context.py | 238 ++++++++++++++++++ .../devtools/test_callback_validation.py | 2 +- .../integration/renderer/test_multi_output.py | 16 +- 13 files changed, 406 insertions(+), 96 deletions(-) delete mode 100644 dash-renderer/src/components/core/DocumentTitle.react.js create mode 100644 dash-renderer/src/observers/documentTitle.ts create mode 100644 dash-renderer/src/utils/wait.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fbc913ce2..074763624d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). ## [UNRELEASED] +### Changed +- [#1385](https://github.com/plotly/dash/pull/1385) Closes [#1350](https://github.com/plotly/dash/issues/1350) and fixes a previously undefined callback behavior when multiple elements are stacked on top of one another and their `n_clicks` props are used as inputs of the same callback. The callback will now trigger once with all the triggered `n_clicks` props changes. + ### Fixed - [#1384](https://github.com/plotly/dash/pull/1384) Fixed a bug introduced by [#1180](https://github.com/plotly/dash/pull/1180) breaking use of `prevent_initial_call` as a positional arg in callback definitions diff --git a/dash-renderer/src/APIController.react.js b/dash-renderer/src/APIController.react.js index 8479b0fd07..46453046c6 100644 --- a/dash-renderer/src/APIController.react.js +++ b/dash-renderer/src/APIController.react.js @@ -20,6 +20,7 @@ import {applyPersistence} from './persistence'; import {getAppState} from './reducers/constants'; import {STATUS} from './constants/constants'; import {getLoadingState, getLoadingHash} from './utils/TreeContainer'; +import wait from './utils/wait'; export const DashContext = createContext({}); @@ -63,8 +64,11 @@ const UnconnectedContainer = props => { useEffect(() => { if (renderedTree.current) { - renderedTree.current = false; - events.current.emit('rendered'); + (async () => { + renderedTree.current = false; + await wait(0); + events.current.emit('rendered'); + })(); } }); diff --git a/dash-renderer/src/AppContainer.react.js b/dash-renderer/src/AppContainer.react.js index 0dcb5e2a65..d03efb0f16 100644 --- a/dash-renderer/src/AppContainer.react.js +++ b/dash-renderer/src/AppContainer.react.js @@ -2,7 +2,6 @@ import {connect} from 'react-redux'; import React from 'react'; import PropTypes from 'prop-types'; import APIController from './APIController.react'; -import DocumentTitle from './components/core/DocumentTitle.react'; import Loading from './components/core/Loading.react'; import Toolbar from './components/core/Toolbar.react'; import Reloader from './components/core/Reloader.react'; @@ -48,7 +47,6 @@ class UnconnectedAppContainer extends React.Component { {show_undo_redo ? : null} - diff --git a/dash-renderer/src/StoreObserver.ts b/dash-renderer/src/StoreObserver.ts index 4bc82382f0..45b31e8d2c 100644 --- a/dash-renderer/src/StoreObserver.ts +++ b/dash-renderer/src/StoreObserver.ts @@ -21,6 +21,7 @@ interface IStoreObserverState { export interface IStoreObserverDefinition { observer: Observer>; inputs: string[] + [key: string]: any; } export default class StoreObserver { diff --git a/dash-renderer/src/components/core/DocumentTitle.react.js b/dash-renderer/src/components/core/DocumentTitle.react.js deleted file mode 100644 index bfea61831e..0000000000 --- a/dash-renderer/src/components/core/DocumentTitle.react.js +++ /dev/null @@ -1,51 +0,0 @@ -import {connect} from 'react-redux'; -import {Component} from 'react'; -import PropTypes from 'prop-types'; - -class DocumentTitle extends Component { - constructor(props) { - super(props); - const {update_title} = props.config; - this.state = { - title: document.title, - update_title, - }; - } - - UNSAFE_componentWillReceiveProps(props) { - if (!this.state.update_title) { - // Let callbacks or other components have full control over title - return; - } - if (props.isLoading) { - this.setState({title: document.title}); - if (this.state.update_title) { - document.title = this.state.update_title; - } - } else { - if (document.title === this.state.update_title) { - document.title = this.state.title; - } else { - this.setState({title: document.title}); - } - } - } - - shouldComponentUpdate() { - return false; - } - - render() { - return null; - } -} - -DocumentTitle.propTypes = { - isLoading: PropTypes.bool.isRequired, - config: PropTypes.shape({update_title: PropTypes.string}), -}; - -export default connect(state => ({ - isLoading: state.isLoading, - config: state.config, -}))(DocumentTitle); diff --git a/dash-renderer/src/observers/documentTitle.ts b/dash-renderer/src/observers/documentTitle.ts new file mode 100644 index 0000000000..3cbd893ab2 --- /dev/null +++ b/dash-renderer/src/observers/documentTitle.ts @@ -0,0 +1,58 @@ +import { IStoreObserverDefinition } from '../StoreObserver'; +import { IStoreState } from '../store'; + +const updateTitle = (getState: () => IStoreState) => { + const { + config, + isLoading + } = getState(); + + const update_title = config?.update_title; + + if (!update_title) { + return; + } + + if (isLoading) { + if (document.title !== update_title) { + observer.title = document.title; + document.title = update_title; + } + } else { + if (document.title === update_title) { + document.title = observer.title; + } else { + observer.title = document.title; + } + } +}; + +const observer: IStoreObserverDefinition = { + inputs: ['isLoading'], + mutationObserver: undefined, + observer: ({ + getState + }) => { + const { + config + } = getState(); + + if (observer.config !== config) { + observer.config = config; + observer.mutationObserver?.disconnect(); + observer.mutationObserver = new MutationObserver(() => updateTitle(getState)); + + const title = document.querySelector('title'); + if (title) { + observer.mutationObserver.observe( + title, + { subtree: true, childList: true, attributes: true, characterData: true } + ); + } + } + + updateTitle(getState); + } +}; + +export default observer; diff --git a/dash-renderer/src/observers/requestedCallbacks.ts b/dash-renderer/src/observers/requestedCallbacks.ts index 2d787a43a1..bae79e4d5a 100644 --- a/dash-renderer/src/observers/requestedCallbacks.ts +++ b/dash-renderer/src/observers/requestedCallbacks.ts @@ -4,12 +4,17 @@ import { difference, filter, flatten, + forEach, groupBy, includes, intersection, isEmpty, isNil, map, + mergeLeft, + mergeWith, + pluck, + reduce, values } from 'ramda'; @@ -17,16 +22,16 @@ import { IStoreState } from '../store'; import { aggregateCallbacks, - removeRequestedCallbacks, removePrioritizedCallbacks, removeExecutingCallbacks, removeWatchedCallbacks, - addRequestedCallbacks, addPrioritizedCallbacks, addExecutingCallbacks, addWatchedCallbacks, removeBlockedCallbacks, - addBlockedCallbacks + addBlockedCallbacks, + addRequestedCallbacks, + removeRequestedCallbacks } from '../actions/callbacks'; import { isMultiValued } from '../actions/dependencies'; @@ -45,17 +50,23 @@ import { IBlockedCallback } from '../types/callbacks'; +import wait from './../utils/wait'; + import { getPendingCallbacks } from '../utils/callbacks'; import { IStoreObserverDefinition } from '../StoreObserver'; const observer: IStoreObserverDefinition = { - observer: ({ + observer: async ({ dispatch, getState }) => { + await wait(0); + const { callbacks, callbacks: { prioritized, blocked, executing, watched, stored }, paths } = getState(); let { callbacks: { requested } } = getState(); + const initialRequested = requested.slice(0); + const pendingCallbacks = getPendingCallbacks(callbacks); /* @@ -78,17 +89,37 @@ const observer: IStoreObserverDefinition = { 1. Remove duplicated `requested` callbacks - give precedence to newer callbacks over older ones */ - /* - Extract all but the first callback from each IOS-key group - these callbacks are duplicates. - */ - const rDuplicates = flatten(map( - group => group.slice(0, -1), - values( - groupBy( - getUniqueIdentifier, - requested - ) + let rDuplicates: ICallback[] = []; + let rMergedDuplicates: ICallback[] = []; + + forEach(group => { + if (group.length === 1) { + // keep callback if its the only one of its kind + rMergedDuplicates.push(group[0]); + } else { + const initial = group.find(cb => cb.initialCall); + if (initial) { + // drop the initial callback if it's not alone + rDuplicates.push(initial); + } + + const groupWithoutInitial = group.filter(cb => cb !== initial); + if (groupWithoutInitial.length === 1) { + // if there's only one callback beside the initial one, keep that callback + rMergedDuplicates.push(groupWithoutInitial[0]); + } else { + // otherwise merge all remaining callbacks together + rDuplicates = concat(rDuplicates, groupWithoutInitial); + rMergedDuplicates.push(mergeLeft({ + changedPropIds: reduce(mergeWith(Math.max), {}, pluck('changedPropIds', groupWithoutInitial)), + executionGroup: filter(exg => !!exg, pluck('executionGroup', groupWithoutInitial)).slice(-1)[0] + }, groupWithoutInitial.slice(-1)[0]) as ICallback); + } + } + }, values( + groupBy( + getUniqueIdentifier, + requested ) )); @@ -97,7 +128,7 @@ const observer: IStoreObserverDefinition = { Clean up the `requested` list - during the dispatch phase, duplicates will be removed for real */ - requested = difference(requested, rDuplicates); + requested = rMergedDuplicates; /* 2. Remove duplicated `prioritized`, `executing` and `watching` callbacks @@ -312,16 +343,24 @@ const observer: IStoreObserverDefinition = { dropped ); + requested = difference( + requested, + readyCallbacks + ); + + const added = difference(requested, initialRequested); + const removed = difference(initialRequested, requested); + dispatch(aggregateCallbacks([ + // Clean up requested callbacks + added.length ? addRequestedCallbacks(added) : null, + removed.length ? removeRequestedCallbacks(removed) : null, // Clean up duplicated callbacks - rDuplicates.length ? removeRequestedCallbacks(rDuplicates) : null, pDuplicates.length ? removePrioritizedCallbacks(pDuplicates) : null, bDuplicates.length ? removeBlockedCallbacks(bDuplicates) : null, eDuplicates.length ? removeExecutingCallbacks(eDuplicates) : null, wDuplicates.length ? removeWatchedCallbacks(wDuplicates) : null, // Prune callbacks - rRemoved.length ? removeRequestedCallbacks(rRemoved) : null, - rAdded.length ? addRequestedCallbacks(rAdded) : null, pRemoved.length ? removePrioritizedCallbacks(pRemoved) : null, pAdded.length ? addPrioritizedCallbacks(pAdded) : null, bRemoved.length ? removeBlockedCallbacks(bRemoved) : null, @@ -330,15 +369,7 @@ const observer: IStoreObserverDefinition = { eAdded.length ? addExecutingCallbacks(eAdded) : null, wRemoved.length ? removeWatchedCallbacks(wRemoved) : null, wAdded.length ? addWatchedCallbacks(wAdded) : null, - // Prune circular callbacks - rCirculars.length ? removeRequestedCallbacks(rCirculars) : null, - // Prune circular assumptions - oldBlocked.length ? removeRequestedCallbacks(oldBlocked) : null, - newBlocked.length ? addRequestedCallbacks(newBlocked) : null, - // Drop non-triggered initial callbacks - dropped.length ? removeRequestedCallbacks(dropped) : null, // Promote callbacks - readyCallbacks.length ? removeRequestedCallbacks(readyCallbacks) : null, readyCallbacks.length ? addPrioritizedCallbacks(readyCallbacks) : null ])); }, diff --git a/dash-renderer/src/store.ts b/dash-renderer/src/store.ts index 3b26f75a92..1989941835 100644 --- a/dash-renderer/src/store.ts +++ b/dash-renderer/src/store.ts @@ -7,6 +7,7 @@ import { ICallbacksState } from './reducers/callbacks'; import { LoadingMapState } from './reducers/loadingMap'; import { IsLoadingState } from './reducers/isLoading'; +import documentTitle from './observers/documentTitle'; import executedCallbacks from './observers/executedCallbacks'; import executingCallbacks from './observers/executingCallbacks'; import isLoading from './observers/isLoading' @@ -33,6 +34,7 @@ const storeObserver = new StoreObserver(); const setObservers = once(() => { const observe = storeObserver.observe; + observe(documentTitle); observe(isLoading); observe(loadingMap); observe(requestedCallbacks); diff --git a/dash-renderer/src/utils/wait.ts b/dash-renderer/src/utils/wait.ts new file mode 100644 index 0000000000..10a2a9ed79 --- /dev/null +++ b/dash-renderer/src/utils/wait.ts @@ -0,0 +1,8 @@ +export default async (duration: number) => { + let _resolve: any; + const p = new Promise(resolve => _resolve = resolve); + + setTimeout(_resolve, duration); + + return p; +} diff --git a/tests/integration/callbacks/test_basic_callback.py b/tests/integration/callbacks/test_basic_callback.py index 13276b1899..afbd81cd97 100644 --- a/tests/integration/callbacks/test_basic_callback.py +++ b/tests/integration/callbacks/test_basic_callback.py @@ -1,5 +1,5 @@ import json -from multiprocessing import Value +from multiprocessing import Lock, Value import pytest @@ -9,9 +9,12 @@ import dash from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate +from dash.testing import wait def test_cbsc001_simple_callback(dash_duo): + lock = Lock() + app = dash.Dash(__name__) app.layout = html.Div( [ @@ -23,8 +26,9 @@ def test_cbsc001_simple_callback(dash_duo): @app.callback(Output("output-1", "children"), [Input("input", "value")]) def update_output(value): - call_count.value = call_count.value + 1 - return value + with lock: + call_count.value = call_count.value + 1 + return value dash_duo.start_server(app) @@ -34,9 +38,11 @@ def update_output(value): input_ = dash_duo.find_element("#input") dash_duo.clear_input(input_) - input_.send_keys("hello world") + for key in "hello world": + with lock: + input_.send_keys(key) - assert dash_duo.find_element("#output-1").text == "hello world" + wait.until(lambda: dash_duo.find_element("#output-1").text == "hello world", 2) dash_duo.percy_snapshot(name="simple-callback-hello-world") assert call_count.value == 2 + len("hello world"), "initial count + each key stroke" @@ -345,6 +351,8 @@ def set_path(n): def test_cbsc008_wildcard_prop_callbacks(dash_duo): + lock = Lock() + app = dash.Dash(__name__) app.layout = html.Div( [ @@ -369,8 +377,9 @@ def test_cbsc008_wildcard_prop_callbacks(dash_duo): @app.callback(Output("output-1", "data-cb"), [Input("input", "value")]) def update_data(value): - input_call_count.value += 1 - return value + with lock: + input_call_count.value += 1 + return value @app.callback(Output("output-1", "children"), [Input("output-1", "data-cb")]) def update_text(data): @@ -382,7 +391,10 @@ def update_text(data): input1 = dash_duo.find_element("#input") dash_duo.clear_input(input1) - input1.send_keys("hello world") + + for key in "hello world": + with lock: + input1.send_keys(key) dash_duo.wait_for_text_to_equal("#output-1", "hello world") dash_duo.percy_snapshot(name="wildcard-callback-2") diff --git a/tests/integration/callbacks/test_callback_context.py b/tests/integration/callbacks/test_callback_context.py index f4f4552c4c..3eabe92e64 100644 --- a/tests/integration/callbacks/test_callback_context.py +++ b/tests/integration/callbacks/test_callback_context.py @@ -1,4 +1,5 @@ import json +import operator import pytest import dash_html_components as html @@ -9,6 +10,9 @@ from dash.dependencies import Input, Output from dash.exceptions import PreventUpdate, MissingCallbackContextException +import dash.testing.wait as wait + +from selenium.webdriver.common.action_chains import ActionChains def test_cbcx001_modified_response(dash_duo): @@ -96,3 +100,237 @@ def report_triggered(n): 'triggered is truthy, has prop/id ["btn", "n_clicks"], and full value ' '[{"prop_id": "btn.n_clicks", "value": 1}]', ) + + +@pytest.mark.DASH1350 +def test_cbcx005_grouped_clicks(dash_duo): + class context: + calls = 0 + callback_contexts = [] + clicks = dict() + + app = Dash(__name__) + app.layout = html.Div( + [ + html.Button("Button 0", id="btn0"), + html.Div( + [ + html.Button("Button 1", id="btn1"), + html.Div( + [html.Div(id="div3"), html.Button("Button 2", id="btn2")], + id="div2", + style=dict(backgroundColor="yellow", padding="50px"), + ), + ], + id="div1", + style=dict(backgroundColor="blue", padding="50px"), + ), + ], + id="div0", + style=dict(backgroundColor="red", padding="50px"), + ) + + @app.callback( + Output("div3", "children"), + [ + Input("div1", "n_clicks"), + Input("div2", "n_clicks"), + Input("btn0", "n_clicks"), + Input("btn1", "n_clicks"), + Input("btn2", "n_clicks"), + ], + prevent_initial_call=True, + ) + def update(div1, div2, btn0, btn1, btn2): + context.calls = context.calls + 1 + context.callback_contexts.append(callback_context.triggered) + context.clicks["div1"] = div1 + context.clicks["div2"] = div2 + context.clicks["btn0"] = btn0 + context.clicks["btn1"] = btn1 + context.clicks["btn2"] = btn2 + + def click(target): + ActionChains(dash_duo.driver).move_to_element_with_offset( + target, 5, 5 + ).click().perform() + + dash_duo.start_server(app) + click(dash_duo.find_element("#btn0")) + assert context.calls == 1 + keys = list(map(operator.itemgetter("prop_id"), context.callback_contexts[-1:][0])) + assert len(keys) == 1 + assert "btn0.n_clicks" in keys + + assert context.clicks.get("btn0") == 1 + assert context.clicks.get("btn1") is None + assert context.clicks.get("btn2") is None + assert context.clicks.get("div1") is None + assert context.clicks.get("div2") is None + + click(dash_duo.find_element("#div1")) + assert context.calls == 2 + keys = list(map(operator.itemgetter("prop_id"), context.callback_contexts[-1:][0])) + assert len(keys) == 1 + assert "div1.n_clicks" in keys + + assert context.clicks.get("btn0") == 1 + assert context.clicks.get("btn1") is None + assert context.clicks.get("btn2") is None + assert context.clicks.get("div1") == 1 + assert context.clicks.get("div2") is None + + click(dash_duo.find_element("#btn1")) + assert context.calls == 3 + keys = list(map(operator.itemgetter("prop_id"), context.callback_contexts[-1:][0])) + assert len(keys) == 2 + assert "btn1.n_clicks" in keys + assert "div1.n_clicks" in keys + + assert context.clicks.get("btn0") == 1 + assert context.clicks.get("btn1") == 1 + assert context.clicks.get("btn2") is None + assert context.clicks.get("div1") == 2 + assert context.clicks.get("div2") is None + + click(dash_duo.find_element("#div2")) + assert context.calls == 4 + keys = list(map(operator.itemgetter("prop_id"), context.callback_contexts[-1:][0])) + assert len(keys) == 2 + assert "div1.n_clicks" in keys + assert "div2.n_clicks" in keys + + assert context.clicks.get("btn0") == 1 + assert context.clicks.get("btn1") == 1 + assert context.clicks.get("btn2") is None + assert context.clicks.get("div1") == 3 + assert context.clicks.get("div2") == 1 + + click(dash_duo.find_element("#btn2")) + assert context.calls == 5 + keys = list(map(operator.itemgetter("prop_id"), context.callback_contexts[-1:][0])) + assert len(keys) == 3 + assert "btn2.n_clicks" in keys + assert "div1.n_clicks" in keys + assert "div2.n_clicks" in keys + + assert context.clicks.get("btn0") == 1 + assert context.clicks.get("btn1") == 1 + assert context.clicks.get("btn2") == 1 + assert context.clicks.get("div1") == 4 + assert context.clicks.get("div2") == 2 + + +@pytest.mark.DASH1350 +def test_cbcx006_initial_callback_predecessor(dash_duo): + class context: + calls = 0 + callback_contexts = [] + + app = Dash(__name__) + app.layout = html.Div( + [ + html.Div( + style={"display": "block"}, + children=[ + html.Div( + [ + html.Label("ID: input-number-1"), + dcc.Input(id="input-number-1", type="number", value=0), + ] + ), + html.Div( + [ + html.Label("ID: input-number-2"), + dcc.Input(id="input-number-2", type="number", value=0), + ] + ), + html.Div( + [ + html.Label("ID: sum-number"), + dcc.Input( + id="sum-number", type="number", value=0, disabled=True + ), + ] + ), + ], + ), + html.Div(id="results"), + ] + ) + + @app.callback( + Output("sum-number", "value"), + [Input("input-number-1", "value"), Input("input-number-2", "value")], + ) + def update_sum_number(n1, n2): + context.calls = context.calls + 1 + context.callback_contexts.append(callback_context.triggered) + + return n1 + n2 + + @app.callback( + Output("results", "children"), + [ + Input("input-number-1", "value"), + Input("input-number-2", "value"), + Input("sum-number", "value"), + ], + ) + def update_results(n1, n2, nsum): + context.calls = context.calls + 1 + context.callback_contexts.append(callback_context.triggered) + + return [ + "{} + {} = {}".format(n1, n2, nsum), + html.Br(), + "ctx.triggered={}".format(callback_context.triggered), + ] + + dash_duo.start_server(app) + + # Initial Callbacks + wait.until(lambda: context.calls == 2, 2) + wait.until(lambda: len(context.callback_contexts) == 2, 2) + + keys0 = list(map(operator.itemgetter("prop_id"), context.callback_contexts[0])) + # Special case present for backward compatibility + assert len(keys0) == 1 + assert "." in keys0 + + keys1 = list(map(operator.itemgetter("prop_id"), context.callback_contexts[1])) + assert len(keys1) == 1 + assert "sum-number.value" in keys1 + + # User action & followup callbacks + dash_duo.find_element("#input-number-1").click() + dash_duo.find_element("#input-number-1").send_keys("1") + + wait.until(lambda: context.calls == 4, 2) + wait.until(lambda: len(context.callback_contexts) == 4, 2) + + keys0 = list(map(operator.itemgetter("prop_id"), context.callback_contexts[2])) + # Special case present for backward compatibility + assert len(keys0) == 1 + assert "input-number-1.value" in keys0 + + keys1 = list(map(operator.itemgetter("prop_id"), context.callback_contexts[3])) + assert len(keys1) == 2 + assert "sum-number.value" in keys1 + assert "input-number-1.value" in keys1 + + dash_duo.find_element("#input-number-2").click() + dash_duo.find_element("#input-number-2").send_keys("1") + + wait.until(lambda: context.calls == 6, 2) + wait.until(lambda: len(context.callback_contexts) == 6, 2) + + keys0 = list(map(operator.itemgetter("prop_id"), context.callback_contexts[4])) + # Special case present for backward compatibility + assert len(keys0) == 1 + assert "input-number-2.value" in keys0 + + keys1 = list(map(operator.itemgetter("prop_id"), context.callback_contexts[5])) + assert len(keys1) == 2 + assert "sum-number.value" in keys1 + assert "input-number-2.value" in keys1 diff --git a/tests/integration/devtools/test_callback_validation.py b/tests/integration/devtools/test_callback_validation.py index a006e23eec..4f57e01254 100644 --- a/tests/integration/devtools/test_callback_validation.py +++ b/tests/integration/devtools/test_callback_validation.py @@ -677,7 +677,7 @@ def c2(children): @app.callback([Output("a", "children")], [Input("c", "children")]) def c3(children): - return children + return (children,) dash_duo.start_server(app, **debugging) diff --git a/tests/integration/renderer/test_multi_output.py b/tests/integration/renderer/test_multi_output.py index 4741471420..433301ea57 100644 --- a/tests/integration/renderer/test_multi_output.py +++ b/tests/integration/renderer/test_multi_output.py @@ -1,8 +1,9 @@ -from multiprocessing import Value +from multiprocessing import Lock, Value import dash from dash.dependencies import Input, Output from dash.exceptions import PreventUpdate +from dash.testing import wait import dash_core_components as dcc import dash_html_components as html @@ -47,6 +48,8 @@ def update_output(n_clicks): def test_rdmo002_multi_outputs_on_single_component(dash_duo): + lock = Lock() + call_count = Value("i") app = dash.Dash(__name__) @@ -66,8 +69,9 @@ def test_rdmo002_multi_outputs_on_single_component(dash_duo): [Input("input", "value")], ) def update_output(value): - call_count.value += 1 - return [value, {"fontFamily": value}, value] + with lock: + call_count.value += 1 + return [value, {"fontFamily": value}, value] dash_duo.start_server(app) @@ -79,7 +83,9 @@ def update_output(value): assert call_count.value == 1 - dash_duo.find_element("#input").send_keys(" hello") + for key in " hello": + with lock: + dash_duo.find_element("#input").send_keys(key) dash_duo.wait_for_text_to_equal("#output-container", "dash hello") _html = dash_duo.find_element("#output-container").get_property("innerHTML") @@ -88,7 +94,7 @@ def update_output(value): 'style="font-family: "dash hello";">dash hello' ) - assert call_count.value == 7 + wait.until(lambda: call_count.value == 7, 3) def test_rdmo003_single_output_as_multi(dash_duo): From 6881805b7d5b873b763caafe5685b32dfcced478 Mon Sep 17 00:00:00 2001 From: Alex Johnson Date: Wed, 2 Sep 2020 22:14:33 -0400 Subject: [PATCH 3/8] Update tests/integration/test_csp.py --- tests/integration/test_csp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_csp.py b/tests/integration/test_csp.py index a6e3df6fe7..1657333ffa 100644 --- a/tests/integration/test_csp.py +++ b/tests/integration/test_csp.py @@ -25,7 +25,7 @@ def does_not_raise(): (True, "sha999", pytest.raises(ValueError)), ], ) -def test_csp_hashes_inline_scripts(dash_duo, add_hashes, hash_algorithm, expectation): +def test_incs001_csp_hashes_inline_scripts(dash_duo, add_hashes, hash_algorithm, expectation): app = dash.Dash(__name__) app.layout = html.Div( From c62e51bac21c26f73fd3fcd973063b63540cc78d Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 2 Sep 2020 22:22:05 -0400 Subject: [PATCH 4/8] black --- tests/integration/test_csp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_csp.py b/tests/integration/test_csp.py index 1657333ffa..6074ac30c8 100644 --- a/tests/integration/test_csp.py +++ b/tests/integration/test_csp.py @@ -25,7 +25,9 @@ def does_not_raise(): (True, "sha999", pytest.raises(ValueError)), ], ) -def test_incs001_csp_hashes_inline_scripts(dash_duo, add_hashes, hash_algorithm, expectation): +def test_incs001_csp_hashes_inline_scripts( + dash_duo, add_hashes, hash_algorithm, expectation +): app = dash.Dash(__name__) app.layout = html.Div( From eb20cfe5d6f5b8cd95f570cc8575d17803fa8959 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 2 Sep 2020 22:50:04 -0400 Subject: [PATCH 5/8] noise From 5e717f1bd793ecaffc1200d97749b141feff98d2 Mon Sep 17 00:00:00 2001 From: jjaraalm Date: Thu, 3 Sep 2020 11:31:42 -0400 Subject: [PATCH 6/8] Improve Callback Graph (#1179) --- .../dash-test-components/package-lock.json | 2459 +++++++---------- CHANGELOG.md | 2 +- dash-renderer/.prettierrc | 5 +- dash-renderer/package-lock.json | 131 +- dash-renderer/package.json | 12 +- dash-renderer/src/APIController.react.js | 22 +- dash-renderer/src/AccessDenied.react.js | 2 +- dash-renderer/src/AppContainer.react.js | 10 +- dash-renderer/src/AppProvider.react.tsx | 2 - dash-renderer/src/StoreObserver.ts | 76 +- dash-renderer/src/TreeContainer.js | 26 +- dash-renderer/src/actions/api.js | 16 +- dash-renderer/src/actions/callbacks.ts | 298 +- dash-renderer/src/actions/constants.js | 2 +- dash-renderer/src/actions/dependencies.js | 60 +- dash-renderer/src/actions/dependencies_ts.ts | 191 +- dash-renderer/src/actions/index.js | 12 +- dash-renderer/src/actions/isAppReady.js | 2 +- dash-renderer/src/actions/isLoading.ts | 8 +- dash-renderer/src/actions/loadingMap.ts | 8 +- dash-renderer/src/actions/paths.js | 2 +- .../src/components/core/Loading.react.js | 6 +- .../src/components/core/Reloader.react.js | 10 +- .../src/components/core/Toolbar.react.js | 28 +- .../CallbackGraph/CallbackGraphContainer.css | 35 +- .../CallbackGraphContainer.react.js | 495 +++- .../CallbackGraphContainerStylesheet.js | 154 ++ .../CallbackGraph/CallbackGraphEffects.js | 161 ++ .../error/ComponentErrorBoundary.react.js | 8 +- .../error/FrontEnd/FrontEndError.react.js | 46 +- .../FrontEnd/FrontEndErrorContainer.react.js | 12 +- .../error/GlobalErrorContainer.react.js | 18 +- .../GlobalErrorContainerPassthrough.react.js | 4 +- .../error/GlobalErrorOverlay.react.js | 4 +- .../components/error/menu/DebugMenu.react.js | 29 +- dash-renderer/src/constants/constants.js | 6 + dash-renderer/src/observers/documentTitle.ts | 31 +- .../src/observers/executedCallbacks.ts | 151 +- .../src/observers/executingCallbacks.ts | 75 +- dash-renderer/src/observers/isLoading.ts | 19 +- dash-renderer/src/observers/loadingMap.ts | 90 +- .../src/observers/prioritizedCallbacks.ts | 169 +- .../src/observers/requestedCallbacks.ts | 300 +- .../src/observers/storedCallbacks.ts | 52 +- dash-renderer/src/persistence.js | 18 +- dash-renderer/src/reducers/callbacks.ts | 81 +- dash-renderer/src/reducers/changed.js | 13 + dash-renderer/src/reducers/constants.js | 2 +- dash-renderer/src/reducers/error.js | 10 +- dash-renderer/src/reducers/history.js | 8 +- dash-renderer/src/reducers/isLoading.ts | 8 +- dash-renderer/src/reducers/layout.js | 2 +- dash-renderer/src/reducers/loadingMap.ts | 8 +- dash-renderer/src/reducers/profile.js | 86 + dash-renderer/src/reducers/reducer.js | 45 +- dash-renderer/src/registry.js | 2 +- dash-renderer/src/store.ts | 12 +- dash-renderer/src/styles/styles.js | 16 +- dash-renderer/src/types/callbacks.ts | 14 +- dash-renderer/src/utils/TreeContainer.ts | 36 +- dash-renderer/src/utils/callbacks.ts | 11 +- dash-renderer/src/utils/wait.ts | 4 +- dash-renderer/tslint.json | 8 +- dash/_callback_context.py | 24 + dash/dash.py | 29 + dash/testing/browser.py | 47 +- tests/__init__.py | 0 tests/assets/__init__.py | 0 tests/assets/todo_app.py | 123 + tests/integration/callbacks/__init__.py | 0 tests/integration/callbacks/test_wildcards.py | 120 +- tests/integration/devtools/__init__.py | 0 .../devtools/test_callback_timing.py | 38 + .../integration/devtools/test_devtools_ui.py | 37 + 74 files changed, 3395 insertions(+), 2656 deletions(-) create mode 100644 dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainerStylesheet.js create mode 100644 dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js create mode 100644 dash-renderer/src/reducers/changed.js create mode 100644 dash-renderer/src/reducers/profile.js create mode 100644 tests/__init__.py create mode 100644 tests/assets/__init__.py create mode 100644 tests/assets/todo_app.py create mode 100644 tests/integration/callbacks/__init__.py create mode 100644 tests/integration/devtools/__init__.py create mode 100644 tests/integration/devtools/test_callback_timing.py diff --git a/@plotly/dash-test-components/package-lock.json b/@plotly/dash-test-components/package-lock.json index f127880c0e..e99d73699c 100644 --- a/@plotly/dash-test-components/package-lock.json +++ b/@plotly/dash-test-components/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/cli": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.4.tgz", - "integrity": "sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.11.5.tgz", + "integrity": "sha512-0umMDxxdEZ98EMZtS9Wgnaf4NdgqBcQHaGYaMfAmP+ZicVglZ2+QZwoHNacfnUq4hCmC1V7Ap5Phq7FInpWrWg==", "dev": true, "requires": { "chokidar": "^2.1.8", @@ -15,10 +15,10 @@ "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "make-dir": "^2.1.0", "slash": "^2.0.0", - "source-map": "^0.5.0" + "source-map": "^0.6.1" }, "dependencies": { "commander": { @@ -26,57 +26,51 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, "requires": { - "@babel/highlight": "^7.8.3" + "@babel/highlight": "^7.10.4" } }, "@babel/compat-data": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", - "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", + "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", "dev": true, "requires": { - "browserslist": "^4.9.1", + "browserslist": "^4.12.0", "invariant": "^2.2.4", "semver": "^5.5.0" } }, "@babel/core": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", - "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helpers": "^7.9.0", - "@babel/parser": "^7.9.0", - "@babel/template": "^7.8.6", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.5.tgz", + "integrity": "sha512-fsEANVOcZHzrsV6dMVWqpSeXClq3lNbYrfFGme6DE25FQWe7pyeYpXyx9guqUnpy466JLzZ8z4uwSr2iv60V5Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", - "source-map": "^0.5.0" + "source-map": "^0.6.1" }, "dependencies": { "debug": { @@ -93,385 +87,432 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, "@babel/generator": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", - "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.5.tgz", + "integrity": "sha512-9UqHWJ4IwRTy4l0o8gq2ef8ws8UPzvtMkVKjTLAiRmza9p9V6Z+OfuNd9fB1j5Q67F+dVJtPC2sZXI8NM9br4g==", "dev": true, "requires": { - "@babel/types": "^7.9.5", + "@babel/types": "^7.11.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "source-map": "^0.6.1" } }, "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-react-jsx": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz", - "integrity": "sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", + "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/types": "^7.9.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-react-jsx-experimental": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.5.tgz", - "integrity": "sha512-HAagjAC93tk748jcXpZ7oYRZH485RCq/+yEv9SIWezHRPv9moZArTnkUNciUNzvwHUABmiWKlcxJvMcu59UwTg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.11.5.tgz", + "integrity": "sha512-Vc4aPJnRZKWfzeCBsqTBnzulVNjABVdahSPhtdMD3Vs80ykx4a87jTHtF/VR+alSrDmNvat7l13yrRHauGcHVw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-module-imports": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/types": "^7.11.5" } }, "@babel/helper-compilation-targets": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", - "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.6", - "browserslist": "^4.9.1", + "@babel/compat-data": "^7.10.4", + "browserslist": "^4.12.0", "invariant": "^2.2.4", "levenary": "^1.1.1", "semver": "^5.5.0" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", - "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-regex": "^7.8.3", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", "regexpu-core": "^4.7.0" } }, "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", + "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", "dev": true, "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.11.0" } }, "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-module-transforms": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", - "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.6", - "@babel/types": "^7.9.0", - "lodash": "^4.17.13" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.10.4" } }, "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", "dev": true, "requires": { - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", + "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-replace-supers": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", - "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.11.0" } }, "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", "dev": true, "requires": { - "@babel/types": "^7.8.3" + "@babel/types": "^7.11.0" } }, "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", "dev": true }, "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", "dev": true, "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.9.0", - "@babel/types": "^7.9.0" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz", - "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==" + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, + "@babel/plugin-proposal-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", + "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", + "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", - "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz", - "integrity": "sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", + "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.9.5" + "@babel/plugin-transform-parameters": "^7.10.4" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", - "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, + "@babel/plugin-proposal-private-methods": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz", - "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.8", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { @@ -483,6 +524,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, "@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", @@ -492,6 +542,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-json-strings": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", @@ -502,12 +561,21 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", - "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz", + "integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-nullish-coalescing-operator": { @@ -520,12 +588,12 @@ } }, "@babel/plugin-syntax-numeric-separator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", - "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-object-rest-spread": { @@ -556,434 +624,461 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", + "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz", - "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.6", - "@babel/helper-split-export-declaration": "^7.8.3", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz", - "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-for-of": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz", - "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", - "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", - "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", - "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz", - "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4" } }, "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" } }, "@babel/plugin-transform-parameters": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz", - "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", - "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz", + "integrity": "sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.4.tgz", - "integrity": "sha512-Mjqf3pZBNLt854CK0C/kRuXAnE6H/bo7xYojP+WGtX8glDGSibcwnsWwhwoSuRg0+EBnxPC1ouVnuetUIlPSAw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz", + "integrity": "sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx": "^7.9.0", - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-builder-react-jsx": "^7.10.4", + "@babel/helper-builder-react-jsx-experimental": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" } }, "@babel/plugin-transform-react-jsx-development": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.9.0.tgz", - "integrity": "sha512-tK8hWKrQncVvrhvtOiPpKrQjfNX3DtkNLSX4ObuGcpS9p0QrGetKmlySIGR07y48Zft8WVgPakqd/bk46JrMSw==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.11.5.tgz", + "integrity": "sha512-cImAmIlKJ84sDmpQzm4/0q/2xrXlDezQoixy3qoz1NJeZL/8PRon6xZtluvr4H4FzwlDGI5tCcFupMnXGtr+qw==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx-experimental": "^7.9.0", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-builder-react-jsx-experimental": "^7.11.5", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" } }, "@babel/plugin-transform-react-jsx-self": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.9.0.tgz", - "integrity": "sha512-K2ObbWPKT7KUTAoyjCsFilOkEgMvFG+y0FqOl6Lezd0/13kMkkjHskVsZvblRPj1PHA44PrToaZANrryppzTvQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz", + "integrity": "sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz", - "integrity": "sha512-K6m3LlSnTSfRkM6FcRk8saNEeaeyG5k7AVkBU2bZK3+1zdkSED3qNdsWrUgQBeTVD2Tp3VMmerxVO2yM5iITmw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz", + "integrity": "sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz", + "integrity": "sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz", - "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.5.tgz", - "integrity": "sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.9.0", - "@babel/helper-compilation-targets": "^7.8.7", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-numeric-separator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.5", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", + "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.11.0", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-export-namespace-from": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.11.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.11.0", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.9.5", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.9.5", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.9.0", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.9.0", - "@babel/plugin-transform-modules-commonjs": "^7.9.0", - "@babel/plugin-transform-modules-systemjs": "^7.9.0", - "@babel/plugin-transform-modules-umd": "^7.9.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.9.5", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.7", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.11.0", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.9.5", - "browserslist": "^4.9.1", + "@babel/types": "^7.11.5", + "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", "levenary": "^1.1.1", @@ -991,9 +1086,9 @@ } }, "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1004,53 +1099,54 @@ } }, "@babel/preset-react": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.4.tgz", - "integrity": "sha512-AxylVB3FXeOTQXNXyiuAQJSvss62FEotbX2Pzx3K/7c+MKJMdSg6Ose6QYllkdCFA8EInCJVw7M/o5QbLuA4ZQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.4.tgz", + "integrity": "sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-transform-react-display-name": "^7.8.3", - "@babel/plugin-transform-react-jsx": "^7.9.4", - "@babel/plugin-transform-react-jsx-development": "^7.9.0", - "@babel/plugin-transform-react-jsx-self": "^7.9.0", - "@babel/plugin-transform-react-jsx-source": "^7.9.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-react-display-name": "^7.10.4", + "@babel/plugin-transform-react-jsx": "^7.10.4", + "@babel/plugin-transform-react-jsx-development": "^7.10.4", + "@babel/plugin-transform-react-jsx-self": "^7.10.4", + "@babel/plugin-transform-react-jsx-source": "^7.10.4", + "@babel/plugin-transform-react-pure-annotations": "^7.10.4" } }, "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/traverse": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", - "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.5", - "@babel/helper-function-name": "^7.9.5", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.5", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" }, "dependencies": { "debug": { @@ -1071,13 +1167,13 @@ } }, "@babel/types": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", - "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.5", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, @@ -1274,6 +1370,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true + }, "acorn-jsx": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", @@ -1281,9 +1383,9 @@ "dev": true }, "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -1299,9 +1401,9 @@ "dev": true }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, "ansi-regex": { @@ -1372,6 +1474,26 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, "assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", @@ -1445,9 +1567,9 @@ } }, "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "dev": true, "requires": { "object.assign": "^4.1.0" @@ -1549,9 +1671,9 @@ "dev": true }, "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", "dev": true }, "brace-expansion": { @@ -1643,21 +1765,50 @@ "requires": { "bn.js": "^4.1.0", "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "browserify-zlib": { @@ -1670,15 +1821,15 @@ } }, "browserslist": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz", - "integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==", + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz", + "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001038", - "electron-to-chromium": "^1.3.390", - "node-releases": "^1.1.53", - "pkg-up": "^2.0.0" + "caniuse-lite": "^1.0.30001111", + "electron-to-chromium": "^1.3.523", + "escalade": "^3.0.2", + "node-releases": "^1.1.60" } }, "buffer": { @@ -1757,9 +1908,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001042", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001042.tgz", - "integrity": "sha512-igMQ4dlqnf4tWv0xjaaE02op9AJ2oQzXKjWf4EuAHFN694Uo9/EfPVIPJcmn2WkU9RqozCxx5e2KPcVClHDbDw==", + "version": "1.0.30001123", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001123.tgz", + "integrity": "sha512-03dJDoa4YC4332jq0rqwiM+Hw6tA5RJtrnZKvOQy7ASoIUv8CinkcmGhYpCvCjedvkBQrrKnkcELxrUSW/XwNQ==", "dev": true }, "chalk": { @@ -1984,13 +2135,21 @@ "dev": true }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "create-hash": { @@ -2154,6 +2313,14 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "doctrine": { @@ -2183,15 +2350,15 @@ } }, "electron-to-chromium": { - "version": "1.3.410", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.410.tgz", - "integrity": "sha512-DbCBdwtARI0l3e3m6ZIxVaTNahb6dSsmGjuag/twiVcWuM4MSpL5IfsJsJSyqLqxosE/m0CXlZaBmxegQW/dAg==", + "version": "1.3.560", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.560.tgz", + "integrity": "sha512-0cEFfOA3sNXfSxo0FIClBhrLVSe/QO9LBiqmmYPm3N/IYyt41NRTa2EhvOMWAOKpjd91t/rq062yhnJzfVMKkQ==", "dev": true }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -2201,6 +2368,14 @@ "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "emoji-regex": { @@ -2225,9 +2400,9 @@ } }, "enhanced-resolve": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", - "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -2266,22 +2441,22 @@ } }, "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -2293,19 +2468,14 @@ "is-callable": "^1.1.4", "is-date-object": "^1.0.1", "is-symbol": "^1.0.2" - }, - "dependencies": { - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - } } }, + "escalade": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", + "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2328,12 +2498,20 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -2348,9 +2526,9 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "events": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", - "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, "evp_bytestokey": { @@ -2494,9 +2672,9 @@ } }, "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { @@ -2529,726 +2707,120 @@ "repeat-string": "^1.6.1", "to-regex-range": "^2.1.0" }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - } - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.3.tgz", - "integrity": "sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==", - "dev": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", - "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { + "dependencies": { + "extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { - "string-width": "^1.0.2 || 2" + "is-extendable": "^0.1.0" } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "optional": true } } }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3352,9 +2924,9 @@ "dev": true }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "has": { @@ -3411,13 +2983,33 @@ } }, "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "hash.js": { @@ -3518,6 +3110,12 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -3570,9 +3168,9 @@ "dev": true }, "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", "dev": true }, "is-data-descriptor": { @@ -3677,12 +3275,21 @@ } }, "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has": "^1.0.3" + "has-symbols": "^1.0.1" } }, "is-windows": { @@ -3817,12 +3424,12 @@ } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, @@ -3929,6 +3536,14 @@ "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "minimalistic-assert": { @@ -4246,27 +3861,27 @@ "dev": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "pako": { @@ -4297,20 +3912,6 @@ "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" - }, - "dependencies": { - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - } } }, "parse-json": { @@ -4428,60 +4029,6 @@ "dev": true, "requires": { "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - } - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" } }, "posix-character-classes": { @@ -4541,6 +4088,14 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "pump": { @@ -4664,14 +4219,6 @@ "acorn": "^7.2.0", "acorn-jsx": "^5.2.0", "core-js": "^3.6.5" - }, - "dependencies": { - "acorn": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", - "dev": true - } } }, "read-pkg": { @@ -4957,26 +4504,6 @@ "@types/json-schema": "^7.0.5", "ajv": "^6.12.4", "ajv-keywords": "^3.5.2" - }, - "dependencies": { - "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - } } }, "semver": { @@ -5360,28 +4887,6 @@ "es-abstract": "^1.17.5" } }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, "string.prototype.trimstart": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", @@ -5922,29 +5427,6 @@ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", "dev": true }, - "enhanced-resolve": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", - "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - } - } - }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -5977,12 +5459,6 @@ "yargs": "^13.3.2" }, "dependencies": { - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -6079,51 +5555,6 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^13.1.2" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - } } }, "yargs-parser": { diff --git a/CHANGELOG.md b/CHANGELOG.md index 7320fcba85..bc03c12eec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed - [#1385](https://github.com/plotly/dash/pull/1385) Closes [#1350](https://github.com/plotly/dash/issues/1350) and fixes a previously undefined callback behavior when multiple elements are stacked on top of one another and their `n_clicks` props are used as inputs of the same callback. The callback will now trigger once with all the triggered `n_clicks` props changes. +- [#1179](https://github.com/plotly/dash/pull/1179) New and improved callback graph in the debug menu. Now based on Cytoscape for much more interactivity, plus callback profiling including number of calls, fine-grained time information, bytes sent and received, and more. You can even add custom timing information on the server with `callback_context.record_timing(name, seconds)` ### Fixed - [#1384](https://github.com/plotly/dash/pull/1384) Fixed a bug introduced by [#1180](https://github.com/plotly/dash/pull/1180) breaking use of `prevent_initial_call` as a positional arg in callback definitions @@ -21,7 +22,6 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#1180](https://github.com/plotly/dash/pull/1180) and [#1375](https://github.com/plotly/dash/pull/1375) `Input`, `Output`, and `State` in callback definitions don't need to be in lists. You still need to provide `Output` items first, then `Input` items, then `State`, and the list form is still supported. In particular, if you want to return a single output item wrapped in a length-1 list, you should still wrap the `Output` in a list. This can be useful for procedurally-generated callbacks. - [#1368](https://github.com/plotly/dash/pull/1368) Updated pytest to v6.0.1. To avoid deprecation warnings, this also updated pytest-sugar to 0.9.4 and pytest-mock to 3.2.0. The pytest-mock update only effects python >= 3.0. Pytest-mock remains pinned at 2.0.0 for python == 2.7. - ## [1.14.0] - 2020-07-27 ### Added - [#1343](https://github.com/plotly/dash/pull/1343) Add `title` parameter to set the diff --git a/dash-renderer/.prettierrc b/dash-renderer/.prettierrc index dcd9342f37..570eb0943d 100644 --- a/dash-renderer/.prettierrc +++ b/dash-renderer/.prettierrc @@ -1,6 +1,7 @@ { "tabWidth": 4, "singleQuote": true, + "jsxSingleQuote": true, "bracketSpacing": false, - "trailingComma": "es5" -} \ No newline at end of file + "trailingComma": "none" +} diff --git a/dash-renderer/package-lock.json b/dash-renderer/package-lock.json index 139405ae99..acf1eb27f8 100644 --- a/dash-renderer/package-lock.json +++ b/dash-renderer/package-lock.json @@ -5358,6 +5358,27 @@ "babel-plugin-jest-hoist": "^25.1.0" } }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, "bail": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", @@ -5431,6 +5452,11 @@ } } }, + "base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" + }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -6789,6 +6815,32 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "cytoscape": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.14.1.tgz", + "integrity": "sha512-s8TaaDzXZ5dMkaoZXrfNRdR053L6MsF+P/Yb4AXpF8Y3VNHZXbuydMpaJTlDG/0HKqs51qHSejTUxV1fyQfU6g==", + "requires": { + "heap": "^0.2.6", + "lodash.debounce": "^4.0.8" + } + }, + "cytoscape-dagre": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cytoscape-dagre/-/cytoscape-dagre-2.2.2.tgz", + "integrity": "sha512-zsg36qNwua/L2stJSWkcbSDcvW3E6VZf6KRe6aLnQJxuXuz89tMqI5EVYVKEcNBgzTEzFMFv0PE3T0nD4m6VDw==", + "requires": { + "dagre": "^0.8.2" + } + }, + "dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "requires": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, "damerau-levenshtein": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", @@ -9785,6 +9837,14 @@ "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, + "graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "requires": { + "lodash": "^4.17.15" + } + }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -9910,6 +9970,11 @@ "minimalistic-assert": "^1.0.1" } }, + "heap": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", + "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -13044,8 +13109,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.camelcase": { "version": "4.3.0", @@ -13053,12 +13117,27 @@ "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, "lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", "dev": true }, + "lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -16188,6 +16267,11 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "pure-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", + "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -16332,6 +16416,15 @@ "prop-types": "^15.6.2" } }, + "react-cytoscapejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-cytoscapejs/-/react-cytoscapejs-1.2.1.tgz", + "integrity": "sha512-8exVCetpzyGCAKuRjXPWGjFCnb22boZ3SXUPpPB/+wQI8Q8BwkT1URN3A7J1Czvj1qAbShh5QQ514mBUp7i7kw==", + "requires": { + "cytoscape": "^3.2.19", + "prop-types": "^15.6.2" + } + }, "react-dom": { "version": "16.13.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.0.tgz", @@ -16348,6 +16441,29 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.11.0.tgz", "integrity": "sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==" }, + "react-json-tree": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.11.2.tgz", + "integrity": "sha512-aYhUPj1y5jR3ZQ+G3N7aL8FbTyO03iLwnVvvEikLcNFqNTyabdljo9xDftZndUBFyyyL0aK3qGO9+8EilILHUw==", + "requires": { + "babel-runtime": "^6.6.1", + "prop-types": "^15.5.8", + "react-base16-styling": "^0.5.1" + }, + "dependencies": { + "react-base16-styling": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", + "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=", + "requires": { + "base16": "^1.0.0", + "lodash.curry": "^4.0.1", + "lodash.flow": "^3.3.0", + "pure-color": "^1.2.0" + } + } + } + }, "react-redux": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz", @@ -19690,6 +19806,12 @@ } } }, + "tslint-config-prettier": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", + "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", + "dev": true + }, "tsscmp": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", @@ -20285,11 +20407,6 @@ "unist-util-stringify-position": "^1.1.1" } }, - "viz.js": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/viz.js/-/viz.js-2.1.2.tgz", - "integrity": "sha512-UO6CPAuEMJ8oNR0gLLNl+wUiIzQUsyUOp8SyyDKTqVRBtq7kk1VnFmIZW8QufjxGrGEuI+LVR7p/C7uEKy0LQw==" - }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", diff --git a/dash-renderer/package.json b/dash-renderer/package.json index 12d43bb271..8cb538d1bd 100644 --- a/dash-renderer/package.json +++ b/dash-renderer/package.json @@ -6,10 +6,10 @@ "scripts": { "prepublishOnly": "rm -rf lib && babel src --extensions=\".ts,.tsx,.js,.jsx\" --out-dir lib --copy-files", "private::format.js-eslint": "eslint --quiet --fix .", - "private::format.js-prettier": "prettier --config .prettierrc --write \"src/**/*.js\"", + "private::format.js-prettier": "prettier --config .prettierrc --write \"src/**/*.{js,jsx,ts,tsx}\"", "private::format.ts": "tslint --fix --project tsconfig.json --config tslint.json", "private::lint.js-eslint": "eslint .", - "private::lint.js-prettier": "prettier --config .prettierrc \"src/**/*.js\" --list-different", + "private::lint.js-prettier": "prettier --config .prettierrc \"src/**/*.{js,jsx,ts,tsx}\" --list-different", "private::lint.ts": "tslint --project tsconfig.json --config tslint.json", "build:js": "webpack --build release", "build:dev": "webpack --build local", @@ -27,18 +27,21 @@ "@babel/polyfill": "7.8.7", "@plotly/dash-component-plugins": "^1.2.0", "cookie": "^0.4.0", + "cytoscape": "^3.14.1", + "cytoscape-dagre": "^2.2.2", "dependency-graph": "^0.9.0", "fast-isnumeric": "^1.1.3", "prop-types": "15.7.2", "radium": "^0.26.0", "ramda": "^0.27.0", "react": "16.13.0", + "react-cytoscapejs": "^1.2.1", "react-dom": "16.13.0", + "react-json-tree": "^0.11.2", "react-redux": "^7.2.0", "redux": "^4.0.5", "redux-actions": "^2.6.5", - "redux-thunk": "^2.3.0", - "viz.js": "2.1.2" + "redux-thunk": "^2.3.0" }, "devDependencies": { "@babel/cli": "^7.10.3", @@ -75,6 +78,7 @@ "ts-jest": "^26.0.0", "ts-loader": "^7.0.2", "tslint": "^6.1.2", + "tslint-config-prettier": "^1.18.0", "typescript": "^3.8.3", "webpack": "^4.42.0", "webpack-cli": "^3.3.11", diff --git a/dash-renderer/src/APIController.react.js b/dash-renderer/src/APIController.react.js index 46453046c6..fe939774f1 100644 --- a/dash-renderer/src/APIController.react.js +++ b/dash-renderer/src/APIController.react.js @@ -10,7 +10,7 @@ import { onError, setGraphs, setPaths, - setLayout, + setLayout } from './actions'; import {computePaths} from './actions/paths'; import {computeGraphs} from './actions/dependencies'; @@ -37,7 +37,7 @@ const UnconnectedContainer = props => { error, layoutRequest, layout, - loadingMap, + loadingMap } = props; const [errorLoading, setErrorLoading] = useState(false); @@ -56,8 +56,8 @@ const UnconnectedContainer = props => { _dashprivate_config: propsRef.current.config, _dashprivate_dispatch: propsRef.current.dispatch, _dashprivate_graphs: propsRef.current.graphs, - _dashprivate_loadingMap: propsRef.current.loadingMap, - }), + _dashprivate_loadingMap: propsRef.current.loadingMap + }) }); useEffect(storeEffect.bind(null, props, events, setErrorLoading)); @@ -77,13 +77,13 @@ const UnconnectedContainer = props => { layoutRequest.status && !includes(layoutRequest.status, [STATUS.OK, 'loading']) ) { - content =
Error loading layout
; + content =
Error loading layout
; } else if ( errorLoading || (dependenciesRequest.status && !includes(dependenciesRequest.status, [STATUS.OK, 'loading'])) ) { - content =
Error loading dependencies
; + content =
Error loading dependencies
; } else if (appLifecycle === getAppState('HYDRATED')) { renderedTree.current = true; @@ -106,7 +106,7 @@ const UnconnectedContainer = props => { ); } else { - content =
Loading...
; + content =
Loading...
; } return config && config.ui === true ? ( @@ -124,7 +124,7 @@ function storeEffect(props, events, setErrorLoading) { error, graphs, layout, - layoutRequest, + layoutRequest } = props; if (isEmpty(layoutRequest)) { @@ -184,7 +184,7 @@ function storeEffect(props, events, setErrorLoading) { UnconnectedContainer.propTypes = { appLifecycle: PropTypes.oneOf([ getAppState('STARTED'), - getAppState('HYDRATED'), + getAppState('HYDRATED') ]), dispatch: PropTypes.func, dependenciesRequest: PropTypes.object, @@ -194,7 +194,7 @@ UnconnectedContainer.propTypes = { loadingMap: PropTypes.any, history: PropTypes.any, error: PropTypes.object, - config: PropTypes.object, + config: PropTypes.object }; const Container = connect( @@ -208,7 +208,7 @@ const Container = connect( graphs: state.graphs, history: state.history, error: state.error, - config: state.config, + config: state.config }), dispatch => ({dispatch}) )(UnconnectedContainer); diff --git a/dash-renderer/src/AccessDenied.react.js b/dash-renderer/src/AccessDenied.react.js index 0201563457..2d78f985a3 100644 --- a/dash-renderer/src/AccessDenied.react.js +++ b/dash-renderer/src/AccessDenied.react.js @@ -46,6 +46,6 @@ function AccessDenied(props) { ); } AccessDenied.propTypes = { - config: PropTypes.object, + config: PropTypes.object }; export default AccessDenied; diff --git a/dash-renderer/src/AppContainer.react.js b/dash-renderer/src/AppContainer.react.js index d03efb0f16..92f4325bbf 100644 --- a/dash-renderer/src/AppContainer.react.js +++ b/dash-renderer/src/AppContainer.react.js @@ -30,8 +30,8 @@ class UnconnectedAppContainer extends React.Component { credentials: 'same-origin', headers: { Accept: 'application/json', - 'Content-Type': 'application/json', - }, + 'Content-Type': 'application/json' + } }; dispatch(setConfig(config)); @@ -40,7 +40,7 @@ class UnconnectedAppContainer extends React.Component { render() { const {config} = this.props; if (type(config) === 'Null') { - return
Loading...
; + return
Loading...
; } const {show_undo_redo} = config; return ( @@ -57,13 +57,13 @@ class UnconnectedAppContainer extends React.Component { UnconnectedAppContainer.propTypes = { hooks: PropTypes.object, dispatch: PropTypes.func, - config: PropTypes.object, + config: PropTypes.object }; const AppContainer = connect( state => ({ history: state.history, - config: state.config, + config: state.config }), dispatch => ({dispatch}) )(UnconnectedAppContainer); diff --git a/dash-renderer/src/AppProvider.react.tsx b/dash-renderer/src/AppProvider.react.tsx index 6b534be900..4214911833 100644 --- a/dash-renderer/src/AppProvider.react.tsx +++ b/dash-renderer/src/AppProvider.react.tsx @@ -30,5 +30,3 @@ AppProvider.defaultProps = { }; export default AppProvider; - - diff --git a/dash-renderer/src/StoreObserver.ts b/dash-renderer/src/StoreObserver.ts index 45b31e8d2c..482ec13a08 100644 --- a/dash-renderer/src/StoreObserver.ts +++ b/dash-renderer/src/StoreObserver.ts @@ -1,12 +1,6 @@ -import { - any, - filter, - forEach, - map, - path -} from 'ramda'; +import {any, filter, forEach, map, path} from 'ramda'; -import { Store, Unsubscribe } from 'redux'; +import {Store, Unsubscribe} from 'redux'; type Observer = (store: TStore) => void; type UnregisterObserver = () => void; @@ -20,7 +14,7 @@ interface IStoreObserverState { export interface IStoreObserverDefinition { observer: Observer>; - inputs: string[] + inputs: string[]; [key: string]: any; } @@ -49,14 +43,14 @@ export default class StoreObserver { this.add(observer.observer, observer.inputs); return () => this.remove(observer.observer); } - } + }; setStore = (store: Store) => { this.__finalize__(); this.__init__(store); - } + }; - private __finalize__ = () => this._unsubscribe?.() + private __finalize__ = () => this._unsubscribe?.(); private __init__ = (store?: Store) => { this._store = store; @@ -64,18 +58,16 @@ export default class StoreObserver { this._unsubscribe = store.subscribe(this.notify); } - forEach(o => o.lastState = null, this._observers); - } + forEach(o => (o.lastState = null), this._observers); + }; - private add = ( - observer: Observer>, - inputs: string[] - ) => this._observers.push({ - inputPaths: map(p => p.split('.'), inputs), - lastState: null, - observer, - triggered: false - }); + private add = (observer: Observer>, inputs: string[]) => + this._observers.push({ + inputPaths: map(p => p.split('.'), inputs), + lastState: null, + observer, + triggered: false + }); private notify = () => { const store = this._store; @@ -86,29 +78,27 @@ export default class StoreObserver { const state = store.getState(); const triggered = filter( - o => !o.triggered && any( - i => path(i, state) !== path(i, o.lastState), - o.inputPaths - ), + o => + !o.triggered && + any(i => path(i, state) !== path(i, o.lastState), o.inputPaths), this._observers ); - forEach(o => o.triggered = true, triggered); + forEach(o => (o.triggered = true), triggered); - forEach( - o => { - o.lastState = store.getState(); - o.observer(store); - o.triggered = false; - }, - triggered - ); - } + forEach(o => { + o.lastState = store.getState(); + o.observer(store); + o.triggered = false; + }, triggered); + }; - private remove = (observer: Observer>) => this._observers.splice( - this._observers.findIndex( - o => observer === o.observer, - this._observers - ), 1 - ); + private remove = (observer: Observer>) => + this._observers.splice( + this._observers.findIndex( + o => observer === o.observer, + this._observers + ), + 1 + ); } diff --git a/dash-renderer/src/TreeContainer.js b/dash-renderer/src/TreeContainer.js index bf317ad17d..7c2ef9efbe 100644 --- a/dash-renderer/src/TreeContainer.js +++ b/dash-renderer/src/TreeContainer.js @@ -15,7 +15,7 @@ import { pick, pickBy, propOr, - type, + type } from 'ramda'; import {notifyObservers, updateProps} from './actions'; import isSimpleComponent from './isSimpleComponent'; @@ -26,12 +26,12 @@ import {getWatchedKeys, stringifyId} from './actions/dependencies'; import { getLoadingHash, getLoadingState, - validateComponent, + validateComponent } from './utils/TreeContainer'; import {DashContext} from './APIController.react'; const NOT_LOADING = { - is_loading: false, + is_loading: false }; function CheckedComponent(p) { @@ -56,7 +56,7 @@ CheckedComponent.propTypes = { layout: PropTypes.any, props: PropTypes.any, extraProps: PropTypes.any, - id: PropTypes.string, + id: PropTypes.string }; function createElement(element, props, extraProps, children) { @@ -117,7 +117,7 @@ class BaseTreeContainer extends Component { _dashprivate_graphs, _dashprivate_dispatch, _dashprivate_path, - _dashprivate_layout, + _dashprivate_layout } = this.props; const oldProps = this.getLayoutProps(); @@ -142,7 +142,7 @@ class BaseTreeContainer extends Component { _dashprivate_dispatch( updateProps({ props: changedProps, - itempath: _dashprivate_path, + itempath: _dashprivate_path }) ); @@ -151,7 +151,7 @@ class BaseTreeContainer extends Component { _dashprivate_dispatch( notifyObservers({ id, - props: pick(watchedKeys, changedProps), + props: pick(watchedKeys, changedProps) }) ); } @@ -184,7 +184,7 @@ class BaseTreeContainer extends Component { const { _dashprivate_config, _dashprivate_dispatch, - _dashprivate_error, + _dashprivate_error } = this.props; if (isEmpty(_dashprivate_layout)) { @@ -208,7 +208,7 @@ class BaseTreeContainer extends Component { } const extraProps = { loading_state: loading_state || NOT_LOADING, - setProps, + setProps }; return ( @@ -242,7 +242,7 @@ class BaseTreeContainer extends Component { const { _dashprivate_layout, _dashprivate_loadingState, - _dashprivate_path, + _dashprivate_path } = this.props; const layoutProps = this.getLayoutProps(); @@ -266,10 +266,10 @@ TreeContainer.propTypes = { _dashprivate_layout: PropTypes.object, _dashprivate_loadingState: PropTypes.oneOfType([ PropTypes.object, - PropTypes.bool, + PropTypes.bool ]), _dashprivate_loadingStateHash: PropTypes.string, - _dashprivate_path: PropTypes.string, + _dashprivate_path: PropTypes.string }; BaseTreeContainer.propTypes = { @@ -278,7 +278,7 @@ BaseTreeContainer.propTypes = { _dashprivate_dispatch: PropTypes.func, _dashprivate_graphs: PropTypes.any, _dashprivate_loadingMap: PropTypes.any, - _dashprivate_path: PropTypes.array, + _dashprivate_path: PropTypes.array }; export default TreeContainer; diff --git a/dash-renderer/src/actions/api.js b/dash-renderer/src/actions/api.js index 8a78708d9f..a2f422aa3a 100644 --- a/dash-renderer/src/actions/api.js +++ b/dash-renderer/src/actions/api.js @@ -10,7 +10,7 @@ function GET(path, fetchConfig) { path, mergeDeepRight(fetchConfig, { method: 'GET', - headers: getCSRFHeader(), + headers: getCSRFHeader() }) ); } @@ -21,7 +21,7 @@ function POST(path, fetchConfig, body = {}) { mergeDeepRight(fetchConfig, { method: 'POST', headers: getCSRFHeader(), - body: body ? JSON.stringify(body) : null, + body: body ? JSON.stringify(body) : null }) ); } @@ -37,14 +37,14 @@ export default function apiThunk(endpoint, method, store, id, body) { if (getState().error.backEndConnected !== connected) { dispatch({ type: 'SET_CONNECTION_STATUS', - payload: connected, + payload: connected }); } } dispatch({ type: store, - payload: {id, status: 'loading'}, + payload: {id, status: 'loading'} }); return request[method](url, config.fetch, body) .then( @@ -61,8 +61,8 @@ export default function apiThunk(endpoint, method, store, id, body) { payload: { status: res.status, content: json, - id, - }, + id + } }); return json; }); @@ -74,8 +74,8 @@ export default function apiThunk(endpoint, method, store, id, body) { type: store, payload: { id, - status: res.status, - }, + status: res.status + } }); }, () => { diff --git a/dash-renderer/src/actions/callbacks.ts b/dash-renderer/src/actions/callbacks.ts index c660bcb218..7bb917f189 100644 --- a/dash-renderer/src/actions/callbacks.ts +++ b/dash-renderer/src/actions/callbacks.ts @@ -10,13 +10,25 @@ import { zip } from 'ramda'; -import { STATUS } from '../constants/constants'; -import { CallbackActionType, CallbackAggregateActionType } from '../reducers/callbacks'; -import { CallbackResult, ICallback, IExecutedCallback, IExecutingCallback, ICallbackPayload, IStoredCallback, IBlockedCallback, IPrioritizedCallback } from '../types/callbacks'; -import { isMultiValued, stringifyId, isMultiOutputProp } from './dependencies'; -import { urlBase } from './utils'; -import { getCSRFHeader } from '.'; -import { createAction, Action } from 'redux-actions'; +import {STATUS} from '../constants/constants'; +import { + CallbackActionType, + CallbackAggregateActionType +} from '../reducers/callbacks'; +import { + CallbackResult, + ICallback, + IExecutedCallback, + IExecutingCallback, + ICallbackPayload, + IStoredCallback, + IBlockedCallback, + IPrioritizedCallback +} from '../types/callbacks'; +import {isMultiValued, stringifyId, isMultiOutputProp} from './dependencies'; +import {urlBase} from './utils'; +import {getCSRFHeader} from '.'; +import {createAction, Action} from 'redux-actions'; export const addBlockedCallbacks = createAction( CallbackActionType.AddBlocked @@ -39,7 +51,9 @@ export const addRequestedCallbacks = createAction( export const addStoredCallbacks = createAction( CallbackActionType.AddStored ); -export const addWatchedCallbacks = createAction(CallbackActionType.AddWatched); +export const addWatchedCallbacks = createAction( + CallbackActionType.AddWatched +); export const removeExecutedCallbacks = createAction( CallbackActionType.RemoveExecuted ); @@ -61,11 +75,11 @@ export const removeStoredCallbacks = createAction( export const removeWatchedCallbacks = createAction( CallbackActionType.RemoveWatched ); -export const aggregateCallbacks = createAction<( - Action | - Action | - null -)[]>(CallbackAggregateActionType.Aggregate); +export const aggregateCallbacks = createAction< + (Action | Action | null)[] +>(CallbackAggregateActionType.Aggregate); + +const updateResourceUsage = createAction('UPDATE_RESOURCE_USAGE'); function unwrapIfNotMulti( paths: any, @@ -90,13 +104,13 @@ function unwrapIfNotMulti( (isStr ? '`' + spec.id + '`' : JSON.stringify(spec.id) + - (anyVals ? ' with MATCH values ' + anyVals : '')) + + (anyVals ? ' with MATCH values ' + anyVals : '')) + ' and the property is `' + spec.property + (isStr ? '`. The string ids in the current layout are: [' + - keys(paths.strs).join(', ') + - ']' + keys(paths.strs).join(', ') + + ']' : '`. The wildcard ids currently available are logged above.'); } else { msg = @@ -129,7 +143,7 @@ function fillVals( const inputVals = getter(paths).map((inputList: any, i: number) => { const [inputs, inputError] = unwrapIfNotMulti( paths, - inputList.map(({ id, property, path: path_ }: any) => ({ + inputList.map(({id, property, path: path_}: any) => ({ id, property, value: (path(path_, layout) as any).props[property] @@ -181,78 +195,113 @@ function refErr(errors: any, paths: any) { const getVals = (input: any) => Array.isArray(input) ? pluck('value', input) : input.value; -const zipIfArray = (a: any, b: any) => (Array.isArray(a) ? zip(a, b) : [[a, b]]); +const zipIfArray = (a: any, b: any) => + Array.isArray(a) ? zip(a, b) : [[a, b]]; -function handleClientside(clientside_function: any, payload: ICallbackPayload) { - const dc = ((window as any).dash_clientside = (window as any).dash_clientside || {}); +function handleClientside( + dispatch: any, + clientside_function: any, + config: any, + payload: ICallbackPayload +) { + const dc = ((window as any).dash_clientside = + (window as any).dash_clientside || {}); if (!dc.no_update) { Object.defineProperty(dc, 'no_update', { - value: { description: 'Return to prevent updating an Output.' }, + value: {description: 'Return to prevent updating an Output.'}, writable: false }); Object.defineProperty(dc, 'PreventUpdate', { - value: { description: 'Throw to prevent updating all Outputs.' }, + value: {description: 'Throw to prevent updating all Outputs.'}, writable: false }); } - const { inputs, outputs, state } = payload; + const {inputs, outputs, state} = payload; + const requestTime = Date.now(); - let returnValue; + const inputDict = inputsToDict(inputs); + const stateDict = inputsToDict(state); + const result: any = {}; + let status: any = STATUS.OK; try { - const { namespace, function_name } = clientside_function; + const {namespace, function_name} = clientside_function; let args = inputs.map(getVals); if (state) { args = concat(args, state.map(getVals)); } // setup callback context - const input_dict = inputsToDict(inputs); dc.callback_context = {}; dc.callback_context.triggered = payload.changedPropIds.map(prop_id => ({ prop_id: prop_id, - value: input_dict[prop_id] + value: inputDict[prop_id] })); dc.callback_context.inputs_list = inputs; - dc.callback_context.inputs = input_dict; + dc.callback_context.inputs = inputDict; dc.callback_context.states_list = state; - dc.callback_context.states = inputsToDict(state); + dc.callback_context.states = stateDict; + + const returnValue = dc[namespace][function_name](...args); + + if (typeof returnValue?.then === 'function') { + throw new Error( + 'The clientside function returned a Promise. ' + + 'Promises are not supported in Dash clientside ' + + 'right now, but may be in the future.' + ); + } - returnValue = dc[namespace][function_name](...args); + zipIfArray(outputs, returnValue).forEach(([outi, reti]) => { + zipIfArray(outi, reti).forEach(([outij, retij]) => { + const {id, property} = outij; + const idStr = stringifyId(id); + const dataForId = (result[idStr] = result[idStr] || {}); + if (retij !== dc.no_update) { + dataForId[property] = retij; + } + }); + }); } catch (e) { if (e === dc.PreventUpdate) { - return {}; + status = STATUS.PREVENT_UPDATE; + } else { + status = STATUS.CLIENTSIDE_ERROR; + throw e; } - throw e; } finally { delete dc.callback_context; - } - if (typeof returnValue?.then === 'function') { - throw new Error( - 'The clientside function returned a Promise. ' + - 'Promises are not supported in Dash clientside ' + - 'right now, but may be in the future.' - ); + // Setting server = client forces network = 0 + const totalTime = Date.now() - requestTime; + const resources = { + __dash_server: totalTime, + __dash_client: totalTime, + __dash_upload: 0, + __dash_download: 0 + }; + + if (config.ui) { + dispatch( + updateResourceUsage({ + id: payload.output, + usage: resources, + status, + result, + inputs, + state + }) + ); + } } - const data: any = {}; - zipIfArray(outputs, returnValue).forEach(([outi, reti]) => { - zipIfArray(outi, reti).forEach(([outij, retij]) => { - const { id, property } = outij; - const idStr = stringifyId(id); - const dataForId = (data[idStr] = data[idStr] || {}); - if (retij !== dc.no_update) { - dataForId[property] = retij; - } - }); - }); - return data; + return result; } function handleServerside( + dispatch: any, hooks: any, config: any, payload: any @@ -261,41 +310,101 @@ function handleServerside( hooks.request_pre(payload); } + const requestTime = Date.now(); + const body = JSON.stringify(payload); + return fetch( `${urlBase(config)}_dash-update-component`, mergeDeepRight(config.fetch, { method: 'POST', headers: getCSRFHeader() as any, - body: JSON.stringify(payload) + body }) - ).then((res: any) => { - const { status } = res; - if (status === STATUS.OK) { - return res.json().then((data: any) => { - const { multi, response } = data; - if (hooks.request_post !== null) { - hooks.request_post(payload, response); + ).then( + (res: any) => { + const {status} = res; + + function recordProfile(result: any) { + if (config.ui) { + // Callback profiling - only relevant if we're showing the debug ui + const resources = { + __dash_server: 0, + __dash_client: Date.now() - requestTime, + __dash_upload: body.length, + __dash_download: Number( + res.headers.get('Content-Length') + ) + } as any; + + const timingHeaders = + res.headers.get('Server-Timing') || ''; + + timingHeaders.split(',').forEach((header: any) => { + const name = header.split(';')[0]; + const dur = header.match(/;dur=[0-9\.]+/); + + if (dur) { + resources[name] = Number(dur[0].slice(5)); + } + }); + + dispatch( + updateResourceUsage({ + id: payload.output, + usage: resources, + status, + result, + inputs: payload.inputs, + state: payload.state + }) + ); } + } - if (multi) { - return response; - } + if (status === STATUS.OK) { + return res.json().then((data: any) => { + const {multi, response} = data; + if (hooks.request_post !== null) { + hooks.request_post(payload, response); + } - const { output } = payload; - const id = output.substr(0, output.lastIndexOf('.')); - return { [id]: response.props }; - }); - } - if (status === STATUS.PREVENT_UPDATE) { - return {}; + let result; + if (multi) { + result = response; + } else { + const {output} = payload; + const id = output.substr(0, output.lastIndexOf('.')); + result = {[id]: response.props}; + } + + recordProfile(result); + return result; + }); + } + if (status === STATUS.PREVENT_UPDATE) { + recordProfile({}); + return {}; + } + throw res; + }, + () => { + // fetch rejection - this means the request didn't return, + // we don't get here from 400/500 errors, only network + // errors or unresponsive servers. + if (config.ui) { + dispatch( + updateResourceUsage({ + id: payload.output, + status: STATUS.NO_RESPONSE, + result: {}, + inputs: payload.inputs, + state: payload.state + }) + ); + } + throw new Error('Callback failed: the server did not respond.'); } - throw res; - }, () => { - // fetch rejection - this means the request didn't return, - // we don't get here from 400/500 errors, only network - // errors or unresponsive servers. - throw new Error('Callback failed: the server did not respond.'); - }); + ); } function inputsToDict(inputs_list: any) { @@ -314,13 +423,13 @@ function inputsToDict(inputs_list: any) { for (let ii = 0; ii < inputsi.length; ii++) { const id_str = `${stringifyId(inputsi[ii].id)}.${ inputsi[ii].property - }`; + }`; inputs[id_str] = inputsi[ii].value ?? null; } } else { const id_str = `${stringifyId(inputs_list[i].id)}.${ inputs_list[i].property - }`; + }`; inputs[id_str] = inputs_list[i].value ?? null; } } @@ -333,9 +442,10 @@ export function executeCallback( hooks: any, paths: any, layout: any, - { allOutputs }: any + {allOutputs}: any, + dispatch: any ): IExecutingCallback { - const { output, inputs, state, clientside_function } = cb.callback; + const {output, inputs, state, clientside_function} = cb.callback; try { const inVals = fillVals(paths, layout, cb, inputs, 'Input', true); @@ -385,25 +495,33 @@ export function executeCallback( outputs: isMultiOutputProp(output) ? outputs : outputs[0], inputs: inVals, changedPropIds: keys(cb.changedPropIds), - state: cb.callback.state.length ? - fillVals(paths, layout, cb, state, 'State') : - undefined + state: cb.callback.state.length + ? fillVals(paths, layout, cb, state, 'State') + : undefined }; if (clientside_function) { try { - resolve({ data: handleClientside(clientside_function, payload), payload }); + resolve({ + data: handleClientside( + dispatch, + clientside_function, + config, + payload + ), + payload + }); } catch (error) { - resolve({ error, payload }); + resolve({error, payload}); } return null; } else { - handleServerside(hooks, config, payload) - .then(data => resolve({ data, payload })) - .catch(error => resolve({ error, payload })); + handleServerside(dispatch, hooks, config, payload) + .then(data => resolve({data, payload})) + .catch(error => resolve({error, payload})); } } catch (error) { - resolve({ error, payload: null }); + resolve({error, payload: null}); } }); @@ -416,7 +534,7 @@ export function executeCallback( } catch (error) { return { ...cb, - executionPromise: { error, payload: null } + executionPromise: {error, payload: null} }; } } diff --git a/dash-renderer/src/actions/constants.js b/dash-renderer/src/actions/constants.js index b9fba5047d..3d61debf6c 100644 --- a/dash-renderer/src/actions/constants.js +++ b/dash-renderer/src/actions/constants.js @@ -7,7 +7,7 @@ const actionList = { SET_APP_LIFECYCLE: 1, SET_CONFIG: 1, ON_ERROR: 1, - SET_HOOKS: 1, + SET_HOOKS: 1 }; export const getAction = action => { diff --git a/dash-renderer/src/actions/dependencies.js b/dash-renderer/src/actions/dependencies.js index 1d77086d1d..b83015dbf8 100644 --- a/dash-renderer/src/actions/dependencies.js +++ b/dash-renderer/src/actions/dependencies.js @@ -23,7 +23,7 @@ import { startsWith, values, zip, - zipObj, + zipObj } from 'ramda'; import { @@ -33,7 +33,7 @@ import { INDIRECT, mergeMax, makeResolvedCallback, - resolveDeps, + resolveDeps } from './dependencies_ts'; import {computePaths, getPath} from './paths'; @@ -56,7 +56,7 @@ const wildcards = {ALL, MATCH, ALLSMALLER}; const allowedWildcards = { Output: {ALL, MATCH}, Input: wildcards, - State: wildcards, + State: wildcards }; const wildcardValTypes = ['string', 'number', 'boolean']; @@ -97,7 +97,7 @@ export function splitIdAndProp(idAndProp) { const idStr = idAndProp.substr(0, dotPos); return { id: parseIfWildcard(idStr), - property: idAndProp.substr(dotPos + 1), + property: idAndProp.substr(dotPos + 1) }; } @@ -193,7 +193,7 @@ function validateDependencies(parsedDependencies, dispatchError) { hasOutputs = false; dispatchError('A callback is missing Outputs', [ 'Please provide an output for this callback:', - JSON.stringify(dep, null, 2), + JSON.stringify(dep, null, 2) ]); } @@ -208,14 +208,14 @@ function validateDependencies(parsedDependencies, dispatchError) { 'Without `Input` elements, it will never get called.', '', 'Subscribing to `Input` components will cause the', - 'callback to be called whenever their values change.', + 'callback to be called whenever their values change.' ]); } const spec = [ [outputs, 'Output'], [inputs, 'Input'], - [state, 'State'], + [state, 'State'] ]; spec.forEach(([args, cls]) => { if (cls === 'Output' && !hasOutputs) { @@ -230,7 +230,7 @@ function validateDependencies(parsedDependencies, dispatchError) { head, `For ${cls}(s) we found:`, JSON.stringify(args), - 'but we expected an Array.', + 'but we expected an Array.' ]); } args.forEach((idProp, i) => { @@ -249,7 +249,7 @@ function validateArg({id, property}, head, cls, i, dispatchError) { dispatchError('Callback property error', [ head, `${cls}[${i}].property = ${JSON.stringify(property)}`, - 'but we expected `property` to be a non-empty string.', + 'but we expected `property` to be a non-empty string.' ]); } @@ -258,7 +258,7 @@ function validateArg({id, property}, head, cls, i, dispatchError) { dispatchError('Callback item missing ID', [ head, `${cls}[${i}].id = {}`, - 'Every item linked to a callback needs an ID', + 'Every item linked to a callback needs an ID' ]); } @@ -267,7 +267,7 @@ function validateArg({id, property}, head, cls, i, dispatchError) { dispatchError('Callback wildcard ID error', [ head, `${cls}[${i}].id has key "${k}"`, - 'Keys must be non-empty strings.', + 'Keys must be non-empty strings.' ]); } @@ -277,7 +277,7 @@ function validateArg({id, property}, head, cls, i, dispatchError) { head, `${cls}[${i}].id["${k}"] = ${v.wild}`, `Allowed wildcards for ${cls}s are:`, - keys(allowedWildcards[cls]).join(', '), + keys(allowedWildcards[cls]).join(', ') ]); } } else if (!includes(typeof v, wildcardValTypes)) { @@ -286,7 +286,7 @@ function validateArg({id, property}, head, cls, i, dispatchError) { `${cls}[${i}].id["${k}"] = ${JSON.stringify(v)}`, 'Wildcard callback ID values must be either wildcards', 'or constants of one of these types:', - wildcardValTypes.join(', '), + wildcardValTypes.join(', ') ]); } }, id); @@ -295,7 +295,7 @@ function validateArg({id, property}, head, cls, i, dispatchError) { dispatchError('Callback item missing ID', [ head, `${cls}[${i}].id = "${id}"`, - 'Every item linked to a callback needs an ID', + 'Every item linked to a callback needs an ID' ]); } const invalidChars = idInvalidChars.filter(c => includes(c, id)); @@ -303,14 +303,14 @@ function validateArg({id, property}, head, cls, i, dispatchError) { dispatchError('Callback invalid ID string', [ head, `${cls}[${i}].id = '${id}'`, - `characters '${invalidChars.join("', '")}' are not allowed.`, + `characters '${invalidChars.join("', '")}' are not allowed.` ]); } } else { dispatchError('Callback ID type error', [ head, `${cls}[${i}].id = ${JSON.stringify(id)}`, - 'IDs must be strings or wildcard-compatible objects.', + 'IDs must be strings or wildcard-compatible objects.' ]); } } @@ -324,7 +324,7 @@ function findDuplicateOutputs(outputs, head, dispatchError, outStrs, outObjs) { if (newOutputStrs[idProp]) { dispatchError('Duplicate callback Outputs', [ head, - `Output ${i} (${idProp}) is already used by this callback.`, + `Output ${i} (${idProp}) is already used by this callback.` ]); } else if (outStrs[idProp]) { dispatchError('Duplicate callback outputs', [ @@ -333,7 +333,7 @@ function findDuplicateOutputs(outputs, head, dispatchError, outStrs, outObjs) { 'Any given output can only have one callback that sets it.', 'To resolve this situation, try combining these into', 'one callback function, distinguishing the trigger', - 'by using `dash.callback_context` if necessary.', + 'by using `dash.callback_context` if necessary.' ]); } else { newOutputStrs[idProp] = 1; @@ -349,7 +349,7 @@ function findDuplicateOutputs(outputs, head, dispatchError, outStrs, outObjs) { head, `Output ${i} (${idProp})`, `overlaps another output (${idProp2})`, - `used in ${selfOverlap ? 'this' : 'a different'} callback.`, + `used in ${selfOverlap ? 'this' : 'a different'} callback.` ]); } else { newOutputObjs.push(idObj); @@ -377,7 +377,7 @@ function findInOutOverlap(outputs, inputs, head, dispatchError) { dispatchError('Same `Input` and `Output`', [ head, `Input ${ini} (${combineIdAndProp(in_)})`, - `matches Output ${outi} (${combineIdAndProp(out)})`, + `matches Output ${outi} (${combineIdAndProp(out)})` ]); } } else if (wildcardOverlap(in_, [out])) { @@ -385,7 +385,7 @@ function findInOutOverlap(outputs, inputs, head, dispatchError) { head, `Input ${ini} (${combineIdAndProp(in_)})`, 'can match the same component(s) as', - `Output ${outi} (${combineIdAndProp(out)})`, + `Output ${outi} (${combineIdAndProp(out)})` ]); } }); @@ -402,13 +402,13 @@ function findMismatchedWildcards(outputs, inputs, state, head, dispatchError) { 'does not have MATCH wildcards on the same keys as', `Output 0 (${combineIdAndProp(outputs[0])}).`, 'MATCH wildcards must be on the same keys for all Outputs.', - 'ALL wildcards need not match, only MATCH.', + 'ALL wildcards need not match, only MATCH.' ]); } }); [ [inputs, 'Input'], - [state, 'State'], + [state, 'State'] ].forEach(([args, cls]) => { args.forEach((arg, i) => { const {matchKeys, allsmallerKeys} = findWildcardKeys(arg.id); @@ -423,7 +423,7 @@ function findMismatchedWildcards(outputs, inputs, state, head, dispatchError) { `where Output 0 (${combineIdAndProp(outputs[0])})`, 'does not have a MATCH wildcard. Inputs and State do not', 'need every MATCH from the Output(s), but they cannot have', - 'extras beyond the Output(s).', + 'extras beyond the Output(s).' ]); } }); @@ -492,7 +492,7 @@ export function validateCallbacksToLayout(state_, dispatchError) { 'generated by other callbacks (and therefore not in the', 'initial layout), you can suppress this exception by setting', '`suppress_callback_exceptions=True`.', - tail(callbacks), + tail(callbacks) ]); } @@ -519,7 +519,7 @@ export function validateCallbacksToLayout(state_, dispatchError) { `in one of the ${cls} items of a callback.`, `This ID is assigned to a ${namespace}.${type} component`, 'in the layout, which does not support this property.', - tail(callbacks), + tail(callbacks) ]); } } @@ -664,7 +664,7 @@ export function computeGraphs(dependencies, dispatchError) { inputMap, outputPatterns, inputPatterns, - callbacks: parsedDependencies, + callbacks: parsedDependencies }; if (hasError) { @@ -683,7 +683,7 @@ export function computeGraphs(dependencies, dispatchError) { if (!wildcardPlaceholders[key]) { wildcardPlaceholders[key] = { exact: [], - expand: 0, + expand: 0 }; } const keyPlaceholders = wildcardPlaceholders[key]; @@ -872,7 +872,7 @@ export function idMatch( vals, refKeys, refPatternVals, - refVals, + refVals }) ); } @@ -1204,7 +1204,7 @@ export function getUnfilteredLayoutCallbacks(graphs, paths, layoutChunk, opts) { return map( cb => ({ ...cb, - priority: getPriority(graphs, paths, cb), + priority: getPriority(graphs, paths, cb) }), callbacks ); diff --git a/dash-renderer/src/actions/dependencies_ts.ts b/dash-renderer/src/actions/dependencies_ts.ts index 7fcf8cba55..a053d4ffd8 100644 --- a/dash-renderer/src/actions/dependencies_ts.ts +++ b/dash-renderer/src/actions/dependencies_ts.ts @@ -16,18 +16,29 @@ import { reduce, zipObj } from 'ramda'; -import { ICallback, ICallbackProperty, ICallbackDefinition, ILayoutCallbackProperty, ICallbackTemplate } from '../types/callbacks'; -import { addAllResolvedFromOutputs, splitIdAndProp, stringifyId, getUnfilteredLayoutCallbacks, isMultiValued, idMatch } from './dependencies'; -import { getPath } from './paths'; +import { + ICallback, + ICallbackProperty, + ICallbackDefinition, + ILayoutCallbackProperty, + ICallbackTemplate +} from '../types/callbacks'; +import { + addAllResolvedFromOutputs, + splitIdAndProp, + stringifyId, + getUnfilteredLayoutCallbacks, + isMultiValued, + idMatch +} from './dependencies'; +import {getPath} from './paths'; export const DIRECT = 2; export const INDIRECT = 1; export const mergeMax = mergeWith(Math.max); -export const combineIdAndProp = ({ - id, - property -}: ICallbackProperty) => `${stringifyId(id)}.${property}`; +export const combineIdAndProp = ({id, property}: ICallbackProperty) => + `${stringifyId(id)}.${property}`; export function getCallbacksByInput( graphs: any, @@ -38,7 +49,7 @@ export function getCallbacksByInput( withPriority: boolean = true ): ICallback[] { const matches: ICallback[] = []; - const idAndProp = combineIdAndProp({ id, property: prop }); + const idAndProp = combineIdAndProp({id, property: prop}); if (typeof id === 'string') { // standard id version @@ -74,7 +85,7 @@ export function getCallbacksByInput( matches.forEach(match => { match.changedPropIds[idAndProp] = changeType || DIRECT; if (withPriority) { - match.priority = getPriority(graphs, paths, match) + match.priority = getPriority(graphs, paths, match); } }); return matches; @@ -85,18 +96,19 @@ export function getCallbacksByInput( * Uses the number of callbacks at each tree depth and the total depth of the tree * to create a sortable priority hash. */ -export function getPriority(graphs: any, paths: any, callback: ICallback): string { +export function getPriority( + graphs: any, + paths: any, + callback: ICallback +): string { let callbacks: ICallback[] = [callback]; - let touchedOutputs: { [key: string]: boolean } = {}; + let touchedOutputs: {[key: string]: boolean} = {}; let priority: number[] = []; while (callbacks.length) { const outputs = filter( o => !touchedOutputs[combineIdAndProp(o)], - flatten(map( - cb => flatten(cb.getOutputs(paths)), - callbacks - )) + flatten(map(cb => flatten(cb.getOutputs(paths)), callbacks)) ); touchedOutputs = reduce( @@ -105,17 +117,20 @@ export function getPriority(graphs: any, paths: any, callback: ICallback): strin outputs ); - callbacks = flatten(map( - ({ id, property }: any) => getCallbacksByInput( - graphs, - paths, - id, - property, - INDIRECT, - false - ), - outputs - )); + callbacks = flatten( + map( + ({id, property}: any) => + getCallbacksByInput( + graphs, + paths, + id, + property, + INDIRECT, + false + ), + outputs + ) + ); if (callbacks.length) { priority.push(callbacks.length); @@ -148,18 +163,19 @@ export const getReadyCallbacks = ( ); // Make `outputs` hash table for faster access - const outputsMap: { [key: string]: boolean } = {}; - forEach(output => outputsMap[output] = true, outputs); + const outputsMap: {[key: string]: boolean} = {}; + forEach(output => (outputsMap[output] = true), outputs); // Find `requested` callbacks that do not depend on a outstanding output (as either input or state) return filter( - cb => all( - cbp => !outputsMap[combineIdAndProp(cbp)], - flatten(cb.getInputs(paths)) - ), + cb => + all( + cbp => !outputsMap[combineIdAndProp(cbp)], + flatten(cb.getInputs(paths)) + ), candidates ); -} +}; export const getLayoutCallbacks = ( graphs: any, @@ -188,14 +204,15 @@ export const getLayoutCallbacks = ( */ while (true) { // Find callbacks for which all inputs are missing or in the exclusions - const [included, excluded] = partition(({ - callback: { inputs }, - getInputs - }) => all(isMultiValued, inputs) || - !isEmpty(difference( - map(combineIdAndProp, flatten(getInputs(paths))), - exclusions - )), + const [included, excluded] = partition( + ({callback: {inputs}, getInputs}) => + all(isMultiValued, inputs) || + !isEmpty( + difference( + map(combineIdAndProp, flatten(getInputs(paths))), + exclusions + ) + ), callbacks ); @@ -209,10 +226,10 @@ export const getLayoutCallbacks = ( // update exclusions with all additional excluded outputs exclusions = concat( exclusions, - map(combineIdAndProp, flatten(map( - ({ getOutputs }) => getOutputs(paths), - excluded - ))) + map( + combineIdAndProp, + flatten(map(({getOutputs}) => getOutputs(paths), excluded)) + ) ); } @@ -220,35 +237,30 @@ export const getLayoutCallbacks = ( Return all callbacks with an `executionGroup` to allow group-processing */ const executionGroup = Math.random().toString(16); - return map(cb => ({ - ...cb, - executionGroup - }), callbacks); -} + return map(cb => ({...cb, executionGroup}), callbacks); +}; export const getUniqueIdentifier = ({ anyVals, - callback: { - inputs, - outputs, - state - } -}: ICallback): string => concat( - map(combineIdAndProp, [ - ...inputs, - ...outputs, - ...state - ]), - Array.isArray(anyVals) ? - anyVals : - anyVals === '' ? [] : [anyVals] + callback: {inputs, outputs, state} +}: ICallback): string => + concat( + map(combineIdAndProp, [...inputs, ...outputs, ...state]), + Array.isArray(anyVals) ? anyVals : anyVals === '' ? [] : [anyVals] ).join(','); -export function includeObservers(id: any, properties: any, graphs: any, paths: any): ICallback[] { - return flatten(map( - propName => getCallbacksByInput(graphs, paths, id, propName), - keys(properties) - )); +export function includeObservers( + id: any, + properties: any, + graphs: any, + paths: any +): ICallback[] { + return flatten( + map( + propName => getCallbacksByInput(graphs, paths, id, propName), + keys(properties) + ) + ); } /* @@ -272,25 +284,34 @@ export const makeResolvedCallback = ( initialCall: false }); -export function pruneCallbacks(callbacks: T[], paths: any): { - added: T[], - removed: T[] +export function pruneCallbacks( + callbacks: T[], + paths: any +): { + added: T[]; + removed: T[]; } { const [, removed] = partition( - ({ getOutputs, callback: { outputs } }) => flatten(getOutputs(paths)).length === outputs.length, + ({getOutputs, callback: {outputs}}) => + flatten(getOutputs(paths)).length === outputs.length, callbacks ); const [, modified] = partition( - ({ getOutputs }) => !flatten(getOutputs(paths)).length, + ({getOutputs}) => !flatten(getOutputs(paths)).length, removed ); const added = map( - cb => assoc('changedPropIds', pickBy( - (_, propId) => getPath(paths, splitIdAndProp(propId).id), - cb.changedPropIds - ), cb), + cb => + assoc( + 'changedPropIds', + pickBy( + (_, propId) => getPath(paths, splitIdAndProp(propId).id), + cb.changedPropIds + ), + cb + ), modified ); @@ -300,11 +321,15 @@ export function pruneCallbacks(callbacks: T[], paths: any): }; } -export function resolveDeps(refKeys?: any, refVals?: any, refPatternVals?: string) { - return (paths: any) => ({ id: idPattern, property }: ICallbackProperty) => { +export function resolveDeps( + refKeys?: any, + refVals?: any, + refPatternVals?: string +) { + return (paths: any) => ({id: idPattern, property}: ICallbackProperty) => { if (typeof idPattern === 'string') { const path = getPath(paths, idPattern); - return path ? [{ id: idPattern, property, path }] : []; + return path ? [{id: idPattern, property, path}] : []; } const _keys = Object.keys(idPattern).sort(); const patternVals = props(_keys, idPattern); @@ -314,7 +339,7 @@ export function resolveDeps(refKeys?: any, refVals?: any, refPatternVals?: strin return []; } const result: ILayoutCallbackProperty[] = []; - keyPaths.forEach(({ values: vals, path }: any) => { + keyPaths.forEach(({values: vals, path}: any) => { if ( idMatch( _keys, @@ -325,7 +350,7 @@ export function resolveDeps(refKeys?: any, refVals?: any, refPatternVals?: strin refPatternVals ) ) { - result.push({ id: zipObj(_keys, vals), property, path }); + result.push({id: zipObj(_keys, vals), property, path}); } }); return result; diff --git a/dash-renderer/src/actions/index.js b/dash-renderer/src/actions/index.js index 7d165f41a6..13e3d74f18 100644 --- a/dash-renderer/src/actions/index.js +++ b/dash-renderer/src/actions/index.js @@ -22,7 +22,7 @@ export const dispatchError = dispatch => (message, lines) => dispatch( onError({ type: 'backEnd', - error: {message, html: lines.join('\n')}, + error: {message, html: lines.join('\n')} }) ); @@ -40,7 +40,7 @@ const logWarningOnce = once(console.warn); export function getCSRFHeader() { try { return { - 'X-CSRFToken': cookie.parse(document.cookie)._csrf_token, + 'X-CSRFToken': cookie.parse(document.cookie)._csrf_token }; } catch (e) { logWarningOnce(e); @@ -60,8 +60,8 @@ function triggerDefaultState(dispatch, getState) { type: 'backEnd', error: { message: 'Circular Dependencies', - html: err.toString(), - }, + html: err.toString() + } }) ); } @@ -69,7 +69,7 @@ function triggerDefaultState(dispatch, getState) { dispatch( addRequestedCallbacks( getLayoutCallbacks(graphs, paths, layout, { - outputsOnly: true, + outputsOnly: true }) ) ); @@ -92,7 +92,7 @@ function moveHistory(changeType) { dispatch( createAction('UNDO_PROP_CHANGE')({ itempath: getPath(paths, id), - props, + props }) ); diff --git a/dash-renderer/src/actions/isAppReady.js b/dash-renderer/src/actions/isAppReady.js index 03465dc2f7..3d8dd69d9a 100644 --- a/dash-renderer/src/actions/isAppReady.js +++ b/dash-renderer/src/actions/isAppReady.js @@ -36,7 +36,7 @@ export default (layout, paths, targets) => { ready, rendered.then( () => document.getElementById(stringifyId(id)) && ready - ), + ) ]) ); } diff --git a/dash-renderer/src/actions/isLoading.ts b/dash-renderer/src/actions/isLoading.ts index a501211317..5635cc7cd6 100644 --- a/dash-renderer/src/actions/isLoading.ts +++ b/dash-renderer/src/actions/isLoading.ts @@ -1,5 +1,7 @@ -import { createAction } from 'redux-actions'; +import {createAction} from 'redux-actions'; -import { IsLoadingActionType, IsLoadingState } from '../reducers/isLoading'; +import {IsLoadingActionType, IsLoadingState} from '../reducers/isLoading'; -export const setIsLoading = createAction(IsLoadingActionType.Set); +export const setIsLoading = createAction( + IsLoadingActionType.Set +); diff --git a/dash-renderer/src/actions/loadingMap.ts b/dash-renderer/src/actions/loadingMap.ts index 2e4834dbd8..60512106ce 100644 --- a/dash-renderer/src/actions/loadingMap.ts +++ b/dash-renderer/src/actions/loadingMap.ts @@ -1,5 +1,7 @@ -import { createAction } from 'redux-actions'; +import {createAction} from 'redux-actions'; -import { LoadingMapActionType, LoadingMapState } from '../reducers/loadingMap'; +import {LoadingMapActionType, LoadingMapState} from '../reducers/loadingMap'; -export const setLoadingMap = createAction(LoadingMapActionType.Set); +export const setLoadingMap = createAction( + LoadingMapActionType.Set +); diff --git a/dash-renderer/src/actions/paths.js b/dash-renderer/src/actions/paths.js index d9eca5a6be..e34bc7ea1c 100644 --- a/dash-renderer/src/actions/paths.js +++ b/dash-renderer/src/actions/paths.js @@ -5,7 +5,7 @@ import { forEachObjIndexed, path, propEq, - props, + props } from 'ramda'; import {crawlLayout} from './utils'; diff --git a/dash-renderer/src/components/core/Loading.react.js b/dash-renderer/src/components/core/Loading.react.js index b4eb2793f5..00f5add0a5 100644 --- a/dash-renderer/src/components/core/Loading.react.js +++ b/dash-renderer/src/components/core/Loading.react.js @@ -4,15 +4,15 @@ import PropTypes from 'prop-types'; function Loading(props) { if (props.isLoading) { - return
; + return
; } return null; } Loading.propTypes = { - isLoading: PropTypes.bool.isRequired, + isLoading: PropTypes.bool.isRequired }; export default connect(state => ({ - isLoading: state.isLoading, + isLoading: state.isLoading }))(Loading); diff --git a/dash-renderer/src/components/core/Reloader.react.js b/dash-renderer/src/components/core/Reloader.react.js index 0a20dd9edd..3bfa9f41eb 100644 --- a/dash-renderer/src/components/core/Reloader.react.js +++ b/dash-renderer/src/components/core/Reloader.react.js @@ -7,7 +7,7 @@ import { lt, path, pathOr, - sort, + sort } from 'ramda'; import React from 'react'; import PropTypes from 'prop-types'; @@ -24,11 +24,11 @@ class Reloader extends React.Component { disabled: false, intervalId: null, packages: null, - max_retry, + max_retry }; } else { this.state = { - disabled: true, + disabled: true }; } this._retry = 0; @@ -202,13 +202,13 @@ Reloader.propTypes = { config: PropTypes.object, reloadRequest: PropTypes.object, dispatch: PropTypes.func, - interval: PropTypes.number, + interval: PropTypes.number }; export default connect( state => ({ config: state.config, - reloadRequest: state.reloadRequest, + reloadRequest: state.reloadRequest }), dispatch => ({dispatch}) )(Reloader); diff --git a/dash-renderer/src/components/core/Toolbar.react.js b/dash-renderer/src/components/core/Toolbar.react.js index 7d5941658d..b30e3de018 100644 --- a/dash-renderer/src/components/core/Toolbar.react.js +++ b/dash-renderer/src/components/core/Toolbar.react.js @@ -12,24 +12,24 @@ function UnconnectedToolbar(props) { display: 'inline-block', opacity: '0.2', ':hover': { - opacity: 1, - }, + opacity: 1 + } }, iconStyle: { - fontSize: 20, + fontSize: 20 }, labelStyle: { - fontSize: 15, - }, + fontSize: 15 + } }; const undoLink = (
{history.past.length > 0 ? undoLink : null} @@ -99,12 +99,12 @@ function UnconnectedToolbar(props) { UnconnectedToolbar.propTypes = { history: PropTypes.object, - dispatch: PropTypes.func, + dispatch: PropTypes.func }; const Toolbar = connect( state => ({ - history: state.history, + history: state.history }), dispatch => ({dispatch}) )(Radium(UnconnectedToolbar)); diff --git a/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainer.css b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainer.css index fb98faa56e..025e59eddc 100644 --- a/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainer.css +++ b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainer.css @@ -3,8 +3,8 @@ position: fixed; bottom: 165px; right: 16px; - max-width: 80vw; - max-height: calc(100vh - 180px); + width: 80vw; + height: calc(100vh - 180px); overflow: auto; box-sizing: border-box; background: #ffffff; @@ -14,3 +14,34 @@ 0px 2px 6px rgba(80, 103, 132, 0.12), 0px 0px 1px rgba(80, 103, 132, 0.32); } + +.dash-callback-dag--info { + border-radius: 4px; + position: absolute; + padding: 8px; + bottom: 16px; + left: 16px; + max-width: calc(100% - 32px); + max-height: 50%; + overflow: auto; + box-sizing: border-box; + background: rgba(255,255,255,0.9); + border: 2px solid #ccc; + font-family: "Arial", sans-serif; +} + +.dash-callback-dag--message { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + line-height: 2em; + font-family: "Arial", sans-serif; +} + +.dash-callback-dag--layoutSelector { + position: absolute; + top: 10px; + right: 10px; +} diff --git a/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainer.react.js b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainer.react.js index 480e65e768..3e298154ce 100644 --- a/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainer.react.js +++ b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainer.react.js @@ -1,89 +1,448 @@ -import React, {useEffect, useRef} from 'react'; +import React, {Component, useState, useMemo, useEffect, useRef} from 'react'; import PropTypes from 'prop-types'; +import {connect, useSelector} from 'react-redux'; +import Cytoscape from 'cytoscape'; +import CytoscapeComponent from 'react-cytoscapejs'; +import Dagre from 'cytoscape-dagre'; +import JSONTree from 'react-json-tree'; +import {keys, mergeRight, omit, path} from 'ramda'; + +import {getPath} from '../../../actions/paths'; +import {stringifyId} from '../../../actions/dependencies'; +import {onError} from '../../../actions'; import './CallbackGraphContainer.css'; +import stylesheet from './CallbackGraphContainerStylesheet'; +import { + updateSelectedNode, + updateChangedProps, + updateCallback +} from './CallbackGraphEffects'; -import Viz from 'viz.js'; -import {Module, render} from 'viz.js/full.render'; +Cytoscape.use(Dagre); -import {stringifyId} from '../../../actions/dependencies'; +/* + * Generates all the elements (nodes, edeges) for the dependency graph. + */ +function generateElements(graphs, profile) { + const consumed = []; + const elements = []; -const CallbackGraphContainer = ({graphs}) => { - const el = useRef(null); + function recordNode(id, property) { + const idStr = stringifyId(id); + const idType = typeof id === 'object' ? 'wildcard' : 'component'; - const viz = useRef(null); + const parent = idStr; + const child = `${idStr}.${property}`; - const makeViz = () => { - viz.current = new Viz({Module, render}); - }; + if (!consumed.includes(parent)) { + consumed.push(parent); + elements.push({ + data: { + id: idStr, + label: idStr, + type: idType + } + }); + } - if (!viz.current) { - makeViz(); + if (!consumed.includes(child)) { + consumed.push(child); + elements.push({ + data: { + id: child, + label: property, + parent: parent, + type: 'property' + } + }); + } + + return child; } - useEffect(() => { - const {callbacks} = graphs; - const elements = {}; - const callbacksOut = []; - const links = callbacks.map(({inputs, outputs}, i) => { - callbacksOut.push(`cb${i};`); - function recordAndReturn({id, property}) { - const idClean = stringifyId(id) - .replace(/[\{\}".;\[\]()]/g, '') - .replace(/:/g, '-') - .replace(/,/g, '_'); - elements[idClean] = elements[idClean] || {}; - elements[idClean][property] = true; - return `"${idClean}.${property}"`; + function recordEdge(source, target, type) { + elements.push({ + data: { + source: source, + target: target, + type: type } - const out_nodes = outputs.map(recordAndReturn).join(', '); - const in_nodes = inputs.map(recordAndReturn).join(', '); - return `{${in_nodes}} -> cb${i} -> {${out_nodes}};`; }); + } - const dot = `digraph G { - overlap = false; fontname="Arial"; fontcolor="#333333"; - edge [color="#888888"]; - node [shape=box, fontname="Arial", style=filled, color="#109DFF", fontcolor=white]; - graph [penwidth=0]; - subgraph callbacks { - node [shape=circle, width=0.3, label="", color="#00CC96"]; - ${callbacksOut.join('\n')} } - - ${Object.entries(elements) - .map( - ([id, props], i) => ` - subgraph cluster_${i} { - bgcolor="#B9C2CE"; - ${Object.keys(props) - .map(p => `"${id}.${p}" [label="${p}"];`) - .join('\n')} - label = "${id}"; }` - ) - .join('\n')} - - ${links.join('\n')} }`; - - viz.current - .renderSVGElement(dot) - .then(vizEl => { - el.current.innerHTML = ''; - el.current.appendChild(vizEl); - }) - .catch(e => { - // https://github.com/mdaines/viz.js/wiki/Caveats - makeViz(); - // eslint-disable-next-line no-console - console.error(e); - el.current.innerHTML = 'Error creating callback graph'; - }); + (graphs.callbacks || []).forEach((callback, i) => { + const cb = `__dash_callback__.${callback.output}`; + const cbProfile = profile.callbacks[callback.output] || {}; + const count = cbProfile.count || 0; + const time = cbProfile.total || 0; + + elements.push({ + data: { + id: cb, + label: `callback.${i}`, + type: 'callback', + mode: callback.clientside_function ? 'client' : 'server', + count: count, + time: count > 0 ? Math.round(time / count) : 0, + loadingSet: Date.now(), + errorSet: Date.now() + } + }); + + callback.outputs.map(({id, property}) => { + const node = recordNode(id, property); + recordEdge(cb, node, 'output'); + }); + + callback.inputs.map(({id, property}) => { + const node = recordNode(id, property); + recordEdge(node, cb, 'input'); + }); + + callback.state.map(({id, property}) => { + const node = recordNode(id, property); + recordEdge(node, cb, 'state'); + }); }); - return
; + return elements; +} + +function reduceStatus(status) { + if (keys(status).length === 2) { + return status.latest; + } + return status; +} + +function flattenOutputs(res) { + const outputs = {}; + for (const idStr in res) { + for (const prop in res[idStr]) { + outputs[idStr + '.' + prop] = res[idStr][prop]; + } + } + return outputs; +} + +function flattenInputs(inArray, final) { + (inArray || []).forEach(inItem => { + if (Array.isArray(inItem)) { + flattenInputs(inItem, final); + } else { + const {id, property, value} = inItem; + final[stringifyId(id) + '.' + property] = value; + } + }); + return final; +} + +// len('__dash_callback__.') +const cbPrefixLen = 18; + +const layouts = { + 'top-down': { + name: 'dagre', + padding: 10, + spacingFactor: 0.8 + }, + 'left-right': { + name: 'dagre', + padding: 10, + nodeSep: 0, + rankSep: 80, + rankDir: 'LR' + }, + force: { + name: 'cose', + padding: 10, + animate: false + } }; -CallbackGraphContainer.propTypes = { - graphs: PropTypes.object, +function CallbackGraph() { + // Grab items from the redux store. + const paths = useSelector(state => state.paths); + const layout = useSelector(state => state.layout); + const graphs = useSelector(state => state.graphs); + const profile = useSelector(state => state.profile); + const changed = useSelector(state => state.changed); + const lifecycleState = useSelector(state => state.appLifecycle); + + // Keep track of cytoscape reference and user selected items. + const [selected, setSelected] = useState(null); + const [cytoscape, setCytoscape] = useState(null); + + const {graphLayout} = profile; + const chosenType = graphLayout?._chosenType; + const layoutSelector = useRef(null); + const [layoutType, setLayoutType] = useState(chosenType || 'top-down'); + + // Generate and memoize the elements. + const elements = useMemo(() => generateElements(graphs, profile), [graphs]); + + // Custom hook to make sure cytoscape is loaded. + const useCytoscapeEffect = (effect, condition) => { + useEffect( + () => (cytoscape && effect(cytoscape)) || undefined, + condition + ); + }; + + function setPresetLayout({cy}) { + const positions = {}; + cy.nodes().each(n => { + positions[n.id()] = n.position(); + }); + profile.graphLayout = { + name: 'preset', + fit: false, + positions, + zoom: cy.zoom(), + pan: cy.pan(), + _chosenType: layoutSelector.current?.value + }; + } + + // Adds callbacks once cyctoscape is intialized. + useCytoscapeEffect( + cy => { + cytoscape.on('tap', 'node', e => setSelected(e.target)); + cytoscape.on('tap', e => { + if (e.target === cy) { + setSelected(null); + } + }); + cytoscape.on('zoom', setPresetLayout); + cytoscape.on('pan', setPresetLayout); + cytoscape.nodes().on('position', setPresetLayout); + }, + [cytoscape] + ); + + // Set node classes on selected. + useCytoscapeEffect( + cy => selected && updateSelectedNode(cy, selected.data().id), + [selected] + ); + + // Flash classes when props change. Uses changed as a trigger. Also + // flash all input edges originating from this node and highlight + // the subtree that contains the selected node. + useCytoscapeEffect( + cy => changed && updateChangedProps(cy, changed.id, changed.props), + [changed] + ); + + // Update callbacks from profiling information. + useCytoscapeEffect( + cy => + profile.updated.forEach(cb => + updateCallback(cy, cb, profile.callbacks[cb]) + ), + [profile.updated] + ); + + if (lifecycleState !== 'HYDRATED') { + // If we get here too early - most likely during hot reloading - then + // we need to bail out and wait for the full state to be available + return ( +
+
+
Waiting for app to be ready...
+
+
+ ); + } + + // FIXME: Move to a new component? + // Generate the element introspection data. + let elementName = ''; + let elementInfo = {}; + let hasPatterns = false; + + if (selected) { + function getComponent(id) { + // for now ignore pattern-matching IDs + // to do better we may need to store the *actual* IDs used for each + // callback invocation, since they need not match what's on the page now. + if (id.charAt(0) === '{') { + hasPatterns = true; + return undefined; + } + const idPath = getPath(paths, id); + return idPath ? path(idPath, layout) : undefined; + } + + function getPropValue(data) { + const parent = getComponent(data.parent); + return parent ? parent.props[data.label] : undefined; + } + + const data = selected.data(); + + switch (data.type) { + case 'component': { + const rest = omit(['id'], getComponent(data.id)?.props); + elementInfo = rest; + elementName = data.id; + break; + } + + case 'property': { + elementName = data.parent; + elementInfo[data.label] = getPropValue(data); + break; + } + + // callback + default: { + elementInfo.type = data.mode; + + // Remove uid and set profile. + const callbackOutputId = data.id.slice(cbPrefixLen); + elementName = callbackOutputId.replace(/(^\.\.|\.\.$)/g, ''); + const cbProfile = profile.callbacks[callbackOutputId]; + if (cbProfile) { + const { + count, + status, + network, + resources, + total, + compute, + result, + inputs, + state + } = cbProfile; + + const avg = v => Math.round(v / (count || 1)); + + elementInfo['call count'] = count; + elementInfo.status = reduceStatus(status); + + const timing = (elementInfo['time (avg milliseconds)'] = { + total: avg(total), + compute: avg(compute) + }); + if (data.mode === 'server') { + timing.network = avg(network.time); + + elementInfo['data transfer (avg bytes)'] = { + download: avg(network.download), + upload: avg(network.upload) + }; + } + for (const key in resources) { + timing['user: ' + key] = avg(resources[key]); + } + + elementInfo.outputs = flattenOutputs(result); + elementInfo.inputs = flattenInputs(inputs, {}); + elementInfo.state = flattenInputs(state, {}); + } else { + elementInfo['call count'] = 0; + } + } + } + } + + const cyLayout = + chosenType === layoutType + ? graphLayout + : mergeRight(layouts[layoutType], {ready: setPresetLayout}); + + return ( +
+ + {selected ? ( +
+ {hasPatterns ? ( +
+ Info isn't supported for pattern-matching IDs at + this time +
+ ) : null} + + _keys.length === 1 ? elementName : _keys[0] + } + getItemString={(type, data, itemType) => ( + {itemType} + )} + shouldExpandNode={(keyName, data, level) => level < 1} + /> +
+ ) : null} + +
+ ); +} + +CallbackGraph.propTypes = {}; + +class UnconnectedCallbackGraphContainer extends Component { + constructor(props) { + super(props); + this.state = {hasError: false}; + } + + static getDerivedStateFromError(_) { + return {hasError: true}; + } + + componentDidCatch(error, info) { + const {dispatch} = this.props; + dispatch( + onError({ + myID: this.state.myID, + type: 'frontEnd', + error, + info + }) + ); + } + + render() { + return this.state.hasError ? ( +
+
+
Oops! The callback graph threw an error.
+
Check the error list for details.
+
+
+ ) : ( + + ); + } +} + +UnconnectedCallbackGraphContainer.propTypes = { + dispatch: PropTypes.func }; +const CallbackGraphContainer = connect(null, dispatch => ({dispatch}))( + UnconnectedCallbackGraphContainer +); + export {CallbackGraphContainer}; diff --git a/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainerStylesheet.js b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainerStylesheet.js new file mode 100644 index 0000000000..60a7305b43 --- /dev/null +++ b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphContainerStylesheet.js @@ -0,0 +1,154 @@ +const stylesheet = [ + { + selector: '*', + style: { + 'font-size': 12, + 'font-family': '"Arial", sans-serif' + } + }, + + { + selector: 'edge', + style: { + width: 2, + 'line-color': '#888888', + 'target-arrow-color': '#888888', + 'target-arrow-shape': 'triangle', + 'target-arrow-fill': 'filled', + 'arrow-scale': 1, + 'curve-style': 'bezier', + 'control-point-step-size': 40, + 'transition-property': 'line-color, target-arrow-color', + 'transition-duration': '0.25s', + 'transition-timing-function': 'ease-in-out-sine' + } + }, + + { + selector: 'edge[type="output"]', + style: { + 'line-color': '#0080D0', + 'target-arrow-color': '#0080D0' + } + }, + + { + selector: 'edge[type="state"]', + style: { + 'line-style': 'dashed' + } + }, + + { + selector: 'node', + style: { + color: '#333333', + padding: 6, + 'text-valign': 'center', + 'text-halign': 'center', + 'border-width': 2, + 'border-opacity': 0, + 'border-style': 'solid', + 'border-color': '#888888', + 'transition-property': 'border-opacity', + 'transition-duration': '0.25s', + 'transition-timing-function': 'ease-in-out-sine' + } + }, + + { + selector: 'node[type="callback"]', + style: { + width: 35, + height: 25, + shape: 'round-rectangle', + label: e => `${e.data().count}\n${e.data().time} ms`, + 'text-wrap': 'wrap', + 'text-justification': 'center' + } + }, + + { + selector: 'node[type="callback"][mode="client"]', + style: { + color: '#323330', + 'background-color': '#F0DB4F' + } + }, + + { + selector: 'node[type="callback"][mode="server"]', + style: { + color: '#323330', + 'background-color': '#00CC96' + } + }, + + { + selector: 'node[type="component"], node[type="wildcard"]', + style: { + width: 'label', + // height: 'label', + shape: 'rectangle', + content: e => e.data().label.replace(/\"/g, ''), + 'text-valign': 'top', + 'background-color': '#B9C2CE' + } + }, + + { + selector: 'node[type="property"]', + style: { + width: 'label', + height: 20, + shape: 'rectangle', + content: 'data(label)', + color: 'white', + 'background-color': '#109DFF' + } + }, + + { + selector: '.selected-node', + style: { + ghost: 'yes', + 'ghost-offset-x': 2, + 'ghost-offset-y': 2, + 'ghost-opacity': 0.25, + 'border-opacity': 1 + } + }, + + { + selector: '.prop-changed, .callback-loading', + style: { + 'border-color': '#CC43B7', + 'border-width': 2, + 'border-opacity': 1 + } + }, + + { + selector: '.callback-error', + style: { + 'background-color': '#E1332E' + } + }, + + { + selector: '.triggered', + style: { + 'line-color': '#CC43B7', + 'target-arrow-color': '#CC43B7' + } + }, + + { + selector: '.inactive', + style: { + opacity: 0.5 + } + } +]; + +export default stylesheet; diff --git a/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js new file mode 100644 index 0000000000..05c777f8cc --- /dev/null +++ b/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js @@ -0,0 +1,161 @@ +import {STATUS, STATUSMAP} from '../../../constants/constants'; + +/** + * getEdgeTypes + * + * Finds all edges connected to a node and splits them by type. + * + * @param {Object} node - Cytoscape node. + * @returns {Object} - Object contaiing the edges, sorted by type. + */ +function getEdgeTypes(node) { + const elements = node.connectedEdges(); + return { + input: elements.filter('[type = "input"]'), + state: elements.filter('[type = "state"]'), + output: elements.filter('[type = "output"]') + }; +} + +/** + * updateSelected + * + * Updates the classes of the selected node and recenters the viewport. + * + * @param {Object} cy - Reference to the cytoscape instance. + * @param {String} id - The id of the selected node. + * @returns {function} - cleanup function, for useEffect hook + */ +export function updateSelectedNode(cy, id) { + if (id) { + const node = cy.getElementById(id); + + // Highlght the selected node. + + node.addClass('selected-node'); + + // Find the subtree that the node belongs to. A subtree contains + // all all ancestors and descendents that are connected via Inputs + // or Outputs (but not State). + + // WARNING: No cycle detection! + + function ascend(node, collection) { + // FIXME: Should we include State parents but non-recursively? + const type = node.data().type === 'callback' ? 'input' : 'output'; + const edges = getEdgeTypes(node)[type]; + const parents = edges.sources(); + collection.merge(edges); + collection.merge(parents); + if (node.data().type === 'property') { + collection.merge(node.ancestors()); + } + parents.forEach(node => ascend(node, collection)); + } + + function descend(node, collection) { + const type = node.data().type === 'callback' ? 'output' : 'input'; + const edges = getEdgeTypes(node)[type]; + const children = edges.targets(); + collection.merge(edges); + collection.merge(children); + if (node.data().type === 'property') { + collection.merge(node.ancestors()); + } + children.forEach(node => descend(node, collection)); + } + + const subtree = cy.collection(); + subtree.merge(node); + ascend(node, subtree); + descend(node, subtree); + + const other = subtree.absoluteComplement(); + other.addClass('inactive'); + + return () => { + node.removeClass('selected-node'); + other.removeClass('inactive'); + }; + } + return undefined; +} + +/** + * updateChangedProp + * + * Flashes property nodes that updated and any inputs they are connected to. + * + * @param {Object} cy - Reference to the cytoscape instance. + * @param {String} id - The component id which updated. + * @param {Object} props - The props that updated. + * @param {Number} flashTime - The time to flash classes for in ms. + * @returns {undefined} + */ +export function updateChangedProps(cy, id, props, flashTime = 500) { + Object.keys(props).forEach(prop => { + const node = cy.getElementById(`${id}.${prop}`); + node.flashClass('prop-changed', flashTime); + node.edgesTo('*') + .filter('[type = "input"]') + .flashClass('triggered', flashTime); + }); +} + +/** + * updateCallback + * + * Updates a callback node with profiling information (call count, avg time) + * and adds or removes classes as necessary. Classes are always assert for + * at least flashTime ms so that they can be visually observed. When callbacks + * resolve, all output edges are flashed. + * + * @param {Object} cy - Reference to the cytoscape instance. + * @param {String} id - The id of the callback (i.e., it's output identifier) + * @param {Object} profile - The callback profiling infomration. + * @param {Number} flashTime - The time to flash classes for in ms. + * @returns {undefined} + */ +export function updateCallback(cy, id, profile, flashTime = 500) { + const node = cy.getElementById(`__dash_callback__.${id}`); + const {count, total, status} = profile; + const {latest} = status; + + // Update data. + const avgTime = count > 0 ? total / count : 0; + node.data('count', count); + node.data('time', Math.round(avgTime)); + + // Either flash the classes OR maintain it for long callbacks. + if (latest === 'loading') { + node.data('loadingSet', Date.now()); + node.addClass('callback-loading'); + } else if (node.hasClass('callback-loading')) { + const timeLeft = node.data('loadingSet') + flashTime - Date.now(); + setTimeout( + () => node.removeClass('callback-loading'), + Math.max(timeLeft, 0) + ); + } + + if ( + latest !== 'loading' && + latest !== STATUSMAP[STATUS.OK] && + latest !== STATUSMAP[STATUS.PREVENT_UPDATE] + ) { + node.data('errorSet', Date.now()); + node.addClass('callback-error'); + } else if (node.hasClass('callback-error')) { + const timeLeft = node.data('errorSet') + flashTime - Date.now(); + setTimeout( + () => node.removeClass('callback-error'), + Math.max(timeLeft, 0) + ); + } + + // FIXME: This will flash branches that return no_update!! + // If the callback resolved properly, flash the outputs. + if (latest === STATUSMAP[STATUS.OK]) { + node.edgesTo('*').flashClass('triggered', flashTime); + } +} diff --git a/dash-renderer/src/components/error/ComponentErrorBoundary.react.js b/dash-renderer/src/components/error/ComponentErrorBoundary.react.js index f62b63cbe6..dbc2171f47 100644 --- a/dash-renderer/src/components/error/ComponentErrorBoundary.react.js +++ b/dash-renderer/src/components/error/ComponentErrorBoundary.react.js @@ -8,7 +8,7 @@ class ComponentErrorBoundary extends Component { this.state = { myID: props.componentId, oldChildren: null, - hasError: false, + hasError: false }; } @@ -23,7 +23,7 @@ class ComponentErrorBoundary extends Component { myID: this.state.myID, type: 'frontEnd', error, - info, + info }) ); dispatch(revert); @@ -38,7 +38,7 @@ class ComponentErrorBoundary extends Component { ) { /* eslint-disable-next-line react/no-did-update-set-state */ this.setState({ - oldChildren: prevChildren, + oldChildren: prevChildren }); } } @@ -53,7 +53,7 @@ ComponentErrorBoundary.propTypes = { children: PropTypes.object, componentId: PropTypes.string, error: PropTypes.object, - dispatch: PropTypes.func, + dispatch: PropTypes.func }; export default ComponentErrorBoundary; diff --git a/dash-renderer/src/components/error/FrontEnd/FrontEndError.react.js b/dash-renderer/src/components/error/FrontEnd/FrontEndError.react.js index a4cac920ea..5703add4db 100644 --- a/dash-renderer/src/components/error/FrontEnd/FrontEndError.react.js +++ b/dash-renderer/src/components/error/FrontEnd/FrontEndError.react.js @@ -12,7 +12,7 @@ class FrontEndError extends Component { constructor(props) { super(props); this.state = { - collapsed: this.props.isListItem, + collapsed: this.props.isListItem }; } @@ -27,20 +27,20 @@ class FrontEndError extends Component { /* eslint-disable no-inline-comments */ const errorHeader = (
this.setState({collapsed: !collapsed})} > - + ⛑️ - + {e.error.message || 'Error'} - - + + {`${e.timestamp.toLocaleTimeString()}`} - + {/* Special percy timestamp for visual testing. * Hidden during regular usage. */} @@ -59,7 +59,7 @@ class FrontEndError extends Component { /* eslint-enable no-inline-comments */ return collapsed ? ( -
{errorHeader}
+
{errorHeader}
) : (
{errorHeader} @@ -73,7 +73,7 @@ const MAX_MESSAGE_LENGTH = 40; /* eslint-disable no-inline-comments */ function UnconnectedErrorContent({error, base}) { return ( -
+
{/* * 40 is a rough heuristic - if longer than 40 then the * message might overflow into ellipses in the title above & @@ -81,16 +81,16 @@ function UnconnectedErrorContent({error, base}) { */} {typeof error.message !== 'string' || error.message.length < MAX_MESSAGE_LENGTH ? null : ( -
-
+
+
{error.message}
)} {typeof error.stack !== 'string' ? null : ( -
-
+
+
@@ -112,8 +112,8 @@ function UnconnectedErrorContent({error, base}) { {typeof error.html !== 'string' ? null : error.html.indexOf( ' -
+
+
{/* Embed werkzeug debugger in an iframe to prevent CSS leaking - werkzeug HTML includes a bunch of CSS on base html elements like `` @@ -138,14 +138,14 @@ function UnconnectedErrorContent({error, base}) { */ width: 'calc(600px - 67px)', height: '75vh', - border: 'none', + border: 'none' }} />
) : ( -
-
{error.html}
+
+
{error.html}
)}
@@ -160,12 +160,12 @@ const errorPropTypes = PropTypes.shape({ stack: PropTypes.string, /* backend error messages */ - html: PropTypes.string, + html: PropTypes.string }); UnconnectedErrorContent.propTypes = { error: errorPropTypes, - base: PropTypes.string, + base: PropTypes.string }; const ErrorContent = connect(state => ({base: urlBase(state.config)}))( @@ -175,15 +175,15 @@ const ErrorContent = connect(state => ({base: urlBase(state.config)}))( FrontEndError.propTypes = { e: PropTypes.shape({ timestamp: PropTypes.object, - error: errorPropTypes, + error: errorPropTypes }), inAlertsTray: PropTypes.bool, - isListItem: PropTypes.bool, + isListItem: PropTypes.bool }; FrontEndError.defaultProps = { inAlertsTray: false, - isListItem: false, + isListItem: false }; export {FrontEndError}; diff --git a/dash-renderer/src/components/error/FrontEnd/FrontEndErrorContainer.react.js b/dash-renderer/src/components/error/FrontEnd/FrontEndErrorContainer.react.js index 794070aebb..3bdbf51cb3 100644 --- a/dash-renderer/src/components/error/FrontEnd/FrontEndErrorContainer.react.js +++ b/dash-renderer/src/components/error/FrontEnd/FrontEndErrorContainer.react.js @@ -26,16 +26,16 @@ class FrontEndErrorContainer extends Component { } return (
-
-
+
+
🛑 Errors ( - + {errorsLength} ){connected ? null : '\u00a0 🚫 Server Unavailable'}
-
{errorElements}
+
{errorElements}
); } @@ -44,11 +44,11 @@ class FrontEndErrorContainer extends Component { FrontEndErrorContainer.propTypes = { errors: PropTypes.array, connected: PropTypes.bool, - inAlertsTray: PropTypes.any, + inAlertsTray: PropTypes.any }; FrontEndErrorContainer.propTypes = { - inAlertsTray: PropTypes.any, + inAlertsTray: PropTypes.any }; export {FrontEndErrorContainer}; diff --git a/dash-renderer/src/components/error/GlobalErrorContainer.react.js b/dash-renderer/src/components/error/GlobalErrorContainer.react.js index bd9fa3887a..4ca50cfd01 100644 --- a/dash-renderer/src/components/error/GlobalErrorContainer.react.js +++ b/dash-renderer/src/components/error/GlobalErrorContainer.react.js @@ -10,15 +10,11 @@ class UnconnectedGlobalErrorContainer extends Component { } render() { - const {config, error, graphs, children} = this.props; + const {config, error, children} = this.props; return ( -
- -
{children}
+
+ +
{children}
); @@ -28,14 +24,12 @@ class UnconnectedGlobalErrorContainer extends Component { UnconnectedGlobalErrorContainer.propTypes = { children: PropTypes.object, config: PropTypes.object, - error: PropTypes.object, - graphs: PropTypes.object, + error: PropTypes.object }; const GlobalErrorContainer = connect(state => ({ config: state.config, - error: state.error, - graphs: state.graphs, + error: state.error }))(Radium(UnconnectedGlobalErrorContainer)); export default GlobalErrorContainer; diff --git a/dash-renderer/src/components/error/GlobalErrorContainerPassthrough.react.js b/dash-renderer/src/components/error/GlobalErrorContainerPassthrough.react.js index 11f4975f29..161f239257 100644 --- a/dash-renderer/src/components/error/GlobalErrorContainerPassthrough.react.js +++ b/dash-renderer/src/components/error/GlobalErrorContainerPassthrough.react.js @@ -6,12 +6,12 @@ class GlobalErrorContainer extends Component { super(props); } render() { - return
{this.props.children}
; + return
{this.props.children}
; } } GlobalErrorContainer.propTypes = { - children: PropTypes.object, + children: PropTypes.object }; export default GlobalErrorContainer; diff --git a/dash-renderer/src/components/error/GlobalErrorOverlay.react.js b/dash-renderer/src/components/error/GlobalErrorOverlay.react.js index bdbc46b34d..4ad50a978c 100644 --- a/dash-renderer/src/components/error/GlobalErrorOverlay.react.js +++ b/dash-renderer/src/components/error/GlobalErrorOverlay.react.js @@ -27,7 +27,7 @@ export default class GlobalErrorOverlay extends Component { return (
{this.props.children}
-
+
{frontEndErrors}
@@ -41,5 +41,5 @@ GlobalErrorOverlay.propTypes = { children: PropTypes.object, visible: PropTypes.bool, error: PropTypes.object, - errorsOpened: PropTypes.any, + errorsOpened: PropTypes.any }; diff --git a/dash-renderer/src/components/error/menu/DebugMenu.react.js b/dash-renderer/src/components/error/menu/DebugMenu.react.js index 636323eb48..47f0d5cf23 100644 --- a/dash-renderer/src/components/error/menu/DebugMenu.react.js +++ b/dash-renderer/src/components/error/menu/DebugMenu.react.js @@ -24,7 +24,7 @@ const buttonFactory = ( iconVariant, label ) => ( -
+
<_Icon className={classes('dash-debug-menu__icon', iconVariant)} /> {label ? ( - + ) : null}
@@ -48,12 +48,12 @@ class DebugMenu extends Component { this.state = { opened: false, callbackGraphOpened: false, - errorsOpened: true, + errorsOpened: true }; } render() { const {opened, errorsOpened, callbackGraphOpened} = this.state; - const {error, graphs, hotReload} = this.props; + const {error, hotReload} = this.props; const errCount = error.frontEnd.length + error.backEnd.length; const connected = error.backEndConnected; @@ -74,16 +74,14 @@ class DebugMenu extends Component { : ClockIcon; const menuContent = opened ? ( -
- {callbackGraphOpened ? ( - - ) : null} +
+ {callbackGraphOpened ? : null} {buttonFactory( callbackGraphOpened, 'callbacks', () => { this.setState({ - callbackGraphOpened: !callbackGraphOpened, + callbackGraphOpened: !callbackGraphOpened }); }, GraphIcon, @@ -108,20 +106,20 @@ class DebugMenu extends Component { )}
) : ( -
+
); const alertsLabel = (errCount || !connected) && !opened ? ( -
-
+
+
{errCount ? ( -
+
{'🛑 ' + errCount}
) : null} {connected ? null : ( -
🚫
+
🚫
)}
@@ -160,8 +158,7 @@ class DebugMenu extends Component { DebugMenu.propTypes = { children: PropTypes.object, error: PropTypes.object, - graphs: PropTypes.object, - hotReload: PropTypes.bool, + hotReload: PropTypes.bool }; export {DebugMenu}; diff --git a/dash-renderer/src/constants/constants.js b/dash-renderer/src/constants/constants.js index f883eb68f8..c37b3b1f75 100644 --- a/dash-renderer/src/constants/constants.js +++ b/dash-renderer/src/constants/constants.js @@ -5,4 +5,10 @@ export const STATUS = { OK: 200, PREVENT_UPDATE: 204, CLIENTSIDE_ERROR: 'CLIENTSIDE_ERROR', + NO_RESPONSE: 'NO_RESPONSE' +}; + +export const STATUSMAP = { + [STATUS.OK]: 'SUCCESS', + [STATUS.PREVENT_UPDATE]: 'NO_UPDATE' }; diff --git a/dash-renderer/src/observers/documentTitle.ts b/dash-renderer/src/observers/documentTitle.ts index 3cbd893ab2..f4d9bcf1f4 100644 --- a/dash-renderer/src/observers/documentTitle.ts +++ b/dash-renderer/src/observers/documentTitle.ts @@ -1,11 +1,8 @@ -import { IStoreObserverDefinition } from '../StoreObserver'; -import { IStoreState } from '../store'; +import {IStoreObserverDefinition} from '../StoreObserver'; +import {IStoreState} from '../store'; const updateTitle = (getState: () => IStoreState) => { - const { - config, - isLoading - } = getState(); + const {config, isLoading} = getState(); const update_title = config?.update_title; @@ -30,24 +27,24 @@ const updateTitle = (getState: () => IStoreState) => { const observer: IStoreObserverDefinition = { inputs: ['isLoading'], mutationObserver: undefined, - observer: ({ - getState - }) => { - const { - config - } = getState(); + observer: ({getState}) => { + const {config} = getState(); if (observer.config !== config) { observer.config = config; observer.mutationObserver?.disconnect(); - observer.mutationObserver = new MutationObserver(() => updateTitle(getState)); + observer.mutationObserver = new MutationObserver(() => + updateTitle(getState) + ); const title = document.querySelector('title'); if (title) { - observer.mutationObserver.observe( - title, - { subtree: true, childList: true, attributes: true, characterData: true } - ); + observer.mutationObserver.observe(title, { + subtree: true, + childList: true, + attributes: true, + characterData: true + }); } } diff --git a/dash-renderer/src/observers/executedCallbacks.ts b/dash-renderer/src/observers/executedCallbacks.ts index 90e3787dd7..9df4809b86 100644 --- a/dash-renderer/src/observers/executedCallbacks.ts +++ b/dash-renderer/src/observers/executedCallbacks.ts @@ -12,7 +12,7 @@ import { toPairs } from 'ramda'; -import { IStoreState } from '../store'; +import {IStoreState} from '../store'; import { aggregateCallbacks, @@ -22,7 +22,7 @@ import { addStoredCallbacks } from '../actions/callbacks'; -import { parseIfWildcard } from '../actions/dependencies'; +import {parseIfWildcard} from '../actions/dependencies'; import { combineIdAndProp, @@ -31,33 +31,22 @@ import { includeObservers } from '../actions/dependencies_ts'; -import { - ICallback, - IStoredCallback -} from '../types/callbacks'; +import {ICallback, IStoredCallback} from '../types/callbacks'; -import { updateProps, setPaths, handleAsyncError } from '../actions'; -import { getPath, computePaths } from '../actions/paths'; +import {updateProps, setPaths, handleAsyncError} from '../actions'; +import {getPath, computePaths} from '../actions/paths'; -import { - applyPersistence, - prunePersistence -} from '../persistence'; -import { IStoreObserverDefinition } from '../StoreObserver'; +import {applyPersistence, prunePersistence} from '../persistence'; +import {IStoreObserverDefinition} from '../StoreObserver'; const observer: IStoreObserverDefinition = { - observer: ({ - dispatch, - getState - }) => { + observer: ({dispatch, getState}) => { const { - callbacks: { - executed - } + callbacks: {executed} } = getState(); function applyProps(id: any, updatedProps: any) { - const { layout, paths } = getState(); + const {layout, paths} = getState(); const itempath = getPath(paths, id); if (!itempath) { return false; @@ -74,7 +63,7 @@ const observer: IStoreObserverDefinition = { // In case the update contains whole components, see if any of // those components have props to update to persist user edits. - const { props } = applyPersistence({ props: updatedProps }, dispatch); + const {props} = applyPersistence({props: updatedProps}, dispatch); dispatch( updateProps({ @@ -91,16 +80,10 @@ const observer: IStoreObserverDefinition = { let storedCallbacks: IStoredCallback[] = []; forEach(cb => { - const predecessors = concat( - cb.predecessors ?? [], - [cb.callback] - ); + const predecessors = concat(cb.predecessors ?? [], [cb.callback]); const { - callback: { - clientside_function, - output - }, + callback: {clientside_function, output}, executionResult } = cb; @@ -108,12 +91,16 @@ const observer: IStoreObserverDefinition = { return; } - const { data, error, payload } = executionResult; + const {data, error, payload} = executionResult; if (data !== undefined) { - forEach(([id, props]: [any, { [key: string]: any }]) => { + forEach(([id, props]: [any, {[key: string]: any}]) => { const parsedId = parseIfWildcard(id); - const { graphs, layout: oldLayout, paths: oldPaths } = getState(); + const { + graphs, + layout: oldLayout, + paths: oldPaths + } = getState(); // Components will trigger callbacks on their own as required (eg. derived) const appliedProps = applyProps(parsedId, props); @@ -121,10 +108,19 @@ const observer: IStoreObserverDefinition = { // Add callbacks for modified inputs requestedCallbacks = concat( requestedCallbacks, - flatten(map( - prop => getCallbacksByInput(graphs, oldPaths, parsedId, prop, true), - keys(props) - )).map(rcb => ({ + flatten( + map( + prop => + getCallbacksByInput( + graphs, + oldPaths, + parsedId, + prop, + true + ), + keys(props) + ) + ).map(rcb => ({ ...rcb, predecessors })) @@ -132,12 +128,19 @@ const observer: IStoreObserverDefinition = { // New layout - trigger callbacks for that explicitly if (has('children', appliedProps)) { - const { children } = appliedProps; + const {children} = appliedProps; - const oldChildrenPath: string[] = concat(getPath(oldPaths, parsedId) as string[], ['props', 'children']); + const oldChildrenPath: string[] = concat( + getPath(oldPaths, parsedId) as string[], + ['props', 'children'] + ); const oldChildren = path(oldChildrenPath, oldLayout); - const paths = computePaths(children, oldChildrenPath, oldPaths); + const paths = computePaths( + children, + oldChildrenPath, + oldPaths + ); dispatch(setPaths(paths)); // Get callbacks for new layout (w/ execution group) @@ -156,7 +159,9 @@ const observer: IStoreObserverDefinition = { requestedCallbacks = concat( requestedCallbacks, getLayoutCallbacks(graphs, oldPaths, oldChildren, { - removedArrayInputsOnly: true, newPaths: paths, chunkPath: oldChildrenPath + removedArrayInputsOnly: true, + newPaths: paths, + chunkPath: oldChildrenPath }).map(rcb => ({ ...rcb, predecessors @@ -172,11 +177,16 @@ const observer: IStoreObserverDefinition = { appliedProps ); if (!isEmpty(addedProps)) { - const { graphs: currentGraphs, paths } = getState(); + const {graphs: currentGraphs, paths} = getState(); requestedCallbacks = concat( requestedCallbacks, - includeObservers(id, addedProps, currentGraphs, paths).map(rcb => ({ + includeObservers( + id, + addedProps, + currentGraphs, + paths + ).map(rcb => ({ ...rcb, predecessors })) @@ -190,25 +200,37 @@ const observer: IStoreObserverDefinition = { storedCallbacks.push({ ...cb, executionMeta: { - allProps: map(combineIdAndProp, flatten(cb.getOutputs(getState().paths))), - updatedProps: flatten(map( - ([id, value]) => map( - property => combineIdAndProp({ id, property }), - keys(value) - ), - toPairs(data) - )) + allProps: map( + combineIdAndProp, + flatten(cb.getOutputs(getState().paths)) + ), + updatedProps: flatten( + map( + ([id, value]) => + map( + property => + combineIdAndProp({id, property}), + keys(value) + ), + toPairs(data) + ) + ) } }); } if (error !== undefined) { const outputs = payload - ? map(combineIdAndProp, flatten([payload.outputs])).join(', ') + ? map(combineIdAndProp, flatten([payload.outputs])).join( + ', ' + ) : output; let message = `Callback error updating ${outputs}`; if (clientside_function) { - const { namespace: ns, function_name: fn } = clientside_function; + const { + namespace: ns, + function_name: fn + } = clientside_function; message += ` via clientside function ${ns}.${fn}`; } @@ -217,19 +239,28 @@ const observer: IStoreObserverDefinition = { storedCallbacks.push({ ...cb, executionMeta: { - allProps: map(combineIdAndProp, flatten(cb.getOutputs(getState().paths))), + allProps: map( + combineIdAndProp, + flatten(cb.getOutputs(getState().paths)) + ), updatedProps: [] } }); } }, executed); - dispatch(aggregateCallbacks([ - executed.length ? removeExecutedCallbacks(executed) : null, - executed.length ? addCompletedCallbacks(executed.length) : null, - storedCallbacks.length ? addStoredCallbacks(storedCallbacks) : null, - requestedCallbacks.length ? addRequestedCallbacks(requestedCallbacks) : null - ])); + dispatch( + aggregateCallbacks([ + executed.length ? removeExecutedCallbacks(executed) : null, + executed.length ? addCompletedCallbacks(executed.length) : null, + storedCallbacks.length + ? addStoredCallbacks(storedCallbacks) + : null, + requestedCallbacks.length + ? addRequestedCallbacks(requestedCallbacks) + : null + ]) + ); }, inputs: ['callbacks.executed'] }; diff --git a/dash-renderer/src/observers/executingCallbacks.ts b/dash-renderer/src/observers/executingCallbacks.ts index ffc92141e4..0ec1ef7908 100644 --- a/dash-renderer/src/observers/executingCallbacks.ts +++ b/dash-renderer/src/observers/executingCallbacks.ts @@ -1,9 +1,4 @@ -import { - assoc, - find, - forEach, - partition -} from 'ramda'; +import {assoc, find, forEach, partition} from 'ramda'; import { addExecutedCallbacks, @@ -13,48 +8,68 @@ import { removeWatchedCallbacks } from '../actions/callbacks'; -import { IStoreObserverDefinition } from '../StoreObserver'; -import { IStoreState } from '../store'; +import {IStoreObserverDefinition} from '../StoreObserver'; +import {IStoreState} from '../store'; const observer: IStoreObserverDefinition = { - observer: ({ - dispatch, - getState - }) => { + observer: ({dispatch, getState}) => { const { - callbacks: { - executing - } + callbacks: {executing} } = getState(); - const [deferred, skippedOrReady] = partition(cb => cb.executionPromise instanceof Promise, executing); + const [deferred, skippedOrReady] = partition( + cb => cb.executionPromise instanceof Promise, + executing + ); - dispatch(aggregateCallbacks([ - executing.length ? removeExecutingCallbacks(executing) : null, - deferred.length ? addWatchedCallbacks(deferred) : null, - skippedOrReady.length ? addExecutedCallbacks(skippedOrReady.map(cb => assoc('executionResult', cb.executionPromise as any, cb))) : null - ])); + dispatch( + aggregateCallbacks([ + executing.length ? removeExecutingCallbacks(executing) : null, + deferred.length ? addWatchedCallbacks(deferred) : null, + skippedOrReady.length + ? addExecutedCallbacks( + skippedOrReady.map(cb => + assoc( + 'executionResult', + cb.executionPromise as any, + cb + ) + ) + ) + : null + ]) + ); forEach(async cb => { const result = await cb.executionPromise; - const { callbacks: { watched } } = getState(); + const { + callbacks: {watched} + } = getState(); // Check if it's been removed from the `watched` list since - on callback completion, another callback may be cancelled // Find the callback instance or one that matches its promise (eg. could have been pruned) - const currentCb = find(_cb => _cb === cb || _cb.executionPromise === cb.executionPromise, watched); + const currentCb = find( + _cb => + _cb === cb || _cb.executionPromise === cb.executionPromise, + watched + ); if (!currentCb) { return; } // Otherwise move to `executed` and remove from `watched` - dispatch(aggregateCallbacks([ - removeWatchedCallbacks([currentCb]), - addExecutedCallbacks([{ - ...currentCb, - executionResult: result - }]) - ])); + dispatch( + aggregateCallbacks([ + removeWatchedCallbacks([currentCb]), + addExecutedCallbacks([ + { + ...currentCb, + executionResult: result + } + ]) + ]) + ); }, deferred); }, inputs: ['callbacks.executing'] diff --git a/dash-renderer/src/observers/isLoading.ts b/dash-renderer/src/observers/isLoading.ts index fc625d45a9..687f607378 100644 --- a/dash-renderer/src/observers/isLoading.ts +++ b/dash-renderer/src/observers/isLoading.ts @@ -1,18 +1,11 @@ -import { IStoreObserverDefinition } from '../StoreObserver'; -import { IStoreState } from '../store'; -import { getPendingCallbacks } from '../utils/callbacks'; -import { setIsLoading } from '../actions/isLoading'; - +import {IStoreObserverDefinition} from '../StoreObserver'; +import {IStoreState} from '../store'; +import {getPendingCallbacks} from '../utils/callbacks'; +import {setIsLoading} from '../actions/isLoading'; const observer: IStoreObserverDefinition = { - observer: ({ - dispatch, - getState - }) => { - const { - callbacks, - isLoading - } = getState(); + observer: ({dispatch, getState}) => { + const {callbacks, isLoading} = getState(); const pendingCallbacks = getPendingCallbacks(callbacks); diff --git a/dash-renderer/src/observers/loadingMap.ts b/dash-renderer/src/observers/loadingMap.ts index 50b300bfc1..d91e057811 100644 --- a/dash-renderer/src/observers/loadingMap.ts +++ b/dash-renderer/src/observers/loadingMap.ts @@ -1,27 +1,14 @@ -import { - equals, - flatten, - isEmpty, - map, - reduce -} from 'ramda'; +import {equals, flatten, isEmpty, map, reduce} from 'ramda'; -import { setLoadingMap } from '../actions/loadingMap'; -import { IStoreObserverDefinition } from '../StoreObserver'; -import { IStoreState } from '../store'; -import { ILayoutCallbackProperty } from '../types/callbacks'; +import {setLoadingMap} from '../actions/loadingMap'; +import {IStoreObserverDefinition} from '../StoreObserver'; +import {IStoreState} from '../store'; +import {ILayoutCallbackProperty} from '../types/callbacks'; const observer: IStoreObserverDefinition = { - observer: ({ - dispatch, - getState - }) => { + observer: ({dispatch, getState}) => { const { - callbacks: { - executing, - watched, - executed - }, + callbacks: {executing, watched, executed}, loadingMap, paths } = getState(); @@ -34,39 +21,48 @@ const observer: IStoreObserverDefinition = { are impacted for this node and nested nodes. */ - const loadingPaths: ILayoutCallbackProperty[] = flatten(map( - cb => cb.getOutputs(paths), - [...executing, ...watched, ...executed] - )); + const loadingPaths: ILayoutCallbackProperty[] = flatten( + map(cb => cb.getOutputs(paths), [ + ...executing, + ...watched, + ...executed + ]) + ); - const nextMap: any = isEmpty(loadingPaths) ? - null : - reduce( - (res, {id, property, path}) => { - let target = res; - const idprop = {id, property}; + const nextMap: any = isEmpty(loadingPaths) + ? null + : reduce( + (res, {id, property, path}) => { + let target = res; + const idprop = {id, property}; - // Assign all affected props for this path and nested paths - target.__dashprivate__idprops__ = target.__dashprivate__idprops__ || []; - target.__dashprivate__idprops__.push(idprop); + // Assign all affected props for this path and nested paths + target.__dashprivate__idprops__ = + target.__dashprivate__idprops__ || []; + target.__dashprivate__idprops__.push(idprop); - path.forEach((p, i) => { - target = (target[p] = target[p] ?? - (p === 'children' && typeof path[i + 1] === 'number' ? [] : {}) - ); + path.forEach((p, i) => { + target = target[p] = + target[p] ?? + (p === 'children' && + typeof path[i + 1] === 'number' + ? [] + : {}); - target.__dashprivate__idprops__ = target.__dashprivate__idprops__ || []; - target.__dashprivate__idprops__.push(idprop); - }); + target.__dashprivate__idprops__ = + target.__dashprivate__idprops__ || []; + target.__dashprivate__idprops__.push(idprop); + }); - // Assign one affected prop for this path - target.__dashprivate__idprop__ = target.__dashprivate__idprop__ || idprop; + // Assign one affected prop for this path + target.__dashprivate__idprop__ = + target.__dashprivate__idprop__ || idprop; - return res; - }, - {} as any, - loadingPaths - ); + return res; + }, + {} as any, + loadingPaths + ); if (!equals(nextMap, loadingMap)) { dispatch(setLoadingMap(nextMap)); diff --git a/dash-renderer/src/observers/prioritizedCallbacks.ts b/dash-renderer/src/observers/prioritizedCallbacks.ts index 0b6efeb2ba..dfda545878 100644 --- a/dash-renderer/src/observers/prioritizedCallbacks.ts +++ b/dash-renderer/src/observers/prioritizedCallbacks.ts @@ -1,15 +1,6 @@ -import { - find, - flatten, - forEach, - map, - partition, - pluck, - sort, - uniq -} from 'ramda'; - -import { IStoreState } from '../store'; +import {find, flatten, forEach, map, partition, pluck, sort, uniq} from 'ramda'; + +import {IStoreState} from '../store'; import { addBlockedCallbacks, @@ -20,11 +11,9 @@ import { removePrioritizedCallbacks } from '../actions/callbacks'; -import { stringifyId } from '../actions/dependencies'; +import {stringifyId} from '../actions/dependencies'; -import { - combineIdAndProp -} from '../actions/dependencies_ts'; +import {combineIdAndProp} from '../actions/dependencies_ts'; import isAppReady from '../actions/isAppReady'; @@ -34,71 +23,94 @@ import { ILayoutCallbackProperty, IPrioritizedCallback } from '../types/callbacks'; -import { IStoreObserverDefinition } from '../StoreObserver'; +import {IStoreObserverDefinition} from '../StoreObserver'; const sortPriority = (c1: ICallback, c2: ICallback): number => { return (c1.priority ?? '') > (c2.priority ?? '') ? -1 : 1; -} +}; -const getStash = (cb: IPrioritizedCallback, paths: any): { - allOutputs: ILayoutCallbackProperty[][], - allPropIds: any[] +const getStash = ( + cb: IPrioritizedCallback, + paths: any +): { + allOutputs: ILayoutCallbackProperty[][]; + allPropIds: any[]; } => { - const { getOutputs } = cb; + const {getOutputs} = cb; const allOutputs = getOutputs(paths); const flatOutputs: any[] = flatten(allOutputs); const allPropIds: any[] = []; const reqOut: any = {}; - flatOutputs.forEach(({ id, property }) => { + flatOutputs.forEach(({id, property}) => { const idStr = stringifyId(id); const idOut = (reqOut[idStr] = reqOut[idStr] || []); idOut.push(property); - allPropIds.push(combineIdAndProp({ id: idStr, property })); + allPropIds.push(combineIdAndProp({id: idStr, property})); }); - return { allOutputs, allPropIds }; -} + return {allOutputs, allPropIds}; +}; -const getIds = (cb: ICallback, paths: any) => uniq(pluck('id', [ - ...flatten(cb.getInputs(paths)), - ...flatten(cb.getState(paths)) -])); +const getIds = (cb: ICallback, paths: any) => + uniq( + pluck('id', [ + ...flatten(cb.getInputs(paths)), + ...flatten(cb.getState(paths)) + ]) + ); const observer: IStoreObserverDefinition = { - observer: async ({ - dispatch, - getState - }) => { - const { callbacks: { executing, watched }, config, hooks, layout, paths } = getState(); - let { callbacks: { prioritized } } = getState(); - - const available = Math.max( - 0, - 12 - executing.length - watched.length - ); + observer: async ({dispatch, getState}) => { + const { + callbacks: {executing, watched}, + config, + hooks, + layout, + paths + } = getState(); + let { + callbacks: {prioritized} + } = getState(); + + const available = Math.max(0, 12 - executing.length - watched.length); // Order prioritized callbacks based on depth and breadth of callback chain prioritized = sort(sortPriority, prioritized); // Divide between sync and async - const [syncCallbacks, asyncCallbacks] = partition(cb => isAppReady( - layout, - paths, - getIds(cb, paths) - ) === true, prioritized); + const [syncCallbacks, asyncCallbacks] = partition( + cb => isAppReady(layout, paths, getIds(cb, paths)) === true, + prioritized + ); const pickedSyncCallbacks = syncCallbacks.slice(0, available); - const pickedAsyncCallbacks = asyncCallbacks.slice(0, available - pickedSyncCallbacks.length); + const pickedAsyncCallbacks = asyncCallbacks.slice( + 0, + available - pickedSyncCallbacks.length + ); if (pickedSyncCallbacks.length) { - dispatch(aggregateCallbacks([ - removePrioritizedCallbacks(pickedSyncCallbacks), - addExecutingCallbacks(map( - cb => executeCallback(cb, config, hooks, paths, layout, getStash(cb, paths)), - pickedSyncCallbacks - )) - ])); + dispatch( + aggregateCallbacks([ + removePrioritizedCallbacks(pickedSyncCallbacks), + addExecutingCallbacks( + map( + cb => + executeCallback( + cb, + config, + hooks, + paths, + layout, + getStash(cb, paths), + dispatch + ), + pickedSyncCallbacks + ) + ) + ]) + ); } if (pickedAsyncCallbacks.length) { @@ -111,29 +123,48 @@ const observer: IStoreObserverDefinition = { pickedAsyncCallbacks ); - dispatch(aggregateCallbacks([ - removePrioritizedCallbacks(pickedAsyncCallbacks), - addBlockedCallbacks(deffered) - ])); + dispatch( + aggregateCallbacks([ + removePrioritizedCallbacks(pickedAsyncCallbacks), + addBlockedCallbacks(deffered) + ]) + ); forEach(async cb => { await cb.isReady; - const { callbacks: { blocked } } = getState(); - - // Check if it's been removed from the `blocked` list since - on callback completion, another callback may be cancelled - // Find the callback instance or one that matches its promise (eg. could have been pruned) - const currentCb = find(_cb => _cb === cb || _cb.isReady === cb.isReady, blocked); + const { + callbacks: {blocked} + } = getState(); + + // Check if it's been removed from the `blocked` list since - on + // callback completion, another callback may be cancelled + // Find the callback instance or one that matches its promise + // (eg. could have been pruned) + const currentCb = find( + _cb => _cb === cb || _cb.isReady === cb.isReady, + blocked + ); if (!currentCb) { return; } - const executingCallback = executeCallback(cb, config, hooks, paths, layout, cb); - - dispatch(aggregateCallbacks([ - removeBlockedCallbacks([cb]), - addExecutingCallbacks([executingCallback]) - ])); + const executingCallback = executeCallback( + cb, + config, + hooks, + paths, + layout, + cb, + dispatch + ); + + dispatch( + aggregateCallbacks([ + removeBlockedCallbacks([cb]), + addExecutingCallbacks([executingCallback]) + ]) + ); }, deffered); } }, diff --git a/dash-renderer/src/observers/requestedCallbacks.ts b/dash-renderer/src/observers/requestedCallbacks.ts index bae79e4d5a..2b1cd1dc0e 100644 --- a/dash-renderer/src/observers/requestedCallbacks.ts +++ b/dash-renderer/src/observers/requestedCallbacks.ts @@ -18,7 +18,7 @@ import { values } from 'ramda'; -import { IStoreState } from '../store'; +import {IStoreState} from '../store'; import { aggregateCallbacks, @@ -34,7 +34,7 @@ import { removeRequestedCallbacks } from '../actions/callbacks'; -import { isMultiValued } from '../actions/dependencies'; +import {isMultiValued} from '../actions/dependencies'; import { combineIdAndProp, @@ -52,18 +52,21 @@ import { import wait from './../utils/wait'; -import { getPendingCallbacks } from '../utils/callbacks'; -import { IStoreObserverDefinition } from '../StoreObserver'; +import {getPendingCallbacks} from '../utils/callbacks'; +import {IStoreObserverDefinition} from '../StoreObserver'; const observer: IStoreObserverDefinition = { - observer: async ({ - dispatch, - getState - }) => { + observer: async ({dispatch, getState}) => { await wait(0); - const { callbacks, callbacks: { prioritized, blocked, executing, watched, stored }, paths } = getState(); - let { callbacks: { requested } } = getState(); + const { + callbacks, + callbacks: {prioritized, blocked, executing, watched, stored}, + paths + } = getState(); + let { + callbacks: {requested} + } = getState(); const initialRequested = requested.slice(0); @@ -110,18 +113,25 @@ const observer: IStoreObserverDefinition = { } else { // otherwise merge all remaining callbacks together rDuplicates = concat(rDuplicates, groupWithoutInitial); - rMergedDuplicates.push(mergeLeft({ - changedPropIds: reduce(mergeWith(Math.max), {}, pluck('changedPropIds', groupWithoutInitial)), - executionGroup: filter(exg => !!exg, pluck('executionGroup', groupWithoutInitial)).slice(-1)[0] - }, groupWithoutInitial.slice(-1)[0]) as ICallback); + rMergedDuplicates.push( + mergeLeft( + { + changedPropIds: reduce( + mergeWith(Math.max), + {}, + pluck('changedPropIds', groupWithoutInitial) + ), + executionGroup: filter( + exg => !!exg, + pluck('executionGroup', groupWithoutInitial) + ).slice(-1)[0] + }, + groupWithoutInitial.slice(-1)[0] + ) as ICallback + ); } } - }, values( - groupBy( - getUniqueIdentifier, - requested - ) - )); + }, values(groupBy(getUniqueIdentifier, requested))); /* TODO? @@ -138,73 +148,94 @@ const observer: IStoreObserverDefinition = { Extract all but the first callback from each IOS-key group these callbacks are `prioritized` and duplicates. */ - const pDuplicates = flatten(map( - group => group.slice(0, -1), - values( - groupBy( - getUniqueIdentifier, - concat(prioritized, requested) + const pDuplicates = flatten( + map( + group => group.slice(0, -1), + values( + groupBy( + getUniqueIdentifier, + concat(prioritized, requested) + ) ) ) - )); - - const bDuplicates = flatten(map( - group => group.slice(0, -1), - values( - groupBy( - getUniqueIdentifier, - concat(blocked, requested) + ); + + const bDuplicates = flatten( + map( + group => group.slice(0, -1), + values( + groupBy( + getUniqueIdentifier, + concat(blocked, requested) + ) ) ) - )) as IBlockedCallback[]; - - const eDuplicates = flatten(map( - group => group.slice(0, -1), - values( - groupBy( - getUniqueIdentifier, - concat(executing, requested) + ) as IBlockedCallback[]; + + const eDuplicates = flatten( + map( + group => group.slice(0, -1), + values( + groupBy( + getUniqueIdentifier, + concat(executing, requested) + ) ) ) - )) as IExecutingCallback[]; - - const wDuplicates = flatten(map( - group => group.slice(0, -1), - values( - groupBy( - getUniqueIdentifier, - concat(watched, requested) + ) as IExecutingCallback[]; + + const wDuplicates = flatten( + map( + group => group.slice(0, -1), + values( + groupBy( + getUniqueIdentifier, + concat(watched, requested) + ) ) ) - )) as IExecutingCallback[]; + ) as IExecutingCallback[]; /* 3. Modify or remove callbacks that are outputting to non-existing layout `id`. */ - const { added: rAdded, removed: rRemoved } = pruneCallbacks(requested, paths); - const { added: pAdded, removed: pRemoved } = pruneCallbacks(prioritized, paths); - const { added: bAdded, removed: bRemoved } = pruneCallbacks(blocked, paths); - const { added: eAdded, removed: eRemoved } = pruneCallbacks(executing, paths); - const { added: wAdded, removed: wRemoved } = pruneCallbacks(watched, paths); + const {added: rAdded, removed: rRemoved} = pruneCallbacks( + requested, + paths + ); + const {added: pAdded, removed: pRemoved} = pruneCallbacks( + prioritized, + paths + ); + const {added: bAdded, removed: bRemoved} = pruneCallbacks( + blocked, + paths + ); + const {added: eAdded, removed: eRemoved} = pruneCallbacks( + executing, + paths + ); + const {added: wAdded, removed: wRemoved} = pruneCallbacks( + watched, + paths + ); /* TODO? Clean up the `requested` list - during the dispatch phase, it will be updated for real */ - requested = concat( - difference( - requested, - rRemoved - ), - rAdded - ); + requested = concat(difference(requested, rRemoved), rAdded); /* 4. Find `requested` callbacks that do not depend on a outstanding output (as either input or state) */ - let readyCallbacks = getReadyCallbacks(paths, requested, pendingCallbacks); + let readyCallbacks = getReadyCallbacks( + paths, + requested, + pendingCallbacks + ); let oldBlocked: ICallback[] = []; let newBlocked: ICallback[] = []; @@ -241,21 +272,32 @@ const observer: IStoreObserverDefinition = { candidates = candidates.slice(1); // Remaining candidates are not blocked by current assumptions - candidates = getReadyCallbacks(paths, candidates, readyCallbacks); + candidates = getReadyCallbacks( + paths, + candidates, + readyCallbacks + ); // Blocked requests need to make sure they have the callback as a predecessor const blockedByAssumptions = difference(candidates, candidates); const modified = filter( - cb => !cb.predecessors || !includes(readyCallback.callback, cb.predecessors), + cb => + !cb.predecessors || + !includes(readyCallback.callback, cb.predecessors), blockedByAssumptions ); oldBlocked = concat(oldBlocked, modified); - newBlocked = concat(newBlocked, modified.map(cb => ({ - ...cb, - predecessors: concat(cb.predecessors ?? [], [readyCallback.callback]) - }))); + newBlocked = concat( + newBlocked, + modified.map(cb => ({ + ...cb, + predecessors: concat(cb.predecessors ?? [], [ + readyCallback.callback + ]) + })) + ); } } @@ -264,13 +306,7 @@ const observer: IStoreObserverDefinition = { Clean up the `requested` list - during the dispatch phase, it will be updated for real */ - requested = concat( - difference( - requested, - oldBlocked - ), - newBlocked - ); + requested = concat(difference(requested, oldBlocked), newBlocked); /* 5. Prune callbacks that became irrelevant in their `executionGroup` @@ -286,7 +322,11 @@ const observer: IStoreObserverDefinition = { const dropped: ICallback[] = filter(cb => { // If there is no `stored` callback for the group, no outputs were dropped -> `cb` is kept - if (!cb.executionGroup || !pendingGroups[cb.executionGroup] || !pendingGroups[cb.executionGroup].length) { + if ( + !cb.executionGroup || + !pendingGroups[cb.executionGroup] || + !pendingGroups[cb.executionGroup].length + ) { return false; } @@ -294,84 +334,76 @@ const observer: IStoreObserverDefinition = { const inputs = map(combineIdAndProp, flatten(cb.getInputs(paths))); // Get all the potentially updated props for the group so far - const allProps = flatten(map( - gcb => gcb.executionMeta.allProps, - pendingGroups[cb.executionGroup] - )); + const allProps = flatten( + map( + gcb => gcb.executionMeta.allProps, + pendingGroups[cb.executionGroup] + ) + ); // Get all the updated props for the group so far - const updated = flatten(map( - gcb => gcb.executionMeta.updatedProps, - pendingGroups[cb.executionGroup] - )); + const updated = flatten( + map( + gcb => gcb.executionMeta.updatedProps, + pendingGroups[cb.executionGroup] + ) + ); // If there's no overlap between the updated props and the inputs, // + there's no props that aren't covered by the potentially updated props, // and not all inputs are multi valued // -> drop `cb` const res = - isEmpty(intersection( - inputs, - updated - )) && - isEmpty(difference( - inputs, - allProps - )) - && !all( - isMultiValued, - cb.callback.inputs - ); + isEmpty(intersection(inputs, updated)) && + isEmpty(difference(inputs, allProps)) && + !all(isMultiValued, cb.callback.inputs); return res; - }, - readyCallbacks - ); + }, readyCallbacks); /* TODO? Clean up the `requested` list - during the dispatch phase, it will be updated for real */ - requested = difference( - requested, - dropped - ); + requested = difference(requested, dropped); - readyCallbacks = difference( - readyCallbacks, - dropped - ); + readyCallbacks = difference(readyCallbacks, dropped); - requested = difference( - requested, - readyCallbacks - ); + requested = difference(requested, readyCallbacks); const added = difference(requested, initialRequested); const removed = difference(initialRequested, requested); - dispatch(aggregateCallbacks([ - // Clean up requested callbacks - added.length ? addRequestedCallbacks(added) : null, - removed.length ? removeRequestedCallbacks(removed) : null, - // Clean up duplicated callbacks - pDuplicates.length ? removePrioritizedCallbacks(pDuplicates) : null, - bDuplicates.length ? removeBlockedCallbacks(bDuplicates) : null, - eDuplicates.length ? removeExecutingCallbacks(eDuplicates) : null, - wDuplicates.length ? removeWatchedCallbacks(wDuplicates) : null, - // Prune callbacks - pRemoved.length ? removePrioritizedCallbacks(pRemoved) : null, - pAdded.length ? addPrioritizedCallbacks(pAdded) : null, - bRemoved.length ? removeBlockedCallbacks(bRemoved) : null, - bAdded.length ? addBlockedCallbacks(bAdded) : null, - eRemoved.length ? removeExecutingCallbacks(eRemoved) : null, - eAdded.length ? addExecutingCallbacks(eAdded) : null, - wRemoved.length ? removeWatchedCallbacks(wRemoved) : null, - wAdded.length ? addWatchedCallbacks(wAdded) : null, - // Promote callbacks - readyCallbacks.length ? addPrioritizedCallbacks(readyCallbacks) : null - ])); + dispatch( + aggregateCallbacks([ + // Clean up requested callbacks + added.length ? addRequestedCallbacks(added) : null, + removed.length ? removeRequestedCallbacks(removed) : null, + // Clean up duplicated callbacks + pDuplicates.length + ? removePrioritizedCallbacks(pDuplicates) + : null, + bDuplicates.length ? removeBlockedCallbacks(bDuplicates) : null, + eDuplicates.length + ? removeExecutingCallbacks(eDuplicates) + : null, + wDuplicates.length ? removeWatchedCallbacks(wDuplicates) : null, + // Prune callbacks + pRemoved.length ? removePrioritizedCallbacks(pRemoved) : null, + pAdded.length ? addPrioritizedCallbacks(pAdded) : null, + bRemoved.length ? removeBlockedCallbacks(bRemoved) : null, + bAdded.length ? addBlockedCallbacks(bAdded) : null, + eRemoved.length ? removeExecutingCallbacks(eRemoved) : null, + eAdded.length ? addExecutingCallbacks(eAdded) : null, + wRemoved.length ? removeWatchedCallbacks(wRemoved) : null, + wAdded.length ? addWatchedCallbacks(wAdded) : null, + // Promote callbacks + readyCallbacks.length + ? addPrioritizedCallbacks(readyCallbacks) + : null + ]) + ); }, inputs: ['callbacks.requested', 'callbacks.completed'] }; diff --git a/dash-renderer/src/observers/storedCallbacks.ts b/dash-renderer/src/observers/storedCallbacks.ts index 83b19518b9..58116a73c8 100644 --- a/dash-renderer/src/observers/storedCallbacks.ts +++ b/dash-renderer/src/observers/storedCallbacks.ts @@ -8,30 +8,23 @@ import { toPairs } from 'ramda'; -import { IStoreState } from '../store'; +import {IStoreState} from '../store'; -import { - aggregateCallbacks, - removeStoredCallbacks -} from '../actions/callbacks'; +import {aggregateCallbacks, removeStoredCallbacks} from '../actions/callbacks'; -import { - ICallback, - IStoredCallback -} from '../types/callbacks'; +import {ICallback, IStoredCallback} from '../types/callbacks'; -import { getPendingCallbacks } from '../utils/callbacks'; -import { IStoreObserverDefinition } from '../StoreObserver'; +import {getPendingCallbacks} from '../utils/callbacks'; +import {IStoreObserverDefinition} from '../StoreObserver'; const observer: IStoreObserverDefinition = { - observer: ({ - dispatch, - getState - }) => { - const { callbacks } = getState(); + observer: ({dispatch, getState}) => { + const {callbacks} = getState(); const pendingCallbacks = getPendingCallbacks(callbacks); - let { callbacks: { stored } } = getState(); + let { + callbacks: {stored} + } = getState(); const [nullGroupCallbacks, groupCallbacks] = partition( cb => isNil(cb.executionGroup), @@ -41,27 +34,30 @@ const observer: IStoreObserverDefinition = { const executionGroups = groupBy( cb => cb.executionGroup as any, groupCallbacks - ) + ); const pendingGroups = groupBy( cb => cb.executionGroup as any, filter(cb => !isNil(cb.executionGroup), pendingCallbacks) ); - let dropped = reduce((res, [ - executionGroup, - executionGroupCallbacks - ]) => !pendingGroups[executionGroup] ? - concat(res, executionGroupCallbacks) : - res, + let dropped = reduce( + (res, [executionGroup, executionGroupCallbacks]) => + !pendingGroups[executionGroup] + ? concat(res, executionGroupCallbacks) + : res, [] as IStoredCallback[], toPairs(executionGroups) ); - dispatch(aggregateCallbacks([ - nullGroupCallbacks.length ? removeStoredCallbacks(nullGroupCallbacks) : null, - dropped.length ? removeStoredCallbacks(dropped) : null - ])); + dispatch( + aggregateCallbacks([ + nullGroupCallbacks.length + ? removeStoredCallbacks(nullGroupCallbacks) + : null, + dropped.length ? removeStoredCallbacks(dropped) : null + ]) + ); }, inputs: ['callbacks.stored', 'callbacks.completed'] }; diff --git a/dash-renderer/src/persistence.js b/dash-renderer/src/persistence.js index 4940d0ff89..41222a6589 100644 --- a/dash-renderer/src/persistence.js +++ b/dash-renderer/src/persistence.js @@ -64,7 +64,7 @@ import { lensPath, mergeRight, set, - type, + type } from 'ramda'; import {createAction} from 'redux-actions'; @@ -78,7 +78,7 @@ function err(e) { return createAction('ON_ERROR')({ type: 'frontEnd', - error, + error }); } @@ -208,14 +208,14 @@ function longString() { } export const stores = { - memory: new MemStore(), + memory: new MemStore() // Defer testing & making local/session stores until requested. // That way if we have errors here they can show up in devtools. }; const backEnds = { local: 'localStorage', - session: 'sessionStorage', + session: 'sessionStorage' }; function tryGetWebStore(backEnd, dispatch) { @@ -262,7 +262,7 @@ function getStore(type, dispatch) { const noopTransform = { extract: propValue => propValue, - apply: (storedValue, _propValue) => storedValue, + apply: (storedValue, _propValue) => storedValue }; const getTransform = (element, propName, propPart) => @@ -294,7 +294,7 @@ const getProps = layout => { element, persistence, persisted_props, - persistence_type, + persistence_type }; }; @@ -306,7 +306,7 @@ export function recordUiEdit(layout, newProps, dispatch) { element, persistence, persisted_props, - persistence_type, + persistence_type } = getProps(layout); if (!canPersist || !persistence) { return; @@ -381,7 +381,7 @@ function persistenceMods(layout, component, path, dispatch) { element, persistence, persisted_props, - persistence_type, + persistence_type } = getProps(component); let layoutOut = layout; @@ -447,7 +447,7 @@ export function prunePersistence(layout, newProps, dispatch) { persistence, persisted_props, persistence_type, - element, + element } = getProps(layout); const getFinal = (propName, prevVal) => diff --git a/dash-renderer/src/reducers/callbacks.ts b/dash-renderer/src/reducers/callbacks.ts index 196c652938..5386960354 100644 --- a/dash-renderer/src/reducers/callbacks.ts +++ b/dash-renderer/src/reducers/callbacks.ts @@ -1,8 +1,4 @@ -import { - concat, - difference, - reduce -} from 'ramda'; +import {concat, difference, reduce} from 'ramda'; import { ICallback, @@ -37,8 +33,8 @@ export enum CallbackAggregateActionType { } export interface IAggregateAction { - type: CallbackAggregateActionType.Aggregate, - payload: (ICallbackAction | ICompletedAction | null)[] + type: CallbackAggregateActionType.Aggregate; + payload: (ICallbackAction | ICompletedAction | null)[]; } export interface ICallbackAction { @@ -47,14 +43,11 @@ export interface ICallbackAction { } export interface ICompletedAction { - type: CallbackAggregateActionType.AddCompleted, - payload: number + type: CallbackAggregateActionType.AddCompleted; + payload: number; } -type CallbackAction = - IAggregateAction | - ICallbackAction | - ICompletedAction; +type CallbackAction = IAggregateAction | ICallbackAction | ICompletedAction; export interface ICallbacksState { requested: ICallback[]; @@ -79,7 +72,7 @@ const DEFAULT_STATE: ICallbacksState = { }; const transforms: { - [key: string]: (a1: ICallback[], a2: ICallback[]) => ICallback[] + [key: string]: (a1: ICallback[], a2: ICallback[]) => ICallback[]; } = { [CallbackActionType.AddBlocked]: concat, [CallbackActionType.AddExecuted]: concat, @@ -98,7 +91,7 @@ const transforms: { }; const fields: { - [key: string]: keyof Omit + [key: string]: keyof Omit; } = { [CallbackActionType.AddBlocked]: 'blocked', [CallbackActionType.AddExecuted]: 'executed', @@ -114,41 +107,41 @@ const fields: { [CallbackActionType.RemoveRequested]: 'requested', [CallbackActionType.RemoveStored]: 'stored', [CallbackActionType.RemoveWatched]: 'watched' -} +}; -const mutateCompleted = ( - state: ICallbacksState, - action: ICompletedAction -) => ({ ...state, completed: state.completed + action.payload }); +const mutateCompleted = (state: ICallbacksState, action: ICompletedAction) => ({ + ...state, + completed: state.completed + action.payload +}); -const mutateCallbacks = ( - state: ICallbacksState, - action: ICallbackAction -) => { +const mutateCallbacks = (state: ICallbacksState, action: ICallbackAction) => { const transform = transforms[action.type]; const field = fields[action.type]; - return (!transform || !field || action.payload.length === 0) ? - state : { - ...state, - [field]: transform(state[field], action.payload) - }; -} - - + return !transform || !field || action.payload.length === 0 + ? state + : { + ...state, + [field]: transform(state[field], action.payload) + }; +}; export default ( state: ICallbacksState = DEFAULT_STATE, action: CallbackAction -) => reduce((s, a) => { - if (a === null) { - return s; - } else if (a.type === CallbackAggregateActionType.AddCompleted) { - return mutateCompleted(s, a); - } else { - return mutateCallbacks(s, a); - } -}, state, action.type === CallbackAggregateActionType.Aggregate ? - action.payload : - [action] -); +) => + reduce( + (s, a) => { + if (a === null) { + return s; + } else if (a.type === CallbackAggregateActionType.AddCompleted) { + return mutateCompleted(s, a); + } else { + return mutateCallbacks(s, a); + } + }, + state, + action.type === CallbackAggregateActionType.Aggregate + ? action.payload + : [action] + ); diff --git a/dash-renderer/src/reducers/changed.js b/dash-renderer/src/reducers/changed.js new file mode 100644 index 0000000000..80fb1db107 --- /dev/null +++ b/dash-renderer/src/reducers/changed.js @@ -0,0 +1,13 @@ +const initialChange = { + id: null, + props: {} +}; + +function changed(state = initialChange) { + // This is empty just to initialize the store. Changes + // are actually recorded in reducer.js so that we can + // resolve paths to id. + return state; +} + +export default changed; diff --git a/dash-renderer/src/reducers/constants.js b/dash-renderer/src/reducers/constants.js index da4dfb20b6..e8943eac5c 100644 --- a/dash-renderer/src/reducers/constants.js +++ b/dash-renderer/src/reducers/constants.js @@ -1,7 +1,7 @@ export function getAppState(state) { const stateList = { STARTED: 'STARTED', - HYDRATED: 'HYDRATED', + HYDRATED: 'HYDRATED' }; if (stateList[state]) { return stateList[state]; diff --git a/dash-renderer/src/reducers/error.js b/dash-renderer/src/reducers/error.js index 4188703054..a5dbfc12a6 100644 --- a/dash-renderer/src/reducers/error.js +++ b/dash-renderer/src/reducers/error.js @@ -3,7 +3,7 @@ import {mergeRight} from 'ramda'; const initialError = { frontEnd: [], backEnd: [], - backEndConnected: true, + backEndConnected: true }; export default function error(state = initialError, action) { @@ -19,19 +19,19 @@ export default function error(state = initialError, action) { return { frontEnd: [ mergeRight(action.payload, {timestamp: new Date()}), - ...frontEnd, + ...frontEnd ], backEnd, - backEndConnected, + backEndConnected }; } else if (action.payload.type === 'backEnd') { return { frontEnd, backEnd: [ mergeRight(action.payload, {timestamp: new Date()}), - ...backEnd, + ...backEnd ], - backEndConnected, + backEndConnected }; } return state; diff --git a/dash-renderer/src/reducers/history.js b/dash-renderer/src/reducers/history.js index 091cfb3221..799ede6705 100644 --- a/dash-renderer/src/reducers/history.js +++ b/dash-renderer/src/reducers/history.js @@ -1,7 +1,7 @@ const initialHistory = { past: [], present: {}, - future: [], + future: [] }; function history(state = initialHistory, action) { @@ -13,7 +13,7 @@ function history(state = initialHistory, action) { return { past: newPast, present: previous, - future: [present, ...future], + future: [present, ...future] }; } @@ -24,7 +24,7 @@ function history(state = initialHistory, action) { return { past: [...past, present], present: next, - future: newFuture, + future: newFuture }; } @@ -35,7 +35,7 @@ function history(state = initialHistory, action) { return { past: newPast, present: previous, - future: [...future], + future: [...future] }; } diff --git a/dash-renderer/src/reducers/isLoading.ts b/dash-renderer/src/reducers/isLoading.ts index 0a25260551..8f0c84124e 100644 --- a/dash-renderer/src/reducers/isLoading.ts +++ b/dash-renderer/src/reducers/isLoading.ts @@ -8,15 +8,11 @@ export interface ILoadingMapAction { } type IsLoadingState = boolean; -export { - IsLoadingState -}; +export {IsLoadingState}; const DEFAULT_STATE: IsLoadingState = true; export default ( state: IsLoadingState = DEFAULT_STATE, action: ILoadingMapAction -) => action.type === IsLoadingActionType.Set ? - action.payload : - state; +) => (action.type === IsLoadingActionType.Set ? action.payload : state); diff --git a/dash-renderer/src/reducers/layout.js b/dash-renderer/src/reducers/layout.js index d9a6b6407f..6c082f313c 100644 --- a/dash-renderer/src/reducers/layout.js +++ b/dash-renderer/src/reducers/layout.js @@ -9,7 +9,7 @@ const layout = (state = {}, action) => { includes(action.type, [ 'UNDO_PROP_CHANGE', 'REDO_PROP_CHANGE', - getAction('ON_PROP_CHANGE'), + getAction('ON_PROP_CHANGE') ]) ) { const propPath = append('props', action.payload.itempath); diff --git a/dash-renderer/src/reducers/loadingMap.ts b/dash-renderer/src/reducers/loadingMap.ts index 1fb31a208d..14981e9268 100644 --- a/dash-renderer/src/reducers/loadingMap.ts +++ b/dash-renderer/src/reducers/loadingMap.ts @@ -8,15 +8,11 @@ export interface ILoadingMapAction { } type LoadingMapState = any; -export { - LoadingMapState -}; +export {LoadingMapState}; const DEFAULT_STATE: LoadingMapState = {}; export default ( state: LoadingMapState = DEFAULT_STATE, action: ILoadingMapAction -) => action.type === LoadingMapActionType.Set ? - action.payload : - state; +) => (action.type === LoadingMapActionType.Set ? action.payload : state); diff --git a/dash-renderer/src/reducers/profile.js b/dash-renderer/src/reducers/profile.js new file mode 100644 index 0000000000..b71a61106d --- /dev/null +++ b/dash-renderer/src/reducers/profile.js @@ -0,0 +1,86 @@ +import {clone} from 'ramda'; + +import {STATUSMAP} from '../constants/constants'; + +const defaultProfile = { + count: 0, + total: 0, + compute: 0, + network: { + time: 0, + upload: 0, + download: 0 + }, + resources: {}, + status: { + latest: null + }, + result: {} +}; + +const defaultState = { + updated: [], + resources: {}, + callbacks: {} +}; + +const profile = (state = defaultState, action) => { + if (action.type === 'UPDATE_RESOURCE_USAGE') { + // Keep a record of the most recent change. This + // is subtly different from history.present becasue + // it watches all props, not just inputs. + const {id, usage, status} = action.payload; + const statusMapped = STATUSMAP[status] || status; + + // Keep track of the callback that actually changed. + const newState = { + updated: [id], + resources: state.resources, + callbacks: state.callbacks + }; + + newState.callbacks[id] = + newState.callbacks[id] || clone(defaultProfile); + + const cb = newState.callbacks[id]; + const cbResources = cb.resources; + const totalResources = newState.resources; + + // Update resource usage & params. + cb.count += 1; + cb.status.latest = statusMapped; + cb.status[statusMapped] = (cb.status[statusMapped] || 0) + 1; + cb.result = action.payload.result; + cb.inputs = action.payload.inputs; + cb.state = action.payload.state; + + if (usage) { + const { + __dash_client, + __dash_server, + __dash_upload, + __dash_download, + ...user + } = usage; + + cb.total += __dash_client; + cb.compute += __dash_server; + cb.network.time += __dash_client - __dash_server; + cb.network.upload += __dash_upload; + cb.network.download += __dash_download; + + for (const r in user) { + if (user.hasOwnProperty(r)) { + cbResources[r] = (cbResources[r] || 0) + user[r]; + totalResources[r] = (totalResources[r] || 0) + user[r]; + } + } + } + + return newState; + } + + return state; +}; + +export default profile; diff --git a/dash-renderer/src/reducers/reducer.js b/dash-renderer/src/reducers/reducer.js index d238b75225..c722119270 100644 --- a/dash-renderer/src/reducers/reducer.js +++ b/dash-renderer/src/reducers/reducer.js @@ -11,6 +11,8 @@ import graphs from './dependencyGraph'; import error from './error'; import history from './history'; import hooks from './hooks'; +import profile from './profile'; +import changed from './changed'; import isLoading from './isLoading'; import layout from './layout'; import loadingMap from './loadingMap'; @@ -20,7 +22,7 @@ export const apiRequests = [ 'dependenciesRequest', 'layoutRequest', 'reloadRequest', - 'loginRequest', + 'loginRequest' ]; function mainReducer() { @@ -32,10 +34,12 @@ function mainReducer() { graphs, history, hooks, + profile, + changed, isLoading, layout, loadingMap, - paths, + paths }; forEach(r => { parts[r] = createApiReducer(r); @@ -44,16 +48,22 @@ function mainReducer() { return combineReducers(parts); } -function getInputHistoryState(itempath, props, state) { - const {graphs, layout, paths} = state; - const idProps = path(itempath.concat(['props']), layout); - const {id} = idProps || {}; +function getInputHistoryState(payload, state, recordChanges) { + const {graphs, paths, layout} = state; + const {itempath, props} = payload; + const refProps = path(itempath.concat(['props']), layout) || {}; + const {id} = refProps; + let historyEntry; if (id) { + if (recordChanges) { + state.changed = {id, props}; + } + historyEntry = {id, props: {}}; keys(props).forEach(propKey => { if (getCallbacksByInput(graphs, paths, id, propKey).length) { - historyEntry.props[propKey] = idProps[propKey]; + historyEntry.props[propKey] = refProps[propKey]; } }); } @@ -63,9 +73,10 @@ function getInputHistoryState(itempath, props, state) { function recordHistory(reducer) { return function(state, action) { // Record initial state - if (action.type === 'ON_PROP_CHANGE') { - const {itempath, props} = action.payload; - const historyEntry = getInputHistoryState(itempath, props, state); + const {type, payload} = action; + if (type === 'ON_PROP_CHANGE') { + // history records all prop changes that are inputs. + const historyEntry = getInputHistoryState(payload, state, true); if (historyEntry && !isEmpty(historyEntry.props)) { state.history.present = historyEntry; } @@ -73,25 +84,17 @@ function recordHistory(reducer) { const nextState = reducer(state, action); - if ( - action.type === 'ON_PROP_CHANGE' && - action.payload.source !== 'response' - ) { - const {itempath, props} = action.payload; + if (type === 'ON_PROP_CHANGE' && payload.source !== 'response') { /* * if the prop change is an input, then * record it so that it can be played back */ - const historyEntry = getInputHistoryState( - itempath, - props, - nextState - ); + const historyEntry = getInputHistoryState(payload, nextState); if (historyEntry && !isEmpty(historyEntry.props)) { nextState.history = { past: [...nextState.history.past, state.history.present], present: historyEntry, - future: [], + future: [] }; } } diff --git a/dash-renderer/src/registry.js b/dash-renderer/src/registry.js index 35d60e4924..00bd498cb9 100644 --- a/dash-renderer/src/registry.js +++ b/dash-renderer/src/registry.js @@ -13,5 +13,5 @@ export default { } throw new Error(`${namespace} was not found.`); - }, + } }; diff --git a/dash-renderer/src/store.ts b/dash-renderer/src/store.ts index 1989941835..3639b92fa2 100644 --- a/dash-renderer/src/store.ts +++ b/dash-renderer/src/store.ts @@ -1,16 +1,16 @@ -import { once } from 'ramda'; -import { createStore, applyMiddleware, Store, Observer } from 'redux'; +import {once} from 'ramda'; +import {createStore, applyMiddleware, Store, Observer} from 'redux'; import thunk from 'redux-thunk'; import {createReducer} from './reducers/reducer'; import StoreObserver from './StoreObserver'; -import { ICallbacksState } from './reducers/callbacks'; -import { LoadingMapState } from './reducers/loadingMap'; -import { IsLoadingState } from './reducers/isLoading'; +import {ICallbacksState} from './reducers/callbacks'; +import {LoadingMapState} from './reducers/loadingMap'; +import {IsLoadingState} from './reducers/isLoading'; import documentTitle from './observers/documentTitle'; import executedCallbacks from './observers/executedCallbacks'; import executingCallbacks from './observers/executingCallbacks'; -import isLoading from './observers/isLoading' +import isLoading from './observers/isLoading'; import loadingMap from './observers/loadingMap'; import prioritizedCallbacks from './observers/prioritizedCallbacks'; import requestedCallbacks from './observers/requestedCallbacks'; diff --git a/dash-renderer/src/styles/styles.js b/dash-renderer/src/styles/styles.js index 868e2ec662..9b84068224 100644 --- a/dash-renderer/src/styles/styles.js +++ b/dash-renderer/src/styles/styles.js @@ -16,7 +16,7 @@ export const base = { html: { fontFamily: "'Open Sans', Helvetica, sans-serif", fontWeight: 400, - color: '#2A3F5F', + color: '#2A3F5F' }, h2: { @@ -24,13 +24,13 @@ export const base = { fontWeight: '600', fontSize: '28px', marginTop: '14px', - marginBottom: '14px', + marginBottom: '14px' }, h4: { fontSize: '18px', marginTop: '9px', - marginBottom: '18px', + marginBottom: '18px' }, button: { @@ -46,27 +46,27 @@ export const base = { lineHeight: '1', cursor: 'pointer', outline: 'none', - margin: '0px', + margin: '0px' }, a: { color: '#119DFF', textDecoration: 'none', - cursor: 'pointer', + cursor: 'pointer' }, caption: { fontSize: '13px', marginTop: '20px', - color: '#A2B1C6', + color: '#A2B1C6' }, container: { marginLeft: 'auto', marginRight: 'auto', width: '90%', - maxWidth: '300px', - }, + maxWidth: '300px' + } }; export default base; diff --git a/dash-renderer/src/types/callbacks.ts b/dash-renderer/src/types/callbacks.ts index 14882607d5..e9790ff704 100644 --- a/dash-renderer/src/types/callbacks.ts +++ b/dash-renderer/src/types/callbacks.ts @@ -1,4 +1,4 @@ -type CallbackId = string | { [key: string]: any } +type CallbackId = string | {[key: string]: any}; export interface ICallbackDefinition { clientside_function?: { @@ -40,9 +40,7 @@ export interface ICallback extends ICallbackTemplate { } // tslint:disable-next-line:no-empty-interface -export interface IPrioritizedCallback extends ICallback { - -} +export interface IPrioritizedCallback extends ICallback {} export interface IBlockedCallback extends IPrioritizedCallback { allOutputs: ILayoutCallbackProperty[][]; @@ -55,9 +53,7 @@ export interface IExecutingCallback extends IPrioritizedCallback { } // tslint:disable-next-line:no-empty-interface -export interface IWatchedCallback extends IExecutingCallback { - -} +export interface IWatchedCallback extends IExecutingCallback {} export interface IExecutedCallback extends IWatchedCallback { executionResult: CallbackResult | null; @@ -67,7 +63,7 @@ export interface IStoredCallback extends IExecutedCallback { executionMeta: { allProps: string[]; updatedProps: string[]; - } + }; } export interface ICallbackPayload { @@ -82,4 +78,4 @@ export type CallbackResult = { data?: any; error?: Error; payload: ICallbackPayload | null; -} +}; diff --git a/dash-renderer/src/utils/TreeContainer.ts b/dash-renderer/src/utils/TreeContainer.ts index cf9df689c1..8e28555160 100644 --- a/dash-renderer/src/utils/TreeContainer.ts +++ b/dash-renderer/src/utils/TreeContainer.ts @@ -1,7 +1,7 @@ -import { path, type, has } from 'ramda'; +import {path, type, has} from 'ramda'; import Registry from '../registry'; -import { stringifyId } from '../actions/dependencies'; +import {stringifyId} from '../actions/dependencies'; function isLoadingComponent(layout: any) { validateComponent(layout); @@ -10,7 +10,11 @@ function isLoadingComponent(layout: any) { const NULL_LOADING_STATE = false; -export function getLoadingState(componentLayout: any, componentPath: any, loadingMap: any) { +export function getLoadingState( + componentLayout: any, + componentPath: any, + loadingMap: any +) { if (!loadingMap) { return NULL_LOADING_STATE; } @@ -43,21 +47,21 @@ export function getLoadingState(componentLayout: any, componentPath: any, loadin return NULL_LOADING_STATE; } -export const getLoadingHash = ( - componentPath: any, - loadingMap: any -) => ( - ((loadingMap && (path(componentPath, loadingMap) as any)?.__dashprivate__idprops__) ?? []) as any[] -).map(({ id, property }) => `${id}.${property}`).join(','); +export const getLoadingHash = (componentPath: any, loadingMap: any) => + (((loadingMap && + (path(componentPath, loadingMap) as any)?.__dashprivate__idprops__) ?? + []) as any[]) + .map(({id, property}) => `${id}.${property}`) + .join(','); export function validateComponent(componentDefinition: any) { if (type(componentDefinition) === 'Array') { throw new Error( 'The children property of a component is a list of lists, instead ' + - 'of just a list. ' + - 'Check the component that has the following contents, ' + - 'and remove one of the levels of nesting: \n' + - JSON.stringify(componentDefinition, null, 2) + 'of just a list. ' + + 'Check the component that has the following contents, ' + + 'and remove one of the levels of nesting: \n' + + JSON.stringify(componentDefinition, null, 2) ); } if ( @@ -70,9 +74,9 @@ export function validateComponent(componentDefinition: any) { ) { throw new Error( 'An object was provided as `children` instead of a component, ' + - 'string, or number (or list of those). ' + - 'Check the children property that looks something like:\n' + - JSON.stringify(componentDefinition, null, 2) + 'string, or number (or list of those). ' + + 'Check the children property that looks something like:\n' + + JSON.stringify(componentDefinition, null, 2) ); } } diff --git a/dash-renderer/src/utils/callbacks.ts b/dash-renderer/src/utils/callbacks.ts index 14befb6e7c..30d592ff8c 100644 --- a/dash-renderer/src/utils/callbacks.ts +++ b/dash-renderer/src/utils/callbacks.ts @@ -1,8 +1,7 @@ -import { omit, values } from 'ramda'; +import {omit, values} from 'ramda'; -import { ICallbacksState } from '../reducers/callbacks'; -import { ICallback } from '../types/callbacks'; +import {ICallbacksState} from '../reducers/callbacks'; +import {ICallback} from '../types/callbacks'; -export const getPendingCallbacks = (state: ICallbacksState) => Array().concat( - ...values(omit(['stored', 'completed'], state)) -); +export const getPendingCallbacks = (state: ICallbacksState) => + Array().concat(...values(omit(['stored', 'completed'], state))); diff --git a/dash-renderer/src/utils/wait.ts b/dash-renderer/src/utils/wait.ts index 10a2a9ed79..3059cb2a7a 100644 --- a/dash-renderer/src/utils/wait.ts +++ b/dash-renderer/src/utils/wait.ts @@ -1,8 +1,8 @@ export default async (duration: number) => { let _resolve: any; - const p = new Promise(resolve => _resolve = resolve); + const p = new Promise(resolve => (_resolve = resolve)); setTimeout(_resolve, duration); return p; -} +}; diff --git a/dash-renderer/tslint.json b/dash-renderer/tslint.json index 600dce2dfc..31269d7c5d 100644 --- a/dash-renderer/tslint.json +++ b/dash-renderer/tslint.json @@ -1,7 +1,8 @@ { "defaultSeverity": "error", "extends": [ - "tslint:recommended" + "tslint:recommended", + "tslint-config-prettier" ], "linterOptions": { "exclude": [ @@ -44,12 +45,7 @@ "ordered-imports": false, "prefer-const": false, "prefer-for-of": false, - "quotemark": [true, "single"], "space-before-function-paren": [false, "always"], - "trailing-comma": [true, { - "singleline": "never", - "multiline": "never" - }], "unified-signatures": false, "variable-name": false }, diff --git a/dash/_callback_context.py b/dash/_callback_context.py index d4e95dfb0e..71997f11dd 100644 --- a/dash/_callback_context.py +++ b/dash/_callback_context.py @@ -72,5 +72,29 @@ def states_list(self): def response(self): return getattr(flask.g, "dash_response") + @staticmethod + @has_context + def record_timing(name, duration=None, description=None): + """Records timing information for a server resource. + + :param name: The name of the resource. + :type name: string + + :param duration: The time in seconds to report. Internally, this + is rounded to the nearest millisecond. + :type duration: float or None + + :param description: A description of the resource. + :type description: string or None + """ + timing_information = getattr(flask.g, "timing_information", {}) + + if name in timing_information: + raise KeyError('Duplicate resource name "{}" found.'.format(name)) + + timing_information[name] = {"dur": round(duration * 1000), "desc": description} + + setattr(flask.g, "timing_information", timing_information) + callback_context = CallbackContext() diff --git a/dash/dash.py b/dash/dash.py index 4720c83f4f..cb3f7aae01 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -9,6 +9,7 @@ import threading import re import logging +import time import mimetypes import hashlib import base64 @@ -1440,6 +1441,34 @@ def _wrap_errors(_): break return get_current_traceback(skip=skip).render_full(), 500 + if debug and dev_tools.ui: + + def _before_request(): + flask.g.timing_information = { + "__dash_server": {"dur": time.time(), "desc": None} + } + + def _after_request(response): + dash_total = flask.g.timing_information["__dash_server"] + dash_total["dur"] = round((time.time() - dash_total["dur"]) * 1000) + + for name, info in flask.g.timing_information.items(): + + value = name + if info.get("desc") is not None: + value += ';desc="{}"'.format(info["desc"]) + + if info.get("dur") is not None: + value += ";dur={}".format(info["dur"]) + + response.headers.add("Server-Timing", value) + + return response + + self.server.before_request(_before_request) + + self.server.after_request(_after_request) + if ( debug and dev_tools.serve_dev_bundles diff --git a/dash/testing/browser.py b/dash/testing/browser.py index 91a09e499e..cbaac4e8fa 100644 --- a/dash/testing/browser.py +++ b/dash/testing/browser.py @@ -103,6 +103,7 @@ def visit_and_snapshot( resource_path, hook_id, wait_for_callbacks=True, + convert_canvases=False, assert_check=True, stay_on_page=False, ): @@ -114,7 +115,11 @@ def visit_and_snapshot( # wait for the hook_id to present and all callbacks get fired self.wait_for_element_by_id(hook_id) - self.percy_snapshot(path, wait_for_callbacks=wait_for_callbacks) + self.percy_snapshot( + path, + wait_for_callbacks=wait_for_callbacks, + convert_canvases=convert_canvases, + ) if assert_check: assert not self.driver.find_elements_by_css_selector( "div.dash-debug-alert" @@ -125,7 +130,7 @@ def visit_and_snapshot( logger.exception("snapshot at resource %s error", path) raise e - def percy_snapshot(self, name="", wait_for_callbacks=False): + def percy_snapshot(self, name="", wait_for_callbacks=False, convert_canvases=False): """percy_snapshot - visual test api shortcut to `percy_runner.snapshot`. It also combines the snapshot `name` with the Python version. """ @@ -148,7 +153,43 @@ def percy_snapshot(self, name="", wait_for_callbacks=False): self.redux_state_rqs, ) - self.percy_runner.snapshot(name=snapshot_name) + if convert_canvases: + self.driver.execute_script( + """ + const stash = window._canvasStash = []; + Array.from(document.querySelectorAll('canvas')).forEach(c => { + const i = document.createElement('img'); + i.src = c.toDataURL(); + i.width = c.width; + i.height = c.height; + i.setAttribute('style', c.getAttribute('style')); + i.className = c.className; + i.setAttribute('data-canvasnum', stash.length); + stash.push(c); + c.parentElement.insertBefore(i, c); + c.parentElement.removeChild(c); + }); + """ + ) + + self.percy_runner.snapshot(name=snapshot_name) + + self.driver.execute_script( + """ + const stash = window._canvasStash; + Array.from( + document.querySelectorAll('img[data-canvasnum]') + ).forEach(i => { + const c = stash[+i.getAttribute('data-canvasnum')]; + i.parentElement.insertBefore(c, i); + i.parentElement.removeChild(i); + }); + delete window._canvasStash; + """ + ) + + else: + self.percy_runner.snapshot(name=snapshot_name) def take_snapshot(self, name): """Hook method to take snapshot when a selenium test fails. The diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/assets/__init__.py b/tests/assets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/assets/todo_app.py b/tests/assets/todo_app.py new file mode 100644 index 0000000000..ed4971be18 --- /dev/null +++ b/tests/assets/todo_app.py @@ -0,0 +1,123 @@ +from multiprocessing import Value + +import dash +from dash.dependencies import Input, Output, State, MATCH, ALL, ALLSMALLER +import dash_html_components as html +import dash_core_components as dcc + + +def todo_app(content_callback=False): + app = dash.Dash(__name__) + + content = html.Div( + [ + html.Div("Dash To-Do list"), + dcc.Input(id="new-item"), + html.Button("Add", id="add"), + html.Button("Clear Done", id="clear-done"), + html.Div(id="list-container"), + html.Hr(), + html.Div(id="totals"), + ] + ) + + if content_callback: + app.layout = html.Div([html.Div(id="content"), dcc.Location(id="url")]) + + @app.callback(Output("content", "children"), [Input("url", "pathname")]) + def display_content(_): + return content + + else: + app.layout = content + + style_todo = {"display": "inline", "margin": "10px"} + style_done = {"textDecoration": "line-through", "color": "#888"} + style_done.update(style_todo) + + app.list_calls = Value("i", 0) + app.style_calls = Value("i", 0) + app.preceding_calls = Value("i", 0) + app.total_calls = Value("i", 0) + + @app.callback( + Output("list-container", "children"), + Output("new-item", "value"), + Input("add", "n_clicks"), + Input("new-item", "n_submit"), + Input("clear-done", "n_clicks"), + State("new-item", "value"), + State({"item": ALL}, "children"), + State({"item": ALL, "action": "done"}, "value"), + ) + def edit_list(add, add2, clear, new_item, items, items_done): + app.list_calls.value += 1 + triggered = [t["prop_id"] for t in dash.callback_context.triggered] + adding = len( + [1 for i in triggered if i in ("add.n_clicks", "new-item.n_submit")] + ) + clearing = len([1 for i in triggered if i == "clear-done.n_clicks"]) + new_spec = [ + (text, done) + for text, done in zip(items, items_done) + if not (clearing and done) + ] + if adding: + new_spec.append((new_item, [])) + new_list = [ + html.Div( + [ + dcc.Checklist( + id={"item": i, "action": "done"}, + options=[{"label": "", "value": "done"}], + value=done, + style={"display": "inline"}, + ), + html.Div( + text, id={"item": i}, style=style_done if done else style_todo + ), + html.Div(id={"item": i, "preceding": True}, style=style_todo), + ], + style={"clear": "both"}, + ) + for i, (text, done) in enumerate(new_spec) + ] + return [new_list, "" if adding else new_item] + + @app.callback( + Output({"item": MATCH}, "style"), + Input({"item": MATCH, "action": "done"}, "value"), + ) + def mark_done(done): + app.style_calls.value += 1 + return style_done if done else style_todo + + @app.callback( + Output({"item": MATCH, "preceding": True}, "children"), + Input({"item": ALLSMALLER, "action": "done"}, "value"), + Input({"item": MATCH, "action": "done"}, "value"), + ) + def show_preceding(done_before, this_done): + app.preceding_calls.value += 1 + if this_done: + return "" + all_before = len(done_before) + done_before = len([1 for d in done_before if d]) + out = "{} of {} preceding items are done".format(done_before, all_before) + if all_before == done_before: + out += " DO THIS NEXT!" + return out + + @app.callback( + Output("totals", "children"), Input({"item": ALL, "action": "done"}, "value") + ) + def show_totals(done): + app.total_calls.value += 1 + count_all = len(done) + count_done = len([d for d in done if d]) + result = "{} of {} items completed".format(count_done, count_all) + if count_all: + result += " - {}%".format(int(100 * count_done / count_all)) + return result + + return app diff --git a/tests/integration/callbacks/__init__.py b/tests/integration/callbacks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/callbacks/test_wildcards.py b/tests/integration/callbacks/test_wildcards.py index f8916340d3..b578b0c84e 100644 --- a/tests/integration/callbacks/test_wildcards.py +++ b/tests/integration/callbacks/test_wildcards.py @@ -1,4 +1,3 @@ -from multiprocessing import Value import pytest import re from selenium.webdriver.common.keys import Keys @@ -9,6 +8,8 @@ from dash.testing import wait from dash.dependencies import Input, Output, State, ALL, ALLSMALLER, MATCH +from ...assets.todo_app import todo_app + def css_escape(s): sel = re.sub("[\\{\\}\\\"\\'.:,]", lambda m: "\\" + m.group(0), s) @@ -16,123 +17,6 @@ def css_escape(s): return sel -def todo_app(content_callback): - app = dash.Dash(__name__) - - content = html.Div( - [ - html.Div("Dash To-Do list"), - dcc.Input(id="new-item"), - html.Button("Add", id="add"), - html.Button("Clear Done", id="clear-done"), - html.Div(id="list-container"), - html.Hr(), - html.Div(id="totals"), - ] - ) - - if content_callback: - app.layout = html.Div([html.Div(id="content"), dcc.Location(id="url")]) - - @app.callback(Output("content", "children"), [Input("url", "pathname")]) - def display_content(_): - return content - - else: - app.layout = content - - style_todo = {"display": "inline", "margin": "10px"} - style_done = {"textDecoration": "line-through", "color": "#888"} - style_done.update(style_todo) - - app.list_calls = Value("i", 0) - app.style_calls = Value("i", 0) - app.preceding_calls = Value("i", 0) - app.total_calls = Value("i", 0) - - @app.callback( - Output("list-container", "children"), - Output("new-item", "value"), - Input("add", "n_clicks"), - Input("new-item", "n_submit"), - Input("clear-done", "n_clicks"), - State("new-item", "value"), - State({"item": ALL}, "children"), - State({"item": ALL, "action": "done"}, "value"), - ) - def edit_list(add, add2, clear, new_item, items, items_done): - app.list_calls.value += 1 - triggered = [t["prop_id"] for t in dash.callback_context.triggered] - adding = len( - [1 for i in triggered if i in ("add.n_clicks", "new-item.n_submit")] - ) - clearing = len([1 for i in triggered if i == "clear-done.n_clicks"]) - new_spec = [ - (text, done) - for text, done in zip(items, items_done) - if not (clearing and done) - ] - if adding: - new_spec.append((new_item, [])) - new_list = [ - html.Div( - [ - dcc.Checklist( - id={"item": i, "action": "done"}, - options=[{"label": "", "value": "done"}], - value=done, - style={"display": "inline"}, - ), - html.Div( - text, id={"item": i}, style=style_done if done else style_todo - ), - html.Div(id={"item": i, "preceding": True}, style=style_todo), - ], - style={"clear": "both"}, - ) - for i, (text, done) in enumerate(new_spec) - ] - return [new_list, "" if adding else new_item] - - @app.callback( - Output({"item": MATCH}, "style"), - Input({"item": MATCH, "action": "done"}, "value"), - ) - def mark_done(done): - app.style_calls.value += 1 - return style_done if done else style_todo - - @app.callback( - Output({"item": MATCH, "preceding": True}, "children"), - Input({"item": ALLSMALLER, "action": "done"}, "value"), - Input({"item": MATCH, "action": "done"}, "value"), - ) - def show_preceding(done_before, this_done): - app.preceding_calls.value += 1 - if this_done: - return "" - all_before = len(done_before) - done_before = len([1 for d in done_before if d]) - out = "{} of {} preceding items are done".format(done_before, all_before) - if all_before == done_before: - out += " DO THIS NEXT!" - return out - - @app.callback( - Output("totals", "children"), Input({"item": ALL, "action": "done"}, "value") - ) - def show_totals(done): - app.total_calls.value += 1 - count_all = len(done) - count_done = len([d for d in done if d]) - result = "{} of {} items completed".format(count_done, count_all) - if count_all: - result += " - {}%".format(int(100 * count_done / count_all)) - return result - - return app - - @pytest.mark.parametrize("content_callback", (False, True)) def test_cbwc001_todo_app(content_callback, dash_duo): app = todo_app(content_callback) diff --git a/tests/integration/devtools/__init__.py b/tests/integration/devtools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/devtools/test_callback_timing.py b/tests/integration/devtools/test_callback_timing.py new file mode 100644 index 0000000000..a84b60942f --- /dev/null +++ b/tests/integration/devtools/test_callback_timing.py @@ -0,0 +1,38 @@ +from time import sleep +import requests + +import dash_html_components as html +import dash +from dash.dependencies import Output, Input + + +def test_dvct001_callback_timing(dash_thread_server): + app = dash.Dash(__name__) + app.layout = html.Div() + + @app.callback(Output("x", "p"), Input("y", "q")) + def x(y): + dash.callback_context.record_timing("pancakes", 1.23) + sleep(0.5) + return y + + dash_thread_server(app, debug=True, use_reloader=False, use_debugger=True) + + response = requests.post( + dash_thread_server.url + "/_dash-update-component", + json={ + "output": "x.p", + "outputs": {"id": "x", "property": "p"}, + "inputs": [{"id": "y", "property": "q", "value": 9}], + "changedPropIds": ["y.q"], + }, + ) + + # eg 'Server-Timing': '__dash_server;dur=505, pancakes;dur=1230' + assert "Server-Timing" in response.headers + st = response.headers["Server-Timing"] + times = {k: int(float(v)) for k, v in [p.split(";dur=") for p in st.split(", ")]} + assert "__dash_server" in times + assert times["__dash_server"] >= 500 # 0.5 sec wait + assert "pancakes" in times + assert times["pancakes"] == 1230 diff --git a/tests/integration/devtools/test_devtools_ui.py b/tests/integration/devtools/test_devtools_ui.py index 878e6377b7..d32facb033 100644 --- a/tests/integration/devtools/test_devtools_ui.py +++ b/tests/integration/devtools/test_devtools_ui.py @@ -5,6 +5,8 @@ import dash import dash.testing.wait as wait +from ...assets.todo_app import todo_app + def test_dvui001_disable_props_check_config(dash_duo): app = dash.Dash(__name__) @@ -62,3 +64,38 @@ def test_dvui002_disable_ui_config(dash_duo): ".dash-debug-menu" ), "the debug menu icon should NOT show up" dash_duo.percy_snapshot("devtools - disable dev tools UI - no debug menu") + + +def test_dvui003_callback_graph(dash_duo): + app = todo_app() + + dash_duo.start_server( + app, + debug=True, + use_reloader=False, + use_debugger=True, + dev_tools_hot_reload=False, + ) + + dash_duo.wait_for_text_to_equal("#totals", "0 of 0 items completed") + + # reset compute and network times for all profiled callbacks, so we get + # a consistent callback graph image + dash_duo.driver.execute_script( + """ + const cbProfiles = window.store.getState().profile.callbacks; + Object.keys(cbProfiles).forEach(k => { + cbProfiles[k].compute = 44; + cbProfiles[k].network.time = 33; + cbProfiles[k].total = 77; + }); + """ + ) + + dash_duo.find_element(".dash-debug-menu").click() + sleep(1) # wait for debug menu opening animation + dash_duo.find_element(".dash-debug-menu__button--callbacks").click() + sleep(3) # wait for callback graph to draw + dash_duo.find_element('canvas[data-id="layer2-node"]') + + dash_duo.percy_snapshot("devtools - callback graph", convert_canvases=True) From 0b13c73dd4bf1fb90709facb62294edc778bdf78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 3 Sep 2020 11:51:28 -0400 Subject: [PATCH 7/8] dash 1.16.0, dash-renderer 1.8.0 --- CHANGELOG.md | 2 +- dash-renderer/package-lock.json | 2 +- dash-renderer/package.json | 2 +- dash/version.py | 2 +- requires-install.txt | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc03c12eec..c5a41709c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## [UNRELEASED] +## [1.16.0] - 2020-09-03 ### Added - [#1371](https://github.com/plotly/dash/pull/1371) You can now get [CSP `script-src` hashes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) of all added inline scripts by calling `app.csp_hashes()` (both Dash internal inline scripts, and those added with `app.clientside_callback`) . diff --git a/dash-renderer/package-lock.json b/dash-renderer/package-lock.json index acf1eb27f8..2b297372fa 100644 --- a/dash-renderer/package-lock.json +++ b/dash-renderer/package-lock.json @@ -1,6 +1,6 @@ { "name": "dash-renderer", - "version": "1.7.0", + "version": "1.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/dash-renderer/package.json b/dash-renderer/package.json index 8cb538d1bd..d20a4146f0 100644 --- a/dash-renderer/package.json +++ b/dash-renderer/package.json @@ -1,6 +1,6 @@ { "name": "dash-renderer", - "version": "1.7.0", + "version": "1.8.0", "description": "render dash components in react", "main": "dash_renderer/dash_renderer.min.js", "scripts": { diff --git a/dash/version.py b/dash/version.py index 6b0872cb2f..638c1217d2 100644 --- a/dash/version.py +++ b/dash/version.py @@ -1 +1 @@ -__version__ = "1.15.0" +__version__ = "1.16.0" diff --git a/requires-install.txt b/requires-install.txt index a23dd78567..ff93db920b 100644 --- a/requires-install.txt +++ b/requires-install.txt @@ -1,8 +1,8 @@ Flask>=1.0.2 flask-compress plotly -dash_renderer==1.7.0 -dash-core-components==1.11.0 -dash-html-components==1.1.0 -dash-table==4.10.0 +dash_renderer>=1.7.0 +dash-core-components>=1.11.0 +dash-html-components>=1.1.0 +dash-table>=4.10.0 future From fa43741d47325d01939f85bded204075058f2e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 3 Sep 2020 13:36:45 -0400 Subject: [PATCH 8/8] strict versions --- requires-install.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requires-install.txt b/requires-install.txt index ff93db920b..2c67c74890 100644 --- a/requires-install.txt +++ b/requires-install.txt @@ -1,8 +1,8 @@ Flask>=1.0.2 flask-compress plotly -dash_renderer>=1.7.0 -dash-core-components>=1.11.0 -dash-html-components>=1.1.0 -dash-table>=4.10.0 +dash_renderer==1.8.0 +dash-core-components==1.12.0 +dash-html-components==1.1.1 +dash-table==4.10.1 future