diff --git a/.github/workflows/compatibility_tests.yml b/.github/workflows/compatibility_tests.yml index 929bb96f..11c9d0cd 100644 --- a/.github/workflows/compatibility_tests.yml +++ b/.github/workflows/compatibility_tests.yml @@ -5,7 +5,7 @@ on: - cron: '0 8 7 * 2' push: branches: - - master + - main env: JEST_ENV: prod @@ -22,7 +22,7 @@ jobs: - name: Build docker image run: docker build . - cellxgene-master-with-python-and-anndata-versions: + cellxgene-main-with-python-and-anndata-versions: name: python versions x anndata versions runs-on: ubuntu-latest strategy: @@ -86,8 +86,8 @@ jobs: - name: Tests run: cd cellxgene && make unit-test ${{ matrix.test-suite }} - cellxgene-master-with-anndata-master: - name: cellxgene master with anndata master + cellxgene-main-with-anndata-master: + name: cellxgene main with anndata master runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ca0f3bec..48d1923a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,7 +2,7 @@ name: Deploy via single cell infra repo on: push: - branches: master + branches: main jobs: deploy: diff --git a/.github/workflows/push_tests.yml b/.github/workflows/push_tests.yml index 0e84a43b..8324af42 100644 --- a/.github/workflows/push_tests.yml +++ b/.github/workflows/push_tests.yml @@ -2,9 +2,9 @@ name: Push Tests on: push: - branches: master + branches: main pull_request: - branches: '*' + branches: "*" env: JEST_ENV: prod @@ -32,7 +32,7 @@ jobs: run: | pip install flake8 cd client - npm i "eslint" "eslint-config-airbnb" "eslint-config-prettier" "eslint-loader" "eslint-plugin-filenames" "eslint-plugin-import" "eslint-plugin-jest" "eslint-plugin-jsx-a11y" "eslint-plugin-react" "eslint-plugin-react-hooks" "eslint-plugin-prettier" + npm install - name: Lint with flake8 run: | make lint-server @@ -41,7 +41,6 @@ jobs: run: | make lint - unit-test: runs-on: ubuntu-latest steps: @@ -73,7 +72,7 @@ jobs: cd client && ./node_modules/codecov/bin/codecov --yml=../.codecov.yml --root=../ --gcov-root=../ -C -F frontend,javascript,unitTest smoke-tests: - runs-on: ubuntu-latest + runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.7 diff --git a/.gitignore b/.gitignore index 4f93ac98..509d9153 100644 --- a/.gitignore +++ b/.gitignore @@ -19,11 +19,7 @@ venv/ cellxgene/ # client build -server/common/web/static/css/ -server/common/web/static/img/ -server/common/web/static/media/ -server/common/web/static/fonts/ -server/common/web/static/js/ +server/common/web/static/* server/common/web/templates/ server/common/web/csp-hashes.json @@ -47,9 +43,13 @@ npm-debug.log __pycache__ *.DS_Store* data +tags # Jekyll docs/_site/ docs/Gemfile.lock client/.eslintcache + +# E2E Testing +ignoreE2E* diff --git a/Makefile b/Makefile index 8da0edd3..0fe4633f 100644 --- a/Makefile +++ b/Makefile @@ -94,7 +94,7 @@ pydist: build # RELEASE HELPERS -# create new version to commit to master +# create new version to commit to main .PHONY: release-stage-1 release-stage-1: dev-env bump clean-lite gen-package-lock @echo "Version bumped part:$(PART) and client built. Ready to commit and push" diff --git a/README.md b/README.md index 73114b36..2330f20f 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ _an interactive explorer for single-cell transcriptomics data_ [![DOI](https://zenodo.org/badge/105615409.svg)](https://zenodo.org/badge/latestdoi/105615409) [![PyPI](https://img.shields.io/pypi/v/cellxgene)](https://pypi.org/project/cellxgene/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/cellxgene)](https://pypistats.org/packages/cellxgene) [![GitHub last commit](https://img.shields.io/github/last-commit/chanzuckerberg/cellxgene)](https://github.com/chanzuckerberg/cellxgene/pulse) [![Push Tests](https://github.com/chanzuckerberg/cellxgene/workflows/Push%20Tests/badge.svg)](https://github.com/chanzuckerberg/cellxgene/actions?query=workflow%3A%22Push+Tests%22) [![Compatibility Tests](https://github.com/chanzuckerberg/cellxgene/workflows/Compatibility%20Tests/badge.svg)](https://github.com/chanzuckerberg/cellxgene/actions?query=workflow%3A%22Compatibility+Tests%22) -![Code Coverage](https://codecov.io/gh/chanzuckerberg/cellxgene/branch/master/graph/badge.svg) +![Code Coverage](https://codecov.io/gh/chanzuckerberg/cellxgene/branch/main/graph/badge.svg) cellxgene (pronounced "cell-by-gene") is an interactive data explorer for single-cell transcriptomics datasets, such as those coming from the [Human Cell Atlas](https://humancellatlas.org). Leveraging modern web development techniques to enable fast visualizations of at least 1 million cells, we hope to enable biologists and computational researchers to explore their data. Whether you need to visualize one thousand cells or one million, cellxgene helps you gain insight into your single-cell data. - + # Getting started ### The comprehensive guide to cellxgene diff --git a/app.json b/app.json index 958f7a28..107f8f27 100644 --- a/app.json +++ b/app.json @@ -11,8 +11,12 @@ "dataviz" ], "buildpacks": [ - "heroku/nodejs", - "heroku/python" + { + "url": "heroku/nodejs" + }, + { + "url": "heroku/python" + } ], "stack": "heroku-18", "env": { diff --git a/client/Makefile b/client/Makefile index 3a9acb5b..5033053f 100644 --- a/client/Makefile +++ b/client/Makefile @@ -7,6 +7,7 @@ ANNOTATIONS_FILENAME := $(shell basename $(ANNOTATIONS)) .PHONY: clean clean: rm -rf node_modules + rm -f __tests__/screenshots/*.png .PHONY: ci ci: @@ -21,39 +22,20 @@ WEBPACK_CONFIG ?= configuration/webpack/webpack.config.prod.js build: npm run build $(WEBPACK_CONFIG) -# Formatting code - -.PHONY: lint -lint: - npx eslint ./src/ - # Development convenience methods .PHONY: start-frontend start-frontend: node server/development.js -.PHONY: e2e -e2e: - node node_modules/jest/bin/jest.js \ - --verbose false \ - --config __tests__/e2e/e2eJestConfig.json \ - e2e/e2e.test.js - -.PHONY: e2e-annotations -e2e-annotations: - node node_modules/jest/bin/jest.js \ - --verbose false \ - --config __tests__/e2e/e2eJestConfig.json \ - e2e/e2eAnnotations.test.js - +# start an instance of cellxgene and run the end-to-end tests .PHONY: smoke-test smoke-test: start_server_and_test \ 'CXG_OPTIONS="--disable-annotations" $(MAKE) start-server' \ $(CXG_SERVER_PORT) \ - '$(MAKE) e2e' - + 'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" npm run e2e -- --verbose false' +# start an instance of cellxgene and run the end-to-end annotations tests .PHONY: smoke-test-annotations smoke-test-annotations: $(eval TMP_DIR := $(shell mktemp -d /tmp/cellxgene_XXXXXX)) @@ -61,13 +43,12 @@ smoke-test-annotations: start_server_and_test \ 'CXG_OPTIONS="--annotations-file $(TMP_DIR)/$(ANNOTATIONS_FILENAME)" $(MAKE) start-server' \ $(CXG_SERVER_PORT) \ - '$(MAKE) e2e-annotations' + 'CXG_URL_BASE="http://localhost:$(CXG_SERVER_PORT)" npm run e2e-annotations -- --verbose false' rm -rf $(TMP_DIR) .PHONY: unit-test unit-test: - node node_modules/jest/bin/jest.js \ - --testPathIgnorePatterns e2e + node node_modules/jest/bin/jest.js --testPathIgnorePatterns e2e # pass remaining commands through to npm run %: diff --git a/client/__tests__/e2e/__snapshots__/e2e.test.js.snap b/client/__tests__/e2e/__snapshots__/e2e.test.js.snap index 9f866bf8..fce4a085 100644 --- a/client/__tests__/e2e/__snapshots__/e2e.test.js.snap +++ b/client/__tests__/e2e/__snapshots__/e2e.test.js.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`did launch page launched 1`] = `"pbmc3kc3k"`; +exports[`did launch page launched 1`] = `"pbmc3kc3k"`; -exports[`metadata loads categories and values from dataset appear 1`] = `"
louvainvain
tint
"`; +exports[`metadata loads categories and values from dataset appear 1`] = `"
louvainvain
tint
"`; diff --git a/client/__tests__/e2e/__snapshots__/e2eAnnotations.test.js.snap b/client/__tests__/e2e/__snapshots__/e2eAnnotations.test.js.snap new file mode 100644 index 00000000..3ad14bca --- /dev/null +++ b/client/__tests__/e2e/__snapshots__/e2eAnnotations.test.js.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`annotations stacked bar graph renders 1`] = ` +Array [ + "
TEST-LABELLABEL
0
", + "
unassignedigned
2132
", +] +`; + +exports[`annotations stacked bar graph renders 2`] = ` +Array [ + "
TEST-LABELLABEL
0
", + "
unassignedigned
2638
", +] +`; diff --git a/client/__tests__/e2e/cellxgeneActions.js b/client/__tests__/e2e/cellxgeneActions.js index e6b5afb6..eee4714c 100644 --- a/client/__tests__/e2e/cellxgeneActions.js +++ b/client/__tests__/e2e/cellxgeneActions.js @@ -1,224 +1,315 @@ +/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */ import { strict as assert } from "assert"; +import { + clearInputAndTypeInto, + clickOn, + getAllByClass, + getOneElementInnerText, + typeInto, + waitByID, + waitByClass, + waitForAllByIds, + clickOnUntil, + getTestClass, + getTestId, + isElementPresent, +} from "./puppeteerUtils"; -export const cellxgeneActions = (page, utils) => ({ - async drag(testId, start, end, lasso = false) { - const layout = await utils.waitByID(testId); - const elBox = await layout.boxModel(); - const x1 = elBox.content[0].x + start.x; - const x2 = elBox.content[0].x + end.x; - const y1 = elBox.content[0].y + start.y; - const y2 = elBox.content[0].y + end.y; +export async function drag(testId, start, end, lasso = false) { + const layout = await waitByID(testId); + const elBox = await layout.boxModel(); + const x1 = elBox.content[0].x + start.x; + const x2 = elBox.content[0].x + end.x; + const y1 = elBox.content[0].y + start.y; + const y2 = elBox.content[0].y + end.y; + await page.mouse.move(x1, y1); + await page.mouse.down(); + if (lasso) { + await page.mouse.move(x2, y1); + await page.mouse.move(x2, y2); + await page.mouse.move(x1, y2); await page.mouse.move(x1, y1); - await page.mouse.down(); - if (lasso) { - await page.mouse.move(x2, y1); - await page.mouse.move(x2, y2); - await page.mouse.move(x1, y2); - await page.mouse.move(x1, y1); - } else { - await page.mouse.move(x2, y2); - } - await page.mouse.up(); - }, + } else { + await page.mouse.move(x2, y2); + } + await page.mouse.up(); +} - async clickOnCoordinate(testId, coord) { - const layout = await utils.waitByID(testId); - const elBox = await layout.boxModel(); - const x = elBox.content[0].x + coord.x; - const y = elBox.content[0].y + coord.y; - await page.mouse.click(x, y); - }, +export async function clickOnCoordinate(testId, coord) { + const layout = await expect(page).toMatchElement(getTestId(testId)); + const elBox = await layout.boxModel(); - async getAllHistograms(testclass, testIds) { - const histTestIds = testIds.map((tid) => `histogram-${tid}`); - // these load asynchronously, so we need to wait for each histogram individually - await utils.waitForAllByIds(histTestIds); - const allHistograms = await utils.getAllByClass(testclass); - return allHistograms.map((hist) => hist.replace(/^histogram-/, "")); - }, + if (!elBox) { + throw Error("Layout's boxModel is not available!"); + } - async getAllCategoriesAndCounts(category) { - await utils.waitByClass("categorical-row"); - return page.$$eval( - `[data-testid="category-${category}"] [data-testclass='categorical-row']`, - (rows) => - Object.fromEntries( - rows.map((row) => { - const cat = row - .querySelector("[data-testclass='categorical-value']") - .getAttribute("aria-label"); + const x = elBox.content[0].x + coord.x; + const y = elBox.content[0].y + coord.y; + await page.mouse.click(x, y); +} - const count = row.querySelector( - "[data-testclass='categorical-value-count']" - ).innerText; - return [cat, count]; - }) - ) +export async function getAllHistograms(testclass, testIds) { + const histTestIds = testIds.map((tid) => `histogram-${tid}`); + + // these load asynchronously, so we need to wait for each histogram individually, + // and they may be quite slow in some cases. + await waitForAllByIds(histTestIds, { timeout: 4 * 60 * 1000 }); + + const allHistograms = await getAllByClass(testclass); + + const testIDs = await Promise.all( + allHistograms.map((hist) => { + return page.evaluate((elem) => { + return elem.dataset.testid; + }, hist); + }) + ); + + return testIDs.map((id) => id.replace(/^histogram-/, "")); +} + +export async function getAllCategoriesAndCounts(category) { + // these load asynchronously, so we have to wait for the specific category. + await waitByID(`category-${category}`); + + return page.$$eval( + `[data-testid="category-${category}"] [data-testclass='categorical-row']`, + (rows) => + Object.fromEntries( + rows.map((row) => { + const cat = row + .querySelector("[data-testclass='categorical-value']") + .getAttribute("aria-label"); + + const count = row.querySelector( + "[data-testclass='categorical-value-count']" + ).innerText; + + return [cat, count]; + }) + ) + ); +} + +export async function getCellSetCount(num) { + await clickOn(`cellset-button-${num}`); + return getOneElementInnerText(`[data-testid='cellset-count-${num}']`); +} + +export async function resetCategory(category) { + const checkboxId = `${category}:category-select`; + await waitByID(checkboxId); + const checkedPseudoclass = await page.$eval( + `[data-testid='${checkboxId}']`, + (el) => el.matches(":checked") + ); + if (!checkedPseudoclass) await clickOn(checkboxId); + + const categoryRow = await waitByID(`${category}:category-expand`); + + const isExpanded = await categoryRow.$( + "[data-testclass='category-expand-is-expanded']" + ); + + if (isExpanded) await clickOn(`${category}:category-expand`); +} + +export async function calcCoordinate(testId, xAsPercent, yAsPercent) { + const el = await waitByID(testId); + const size = await el.boxModel(); + return { + x: Math.floor(size.width * xAsPercent), + y: Math.floor(size.height * yAsPercent), + }; +} + +export async function calcDragCoordinates(testId, coordinateAsPercent) { + return { + start: await calcCoordinate( + testId, + coordinateAsPercent.x1, + coordinateAsPercent.y1 + ), + end: await calcCoordinate( + testId, + coordinateAsPercent.x2, + coordinateAsPercent.y2 + ), + }; +} + +export async function selectCategory(category, values, reset = true) { + if (reset) await resetCategory(category); + + await clickOn(`${category}:category-expand`); + await clickOn(`${category}:category-select`); + + for (const value of values) { + await clickOn(`categorical-value-select-${category}-${value}`); + } +} + +export async function expandCategory(category) { + const expand = await waitByID(`${category}:category-expand`); + const notExpanded = await expand.$( + "[data-testclass='category-expand-is-not-expanded']" + ); + if (notExpanded) await clickOn(`${category}:category-expand`); +} + +export async function clip(min = 0, max = 100) { + await clickOn("visualization-settings"); + await clearInputAndTypeInto("clip-min-input", min); + await clearInputAndTypeInto("clip-max-input", max); + await clickOn("clip-commit"); +} + +export async function createCategory(categoryName) { + await clickOnUntil("open-annotation-dialog", async () => { + await expect(page).toMatchElement(getTestId("new-category-name")); + }); + + await typeInto("new-category-name", categoryName); + await clickOn("submit-category"); +} + +export async function duplicateCategory(categoryName) { + await clickOn("open-annotation-dialog"); + + await typeInto("new-category-name", categoryName); + + const dropdownOptionClass = "duplicate-category-dropdown-option"; + + await clickOnUntil("duplicate-category-dropdown", async () => { + await expect(page).toMatchElement(getTestClass(dropdownOptionClass)); + }); + + const option = await expect(page).toMatchElement( + getTestClass(dropdownOptionClass) + ); + + await option.click(); + + await clickOnUntil("submit-category", async () => { + await expect(page).toMatchElement( + getTestId(`${categoryName}:category-expand`) ); - }, + }); - async cellSet(num) { - await utils.clickOn(`cellset-button-${num}`); - return utils.getOneElementInnerText(`[data-testid='cellset-count-${num}']`); - }, + await waitByClass("autosave-complete"); +} - async resetCategory(category) { - const checkboxId = `${category}:category-select`; - await utils.waitByID(checkboxId); - const checkedPseudoclass = await page.$eval( - `[data-testid='${checkboxId}']`, - (el) => el.matches(":checked") - ); - if (!checkedPseudoclass) await utils.clickOn(checkboxId); - try { - const categoryRow = await utils.waitByID(`${category}:category-expand`); - const isExpanded = await categoryRow.$( - "[data-testclass='category-expand-is-expanded']" - ); - if (isExpanded) await utils.clickOn(`${category}:category-expand`); - } catch {} - }, +export async function renameCategory(oldCategoryName, newCategoryName) { + await clickOn(`${oldCategoryName}:see-actions`); + await clickOn(`${oldCategoryName}:edit-category-mode`); + await clearInputAndTypeInto( + `${oldCategoryName}:edit-category-name-text`, + newCategoryName + ); + await clickOn(`${oldCategoryName}:submit-category-edit`); +} - async calcCoordinate(testId, xAsPercent, yAsPercent) { - const el = await utils.waitByID(testId); - const size = await el.boxModel(); - return { - x: Math.floor(size.width * xAsPercent), - y: Math.floor(size.height * yAsPercent), - }; - }, +export async function deleteCategory(categoryName) { + const targetId = `${categoryName}:delete-category`; - async calcDragCoordinates(testId, coordinateAsPercent) { - return { - start: await this.calcCoordinate( - testId, - coordinateAsPercent.x1, - coordinateAsPercent.y1 - ), - end: await this.calcCoordinate( - testId, - coordinateAsPercent.x2, - coordinateAsPercent.y2 - ), - }; - }, + await clickOnUntil(`${categoryName}:see-actions`, async () => { + await expect(page).toMatchElement(getTestId(targetId)); + }); - async selectCategory(category, values, reset = true) { - if (reset) await this.resetCategory(category); - await utils.clickOn(`${category}:category-expand`); - await utils.clickOn(`${category}:category-select`); - for (const val of values) { - await utils.clickOn(`categorical-value-select-${category}-${val}`); - } - }, + await clickOn(targetId); - async expandCategory(category) { - const expand = await utils.waitByID(`${category}:category-expand`); - const notExpanded = await expand.$( - "[data-testclass='category-expand-is-not-expanded']" - ); - if (notExpanded) await utils.clickOn(`${category}:category-expand`); - }, + await assertCategoryDoesNotExist(); +} - async clip(min = 0, max = 100) { - await utils.clickOn("visualization-settings"); - await utils.clearInputAndTypeInto("clip-min-input", min); - await utils.clearInputAndTypeInto("clip-max-input", max); - await utils.clickOn("clip-commit"); - }, +export async function createLabel(categoryName, labelName) { + /** + * (thuang): This explicit wait is needed, since currently showing + * the modal again quickly after the previous action dismissing the + * modal will persist the input value from the previous action. + * + * To reproduce: + * 1. Click on the plus sign to show the modal to add a new label to the category + * 2. Type `123` in the input box + * 3. Hover over your mouse over the plus sign and double click to quickly dismiss and + * invoke the modal again + * 4. You will see `123` is persisted in the input box + * 5. Expected behavior is to get an empty input box + */ + await page.waitFor(500); - async createCategory(categoryName) { - await utils.clickOn("open-annotation-dialog"); - await utils.typeInto("new-category-name", categoryName); - await utils.clickOn("submit-category"); - }, + await clickOn(`${categoryName}:see-actions`); - async renameCategory(oldCatgoryName, newCategoryName) { - await utils.clickOn(`${oldCatgoryName}:see-actions`); - await utils.clickOn(`${oldCatgoryName}:edit-category-mode`); - await utils.clearInputAndTypeInto( - `${oldCatgoryName}:edit-category-name-text`, - newCategoryName - ); - await utils.clickOn(`${oldCatgoryName}:submit-category-edit`); - }, + await clickOn(`${categoryName}:add-new-label-to-category`); - async deleteCategory(categoryName) { - await utils.clickOn(`${categoryName}:see-actions`); - await utils.clickOn(`${categoryName}:delete-category`); - }, + await typeInto(`${categoryName}:new-label-name`, labelName); - async createLabel(categoryName, labelName) { - await utils.clickOn(`${categoryName}:see-actions`); - await utils.clickOn(`${categoryName}:add-new-label-to-category`); - await utils.typeInto(`${categoryName}:new-label-name`, labelName); - await utils.clickOn(`${categoryName}:submit-label`); - }, + await clickOn(`${categoryName}:submit-label`); +} - async deleteLabel(categoryName, labelName) { - await this.expandCategory(categoryName); - await utils.clickOn(`${categoryName}:${labelName}:see-actions`); - await utils.clickOn(`${categoryName}:${labelName}:delete-label`); - }, +export async function deleteLabel(categoryName, labelName) { + await expandCategory(categoryName); + await clickOn(`${categoryName}:${labelName}:see-actions`); + await clickOn(`${categoryName}:${labelName}:delete-label`); +} - async renameLabel(categoryName, oldLabelName, newLabelName) { - await this.expandCategory(categoryName); - await utils.clickOn(`${categoryName}:${oldLabelName}:see-actions`); - await utils.clickOn(`${categoryName}:${oldLabelName}:edit-label`); - await utils.clearInputAndTypeInto( - `${categoryName}:${oldLabelName}:edit-label-name`, - newLabelName - ); - await utils.clickOn(`${categoryName}:${oldLabelName}:submit-label-edit`); - }, +export async function renameLabel(categoryName, oldLabelName, newLabelName) { + await expandCategory(categoryName); + await clickOn(`${categoryName}:${oldLabelName}:see-actions`); + await clickOn(`${categoryName}:${oldLabelName}:edit-label`); + await clearInputAndTypeInto( + `${categoryName}:${oldLabelName}:edit-label-name`, + newLabelName + ); + await clickOn(`${categoryName}:${oldLabelName}:submit-label-edit`); +} - async addGeneToSearch(geneName) { - await utils.typeInto("gene-search", geneName); - await page.keyboard.press("Enter"); - await page.waitForSelector(`[data-testid='histogram-${geneName}']`); - }, +export async function addGeneToSearch(geneName) { + await typeInto("gene-search", geneName); + await page.keyboard.press("Enter"); + await page.waitForSelector(`[data-testid='histogram-${geneName}']`); +} - async subset(coordinatesAsPercent) { - // In order to deselect the selection after the subset, make sure we have some clear part - // of the scatterplot we can click on - assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99); - const lassoSelection = await this.calcDragCoordinates( - "layout-graph", - coordinatesAsPercent - ); - await this.drag( - "layout-graph", - lassoSelection.start, - lassoSelection.end, - true - ); - await utils.clickOn("subset-button"); - const clearCoordinate = await this.calcCoordinate( - "layout-graph", - 0.5, - 0.99 - ); - await this.clickOnCoordinate("layout-graph", clearCoordinate); - }, +export async function subset(coordinatesAsPercent) { + // In order to deselect the selection after the subset, make sure we have some clear part + // of the scatterplot we can click on + assert(coordinatesAsPercent.x2 < 0.99 || coordinatesAsPercent.y2 < 0.99); + const lassoSelection = await calcDragCoordinates( + "layout-graph", + coordinatesAsPercent + ); + await drag("layout-graph", lassoSelection.start, lassoSelection.end, true); + await clickOn("subset-button"); + const clearCoordinate = await calcCoordinate("layout-graph", 0.5, 0.99); + await clickOnCoordinate("layout-graph", clearCoordinate); +} - async setSellSet(cellSet, cellSetNum) { - for (const selection of cellSet.filter( - (sel) => sel.kind === "categorical" - )) { - await this.selectCategory(selection.metadata, selection.values, true); - } - await this.cellSet(cellSetNum); - }, +export async function setSellSet(cellSet, cellSetNum) { + const selections = cellSet.filter((sel) => sel.kind === "categorical"); - async runDiffExp(cellSet1, cellSet2) { - await this.setSellSet(cellSet1, 1); - await this.setSellSet(cellSet2, 2); - await utils.clickOn("diffexp-button"); - }, + for (const selection of selections) { + await selectCategory(selection.metadata, selection.values, true); + } - async bulkAddGenes(geneNames) { - await utils.clickOn("section-bulk-add"); - await utils.typeInto("input-bulk-add", geneNames.join(",")); - await page.keyboard.press("Enter"); - }, -}); + await getCellSetCount(cellSetNum); +} + +export async function runDiffExp(cellSet1, cellSet2) { + await setSellSet(cellSet1, 1); + await setSellSet(cellSet2, 2); + await clickOn("diffexp-button"); +} + +export async function bulkAddGenes(geneNames) { + await clickOn("section-bulk-add"); + await typeInto("input-bulk-add", geneNames.join(",")); + await page.keyboard.press("Enter"); +} + +export async function assertCategoryDoesNotExist(categoryName) { + const result = await isElementPresent( + getTestId(`${categoryName}:category-label`) + ); + + await expect(result).toBe(false); +} +/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */ diff --git a/client/__tests__/e2e/config.js b/client/__tests__/e2e/config.js index 80078f8b..4222a103 100644 --- a/client/__tests__/e2e/config.js +++ b/client/__tests__/e2e/config.js @@ -1,10 +1,8 @@ -export const jestEnv = process.env.JEST_ENV; -export const appPort = process.env.CXG_SERVER_PORT; -export const appUrlBase = - process.env.CXG_URL_BASE || `http://localhost:${appPort}`; -export const DEV = jestEnv === "dev"; -export const DEBUG = jestEnv === "debug"; -export const DATASET = "pbmc3k"; +import * as ENV_DEFAULT from "../../../environment.default.json"; -if (DEBUG) jest.setTimeout(100000); -if (DEV) jest.setTimeout(10000); +export const jestEnv = process.env.JEST_ENV || ENV_DEFAULT.JEST_ENV; +export const appUrlBase = + process.env.CXG_URL_BASE || `http://localhost:${ENV_DEFAULT.CXG_CLIENT_PORT}`; +export const DATASET = "pbmc3k"; +export const isDev = jestEnv === ENV_DEFAULT.DEV; +export const isDebug = jestEnv === ENV_DEFAULT.DEBUG; diff --git a/client/__tests__/e2e/data.js b/client/__tests__/e2e/data.js index e0fda7a8..b7f8db77 100644 --- a/client/__tests__/e2e/data.js +++ b/client/__tests__/e2e/data.js @@ -27,7 +27,7 @@ export const datasets = { lasso: [ { "coordinates-as-percent": { x1: 0.1, y1: 0.25, x2: 0.7, y2: 0.75 }, - count: "1173" + count: "1173", }, ], categorical: [ @@ -111,7 +111,7 @@ export const datasets = { panzoom: { lasso: { "coordinates-as-percent": { x1: 0.3, y1: 0.3, x2: 0.5, y2: 0.5 }, - count: "24", + count: "38", }, }, }, diff --git a/client/__tests__/e2e/e2e.test.js b/client/__tests__/e2e/e2e.test.js index 58b4367e..982a076a 100644 --- a/client/__tests__/e2e/e2e.test.js +++ b/client/__tests__/e2e/e2e.test.js @@ -1,62 +1,69 @@ -/* -Smoke test suite that will be run in Travis CI - -Tests included in this file are expected to be relatively stable and test core features +/** + * Smoke test suite that will be run in Travis CI + * Tests included in this file are expected to be relatively stable and test core features */ + +/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */ import { appUrlBase, DATASET } from "./config"; -import { setupTestBrowser } from "./testBrowser"; + import { datasets } from "./data"; -let browser; -let page; -let utils; -let cxgActions; +import { + clickOn, + getAllByClass, + getElementCoordinates, + getOneElementInnerHTML, + getTestId, + goToPage, + typeInto, + waitByID, +} from "./puppeteerUtils"; + +import { + addGeneToSearch, + bulkAddGenes, + calcDragCoordinates, + clip, + drag, + getAllCategoriesAndCounts, + getAllHistograms, + getCellSetCount, + runDiffExp, + selectCategory, + subset, +} from "./cellxgeneActions"; + const data = datasets[DATASET]; -beforeAll(async () => { - [browser, page, utils, cxgActions] = await setupTestBrowser(); -}); - -beforeEach(async () => { - await page.goto(appUrlBase); -}); - -afterAll(() => { - if (browser !== undefined) browser.close(); -}); - describe("did launch", () => { test("page launched", async () => { - const element = await utils.getOneElementInnerHTML( - "[data-testid='header']" - ); - expect(element).toMatchSnapshot(); - }); + await goToPage(appUrlBase); - test("terms of service, if they are there", async () => { - try { - await utils.clickOn("tos-cookies-accept", { timeout: 3000 }); - } catch { - console.warn("No terms of service footer detected."); - } - page.waitFor(50); // give the footer a chance to disappear - const result = await page.$("[data-testid='tos-cookies-accept']"); - expect(result).toBeNull(); + const element = await getOneElementInnerHTML(getTestId("header")); + + expect(element).toMatchSnapshot(); }); }); describe("metadata loads", () => { test("categories and values from dataset appear", async () => { - for (const label in data.categorical) { - const elem = await utils.getOneElementInnerHTML( - `[data-testid="category-${label}"]` + await goToPage(appUrlBase); + + for (const label of Object.keys(data.categorical)) { + const element = await getOneElementInnerHTML( + getTestId(`category-${label}`) ); - expect(elem).toMatchSnapshot(); - await utils.clickOn(`${label}:category-expand`); - const categories = await cxgActions.getAllCategoriesAndCounts(label); + + expect(element).toMatchSnapshot(); + + await clickOn(`${label}:category-expand`); + + const categories = await getAllCategoriesAndCounts(label); + expect(Object.keys(categories)).toMatchObject( Object.keys(data.categorical[label]) ); + expect(Object.values(categories)).toMatchObject( Object.values(data.categorical[label]) ); @@ -64,74 +71,98 @@ describe("metadata loads", () => { }); test("continuous data appears", async () => { - for (const label in data.continuous) { - await utils.waitByID(`histogram-${label}`); + await goToPage(appUrlBase); + + for (const label of Object.keys(data.continuous)) { + await waitByID(`histogram-${label}`); } }); }); describe("cell selection", () => { test("selects all cells cellset 1", async () => { - const cellCount = await cxgActions.cellSet(1); + await goToPage(appUrlBase); + + const cellCount = await getCellSetCount(1); expect(cellCount).toBe(data.dataframe.nObs); }); test("selects all cells cellset 2", async () => { - const cellCount = await cxgActions.cellSet(2); + await goToPage(appUrlBase); + + const cellCount = await getCellSetCount(2); expect(cellCount).toBe(data.dataframe.nObs); }); test("selects cells via lasso", async () => { + await goToPage(appUrlBase); + for (const cellset of data.cellsets.lasso) { - const cellset1 = await cxgActions.calcDragCoordinates( + const cellset1 = await calcDragCoordinates( "layout-graph", cellset["coordinates-as-percent"] ); - await cxgActions.drag("layout-graph", cellset1.start, cellset1.end, true); - const cellCount = await cxgActions.cellSet(1); + + await drag("layout-graph", cellset1.start, cellset1.end, true); + const cellCount = await getCellSetCount(1); expect(cellCount).toBe(cellset.count); } }); test("selects cells via categorical", async () => { + await goToPage(appUrlBase); + for (const cellset of data.cellsets.categorical) { - await utils.clickOn(`${cellset.metadata}:category-expand`); - await utils.clickOn(`${cellset.metadata}:category-select`); - for (const val of cellset.values) { - await utils.clickOn( - `categorical-value-select-${cellset.metadata}-${val}` - ); + await clickOn(`${cellset.metadata}:category-expand`); + await clickOn(`${cellset.metadata}:category-select`); + + for (const value of cellset.values) { + await clickOn(`categorical-value-select-${cellset.metadata}-${value}`); } - const cellCount = await cxgActions.cellSet(1); + + const cellCount = await getCellSetCount(1); + expect(cellCount).toBe(cellset.count); } }); test("selects cells via continuous", async () => { + await goToPage(appUrlBase); + for (const cellset of data.cellsets.continuous) { const histBrushableAreaId = `histogram-${cellset.metadata}-plot-brushable-area`; - const coords = await cxgActions.calcDragCoordinates( + + const coords = await calcDragCoordinates( histBrushableAreaId, cellset["coordinates-as-percent"] ); - await cxgActions.drag(histBrushableAreaId, coords.start, coords.end); - const cellCount = await cxgActions.cellSet(1); + + await drag(histBrushableAreaId, coords.start, coords.end); + + const cellCount = await getCellSetCount(1); + expect(cellCount).toBe(cellset.count); } }); }); describe("gene entry", () => { - test("search for single gene", async () => - cxgActions.addGeneToSearch(data.genes.search)); + test("search for single gene", async () => { + await goToPage(appUrlBase); + await addGeneToSearch(data.genes.search); + }); test("bulk add genes", async () => { + await goToPage(appUrlBase); + const testGenes = data.genes.bulkadd; - await cxgActions.bulkAddGenes(testGenes); - const allHistograms = await cxgActions.getAllHistograms( + + await bulkAddGenes(testGenes); + const allHistograms = await getAllHistograms( "histogram-user-gene", testGenes ); + expect(allHistograms).toEqual(expect.arrayContaining(testGenes)); expect(allHistograms).toHaveLength(testGenes.length); }); @@ -139,31 +170,42 @@ describe("gene entry", () => { describe("differential expression", () => { test("selects cells, saves them and performs diffexp", async () => { - await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2); - const allHistograms = await cxgActions.getAllHistograms( + await goToPage(appUrlBase); + + await runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2); + + const allHistograms = await getAllHistograms( "histogram-diffexp", data.diffexp["gene-results"] ); + expect(allHistograms).toEqual( expect.arrayContaining(data.diffexp["gene-results"]) ); + expect(allHistograms).toHaveLength(data.diffexp["gene-results"].length); }); }); describe("subset", () => { test("subset - cell count matches", async () => { + await goToPage(appUrlBase); + for (const select of data.subset.cellset1) { if (select.kind === "categorical") { - await cxgActions.selectCategory(select.metadata, select.values, true); + await selectCategory(select.metadata, select.values, true); } } - await utils.clickOn("subset-button"); - for (const label in data.subset.categorical) { - const categories = await cxgActions.getAllCategoriesAndCounts(label); + + await clickOn("subset-button"); + + for (const label of Object.keys(data.subset.categorical)) { + const categories = await getAllCategoriesAndCounts(label); + expect(Object.keys(categories)).toMatchObject( Object.keys(data.subset.categorical[label]) ); + expect(Object.values(categories)).toMatchObject( Object.values(data.subset.categorical[label]) ); @@ -171,79 +213,99 @@ describe("subset", () => { }); test("lasso after subset", async () => { + await goToPage(appUrlBase); + for (const select of data.subset.cellset1) { if (select.kind === "categorical") { - await cxgActions.selectCategory(select.metadata, select.values, true); + await selectCategory(select.metadata, select.values, true); } } - await utils.clickOn("subset-button"); - const lassoSelection = await cxgActions.calcDragCoordinates( + + await clickOn("subset-button"); + + const lassoSelection = await calcDragCoordinates( "layout-graph", data.subset.lasso["coordinates-as-percent"] ); - await cxgActions.drag( - "layout-graph", - lassoSelection.start, - lassoSelection.end, - true - ); - const cellCount = await cxgActions.cellSet(1); + + await drag("layout-graph", lassoSelection.start, lassoSelection.end, true); + + const cellCount = await getCellSetCount(1); expect(cellCount).toBe(data.subset.lasso.count); }); test("undo selection appends the top diff exp genes to user defined genes", async () => { + await goToPage(appUrlBase); + const userDefinedGenes = data.genes.bulkadd; const diffExpGenes = data.diffexp["gene-results"]; - await cxgActions.bulkAddGenes(userDefinedGenes); - const userDefinedHistograms = await cxgActions.getAllHistograms( + + await bulkAddGenes(userDefinedGenes); + const userDefinedHistograms = await getAllHistograms( "histogram-user-gene", userDefinedGenes ); + expect(userDefinedHistograms).toEqual( expect.arrayContaining(userDefinedGenes) ); - await cxgActions.subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 }); - await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2); - const diffExpHistograms = await cxgActions.getAllHistograms( + + await subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 }); + await runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2); + + const diffExpHistograms = await getAllHistograms( "histogram-diffexp", diffExpGenes ); + expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes)); - await utils.clickOn("reset-subset-button"); + + await clickOn("reset-subset-button"); const expected = [].concat(userDefinedGenes, diffExpGenes); - const userDefinedHistogramsAfterSubset = await cxgActions.getAllHistograms( + const userDefinedHistogramsAfterSubset = await getAllHistograms( "histogram-user-gene", expected ); + expect(userDefinedHistogramsAfterSubset).toEqual( expect.arrayContaining(expected) ); }); test("subset selection appends the top diff exp genes to user defined genes", async () => { + await goToPage(appUrlBase); + const userDefinedGenes = data.genes.bulkadd; const diffExpGenes = data.diffexp["gene-results"]; - await cxgActions.bulkAddGenes(userDefinedGenes); - const userDefinedHistograms = await cxgActions.getAllHistograms( + + await bulkAddGenes(userDefinedGenes); + const userDefinedHistograms = await getAllHistograms( "histogram-user-gene", userDefinedGenes ); + expect(userDefinedHistograms).toEqual( expect.arrayContaining(userDefinedGenes) ); - await cxgActions.subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 }); - await cxgActions.runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2); - const diffExpHistograms = await cxgActions.getAllHistograms( + + await subset({ x1: 0.15, y1: 0.1, x2: 0.98, y2: 0.98 }); + await runDiffExp(data.diffexp.cellset1, data.diffexp.cellset2); + + const diffExpHistograms = await getAllHistograms( "histogram-diffexp", diffExpGenes ); + expect(diffExpHistograms).toEqual(expect.arrayContaining(diffExpGenes)); - await cxgActions.subset({ x1: 0.16, y1: 0.11, x2: 0.97, y2: 0.97 }); + + await subset({ x1: 0.16, y1: 0.11, x2: 0.97, y2: 0.97 }); + const expected = [].concat(userDefinedGenes, diffExpGenes); - const userDefinedHistogramsAfterSubset = await cxgActions.getAllHistograms( + const userDefinedHistogramsAfterSubset = await getAllHistograms( "histogram-user-gene", expected ); + expect(userDefinedHistogramsAfterSubset).toEqual( expect.arrayContaining(expected) ); @@ -252,38 +314,51 @@ describe("subset", () => { describe("scatter plot", () => { test("scatter plot appears", async () => { - await cxgActions.bulkAddGenes(Object.values(data.scatter.genes)); - await utils.clickOn(`plot-x-${data.scatter.genes.x}`); - await utils.clickOn(`plot-y-${data.scatter.genes.y}`); - await utils.waitByID("scatterplot"); + await goToPage(appUrlBase); + + await bulkAddGenes(Object.values(data.scatter.genes)); + await clickOn(`plot-x-${data.scatter.genes.x}`); + await clickOn(`plot-y-${data.scatter.genes.y}`); + await waitByID("scatterplot"); }); }); describe("clipping", () => { test("clip continuous", async () => { - await cxgActions.clip(data.clip.min, data.clip.max); + await goToPage(appUrlBase); + + await clip(data.clip.min, data.clip.max); const histBrushableAreaId = `histogram-${data.clip.metadata}-plot-brushable-area`; - const coords = await cxgActions.calcDragCoordinates( + const coords = await calcDragCoordinates( histBrushableAreaId, data.clip["coordinates-as-percent"] ); - await cxgActions.drag(histBrushableAreaId, coords.start, coords.end); - const cellCount = await cxgActions.cellSet(1); + await drag(histBrushableAreaId, coords.start, coords.end); + const cellCount = await getCellSetCount(1); expect(cellCount).toBe(data.clip.count); }); test("clip gene", async () => { - await utils.typeInto("gene-search", data.clip.gene); + await goToPage(appUrlBase); + + await typeInto("gene-search", data.clip.gene); await page.keyboard.press("Enter"); + await page.waitForSelector(`[data-testid='histogram-${data.clip.gene}']`); - await cxgActions.clip(data.clip.min, data.clip.max); + + await clip(data.clip.min, data.clip.max); + const histBrushableAreaId = `histogram-${data.clip.gene}-plot-brushable-area`; - const coords = await cxgActions.calcDragCoordinates( + + const coords = await calcDragCoordinates( histBrushableAreaId, data.clip["coordinates-as-percent"] ); - await cxgActions.drag(histBrushableAreaId, coords.start, coords.end); - const cellCount = await cxgActions.cellSet(1); + + await drag(histBrushableAreaId, coords.start, coords.end); + + const cellCount = await getCellSetCount(1); + expect(cellCount).toBe(data.clip["gene-cell-count"]); }); }); @@ -291,85 +366,92 @@ describe("clipping", () => { // interact with UI elements just that they do not break describe("ui elements don't error", () => { test("color by", async () => { - for (const label in data.categorical) { - await utils.clickOn(`colorby-${label}`); - } - for (const label in data.continuous) { - await utils.clickOn(`colorby-${label}`); + await goToPage(appUrlBase); + + const allLabels = [ + ...Object.keys(data.categorical), + ...Object.keys(data.continuous), + ]; + + for (const label of allLabels) { + await clickOn(`colorby-${label}`); } }); test("color by for gene", async () => { - await utils.typeInto("gene-search", data.genes.search); + await goToPage(appUrlBase); + + await typeInto("gene-search", data.genes.search); await page.keyboard.press("Enter"); await page.waitForSelector( `[data-testid='histogram-${data.genes.search}']` ); - await utils.clickOn(`colorby-${data.genes.search}`); + await clickOn(`colorby-${data.genes.search}`); }); test("pan and zoom", async () => { - await utils.clickOn("mode-pan-zoom"); - const panCoords = await cxgActions.calcDragCoordinates( + await goToPage(appUrlBase); + + await clickOn("mode-pan-zoom"); + const panCoords = await calcDragCoordinates( "layout-graph", data.pan["coordinates-as-percent"] ); - await cxgActions.drag( - "layout-graph", - panCoords.start, - panCoords.end, - false - ); + + await drag("layout-graph", panCoords.start, panCoords.end, false); + await page.evaluate("window.scrollBy(0, 1000);"); }); }); describe("centroid labels", () => { test("labels are created", async () => { + await goToPage(appUrlBase); + const labels = Object.keys(data.categorical); - await utils.clickOn(`colorby-${labels[0]}`); - await utils.clickOn("centroid-label-toggle"); - /* eslint-disable no-await-in-loop */ + await clickOn(`colorby-${labels[0]}`); + await clickOn("centroid-label-toggle"); + // Toggle colorby for each category and check to see if labels are generated for (let i = 0, { length } = labels; i < length; i += 1) { const label = labels[i]; // first label is already enabled - if (i !== 0) await utils.clickOn(`colorby-${label}`); - const generatedLabels = await utils.getAllByClass("centroid-label"); + if (i !== 0) await clickOn(`colorby-${label}`); + const generatedLabels = await getAllByClass("centroid-label"); // Number of labels generated should be equal to size of the object expect(generatedLabels).toHaveLength( Object.keys(data.categorical[label]).length ); } - /* eslint-enable no-await-in-loop */ }); }); describe("graph overlay", () => { test("transform centroids correctly", async () => { + await goToPage(appUrlBase); + const category = Object.keys(data.categorical)[0]; - await utils.clickOn(`colorby-${category}`); - await utils.clickOn("centroid-label-toggle"); - await utils.clickOn("mode-pan-zoom"); - const panCoords = await cxgActions.calcDragCoordinates( + + await clickOn(`colorby-${category}`); + await clickOn("centroid-label-toggle"); + await clickOn("mode-pan-zoom"); + + const panCoords = await calcDragCoordinates( "layout-graph", data.pan["coordinates-as-percent"] ); const categoryValue = Object.keys(data.categorical[category])[0]; - const initialCoordinates = await utils.getElementCoordinates( + const initialCoordinates = await getElementCoordinates( `${categoryValue}-centroid-label` ); - await cxgActions.drag( - "layout-graph", - panCoords.start, - panCoords.end, - false - ); - const terminalCoordinates = await utils.getElementCoordinates( + + await drag("layout-graph", panCoords.start, panCoords.end, false); + const terminalCoordinates = await getElementCoordinates( `${categoryValue}-centroid-label` ); + expect(terminalCoordinates[0] - initialCoordinates[0]).toBeCloseTo( panCoords.end.x - panCoords.start.x ); @@ -378,3 +460,62 @@ describe("graph overlay", () => { ); }); }); + +test("pan zoom mode resets lasso selection", async () => { + await goToPage(appUrlBase); + + const panzoomLasso = data.features.panzoom.lasso; + + const lassoSelection = await calcDragCoordinates( + "layout-graph", + panzoomLasso["coordinates-as-percent"] + ); + + await drag("layout-graph", lassoSelection.start, lassoSelection.end, true); + await waitByID("lasso-element", { visible: true }); + + const initialCount = await getCellSetCount(1); + + expect(initialCount).toBe(panzoomLasso.count); + + await clickOn("mode-pan-zoom"); + await clickOn("mode-lasso"); + + const modeSwitchCount = await getCellSetCount(1); + + expect(modeSwitchCount).toBe(initialCount); +}); + +test("lasso moves after pan", async () => { + await goToPage(appUrlBase); + + const panzoomLasso = data.features.panzoom.lasso; + const coordinatesAsPercent = panzoomLasso["coordinates-as-percent"]; + + const lassoSelection = await calcDragCoordinates( + "layout-graph", + coordinatesAsPercent + ); + + await drag("layout-graph", lassoSelection.start, lassoSelection.end, true); + await waitByID("lasso-element", { visible: true }); + + const initialCount = await getCellSetCount(1); + + expect(initialCount).toBe(panzoomLasso.count); + + await clickOn("mode-pan-zoom"); + + const panCoords = await calcDragCoordinates( + "layout-graph", + coordinatesAsPercent + ); + + await drag("layout-graph", panCoords.start, panCoords.end, false); + await clickOn("mode-lasso"); + + const panCount = await getCellSetCount(2); + + expect(panCount).toBe(initialCount); +}); +/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */ diff --git a/client/__tests__/e2e/e2eAnnotations.test.js b/client/__tests__/e2e/e2eAnnotations.test.js index 3678b872..e284845c 100644 --- a/client/__tests__/e2e/e2eAnnotations.test.js +++ b/client/__tests__/e2e/e2eAnnotations.test.js @@ -2,249 +2,332 @@ Tests included in this file are specific to annotation features */ import { appUrlBase, DATASET } from "./config"; -import { setupTestBrowser } from "./testBrowser"; import { datasets } from "./data"; -let browser; -let page; -let utils; -let actions; +import { + clickOn, + goToPage, + waitByClass, + waitByID, + getTestId, + getTestClass, + getAllByClass, +} from "./puppeteerUtils"; + +import { + assertCategoryDoesNotExist, + calcDragCoordinates, + createCategory, + createLabel, + deleteCategory, + deleteLabel, + drag, + expandCategory, + renameCategory, + renameLabel, + subset, + duplicateCategory, +} from "./cellxgeneActions"; + const data = datasets[DATASET]; -beforeAll(async () => { - [browser, page, utils, actions] = await setupTestBrowser(); -}); +const perTestCategoryName = "TEST-CATEGORY"; +const perTestLabelName = "TEST-LABEL"; -afterAll(() => { - if (browser !== undefined) browser.close(); -}); +async function setup(config) { + await goToPage(appUrlBase); + + // setup the test fixtures + await createCategory(perTestCategoryName); + await createLabel(perTestCategoryName, perTestLabelName); + + if (config.withSubset) { + await subset({ x1: 0.1, y1: 0.1, x2: 0.8, y2: 0.8 }); + } + + await waitByClass("autosave-complete"); +} describe.each([ { withSubset: true, tag: "subset" }, { withSubset: false, tag: "whole" }, ])("annotations", (config) => { - const perTestCategoryName = "per-test-category"; - const perTestLabelName = "per-test-label"; - - beforeEach(async () => { - await page.goto(appUrlBase); - // wait for the page to load - await utils.waitByClass("autosave-complete"); - // setup the test fixtures - await actions.createCategory(perTestCategoryName); - await actions.createLabel(perTestCategoryName, perTestLabelName); - if (config.withSubset) - await actions.subset({ x1: 0.1, y1: 0.1, x2: 0.8, y2: 0.8 }); - await utils.waitByClass("autosave-complete"); - }); - - afterEach(async () => { - await deleteCategoryIfExists(perTestCategoryName); - await utils.waitByClass("autosave-complete"); - }); - test("create a category", async () => { + await setup(config); + const categoryName = `category-created-${config.tag}`; + await assertCategoryDoesNotExist(categoryName); - await actions.createCategory(categoryName); + + await createCategory(categoryName); + await assertCategoryExists(categoryName); }); test("delete a category", async () => { - await actions.deleteCategory(perTestCategoryName); + await setup(config); + + await deleteCategory(perTestCategoryName); await assertCategoryDoesNotExist(perTestCategoryName); }); test("rename a category", async () => { - const newCategoryName = `cluster-for-real-${config.tag}`; - await actions.renameCategory(perTestCategoryName, newCategoryName); + await setup(config); + + const newCategoryName = `NEW-${config.tag}`; + + await renameCategory(perTestCategoryName, newCategoryName); await assertCategoryDoesNotExist(perTestCategoryName); await assertCategoryExists(newCategoryName); }); test("create a label", async () => { + await setup(config); + const labelName = `new-label-${config.tag}`; + await assertLabelDoesNotExist(perTestCategoryName, labelName); - await actions.createLabel(perTestCategoryName, labelName); + + await createLabel(perTestCategoryName, labelName); + await assertLabelExists(perTestCategoryName, labelName); }); test("delete a label", async () => { - await actions.deleteLabel(perTestCategoryName, perTestLabelName); + await setup(config); + + await deleteLabel(perTestCategoryName, perTestLabelName); await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName); }); test("rename a label", async () => { + await setup(config); + const newLabelName = "my-cool-new-label"; + await assertLabelDoesNotExist(perTestCategoryName, newLabelName); - await actions.renameLabel( - perTestCategoryName, - perTestLabelName, - newLabelName - ); + await renameLabel(perTestCategoryName, perTestLabelName, newLabelName); await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName); await assertLabelExists(perTestCategoryName, newLabelName); }); test("check cell count for a label loaded from file", async () => { - const categoryName = "cluster-test"; - const labelName = "four"; - await actions.expandCategory(categoryName); - const result = await utils.waitByID( - `categorical-value-count-${categoryName}-${labelName}` + await setup(config); + + const duplicateCategoryName = "duplicate"; + await duplicateCategory(duplicateCategoryName); + + await page.reload({ waitUntil: ["networkidle0", "domcontentloaded"] }); + + const firstCategoryExpandIcon = await expect(page).toMatchElement( + getTestClass("category-expand") ); - expect(await result.evaluate((node) => node.innerText)).toBe( - data.annotationsFromFile.count.bySubsetConfig[config.withSubset] + + await firstCategoryExpandIcon.click(); + + const expectedCategoryRow = await expect(page).toMatchElement( + getTestClass("categorical-row") ); + const expectedLabelName = await getInnerText( + expectedCategoryRow, + "categorical-value" + ); + const expectedLabelCount = await getInnerText( + expectedCategoryRow, + "categorical-value-count" + ); + + await expandCategory(duplicateCategoryName); + + const expectedCategory = await expect(page).toMatchElement( + getTestClass("category") + ); + + const actualCategoryRow = await expect(expectedCategory).toMatchElement( + getTestClass("categorical-row") + ); + const actualLabelName = await getInnerText( + actualCategoryRow, + "categorical-value" + ); + const actualLabelCount = await getInnerText( + actualCategoryRow, + "categorical-value-count" + ); + + expect(actualLabelName).toBe(expectedLabelName); + expect(actualLabelCount).toBe(expectedLabelCount); + + async function getInnerText(element, className) { + return element.$eval(getTestClass(className), (node) => node?.innerText); + } }); test("assign cells to a label", async () => { - await actions.expandCategory(perTestCategoryName); - const lassoSelection = await actions.calcDragCoordinates( + await setup(config); + + await expandCategory(perTestCategoryName); + + const lassoSelection = await calcDragCoordinates( "layout-graph", data.categoryLabel.lasso["coordinates-as-percent"] ); - await actions.drag( - "layout-graph", - lassoSelection.start, - lassoSelection.end, - true - ); - await utils.waitByID("lasso-element", { visible: true }); - await utils.clickOn( - `${perTestCategoryName}:${perTestLabelName}:see-actions` - ); - await utils.clickOn( + + await drag("layout-graph", lassoSelection.start, lassoSelection.end, true); + await waitByID("lasso-element", { visible: true }); + await clickOn(`${perTestCategoryName}:${perTestLabelName}:see-actions`); + await clickOn( `${perTestCategoryName}:${perTestLabelName}:add-current-selection-to-this-label` ); - const result = await utils.waitByID( + + const result = await waitByID( `categorical-value-count-${perTestCategoryName}-${perTestLabelName}` ); + expect(await result.evaluate((node) => node.innerText)).toBe( data.categoryLabel.newCount.bySubsetConfig[config.withSubset] ); }); test("undo/redo category creation", async () => { + await setup(config); + const categoryName = `category-created-undo-${config.tag}`; + await assertCategoryDoesNotExist(categoryName); - await actions.createCategory(categoryName); + await createCategory(categoryName); await assertCategoryExists(categoryName); - await utils.clickOn("undo"); + await clickOn("undo"); await assertCategoryDoesNotExist(categoryName); - await utils.clickOn("redo"); + await clickOn("redo"); await assertCategoryExists(categoryName); }); test("undo/redo category deletion", async () => { + await setup(config); + const categoryName = `category-deleted-undo-${config.tag}`; - await actions.createCategory(categoryName); + + await createCategory(categoryName); await assertCategoryExists(categoryName); - await actions.deleteCategory(categoryName); + await deleteCategory(categoryName); await assertCategoryDoesNotExist(categoryName); - await utils.clickOn("undo"); + await clickOn("undo"); await assertCategoryExists(categoryName); - await utils.clickOn("redo"); + await clickOn("redo"); await assertCategoryDoesNotExist(categoryName); }); test("undo/redo category rename", async () => { + await setup(config); + const newCategoryName = `category-renamed-undo-${config.tag}`; + await assertCategoryDoesNotExist(newCategoryName); - await actions.renameCategory(perTestCategoryName, newCategoryName); + await renameCategory(perTestCategoryName, newCategoryName); await assertCategoryExists(newCategoryName); await assertCategoryDoesNotExist(perTestCategoryName); - await utils.clickOn("undo"); + await clickOn("undo"); await assertCategoryExists(perTestCategoryName); await assertCategoryDoesNotExist(newCategoryName); - await utils.clickOn("redo"); + await clickOn("redo"); await assertCategoryExists(newCategoryName); await assertCategoryDoesNotExist(perTestCategoryName); }); test("undo/redo label creation", async () => { + await setup(config); + const labelName = `label-created-undo-${config.tag}`; + await assertLabelDoesNotExist(perTestCategoryName, labelName); - await actions.createLabel(perTestCategoryName, labelName); + await createLabel(perTestCategoryName, labelName); await assertLabelExists(perTestCategoryName, labelName); - await utils.clickOn("undo"); + await clickOn("undo"); await assertLabelDoesNotExist(perTestCategoryName); - await utils.clickOn("redo"); + await clickOn("redo"); await assertLabelExists(perTestCategoryName, labelName); }); test("undo/redo label deletion", async () => { - await actions.deleteLabel(perTestCategoryName, perTestLabelName); + await setup(config); + + await deleteLabel(perTestCategoryName, perTestLabelName); await assertLabelDoesNotExist(perTestCategoryName); - await utils.clickOn("undo"); + await clickOn("undo"); await assertLabelExists(perTestCategoryName, perTestLabelName); - await utils.clickOn("redo"); + await clickOn("redo"); await assertLabelDoesNotExist(perTestCategoryName); }); test("undo/redo label rename", async () => { + await setup(config); + const newLabelName = `label-renamed-undo-${config.tag}`; + await assertLabelDoesNotExist(perTestCategoryName, newLabelName); - await actions.renameLabel( - perTestCategoryName, - perTestLabelName, - newLabelName - ); + await renameLabel(perTestCategoryName, perTestLabelName, newLabelName); await assertLabelExists(perTestCategoryName, newLabelName); await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName); - await utils.clickOn("undo"); + await clickOn("undo"); await assertLabelExists(perTestCategoryName, perTestLabelName); await assertLabelDoesNotExist(perTestCategoryName, newLabelName); - await utils.clickOn("redo"); + await clickOn("redo"); await assertLabelExists(perTestCategoryName, newLabelName); await assertLabelDoesNotExist(perTestCategoryName, perTestLabelName); }); + test("stacked bar graph renders", async () => { + await setup(config); + + await expandCategory(perTestCategoryName); + + await clickOn(`colorby-louvain`); + + const labels = await getAllByClass("categorical-row"); + + const result = await Promise.all( + labels.map((label) => { + return page.evaluate((element) => { + return element.outerHTML; + }, label); + }) + ); + + expect(result).toMatchSnapshot(); + }); + async function assertCategoryExists(categoryName) { - const handle = await utils.waitByID(`${categoryName}:category-label`); + const handle = await waitByID(`${categoryName}:category-label`); const result = await handle.evaluate((node) => node.getAttribute("aria-label") ); - expect(result).toBe(categoryName); - } - - async function assertCategoryDoesNotExist(categoryName) { - const result = await page.$( - `[data-testid='${categoryName}:category-label']` - ); - expect(result).toBeNull(); + return expect(result).toBe(categoryName); } async function assertLabelExists(categoryName, labelName) { - const category = await utils.waitByID(`${categoryName}:category-expand`); - expect(category).not.toBeNull(); - await actions.expandCategory(categoryName); - const previous = await utils.waitByID( + await expect(page).toMatchElement( + getTestId(`${categoryName}:category-expand`) + ); + + await expandCategory(categoryName); + + const previous = await waitByID( `categorical-value-${categoryName}-${labelName}` ); + expect( await previous.evaluate((node) => node.getAttribute("aria-label")) ).toBe(labelName); } async function assertLabelDoesNotExist(categoryName, labelName) { - await actions.expandCategory(categoryName); + await expandCategory(categoryName); const result = await page.$( `[data-testid='categorical-value-${categoryName}-${labelName}']` ); expect(result).toBeNull(); } - - async function deleteCategoryIfExists(categoryName) { - try { - const category = await page.waitForSelector( - `[data-testid='${categoryName}:category-expand']`, - { timeout: 200 } - ); - if (category !== null) return await actions.deleteCategory(categoryName); - } catch {} - return null; - } }); diff --git a/client/__tests__/e2e/e2eJestConfig.json b/client/__tests__/e2e/e2eJestConfig.json index 585d59f0..95035bf3 100644 --- a/client/__tests__/e2e/e2eJestConfig.json +++ b/client/__tests__/e2e/e2eJestConfig.json @@ -1,5 +1,10 @@ { + "testRunner": "jest-circus/runner", "preset": "jest-puppeteer", "testMatch": ["**/__tests__/**/?(*.)(spec|test).js?(x)"], - "setupFiles": ["../setupMissingGlobals.js"] + "setupFiles": ["../setupMissingGlobals.js"], + "setupFilesAfterEnv": ["expect-puppeteer", "./puppeteer.setup.js"], + "globalSetup": "jest-environment-puppeteer/setup", + "globalTeardown": "jest-environment-puppeteer/teardown", + "testEnvironment": "./screenshot_env.js" } diff --git a/client/__tests__/e2e/feature.test.js b/client/__tests__/e2e/feature.test.js deleted file mode 100644 index 9e7adc11..00000000 --- a/client/__tests__/e2e/feature.test.js +++ /dev/null @@ -1,122 +0,0 @@ -/* -NOT run in Travis CI - -UX tests using puppeteer to be run locally. - -To run locally, ensure you are running the client is running on port 3000. -Then run jest --verbose false --config __tests__/e2e/e2eJestConfig.json feature. - */ - -import puppeteer from "puppeteer"; -import { appUrlBase, DEBUG, DEV, DATASET } from "./config"; -import { puppeteerUtils, cellxgeneActions } from "./puppeteerUtils"; -import { datasets } from "./data"; - -let browser; -let page; -let utils; -let cxgActions; -let spy; -const browserViewport = { width: 1280, height: 960 }; -const data = datasets[DATASET].features; - -if (DEBUG) jest.setTimeout(100000); -if (DEV) jest.setTimeout(10000); - -beforeAll(async () => { - const browserParams = DEV - ? { headless: false, slowMo: 5 } - : DEBUG - ? { headless: false, slowMo: 100, devtools: true } - : {}; - browser = await puppeteer.launch(browserParams); - page = await browser.newPage(); - await page.setViewport(browserViewport); - if (DEV || DEBUG) { - page.on("console", (msg) => console.log(`PAGE LOG: ${msg.text()}`)); - } - page.on("pageerror", (err) => { - throw new Error(`Console error: ${err}`); - }); - utils = puppeteerUtils(page); - cxgActions = cellxgeneActions(page); -}); - -beforeEach(async () => { - await page.goto(appUrlBase); -}); - -afterAll(() => { - if (!DEBUG) { - browser.close(); - } -}); - -describe("zoom interaction", async () => { - // Skip this test since UI is to hide lasso path when switching modes - test.skip("lasso visible after switching modes to pan/zoom", async () => { - const lassoSelection = await cxgActions.calcDragCoordinates( - "layout-graph", - data.panzoom.lasso["coordinates-as-percent"] - ); - await cxgActions.drag( - "layout-graph", - lassoSelection.start, - lassoSelection.end, - true - ); - await utils.waitByID("lasso-element", { visible: true }); - await utils.clickOn("mode-pan-zoom"); - await utils.waitByID("lasso-element", { visible: true }); - }); - - test("pan zoom mode resets lasso selection", async () => { - const lassoSelection = await cxgActions.calcDragCoordinates( - "layout-graph", - data.panzoom.lasso["coordinates-as-percent"] - ); - await cxgActions.drag( - "layout-graph", - lassoSelection.start, - lassoSelection.end, - true - ); - await utils.waitByID("lasso-element", { visible: true }); - const initialCount = await cxgActions.cellSet(1); - expect(initialCount).toBe(data.panzoom.lasso.count); - await utils.clickOn("mode-pan-zoom"); - await utils.clickOn("mode-lasso"); - const modeSwitchCount = await cxgActions.cellSet(1); - expect(modeSwitchCount).toBe(initialCount); - }); - - test("lasso moves after pan", async () => { - const lassoSelection = await cxgActions.calcDragCoordinates( - "layout-graph", - data.panzoom.lasso["coordinates-as-percent"] - ); - await cxgActions.drag( - "layout-graph", - lassoSelection.start, - lassoSelection.end, - true - ); - await utils.waitByID("lasso-element", { visible: true }); - const initialCount = await cxgActions.cellSet(1); - expect(initialCount).toBe(data.panzoom.lasso.count); - await utils.clickOn("mode-pan-zoom"); - const panCoords = await cxgActions.calcDragCoordinates( - "layout-graph", - data.panzoom.lasso["coordinates-as-percent"] - ); - await cxgActions.drag( - "layout-graph", - panCoords.start, - panCoords.end, - false - ); - await utils.clickOn("mode-lasso"); - const panCount = await cxgActions.cellSet(2); - expect(panCount).toBe(initialCount); - }); -}); diff --git a/client/__tests__/e2e/puppeteer.setup.js b/client/__tests__/e2e/puppeteer.setup.js new file mode 100644 index 00000000..40453777 --- /dev/null +++ b/client/__tests__/e2e/puppeteer.setup.js @@ -0,0 +1,58 @@ +/** + * `client/jest-puppeteer.config.js` is for configuring Puppeteer's launch config options + * `client/__tests__/e2e/puppeteer.setup.js` is for configuring `jest`, `browser`, + * and `page` objects + */ + +import { setDefaultOptions } from "expect-puppeteer"; +import { isDebug, isDev } from "./config"; +import * as ENV_DEFAULT from "../../../environment.default.json"; + +// (thuang): This is the max time a test can take to run. +// Since when debugging, we run slowMo and !headless, this means +// a test can take more time to finish, so we don't want +// jest to shut off the test too soon +jest.setTimeout(2 * 60 * 1000); +setDefaultOptions({ timeout: 20 * 1000 }); + +jest.retryTimes(ENV_DEFAULT.RETRY_ATTEMPTS); + +(async () => { + const userAgent = await browser.userAgent(); + await page.setUserAgent(`${userAgent}bot`); + + await page._client.send("Animation.setPlaybackRate", { playbackRate: 12 }); + + page.on("pageerror", (err) => { + throw new Error(`Console error: ${err}`); + }); + + page.on("error", (err) => { + throw new Error(`Console error: ${err}`); + }); + + page.on("console", async (msg) => { + if (isDev || isDebug) { + // If there is a console.error but an error is not thrown, this will ensure the test fails + console.log(`PAGE LOG: ${msg.text()}`); + if (msg.type() === "error") { + // TODO: chromium does not currently support the CSP directive on the + // line below, so we swallow this error. Remove this when the test + // suite uses a browser version that supports this directive. + if ( + msg.text() === + "Unrecognized Content-Security-Policy directive 'require-trusted-types-for'.\n" + ) { + return; + } + const errorMsgText = await Promise.all( + // TODO can we do this without internal properties? + msg.args().map((arg) => arg._remoteObject.description) + ); + throw new Error(`Console error: ${errorMsgText}`); + } + } + }); +})().catch((error) => { + console.error("puppeteer.setup.js error", error); +}); diff --git a/client/__tests__/e2e/puppeteerUtils.js b/client/__tests__/e2e/puppeteerUtils.js index d9dc82c6..df915f1f 100644 --- a/client/__tests__/e2e/puppeteerUtils.js +++ b/client/__tests__/e2e/puppeteerUtils.js @@ -1,69 +1,131 @@ -export const puppeteerUtils = (page) => ({ - async waitByID(testId, props = {}) { - return page.waitForSelector(`[data-testid='${testId}']`, props); - }, +/* eslint-disable no-await-in-loop -- await in loop is needed to emulate sequential user actions */ +export function getTestId(id) { + return `[data-testid='${id}']`; +} - async waitByClass(testClass, props = {}) { - return page.waitForSelector(`[data-testclass='${testClass}']`, props); - }, +export function getTestClass(className) { + return `[data-testclass='${className}']`; +} - async waitForAllByIds(testIds) { - await Promise.all( - testIds.map((testId) => page.waitForSelector(`[data-testid='${testId}']`)) - ); - }, +export async function waitByID(testId, props = {}) { + return page.waitForSelector(getTestId(testId), props); +} - async getAllByClass(testClass) { - return page.$$eval(`[data-testclass=${testClass}]`, (eles) => - eles.map((ele) => ele.dataset.testid) - ); - }, +export async function waitByClass(testClass, props = {}) { + await page.waitForSelector(`[data-testclass='${testClass}']`, props); +} - async typeInto(testId, text) { - // blueprint's typeahead is treating typing weird, clicking & waiting first solves this - // only works for text without special characters - await this.waitByID(testId); - const selector = `[data-testid='${testId}']`; - // type ahead can be annoying if you don't pause before you type - await page.click(selector); - await page.waitFor(200); - await page.type(selector, text); - }, +export async function waitForAllByIds(testIds) { + await Promise.all( + testIds.map((testId) => page.waitForSelector(getTestId(testId))) + ); +} - async clearInputAndTypeInto(testId, text) { - await this.waitByID(testId); - const selector = `[data-testid='${testId}']`; - // only works for text without special characters - // type ahead can be annoying if you don't pause before you type - await page.click(selector); - await page.waitFor(200); - // select all - await page.click(selector, { clickCount: 3 }); - await page.keyboard.press("Backspace"); - await page.type(selector, text); - }, +export async function getAllByClass(testClass) { + return page.$$(`[data-testclass=${testClass}]`); +} - async clickOn(testid, options = {}) { - await this.waitByID(testid, options); - const click = await page.click(`[data-testid='${testid}']`); - await page.waitFor(50); - return click; - }, +export async function typeInto(testId, text) { + // blueprint's typeahead is treating typing weird, clicking & waiting first solves this + // only works for text without special characters + await waitByID(testId); + const selector = getTestId(testId); + // type ahead can be annoying if you don't pause before you type + await page.click(selector); + await page.waitFor(200); + await page.type(selector, text); +} - async getOneElementInnerHTML(selector, options = {}) { - await page.waitForSelector(selector, options); - return page.$eval(selector, (el) => el.innerHTML); - }, +export async function clearInputAndTypeInto(testId, text) { + await waitByID(testId); + const selector = getTestId(testId); + // only works for text without special characters + // type ahead can be annoying if you don't pause before you type + await page.click(selector); + await page.waitFor(200); + // select all + await page.click(selector, { clickCount: 3 }); + await page.keyboard.press("Backspace"); + await page.type(selector, text); +} - async getOneElementInnerText(selector) { - await page.waitForSelector(selector); - return page.$eval(selector, (el) => el.innerText); - }, +export async function clickOn(testId, options = {}) { + await expect(page).toClick(getTestId(testId), options); +} - async getElementCoordinates(testid) { - return page.$eval(`[data-testid='${testid}']`, (elem) => { - const { left, top } = elem.getBoundingClientRect(); - return [left, top]; - }); - }, -}); +/** + * (thuang): There are times when Puppeteer clicks on a button and the page doesn't respond. + * So I added clickOnUntil() to retry clicking until a given condition is met. + */ +export async function clickOnUntil(testId, assert) { + const MAX_RETRY = 10; + const WAIT_FOR_MS = 200; + + let retry = 0; + + while (retry < MAX_RETRY) { + try { + await clickOn(testId); + await assert(); + + break; + } catch (error) { + retry += 1; + + await page.waitFor(WAIT_FOR_MS); + } + } + + if (retry === MAX_RETRY) { + throw Error("clickOnUntil() assertion failed!"); + } +} + +export async function getOneElementInnerHTML(selector, options = {}) { + await page.waitForSelector(selector, options); + + return page.$eval(selector, (el) => el.innerHTML); +} + +export async function getOneElementInnerText(selector) { + expect(page).toMatchElement(selector); + + return page.$eval(selector, (el) => el.innerText); +} + +export async function getElementCoordinates(testId) { + return page.$eval(getTestId(testId), (elem) => { + const { left, top } = elem.getBoundingClientRect(); + return [left, top]; + }); +} + +async function clickTermsOfService() { + if (!(await isElementPresent(getTestId("tos-cookies-accept")))) return; + + await clickOn("tos-cookies-accept"); +} + +async function nameNewAnnotation() { + if (await isElementPresent(getTestId("annotation-dialog"))) { + await typeInto("new-annotation-name", "ignoreE2E"); + await clickOn("submit-annotation"); + + // wait for the page to load + await waitByClass("autosave-complete"); + } +} + +export async function goToPage(url) { + await page.goto(url, { + waitUntil: "networkidle0", + }); + + await nameNewAnnotation(); + await clickTermsOfService(); +} + +export async function isElementPresent(selector, options) { + return Boolean(await page.$(selector, options)); +} +/* eslint-enable no-await-in-loop -- await in loop is needed to emulate sequential user actions */ diff --git a/client/__tests__/e2e/screenshot_env.js b/client/__tests__/e2e/screenshot_env.js new file mode 100644 index 00000000..4701d8ca --- /dev/null +++ b/client/__tests__/e2e/screenshot_env.js @@ -0,0 +1,36 @@ +const PuppeteerEnvironment = require("jest-environment-puppeteer"); +require("jest-circus"); +const ENV_DEFAULT = require("../../../environment.default.json"); + +const takeScreenshot = require("./takeScreenshot"); + +class ScreenshotEnvironment extends PuppeteerEnvironment { + async handleTestEvent(event, state) { + if (["test_start", "test_done"].includes(event.name)) { + console.log("------------------event name:\n", event.name); + console.log("~~~~ Current test errors\n", new Date(), event.test.errors); + console.log("~~~~ Current test\n", new Date(), event.test); + } + + if (event.name === "error") { + console.log("error event:", JSON.stringify(event)); + } + + if (event.name === "test_fn_failure" || event.name === "hook_failure") { + console.log("------------------event name:\n", event.name); + console.log(">>>> Current state\n", new Date(), state); + console.log("===> Failure event\n", new Date(), event); + + // (thuang): We only want to take screenshot on the last try + if ( + state.currentlyRunningTest.invocations <= ENV_DEFAULT.RETRY_ATTEMPTS + ) { + return; + } + + await takeScreenshot(state.currentlyRunningTest.name, this.global.page); + } + } +} + +module.exports = ScreenshotEnvironment; diff --git a/client/__tests__/e2e/takeScreenshot.js b/client/__tests__/e2e/takeScreenshot.js new file mode 100644 index 00000000..9800efab --- /dev/null +++ b/client/__tests__/e2e/takeScreenshot.js @@ -0,0 +1,17 @@ +function toFilename(name) { + return name.replace(/[^a-z0-9.-]+/gi, "-"); +} + +async function takeScreenshot(currentTestName, page) { + const testName = toFilename(currentTestName); + + // Take a screenshot at the point of failure + const date = new Date().toISOString(); + const screenshotName = `${date}-${testName}.png`; + + await page.screenshot({ + path: `./__tests__/screenshots/ignoreE2E-screenshot-${screenshotName}`, + }); +} + +module.exports = takeScreenshot; diff --git a/client/__tests__/e2e/testBrowser.js b/client/__tests__/e2e/testBrowser.js deleted file mode 100644 index 2931c17f..00000000 --- a/client/__tests__/e2e/testBrowser.js +++ /dev/null @@ -1,58 +0,0 @@ -import puppeteer from "puppeteer"; -import { DEBUG, DEV } from "./config"; -import { puppeteerUtils } from "./puppeteerUtils"; -import { cellxgeneActions } from "./cellxgeneActions"; - -export async function setupTestBrowser() { - const browserViewport = { width: 1280, height: 960 }; - const browserParams = DEV - ? { - headless: false, - slowMo: 5, - args: [ - `--window-size=${browserViewport.width},${browserViewport.height}`, - ], - } - : DEBUG - ? { - headless: false, - slowMo: 100, - devtools: true, - args: [ - `--window-size=${browserViewport.width + 560},${ - browserViewport.height - }`, - ], - } - : { - args: [ - `--window-size=${browserViewport.width},${browserViewport.height}`, - ], - }; - const browser = await puppeteer.launch(browserParams); - const page = await browser.pages().then((pages) => pages[0]); - await page.setViewport(browserViewport); - if (DEV || DEBUG) { - page.on("console", async (msg) => { - // If there is a console.error but an error is not thrown, this will ensure the test fails - if (msg.type() === "error") { - // TODO: chromium does not currently support the CSP directive on the - // line below, so we swallow this error. Remove this when the test - // suite uses a browser version that supports this directive. - if (msg.text() === "Unrecognized Content-Security-Policy directive 'require-trusted-types-for'.\n") return; - const errorMsgText = await Promise.all( - // TODO can we do this without internal properties? - msg.args().map((arg) => arg._remoteObject.description) - ); - throw new Error(`Console error: ${errorMsgText}`); - } - console.log(`PAGE LOG: ${msg.text()}`); - }); - } - page.on("pageerror", (err) => { - throw new Error(`Console error: ${err}`); - }); - const utils = puppeteerUtils(page); - const cxgActions = cellxgeneActions(page, utils); - return [browser, page, utils, cxgActions]; -} diff --git a/client/__tests__/screenshots/.gitignore b/client/__tests__/screenshots/.gitignore new file mode 100644 index 00000000..5e7d2734 --- /dev/null +++ b/client/__tests__/screenshots/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/client/__tests__/util/annoMatrix/annoMatrix.test.js b/client/__tests__/util/annoMatrix/annoMatrix.test.js new file mode 100644 index 00000000..17513d19 --- /dev/null +++ b/client/__tests__/util/annoMatrix/annoMatrix.test.js @@ -0,0 +1,316 @@ +// these TWO statements MUST be first in the file, before any other imports +import { enableFetchMocks } from "jest-fetch-mock"; +import * as serverMocks from "./serverMocks"; +// OK, continue on! + +import { + AnnoMatrixLoader, + clip, + isubset, + isubsetMask, +} from "../../../src/annoMatrix"; +import { Dataframe } from "../../../src/util/dataframe"; + +enableFetchMocks(); + +describe("AnnoMatrix", () => { + let annoMatrix; + + beforeEach(async () => { + fetch.resetMocks(); // reset all fetch mocking state + annoMatrix = new AnnoMatrixLoader( + serverMocks.baseDataURL, + serverMocks.schema.schema + ); + }); + + describe("basics", () => { + test("annomatrix static checks", () => { + expect(annoMatrix).toBeDefined(); + expect(annoMatrix.schema).toMatchObject(serverMocks.schema.schema); + expect(annoMatrix.nObs).toEqual(serverMocks.schema.schema.dataframe.nObs); + expect(annoMatrix.nVar).toEqual(serverMocks.schema.schema.dataframe.nVar); + expect(annoMatrix.isView).toBeFalsy(); + expect(annoMatrix.viewOf).toBeUndefined(); + expect(annoMatrix.rowIndex).toBeDefined(); + }); + + test("simple single column fetch", async () => { + fetch.once(serverMocks.annotationsObs(["name_0"])); + + const df = await annoMatrix.fetch("obs", "name_0"); + expect(df).toBeInstanceOf(Dataframe); + expect(df.colIndex.labels()).toEqual(["name_0"]); + expect(df.dims).toEqual([annoMatrix.nObs, 1]); + }); + + test("simple multi column fetch", async () => { + fetch + .once(serverMocks.annotationsObs(["name_0"])) + .once(serverMocks.annotationsObs(["n_genes"])); + + await expect( + annoMatrix.fetch("obs", ["name_0", "n_genes"]) + ).resolves.toBeInstanceOf(Dataframe); + }); + + describe("fetch from field", () => { + const getLastTwo = async (field) => { + const columnNames = annoMatrix.getMatrixColumns(field).slice(-2); + fetch.mockResponses(...columnNames.map(() => serverMocks.responder)); + await expect( + annoMatrix.fetch(field, columnNames) + ).resolves.toBeInstanceOf(Dataframe); + }; + + test("obs", async () => getLastTwo("obs")); + test("var", async () => getLastTwo("var")); + test("emb", async () => getLastTwo("emb")); + }); + + test("fetch - test all query forms", async () => { + // single string is a column name + fetch.once(serverMocks.annotationsObs(["n_genes"])); + await expect(annoMatrix.fetch("obs", "n_genes")).resolves.toBeInstanceOf( + Dataframe + ); + + // array of column names, expecting n_genes to be cached. + fetch.once(serverMocks.annotationsObs(["percent_mito"])); + await expect( + annoMatrix.fetch("obs", ["n_genes", "percent_mito"]) + ).resolves.toBeInstanceOf(Dataframe); + + // more complex value filter query, enumerated + fetch.once(serverMocks.responder); + await expect( + annoMatrix.fetch("X", { + field: "var", + column: annoMatrix.schema.annotations.var.index, + value: "TYMP", + }) + ).resolves.toBeInstanceOf(Dataframe); + + // more complex value filter query, range + const varIndex = annoMatrix.schema.annotations.var.index; + fetch + .once( + serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "SUMO3"]]) + ) + .once( + serverMocks.withExpected("/data/var", [[`var:${varIndex}`, "TYMP"]]) + ); + await expect( + annoMatrix.fetch("X", [ + { + field: "var", + column: varIndex, + value: "SUMO3", + }, + { + field: "var", + column: varIndex, + value: "TYMP", + }, + ]) + ).resolves.toBeInstanceOf(Dataframe); + // XXX inspect the wherecache? + }); + + test("push and pop views", async () => { + const am1 = clip(annoMatrix, 0.1, 0.9); + expect(am1.viewOf).toBe(annoMatrix); + expect(am1.nObs).toEqual(annoMatrix.nObs); + expect(am1.nVar).toEqual(annoMatrix.nVar); + expect(am1.rowIndex).toBe(annoMatrix.rowIndex); + + const am2 = clip(annoMatrix, 0.1, 0.9); + expect(am2.viewOf).toBe(annoMatrix); + expect(am2).not.toBe(am1); + expect(am2.rowIndex).toBe(annoMatrix.rowIndex); + }); + + test("schema accessors", () => { + expect(annoMatrix.getMatrixFields()).toEqual( + expect.arrayContaining(["X", "obs", "emb", "var"]) + ); + expect(annoMatrix.getMatrixColumns("obs")).toEqual( + expect.arrayContaining(["name_0", "n_genes", "louvain"]) + ); + expect(annoMatrix.getColumnSchema("emb", "umap")).toEqual({ + name: "umap", + dims: ["umap_0", "umap_1"], + type: "float32", + }); + expect(annoMatrix.getColumnDimensions("emb", "umap")).toEqual([ + "umap_0", + "umap_1", + ]); + }); + + /* + test the mask & label access to subset via isubset and isubsetMask + */ + test("isubset", async () => { + const rowList = [0, 10]; + const rowMask = new Uint8Array(annoMatrix.nObs); + for (let i = 0; i < rowList.length; i += 1) { + rowMask[rowList[i]] = 1; + } + + const am1 = isubset(annoMatrix, rowList); + const am2 = isubsetMask(annoMatrix, rowMask); + expect(am1).not.toBe(am2); + expect(am1.nObs).toEqual(2); + expect(am1.nObs).toEqual(am2.nObs); + expect(am1.nVar).toEqual(am2.nVar); + + fetch + .once(serverMocks.annotationsObs(["n_genes"])) + .once(serverMocks.annotationsObs(["n_genes"])); + const ng1 = await am1.fetch("obs", "n_genes"); + const ng2 = await am2.fetch("obs", "n_genes"); + expect(ng1).toHaveLength(ng2.length); + expect(ng1.colIndex.labels()).toEqual(ng2.colIndex.labels()); + expect(ng1.col("n_genes").asArray()).toEqual( + ng2.col("n_genes").asArray() + ); + }); + }); + + describe("add/drop column", () => { + async function addDrop(base) { + expect(base.getMatrixColumns("obs")).not.toContain("foo"); + fetch.mockRejectOnce(new Error("unknown column name")); + await expect(base.fetch("obs", "foo")).rejects.toThrow( + "unknown column name" + ); + + /* add */ + const am1 = base.addObsColumn( + { name: "foo", type: "float32", writable: true }, + Float32Array, + 0 + ); + expect(base.getMatrixColumns("obs")).not.toContain("foo"); + expect(am1.getMatrixColumns("obs")).toContain("foo"); + const foo = await am1.fetch("obs", "foo"); + expect(foo).toBeDefined(); + expect(foo).toBeInstanceOf(Dataframe); + expect(foo).toHaveLength(am1.nObs); + expect(foo.col("foo").asArray()).toEqual( + new Float32Array(am1.nObs).fill(0) + ); + + /* drop */ + const am2 = am1.dropObsColumn("foo"); + expect(base.getMatrixColumns("obs")).not.toContain("foo"); + expect(am2.getMatrixColumns("obs")).not.toContain("foo"); + fetch.mockRejectOnce(new Error("unknown column name")); + await expect(am2.fetch("obs", "foo")).rejects.toThrow( + "unknown column name" + ); + } + + test("add/drop column, without view", async () => { + await addDrop(annoMatrix); + }); + + test("add/drop column, with view", async () => { + const am1 = clip(annoMatrix, 0.1, 0.9); + await addDrop(am1); + + const am2 = isubset(am1, [0, 1, 2, 20, 30, 400]); + await addDrop(am2); + + const am3 = isubset(annoMatrix, [10, 0, 7, 3]); + await addDrop(am3); + + const am4 = clip(am3, 0, 1); + await addDrop(am4); + + fetch.mockResponse(serverMocks.responder); + + await am1.fetch("obs", am1.getMatrixColumns("obs")); + await am2.fetch("obs", am2.getMatrixColumns("obs")); + await am3.fetch("obs", am3.getMatrixColumns("obs")); + await am4.fetch("obs", am4.getMatrixColumns("obs")); + + fetch.resetMocks(); + + await addDrop(am1); + await addDrop(am2); + await addDrop(am3); + await addDrop(am4); + }); + }); + + describe("setObsColumnValues", () => { + async function addSetDrop(base) { + /* add column */ + let am = base.addObsColumn( + { + name: "test", + type: "categorical", + categories: ["unassigned", "red", "green"], + writable: true, + }, + Array, + "unassigned" + ); + + const testVal = await am.fetch("obs", "test"); + expect(testVal.col("test").asArray()).toEqual( + new Array(am.nObs).fill("unassigned") + ); + + /* set values in column */ + const whichRows = [1, 2, 10]; + const am1 = await am.setObsColumnValues("test", whichRows, "yo"); + const testVal1 = await am1.fetch("obs", "test"); + const expt = new Array(am1.nObs).fill("unassigned"); + for (let i = 0; i < whichRows.length; i += 1) { + const offset = am1.rowIndex.getOffset(whichRows[i]); + expt[offset] = "yo"; + } + expect(testVal1).not.toBe(testVal); + expect(testVal1.col("test").asArray()).toEqual(expt); + expect(am1.getColumnSchema("obs", "test").type).toBe("categorical"); + expect(am1.getColumnSchema("obs", "test").categories).toEqual( + expect.arrayContaining(["unassigned", "red", "green", "yo"]) + ); + + /* drop column */ + fetch.mockRejectOnce(new Error("unknown column name")); + am = am1.dropObsColumn("test"); + await expect(am.fetch("obs", "test")).rejects.toThrow( + "unknown column name" + ); + } + + test("set, without a view", async () => { + await addSetDrop(annoMatrix); + }); + + test("set, with a view", async () => { + const am1 = clip(annoMatrix, 0.1, 0.9); + await addSetDrop(am1); + + const am2 = isubset(am1, [0, 1, 2, 10, 20, 30, 400]); + await addSetDrop(am2); + + const am3 = isubset(annoMatrix, [10, 1, 0, 30, 2]); + await addSetDrop(am3); + + fetch.mockResponse(serverMocks.responder); + + await am1.fetch("obs", am1.getMatrixColumns("obs")); + await am2.fetch("obs", am2.getMatrixColumns("obs")); + await am3.fetch("obs", am3.getMatrixColumns("obs")); + + await addSetDrop(am1); + await addSetDrop(am2); + await addSetDrop(am3); + }); + }); +}); diff --git a/client/__tests__/util/annoMatrix/crossfilter.test.js b/client/__tests__/util/annoMatrix/crossfilter.test.js new file mode 100644 index 00000000..8ee0758b --- /dev/null +++ b/client/__tests__/util/annoMatrix/crossfilter.test.js @@ -0,0 +1,688 @@ +// these TWO statements MUST be first in the file, before any other imports +import { enableFetchMocks } from "jest-fetch-mock"; +import * as serverMocks from "./serverMocks"; +// OK, continue on! + +import obsLouvain from "./louvain.json"; +import obsNGenes from "./n_genes.json"; +import embUmap from "./umap.json"; + +import { + AnnoMatrixLoader, + AnnoMatrixObsCrossfilter, + isubsetMask, +} from "../../../src/annoMatrix"; +import { rangeFill } from "../../../src/util/range"; + +enableFetchMocks(); + +describe("AnnoMatrixCrossfilter", () => { + let annoMatrix; + let crossfilter; + + beforeEach(async () => { + fetch.resetMocks(); // reset all fetch mocking state + annoMatrix = new AnnoMatrixLoader( + serverMocks.baseDataURL, + serverMocks.schema.schema + ); + crossfilter = new AnnoMatrixObsCrossfilter(annoMatrix); + }); + + test("initial state of crossfilter", () => { + const { nObs } = annoMatrix; + + expect(crossfilter).toBeDefined(); + expect(crossfilter.size()).toEqual(nObs); + expect(crossfilter.annoMatrix).toBe(annoMatrix); + + /* by default, everything should be selected, even if no data in cache */ + expect(crossfilter.countSelected()).toEqual(nObs); + expect(crossfilter.allSelectedLabels()).toEqual( + rangeFill(new Int32Array(nObs)) + ); + expect(crossfilter.allSelectedMask()).toEqual(new Uint8Array(nObs).fill(1)); + expect(crossfilter.fillByIsSelected(new Uint8Array(nObs), 2, 1)).toEqual( + new Uint8Array(nObs).fill(2) + ); + }); + + describe("select", () => { + /* + test the selection state via crossfilter proxy + */ + + test("select loads index", async () => { + /* + Select should transparently load/create dimension index. + + Internal dimension names are field/col:col:col..., eg, + + obs:louvain + emb:umap_0:umap_1 + + */ + expect(crossfilter.obsCrossfilter.dimensionNames()).toEqual([]); + expect( + crossfilter.obsCrossfilter.hasDimension("obs/louvain") + ).toBeFalsy(); + + fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain])); + let newCrossfilter = await crossfilter.select("obs", "louvain", { + mode: "none", + }); + + expect(newCrossfilter.countSelected()).toEqual(0); + expect( + newCrossfilter.obsCrossfilter.hasDimension("obs/louvain") + ).toBeTruthy(); + expect(fetch.mock.calls).toHaveLength(1); + + newCrossfilter = await crossfilter.select("obs", "louvain", { + mode: "all", + }); + expect(newCrossfilter.countSelected()).toEqual(annoMatrix.nObs); + }); + + test("simple column select", async () => { + let xfltr; + + fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain])); + xfltr = await crossfilter.select("obs", "louvain", { + mode: "exact", + values: ["NK cells", "B cells"], + }); + + expect(xfltr).toBeDefined(); + expect(xfltr.countSelected()).toEqual(496); + expect(xfltr.allSelectedMask()).toEqual( + Uint8Array.from( + obsLouvain.map((val) => + val === "NK cells" || val === "B cells" ? 1 : 0 + ) + ) + ); + expect(xfltr.allSelectedLabels()).toEqual( + Int32Array.from( + obsLouvain.reduce((acc, val, idx) => { + if (val === "NK cells" || val === "B cells") acc.push(idx); + return acc; + }, []) + ) + ); + expect( + xfltr.fillByIsSelected(new Uint8Array(annoMatrix.nObs), 3, 1) + ).toEqual( + Uint8Array.from( + obsLouvain.map((val) => + val === "NK cells" || val === "B cells" ? 3 : 1 + ) + ) + ); + + const df = await annoMatrix.fetch("obs", "louvain"); + const values = df.col("louvain").asArray(); + const selected = xfltr.allSelectedMask(); + values.every( + (val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx] + ); + + fetch.once( + serverMocks.dataframeResponse(["n_genes"], [new Int32Array(obsNGenes)]) + ); + xfltr = await xfltr.select("obs", "n_genes", { + mode: "range", + lo: 0, + hi: 500, + inclusive: false, + }); + expect(xfltr.countSelected()).toEqual(33); + expect(xfltr.allSelectedLabels()).toEqual( + Int32Array.from( + obsNGenes.reduce((acc, val, idx) => { + const louvain = obsLouvain[idx]; + if ( + val >= 0 && + val < 500 && + (louvain === "NK cells" || louvain === "B cells") + ) + acc.push(idx); + return acc; + }, []) + ) + ); + + xfltr = await xfltr.selectAll(); + expect(xfltr.countSelected()).toEqual(annoMatrix.nObs); + }); + + test("join column select", async () => { + const varIndex = annoMatrix.schema.annotations.var.index; + + const { nObs } = annoMatrix.schema.dataframe; + fetch.once( + serverMocks.dataframeResponse( + ["TEST"], + [rangeFill(new Float32Array(nObs), 0, 0.1)] + ) + ); + + const xfltr = await crossfilter.select( + "X", + { + field: "var", + column: varIndex, + value: "TYMP", + }, + { + mode: "range", + lo: 0, + hi: 50, + inclusive: true, + } + ); + + expect(xfltr).toBeDefined(); + expect(xfltr.countSelected()).toEqual(501); + + const df = await annoMatrix.fetch("X", { + field: "var", + column: varIndex, + value: "TYMP", + }); + const values = df.icol(0).asArray(); + const selected = xfltr.allSelectedMask(); + values.every((val, idx) => !(val >= 0 && val <= 50) !== !selected[idx]); + expect(selected.reduce((acc, val) => (val ? acc + 1 : acc), 0)).toEqual( + xfltr.countSelected() + ); + }); + + test("spatial column select", async () => { + fetch.once( + serverMocks.dataframeResponse( + ["umap_0", "umap_1"], + [Float32Array.from(embUmap[0]), Float32Array.from(embUmap[1])] + ) + ); + const xfltr = await crossfilter.select("emb", "umap", { + mode: "within-rect", + minX: 0, + minY: 0, + maxX: 0.5, + maxY: 0.5, + }); + expect(xfltr.countSelected()).toEqual(16); + }); + + test("select on subset", async () => { + const mask = new Uint8Array(annoMatrix.nObs).fill(0); + for (let i = 0; i < mask.length; i += 2) { + mask[i] = true; + } + const annoMatrixSubset = isubsetMask(annoMatrix, mask); + expect(annoMatrixSubset.nObs).toEqual(Math.floor(annoMatrix.nObs / 2)); + + let xfltr = new AnnoMatrixObsCrossfilter(annoMatrixSubset); + expect(xfltr.countSelected()).toEqual(annoMatrixSubset.nObs); + + fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain])); + xfltr = await xfltr.select("obs", "louvain", { + mode: "exact", + values: ["NK cells", "B cells"], + }); + + expect(xfltr).toBeDefined(); + expect(xfltr.countSelected()).toEqual(240); + + const df = await annoMatrixSubset.fetch("obs", "louvain"); + const values = df.col("louvain").asArray(); + const selected = xfltr.allSelectedMask(); + values.every( + (val, idx) => !["NK cells", "B cells"].includes(val) !== !selected[idx] + ); + }); + + test("select catches errors", async () => { + await expect(crossfilter.select("NADA", "foo")).rejects.toThrow( + "Unknown field name" + ); + + await expect(crossfilter.select("var", "foo")).rejects.toThrow( + "unable to obsSelect upon the var dimension" + ); + + fetch.mockRejectOnce(new Error("unknown column name")); + await expect(crossfilter.select("obs", "foo")).rejects.toThrow( + "unknown column name" + ); + }); + }); + + describe("mutate matrix", () => { + /* + test the matrix mutators via crossfilter proxy + */ + async function helperAddTestCol(cf, colName, colSchema = null) { + expect( + cf.annoMatrix.getMatrixColumns("obs").includes(colName) + ).toBeFalsy(); + + if (colSchema === null) { + colSchema = { + name: colName, + type: "categorical", + categories: ["toasty"], + }; + } + colSchema.name = colName; + const initValue = colSchema.categories[0]; + const xfltr = cf.addObsColumn(colSchema, Array, initValue); + expect( + xfltr.annoMatrix.schema.annotations.obs.columns.filter( + (v) => v.name === colName + ) + ).toHaveLength(1); + const df = await xfltr.annoMatrix.fetch("obs", colName); + expect(df.hasCol(colName)).toBeTruthy(); + return xfltr; + } + + test("addObsColumn", async () => { + expect(crossfilter.countSelected()).toBe(annoMatrix.nObs); + expect( + crossfilter.annoMatrix.getMatrixColumns("obs").includes("foo") + ).toBeFalsy(); + const xfltr = crossfilter.addObsColumn( + { name: "foo", type: "categorical", categories: ["A"] }, + Array, + "A" + ); + + // check schema updates correctly. + expect(xfltr.countSelected()).toBe(annoMatrix.nObs); + expect( + xfltr.annoMatrix.getMatrixColumns("obs").includes("foo") + ).toBeTruthy(); + expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toMatchObject({ + name: "foo", + type: "categorical", + }); + expect( + xfltr.annoMatrix.schema.annotations.obs.columns.filter( + (v) => v.name === "foo" + ) + ).toHaveLength(1); + + // check data update. + const df = await xfltr.annoMatrix.fetch("obs", "foo"); + expect( + df + .col("foo") + .asArray() + .every((v) => v === "A") + ).toBeTruthy(); + + // check that we catch dups + expect(() => + xfltr.addObsColumn( + { name: "foo", type: "categorical" }, + Array, + "toasty" + ) + ).toThrow("column already exists"); + expect(() => + xfltr.addObsColumn( + { name: "louvain", type: "categorical" }, + Array, + "toasty" + ) + ).toThrow("column already exists"); + }); + + test("dropObsColumn", async () => { + let xfltr; + + /* check that we catch attempt to drop readonly dimension */ + expect(() => crossfilter.dropObsColumn("louvain")).toThrow( + "Unknown or readonly obs column" + ); + /* non-existent column */ + expect(() => crossfilter.dropObsColumn("does-not-exist")).toThrow( + "Unknown or readonly obs column" + ); + + // add a column, then drop it. + xfltr = await helperAddTestCol(crossfilter, "foo"); + xfltr = xfltr.dropObsColumn("foo"); + expect( + xfltr.annoMatrix.schema.annotations.obs.columns.filter( + (v) => v.name === "foo" + ) + ).toHaveLength(0); + expect(xfltr.annoMatrix.schema.annotations.obsByName.foo).toBeUndefined(); + fetch.mockRejectOnce(new Error("unknown column name")); + await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow( + "unknown column name" + ); + + // now same, but ensure we have built an index before doing the drop + xfltr = await helperAddTestCol(crossfilter, "bar"); + xfltr = await xfltr.select("obs", "bar", { + mode: "exact", + values: "whatever", + }); + xfltr = xfltr.dropObsColumn("bar"); + + fetch.mockRejectOnce(new Error("unknown column name")); + await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow( + "unknown column name" + ); + }); + + test("renameObsColumn", async () => { + let xfltr; + + /* catch attempts to rename non-existent or readonly columns */ + expect(() => + crossfilter.renameObsColumn("does-not-exist", "foo") + ).toThrow("Unknown or readonly obs column"); + expect(() => crossfilter.renameObsColumn("louvain", "foo")).toThrow( + "Unknown or readonly obs column" + ); + + // add a column, then rename it. + xfltr = await helperAddTestCol(crossfilter, "foo"); + xfltr = xfltr.renameObsColumn("foo", "bar"); + expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toBeUndefined(); + expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({ + name: "bar", + type: "categorical", + }); + + fetch.mockRejectOnce(new Error("unknown column name")); + await expect(xfltr.annoMatrix.fetch("obs", "foo")).rejects.toThrow( + "unknown column name" + ); + const df = await xfltr.annoMatrix.fetch("obs", "bar"); + expect(df.hasCol("bar")).toBeTruthy(); + + // now same, but ensure we have built an index before doing the rename + xfltr = await helperAddTestCol(crossfilter, "bar"); + xfltr = await xfltr.select("obs", "bar", { + mode: "exact", + values: "whatever", + }); + xfltr = xfltr.renameObsColumn("bar", "xyz"); + + fetch.mockRejectOnce(new Error("unknown column name")); + await expect(xfltr.select("obs", "bar", { mode: "all" })).rejects.toThrow( + "unknown column name" + ); + await expect( + xfltr.select("obs", "xyz", { mode: "none" }) + ).resolves.toBeInstanceOf(AnnoMatrixObsCrossfilter); + }); + + test("addObsAnnoCategory", async () => { + let xfltr; + + // catch unknown or readonly columns + expect(() => crossfilter.addObsAnnoCategory("louvain", "mumble")).toThrow( + "Unknown or readonly obs column" + ); + expect(() => + crossfilter.addObsAnnoCategory("undefined-name", "mumble") + ).toThrow("Unknown or readonly obs column"); + + // add a column and then add category to it + xfltr = await helperAddTestCol(crossfilter, "foo", { + name: "foo", + type: "categorical", + categories: ["unassigned"], + }); + xfltr = xfltr.addObsAnnoCategory("foo", "a-new-label"); + expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ + name: "foo", + type: "categorical", + categories: expect.arrayContaining(["a-new-label", "unassigned"]), + }); + + // do it again, dup; should throw + expect(() => xfltr.addObsAnnoCategory("foo", "a-new-label")).toThrow( + "category already exists" + ); + + // now same, but ensure we have built an index before doing the operation + xfltr = await helperAddTestCol(crossfilter, "bar", { + name: "bar", + type: "categorical", + categories: ["unassigned"], + }); + xfltr = await xfltr.select("obs", "bar", { + mode: "exact", + values: "something", + }); + xfltr = xfltr.addObsAnnoCategory("bar", "a-new-label"); + expect(xfltr.annoMatrix.getColumnSchema("obs", "bar")).toMatchObject({ + name: "bar", + type: "categorical", + categories: expect.arrayContaining(["a-new-label", "unassigned"]), + }); + }); + + test("removeObsAnnoCategory", async () => { + let xfltr; + + // catch unknown or readonly categories + await expect(() => + crossfilter.removeObsAnnoCategory("louvain", "mumble", "unassigned") + ).rejects.toThrow("Unknown or readonly obs column"); + await expect(() => + crossfilter.removeObsAnnoCategory("undefined-name", "mumble") + ).rejects.toThrow("Unknown or readonly obs column"); + + xfltr = await helperAddTestCol(crossfilter, "foo", { + name: "foo", + type: "categorical", + categories: ["unassigned", "red", "green", "blue"], + }); + xfltr = await xfltr.select("obs", "foo", { mode: "all" }); + expect( + (await xfltr.annoMatrix.fetch("obs", "foo")) + .col("foo") + .asArray() + .every((v) => v === "unassigned") + ).toBeTruthy(); + expect(xfltr.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ + name: "foo", + type: "categorical", + categories: expect.arrayContaining([ + "unassigned", + "red", + "green", + "blue", + ]), + }); + + // remove an unused category + const xfltr1 = await xfltr.removeObsAnnoCategory("foo", "red", "mumble"); + expect( + (await xfltr1.annoMatrix.fetch("obs", "foo")) + .col("foo") + .asArray() + .every((v) => v === "unassigned") + ).toBeTruthy(); + expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ + name: "foo", + type: "categorical", + categories: expect.arrayContaining([ + "unassigned", + "green", + "blue", + "mumble", + ]), + }); + + // remove a used category + const xfltr2 = await xfltr.removeObsAnnoCategory( + "foo", + "unassigned", + "red" + ); + expect( + (await xfltr2.annoMatrix.fetch("obs", "foo")) + .col("foo") + .asArray() + .every((v) => v === "red") + ).toBeTruthy(); + expect(xfltr2.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ + name: "foo", + type: "categorical", + categories: expect.arrayContaining(["green", "blue", "red"]), + }); + }); + + test("setObsColumnValues", async () => { + // catch unknown or readonly categories + await expect(() => + crossfilter.setObsColumnValues("louvain", [0, 1], "unassigned") + ).rejects.toThrow("Unknown or readonly obs column"); + await expect(() => + crossfilter.setObsColumnValues("undefined-name", [0], "mumble") + ).rejects.toThrow("Unknown or readonly obs column"); + + let xfltr = await helperAddTestCol(crossfilter, "foo", { + name: "foo", + type: "categorical", + categories: ["unassigned", "red", "green", "blue"], + }); + xfltr = await xfltr.select("obs", "foo", { mode: "all" }); + + // catch unknown row label + await expect(() => + xfltr.setObsColumnValues("foo", [-1], "red") + ).rejects.toThrow("Unknown row label"); + + // set a few rows + expect( + (await xfltr.annoMatrix.fetch("obs", "foo")) + .col("foo") + .asArray() + .every((v) => v === "unassigned") + ).toBeTruthy(); + const xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple"); + expect( + (await xfltr1.annoMatrix.fetch("obs", "foo")) + .col("foo") + .asArray() + .every( + (v, i) => + v === "unassigned" || (v === "purple" && (i === 0 || i === 10)) + ) + ).toBeTruthy(); + expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ + name: "foo", + type: "categorical", + categories: expect.arrayContaining([ + "unassigned", + "red", + "green", + "blue", + "purple", + ]), + }); + + expect(xfltr1.countSelected()).toEqual(xfltr1.annoMatrix.nObs); + const xfltr2 = await xfltr1.select("obs", "foo", { + mode: "exact", + values: ["purple"], + }); + expect(xfltr2.countSelected()).toEqual(2); + expect(xfltr2.allSelectedLabels()).toEqual(Int32Array.from([0, 10])); + }); + + test("resetObsColumnValues", async () => { + // catch unknown or readonly categories + await expect(() => + crossfilter.resetObsColumnValues("louvain", "red", "blue") + ).rejects.toThrow("Unknown or readonly obs column"); + await expect(() => + crossfilter.resetObsColumnValues("undefined-name", "red", "blue") + ).rejects.toThrow("Unknown or readonly obs column"); + + let xfltr = await helperAddTestCol(crossfilter, "foo", { + name: "foo", + type: "categorical", + categories: ["unassigned", "red", "green", "blue"], + }); + xfltr = await xfltr.select("obs", "foo", { + mode: "exact", + values: "red", + }); + + // catch unknown category name label + await expect(() => + xfltr.resetObsColumnValues("foo", "unknown-label", "red") + ).rejects.toThrow("unknown category"); + + let xfltr1 = await xfltr.setObsColumnValues("foo", [0, 10], "purple"); + xfltr1 = await xfltr1.select("obs", "foo", { + mode: "exact", + values: "purple", + }); + expect( + (await xfltr1.annoMatrix.fetch("obs", "foo")) + .col("foo") + .asArray() + .filter((v) => v === "purple") + ).toHaveLength(2); + + xfltr1 = await xfltr1.resetObsColumnValues("foo", "purple", "magenta"); + expect( + (await xfltr1.annoMatrix.fetch("obs", "foo")) + .col("foo") + .asArray() + .filter((v) => v === "magenta") + ).toHaveLength(2); + expect( + (await xfltr1.annoMatrix.fetch("obs", "foo")) + .col("foo") + .asArray() + .filter((v) => v === "purple") + ).toHaveLength(0); + expect(xfltr1.annoMatrix.getColumnSchema("obs", "foo")).toMatchObject({ + name: "foo", + type: "categorical", + categories: expect.arrayContaining([ + "unassigned", + "red", + "green", + "blue", + "purple", + "magenta", + ]), + }); + }); + }); + + describe("edge cases", () => { + test("transition from empty annoMatrix", async () => { + // select before fetch needs to work + fetch.once(serverMocks.dataframeResponse(["louvain"], [obsLouvain])); + const xfltr = await crossfilter.select("obs", "louvain", { + mode: "exact", + values: "B cells", + }); + expect(fetch.mock.calls).toHaveLength(1); + expect(xfltr.obsCrossfilter.hasDimension("obs/louvain")).toBeTruthy(); + expect(xfltr.obsCrossfilter.all()).toBe(xfltr.annoMatrix._cache.obs); + expect(xfltr.countSelected()).toEqual( + obsLouvain.reduce( + (count, v) => (v === "B cells" ? count + 1 : count), + 0 + ) + ); + }); + }); +}); diff --git a/client/__tests__/util/annoMatrix/louvain.json b/client/__tests__/util/annoMatrix/louvain.json new file mode 100644 index 00000000..ac60509f --- /dev/null +++ b/client/__tests__/util/annoMatrix/louvain.json @@ -0,0 +1,2640 @@ +[ + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "CD8 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "B cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "Dendritic cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "B cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "Dendritic cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "B cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "B cells", + "CD8 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD14+ Monocytes", + "B cells", + "Dendritic cells", + "B cells", + "CD14+ Monocytes", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "FCGR3A+ Monocytes", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "NK cells", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "NK cells", + "CD8 T cells", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "Megakaryocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "Megakaryocytes", + "NK cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "NK cells", + "Megakaryocytes", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "B cells", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "NK cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "B cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "NK cells", + "CD14+ Monocytes", + "B cells", + "Dendritic cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "NK cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "B cells", + "CD8 T cells", + "NK cells", + "NK cells", + "CD8 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "Dendritic cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "B cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD8 T cells", + "CD14+ Monocytes", + "NK cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "Dendritic cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "NK cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "B cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "B cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "Megakaryocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "Dendritic cells", + "CD8 T cells", + "Dendritic cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "B cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "Megakaryocytes", + "CD8 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "NK cells", + "B cells", + "B cells", + "B cells", + "B cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "B cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "B cells", + "B cells", + "NK cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "NK cells", + "B cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "Dendritic cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "Megakaryocytes", + "CD14+ Monocytes", + "NK cells", + "CD8 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "B cells", + "B cells", + "Dendritic cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "B cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "NK cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "B cells", + "B cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "B cells", + "B cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "NK cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "Dendritic cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "NK cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "B cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "CD8 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "B cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "NK cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "Dendritic cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "NK cells", + "Dendritic cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "Megakaryocytes", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "NK cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "B cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "Dendritic cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "Dendritic cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "Dendritic cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "B cells", + "B cells", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "FCGR3A+ Monocytes", + "B cells", + "Dendritic cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "Megakaryocytes", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "B cells", + "CD8 T cells", + "B cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "B cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "Megakaryocytes", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "B cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "B cells", + "B cells", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "Dendritic cells", + "NK cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "B cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "Megakaryocytes", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "Dendritic cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "Megakaryocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "Dendritic cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "NK cells", + "B cells", + "B cells", + "B cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "NK cells", + "Megakaryocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "Dendritic cells", + "B cells", + "CD14+ Monocytes", + "B cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "NK cells", + "NK cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "B cells", + "B cells", + "B cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD14+ Monocytes", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "Megakaryocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "B cells", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "B cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "NK cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "Megakaryocytes", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD14+ Monocytes", + "NK cells", + "CD14+ Monocytes", + "B cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "B cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "B cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "B cells", + "B cells", + "Dendritic cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "NK cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "NK cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "NK cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "NK cells", + "B cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "NK cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD8 T cells", + "FCGR3A+ Monocytes", + "B cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD8 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "B cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "Dendritic cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "NK cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "B cells", + "B cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD14+ Monocytes", + "B cells", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "NK cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "B cells", + "CD8 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD8 T cells", + "NK cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "NK cells", + "FCGR3A+ Monocytes", + "B cells", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD8 T cells", + "B cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "Megakaryocytes", + "CD14+ Monocytes", + "Dendritic cells", + "CD4 T cells", + "NK cells", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "NK cells", + "CD4 T cells", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "Dendritic cells", + "CD14+ Monocytes", + "B cells", + "B cells", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "Dendritic cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "B cells", + "NK cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "CD8 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD4 T cells", + "CD4 T cells", + "CD14+ Monocytes", + "NK cells", + "CD14+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "NK cells", + "FCGR3A+ Monocytes", + "B cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "CD4 T cells", + "B cells", + "CD14+ Monocytes", + "Dendritic cells", + "CD4 T cells", + "CD8 T cells", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "CD14+ Monocytes", + "FCGR3A+ Monocytes", + "CD8 T cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "FCGR3A+ Monocytes", + "CD4 T cells", + "B cells", + "B cells", + "B cells", + "CD4 T cells", + "CD4 T cells", + "B cells", + "NK cells", + "B cells", + "CD4 T cells", + "CD14+ Monocytes", + "Dendritic cells", + "CD14+ Monocytes", + "CD14+ Monocytes", + "CD4 T cells", + "CD8 T cells", + "B cells", + "CD14+ Monocytes", + "B cells", + "CD4 T cells", + "Dendritic cells", + "CD14+ Monocytes", + "B cells", + "B cells", + "B cells", + "CD4 T cells" +] diff --git a/client/__tests__/util/annoMatrix/n_genes.json b/client/__tests__/util/annoMatrix/n_genes.json new file mode 100644 index 00000000..29a4fece --- /dev/null +++ b/client/__tests__/util/annoMatrix/n_genes.json @@ -0,0 +1,2640 @@ +[ + 781, + 1352, + 1131, + 960, + 522, + 782, + 783, + 790, + 533, + 550, + 1116, + 751, + 866, + 1059, + 458, + 335, + 1424, + 1014, + 1446, + 446, + 1020, + 417, + 878, + 789, + 510, + 824, + 1545, + 996, + 937, + 1368, + 428, + 406, + 1020, + 786, + 1019, + 750, + 822, + 982, + 876, + 930, + 838, + 1014, + 732, + 877, + 782, + 787, + 791, + 880, + 801, + 1215, + 343, + 1460, + 1250, + 756, + 836, + 824, + 827, + 1238, + 1243, + 1652, + 843, + 825, + 656, + 776, + 766, + 1465, + 790, + 871, + 803, + 965, + 800, + 876, + 690, + 988, + 906, + 741, + 620, + 867, + 916, + 969, + 803, + 732, + 555, + 790, + 862, + 900, + 674, + 397, + 663, + 563, + 786, + 859, + 568, + 412, + 1043, + 1206, + 702, + 1263, + 929, + 1079, + 938, + 316, + 900, + 919, + 862, + 903, + 390, + 1717, + 819, + 1877, + 660, + 791, + 478, + 769, + 481, + 819, + 866, + 600, + 1185, + 650, + 775, + 699, + 642, + 857, + 832, + 388, + 710, + 341, + 894, + 935, + 604, + 1008, + 985, + 679, + 603, + 864, + 1031, + 887, + 603, + 610, + 1119, + 669, + 794, + 963, + 756, + 637, + 1032, + 776, + 860, + 825, + 852, + 745, + 858, + 925, + 1228, + 806, + 715, + 668, + 779, + 1197, + 888, + 1273, + 873, + 847, + 781, + 959, + 805, + 554, + 604, + 785, + 978, + 910, + 936, + 997, + 961, + 1314, + 799, + 1112, + 677, + 775, + 1298, + 657, + 626, + 1313, + 467, + 936, + 977, + 780, + 1311, + 432, + 579, + 850, + 736, + 800, + 892, + 860, + 720, + 822, + 681, + 954, + 889, + 1265, + 919, + 799, + 833, + 496, + 1476, + 848, + 869, + 1059, + 490, + 897, + 832, + 864, + 419, + 856, + 907, + 791, + 756, + 771, + 957, + 1190, + 680, + 524, + 908, + 506, + 851, + 775, + 793, + 748, + 951, + 643, + 1277, + 828, + 480, + 969, + 1112, + 648, + 805, + 1223, + 1023, + 669, + 489, + 390, + 350, + 1113, + 837, + 1547, + 840, + 581, + 748, + 1861, + 735, + 488, + 1016, + 585, + 797, + 769, + 490, + 1307, + 895, + 686, + 602, + 772, + 704, + 892, + 1169, + 1375, + 1189, + 892, + 2455, + 355, + 1856, + 1317, + 703, + 825, + 736, + 1997, + 892, + 1034, + 545, + 1188, + 659, + 1056, + 819, + 979, + 632, + 598, + 690, + 310, + 803, + 743, + 560, + 1073, + 844, + 882, + 841, + 815, + 771, + 976, + 986, + 820, + 957, + 640, + 1012, + 927, + 794, + 753, + 791, + 366, + 539, + 752, + 769, + 650, + 947, + 771, + 824, + 744, + 837, + 723, + 640, + 923, + 1174, + 1597, + 699, + 618, + 1418, + 820, + 1047, + 981, + 866, + 527, + 762, + 717, + 860, + 603, + 828, + 476, + 1071, + 775, + 614, + 913, + 836, + 669, + 942, + 792, + 871, + 1046, + 859, + 793, + 822, + 751, + 435, + 1142, + 781, + 718, + 471, + 1750, + 892, + 841, + 1156, + 1031, + 912, + 873, + 824, + 1233, + 1312, + 575, + 605, + 717, + 1019, + 1215, + 928, + 1780, + 657, + 718, + 646, + 808, + 1120, + 750, + 390, + 1100, + 456, + 755, + 1118, + 571, + 867, + 728, + 916, + 491, + 960, + 625, + 1090, + 772, + 968, + 480, + 810, + 725, + 1016, + 1011, + 1075, + 808, + 672, + 950, + 862, + 766, + 963, + 507, + 570, + 678, + 768, + 1037, + 885, + 1426, + 1496, + 587, + 879, + 924, + 783, + 696, + 1057, + 783, + 867, + 919, + 1251, + 1023, + 727, + 645, + 1217, + 929, + 792, + 994, + 1025, + 946, + 600, + 881, + 975, + 1609, + 758, + 772, + 682, + 998, + 979, + 1045, + 706, + 808, + 855, + 819, + 1147, + 742, + 914, + 969, + 704, + 1398, + 581, + 809, + 921, + 805, + 542, + 888, + 519, + 1092, + 762, + 698, + 752, + 771, + 899, + 1101, + 760, + 881, + 1124, + 809, + 445, + 1703, + 789, + 641, + 819, + 890, + 767, + 806, + 1323, + 942, + 807, + 981, + 888, + 726, + 1190, + 826, + 661, + 713, + 816, + 822, + 806, + 864, + 464, + 664, + 931, + 860, + 674, + 803, + 464, + 788, + 1068, + 781, + 843, + 779, + 873, + 707, + 492, + 669, + 982, + 749, + 789, + 780, + 561, + 655, + 432, + 801, + 945, + 770, + 503, + 766, + 776, + 970, + 989, + 654, + 762, + 882, + 1236, + 1499, + 626, + 1380, + 1170, + 491, + 833, + 924, + 581, + 842, + 596, + 811, + 542, + 1572, + 758, + 854, + 274, + 1413, + 872, + 559, + 907, + 951, + 1175, + 946, + 905, + 737, + 604, + 843, + 606, + 1079, + 668, + 785, + 726, + 978, + 941, + 994, + 655, + 1013, + 987, + 591, + 1041, + 625, + 582, + 814, + 570, + 775, + 822, + 715, + 738, + 956, + 1178, + 743, + 1861, + 841, + 944, + 783, + 643, + 924, + 936, + 431, + 490, + 532, + 524, + 620, + 749, + 1334, + 834, + 790, + 702, + 892, + 693, + 784, + 944, + 471, + 839, + 529, + 729, + 1084, + 802, + 886, + 815, + 856, + 746, + 1318, + 545, + 696, + 872, + 1154, + 467, + 725, + 1027, + 479, + 728, + 926, + 1282, + 907, + 833, + 1024, + 838, + 900, + 737, + 367, + 459, + 1030, + 1279, + 756, + 662, + 1323, + 1003, + 359, + 770, + 813, + 634, + 924, + 1184, + 901, + 816, + 1421, + 771, + 706, + 953, + 348, + 716, + 870, + 715, + 550, + 689, + 947, + 1157, + 690, + 383, + 374, + 882, + 697, + 246, + 833, + 1006, + 1181, + 974, + 856, + 978, + 1551, + 965, + 907, + 565, + 417, + 907, + 927, + 966, + 658, + 727, + 743, + 381, + 385, + 905, + 645, + 1167, + 936, + 724, + 618, + 1038, + 853, + 808, + 1403, + 762, + 741, + 687, + 932, + 1096, + 601, + 652, + 895, + 682, + 1553, + 604, + 823, + 803, + 646, + 917, + 942, + 850, + 795, + 771, + 949, + 516, + 664, + 1001, + 637, + 619, + 907, + 961, + 812, + 793, + 1043, + 1343, + 1326, + 981, + 675, + 937, + 631, + 1026, + 1135, + 499, + 948, + 801, + 848, + 741, + 604, + 864, + 1076, + 1106, + 1111, + 624, + 1008, + 908, + 815, + 346, + 1062, + 803, + 749, + 779, + 1027, + 1032, + 1040, + 654, + 631, + 755, + 854, + 850, + 798, + 864, + 1078, + 690, + 864, + 1523, + 838, + 966, + 389, + 1654, + 808, + 885, + 1665, + 920, + 855, + 807, + 859, + 1276, + 987, + 1079, + 678, + 626, + 831, + 829, + 1009, + 547, + 893, + 722, + 656, + 415, + 773, + 1262, + 1218, + 365, + 661, + 805, + 1409, + 1094, + 779, + 898, + 830, + 1242, + 864, + 576, + 901, + 1274, + 852, + 1006, + 719, + 763, + 683, + 957, + 831, + 724, + 354, + 763, + 910, + 749, + 626, + 870, + 795, + 1078, + 736, + 835, + 402, + 1203, + 699, + 755, + 697, + 1229, + 637, + 666, + 846, + 1036, + 1027, + 831, + 999, + 942, + 891, + 1007, + 712, + 990, + 725, + 745, + 921, + 333, + 1042, + 895, + 873, + 1612, + 724, + 929, + 601, + 862, + 908, + 658, + 775, + 724, + 753, + 741, + 690, + 379, + 608, + 927, + 777, + 969, + 827, + 709, + 385, + 690, + 769, + 554, + 892, + 761, + 367, + 731, + 1103, + 944, + 832, + 675, + 652, + 418, + 727, + 745, + 872, + 1336, + 863, + 934, + 844, + 721, + 432, + 782, + 1006, + 834, + 840, + 840, + 819, + 699, + 489, + 665, + 576, + 1291, + 1102, + 826, + 880, + 738, + 904, + 686, + 874, + 887, + 873, + 560, + 766, + 710, + 1135, + 1054, + 805, + 724, + 973, + 1201, + 575, + 838, + 865, + 546, + 811, + 884, + 886, + 791, + 1026, + 2000, + 644, + 763, + 969, + 800, + 359, + 624, + 993, + 800, + 1167, + 833, + 1871, + 616, + 822, + 647, + 1000, + 618, + 734, + 618, + 1938, + 861, + 945, + 1032, + 723, + 984, + 994, + 771, + 738, + 1583, + 1113, + 614, + 1146, + 615, + 848, + 983, + 677, + 972, + 791, + 827, + 804, + 395, + 843, + 493, + 741, + 941, + 1659, + 742, + 1517, + 559, + 937, + 740, + 781, + 819, + 813, + 578, + 1022, + 1191, + 824, + 1146, + 757, + 638, + 830, + 713, + 609, + 1271, + 680, + 769, + 1119, + 731, + 804, + 781, + 916, + 735, + 835, + 1257, + 472, + 879, + 851, + 1023, + 661, + 1008, + 748, + 845, + 393, + 675, + 843, + 876, + 939, + 932, + 760, + 735, + 1561, + 752, + 940, + 705, + 405, + 690, + 1071, + 544, + 927, + 817, + 388, + 560, + 1322, + 640, + 886, + 1075, + 689, + 524, + 606, + 802, + 868, + 939, + 753, + 770, + 1105, + 841, + 786, + 445, + 703, + 593, + 875, + 901, + 927, + 798, + 1221, + 415, + 1381, + 949, + 1322, + 1169, + 745, + 727, + 799, + 490, + 767, + 943, + 808, + 926, + 664, + 569, + 843, + 727, + 1222, + 457, + 1515, + 1138, + 1174, + 525, + 878, + 525, + 999, + 778, + 772, + 819, + 1015, + 939, + 856, + 715, + 793, + 837, + 1193, + 764, + 834, + 677, + 625, + 420, + 837, + 874, + 679, + 811, + 546, + 787, + 587, + 821, + 669, + 813, + 780, + 649, + 924, + 1322, + 701, + 792, + 817, + 667, + 804, + 593, + 740, + 1243, + 859, + 685, + 596, + 1193, + 859, + 775, + 947, + 689, + 907, + 734, + 621, + 336, + 922, + 802, + 812, + 941, + 943, + 868, + 947, + 767, + 701, + 692, + 423, + 775, + 500, + 1100, + 812, + 728, + 616, + 928, + 835, + 454, + 812, + 769, + 1058, + 914, + 628, + 649, + 452, + 754, + 1327, + 776, + 670, + 1017, + 599, + 431, + 967, + 1077, + 2033, + 731, + 533, + 926, + 725, + 559, + 870, + 964, + 1341, + 1981, + 1103, + 326, + 428, + 808, + 821, + 554, + 596, + 680, + 1034, + 849, + 566, + 879, + 1091, + 823, + 447, + 1688, + 868, + 1254, + 942, + 462, + 1055, + 852, + 738, + 804, + 775, + 726, + 993, + 1462, + 1007, + 798, + 1036, + 786, + 948, + 743, + 761, + 838, + 1040, + 859, + 867, + 1188, + 846, + 687, + 672, + 629, + 725, + 660, + 809, + 469, + 600, + 812, + 856, + 397, + 786, + 895, + 882, + 449, + 890, + 823, + 1051, + 823, + 1055, + 741, + 999, + 1241, + 790, + 878, + 778, + 1066, + 815, + 465, + 1079, + 743, + 1098, + 807, + 1120, + 1025, + 805, + 676, + 828, + 763, + 997, + 852, + 866, + 1118, + 508, + 928, + 958, + 932, + 892, + 905, + 494, + 710, + 1068, + 795, + 787, + 951, + 720, + 842, + 890, + 1355, + 1005, + 872, + 1185, + 912, + 869, + 894, + 997, + 770, + 554, + 806, + 1426, + 1012, + 452, + 896, + 426, + 239, + 829, + 895, + 787, + 1139, + 925, + 1015, + 1360, + 1097, + 650, + 853, + 549, + 1052, + 307, + 1152, + 907, + 1628, + 731, + 897, + 1749, + 762, + 712, + 1195, + 851, + 864, + 968, + 845, + 331, + 840, + 734, + 948, + 842, + 1543, + 661, + 981, + 912, + 912, + 1063, + 683, + 823, + 996, + 695, + 1483, + 927, + 574, + 1052, + 571, + 1028, + 1263, + 671, + 958, + 747, + 866, + 896, + 489, + 643, + 923, + 820, + 1466, + 550, + 1112, + 1006, + 1448, + 727, + 899, + 998, + 563, + 870, + 903, + 516, + 754, + 879, + 588, + 740, + 798, + 798, + 653, + 902, + 990, + 724, + 953, + 891, + 1437, + 653, + 714, + 956, + 877, + 1012, + 824, + 1077, + 740, + 692, + 1063, + 771, + 808, + 1389, + 1264, + 952, + 816, + 795, + 795, + 760, + 886, + 349, + 868, + 842, + 819, + 626, + 418, + 903, + 838, + 723, + 436, + 1112, + 724, + 1299, + 719, + 843, + 1090, + 696, + 885, + 627, + 809, + 423, + 729, + 853, + 855, + 608, + 627, + 823, + 1063, + 575, + 743, + 1528, + 681, + 544, + 422, + 731, + 920, + 761, + 884, + 982, + 784, + 496, + 573, + 521, + 663, + 794, + 975, + 856, + 978, + 590, + 905, + 695, + 816, + 976, + 816, + 753, + 791, + 858, + 813, + 841, + 1085, + 1692, + 716, + 955, + 1467, + 741, + 296, + 738, + 1573, + 1119, + 918, + 283, + 703, + 842, + 1253, + 676, + 1636, + 1273, + 380, + 799, + 1491, + 878, + 939, + 725, + 1365, + 818, + 719, + 1343, + 905, + 837, + 803, + 990, + 1084, + 976, + 1630, + 795, + 1408, + 771, + 650, + 779, + 648, + 817, + 1127, + 882, + 954, + 830, + 732, + 783, + 756, + 708, + 976, + 718, + 887, + 809, + 795, + 662, + 912, + 1550, + 1509, + 1021, + 1751, + 776, + 910, + 714, + 530, + 846, + 631, + 1152, + 1118, + 755, + 573, + 1176, + 267, + 918, + 1132, + 849, + 938, + 1140, + 909, + 840, + 806, + 904, + 788, + 778, + 715, + 869, + 714, + 883, + 767, + 858, + 788, + 553, + 634, + 1230, + 1131, + 849, + 811, + 827, + 1753, + 713, + 484, + 783, + 722, + 596, + 514, + 372, + 925, + 747, + 840, + 673, + 955, + 796, + 719, + 718, + 857, + 578, + 1063, + 594, + 997, + 1268, + 341, + 388, + 753, + 834, + 593, + 1189, + 911, + 738, + 476, + 816, + 641, + 746, + 635, + 953, + 801, + 326, + 849, + 499, + 865, + 1420, + 487, + 876, + 797, + 981, + 756, + 850, + 1097, + 998, + 829, + 1159, + 955, + 1061, + 696, + 786, + 743, + 1211, + 893, + 491, + 744, + 1447, + 767, + 1050, + 853, + 1118, + 1428, + 555, + 612, + 854, + 789, + 889, + 784, + 712, + 1323, + 921, + 682, + 794, + 846, + 1012, + 839, + 807, + 587, + 881, + 781, + 1010, + 1182, + 1149, + 812, + 607, + 1155, + 714, + 642, + 1088, + 1291, + 1196, + 325, + 804, + 370, + 359, + 855, + 1165, + 836, + 923, + 863, + 1284, + 1011, + 889, + 984, + 512, + 939, + 883, + 697, + 1211, + 362, + 969, + 1135, + 1239, + 580, + 1103, + 975, + 825, + 1170, + 921, + 640, + 1180, + 378, + 982, + 916, + 1122, + 792, + 619, + 750, + 913, + 775, + 661, + 766, + 768, + 675, + 944, + 940, + 761, + 727, + 767, + 873, + 1043, + 850, + 995, + 680, + 595, + 700, + 753, + 736, + 891, + 685, + 780, + 986, + 989, + 830, + 810, + 685, + 784, + 642, + 742, + 961, + 906, + 829, + 621, + 822, + 717, + 1210, + 800, + 1963, + 749, + 757, + 570, + 831, + 721, + 336, + 802, + 1001, + 886, + 631, + 759, + 631, + 550, + 984, + 767, + 835, + 777, + 639, + 860, + 1413, + 747, + 779, + 540, + 367, + 1629, + 1380, + 689, + 1001, + 809, + 337, + 1103, + 796, + 966, + 782, + 1018, + 642, + 967, + 436, + 826, + 779, + 1000, + 601, + 796, + 945, + 1679, + 1123, + 596, + 995, + 720, + 588, + 759, + 452, + 780, + 836, + 515, + 846, + 392, + 283, + 710, + 1158, + 796, + 895, + 585, + 559, + 859, + 879, + 858, + 842, + 643, + 1308, + 595, + 1181, + 909, + 710, + 821, + 817, + 841, + 1197, + 640, + 1425, + 947, + 900, + 852, + 460, + 1100, + 824, + 780, + 932, + 542, + 1137, + 1225, + 997, + 572, + 780, + 765, + 906, + 793, + 753, + 772, + 854, + 936, + 1048, + 819, + 645, + 619, + 314, + 726, + 737, + 1162, + 1081, + 868, + 1032, + 913, + 476, + 490, + 799, + 1201, + 997, + 898, + 212, + 1586, + 427, + 947, + 937, + 724, + 380, + 715, + 739, + 931, + 973, + 773, + 1497, + 906, + 798, + 953, + 471, + 821, + 806, + 714, + 828, + 727, + 773, + 976, + 856, + 727, + 761, + 1084, + 1557, + 693, + 559, + 627, + 795, + 750, + 838, + 803, + 453, + 734, + 607, + 1029, + 805, + 669, + 505, + 858, + 832, + 1019, + 585, + 1225, + 1287, + 903, + 752, + 2020, + 774, + 666, + 843, + 857, + 887, + 1082, + 656, + 674, + 911, + 734, + 910, + 672, + 802, + 539, + 699, + 941, + 828, + 800, + 642, + 733, + 607, + 992, + 379, + 562, + 847, + 787, + 1461, + 732, + 941, + 785, + 696, + 795, + 809, + 828, + 651, + 882, + 972, + 1323, + 679, + 774, + 784, + 766, + 520, + 671, + 796, + 644, + 1549, + 756, + 723, + 788, + 643, + 856, + 825, + 730, + 831, + 653, + 429, + 641, + 637, + 812, + 1527, + 859, + 972, + 744, + 869, + 508, + 624, + 923, + 976, + 801, + 1014, + 1429, + 586, + 692, + 704, + 1176, + 806, + 883, + 1249, + 765, + 743, + 907, + 666, + 669, + 364, + 794, + 959, + 766, + 937, + 1398, + 942, + 1469, + 905, + 812, + 572, + 1378, + 1058, + 1215, + 697, + 531, + 676, + 1819, + 503, + 801, + 943, + 874, + 813, + 694, + 494, + 660, + 1467, + 976, + 833, + 689, + 921, + 625, + 1428, + 817, + 909, + 956, + 765, + 1207, + 829, + 1648, + 554, + 1500, + 953, + 647, + 537, + 786, + 814, + 761, + 862, + 838, + 1102, + 1392, + 1042, + 372, + 971, + 1364, + 1137, + 847, + 935, + 710, + 1070, + 914, + 855, + 759, + 654, + 981, + 1193, + 397, + 1123, + 616, + 747, + 876, + 949, + 965, + 789, + 752, + 717, + 1198, + 952, + 625, + 784, + 914, + 659, + 913, + 863, + 959, + 1637, + 796, + 1508, + 906, + 714, + 1195, + 867, + 819, + 375, + 656, + 1047, + 745, + 866, + 1186, + 1669, + 539, + 942, + 839, + 927, + 796, + 734, + 831, + 967, + 620, + 814, + 605, + 1391, + 655, + 1512, + 625, + 719, + 547, + 864, + 902, + 853, + 1143, + 990, + 858, + 604, + 1291, + 701, + 859, + 768, + 1621, + 715, + 594, + 783, + 1608, + 927, + 740, + 805, + 705, + 491, + 514, + 768, + 880, + 993, + 356, + 748, + 993, + 801, + 843, + 1194, + 794, + 606, + 810, + 882, + 682, + 1126, + 792, + 829, + 1657, + 786, + 610, + 664, + 790, + 852, + 678, + 589, + 930, + 690, + 840, + 647, + 622, + 427, + 856, + 522, + 1262, + 722, + 641, + 997, + 983, + 1099, + 607, + 704, + 947, + 886, + 1062, + 659, + 995, + 492, + 754, + 676, + 837, + 532, + 598, + 1274, + 921, + 700, + 828, + 828, + 543, + 815, + 709, + 842, + 1200, + 1126, + 1327, + 849, + 1160, + 707, + 675, + 709, + 990, + 270, + 445, + 892, + 1040, + 832, + 815, + 437, + 1401, + 1341, + 480, + 1517, + 704, + 622, + 823, + 731, + 781, + 849, + 1209, + 472, + 838, + 643, + 1101, + 879, + 895, + 408, + 832, + 845, + 928, + 920, + 1269, + 652, + 1463, + 840, + 706, + 956, + 1219, + 612, + 970, + 789, + 382, + 533, + 627, + 388, + 655, + 1103, + 782, + 1231, + 617, + 589, + 758, + 882, + 909, + 661, + 558, + 947, + 491, + 1386, + 1481, + 823, + 1064, + 681, + 652, + 363, + 824, + 933, + 611, + 886, + 806, + 531, + 864, + 648, + 1142, + 736, + 955, + 826, + 725, + 1174, + 841, + 1006, + 1418, + 955, + 829, + 1038, + 632, + 750, + 846, + 1033, + 859, + 890, + 893, + 1030, + 913, + 1385, + 750, + 416, + 703, + 522, + 856, + 914, + 742, + 381, + 1079, + 671, + 1101, + 858, + 663, + 581, + 845, + 881, + 823, + 835, + 1290, + 598, + 1130, + 793, + 838, + 765, + 768, + 815, + 810, + 912, + 818, + 790, + 665, + 703, + 875, + 657, + 1567, + 688, + 749, + 1076, + 782, + 543, + 1100, + 841, + 809, + 789, + 1263, + 758, + 368, + 1022, + 730, + 1101, + 478, + 488, + 618, + 940, + 771, + 784, + 847, + 1303, + 821, + 1111, + 752, + 1128, + 958, + 742, + 782, + 824, + 751, + 872, + 939, + 606, + 698, + 705, + 963, + 607, + 621, + 1650, + 1093, + 545, + 670, + 617, + 723, + 1194, + 1019, + 1291, + 758, + 855, + 1549, + 743, + 1372, + 802, + 337, + 1121, + 1028, + 1524, + 645, + 847, + 866, + 941, + 751, + 583, + 796, + 793, + 975, + 936, + 524, + 659, + 607, + 1433, + 562, + 696, + 927, + 517, + 719, + 599, + 639, + 977, + 1019, + 816, + 672, + 1903, + 1162, + 964, + 936, + 947, + 989, + 790, + 902, + 982, + 673, + 856, + 629, + 692, + 843, + 940, + 795, + 780, + 821, + 471, + 702, + 631, + 1557, + 868, + 901, + 798, + 1020, + 885, + 881, + 719, + 1043, + 1238, + 565, + 776, + 696, + 725, + 365, + 811, + 1212, + 1178, + 1132, + 661, + 831, + 786, + 471, + 835, + 564, + 929, + 958, + 706, + 388, + 842, + 965, + 1088, + 511, + 794, + 900, + 865, + 789, + 504, + 701, + 817, + 796, + 972, + 906, + 871, + 922, + 724, + 628, + 1479, + 533, + 1101, + 1913, + 855, + 1266, + 884, + 817, + 619, + 591, + 685, + 887, + 1336, + 656, + 1227, + 980, + 817, + 582, + 1370, + 460, + 638, + 471, + 650, + 414, + 907, + 1147, + 732, + 992, + 801, + 822, + 529, + 737, + 806, + 816, + 889, + 1305, + 588, + 657, + 1154, + 713, + 326, + 1129, + 1603, + 879, + 1156, + 642, + 285, + 825, + 823, + 719, + 1253, + 971, + 853, + 916, + 1053, + 515, + 1017, + 953, + 832, + 645, + 667, + 1326, + 547, + 636, + 1783, + 1211, + 788, + 807, + 1104, + 884, + 848, + 788, + 1013, + 1003, + 916, + 818, + 828, + 882, + 959, + 395, + 368, + 787, + 929, + 1379, + 711, + 733, + 752, + 464, + 626, + 735, + 946, + 876, + 647, + 536, + 954, + 486, + 712, + 786, + 438, + 807, + 1016, + 551, + 841, + 929, + 757, + 971, + 708, + 567, + 881, + 801, + 873, + 805, + 1359, + 866, + 945, + 1068, + 819, + 815, + 1058, + 845, + 881, + 1051, + 1179, + 718, + 657, + 882, + 709, + 754, + 735, + 603, + 944, + 1794, + 712, + 721, + 1097, + 813, + 788, + 917, + 656, + 1104, + 1268, + 1239, + 862, + 739, + 858, + 1066, + 752, + 615, + 721, + 571, + 861, + 933, + 807, + 1082, + 820, + 887, + 850, + 1567, + 803, + 1156, + 721, + 692, + 700, + 458, + 637, + 873, + 1544, + 1155, + 1227, + 622, + 454, + 724 +] diff --git a/client/__tests__/util/annoMatrix/serverMocks/index.js b/client/__tests__/util/annoMatrix/serverMocks/index.js new file mode 100644 index 00000000..a8ef1285 --- /dev/null +++ b/client/__tests__/util/annoMatrix/serverMocks/index.js @@ -0,0 +1,11 @@ +export const baseDataURL = "https://a.fake.url/api/v0.2"; + +window.CELLXGENE = { + API: { + prefix: baseDataURL, + version: "v0.2/", + }, +}; + +export { schema } from "./schema"; +export * from "./routes"; diff --git a/client/__tests__/util/annoMatrix/serverMocks/routes.js b/client/__tests__/util/annoMatrix/serverMocks/routes.js new file mode 100644 index 00000000..94fbe3f6 --- /dev/null +++ b/client/__tests__/util/annoMatrix/serverMocks/routes.js @@ -0,0 +1,211 @@ +import { schema } from "./schema"; +import { Dataframe, KeyIndex } from "../../../../src/util/dataframe"; +import { encodeMatrixFBS } from "../../../../src/util/stateManager/matrix"; + +const indexedSchema = { + obsByName: Object.fromEntries( + schema.schema.annotations.obs.columns.map((v) => [v.name, v]) ?? [] + ), + varByName: Object.fromEntries( + schema.schema.annotations.var.columns.map((v) => [v.name, v]) ?? [] + ), + embByName: Object.fromEntries( + schema.schema.layout.obs.map((v) => [v.name, v]) ?? [] + ), +}; + +function makeMockColumn(s, length) { + const { type } = s; + switch (type) { + case "int32": + return new Int32Array(length).fill(Math.floor(99 * Math.random())); + + case "string": + return new Array(length).fill("test"); + + case "float32": + return new Float32Array(length).fill(99 * Math.random()); + + case "boolean": + return new Array(length).fill(false); + + case "categorical": + return new Array(length).fill(s.categories[0]); + + default: + throw new Error("unkonwn type"); + } +} + +function getEncodedDataframe(colNames, length, colSchemas) { + const colIndex = new KeyIndex(colNames); + const columns = colSchemas.map((s) => makeMockColumn(s, length)); + const df = new Dataframe([length, colNames.length], columns, null, colIndex); + const body = encodeMatrixFBS(df); + return body; +} + +export function dataframeResponse(colNames, columns) { + const colIndex = new KeyIndex(colNames); + const df = new Dataframe( + [columns[0].length, colNames.length], + columns, + null, + colIndex + ); + const body = encodeMatrixFBS(df); + const headers = new Headers({ + "Content-Type": "application/octet-stream", + }); + return () => Promise.resolve({ body, init: { status: 200, headers } }); +} + +function annotationObsResponse(request) { + const url = new URL(request.url); + const params = Array.from(url.searchParams.entries()); + const names = params + .filter(([k]) => k === "annotation-name") + .map(([, v]) => v); + if (!names.every((n) => indexedSchema.obsByName[n])) { + return Promise.reject(new Error("bad obs annotation name in URL")); + } + const colSchemas = names.map((n) => indexedSchema.obsByName[n]); + const body = getEncodedDataframe( + names, + schema.schema.dataframe.nObs, + colSchemas + ); + + const headers = new Headers({ + "Content-Type": "application/octet-stream", + }); + return Promise.resolve({ + body, + init: { status: 200, headers }, + }); +} + +function annotationVarResponse(request) { + const url = new URL(request.url); + const params = Array.from(url.searchParams.entries()); + const names = params + .filter(([k]) => k === "annotation-name") + .map(([, v]) => v); + if (!names.every((n) => indexedSchema.varByName[n])) { + return Promise.reject(new Error("bad var annotation name in URL")); + } + const colSchemas = names.map((n) => indexedSchema.varByName[n]); + const body = getEncodedDataframe( + names, + schema.schema.dataframe.nVar, + colSchemas + ); + + const headers = new Headers({ + "Content-Type": "application/octet-stream", + }); + return Promise.resolve({ + body, + init: { status: 200, headers }, + }); +} + +function layoutObsResponse(request) { + const url = new URL(request.url); + const params = Array.from(url.searchParams.entries()); + const names = params.filter(([k]) => k === "layout-name").map(([, v]) => v); + if (!names.every((n) => indexedSchema.embByName[n])) { + return Promise.reject(new Error("bad layout name in URL")); + } + const dims = names.map((n) => indexedSchema.embByName[n].dims).flat(); + const colSchemas = names + .map((n) => [indexedSchema.embByName[n], indexedSchema.embByName[n]]) + .flat(); + const body = getEncodedDataframe( + dims, + schema.schema.dataframe.nObs, + colSchemas + ); + + const headers = new Headers({ + "Content-Type": "application/octet-stream", + }); + return Promise.resolve({ + body, + init: { status: 200, headers }, + }); +} + +function dataVarResponse(request) { + const url = new URL(request.url); + const params = Array.from(url.searchParams.entries()); + + const colNames = params.map((v) => `${v[0]}/${v[1]}`); + const colSchemas = colNames.map(() => schema.schema.dataframe); + const body = getEncodedDataframe( + colNames, + schema.schema.dataframe.nObs, + colSchemas + ); + + const headers = new Headers({ + "Content-Type": "application/octet-stream", + }); + return Promise.resolve({ + body, + init: { status: 200, headers }, + }); +} + +export function responder(request) { + const url = new URL(request.url); + const { pathname } = url; + if (pathname.endsWith("/annotations/obs")) { + return annotationObsResponse(request); + } + if (pathname.endsWith("/annotations/var")) { + return annotationVarResponse(request); + } + if (pathname.endsWith("/layout/obs")) { + return layoutObsResponse(request); + } + if (pathname.endsWith("/data/var")) { + return dataVarResponse(request); + } + return Promise.reject(new Error("bad URL")); +} + +export function withExpected(expectedURL, expectedParams) { + /* + Do some additional error checking + */ + return (request) => { + // if URL is bogus, reject the promise + const url = new URL(request.url); + if (!url.pathname.endsWith(expectedURL)) { + return Promise.reject(new Error("Unexpected URL!")); + } + const params = Array.from(url.searchParams.entries()).sort( + (a, b) => a[0] < b[0] + ); + expectedParams = expectedParams.slice().sort((a, b) => a[0] < b[0]); + + if ( + params.length !== expectedParams.length || + !params.every( + (p, i) => p[0] === expectedParams[i][0] && p[1] === expectedParams[i][1] + ) + ) { + return Promise.reject(new Error("unexpected name requested in URL")); + } + + return responder(request); + }; +} + +export function annotationsObs(names) { + return withExpected( + "/annotations/obs", + names.map((name) => ["annotation-name", name]) + ); +} diff --git a/client/__tests__/util/annoMatrix/serverMocks/schema.js b/client/__tests__/util/annoMatrix/serverMocks/schema.js new file mode 100644 index 00000000..fe995b05 --- /dev/null +++ b/client/__tests__/util/annoMatrix/serverMocks/schema.js @@ -0,0 +1,80 @@ +export const schema = { + schema: { + annotations: { + obs: { + columns: [ + { + name: "name_0", + type: "string", + writable: false, + }, + { + name: "n_genes", + type: "int32", + writable: false, + }, + { + name: "percent_mito", + type: "float32", + writable: false, + }, + { + name: "n_counts", + type: "float32", + writable: false, + }, + { + name: "louvain", + type: "string", + writable: false, + }, + ], + index: "name_0", + }, + var: { + columns: [ + { + name: "name_0", + type: "string", + writable: false, + }, + { + name: "n_cells", + type: "int32", + writable: false, + }, + ], + index: "name_0", + }, + }, + dataframe: { + nObs: 2638, + nVar: 1838, + type: "float32", + }, + layout: { + obs: [ + { + dims: ["draw_graph_fr_0", "draw_graph_fr_1"], + name: "draw_graph_fr", + type: "float32", + }, + { + dims: ["pca_0", "pca_1"], + name: "pca", + type: "float32", + }, + { + dims: ["tsne_0", "tsne_1"], + name: "tsne", + type: "float32", + }, + { + dims: ["umap_0", "umap_1"], + name: "umap", + type: "float32", + }, + ], + }, + }, +}; diff --git a/client/__tests__/util/annoMatrix/umap.json b/client/__tests__/util/annoMatrix/umap.json new file mode 100644 index 00000000..5d7134da --- /dev/null +++ b/client/__tests__/util/annoMatrix/umap.json @@ -0,0 +1,5282 @@ +[ + [ + 1.35285573560809, + -0.47802448287846216, + 2.165888749165179, + -8.69549315663449, + 2.0652175331122584, + 0.49520189144034144, + 1.051380238311061, + 1.8418954223210249, + 2.0126272839211943, + -8.406228786835502, + -0.7475822029589808, + 1.0398710174106818, + 2.4614340402375072, + -7.780092250827048, + 2.121988331057579, + 2.8140497988665834, + -9.117412304441292, + 0.7443418440268849, + -0.12612876310385598, + 1.1799221119905499, + -0.6887250690467789, + 3.0125133545646734, + -7.143523307396688, + 0.49794596192762824, + 2.1879318296159007, + -0.04074260948831929, + -1.6304621183653067, + 0.9185120650755632, + 3.330950131646921, + -8.249406782019472, + 2.9837694164624335, + 0.6013868697229028, + -7.973320868544591, + 1.2557275472097205, + -9.037432741704768, + 0.5272899942439975, + -7.3620383459740015, + -8.710739809962973, + 0.04565450184186127, + 0.9995702400422025, + 1.3707704392248314, + 0.4646969234541549, + 3.6660903522856345, + 1.5514822021133166, + 1.8014230367905788, + 0.8562655175966818, + 2.528521602131749, + -8.054206350208998, + 0.6986495744500447, + -8.360293645270179, + -7.028788335956598, + -8.117341523020128, + -8.975178374370575, + -0.12287461935615306, + 0.14568919159482138, + -0.22935322444119527, + -7.197623936888145, + 0.13018576439615953, + -8.262411494404354, + -9.10852755735242, + 0.7589399187829838, + 1.8111041361908446, + 2.0805181051825774, + -8.016584746538891, + 3.1365046821015947, + -8.174435180354182, + 1.8663362319091807, + 3.1219524797361493, + 0.6892426837009439, + 2.244499608696403, + 1.5623054194259811, + 2.3384273670301057, + 0.5378190418426231, + 2.7951515340684123, + -0.5755601568500485, + -0.5065756491480748, + 2.92485140197707, + 1.488221487102054, + 2.209257327297217, + -8.149923929399492, + -8.283361558328565, + 1.2593016566196622, + -7.614791259843514, + 2.0060290322527274, + -7.1957257296755675, + -0.6153994129631802, + 0.647621585176719, + 3.825122324575844, + -0.617206942026205, + -0.0723953060051589, + 2.7502344438270008, + -8.316773079712256, + -0.12703127087171923, + 0.032541047575098556, + -8.366227754298668, + -6.412773785556261, + 1.3622057178832003, + -7.233870565183863, + -7.394245052883867, + -4.525292336025702, + 2.10907364705061, + -7.274801711351849, + 0.4465992849099335, + 3.4309033386585783, + 1.9395204719917993, + 0.023946918636043358, + 2.833582913855396, + -0.3272931840397848, + 0.5946590476457169, + -4.118204094213762, + -8.85541744506898, + 2.4091434789774158, + 3.6121360006509553, + 2.574694350221088, + -8.386406382028346, + -8.087563939058514, + 0.4934831447839657, + 0.9560922248955092, + -9.01149783933102, + 0.8918896398008918, + 2.2792529871367955, + -6.807368181855979, + -0.016013842245326707, + 1.1749914467666223, + 0.5240606442534225, + 3.5680691256852217, + -7.131107288833797, + 0.5345528022913716, + 2.2504990630744834, + 0.43808793952614933, + 0.7054144783650844, + 0.010596177968814935, + -0.44663998860576865, + 2.365041933566587, + 0.8319562986581416, + -8.069079321353989, + 0.3509974873712663, + 1.997283773551994, + 2.4671002696984927, + 1.2639763865572942, + 0.5352041836925608, + 1.8841680192540224, + 2.905270971072112, + 0.1267833933956664, + 2.1242670578822405, + 2.6031580899982645, + -0.4312188043186363, + 0.9370138686596056, + -8.613621000724036, + 3.401549960736509, + 2.2160376306817655, + 0.9212426942869542, + 2.7430630793423205, + -0.5279990283280503, + -8.705539904925752, + 2.4868955732930313, + 1.9190472369020852, + 2.6080540180978486, + 3.2231396825311593, + -9.276648677138272, + 0.30053384369225394, + -5.1928361913065695, + 0.7561482591752087, + -7.571335606375206, + 1.046280867035641, + 0.1068853107329791, + 0.017020521216728503, + 1.8023357156616873, + 2.3724127779605406, + 0.9466561207261283, + 1.110832640530842, + 0.4122690719532064, + 2.5583580087383386, + -7.3448006321193695, + 2.7344695021869314, + -4.7003697238665945, + 0.20345204016224966, + 0.629245474557074, + 0.9436280743194153, + 0.1950924896942039, + -8.39321990754313, + 1.2933036946172936, + 0.7299735362981703, + -8.126373566878598, + -7.3662003803191425, + 1.6881029482355105, + 1.3907142000183166, + 2.5272762201075922, + -6.930322251680463, + 3.0775809978141795, + -0.07918514822520616, + 2.953742730539597, + -0.0017677535678961968, + 2.1886055501621033, + 1.7453622702042706, + 1.4951683077935347, + 1.9836262513675331, + 2.501592487882715, + 1.889625190366337, + 0.7353185031725933, + -6.550417322255917, + 0.7162273209727067, + 1.991404254201194, + -0.37267481858485996, + 0.5938391769786189, + -7.411420218588439, + -8.212939911852136, + 1.7480198752263079, + 1.370594139234737, + 2.6366163782948586, + 4.02933338975498, + 2.0320398581703496, + 1.6817961799238048, + 0.8887318601823143, + 1.8216291886789278, + -8.027511231072134, + -8.486856587037046, + 2.735438151582886, + 0.4550883264617053, + 3.3275023814892997, + 1.914579541768007, + -6.3584026650996535, + 0.8204178209761795, + 1.3315424166556709, + 1.3732448825623564, + 0.9586113508612994, + 1.2104802142030255, + 1.1705199569069644, + 3.2612705045348678, + 1.0492599729227439, + 1.3956439354853487, + -0.026480386394438813, + -8.989715611105217, + 0.8107151133592344, + -7.903104872958988, + 0.5039574785355939, + 2.4828705420315065, + 2.4115742227072956, + 2.8268743166688304, + -8.105979455165985, + 1.8751676391425214, + 0.47027295054406565, + -7.137759512944296, + 2.778992280455717, + -6.954649903722504, + -8.99402332832767, + 1.737009099149306, + -6.436508767115393, + 0.09142569258522178, + 1.4447896979783696, + 3.4407425738326562, + -2.6245975457760076, + 2.7538737258437753, + 1.4538389571409434, + -8.714958685582125, + -7.441383816313331, + 0.7510754908666685, + 0.3637562228618128, + 0.8399532151071638, + -8.08883603972419, + 1.6310142994433159, + 0.6705970298646083, + 2.1442899819450436, + 0.556478231632356, + -7.145388982529729, + 3.0647569182319536, + -8.977111424649609, + -7.711063727771787, + -8.833675637290202, + 2.1741483255925496, + -6.835536842196359, + 3.445543676407967, + -0.5822709787922326, + -8.080212990395578, + 0.713903340693877, + 0.40152969027500107, + 1.036963501913817, + -3.9835806018617936, + 1.4744144211922672, + 0.411592050621132, + 0.9364087576033505, + 0.1137082665706626, + 0.7301752132908366, + -8.266130955066824, + 0.2735006004760863, + 2.184378247744145, + 2.354302848998888, + 2.5485981453437163, + 1.2137100558133114, + -7.247752798634288, + -8.43194359917085, + 3.383507975280563, + 2.2450433484087418, + -9.051751477158193, + 2.8161048993789337, + -0.4262984388451197, + 2.464041660712454, + 1.994465005190346, + 0.22374471688588723, + 2.643110468109436, + 2.0702070585902668, + -7.581863095749053, + -7.512666918910498, + 0.6128565944808063, + 0.3835269276924121, + 1.6392539494894134, + 0.8320845595954978, + 1.5106365905385972, + 1.7720586658631, + 1.0291963010592182, + 1.6418052848350662, + -7.188246151127988, + 0.09198371062898206, + 0.5215544251331047, + 0.6371849805552529, + 3.1589727819067033, + 1.268813153780352, + 0.16681320384399848, + 2.1280869821189468, + -9.082202106655455, + 3.3172849637356663, + -6.590274113813707, + -9.111097213275565, + 1.8592883936797295, + -5.673139437865187, + 0.2980193119458896, + -4.389165578227743, + 1.8052387859523857, + -7.713983531304735, + 2.2016552664769455, + 3.3577319679959863, + 2.7035250626548364, + 2.8625973320310107, + 2.219272719282033, + 2.0324556166791155, + -7.081447243116417, + 1.9937689198645958, + 0.9748403495325826, + -8.927681326460663, + 1.3041703425278484, + 1.82168902095041, + -6.571880276673609, + 2.1953614271043365, + 1.3750668155272605, + 1.512305343143351, + 2.1735048519170364, + 1.2434736921614349, + 2.245753919843488, + 1.0011469888166296, + 1.5077489169341487, + 2.861366444731574, + -0.12975319864357043, + -7.759798128516371, + 1.0486401152193379, + -8.567409130119385, + -8.776709766409404, + 2.0523959720629676, + 0.9702463176224747, + 1.2464262446181968, + 1.2126888337289463, + -9.064260688467613, + 0.2681989849780224, + 2.0648972895331412, + 2.0441326659108716, + 3.0831011651576077, + 2.830334712439813, + 0.08059882892829817, + -7.234692017738882, + 0.6632578036213854, + 1.7306600879590341, + -4.864581457973878, + -9.091852992643224, + 0.07669833990740652, + 0.02245052822235808, + 2.8223097687713388, + 0.6186772967822212, + 0.5475132602144684, + 0.09821139171848503, + -7.1523470860208445, + 2.6794128585643873, + -7.894703748856715, + -7.997464605281789, + 1.9629575179131227, + 1.1035892676473205, + -8.637887747398574, + 1.2205319340415708, + -0.2938745402049933, + -7.00559485207825, + 2.818656762828998, + 1.3729772425199083, + 0.8097641508866747, + 0.8410347707617063, + 1.0747633751558396, + 2.773461678987149, + 0.3807626926184541, + 1.959034468961967, + 2.050909653235062, + -7.630535795118942, + -8.455389050665026, + -0.13534377253691748, + 1.0909180055505898, + 2.2125493767531754, + 0.05717055980198288, + -5.835409235490379, + -8.907589902674092, + 0.6672827627892893, + -0.49655582121382663, + 1.1645365854688123, + 1.034959444203318, + -0.4064455989921577, + -8.907457084740749, + 0.09626630962876037, + -0.30648636877226526, + -8.087071864621759, + -8.984939584746886, + 3.254164419936194, + 0.7377131144975152, + 0.12653586552659382, + 1.5657379656562431, + 0.16448804787290544, + 2.5266905700100337, + 0.20972239604758247, + 1.137118048499744, + 2.7570215436697003, + -8.655373197480369, + -8.228388519208059, + -0.5078345067119657, + 1.5229586758713414, + -7.971332052123057, + 1.6870682706355173, + -7.545705194606324, + -6.862974180738561, + 2.0236854098282038, + 0.33415671891411824, + 1.7267148126287122, + 2.698653301230399, + 2.8048534205439735, + -4.087625024003784, + 0.16022206804150776, + 2.841513133217909, + -0.5047976304668866, + -7.263502458048841, + 1.123338299216964, + -7.710038123638572, + 2.0799806194502155, + -8.73419889042598, + 0.6330292914853181, + 2.1243368489395085, + -0.43313493099211564, + 1.1823642197137227, + -0.42859023726553575, + -0.5091823498451181, + 1.9973258492370753, + -8.38836915444216, + 0.7375029392148328, + 2.1736812780490955, + 1.738419181698482, + 1.6883070352583138, + 0.5537049352527356, + 2.6506657614629896, + 2.8142428026527817, + 1.2235332414634719, + 3.397442150460223, + 1.030353665351954, + 1.3116442758866809, + 1.6452859224830017, + 0.5011618044043213, + -9.088557246507733, + -0.01240447994027973, + 2.581084952945822, + -8.463302117338767, + 0.7118590055640489, + -8.130625360247175, + 1.0834207438804553, + 2.640358178533733, + 0.5653084044265729, + 2.6702250700203165, + 1.6468270224796138, + 0.846414266068259, + 3.1615434834361, + -7.691410230354492, + 1.2557341558261315, + 0.20992282186654185, + 2.477869816844542, + 0.3771536857972718, + 1.9971442195608315, + 0.12915305927423792, + 3.2829579226358123, + 0.07537013445606805, + 2.3092976438959476, + 0.2329424582448878, + 0.8357888209020299, + 1.2180893842173306, + 0.04856351346397796, + 3.5744979379043267, + 1.669535155953666, + 2.3207366356258663, + 0.32681234021190014, + 1.5151106657587101, + 1.8281001077630386, + 3.831754713753577, + 0.8185537560162401, + -5.962281540225824, + 0.9255740617320212, + 0.7820050943170265, + -7.9288721156215605, + 1.0142181524892333, + -7.707662624985399, + 0.6503250943985074, + 1.380100923803612, + -8.05200622560956, + 1.6043286625304296, + 0.8451562069463898, + 0.943690736995706, + -6.704163983926268, + -7.651675754191798, + 2.8069842371494023, + 2.9804034094200733, + 2.512406670381577, + 1.6303008295865888, + 1.2743826657141013, + 2.968429649588173, + 0.07981202025095484, + 0.4659555505286844, + 2.4135861375911265, + 0.3470879379696273, + 2.096925716384769, + 1.7104732337010358, + -8.93244670648019, + -9.054077418131602, + 3.3272023035340923, + 1.9533029770509784, + -0.3578635861118119, + 1.7387635157985106, + 1.0622233414382256, + 1.166330984649243, + 2.290374493758918, + 1.6007386358604836, + 0.4069471299462136, + 3.183141909139203, + -8.289505399777722, + -8.735818891934889, + 1.1927270785407693, + 0.8117335091610367, + -6.839778391731439, + -8.560621059700626, + 3.2327524190054517, + 0.2351280054174659, + -8.069124435396784, + 1.5934052451891958, + -8.016886503130472, + 2.5166474443118187, + 1.8077703556852782, + -6.89917040548509, + 2.331295274393149, + 0.028341084904075293, + 2.45294414025638, + 2.4982955100934663, + 2.6528746304421698, + 0.86949660097944, + 2.9663214606223556, + -0.3283375313402265, + -0.38263116649533446, + -8.617634243641117, + 0.9891606519058725, + 0.11258801303709674, + 2.5995061442041325, + 1.1101383528502828, + 1.7915850107514966, + -0.39955038419449096, + -8.952822233799061, + 0.4338327371453445, + 1.5069996136757637, + 2.2547717788501167, + 2.7922905917041314, + -6.966528492028682, + 0.2707484789662109, + -8.526624100991679, + -4.546150834872335, + 1.968289743381796, + -4.085814940218094, + -8.75651859188767, + 1.0492010807174819, + -8.467274974407134, + 1.593124734813376, + 0.3481380884273988, + -0.15583301716388614, + -6.592603378805345, + -7.117952854897646, + 0.6178371906010927, + 0.9282037231419841, + -7.644178100661795, + -6.847530121470798, + -8.612602760529702, + 1.9615040056293946, + 1.0813198807975406, + -0.07916973209749419, + 0.22878581671923293, + 1.5852329652618449, + 2.6568979004682602, + -8.247382625035774, + -7.31905533769166, + 1.7230334499316773, + 2.1442150754629377, + -0.3913867311853231, + -8.565731523795018, + 0.48836673876161385, + -8.550981123529034, + 2.0765156901835993, + 1.1791232493554893, + 0.4628133554809247, + -8.570612481576456, + 0.31743916249521, + 1.3689304528387372, + 0.9202159446125323, + -0.2270501708321309, + -6.940721954427759, + -7.061116366059591, + -0.2987105617995114, + 3.283450614839531, + 2.1621752246384274, + -8.016471862737987, + -8.167882170647548, + 3.0123025587277055, + -0.12776957890467389, + 0.6576203647895076, + 1.2206369385103255, + 1.7771949646291936, + 0.870129676006453, + -6.896514945865017, + 2.72395569606518, + -0.708821266359798, + -8.178817525909968, + 2.599012631508349, + -7.4413028157194345, + -8.860103070305422, + 3.0123938603834906, + 0.6840626894874334, + 0.10459117875634981, + -0.3227860929645202, + -0.11296946986870815, + 1.8379339717420056, + -7.758420382491858, + 0.2711735110428051, + 3.008850738872426, + -8.328873089908148, + -0.6434888208653092, + 1.381037343424551, + 0.4312276140533374, + 0.5825181663460871, + -6.970927901949955, + 2.0695117930134517, + 0.6152340280892129, + 3.49595199368453, + 0.6938727172986299, + 1.4475996148194237, + -8.609142018851355, + -7.7772768844184315, + -7.544894885797945, + -6.908331226910449, + 0.9089285567242396, + 0.9387047714748711, + 3.7148527852348368, + -7.2476532791433765, + -8.998549396936156, + -6.595387338748019, + 1.438191259922225, + 2.288214765306113, + -0.014760414413903546, + -7.770974287483711, + 0.6453677444470884, + 2.087141400809658, + -8.01699871457284, + 2.400436142220324, + 0.9203163748065302, + 2.9250246221734177, + 1.739337623547591, + 1.0263075400063275, + 1.2133803601560305, + 0.8477548670296504, + 0.2776668814036649, + 1.476863079748618, + 2.38986676603255, + 0.8190010540303145, + -8.600849114974137, + 2.466931763065777, + 0.6942828631671454, + -0.48472949679486216, + 1.3690461720825393, + 1.1386794631201784, + 2.3000790452898423, + -0.5737801992789094, + 1.4894503198246298, + 2.20744903509327, + 2.346612854012468, + 1.9262174442593494, + -8.518158953023475, + 1.2307221290531465, + 2.1206736732011553, + 0.4899710406891571, + 3.1006385409976778, + -7.521213500124812, + 0.5237439927790518, + 1.1519603194807617, + 1.6256078327550763, + -8.914362686096426, + -7.845587362754366, + -8.485506628202467, + 1.9268753736882878, + 1.3412107413635537, + 1.7626357558833163, + -0.3848726650847515, + 2.0535608777636822, + 0.6386486562532143, + 2.1668214225250875, + 1.7621364606735226, + 0.144604733364891, + 3.2287546365823094, + 2.1172647727359086, + 3.2498858720172166, + 2.910690182828211, + 0.5560038424597556, + -5.053249259330138, + 0.1267741317413434, + -7.7520600294259765, + 3.0099939457558884, + 2.530862687511373, + -7.569045043727168, + 3.100572766345718, + -7.598035533924212, + 3.0812254019580134, + 2.665871025642842, + -7.783751872917371, + 0.7792923984254838, + 0.13550411979575336, + 1.9555605803234042, + 2.8109919605641784, + 1.4302939303354323, + -7.077889093884513, + -8.81451549884432, + 1.7948047097881592, + 1.3136659105045494, + -0.05431808661282797, + 2.1745446641766275, + -6.744529238152495, + -7.639481164896453, + 1.9508598546238556, + 2.994506265077541, + -8.047126695755605, + -7.9055428000056445, + -6.880506675107962, + 1.2064549161519937, + 2.40303126836893, + 3.721222591763574, + 1.0392622357574917, + 0.989513670148371, + 1.111295715578283, + 1.5197712593885788, + -0.003830666980349542, + -8.871502646042059, + -7.710228767479039, + 1.316567130437144, + -8.153346603694768, + -0.3065364380133689, + 1.1701046228289196, + 1.4383032441394334, + -5.3338371145364105, + -0.4210221997984218, + 1.6444079959956204, + -8.877769334741437, + 3.103492802343518, + 1.516321164532454, + 0.04550566552419573, + 2.0988285005728082, + -8.805073663059847, + 2.238823922089626, + -7.760574953561919, + 1.7399929534231842, + -6.362716239042421, + 1.342770941232623, + 0.2942419947671681, + 2.460133192304222, + 3.1160109542981256, + -7.863404589522134, + 1.339783928604639, + 3.1828691359625, + -7.419356727249834, + 0.7064775403365423, + -0.30887312014924456, + -8.456035013610844, + -6.53454968512149, + 2.178316715644698, + 1.6842159505158856, + -9.021975554572357, + -7.507648493713097, + 3.202200540495128, + -8.100426309998456, + 0.9594422746731098, + -8.845494271765292, + 1.175056685814502, + 0.04214137587627019, + -0.5673660022469825, + -9.20656186730306, + 2.973187278981339, + 0.5737719406547798, + 1.3587860979146245, + 2.7985813475724375, + 0.7094710290003688, + 2.0531183191182882, + 0.8511068042526803, + -8.590135642397662, + -7.189965033714168, + -7.154677422733394, + 0.19090233780148586, + 2.931853633383166, + 2.2561671508640466, + 0.901416475523869, + 1.7027262016250704, + 1.971219776112316, + 2.4174107152765125, + 3.0487575561656595, + 2.924163384267368, + -7.054310537828337, + -8.28566569244672, + 0.7085209499196015, + 2.6833955075498936, + 0.8698284742955038, + 3.0152347189355497, + 1.2579402799717339, + 3.2603225427799205, + 0.1782881730598257, + -0.42615362191586, + -7.6490739603896785, + 0.3077973439238357, + -8.900953410878762, + 0.20201870504381775, + -8.423384087563129, + 2.722527284583259, + 1.3910530785859019, + 0.3946761807726644, + 2.8332292593118793, + 0.8936884956897678, + 1.1787387461648193, + 1.567348877300945, + 2.2045001284416883, + 3.3631224988915234, + 0.04816168052311782, + 2.1389438147027393, + 2.731851521918433, + 0.6069927084759039, + 1.298579162371399, + 2.476184318231664, + 1.607135746688546, + 2.873246076164953, + 0.20254332006482556, + 3.2014673304770636, + 0.6497769827541435, + -0.9030778286503153, + 3.431642966558377, + -0.5037706052510608, + -0.2718177142045583, + 2.3794794835113224, + 1.5218843153608947, + 2.7417262156712914, + 1.5111841271116933, + 3.3016623670243375, + -0.8018726566463645, + 2.0817782033989625, + 1.4761472522587435, + -0.3830622843308061, + 2.96125695796714, + -6.458820226724634, + 2.8812453262878064, + -7.737636277265019, + 0.47866633406865, + 0.7686251627279825, + 0.6575443015225664, + 2.717223225558806, + 1.4511419170513968, + 0.5653414107484783, + 1.7957559372188365, + 2.802780980402567, + -7.803092629931442, + 1.869341396799709, + 0.8432598728970127, + 0.7274576465338105, + -0.056347721327875196, + 1.8111595457186436, + 2.012187082121611, + 2.6766884228160452, + 2.2395446030241155, + 2.0220035695704683, + 2.551669192710943, + -7.084275988118852, + 3.2160814880125, + -7.512818306561267, + 0.2651693233580314, + 2.709930601235107, + -8.25308000044569, + -7.931544018548265, + 0.611974022845457, + 0.6022362872430828, + -7.881514129199376, + 0.2976247285209178, + 2.425138890802724, + 2.6235880841735737, + 3.035755884725319, + 0.08067025748216224, + -7.378277352583638, + 2.684047451077735, + 2.1405945720061563, + -8.180315407048122, + 2.8092973588605585, + 2.0990750518716936, + -7.957518989117321, + 0.47518320727383545, + 0.3367399214742779, + 2.2771983564378764, + -7.800606427520901, + -0.7464765130831107, + 1.448488093446764, + 1.712299710929907, + 2.3905323834778778, + -8.509083225983312, + 0.5528745670823761, + 0.056218070305737056, + -0.6790425174878887, + 0.3472145623189714, + 3.0225355309737423, + 1.633286832099552, + 0.5266672630777911, + 3.6459537790447833, + 2.236649898911319, + -0.40944473333247927, + 0.6850113779650434, + -8.21619992893973, + 2.1304260290468564, + 2.723864737812909, + 1.3399722497086435, + -6.775697936934701, + 2.3964616584226683, + 1.3842313378533297, + 2.7615107981919227, + 1.7408744101042368, + -7.738718786684235, + -0.919610965593588, + 2.0792344954104793, + 0.9850629393893267, + -5.201540497852601, + 0.1823978516841003, + -0.22885521322494967, + 0.21747576894502027, + 2.19322258510467, + 3.2986334553791505, + -8.619684862683377, + -8.309541341330789, + -7.835321833014975, + -8.764712564101124, + 0.4055112088733752, + 0.09600089149065433, + 1.0886691275686606, + 1.0145068198945755, + 1.0888323325702034, + 2.0683632581969045, + 0.9895286989042258, + -8.600637855511625, + 0.17563402960720662, + 2.9883877363976623, + 3.0891431470855233, + 0.4400202797717173, + 2.029800770955799, + -9.112364152978662, + -7.851845284401957, + -8.887578129757253, + -6.59489218895868, + 0.21776659706458837, + 1.9634443338982246, + 1.0414843328983996, + 0.469215937663879, + -7.298687916443551, + 0.7135889748814117, + 2.4196631407822804, + 0.3076025091214727, + 1.3017289266943042, + -7.020706662519961, + -7.268765471299113, + 2.1993518896259134, + 1.3378718213097844, + 1.1520314971425436, + 2.4649510119124662, + -8.44771242798184, + 2.148046492351158, + 1.454621827239103, + 3.0266238416161153, + 2.2834842529185346, + 1.6493444110044522, + -0.2807564957450657, + 0.9869961857361259, + 1.196311234640384, + -0.03138504857722978, + -8.302173262858318, + -7.147662968691874, + 2.4203753479775765, + -7.200195535346051, + -6.121405179007928, + 1.446540869790915, + 2.4946091536424513, + 2.5317860291442043, + 3.08885700076078, + 0.686600318537168, + 1.9712547495650867, + -0.3406283631713244, + -0.1917046102063393, + -0.34626361744265993, + -7.146658637459202, + 1.3912626769594616, + 2.101908290357388, + -8.951955330528236, + 1.3382607486557812, + 3.0197774381417752, + -6.705803972980068, + -6.319106555034937, + 1.3228667939258831, + -7.181379021616384, + 0.8231254810629439, + 1.6096906991986024, + 0.022087670079872737, + -6.322223274773663, + 1.9142490369319745, + -8.8395741689585, + -6.7066508601620445, + -7.861254754341756, + 1.1913256927324036, + 3.3503200114900453, + -0.002659230132193009, + 0.7771927695492519, + -8.499741678847203, + 0.17494165662536784, + -6.35141558349304, + 0.7622898073252403, + 0.4285016657869558, + 1.149946839332002, + 1.7000250524900118, + 0.609892291556713, + 2.2464330993860964, + 1.2761432007451525, + -7.398300341150756, + 2.5135528106361136, + -8.818914883272242, + -0.5170330719567374, + 2.1053277386851357, + -9.129669203870366, + -7.274116432853194, + -9.138206918075909, + -0.17477826504362012, + -7.755516593671772, + -8.460406129073084, + 1.615219452962977, + 1.0557400143215432, + 1.3559188758553349, + -6.746612576630017, + -8.427016362142876, + 1.5538988907147384, + 0.9101032737721856, + 1.1443464196683368, + 2.548440430962473, + 0.2644421770757484, + 1.0395390384388348, + 1.3279202568763526, + -8.753212949759881, + 1.4004389951274052, + -9.186945950420203, + 0.1958855237232713, + -7.954922238118083, + 2.9082245870181613, + 2.3287302443115694, + 2.6926289649200346, + -8.148594251795902, + -7.839235694550561, + 1.2882149500697657, + 0.11489142233158645, + -7.791723998974786, + -8.273808442110104, + 1.4749481617406814, + 2.308452285743202, + -0.19107627716397657, + 2.0404769868284305, + -7.985717654331616, + 1.2107905054682147, + 2.7728430561860087, + 2.3684759764589596, + -7.816121343046642, + 2.1400333002462677, + 1.3579178690393816, + 1.4137790688956473, + 2.0282163403938696, + -0.32634073086743637, + 0.13456806137205918, + -0.07652955293384209, + -8.729145156318214, + 1.2956587419826326, + -8.051340353153803, + 2.7730771407884194, + -7.954770826966083, + 0.6108161558439745, + 0.4636852274811087, + -6.185525634375222, + 1.9723213500676102, + 1.1661462020860072, + -0.127179504501362, + 0.22175017909053582, + -8.439166693494329, + 3.6310621292827308, + 1.1202246125640114, + -8.696202059696393, + 2.4645177606532807, + 0.03848449495185448, + -7.061346258741949, + -7.880999495057979, + -7.652141323360686, + 1.9346967782332938, + 1.8519134375341029, + 1.045358025225612, + 1.867882738618223, + -0.29347325176852873, + 0.8858271759641015, + -7.369184353233702, + 0.10839110864985028, + 1.1973366351322996, + 0.932204017410818, + 2.2627473995350016, + 2.947677434897453, + 0.9432060861694127, + 0.05395284885089928, + 0.889687036155159, + 1.7276491304778132, + 3.4987625677755085, + 2.346194337240592, + 1.4351247091556645, + 0.8923018476658329, + 0.8265583627496746, + -7.870220121771367, + 3.1760069390670287, + 1.8509322432764934, + -7.808546169673091, + 2.8237190425263408, + 2.946952661753927, + 1.7308140156045613, + 0.9977879893701315, + -0.5708308107304996, + 0.07889506960533402, + -6.643437055613775, + -0.56528230535463, + 0.870224700285937, + 2.6916260628152835, + -7.828681811589555, + 1.3279773734160638, + 2.8973836999999003, + -7.929287638220337, + 1.1193036378595262, + 3.594043711978328, + -8.13911330674277, + -8.90486254564379, + -0.2957064939222399, + 0.8663816353200503, + -8.521579935310447, + 2.3157669071904206, + -8.119140992084624, + 2.6021021719824646, + 2.2603643030736924, + 0.8741761862261239, + -0.06006143611101942, + -3.1598465288412205, + 0.7185513031937687, + 1.1488660787139893, + 1.439320893331675, + 0.6558899732621498, + 1.3376290043476202, + 0.33866355369779044, + 0.49100124614685187, + 2.0732762735938026, + -0.17658301058699613, + -0.15211639502969043, + 0.26358826632899807, + 1.5898050628603841, + -7.571158183163497, + 0.5023378932875282, + 2.5664867528077773, + -7.752126581163239, + 1.5202060246679063, + -7.586353065525089, + 2.458743346964622, + -7.117655044831394, + -0.2766314552971195, + 1.4995385114633468, + 2.047751662904562, + 1.7034799222273926, + -0.20718681963969204, + -0.09499902849411072, + 3.231440450246778, + -5.725631186022914, + 1.152706025263952, + -0.8436966252912544, + -0.42918929142871276, + 2.537290305930872, + -7.628772256548188, + -7.374689762871147, + 1.061013263717064, + 1.962147458108563, + 2.3280301886732393, + 1.24919179774868, + 0.8767430061555562, + -8.06549023313639, + 1.0800306557640134, + 3.1190590402572766, + 2.7199558294175286, + -7.759909637482053, + 3.189223805081286, + 2.7779181975289386, + 2.3847605509285104, + 3.329954274336646, + 0.9938937020826742, + 0.4984620320293087, + 2.831849103378229, + 2.265552368615562, + 1.6462215082838754, + -7.707758932585142, + 1.0978755588155906, + -7.9210876752988835, + 0.5379123234590063, + 1.8476018914385226, + -8.715940582759224, + 2.2899960983145, + -7.010182624606673, + 2.1097220914326766, + 1.8062889811487706, + -8.667243244723918, + 2.5551923428204635, + 1.7707424165846375, + 2.917138503950332, + -0.6786053381483399, + 2.7201581612918875, + 2.7011790013409924, + 1.2685343752484828, + 2.609497641079454, + 0.328462313212367, + 1.8540862208537516, + 0.9111878083785402, + -0.28426319299110525, + 1.928068219117086, + 0.9823570695344068, + 0.8036169357123898, + 0.8638327636608106, + 1.5631105979370687, + -8.579141238483885, + 1.9726952954724573, + -7.98143064290487, + -8.411479380206911, + -0.31646714121733316, + 1.9135385993316474, + 2.089089710673739, + 1.9960922223992736, + 0.6950167358804251, + 3.075747505339304, + 2.340950812292796, + -8.993901506596375, + 1.3335940745655013, + 1.5029656732086905, + 2.547186244573038, + -7.177198157100625, + 2.020910148648964, + 0.575026245669857, + -8.722887642994142, + 2.3931476392962545, + 1.9883487781163314, + 3.185344417621772, + 0.4611860136178839, + -0.5182075979179117, + -8.30489727021605, + 1.8201609543537527, + 3.0689388476915873, + 2.173698425464305, + 2.706005868873169, + -0.618837066000761, + -8.14167270676203, + 1.1838694232674125, + 0.5789388369320245, + 3.504126154437088, + 3.588433303842934, + -7.5506588361234215, + 2.061290460031166, + 2.646008007089369, + -8.528312183408428, + 1.7989478462750759, + 2.3431068088473297, + -8.607046603987758, + -7.233462314309418, + -8.236210998622116, + 0.7601890495346417, + 0.8199533451747715, + 2.8988379269433366, + -7.2460258547667395, + -0.04260741710161611, + 2.543539577935952, + -8.565529430160256, + 0.8452684387141972, + 0.02137488235097795, + -0.4171229078550339, + 0.5329862737316927, + 2.429445635061916, + -8.702289720690896, + 0.27413419442950115, + 0.778047647796611, + 3.1213402599631923, + 0.6452591727752793, + -7.067189783892758, + 1.9131896533628667, + -8.563014949448029, + 2.5352804472036112, + 2.2290075909502245, + -0.3470878959227531, + 1.105668261238746, + -8.376087342183945, + -8.15269169773546, + 1.3711035698490532, + -0.36426272454991426, + 0.7551506444748286, + -7.757463803645644, + 1.5490100734886154, + 2.313830351044823, + -0.8541111915958685, + 0.496654227916699, + 2.659244403352773, + -0.4430331751416259, + -6.629243243300451, + -9.01836562393277, + -8.267930482302894, + 0.3592826993210026, + 0.10107159656091669, + -7.887902440252809, + 0.5279084501674415, + 1.2915579555085936, + -7.355768275596031, + 2.469362161506793, + -7.102518289138929, + 1.852060918877644, + -8.718197701309421, + 2.965847630372054, + 1.6860490514540332, + -8.510593223376, + -4.536688807086601, + 2.0334442262280485, + -0.3426201204341113, + -8.453688176881816, + -8.342615611274992, + 0.9115626260883183, + -8.66913823054388, + 3.211134229958071, + 1.5064447559646312, + 2.425614930885676, + 2.1285963080005565, + 3.6280943058081268, + 2.3581021902785757, + 3.443999940056457, + 2.4524429553642957, + -7.630602134495716, + 0.9749722474485204, + 2.023063317973442, + 0.4449547911800854, + -0.6651643924572769, + -8.977257191576923, + -0.09940376983580983, + 1.0983446316458962, + 2.957566880493749, + 2.231581703132769, + 2.992648636070979, + 0.10029857579906186, + -0.10486224630613837, + 2.7052214883187347, + 1.7321038147581467, + -8.553642928754616, + 0.7678399545232438, + 1.7369801350368201, + -8.959210380086098, + 0.39429857882813435, + 1.5829811856819702, + 3.2572325544113223, + -8.304367400379208, + 2.3880265079951997, + 0.9446753401638908, + 1.8016424209164952, + 1.8749987305003346, + 2.5381615514093196, + 0.9140264280810966, + 1.6888308847238722, + 1.4701940930143365, + 1.3672739186583218, + -7.978877810221675, + 1.751007663957888, + 2.762920710051229, + 3.1207677452514786, + -8.294409328210083, + 2.118279801483606, + -9.136774027043517, + 1.2960232456627068, + -7.6203490396486115, + -7.170062781547793, + 1.4606818503734946, + 2.254454243477766, + 0.4888493896618929, + 2.445951735545381, + 3.130901934841017, + 2.0169064244671624, + 0.5781320235978845, + 1.164559165113405, + -7.085727391465785, + 1.5563134560808463, + 2.5246786453408587, + 1.8306790539513156, + 2.063158354285914, + 2.951222072815112, + -8.011694407902795, + 0.6912192194209621, + 1.7567867998704312, + 1.227745741829922, + -0.15483148523896537, + 3.080062309803283, + -7.014075101217134, + 1.7153006739574652, + 0.42563776715604157, + -0.20460280950116075, + 0.08112706131989966, + 2.3249243480067583, + 2.8599522371258654, + 2.8250697629458648, + 1.3263597524146535, + 0.6689904965545804, + 1.7211640556971661, + 2.664252570995877, + 1.9021155590745604, + 1.3039480575116782, + 1.6186179551129143, + 0.6994200226046031, + -0.2762234251771253, + 1.6463651245781266, + 1.714335455602332, + 2.863930022411937, + 1.5345625659420514, + 1.9874458198165799, + 0.6236563963009678, + 0.5487365154798838, + -7.699812241244133, + 1.9022132491593855, + 2.0582611608232324, + -8.241695983493726, + -6.6537679124807, + 2.1727184060924447, + 0.9273336782436994, + -4.084132513497494, + -6.170446784167851, + 0.16227905402765097, + -6.5818959164282065, + 0.3856228859005982, + 1.0640002227515153, + -7.938741293583762, + 2.093082240549366, + -8.7906152886363, + -8.901765187372959, + 3.8337110169924444, + 2.1154827591984047, + -0.024563247863549253, + 2.265518351996009, + 1.9006504722937423, + 1.917766167905046, + -8.633956025491374, + -8.851789595046611, + -0.38659176008223367, + -8.87236621013963, + -8.501176765733607, + 1.9191111526959272, + 2.0092982704073235, + -7.981956809324846, + 1.4206166475637727, + 0.6387858496083604, + -4.240260019432114, + 1.3929661181125392, + -8.64662315068952, + -7.421202866081349, + 0.007880371938225328, + -0.19056302410489345, + 0.503120685047802, + 2.1688687868026357, + -8.292235911623273, + 0.4841239485526286, + -0.21587616980271407, + 2.6559586365930166, + 0.8458314061382362, + 2.06133046178105, + 2.6770161025917525, + 2.2054628111229193, + 0.15780246248383983, + 1.8897321611408149, + 1.1275903344847946, + 0.7875862341118278, + 2.1134528176796077, + 0.043938642482980586, + 2.215455707867413, + -8.456452178054775, + -8.692009213476931, + -0.5101357762192221, + -4.421699075118912, + -8.284672622364882, + -8.836080267171257, + 0.2811631865005029, + 0.7142526358338925, + -0.30874882508807805, + 1.7719153462076291, + -7.779768112165598, + -0.2753804205635559, + 1.9260020511696359, + 0.05227095637109941, + 1.1788794487568979, + -6.726659315471383, + 1.6706662165839776, + -8.947952635047871, + 3.162863553794859, + 1.5389964899535564, + 1.2544440603559768, + 1.7145010953616646, + 0.4822413648004758, + 0.8802826978220544, + -6.928476477946392, + 0.10778270716980459, + -0.39126373206777293, + 0.8622491527567213, + 0.31010488292438115, + 2.03192764830168, + -8.796503623860978, + -7.25126358436173, + 0.3351051631162634, + 2.401849776359339, + 0.9593991430845032, + 0.28888122690848994, + -7.88160986575865, + -8.795296799403625, + 1.0757305446731973, + 2.1924085737667283, + 1.0972505543442912, + -0.2774989094254504, + -7.066242301005415, + 0.11467791494875802, + 1.6091405789212163, + -7.260729719065053, + 0.33316620169517214, + 2.205281933674402, + 1.172124004482102, + 2.4696000397910645, + 2.3730224401479516, + 1.3335890617350563, + 1.7094335595127936, + 0.8208981431645013, + 1.8647377935509077, + 0.36747321127457044, + -7.060084906231471, + 2.7804890302269643, + 2.9671177107298687, + -7.505782900572595, + 1.0474434372261008, + 2.9724405167414307, + -7.635142339082416, + -7.217456677559582, + 2.727741118573167, + 0.8465807600627744, + 0.7523149050591599, + 0.5201613891793713, + 1.925880550199435, + 1.9625870722855867, + 1.010200439428451, + 0.22032048692175552, + 0.7273647588069262, + -0.1355918572976582, + 1.345341378000972, + 0.5800264441178827, + 1.380656425038407, + 1.8792469186637635, + -6.514298843285813, + 2.099765193816464, + -7.509190765310397, + -6.890897830401605, + -9.222846612291947, + -7.172078812604409, + -7.789712879680477, + 1.254734993818503, + 0.5531895666629048, + -8.612093971837751, + 2.0864710531435913, + 0.45494547747797875, + -5.508790997390626, + 1.1149993703403722, + -8.0440977364811, + 1.6559663293874263, + 2.420212513816951, + 2.9853181372794784, + 1.515004941287756, + 2.547898257908868, + -6.622344884313547, + 2.279783867318617, + -7.461243943269229, + 1.083537216055773, + -8.539160423577666, + 2.5671093694551743, + 2.5120642770654174, + 2.7201046348614373, + -8.83034854050044, + 0.40813570966610907, + 0.1478548923910506, + 1.378802988881486, + -8.145853074840295, + 0.5859367793324454, + 1.2685315732446376, + 0.5816188712363226, + 0.6475648145998375, + -9.170946475716914, + 1.1255854211133745, + -7.204255242285411, + -6.735249012873698, + 2.835288408826582, + 1.3975350610213035, + 2.0655171510622794, + -7.142744911960321, + 2.5084973350342827, + 0.39401025608934637, + 0.8413376253131408, + -8.22411101126183, + -8.316357387285601, + -8.301731394638491, + 3.1850216861049936, + 0.31145632933967327, + 0.7490086616134807, + 0.16499663357513214, + 1.3340694243972384, + 1.067740313249043, + -8.778641494106067, + 0.5323884586681293, + -6.791677941954464, + 2.7181197902959324, + -8.120989910120539, + -6.566977462156488, + 0.19971778956130795, + 1.5142333116934048, + 3.049490121824, + 1.1362102180694054, + 0.388842475083235, + -9.19938011247477, + -7.435297148324332, + 0.5219091136830705, + 1.6676803894699457, + 2.9213805693889197, + 0.6423880183415991, + -5.6180731048586905, + 3.041626787445567, + -6.6873096359841275, + -7.085387574060344, + 3.074449590208328, + -9.049286753289348, + -0.07955063623519049, + 2.547868234063361, + -9.028291326562696, + 0.7130986820012628, + 2.132638517739885, + -9.065414093828675, + -0.021756493080876613, + 0.6630241370437974, + -8.042827548943682, + 0.12705662340916574, + 0.9458870828880522, + -0.3662118100714234, + 1.0640708262928484, + 0.7090274222032339, + 2.7672877694347844, + 2.766279528804059, + 0.1334718113791702, + 1.11615265120768, + 2.6488368076661493, + 2.2412978981691953, + 0.12403072980622999, + 2.5653841751762845, + 2.46840596735832, + 0.7664635895912416, + 2.1808954147589534, + 0.7780720929790442, + 1.154606096196686, + 0.10572028977310916, + 1.2851076877131016, + 0.556425323239768, + 1.0685018048754145, + 0.7454887942125477, + 1.7527547535834673, + -7.546222752678161, + 1.3532650448586065, + 2.3714247164495292, + 1.982143701618346, + 2.618916661184878, + 2.640381933720139, + -8.385071772460382, + 0.7444984479293888, + 1.5068169150034114, + 2.2271220796396216, + 2.7368767412298602, + 2.417331566583442, + 1.1871226601686244, + -6.650805662509953, + 1.8867538961050732, + -8.809438191940412, + 1.0340198085047216, + 0.09741528294006592, + 1.1144739131504813, + 3.568290861071225, + -8.675318927552368, + 0.27057758173492047, + 1.5034740911958426, + 0.6576685640069088, + -7.970029234276875, + 2.0543143183721093, + 2.4976178443593477, + 2.592234390534009, + 1.2794420349507243, + 1.3280661268357454, + -6.1397431101450515, + 0.6493502392033413, + 2.451702099349213, + 0.3856119997859295, + -7.846337417047921, + 2.198060056284684, + -6.961349711979518, + 1.7437468720369593, + 3.0464861871275026, + 2.257505905763807, + 0.30885351917698495, + 0.8297613717282993, + 1.5747759023875523, + 1.317957570410962, + 2.553616904435545, + 1.424238444833736, + -6.409821252587784, + -3.7121101731513755, + -8.07988081118465, + 1.1581201368122398, + -8.736322415995028, + 0.6153135417031852, + 1.1851950640560585, + -8.04273204084384, + 0.3753515125893134, + 0.6044410800186297, + -0.4462806348390067, + 2.4876025235421375, + 0.4243786377495357, + 2.397867885817016, + 3.042072995902169, + -7.042235067429063, + 1.4433894825003244, + 1.3503899983375744, + 1.9267961475043072, + 0.11303028424955176, + 1.2802237387762023, + 1.1807097565574458, + -8.396811638245762, + 1.966221028236108, + 2.670565206957964, + 1.941463422585001, + -7.301573843511433, + -6.791358138871878, + -7.753009533802501, + 2.0725098091559078, + 3.3508596926505434, + -8.336750014567741, + 1.328999925878136, + -7.498577814850508, + -6.909957244961768, + 1.6808666342979746, + -9.259426955145779, + 1.0529248929166835, + -8.659503064520303, + 1.0794283442346126, + -8.169354608391833, + 1.2687417154610972, + -0.05008806804418893, + 2.554466483058769, + 2.6463387561425358, + 2.0915278486625852, + 1.963493051516514, + -0.058111812310782905, + -6.719162782261123, + 2.1286532096533546, + 2.6711102253818306, + 0.8113490399413341, + 0.1691688087031465, + 0.7576914618941437, + -8.573974536623778, + -8.202351570300284, + -5.022030620598906, + 0.7378700433168435, + -7.896595642041391, + 2.715954389554796, + -7.328039581111833, + 0.4656709743306232, + 0.38780981951749893, + 2.715794355311965, + 0.6832808179661064, + 0.8165883110436294, + -0.7747965792331853, + -8.73586196187189, + 1.4133836545434357, + 2.772935925322485, + -8.717567004542799, + -7.534740709216023, + 2.925460725713395, + 2.8751008445351975, + 3.0793788378304163, + 2.1533793279196245, + 1.7978212944486598, + 0.7107077365239324, + 1.0427976193825639, + 1.7330570013400586, + 0.00556742814150668, + 1.5612831466067632, + -6.734421807039429, + 0.4013219184744089, + 3.5262979904457143, + -8.95170377984456, + 2.2141611432455224, + -0.08047619438343041, + -5.957196390708477, + 3.3226888221987836, + -0.37803085074796233, + 2.645414596563064, + -0.33862855976270245, + -8.46555558570551, + 2.76345646211871, + 2.40494295752824, + -6.840982915756616, + -0.3242636234143703, + 2.7750295264645297, + 1.8183999533967665, + -7.678438800179548, + 1.0160749550942851, + 1.7137173475535832, + 2.8083713130665635, + 3.1604421650794694, + -0.4229217013580974, + 2.1765062672188935, + 1.0311177140396095, + 0.6027953301818604, + 3.2893079766983124, + -8.419775319138095, + 1.1517915087767054, + 3.806484015949818, + 1.813709085411365, + 1.586889829445234, + 1.0553305281082588, + 2.0387604323716473, + 0.46255061828788563, + 2.5403778903660448, + 0.8978741659153907, + 2.9667459702613748, + 1.7935809280277752, + 0.9979198425330458, + -7.348823524408095, + -8.190100691652388, + 1.013482899172069, + -8.176589705475472, + -6.776804544087918, + 2.5175129624009247, + 0.07453293651955747, + 1.1677142732080015, + 0.8121530043524006, + 1.0728087393996888, + 1.3207866981412948, + 1.391487660213971, + -7.325262871111656, + 2.480512446508523, + -0.6726147334600983, + 1.286633878671537, + 1.2848571814950263, + -8.630068144410178, + 1.641074512334856, + -8.372520390263729, + 3.6220477954119166, + 1.5476109871904122, + 1.3313072985443402, + 0.4509802411964847, + -3.676572896300967, + 0.3431658561343909, + -7.394169280242893, + 1.0111153209989923, + 0.6236978299182561, + 3.2232768877654876, + 0.9889212142907509, + 0.9557662078999765, + -0.16552310968824946, + 2.557882614305319, + 3.170986388211581, + 0.562007121308636, + -0.6890649793533657, + 2.5864702274219007, + 3.147873301590315, + 1.8452327103988686, + 2.2300578670635, + -7.914572431346472, + 1.681388435406869, + -7.934181576720307, + 1.213953350024886, + -7.767409167529324, + 0.6125353497428956, + -5.862074779777948, + -0.05579432135103433, + 1.86187714887813, + -6.831639403701684, + -8.368366805004245, + 0.7141650842394128, + -7.2296156596447805, + 1.9658449157112, + 1.203402773136181, + 3.151848920524179, + 0.8134834215896044, + -0.0015891696015058621, + -6.6206322071046335, + 2.442034190696451, + 1.4642792894623475, + -7.662456653055396, + 0.5578606457466265, + 0.4827129085038648, + 0.2447768553210342, + 1.2446398205960199, + -0.25148268369407345, + -7.866482447918518, + -6.778773007540573, + 2.364212567590591, + -8.876829124962727, + 2.3709111578808897, + 2.443004766387172, + 0.7629475448244297, + 2.5660739576610623, + 1.543324993772585, + -7.445412828672346, + 1.3108578718592787, + -7.38077750258055, + -0.06035745322128779, + -8.065274811827726, + 1.3228399598913307, + -6.855601930353441, + 0.48550708554397876, + -8.118421982992933, + 0.47680284185583893, + -7.882497296555905, + 1.250031921419141, + 1.9815759668868294, + -7.84848669377249, + 3.367580290061463, + -8.440463565239842, + 3.0765713208166643, + 2.047624797367175, + 0.4889432739407685, + 2.5096694366000163, + 1.5221815582718397, + -0.24634193428253054, + 1.9915302143198261, + 0.6635624893207551, + -7.891419294916788, + 1.5075850215925033, + 1.2568996983393395, + 2.378860296841117, + 3.1394575024244022, + 0.37361594939970977, + 0.935152027531952, + 3.047339993260869, + -6.773412066573279, + 1.0136520227701125, + 1.6683344851535156, + 1.3800348556849296, + 0.3710586489799465, + -8.01667358521836, + 0.990252834741967, + -9.126682421524748, + -9.193666922759986, + 1.9428961494619439, + 3.587663110515121, + -0.2622805993300625, + -8.393066513474231, + -8.596771623489156, + 2.302138698806313, + 2.0258880548889793, + 2.6910996993473884, + -0.6734939697369002, + 1.04114747684359, + 1.718185888051403, + 3.48276539461964, + -6.95854436686443, + 2.4735697481156858, + 2.041149990149579, + 0.673791469689127, + 2.9897117569477327, + -8.238988517507169, + 0.9761093805477352, + 1.8216332855723016, + 2.2164179615841872, + 0.4932126968317748, + 0.3780496352396801, + 2.0649711404519855, + -0.3613929635545015, + 1.530645089629263, + 2.457243579429912, + 0.05440985134375729, + 0.06784140843088526, + -0.003004518576022715, + -4.447565313413029, + 0.7159244478392003, + -7.4391021004275935, + -9.10853187457136, + 0.34616954373534803, + 0.3874840426360299, + -0.32695879149184703, + 0.7751933946282066, + 2.9773628087798647, + 2.2142437878977925, + 2.1843254988850855, + 3.059126414689936, + -7.692981165498031, + 0.3024228093713769, + 3.605203578070868, + 1.4644727704422869, + -8.727072293408847, + -8.091615408220388, + 0.4629390055507127, + -6.980247924896281, + 2.4171511912056944, + 0.9091940581067055, + 0.045724673301188894, + 3.0929852250357976, + 3.198135004353189, + -0.21094478484423296, + -8.564324258125765, + -8.810215664648535, + -6.815470170564254, + 0.9382340965406358, + 2.558848589992109, + -6.860407582762542, + -0.11677156318837643, + 1.7632932116246454, + -0.567984892256506, + 1.268764912958375, + -6.793588858602195, + 0.44299312590662765, + -6.859297675764476, + 2.587620793885912, + -6.352486573862255, + 1.1729864504462661, + 2.340260731160978, + -8.048569125070872, + 2.318283003374852, + 3.4853185969834053, + -8.250982909735939, + -7.642972426754199, + 3.2704918520039516, + -7.899950558873467, + 1.412053293639085, + 2.454183942959644, + -8.471969075893853, + 1.8275377932192616, + 1.1836780899054724, + 1.3203521358289407, + 0.9678677193752282, + 0.481491658469547, + 0.8447740588469119, + 0.9658229991595761, + -0.24011170869178097, + 2.839010771182523, + 1.9624349399608014, + -8.599242423056895, + 0.4211472361023467, + -0.2793320314444414, + -8.797108635586614, + 2.9392182781603906, + 1.256873152990501, + 1.5665471944473703, + 2.6547265873722368, + 0.8105761486188217, + 1.721884808291741, + -8.234430197275405, + 0.5536636270100801, + -8.355106987612643, + 3.178140091519325, + -7.73626088974614, + -7.144415677325785, + -6.691323046710537, + 0.18838902967814972, + 1.3810650697628872, + -8.528176568166815, + 1.8735027217692781, + 1.8177810237061083, + 1.8528978825048201, + -7.6581702947911285, + 0.9851936282135941, + 0.9749140655529156, + 3.4804614557444093, + -8.271591218158985, + -0.0692916061467144, + 0.23636074444602018, + 2.2536748103998634, + 0.6620572261158134, + 0.5272863441054563, + 0.31987648412936154, + 0.7902815603429416, + -6.85058804260707, + 1.0999584013545434, + 1.859646243461103, + -8.559557059496289, + 0.8252105446203934, + 1.886734510600049, + 1.6134861199944395, + -7.985644994531114, + 2.971573949701723, + 1.3672364573922098, + -0.3682007887540835, + -7.103789187918907, + 0.9405168733953734, + 3.4871526094460066, + 2.1859096620649496, + -0.06289084203356823, + -0.10258851143346698, + -0.18803971518510462, + 0.8634144021978678, + 2.1582894982893395, + -0.4677742138351482, + 2.0928793604794955, + 1.2878926947686777, + 2.336889592470472, + 0.6872067448511159, + -7.748766216039927, + 3.097444263728701, + 0.5556866987589535, + 2.6712301633728353, + 1.1849033710043753, + 0.5486668885120461, + 1.0907831266305195, + 0.8434242995293644, + 2.945520310117043, + 2.8605173091238214, + 2.19486511424655, + -8.827037046270771, + 0.8032457482846421, + 2.7703795059818543, + 0.0388776722783144, + 1.2645413629366868, + 2.1265867903136657, + 2.3385576793976584, + 1.917127276522154, + 1.0529020988066884, + 2.0148206403888405, + -8.191291663926195, + -0.12305657129402683, + -0.04938015097326366, + 1.3313074227837138, + 2.649367557322003, + -0.08737222278800186, + 1.8643940520501083, + 0.33554885638604737, + -0.10208068750017399, + -3.351687711644993, + 2.2491187462351876, + -7.264665631287486, + 2.468811775400256, + 2.236940253230926, + 1.4395837747782287, + 0.5756193294246122, + 1.566682837365336, + 1.1941344117585693, + 0.7864333853514603, + -9.130530246586128, + -7.542220059862066, + 1.57835349664291, + -0.5198230637738277, + 2.367130441925575, + -7.508216068087835, + 0.3251150387191636, + 0.4305638446644724, + 3.576750227255939, + 2.926957105266908, + 0.6489723337676349, + 2.6600070511561866, + 3.0978307529860145, + 2.14332749047063, + 0.8417453308787288, + -8.362638563146596, + 0.7655484851530803, + -7.358857148523173, + -8.795863146335122, + 1.7528113987581404, + 2.1996806032770424, + 1.1842707222509872, + 0.3370356510506722, + 1.5657862898662531, + 0.9954573129035333, + -8.362659182252077, + -8.572523895071708, + 0.6623382018746384, + 0.6994445665772647, + -8.812671581275692, + 0.7069222566198479, + -7.0821163329383, + 1.9549163979002904, + 2.7673886265006495, + 1.58020571085722, + 1.1157305393370385, + 1.8192078696921687, + -8.648738166417022, + 2.628514031659668, + 2.307267931960636, + 0.17720288819135216, + 1.7561274604591817, + 0.09524221875625612, + -9.01315111179075, + 0.6047127666500285, + -7.244699265066096, + 0.350818800626781, + -6.8329565403043935, + 2.2955591146423098, + 3.0182115153169815, + 2.3746049023107285, + -7.019640140605208, + 2.4647840205414395, + 1.3434268505839981, + 1.6854086164192068, + 2.295861317921914, + 2.2764302909930985, + -7.983364933412937, + 0.5807929594978267, + -8.894143291256958, + 2.9938838722469345, + -7.0788633945319415, + 1.2178067680325075, + 2.856905931248516, + -8.868555023102441, + -8.111576071477934, + 0.7355491495970233, + -8.884594494490006, + 1.3008471541908617, + 2.0846825428565143, + 0.940236823123737, + 2.6584950353466343, + 2.5762583674418824, + 3.3819401974882135, + 3.0910570249281393, + 1.2778711559145604, + -0.4827457256339073, + 0.8455691575019935, + 2.989985556284473, + -8.594252280966447, + 1.9197413450145182, + -7.46547909931198, + 1.6722748789866753, + 2.2138221464125243, + -8.270938951734585, + 1.7386397987695124, + 1.033678879096028, + -8.516503001822748, + 0.512090110414948, + 2.0059308250635013, + -7.781026228664235, + 0.36210339688697013, + 1.8975077592436573, + 0.7694797196827183, + 2.9175852118329306, + 0.35192891316867225, + 2.7464548788808485, + 1.8181456629054111, + -8.879105287057802, + 2.364591270774948, + -8.811648789232835, + 2.2303404870811443, + -7.212500783453163, + -0.04252695108275349, + -8.27691641027849, + 0.17389279156214596, + 0.3419299559039324, + 1.1168557615318893, + 2.6300729259371325, + 0.519545977271745, + 2.026006377830216, + 2.4542416541761796, + 0.6214882222636962, + 2.56545276551517, + 2.371782875552455, + -8.410901915894607, + -7.755180617297871, + 0.26854433319712717, + -0.29016912404990974, + 2.7855698716380655, + 0.7031441386051922, + 0.6463634663828767, + -0.14572491547363256, + 1.0846660011879703, + 2.9877269537925093, + -7.4014817343606, + 1.532485901854579, + 3.2834185214330414, + -8.395186846378069, + 0.7067177076339348, + 1.790307058312988, + 1.9045140645897747, + 0.8155319473452132, + 1.558527430207653, + -0.4562439874529775, + 0.6283683976770774, + 0.3170889013577815, + 1.2269672091211687, + 0.13239083090554576, + 0.9672200895075614, + 2.3228852138079854, + -4.508240674846544, + 0.937926825482767, + 1.9403937731548746, + 2.5476778851631727, + -8.791030377657798, + -6.90900624236735, + 0.38668188599048714, + -8.387398417504254, + -7.344755132587228, + -8.401990418987419, + -7.427545106366351, + 2.052933392737336, + 1.243724639000523, + 2.492261060779638, + 0.9113773906922337, + -0.15816106323304419, + -7.470553956059105, + -8.846441596666375, + 2.827116680018038, + -7.116712997211687, + 1.2433205851368418, + -8.346468787028506, + -7.978333067918487, + 1.172963260744282, + 0.2608874203114955, + -0.46724686695630874, + 1.0564086871641414, + 0.06130467277244044, + 1.6818877741501426, + 1.318743028841478, + 2.8373390653938273, + 2.576615766795672, + 1.2681807565928347, + 1.4580249782652615, + -6.524433867154633, + 0.09581451622485024, + 1.8231985588689075, + -7.358029518015586, + 3.511196746259249, + 1.8394281143396174, + -0.6754116281933468, + -0.020197099799179743, + 2.1780440921947566, + -7.409891359292506, + 1.052901385861119, + 1.2241086946853335, + 0.3665532298720331, + 0.14478725159505287, + -8.151941019411806, + -6.704773956752906, + -6.115756625251118, + -6.974690256772126, + 2.0210836961226892, + -9.015001772676642, + 0.2646431501384486, + 1.6940258171839286, + -0.2610635405056007, + 1.7344415107250657, + 2.4462330102590086, + -7.068860147884828, + 0.08695837348604589, + 1.8701982293321116, + 1.0504262384847716, + 0.019527985748346775, + 0.5097245787288074, + 0.2389915874027862, + -0.19218159389880246, + -8.855488867463228, + 0.6020616642969194, + -8.09265510730711, + 1.3150371364975184, + 1.4759405666726864, + 0.6125503265197201, + 1.0338508449971633, + 0.8355074008305553, + 1.5657388390288403, + 0.19060171190029251, + 1.9216391475271553, + -8.552212424405148, + -0.10872450136022829, + 1.5803257663654455, + -8.312807240378005, + 0.19254420609740558, + 1.5921086618162703, + 3.0724652883798753, + 3.119585347470178, + 2.3680946054804948, + -8.996751873936851, + 0.5450779135596553, + 0.3631979793991185, + -0.08367913106684004, + 1.523933920210135, + 1.9337098908579557, + 1.618362115241489, + -0.2920987527409687, + -0.08027388569056067, + -8.473382618231554, + -7.768478821668898, + -7.782876606094697, + 0.12206627215748748, + -8.662712369595276, + 0.7044770310142159, + 1.2240212148539857, + -6.580456328124205, + 1.0167328126861905, + 2.898843091197227, + 0.587714076153449, + 0.6216570749055097, + 1.0414748533858058, + -9.219472775085773, + 2.2001939905709453, + 2.094708624460661, + 1.746527945940157, + 3.03051313566259, + -7.249968841919131, + 1.3408108007543746, + -8.166942766681114, + 0.979736597403027, + -7.324885568806586, + 2.6278233426622917, + 2.854107404015036, + -8.315196696814988, + 1.64949805448383, + 0.9702802532651041, + 0.7306766790583936, + 0.5492725402351939, + 0.9757266772327314, + 0.5017028463917943, + 3.1800094770093223, + -9.145099532522584, + 0.5049374526866867, + 1.386725096088461, + 1.3587664430780686, + -7.140336167363484, + 1.8245134915391863, + 2.073295079791952, + 0.751375960285473, + -7.606091978393865, + 2.0767323624294556, + 1.6065306443491023, + 3.235065720540448, + -8.721850121309014, + 0.7196711904884685, + 0.9816788132475567, + -8.84696255216795, + 2.626809215666006, + 2.549815866941008, + -8.911171932324867, + 0.9509628399304939, + -0.23927259256889213, + -3.875366962750523, + 1.3908433382198686, + 1.8916021423621145, + 0.7357495578652032, + 1.8923717026364686, + 0.3266032798067937, + 2.300474414349679, + 1.5890325507701302, + -0.06549343928613015, + -7.538525770096769, + 2.318466047257237, + 2.8665125668115454, + -8.153893833561344, + 2.376837200867359, + 1.4709715498404656, + -7.706214252240921, + 2.740327301519449, + 0.09028636054530002, + 3.228455947564836, + 0.5513794682867488, + -7.295604836083069, + -7.759906957734117, + -8.09777432729763, + -8.756864903683306, + 1.2763511405271517, + 2.945562275839741, + 1.3838398593194638, + 2.6854401964823555, + 1.656261678163789, + 1.7533428744601358, + 1.3611257362588403, + -8.655329830508306, + 1.2443715368140775, + -0.1609367996719012, + 0.4399722778070814, + 1.4550332512164776, + -8.299697577767187, + -6.791177821351162, + -7.296317217122517, + -4.24043076076241, + 1.9744461728026013, + 3.0169108659129678, + 2.085481071313922, + 1.5796231665236808, + 0.28883706586254004, + -7.591695100980768, + 0.6925641152794203, + -9.11235095395525, + -0.2735603783775178, + 1.1818571609991912, + -0.018031404946787462, + -8.419312407491887, + -6.567851485538858, + 1.025406857208014, + 0.9351698697703867, + 1.818433527458495, + 2.572785712333763, + -0.17393935498240165, + -9.016630101374009, + 3.2792795030834743, + 0.6658242879271989, + -4.152270499925638, + -8.157087288303547, + 0.335924182306347, + 0.9271775190846979, + -0.03618409014146321, + 1.479665880222401, + 3.3166424655428544, + -6.576355835404252, + -8.394098885439393, + -8.303510483979204, + 0.9062729084027537, + 0.899839531830259, + 1.8193688012574165, + 1.4364119637784045, + -8.767490172955053, + -8.595866558481568, + 2.894212712135655, + 3.0533541874355197, + 1.476114993804659, + -0.6692909143542188, + -7.138202852784044, + 0.21695706225651779, + -5.7962601631962904, + 3.7264192571904973, + 1.0442589644292544, + -7.076269965204877, + 1.367915964860723, + -7.544790546463144, + 1.266952685212199, + -7.804861505331333, + 0.47349039096221374, + 1.5930269805126527, + 2.5782435864991484, + -8.074721153378004, + 3.5025819239037292, + 2.9573712662329408, + -8.193982940881433, + 3.2661504885678805, + -8.948840147357354, + 0.6610691084305056, + 2.6835411548086467, + -8.934693065916505, + 2.10816139136865, + -7.548985911968935, + 2.4765230790798833, + 2.4141491397851342, + 2.739887738696199, + 1.1338375590537584, + -7.31405143327513, + 1.869248007035211, + 2.1401939557341008, + 1.2329394233202717, + -6.723686979858944, + 2.258413389809931, + -8.768330194156949, + 1.2589709754909724, + -0.3036390540432826, + 1.9527663384310978, + 1.9649532855712855, + -7.981643134186547, + 0.8111442705420386, + -8.775732723411297, + 1.2730604738899645, + -0.22849125581746754, + 1.5137214298375352, + -0.5539006361886138, + -6.960063658385964, + -3.89830422876073, + 2.8616026407122552, + 2.9130500271371296, + -6.828704833371259, + 1.0667775059630333, + 1.151566623944205, + -8.838089952827328, + 2.2104783974902467, + -7.902865224549581, + -8.302070760649276, + 0.5350976393159605, + -6.716064221510766, + 1.267120489241334, + 0.6953533727748282, + -8.502301241872468, + 1.5904998311089247, + 0.8035782114725972, + 0.4045367467954588, + -0.08084644425017389, + 3.1042891336787104, + -0.39528854294041516, + 0.568607566886694, + 3.2746664864400366, + 0.1352387754209979, + -0.43424640761761435, + -8.46713488994006, + -3.400403057005808, + -6.655886343018463, + -7.836082067870872, + 1.4006280436181675, + 1.8310149135386509, + -0.2144436816897158, + -7.662535451019991, + -0.28366331573350656, + 3.6834853049456493, + -2.8908696938583747, + -9.092271857083439, + -0.129077551303571, + 0.3467097889851762, + 0.19864145631650879, + 2.628033215265815 + ], + [ + 2.26612718696679, + 7.877304234882818, + -0.2448122627872643, + 4.516540904583949, + -4.185123130466475, + -0.3563651520223698, + -0.8099006227273609, + -0.7273773022983425, + -1.0774494744803462, + 3.3295276863355623, + 8.016118846675909, + 1.566371903855566, + 2.527331443604742, + 6.0204023210462285, + -1.2725524662708634, + 1.6069492541685462, + 5.49339016416923, + -0.14877824811217932, + 7.541563678570806, + 7.828715198686858, + 8.78143506494612, + 3.0724004942655743, + 6.910282329799579, + 8.520562682219973, + 3.034769667067511, + 1.9556477732053121, + 1.391521150600587, + 3.4951871559764998, + -0.17956672474858976, + 2.26349058202461, + -0.5435329290392841, + 8.741637845270487, + 6.471130230045931, + 0.4137720216823771, + 5.314452356601059, + 2.6065034944063608, + 6.069220385698501, + 3.3364078398310455, + 3.328331508170648, + -3.3524926199567573, + -1.435579284246872, + -0.9751170795016273, + 2.0364383183517596, + 1.960212653802774, + 0.7864521202540135, + 0.5657911702861587, + 3.092853905227683, + 5.01806876941713, + 3.8663921379199855, + 2.821039512684959, + 4.622541969701707, + 2.9858484817831323, + 3.6261134085537847, + 1.1724037712799356, + 1.3838290511047608, + 8.806048701083698, + 5.668478923267121, + 0.8018037690672242, + 5.142631152815043, + 5.162406058696506, + 2.6062536456732297, + 3.2943461105091645, + -4.990164104959657, + 3.806517214224376, + 1.8949308089289694, + 3.6353370033600987, + -0.27148904339677543, + 2.7380806442893926, + 3.006631409999392, + 2.0071121263434377, + 0.04628078340754096, + -5.1377197960285175, + 1.0483368306122867, + -4.784796367966544, + 1.5862439101500176, + 8.756330051078708, + 1.5805286130098075, + 1.7098684717663246, + 3.200383653069714, + 6.709460319064008, + 6.307911810299551, + -1.739665781844903, + 4.245169768183147, + 1.666455373730062, + 6.837709319524681, + 8.3481618163966, + 2.9875858571030065, + 2.8139839176386436, + 9.099261178847472, + 8.948801052040421, + 2.006202530800494, + 6.73880301635272, + 8.17611168200775, + 8.773745685376184, + 5.91600439185124, + 6.104433949863836, + 3.7052400992485914, + 6.192779846035659, + 6.600091346107618, + 5.923022741981018, + -5.250204433842824, + 5.223817204947422, + 0.696206808198047, + -2.9769151747495926, + -4.130522494380668, + 7.584957421710554, + -1.5598149353078123, + 7.718639481716779, + 3.0013319833107084, + 5.928077111182149, + 5.152861273092454, + 0.9286834020630569, + 1.15570293124766, + -0.11413301755132071, + 5.3564497446505275, + 3.8928146168617306, + 2.967358817084032, + 9.258863388399549, + 3.629272759041251, + 1.6426518374793857, + -2.8569863552591284, + 6.781260075162287, + 7.624208895126389, + -2.251232030343179, + 0.46027511263626997, + 2.1982523757635377, + 4.5679304243381935, + 7.908571146138121, + -4.853529006657921, + 1.5476649669885307, + 0.8422206760808629, + 3.314306441122399, + 1.435163153918387, + -1.729483134322791, + 9.254698656553444, + 4.161313073939071, + -0.19285497459513173, + 1.471278108190918, + 0.1733342761260208, + 9.03087243734635, + 3.4629830189459407, + -2.6264808461106277, + -4.71657716085353, + 7.869469706260304, + -2.620405496749864, + -1.578270511956123, + 8.357815415289556, + 2.752648283396273, + 5.565302741704507, + -4.805089857063229, + -2.1885042710137363, + 2.0585183776678355, + 1.1610136154324948, + 1.4912027874169376, + 5.914379471375557, + 1.3499504765324346, + 2.044807431029458, + -5.406212771764639, + -2.521892586706258, + 6.1418606819986445, + 8.415457501107205, + 5.604133372216368, + 9.143496250718814, + 7.319575349089649, + -2.06421150615848, + 8.215989933714578, + 1.6857886508980149, + -1.0878353251562816, + -1.5829661285262637, + 1.4674923894311405, + 2.7741856684983164, + 0.9185064155989479, + 1.2781037230916843, + 6.884473682406043, + 0.2934983262573301, + 5.785360809322976, + 2.453893016522862, + -0.7453305767736639, + 8.396234722381891, + 9.850354025725204, + 2.216365887386976, + 8.431236102153253, + 3.3932288637112924, + 3.6608661914291054, + 6.6026731099986815, + -4.953410384187296, + -4.4383215629895725, + 0.07462075161455901, + 5.919009879800911, + 2.95366980628499, + 9.444938044811291, + 0.9076977968117299, + 8.586093101374825, + -2.947154691692993, + -4.633162015997375, + -1.6398464357794498, + -1.2357874214259623, + -5.298383006143634, + -0.43896349898626935, + -2.3401534496139194, + 5.210537676425541, + -2.299537704354067, + -1.1292417029652433, + 1.5799175742083458, + -0.336520751004366, + 4.223746594866309, + 2.8317988965614815, + 1.8947275849516294, + -1.67358278514923, + -5.253236405380462, + 2.219133984219187, + -2.1880534239704144, + -1.3375574851781555, + -1.6072610776058833, + 1.5397232688385492, + 7.036819843042076, + 6.692937873958802, + 1.082746220482573, + 7.797508199429759, + 1.2007123936245745, + 1.8770497605398146, + 6.8994754889657735, + 0.3012191088043153, + 1.068935678724868, + 3.8501454879447587, + 1.7790542501409592, + 1.9614894543599724, + -2.3388476137009833, + 3.110452608712005, + 2.488897512781782, + 0.29446492568337673, + 9.228134471260587, + 3.063679541623608, + 3.127534142642514, + 7.2918077503859715, + 2.1074622260198823, + 0.03666378061265447, + 2.348354665889141, + -2.776578577073148, + 2.5981465208878927, + 1.9166047514477609, + 9.467577997348005, + 5.157166112164271, + 2.7420707192882334, + 0.589056280377641, + 6.296664749024959, + 0.9971281607317918, + 5.968437263241744, + 1.0571690485567766, + 8.746282612307349, + 0.34173616693893566, + 5.460155699051275, + -0.33199671017021387, + 8.506589166854504, + 4.760513163928613, + 3.9122175258157936, + 1.819189742944049, + 9.482487935152745, + 7.768142046743971, + 2.778312560578788, + 0.8924815069256471, + 0.5781476976836171, + 0.879392015766445, + -1.2513183283229647, + 2.9135625658416435, + -5.218754075977553, + 5.869987475209426, + 6.215557257640273, + 3.7965216268335875, + -3.978376269047853, + 0.6441282455417816, + 3.195727870886227, + 7.56439441284703, + 3.385762283468298, + -0.24603155345912425, + 7.62699568971826, + 3.095762191436015, + 5.973306919254448, + 2.2140268056041097, + 1.1127192954394922, + 9.6943693980643, + 7.492005482707253, + -3.6717161892789565, + 6.318002419686285, + 0.07016674311514137, + 1.6940972871714632, + -0.08558940895614194, + -3.8815121919455793, + 9.249512566645945, + 4.598958709603496, + 6.177639401450066, + 2.158306022736798, + 1.6740916570728577, + 3.2165428053675784, + 2.139437242413739, + 8.527661461666613, + -3.1283742405260058, + 3.0533838621473004, + 2.8169855715416996, + -5.02273058607592, + -5.114225383806405, + 2.7643256614152265, + 5.972647138843313, + 8.896241804531897, + 0.8767719229947301, + 0.5510963795699688, + 2.0798971261268364, + -0.20474346020074016, + -2.6385424632674277, + 3.0197194877796796, + -1.6532785804244121, + 7.350061942234777, + 8.424823833262176, + 8.59104964106905, + -0.11483904098985327, + 1.3641616272742318, + 0.9584917078748736, + 9.580245305611498, + 0.7483599943956905, + 4.655927249095971, + 0.900799899593537, + 6.2130604458748, + 4.445533750316594, + -3.932854365326212, + 5.551721373679071, + 8.407643877761046, + 5.885232771171051, + 2.4313967402599874, + 3.669780715212272, + -4.904065807718558, + 1.0158008170609327, + -1.6272293048652275, + 2.845528271618909, + 2.807947902007598, + -2.1459588093206796, + 5.608674291803838, + -4.561053175109312, + 7.69017878292755, + 4.905071561851479, + 1.3413222347068192, + -1.1824406687276312, + 6.738801169688746, + 0.9426274806618206, + 0.6751144483531506, + 2.5848520669007238, + 2.1630373426657936, + -2.7076800307547133, + 1.864371841092596, + -1.9682191074896516, + 3.4172413227955696, + 0.020744702897102174, + 9.769505400367716, + 5.798720665320329, + 0.10253613057523274, + 6.0749341819718214, + 5.357902585098012, + 2.4046800092407876, + 3.512704197882585, + 1.29949383757325, + -2.356963075208514, + 3.1765333049669566, + 7.959069530307456, + -1.966846171005304, + -4.25330821080038, + -4.9219589292228685, + -2.197056092175947, + 7.616489594588331, + 6.040817035671559, + 1.8018917838550688, + 3.5407913971559943, + 5.7890179781018425, + 3.5262942067172984, + 1.9950824464659818, + -1.2893474491122634, + -4.63882900188689, + 2.6694949944379345, + 9.674111224668177, + 8.667295005270228, + 5.737081498597616, + 0.4107629657054798, + 3.877046863822763, + 6.0243859744065915, + 1.8838291584410343, + 2.2562445930114294, + 5.4540041564283, + 8.274269306597581, + 1.2052437166236634, + 6.009785777223097, + -4.383503344244362, + 8.656175590418812, + 0.3181436886883143, + 3.1680255522231233, + 0.6533605734315873, + -2.0973750071005584, + 1.994294615835681, + 3.2127097033396788, + 3.439095899977053, + 7.2040540626614, + 6.785366464604335, + 1.9671587153234735, + -1.5827704027494358, + 0.2878181187329489, + 8.701213078576167, + 5.775702232264342, + 3.796888482981822, + -2.2656130867226323, + 3.3040363272429416, + 7.8336984140783645, + 9.341343078834914, + 0.8594581274173383, + 5.3238697665732495, + 7.72383488521013, + 2.0805761896462105, + 5.00590427740257, + 2.4523488846181003, + 1.530253353729973, + 2.2370825976050397, + 1.660862575032461, + -2.2119403145718572, + 8.457984430510653, + -4.738004795759159, + 0.4176833442710331, + 8.521874684509584, + -1.6000253603261758, + 6.170469016612993, + 5.183781673399163, + 9.17207654732024, + 8.7802004157023, + 3.315232146309697, + 1.7255707001482075, + 5.615285248049787, + 6.452463946699582, + -4.925951924112487, + 2.882800800278358, + 3.93467062640467, + 1.8659188085616192, + 0.3306654617084946, + 5.88741276107328, + 2.550510994085204, + 1.3678259326902338, + 8.663112992818656, + 7.745060334809355, + 0.2809562613068918, + 5.757109520649999, + -1.762138015439035, + 2.8786271321357484, + 1.5474645624637917, + -2.261619052143985, + 0.46857555701517345, + 2.5125424778036147, + 2.672966408932212, + 1.816597263740069, + 2.340187280432554, + 3.634287582524317, + 7.989955726847132, + -4.1380036275031475, + 2.657718947754817, + 0.013787158369857909, + 3.3229210110695737, + -4.201062825692139, + 0.6624903490617761, + 0.6944889704349064, + 2.3533513425377217, + 3.7656997519365882, + 1.788749981152106, + 0.7204561771404412, + 0.5730477522205016, + 5.847178671138482, + 2.8793680686193746, + 0.7398060516511077, + 3.4301937342836974, + 2.531013519887129, + 4.755796561060134, + 0.03385492782234758, + 2.0252506551361464, + 1.6234611019159673, + 1.228843641458273, + -2.1668553315537094, + 7.900290985046659, + 2.7050616439468067, + 3.0006973624200683, + -2.1759904645467083, + 1.5375916476358278, + -0.2253952541126304, + 3.343969551234012, + 2.8107101876639935, + 1.0346932550959453, + -3.6532774566258266, + 8.814079961490908, + -5.097909229812539, + 1.2520930353511095, + -1.2672246839647656, + 2.293286603007841, + 0.2410596637437222, + 2.4817504139530056, + 2.248527285829585, + -5.447774337207916, + 2.5923894677692516, + -1.6454701766763544, + -2.430822876606998, + 1.457649746056256, + 0.7661499751857752, + 6.0009153574611185, + 0.1712479024293902, + 9.530954997711033, + 4.983304885063888, + -1.8163769564966774, + 4.880132622934018, + 8.63294213048147, + -1.7947779683144798, + 3.8634935719553365, + -0.8598586499831854, + 1.7046802605527513, + 8.229281772842395, + 5.5246578628586045, + 5.398118716576608, + 1.5665377764811041, + 0.4245517342194059, + 0.42220229215306493, + -4.167833021757446, + 8.930094781851475, + -0.35446000339359723, + 2.0690947296225084, + 2.2632723564752517, + 2.278629737720776, + 8.756058383175949, + -2.384089444260743, + -1.438680090544982, + 4.019783025134954, + 2.770246387450514, + 1.8829131809196227, + -4.477963763370143, + 0.32452313825904894, + -0.21195692236210456, + -2.161747413311612, + -0.1104742560807789, + 2.731352200053975, + 0.13773536558903318, + 1.7722498157924262, + -0.19267629049184617, + 5.9428729939902, + 3.1430293610074944, + 3.722242795519652, + -2.1106747591923156, + 1.0177343680607016, + 2.3476469900890904, + 1.3611397625517598, + 0.5769222962931847, + 6.296183309833097, + 1.1314346686866923, + 6.996354905775925, + -0.49731034627470605, + 1.6514206933772515, + 6.520507562296869, + -2.4876418860943246, + 2.2307122674056097, + 1.2381767470195828, + -2.41036271414792, + 2.8954645460727777, + -3.2678317085548385, + 1.0575729842665866, + 8.232678699212576, + 2.4739770514016617, + 6.4903251593467965, + 0.9560520936909322, + 2.1733654915775573, + 1.7088617486581057, + 0.768606455869161, + -0.18151377978512884, + 9.302470491788773, + 2.9242421348918497, + -0.21070765493454563, + 8.523457662651651, + 1.678976168232762, + 0.4907273484125949, + 5.294112902913152, + 0.9570337069921794, + 6.309717848572128, + 5.853969411321018, + -1.3623712064374873, + 5.889476548951992, + 5.9315557030842285, + 0.9762018475430272, + 6.3787887818601385, + 3.702617423313039, + -0.4259287963564401, + 3.3940136837773305, + 6.33254888740099, + 7.22386178996473, + 9.48026934550518, + 7.942085137181178, + 6.426147842502401, + 6.193095220350733, + 4.7154873457616215, + -4.929461685703354, + 0.40851879820326237, + 1.0355585657555777, + 9.311049935380138, + 1.9601546187502212, + 0.8503197268897879, + 6.417658240356214, + 5.596080884732174, + 0.10266942032352865, + 1.979006093526083, + 2.0923008866853228, + 4.450351856234537, + 1.9178224408715299, + 6.065730723024631, + -2.1405682647230044, + 3.556202577526532, + 2.1423103843101505, + 2.5180534174094977, + 2.221808263874568, + 1.3245232660550748, + 2.846493012934731, + 0.8640007518413745, + 5.815377980880926, + 6.8076746368586845, + 3.212876640337127, + 0.6620465641915558, + 2.8358557130032866, + 5.905823174293553, + 3.0122412380018124, + 0.6042312258565233, + 2.7189140793266566, + -1.6466964841111296, + -1.433601831253046, + 1.7738882086077163, + 8.874211341294819, + 0.6374919565889643, + -2.250631300713733, + 8.398314862302222, + 3.109991448596508, + -0.10447829604288518, + 4.7465579152728905, + 2.450563286997713, + -5.3495016911775926, + 8.243966840080844, + 9.353859126160001, + 8.779407481837953, + 9.625349294803156, + -1.1690780188806251, + 6.883270495069991, + 3.420071073653294, + -2.7124407552557543, + 2.431871694876404, + 8.169142653739367, + 0.10312628339127165, + 3.1747240309485587, + 7.605851681417775, + 6.778454276450802, + -0.33823536051106673, + 9.159816946971777, + 3.009476478092635, + 2.2639212699097904, + 1.9662337900276088, + 5.456163902180738, + 6.2728300525670555, + 6.046612146481042, + 5.6254154012391755, + 1.7448738576169487, + 8.702308099646093, + 2.934366073684294, + 7.210872628959229, + 5.892411059521462, + 5.08227461046624, + -1.7395127801589216, + 0.1317231915653626, + 1.3125074882009866, + 3.0260692912174343, + 0.9616394687550338, + 1.0133611506450468, + 4.889654943041665, + -0.9629338146674636, + -2.4724254857034027, + -5.456164226742061, + -1.0820298779937545, + 1.5859048729025889, + -2.803255229805306, + 8.563567374075099, + 8.004869380926394, + 7.789872652488633, + -5.178475355138235, + 9.00872844603491, + 4.1139580524028085, + 0.8012152195681193, + 2.9547142410904255, + 9.368726650392437, + 2.573308753297779, + 0.8721362717477031, + -4.518618928892753, + 3.038737898072484, + 3.494236477379651, + 1.7802679565870878, + 1.8305970064035781, + -4.721803051184619, + 5.4894357947523185, + 9.024945875104052, + 0.33248042116655596, + 8.978545880756572, + 0.8265498387088497, + 5.253669495716501, + 7.7831176744839246, + 0.13285521333224828, + 1.962292141861213, + 6.457116157100644, + 5.325750037176592, + 5.080589681950436, + 0.9492611156552451, + 8.370070015424393, + 3.7195662602507493, + 2.306501093822457, + -1.943000191988212, + 8.935275055211779, + 0.4523697013583989, + -3.776936478186719, + 8.525555483994319, + -4.612421394543514, + 1.382647763042897, + 1.1862068046882155, + 3.502817236032331, + 1.3690705190155288, + 5.794610055631808, + -0.9857006762062972, + 5.062053934761011, + 1.6804265993382794, + 0.46795210451425295, + 5.91865159008816, + -3.274753687313677, + 7.117602921781142, + 1.1929818334326139, + 0.7770110791675934, + 5.987436755285214, + 0.6581091618990716, + 0.22582897088511306, + -4.02472552189992, + -3.281325205464603, + -0.6180917569514581, + 6.92089529271475, + 4.147765423783696, + 3.889305189579335, + 0.3056958065777463, + 3.4624723766970944, + -0.310507442372391, + 0.8709343758066993, + 5.575731062495118, + -3.9981833864785656, + -0.021001128775697733, + 6.105224149146419, + 3.198468413659969, + 5.394911906600809, + 2.5417900288708477, + -0.8225464773786691, + 1.9292366115723005, + 2.580419662349196, + 1.4624693592572309, + 0.5847068525339708, + 1.292978687785169, + 2.5908710486444897, + 3.348522105080629, + 5.70882232007594, + 2.526697142258413, + 2.8387534278746305, + 8.926971356284202, + 8.826907454040457, + 8.732591313237428, + 5.670388913071506, + 1.181592394821869, + 1.2455224165480927, + 2.612553991589734, + 0.49924191048675853, + 3.0163431566816477, + 1.2389425711694886, + -3.454256812240126, + 2.541300376969366, + 1.1855211805598396, + 3.919987236532716, + 3.108104199692774, + 6.761561693459247, + 1.6617163362455774, + 7.988216917362715, + -3.7069269200955604, + 1.8956754945791268, + 6.513191358838873, + 3.0160456028606513, + 2.556900557970246, + 5.794776377613141, + 2.8913461749272518, + 0.5584477187851367, + 5.823652757914274, + 6.652661373779732, + 2.895484766286893, + 0.004865004026195168, + 3.7362302042851407, + 5.1590787450811115, + -3.2477231320830846, + 5.160237785597462, + -0.0019050703376409799, + 4.6657067577511215, + 2.533925071913782, + 7.49038147862633, + 9.234610799969083, + 2.8091581370951944, + -3.0852521411396947, + 3.8404146939567805, + -1.7364762360283625, + -0.06075348821321888, + 7.855533253661598, + -4.606191298490445, + 8.930715901769902, + 6.433824620680145, + 5.023330657133031, + 7.176348684385988, + 1.5979034036313131, + -3.8353239436016064, + 1.9302760105322174, + 0.4906948818786122, + 0.8236304101051184, + -3.808384325093473, + -3.0486518743757527, + 1.950339059255233, + 2.8772324445470883, + 5.748787174063801, + 4.506554717027688, + 3.3188306684877382, + -0.39989321009171214, + -1.5738329587862223, + 0.8952788762998415, + 2.137591708106581, + 0.9465115280495907, + 0.05176049572996668, + 2.649712588418019, + 6.594377339599191, + -0.6593129867093952, + 3.4751822144088056, + 1.4732219752230908, + 5.704914737143688, + -2.0579211773330077, + 0.6241604121733543, + 3.5941317673956266, + 2.2980594307532347, + -3.9327004047025476, + 3.2251181402296454, + -0.8913391361706277, + -2.7466344743809117, + 1.6619318036068254, + -1.0500979205665155, + -3.0351188482866753, + -2.817952849035236, + 8.918295682184953, + 3.060364669625106, + -2.655530445833815, + 0.641848597267752, + -1.9681102112267448, + 8.525813146195036, + 1.4343982755532703, + 8.908466876353918, + 8.590443320024665, + 2.711023131254724, + 8.89947385113805, + 2.0939160057034907, + 2.65103084662334, + 1.4626356674124337, + -3.208343456221911, + -3.394173167006077, + 1.932850927448628, + 8.805758171186842, + 2.1243490534911382, + 9.028296832464864, + 8.937647726199716, + -2.676577582815389, + 5.903288211290661, + 0.6846977509561695, + 3.8156052421272375, + 0.7634478610334001, + 1.0540467411943064, + 9.699583427222773, + -2.3747586336050936, + -0.95596567080788, + 3.4920439930271123, + 3.11235139509691, + 0.9378696639670594, + 3.35621409897869, + 3.679262467886785, + 3.2029311363539437, + 0.8203047778014468, + 8.554043310004833, + -0.42993598576078784, + 2.511692309884933, + -4.756775876827685, + 2.0603485204111323, + -5.337213657671816, + 0.41725336047678596, + 6.629983319214528, + 0.8903099477880994, + 6.183510404022067, + 0.27046258864924877, + 2.8507973745983386, + 5.953930731914141, + 6.667463064136371, + -1.1853152943830194, + 0.10929547791890014, + 5.159454256150641, + 0.9575382360439176, + 1.0472819291348658, + -4.478812598974675, + 1.63438677088275, + 1.3966105306914227, + 5.436410376920915, + 0.3892766858938439, + -2.2905961508012016, + 5.229602809963853, + -4.864452941250325, + -1.0538101708637058, + 7.164793904682805, + 1.566138730677346, + 0.02704482509077386, + 0.7886677241533953, + 3.979684289364822, + 8.562397301181349, + 9.285407951263371, + 2.0223634080105914, + 0.6748346443384347, + 4.836315638695997, + 1.9106703975216532, + 0.5866544286142371, + 7.697482947447593, + 9.036618052460097, + -3.3937610246659538, + 1.4695633885567845, + 9.806593022345849, + 2.803053620065441, + 2.79080811918104, + 9.085897942228886, + 0.20786152462408714, + 4.554414906944457, + 2.2687730874822374, + -4.4758778295507735, + 3.01749403025241, + 7.009051356750071, + 3.301496781305262, + 1.5978822792179508, + 3.507883946533327, + 2.475904595046087, + 4.723309718269045, + 1.2999810793281195, + 2.6994776633835635, + 2.1776105906692953, + 5.636269224455179, + 9.227390068887006, + 1.6653064746540052, + 2.531245644163319, + -4.796726394177121, + -4.696260149586186, + 2.360303171044659, + 4.809360672005284, + 6.792224681851225, + 5.0239174109239295, + 9.758115953321388, + 1.7938014075697666, + 0.5624509627239803, + 9.136243614649574, + 3.355605586871464, + 1.5306096828306694, + 8.536995394913296, + 4.284406786162418, + 9.288709095420277, + -5.409560336785602, + 2.964373643784957, + 3.7541139426875, + 0.07436249434529059, + 2.6543557259993937, + 7.326068587028396, + 2.361487178667938, + 6.089912059689269, + 2.3291514353075016, + -2.5797469891430884, + -2.204272569458718, + 1.5814138070450303, + 6.603394036525644, + 7.97166923117362, + -5.421796406778391, + 2.310834537661771, + 3.83348926088483, + 5.924444956089124, + 5.705844596614569, + 0.14904642961421793, + 3.2627918032804986, + 9.156346620095126, + 2.3018313060026943, + 2.871420436775351, + 1.748071777193481, + -2.1699630188713317, + -4.423588831601064, + -1.752210329525351, + -2.3332531026084373, + 8.078537148930103, + 1.7468782968550507, + 3.870771091266975, + 9.570067082920405, + 6.532531074910839, + 5.312720960762101, + -3.3929967864383554, + 7.086090345521071, + 6.218945608429214, + 1.8768890922016566, + 2.607564369570601, + 2.7622849353834233, + -2.5914874773514547, + 8.198193747137957, + 2.989820306813261, + 1.7633582516145228, + 2.9524698537504466, + 2.9608279326797815, + 5.159943485109591, + 2.258405427983319, + 3.3374602636934623, + 3.0273792858264996, + 3.1487351190544213, + -2.4495969677949208, + 5.929783834857964, + 6.711771007305786, + 2.081925344296031, + 5.877244018303804, + 8.886653410012418, + 0.6264009865824708, + 1.114324963535137, + 5.582000655882144, + 0.10484985819812484, + 3.9844989239986037, + 7.045339331126441, + 7.376022206417836, + 1.237898376943498, + 1.7470287460799372, + 8.605965387841337, + 9.617446675860762, + 6.882498231126236, + 1.9704705198454397, + 6.000954757287207, + 8.630755642486054, + 0.4865847848937903, + 0.5890115071805289, + 1.4217583119146444, + 0.5759370890401244, + -0.9918362868258609, + 8.505381270758573, + 5.205225724209531, + -0.1641339289559732, + 4.271545367645099, + 8.773787765364231, + 2.8341861182864894, + 6.291040395037355, + 5.433244854956153, + 5.102936340570758, + 8.422552614349282, + 2.8196855273663104, + 5.678926500302951, + -0.5383477536287377, + 9.4253523750977, + -0.22856265846694768, + 5.45239998827267, + 6.874084499868811, + -0.18137759959854494, + 9.478295589662668, + 3.431633175563881, + 2.643320466978511, + 8.186081903820728, + 1.1989638010195498, + 8.54718584644179, + 6.557128513378248, + 1.2300152894799241, + 4.51921956332824, + 0.5693115943422644, + 7.553359478391619, + 2.893921560929296, + -0.7541870943606744, + 3.4344316370472177, + 4.4779116179245815, + 5.302441643354457, + -2.439565181008059, + 9.512899428908435, + 5.385826259956362, + 5.908299075924965, + 2.100513563369361, + 3.3266700309599027, + 8.089733332102341, + -2.6243622656468855, + 7.230528970232492, + -1.0218346404298337, + 2.5739332556555383, + 2.4854245355736633, + 6.832761487018405, + -1.5149844127528729, + 0.9201408348610742, + 2.1035468063646627, + 0.6368115250012512, + 8.214982113070004, + 8.918916811304616, + 2.7106941171332286, + 3.8123959687632794, + 1.9182183558652177, + 3.3011590899178067, + -4.268458134039248, + 4.1607923908684805, + 2.5607637404629346, + 2.537623329117577, + 6.203676444622138, + 3.2449911641283866, + -1.4863704718561914, + 2.0914264281124773, + 8.866073437504284, + 6.360534894196076, + 1.8548388788418777, + 8.546565459118982, + 3.8121042191991363, + 1.782761627693268, + 8.19409331440138, + 7.271377077104276, + 6.713834371576997, + 4.7054015736273005, + 0.6900279581442493, + -1.9950523194654137, + 0.5019672047701859, + -2.4425971792925014, + 8.397284515101749, + 8.274477379414355, + 4.320281308205884, + 2.7306208965349557, + 1.0268803986720851, + 8.041844717647683, + -4.865558143399747, + -4.43090033274667, + 2.080138753512541, + 0.3985237911488679, + 0.13915356157220796, + -0.6797187918311256, + 1.499451661991291, + 3.1062195316504724, + 1.534350188556406, + -0.42490380560179697, + -0.8824812983295844, + 6.137019298389204, + -2.442387086532495, + 3.5302750806073444, + 5.623701737032925, + -3.048559332327405, + -1.7927761727406366, + 2.2501008966231493, + -0.0434390996019198, + 2.0713629857341833, + 0.6667391023716165, + 7.138508838403462, + 9.117134927041638, + 2.844253892877252, + -3.010065734551748, + 5.3434391962662975, + -0.03387006007417064, + -4.321302231073703, + 6.015578668701478, + 8.694611177352565, + 2.840831058685417, + 3.590468322991271, + 5.925869307767682, + -0.4632539232111991, + 9.852085447425996, + 4.246050458490948, + 0.0795569848871431, + 6.342863222998658, + 1.4051373647168843, + -2.1635570082088544, + 2.2571055692634374, + 3.578015263178784, + 5.92501466937786, + -1.9992269400077587, + 7.613431338304448, + 2.0586657648069444, + 1.7131255525320113, + 2.850162142184762, + 9.387208612221631, + 2.9743469463435983, + 1.1035216432033375, + 1.5082199385262602, + 1.1770277270238634, + 8.727374689487656, + -0.5503031034740972, + 5.996322472782943, + -2.106014886932281, + 2.7145712429255298, + 2.959255404634817, + 3.113836493861464, + 2.769285267659297, + -0.03383966505561062, + 6.981888952467136, + 1.987340238395796, + 2.5935349892602932, + 0.688685934326811, + 0.6023938118323606, + 8.280187749951624, + 8.657520654006337, + -4.614428004690138, + 5.490273423799091, + -0.45654403516637426, + 2.2552766262157378, + 8.950763875630653, + -0.752654576519053, + 6.198515988684498, + 6.375645140382823, + 7.778627264011731, + 0.0679528135196061, + 3.1097816129485856, + 1.9708437429931311, + 1.2519087257185952, + 6.035823894471679, + 2.641606741377335, + 2.3611442950292063, + -1.2138498292937214, + 5.848272228719679, + 0.9787762749408776, + 0.7896001910941994, + -2.9666200974972514, + 1.2153395327671428, + 8.51216629567888, + 0.13161855421830565, + -3.1983695913395485, + 2.4048892802020774, + 3.2731024798000132, + 5.892377898027779, + 0.6485778189416769, + 5.531378659573957, + -1.8313893011262574, + 3.462656522250383, + 3.0240830103191456, + -1.3988666551884819, + 7.196571559897184, + 0.3669345964179366, + -2.8035394109630563, + 4.692363004269288, + -4.476727261762838, + 0.32343844200745103, + -4.182356703599284, + 2.928534228041603, + -2.7685368080690473, + -1.2654701804392694, + -4.052775980189864, + 2.6577769067459074, + -0.9883034373434559, + -0.3934847099090396, + 1.2274252023207477, + 0.1772146684449812, + 1.4914482504911268, + 1.8188586228106742, + 1.5897815313464005, + 1.0307870297513482, + -4.552732358353905, + 5.596899036377971, + 1.1599206410942584, + 3.4385418183942993, + 6.082213341527612, + 0.7341908164143667, + -0.2480626007455346, + -3.1575191950472297, + 2.5389293824676726, + -1.4003061229056613, + 2.6833282298497223, + -0.5467975634173019, + 5.98852696995778, + 0.21292691886096327, + -2.6893512720716815, + -5.117588668343177, + 6.794322180635246, + -4.5742625010613285, + 3.3548586996326435, + 5.0515600035370225, + -2.900449599496725, + 0.10237610962475915, + -5.064851833194207, + 8.531627029244655, + 1.5742438748697312, + 4.082638432652501, + -5.0770992768786645, + 2.119205675069604, + -0.549498327530717, + 3.5730581737051277, + 3.196962670970756, + 7.035845030498391, + 8.548505437332542, + 2.678325032018049, + 2.171957067852686, + 3.4355159234823076, + 5.376546760599714, + -3.487535293579003, + -0.7959857552014767, + 3.109184499044637, + -0.034074417858167665, + -4.463432440942218, + 4.754451040108373, + 5.977086152281335, + 6.3113939537838615, + -1.1073355025666125, + 8.245632488073356, + -5.2333112598995895, + 5.178870586953865, + 0.6180633531176227, + -2.26460261809812, + 4.353674921953694, + 2.8072255410103386, + 1.521416200823173, + 7.589366619945358, + 7.701394142184665, + 1.2116731292036584, + 3.2138291408691844, + 2.8443169344773516, + 0.8903178116947174, + -5.108687151576169, + 7.717847243550239, + 5.103365026900776, + 2.2950781198168375, + 6.397420501802669, + -4.784056687598436, + -5.164879189057719, + 2.612616948933114, + 2.368955342992421, + 6.57506537945875, + 4.983841437905054, + 2.50680520340803, + 0.9418427383823713, + 2.346670739637181, + 6.7670420058449485, + 1.1729036494953344, + 1.3973552011354742, + 1.984364716958311, + 1.1219650572007647, + 1.854444243223755, + 0.15395235570302412, + 6.207396756273517, + 4.902381553967024, + 4.057220866025185, + -0.1327819842430041, + 0.24745711201195134, + 6.1793343047872735, + 1.8642901437174684, + 0.6795484548721835, + 4.802713069866874, + 2.192594562354952, + 7.504825326157448, + 0.4360785367712627, + 3.0969826788534904, + 0.3149099546820246, + 0.631302944620672, + 4.008347085568968, + 5.796200617136318, + 2.690265077072964, + 2.4509671492071647, + 3.1794303634347654, + 5.480282099311519, + 3.793009761585952, + 6.694411441476124, + 0.14085170243202885, + 2.6005776440097637, + -5.2332737609043765, + -2.9808615128824476, + 0.8786289985481496, + 1.8675196537055403, + 1.8798070856567528, + 2.979399276715977, + 7.348120227074838, + 1.0545657973863674, + 0.35836880076700756, + -0.26505513627447935, + 8.37604052125493, + 3.8108946951851164, + 9.371530125128047, + 2.1527731861407666, + 2.22024217006518, + 1.0043860933717168, + -2.8223649837661275, + 1.4529377327135113, + 0.315286921325931, + -3.759919055529933, + 3.192557001334405, + 5.99258651557055, + 2.8135841369438124, + 2.835625816399913, + 4.784946846678241, + -1.7062587841124341, + -1.4443605566411828, + 1.599719712031716, + 4.626872133922204, + -2.066200466458587, + 3.4202402597721107, + -1.9258775472193792, + -0.7109237030323599, + -3.0633148300714814, + 0.7916130122573279, + -2.8003481525863623, + 1.2805196495791191, + 7.641070031410805, + 6.899822557600844, + 1.6947028912538717, + 2.5581600518961483, + 2.0034929243649846, + 5.857911263653215, + 1.4636246547379912, + 2.8971993456484375, + -2.251511707977432, + 5.884447799046445, + 6.7644617214749285, + 2.4674566860698466, + 2.399380076812881, + 8.639067607097326, + 1.4161103959753811, + 2.655889404446979, + 1.8657322395014868, + 8.099395562761458, + 1.2882372797576638, + 5.344775989736017, + 3.2843163197624916, + 1.3268692580615533, + 0.8743822373812074, + 3.4891420513567053, + 0.07385661891207071, + 2.922503933319907, + 8.714379542426268, + 0.6501668013700235, + -1.1375008855416793, + 1.857754709954258, + -2.9945232842258678, + 4.684544304995022, + 0.12886049121117665, + 0.03972938223109909, + 9.237892036506507, + 8.51762483804629, + 3.3724974386724624, + 3.5329200482619085, + 0.8927369288119711, + 1.098034894330614, + 0.0766899373263363, + -1.5153756903132682, + -4.859313173242173, + 2.239424598692307, + 0.3087161707098317, + 1.4471925711686535, + -0.690409487135307, + 1.6160860392915781, + 2.7508251678939137, + 1.688227865698469, + 1.8258302446153771, + 1.2362379094640268, + 2.227100763719389, + 0.013469605778813766, + -0.2565112959825642, + 2.3112040766131825, + 0.6319471784615154, + 0.20327688152306575, + 2.604414592341301, + 5.66856972968723, + 2.7980140743746076, + 8.67085362838519, + 5.918440790189692, + 5.7224556859282, + 0.857285364668508, + 5.544811900362975, + 9.4435138232807, + -0.20236353549092811, + 5.016793650411961, + 2.720699822325314, + 4.5479069977602204, + 2.715983316290806, + 1.7525824693752088, + -5.264974061070002, + -0.6496290315234062, + 2.367614338923079, + 0.6879917909303833, + 1.3029286630363872, + 3.0138918746597065, + 6.640804353774791, + 2.7172754438040307, + 5.76419355748975, + 5.017375178348365, + -0.10816025840842662, + 0.9453881730249583, + 6.720643124414721, + -0.1103631054118109, + 3.3408766939456034, + 5.794047549702596, + -2.025824021148328, + 3.252234698808337, + 5.530672000744878, + 8.64747695134738, + 9.515097696825404, + 3.2344927992474717, + -4.561284055904563, + 5.603927007833755, + 1.6239964904833328, + 1.8190481024621308, + -5.303239347416054, + 3.4400884108742207, + 2.076151202158285, + 2.1590840988805513, + -3.874933358494983, + 8.009236378478496, + 2.742251873418243, + 2.4574402301105303, + 2.1704695708632493, + 0.07441695251940642, + 9.142691879015315, + 0.09950685263139657, + 2.547726388955141, + 2.4264440031043555, + 8.995420023183437, + 5.928160199896791, + 5.10369858225642, + 6.797099162610654, + 9.891558600174584, + 1.6271653661516037, + 0.865228175916761, + 3.293969600310039, + 4.5338781574902605, + 1.8576515517527716, + 2.200264025873621, + 9.229541062816832, + -0.5258640404899334, + 0.7878662950097198, + -4.809662866623755, + 6.172140436212999, + 2.353632835509077, + 0.3245596351457882, + 0.7492161994440059, + 2.6243550810331664, + 3.6033101201399518, + 2.883674831846493, + 4.777864163973319, + 7.608122293011118, + 9.094148010265082, + 2.8586547600235472, + 3.0637538813442333, + -0.16306496309051946, + 5.978283707847633, + 7.0152850555910025, + 2.089439744209332, + 1.2699966622815706, + 7.632606479355867, + 7.426884733347851, + 4.919860769652532, + 3.4636433196516427, + -1.185439361099015, + 1.223042401715309, + -1.7204930505004916, + 1.2498940606612439, + 6.096280333402219, + 0.0255562791229087, + -4.7421129527816, + 6.9134628087321275, + 9.345092400602578, + -0.7428833847718531, + 7.751469449471995, + -3.174355411099064, + -0.4389593568137036, + 0.7889164889254271, + 0.42671004071595564, + 0.4146530713836919, + 2.9852951657239575, + 2.420596392633052, + 7.280976775617019, + -3.9833538656439123, + 1.8123279460938362, + 5.254191706507663, + 2.988686456685128, + -5.122771852329393, + 5.460160945532948, + 5.556392417120963, + 1.1989693659532146, + 1.8856338222888531, + 1.4304240447047571, + 9.103673874712799, + 0.06974033136878711, + -1.8634596072735357, + 9.181488312562077, + 7.782179970735101, + 2.0106552241346183, + 8.423518536106023, + 1.5602445629709538, + 8.192057648102823, + 1.166091512538277, + 2.924903190780516, + 5.805348422221177, + -1.870811325838246, + 5.497133289900674, + 0.6912014452029617, + 2.4494309050757823, + 6.214442790508838, + 5.532218926098756, + 1.8484648675848672, + 0.5357420048696545, + 6.168881879285348, + -4.164441414751242, + 0.8497563909106339, + 6.068046616819917, + 1.6311217190066913, + 4.935653352145963, + 2.909436523764689, + 0.3586354983898887, + 2.6396021300049144, + 3.4768890569507467, + -2.888866342024628, + 6.580642289649002, + 2.302982426221136, + 5.055472805495961, + -0.4836179568565782, + 2.6416319996966986, + 2.3803682290271317, + -4.975249596900043, + -1.4678816639945595, + 6.54720672263416, + -0.943030719721922, + 8.06944049964118, + 9.456410932217949, + 5.477892548341206, + 2.0069868969636064, + 0.33000045858721744, + 0.45137009084748314, + 9.585272571025595, + 2.6585240479374606, + 0.8401318359631659, + 5.921237265866324, + 7.0614974156154044, + 1.3234965998892707, + -0.19082410082550832, + 0.9482892986162604, + 5.1980514735827015, + 1.8648414428434004, + 3.8275970013636003, + 2.145441639200901, + 7.18545866225611, + 3.7681560899027025, + 6.7706972228375815, + 1.710843392703091, + 9.653896454935389, + 8.991595342866164, + 9.186382157891034, + 9.04406304944811, + 0.8172915176930347, + 2.7566256486526353, + 9.660910576572753, + 5.774924628332689, + 1.0341297924498363, + 6.776970079531831, + 6.870337511570097, + 3.316407570037295, + -0.7327186727816404, + 1.4785073400784159, + 3.2646800316507143, + -0.20914306937205182, + 4.64867840898082, + 5.091437827560491, + 2.543828629721811, + 2.491519287769942, + -0.13127409974118892, + -0.6849444346996574, + 5.704748046506554, + -4.580428758802872, + 5.9276986115834305, + 5.949946091737831, + -5.112703533067682, + 6.328840062412349, + 1.3725722459483412, + 1.484950850899703, + 4.922692136902319, + -0.26360087335294413, + 0.3972863993323734, + 6.43499018390537, + 1.9261614464785353, + 9.25217428814158, + 3.4887977439401117, + 7.798413137478559, + -3.859332588932147, + 1.0076305806755113, + -1.595328104377359, + -1.9345755656466876, + 1.3656568430290457, + 2.010239600296628, + 3.0842970345182317, + 1.8307019509259435, + 2.5249665379978907, + 0.21114084691696372, + 3.010763869995198, + 1.5804254258562025, + -4.520042320218686, + -2.128356334095731, + 0.7405047744804988, + 1.7154819971091906, + -0.8522515684542971, + 2.442382303303678, + 0.18784706156999456, + -2.109265073450941, + -0.45500559588426437, + 0.7348284376716618, + 1.7123931297303872, + 5.254688512601999, + 2.7456168285842244, + 0.5416658212070915, + 2.301238469829127, + -3.1464762215917483, + -0.9007639718009747, + 5.42006148595848, + -0.9861660783712057, + -2.162965897976326, + -4.493082242249678, + -3.8189365772831603, + -0.10234080732201847, + 3.8222780518158883, + 6.069040078680538, + -5.257236694544421, + 4.043152750327152, + 2.9400998090483847, + 8.123198210212966, + 1.1001939822886846, + 1.2565680855375772, + 2.245798308281623, + 2.4479921134731173, + -3.158270658414151, + -1.5481370872038624, + 6.312918687487506, + -0.37121749047904795, + 1.6919954011846836, + 0.6875554834459174, + 7.242510466143665, + 3.2722108293917302, + 5.8160212902859705, + 0.26737489698121397, + 3.0063850526444735, + 3.506124855697979, + 7.042641095704005, + 3.3569127880879672, + 1.5852174746984395, + 3.6751882693142797, + -4.165220031935649, + 1.215199283565774, + 7.90308322296078, + 1.5939925329095328, + -4.664195921536808, + 2.4132307103650708, + 1.6273049780055568, + 8.030453359488645, + 5.926901407210981, + 5.96945226815641, + 3.0528213692017427, + 0.5294928795205264, + 6.111198931739082, + 3.27826803975091, + 7.979729831606848, + 6.579075127990691, + 1.0644579389040314, + 3.102471494981852, + 1.6526255527926923, + 1.7127900541239869, + 8.283760640459029, + 2.1924222459051204, + 1.900806515428295, + 5.329619827785321, + -2.071511831927549, + 2.1229332814500315, + -2.4852779587449625, + 3.4704642694533496, + -0.37053887420146553, + 8.385984728405884, + 4.493223761008627, + 2.180319410350078, + -3.01208855357939, + 1.332983186514152, + 6.130263721597461, + 6.9534807344215785, + 5.88853010829384, + -2.8407853967671577, + 1.8033610815004772, + 5.949222221028675, + 1.9889057182006664, + 5.176870999104773, + 0.9709025783457912, + 0.4775110339612653, + 6.003172783895949, + 1.1595497820232883, + 6.039896301978691, + 8.88875053895144, + 5.660666726152205, + -0.20707853144034163, + 0.4656841519810458, + -0.46729412109856766, + -4.763487218056729, + 1.0575797208447806, + 1.4690708457781543, + 8.201368891883424, + 6.322509791870492, + 1.3428277584119488, + 2.6268228995812533, + -2.021077629389779, + 9.536296205942017, + 0.05281985636909039, + 5.667685369387791, + 6.0217023360515896, + 5.878501643642019, + 0.02298604850952251, + 5.729457157547902, + -2.2733129918903487, + 7.357345572460884, + -1.6602227754960404, + 2.8933777470936346, + -5.300255477680182, + 6.674143746202342, + 7.354433017740983, + 8.32057151070352, + 4.986715253364944, + -0.8236511115608395, + 3.000653918731319, + 5.142268479074093, + 4.991873612764421, + -4.462314080258194, + 1.4099959224940581, + 0.5140333598695296, + 0.462038563308794, + -0.5580698037160144, + 2.4156214850354365, + 2.4150056047346644, + -1.4348713625629237, + 9.37189601203025, + 2.2128480268481603, + 5.501830807729627, + 0.5970505131245347, + 1.7086361564802768, + 4.966871116438676, + -5.51059720745767, + 0.40783914628227114, + 5.655657432376619, + 2.1835504862122175, + 8.168986002754655, + -2.7193188251020834, + 9.398775404415085, + 3.2418349793456707, + 0.8676509750580216, + -4.113737353548256, + 1.024007404044521, + -0.34756324082994794, + 1.9602376164792763, + 0.7832705087881328, + 6.335484216782878, + 3.848417262983378, + 2.789319415517467, + 2.7804749683831655, + -4.209835262087807, + 2.134517779191862, + 0.20877152273204996, + -1.6896473650266692, + -1.2400823830799401, + 0.8168266270138272, + 3.003065455584158, + 2.2137576704758306, + 2.3334723718138632, + -0.8022301886454714, + -2.3591277338516994, + 3.269962993932029, + -2.0834230276898755, + 0.9951956155368633, + 0.9691860719679056, + -3.888839739561202, + 2.1629207058674904, + -1.905675289518531, + 2.69453691444355, + 6.701800635735289, + 2.5725594382816372, + 8.588496425207529, + 4.7106805174377735, + 6.410515675188265, + -2.6226997711629716, + 2.072002183326608, + 1.3526862546035745, + 2.1997548005471264, + 2.526355377006503, + 3.3454645401850533, + 1.8236407076363763, + 6.637010415903734, + 0.6187101895208668, + 9.301085778983579, + 7.726784389543951, + 0.9099830270183308, + 6.722528024137925, + 1.256374452711205, + 6.793998223541745, + 3.0663208923461376, + 2.462996441838422, + -1.2483010887074464, + 2.4878297941421663, + 5.988784487203425, + 9.308199852291192, + 5.883351018668258, + 8.143463243475388, + -0.8356458655755094, + 0.5497825145679909, + 3.236547594377735, + 9.029397780799197, + 0.22996970906321432, + -5.012819578978871, + -3.8497149088058915, + 1.7755407000814296, + 8.59826241534019, + -0.4075788598887008, + 0.5821111590672242, + 0.23063765524325244, + 0.9189563098666551, + 7.232389094906414, + -1.268420609294997, + 5.7521552238840465, + 2.203239339828908, + 7.005547360934609, + -1.1313653909462507, + 5.67130135575973, + 9.377013452063188, + 3.4162063856020475, + 6.891357953756193, + 5.572513194602001, + 9.616751460142105, + 7.3777860186219915, + 3.5562906047735603, + -4.282360119923921, + 0.6698010007632262, + -3.2554024637200842, + 0.29250443938453025, + 6.425718881533466, + -3.177602331295984, + -2.454493843289483, + 6.314811684076483, + 8.979560158834992, + 9.870736213896537, + 8.757264493637411, + -0.003453712350984302, + 8.817969535806293, + 5.843733799478246, + 7.054684804368724, + 3.1919023793630075, + 2.699640057234987, + 2.141516681314163, + -3.4472292168477012, + 1.36908126062115, + -1.9441012592167115, + -1.9236197068234189, + 5.397184307275612, + 2.0757701792890675, + 6.778390763843739, + 8.443855120253565, + 7.08532983610067, + 7.9608745265204846, + 7.225488139730609, + 1.845551458086398, + 2.6478648952083716, + 0.699909602488156, + 5.578408909149145, + 2.3707672322763456, + -4.360423816613405, + 6.34034815381996, + 2.5123983953755586, + 6.234127752856539, + -3.403233447929865, + -2.6398216695932124, + 0.11824856949582217, + -0.4560083455354007, + 1.3623351137161217, + 2.488437018998219, + -2.0996984449895026, + 0.4529341843863447, + 5.7316943952319965, + -1.6794395006623484, + -1.662952700628585, + 0.7528252276279854, + 1.86526470435675, + 0.8250661486800847, + 7.545149459365755, + -2.9784914139412986, + 0.7955122992388768, + -1.749696914354937, + 3.007033532357639, + 3.2539694960317154, + 2.325020884091079, + 3.0682380835267087, + 3.4877633370885603, + 4.755907494880818, + 6.128266753985117, + -5.363081889079644, + 1.5887297020473807, + 9.505091939439223, + 4.365143981465876, + 6.7548503826243715, + 2.5052979166231886, + 1.4353576508393462, + -0.9928554982734809, + 2.922494124533855, + 8.10081092078275, + -1.3002214585010545, + 0.8404824712467401, + 5.94837787669559, + -4.767683732401647, + 2.0017751275916296, + 7.915636914038291, + 2.5822586807239722, + 2.8495043174738024, + 0.29281685345900793, + 3.07193328710613, + 0.32780731449595507, + 1.078728624062782, + 9.212142393190357, + -3.234096488944491, + 8.99985503777453, + -2.855040830930229, + -5.295863603354861, + 2.9629710484192806, + 9.59196741817357, + 0.7743349776222785, + 5.792043147303167, + 0.7308440213299643, + 7.350415660821069, + 3.495075238229184, + 8.647182976752868, + -1.534758576855379, + 2.073463430160869, + 1.963332441387051, + -4.694354180861564, + 3.6714354834316927, + -3.9262388559123464, + -5.546500365026073, + 7.445083723470979, + 0.5343682186596866, + 3.183237056362217, + -2.403816101636754, + 2.8857332178958295, + 6.672428835630001, + 3.6953141788532005, + 5.29590329943819, + -1.8554545565340235, + -0.17610077417452594, + 8.271235986900548, + -0.8302414238237459, + 2.4378481735230384, + 9.57202802197544, + 5.557141877865675, + 6.5268978634694035, + 0.8407924426877699, + 7.979052578435055, + 3.117590024813628, + 5.96352037754916, + 2.094825703273501, + 0.07296691179388555, + 1.8079190555258078, + 1.7205218009412788, + 5.730311841139373, + 9.168679321911021, + 6.63794178471708, + -4.770023224802359, + 6.32579824811486, + 8.060919532649441, + -4.3498104635619965, + 6.056146366036844, + 1.3888378059597648, + 1.5681318199046925, + 6.934114111947524, + 4.553233986454347, + 1.4275240710301833, + 3.747256941001211, + -0.7793987571499036, + 2.4159574731318942, + 6.360954669715619, + 0.4084551726607402, + 1.7502096715103717, + 8.32356944907215, + 9.411852540453843, + -0.24749985885791767, + 3.6441437548002984, + 0.6638796329985154, + 0.49554468852439093, + -5.288299956200278, + 1.5867630719594317, + 5.266978763389619, + -0.1730881110987882, + 2.027850474654688, + 6.70043355578779, + 0.20699152625797068, + -1.4169490537802918, + 3.567763215349293, + 0.2333312464661514, + -1.4878662123333741, + 1.091060642589443, + 2.4017502719866424, + 7.9782861690822, + 3.44782406334824, + 1.0432269698264383, + 4.640994658240865, + 5.749646077991115, + 6.089790596813214, + 2.9853600065784107, + 1.585551391839002, + 4.403944696472623, + 0.29951265146255124, + 0.24389931462395698, + 0.8540781982541754, + 4.611025267027386, + -0.45181284082359413, + -3.5542731441518876, + 0.20103598685438334, + 2.3975234423332497, + 8.6230597387424, + 8.663118774999928, + -4.529682685118797, + -0.8760724945413672, + 0.25604543050627254, + 9.032110091018815, + 0.19628068798147708, + 5.911953902186222, + 8.11065712561067, + 1.5106611736082836, + 6.483990941242417, + 0.9154031653035811, + 0.4733209855793123, + 1.2729172153754813, + 6.226232576039953, + -3.7857216516839913, + 2.85210708040812, + 2.3947148486545013, + 7.649662954804599, + 9.16162216363764, + 2.2116236694661873, + -4.445492844370273, + 0.11508527097851792, + 9.076362458267964, + 0.4348146585260673, + 3.688186838147284, + -5.321770884683309, + 7.415460097453132, + -1.9534807833504129, + 8.50852643998329, + 0.8570152188041182, + 3.002819999704473, + 6.0288006491872315, + -3.2528245994908955, + 1.425515401254983, + 2.518619468789845, + 2.6021768268562155, + 2.414457971274285, + 8.811798173197223, + 8.463135654386775, + -2.3599709268161466, + -4.470741594770652, + 1.9029054733785435, + 3.5107539529337575, + 2.517914528908818, + 3.2373739399765995, + 0.9559242825654067, + -1.4022193331720425, + -4.164011090781615, + 3.5098720942728066, + -1.5152128618720702, + 1.2042015283823664, + -2.5223273022794563, + 4.6568722554070785, + 8.748779660690749, + 1.64867887605269, + -1.6451264272207415, + 2.223501415745643, + 8.983241413406752, + 0.7856413355998099, + 8.271058597380113, + 8.759786053557635, + 5.964851993500417, + -1.9854392851535787, + 5.417165541146182, + -3.6442197852501232, + 3.4828188869815073, + 8.467767959705135, + 1.2727275656548527, + 8.52188240945984, + 1.2193859287039661, + 1.6753573505177883, + 2.7578999432181828, + 5.752416727518017, + 3.4895594687669718, + 2.848717380504166, + 1.1007765674235717, + 7.3936145172341945, + 8.335571225152114, + 1.3472097319919996, + 3.1371705224091144, + 0.4215143657193758, + -2.0292815257034817, + -5.49035861680535, + -5.183291470456518, + 1.8343121382967842, + -0.30212063468598305, + 2.67347488141855, + 1.2506741064376266, + 6.705326698795701, + 2.3117777668167516, + -2.166111449868967, + 3.3066654185985973, + 0.8743752567181792, + 9.433552236501237, + 1.3666782032393632, + 1.130142268909575, + 3.2738566103698905, + 6.5968220577140055, + -1.3891345838746347, + -3.5128981904432948, + 5.0804414422022335, + -1.3058470702411964, + 7.130933426715227, + 2.4273980019954204, + -2.1608284982186197, + -0.9100596860292837, + 3.5894835328702106, + -4.751280916256097, + 2.569585024243075, + 0.2919735183319088, + -4.406471808552807, + 9.003775888319385, + 2.7525231897174263, + 8.491374502683652, + 4.376946832713647, + 8.527523053929686, + 5.632602084917159, + 1.4052219593748756, + 5.5737253668239655, + 3.4569678545306575, + 2.8171500161674357, + -1.432944115552822, + 5.697969375029198, + -4.621445460935588, + 2.0787510016080533, + -5.326683128959758, + 0.18067104116133456, + -2.502498411746137, + 5.629349911367992, + -1.6890235276676102, + 3.045990978436232, + 2.068467399984089, + 5.084093781542543, + -0.3929337578092091, + 2.5605365794849444, + 5.53869708350438, + 2.614522456632436, + -1.5480239773209241, + 3.5678945576150123, + 8.83401771512541, + -1.5963844207514608, + 7.624379929762122, + 0.5005540393521262, + -5.527784938566744, + 2.65125893195905, + 2.130758952863588, + -1.8332809453947294, + 9.30394757121743, + -1.446331856826678, + 3.2165312128841435, + 4.04687189616058, + -3.5056213436587336, + 6.666219551384144, + 1.3673469667147284, + 1.6231652648575179, + 4.4948549236610305, + 2.0815856253285214, + -0.60043979994161, + 3.590976451375838, + -0.7453152161686788, + 1.564348734142829, + 5.541589960918232, + 8.202930465965972, + 0.7444615247059749, + 9.587703248667736, + 0.4935395980500209, + 2.02135191370068, + -2.63757568386335, + 1.6931078983944303, + 6.194891401826324, + -4.755698900045702, + 3.053941263428849, + 1.738035826808363, + 5.482444085458718, + 8.578249176772534, + 6.622930301440895, + 2.125057661575636, + 0.9054222604976315, + -1.7109375809695786, + 1.4198761476337516, + 1.054867119727166, + -3.925252521521188, + -3.0436012570906885, + 2.4145304719634875, + -2.7089004374087096, + -2.5781187088980415, + 4.040219917034839, + 5.7177319240579125, + 0.41648218881027665, + 0.5737150782632761, + 1.3605157350455324, + 7.762686896051611, + -1.890859886923358, + 2.9869526962840838, + -0.8018564162424652, + 2.1407876001056168, + 5.863579989317062, + 1.1422827886056979, + -5.034414504153599, + 5.306443885502887, + 7.855634915825041, + -1.840671726488353, + 3.1453425959097934, + 9.521053083576877, + 1.1797955103350146, + 8.34289646049561, + 0.9864026286857854, + 1.5511568318528561, + 8.434271139212683, + 0.20427819364048902, + 2.0271112325590077, + -3.9356698611216534, + 5.870340206181536, + 0.05269352565779371, + 1.7920616044663369, + -2.42138548840195, + 3.475007131820548, + 7.011108331408282, + 9.515231228026291, + 6.346792668752203, + 6.494414623263908, + 4.472927613760124, + 4.524003924372726, + -1.4134186597873823, + 8.723691479901538, + 1.8639456444607319, + 0.7382556576116613, + 1.7673853588875232, + 6.396387432623712, + 5.591000021046794, + -2.2765896318071217, + 6.2962062739823965, + 2.4212794737100123, + 2.697188160050856, + 3.5064505365695777, + -0.9661428559971644, + 1.1653071361402214, + 8.832158933958235, + 1.926594996120538, + 2.2382815782643033, + -2.138572477012913, + 9.020160211222818, + 0.34070599225005027, + 3.3797625149541055, + 0.05703965444493409, + 2.305032462030537, + 6.2463349336626495, + -0.6584707349200902, + -0.38389619151492244, + 6.120169056030916, + 1.6388345686915071, + 2.205438800430803, + 9.043723892497786, + -0.05324007737301528, + 0.3651143038587914, + 7.001822659426594, + 2.5765444604895604, + -0.04862648951907117, + -0.8854025072266365, + -0.32410129627632506, + 3.0812142492739736, + 6.295356520537049, + 6.177967110174736, + 7.079564521405287, + 1.83353114343839, + 3.2388910348859192, + 8.913712343332712, + 2.4375146713115385, + 1.2634733710281858, + -4.414938136285208, + 3.4759377909794242, + 6.569247134786052, + 3.3257450358478975, + 1.0495790550303425, + 2.9650735879571166, + 3.4456722296078293, + -1.9425450759671543, + 1.599117095043776, + 8.497017451637193, + 2.6579556546365266, + 7.910087173332323, + 2.8799774369661812, + -0.07025037126624543, + 8.073942200277664, + 2.3342142619361534, + 2.8830916269966758, + 9.100411166653252, + 1.2058185139176982, + 2.675872700135464, + 0.9666762827582379, + 5.616586906563049, + 6.905637710372154, + -4.860074337814643, + 4.0158954528166, + 1.0941869678450136, + 1.3322841635744591, + -4.8525029564210955, + 1.6950143873386676, + 2.275866960775223, + 3.7546696075480233, + 8.522580158693797, + 8.479289443791219, + 9.084376204745716, + 0.524887035594922, + 1.7930496347425775, + 8.36265519445222, + 9.13207967711533, + 8.728248933808025, + 5.104280192568869, + 6.932520932186675, + 6.57954219836973, + 8.358960821108504, + 2.5870592720871555, + -1.8119057173172903, + 1.7265071935675682, + 5.343747915510189, + -2.5904371427923047, + -5.105623763927343, + 1.7555879054859012, + 7.85831749823218, + 0.3955986367670087, + 2.693545835650025, + 2.2783352602443867, + -4.107147221405172, + -0.3589112571594508, + 0.2739780970973445, + 6.00113681801525, + -0.9520046557887152, + 4.455509206119695, + -0.07994742403490834, + 6.912346070734264, + 1.7374666740318419, + -1.9482137044069845, + 5.243596178912719, + 3.718827522220301, + 1.1880164187237157, + 8.047695963346682, + 8.117318312531806, + -1.5085045180979209, + 8.80835524866147, + 2.9999212372772712, + 5.458046876567976, + 0.007803281932174618, + 1.3849554785538867, + 8.51477432156869, + 4.656161890950504, + -0.49453412636627153, + -4.9425646575433895, + 1.5754507134189302, + 4.071093984813563, + 1.4821345261234662, + 2.815670718021292, + 1.9423374489222467, + 5.64234093990883, + 1.711516119661729, + 2.5246316441116576, + 5.404768257953342, + -4.933144619659127, + -4.0862479358951616, + 2.414531419272294, + 9.27182973316845, + 2.2064135982664714, + 5.872298049989269, + 1.4429082064623937, + 3.99304417572891, + -1.7976779555774933, + 2.8060601507477996, + 9.544330963742757, + -0.2234861369430893, + 1.1926141918363296, + 2.8595940374636495, + 2.6911712476226657, + 2.458521202978513, + -2.694375476554727, + 3.9776603483448296, + 0.6718385790624146, + 2.37716045025641, + 4.041421509916807, + -1.6349543418763945, + 7.9212454675679185, + 0.7721147216829433, + 8.959113895222348, + 4.982386370940765, + 7.1514158587805365, + 4.073127252924829, + 3.973102469449036, + -1.4206098126378905, + -2.884552512926147, + 1.9047385965830614, + -3.169287508333552, + -1.7392144968569563, + 0.7721044520820948, + 1.927206272793925, + 7.104641765501545, + 3.4505110590561854, + 0.8733130174974519, + 9.266268836620407, + 0.08700274201436375, + 6.052240993895984, + 0.9007896354342442, + 6.173886287536836, + 5.907638810176699, + 1.9606259324926214, + -5.130693452152175, + 3.0132523997110416, + 2.4628682686429966, + 3.3604641188713784, + 5.456330102509671, + 3.1129611127328216, + 4.621919115236012, + 1.6051966558753044, + -2.395592286301594, + 9.415942637822253, + 4.5551016138441645, + 5.462041681472252, + 1.2553444252956512, + 9.125289294805025, + -5.19635425788795, + 0.6479316122636503, + 9.211129276541376, + 4.088576979982686, + 1.778078098119498, + 8.272830237615167, + 5.941247087817222, + 6.483218672754511, + 7.836505848839812, + 9.10777300419422, + 0.8697894759196668, + -1.8048153062968533, + 2.6932056631239343, + 6.270438904360643, + 3.793841292975147, + 5.93176798393608, + 0.5794319523472153, + -1.0634626786112789, + 1.5525130634435254, + 1.9069750675731074, + 5.450872922288456, + 3.352999491608821, + 2.6061036797014023, + 0.46996693176866594, + 3.089799989155103, + 2.1768196533032103, + 7.209632078897733, + 2.9382709469704604, + 5.81830042125279, + 3.0254259849920686, + 7.952336979799083, + 6.556182022826983, + -0.1976664554077333, + 5.064374587699065, + 2.5790684611219734, + 5.416054005071723, + 7.929922860554962, + -4.987485362225389, + -2.365232243588474, + 6.067477778583785, + 2.7165906101272084, + -5.0554658081074315, + 4.826017906934126, + 2.3051068664341603, + 3.2425676342002556, + 1.2996408495052318, + 0.703193985050713, + 2.7805627349610846, + 1.0491896153647293, + 6.821299225780244, + 0.7046460079936201, + -2.953473023647294, + -3.6590226625841997, + 0.4254855627307656, + 4.5834436824020655, + -0.1264574637622443, + 0.062170134272150034, + 1.0044135902927296, + 5.894066030179587, + -5.032302428711589, + 4.3565237912861, + 0.8204878684140501, + 8.542998995034315, + 0.19522163995677777, + -5.105510462957291, + 3.664400379320447, + 9.523795502928193, + 3.1412037401144466, + 2.2838679019892427, + 8.175845575985958, + 1.4812611753280103, + 9.229352687073469, + 7.315547660481734, + 5.826430520451797, + 0.7545773335194408, + -2.2423574079782207, + 6.472756705414401, + 1.37941517062476, + -2.1564940827513968, + 3.372013235866089, + 2.3650725020331045, + 6.568125635833751, + 2.445340791793013, + -0.9932390025071863, + 6.221281466545461, + 7.995363198747193, + 3.5240304820715087, + 3.7052514666884435, + 3.039674786023824, + 8.72316174744997, + 9.686001035805639, + 8.472376648409222, + 0.3369873707042546, + 0.4055299277861853, + 9.055907499616207, + -5.005667267446557, + 7.961191247169811, + 3.229307450854857, + 5.780672932325554, + 6.190242116673266, + 6.102394102049797, + 4.171372057552599, + 1.241947275086106, + -2.0859279953693677, + 9.473391633273852, + 5.49488016127507, + 9.122434389343034, + 1.0929961023746564, + 6.043910303777906, + 5.392428857667593, + 7.4885983371889955, + 8.349677984222893, + 9.566987967368911, + 0.36722542845369793 + ] +] diff --git a/client/__tests__/util/annoMatrix/whereCache.test.js b/client/__tests__/util/annoMatrix/whereCache.test.js new file mode 100644 index 00000000..f9f8e652 --- /dev/null +++ b/client/__tests__/util/annoMatrix/whereCache.test.js @@ -0,0 +1,184 @@ +import { + _whereCacheGet, + _whereCacheCreate, + _whereCacheMerge, +} from "../../../src/annoMatrix/whereCache"; + +const schema = {}; + +describe("whereCache", () => { + test("whereCacheGet - missing cache values", () => { + expect( + _whereCacheGet({}, schema, "X", { + field: "var", + column: "foo", + value: "bar", + }) + ).toEqual([undefined]); + expect( + _whereCacheGet({ X: {} }, schema, "X", { + field: "var", + column: "foo", + value: "bar", + }) + ).toEqual([undefined]); + expect( + _whereCacheGet({ X: { var: new Map() } }, schema, "X", { + field: "var", + column: "foo", + value: "bar", + }) + ).toEqual([undefined]); + expect( + _whereCacheGet( + { X: { var: new Map([["foo", new Map()]]) } }, + schema, + "X", + { + field: "var", + column: "foo", + value: "bar", + } + ) + ).toEqual([undefined]); + }); + + test("whereCacheGet - varied lookups", () => { + const whereCache = { + X: { + var: new Map([ + [ + "foo", + new Map([ + ["bar", [0]], + ["baz", [1, 2]], + ]), + ], + ]), + }, + }; + + expect( + _whereCacheGet(whereCache, schema, "X", { + field: "var", + column: "foo", + value: "bar", + }) + ).toEqual([0]); + expect( + _whereCacheGet(whereCache, schema, "X", { + field: "var", + column: "foo", + value: "baz", + }) + ).toEqual([1, 2]); + expect(_whereCacheGet(whereCache, schema, "Y", {})).toEqual([undefined]); + expect( + _whereCacheGet(whereCache, schema, "X", { + field: "whoknows", + column: "whatever", + value: "snork", + }) + ).toEqual([undefined]); + expect( + _whereCacheGet(whereCache, schema, "X", { + field: "var", + column: "whatever", + value: "snork", + }) + ).toEqual([undefined]); + expect( + _whereCacheGet(whereCache, schema, "X", { + field: "var", + column: "foo", + value: "snork", + }) + ).toEqual([undefined]); + }); + + test("whereCacheCreate", () => { + const query = { + field: "queryField", + column: "queryColumn", + value: "queryValue", + }; + const wc = _whereCacheCreate( + "field", + { field: "queryField", column: "queryColumn", value: "queryValue" }, + [0, 1, 2] + ); + expect(wc).toBeDefined(); + expect(wc).toEqual( + expect.objectContaining({ + field: { + queryField: expect.any(Map), + }, + }) + ); + expect(wc.field.queryField.has("queryColumn")).toEqual(true); + expect(wc.field.queryField.get("queryColumn")).toBeInstanceOf(Map); + expect(wc.field.queryField.get("queryColumn").has("queryValue")).toEqual( + true + ); + expect(_whereCacheGet(wc, schema, "field", query)).toEqual([0, 1, 2]); + }); + + test("whereCacheMerge", () => { + let wc; + + // remember, will mutate dst + const src = _whereCacheCreate( + "field", + { field: "queryField", column: "queryColumn", value: "foo" }, + ["foo"] + ); + + const dst1 = _whereCacheCreate( + "field", + { field: "queryField", column: "queryColumn", value: "bar" }, + ["dst1"] + ); + wc = _whereCacheMerge(dst1, src); + expect( + _whereCacheGet(wc, schema, "field", { + field: "queryField", + column: "queryColumn", + value: "foo", + }) + ).toEqual(["foo"]); + expect( + _whereCacheGet(wc, schema, "field", { + field: "queryField", + column: "queryColumn", + value: "bar", + }) + ).toEqual(["dst1"]); + + const dst2 = _whereCacheCreate( + "field", + { field: "queryField", column: "queryColumn", value: "bar" }, + ["dst2"] + ); + wc = _whereCacheMerge(dst2, dst1, src); + expect( + _whereCacheGet(wc, schema, "field", { + field: "queryField", + column: "queryColumn", + value: "foo", + }) + ).toEqual(["foo"]); + expect( + _whereCacheGet(wc, schema, "field", { + field: "queryField", + column: "queryColumn", + value: "bar", + }) + ).toEqual(["dst1"]); + + wc = _whereCacheMerge({}, src); + expect(wc).toEqual(src); + + wc = _whereCacheMerge({ field: { queryField: new Map() } }, src); + expect(wc).toEqual(src); + }); +}); diff --git a/client/__tests__/util/centroid.test.js b/client/__tests__/util/centroid.test.js index 9ed75c3a..969bc3c9 100644 --- a/client/__tests__/util/centroid.test.js +++ b/client/__tests__/util/centroid.test.js @@ -1,53 +1,35 @@ import _ from "lodash"; import calcCentroid from "../../src/util/centroid"; - import quantile from "../../src/util/quantile"; -import * as Universe from "../../src/util/stateManager/universe"; import { matrixFBSToDataframe } from "../../src/util/stateManager/matrix"; -import * as World from "../../src/util/stateManager/world"; import * as REST from "./stateManager/sampleResponses"; -import { ControlsHelpers as CH } from "../../src/util/stateManager"; +import { indexEntireSchema } from "../../src/util/stateManager/schemaHelpers"; +import { _normalizeCategoricalSchema } from "../../src/annoMatrix/schema"; describe("centroid", () => { - let world; - let categoricalSelection; + let schema; + let obsAnnotations; + let obsLayout; + beforeAll(() => { - // Create world + universe - let universe = Universe.createUniverseFromResponse( - _.cloneDeep(REST.config), - _.cloneDeep(REST.schema) - ); + schema = indexEntireSchema(_.cloneDeep(REST.schema.schema)); + obsAnnotations = matrixFBSToDataframe(REST.annotationsObs); + obsLayout = matrixFBSToDataframe(REST.layoutObs); - universe = { - ...universe, - ...Universe.addObsAnnotations( - universe, - matrixFBSToDataframe(REST.annotationsObs) - ), - ...Universe.addVarAnnotations( - universe, - matrixFBSToDataframe(REST.annotationsVar) - ), - ...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)), - }; - world = World.createWorldFromEntireUniverse(universe); - - // Create categorical selection from world - categoricalSelection = CH.createCategoricalSelection( - world, - CH.selectableCategoryNames(world.schema, CH.maxCategoryItems(REST.config)) + _normalizeCategoricalSchema( + schema.annotations.obsByName.field3, + obsAnnotations.col("field3") ); }); test("field4 (categorical obsAnnotation)", () => { const centroidResult = calcCentroid( - world.obsAnnotations, - world.obsLayout, + schema, "field4", - ["umap_0", "umap_1"], - categoricalSelection, - world.schema.annotations.obsByName + obsAnnotations, + { current: "umap", currentDimNames: ["umap_0", "umap_1"] }, + obsLayout ); // Check to see that a centroid has been calculated for every categorical value @@ -58,8 +40,8 @@ describe("centroid", () => { // This expected result assumes that all cells belong in all categorical values inside of sample response const expectedResult = [ - quantile([0.5], world.obsLayout.col("umap_0").asArray())[0], - quantile([0.5], world.obsLayout.col("umap_1").asArray())[0], + quantile([0.5], obsLayout.col("umap_0").asArray())[0], + quantile([0.5], obsLayout.col("umap_1").asArray())[0], ]; centroidResult.forEach((coordinate) => { @@ -69,12 +51,11 @@ describe("centroid", () => { test("field3 (boolean obsAnnotation)", () => { const centroidResult = calcCentroid( - world.obsAnnotations, - world.obsLayout, + schema, "field3", - ["umap_0", "umap_1"], - categoricalSelection, - world.schema.annotations.obsByName + obsAnnotations, + { current: "umap", currentDimNames: ["umap_0", "umap_1"] }, + obsLayout ); // Check to see that a centroid has been calculated for every categorical value @@ -83,8 +64,8 @@ describe("centroid", () => { // This expected result assumes that all cells belong in all categorical values inside of sample response const expectedResult = [ - quantile([0.5], world.obsLayout.col("umap_0").asArray())[0], - quantile([0.5], world.obsLayout.col("umap_1").asArray())[0], + quantile([0.5], obsLayout.col("umap_0").asArray())[0], + quantile([0.5], obsLayout.col("umap_1").asArray())[0], ]; centroidResult.forEach((coordinate) => { diff --git a/client/__tests__/util/dataframe/dataframe.test.js b/client/__tests__/util/dataframe/dataframe.test.js index b15df2e1..727f0a7f 100644 --- a/client/__tests__/util/dataframe/dataframe.test.js +++ b/client/__tests__/util/dataframe/dataframe.test.js @@ -918,82 +918,525 @@ describe("dataframe col", () => { }); describe("label indexing", () => { + describe("isLabelIndex", () => { + expect( + Dataframe.isLabelIndex(new Dataframe.IdentityInt32Index(4)) + ).toBeTruthy(); + expect( + Dataframe.isLabelIndex(new Dataframe.DenseInt32Index([2, 4, 99])) + ).toBeTruthy(); + expect( + Dataframe.isLabelIndex(new Dataframe.KeyIndex(["a", 4, "toasty"])) + ).toBeTruthy(); + expect(Dataframe.isLabelIndex(false)).toBeFalsy(); + expect(Dataframe.isLabelIndex(undefined)).toBeFalsy(); + expect(Dataframe.isLabelIndex(null)).toBeFalsy(); + expect(Dataframe.isLabelIndex(true)).toBeFalsy(); + expect(Dataframe.isLabelIndex([])).toBeFalsy(); + expect(Dataframe.isLabelIndex({})).toBeFalsy(); + expect(Dataframe.isLabelIndex(Dataframe.IdentityInt32Index)).toBeFalsy(); + }); - test("IdentityInt32Index", () => { + describe("IdentityInt32Index", () => { const idx = new Dataframe.IdentityInt32Index(12); // [0, 12) - expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + test("create", () => { + expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + }); - expect(idx.labels()).toEqual(new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); - expect(idx.getLabel(1)).toEqual(1); - expect(idx.getOffset(1)).toEqual(1); - expect(idx.getOffsets([1,3])).toEqual([1,3]) - expect(idx.getLabels([1, 3])).toEqual([1,3]) - expect(idx.size()).toEqual(12); + test("labels", () => { + expect(idx.labels()).toEqual( + new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + ); + expect(idx.getLabel(1)).toEqual(1); + expect(idx.getLabels([1, 3])).toEqual([1, 3]); + expect(idx.size()).toEqual(12); + }); - expect(idx.subset([2]).labels()).toEqual([2]); - expect(idx.subset([2, 3, 4]).labels()).toEqual(new Int32Array([2, 3, 4])); - expect(idx.subset([0, 1, 2, 3]).labels()).toEqual(new Int32Array([0, 1, 2, 3])); + test("offsets", () => { + expect(idx.getOffset(1)).toEqual(1); + expect(idx.getOffsets([1, 3])).toEqual([1, 3]); + }); - expect(idx.isubset([2]).labels()).toEqual([2]); - expect(idx.isubset([2, 3, 4]).labels()).toEqual(new Int32Array([2, 3, 4])); - expect(idx.isubset([0, 1, 2, 3]).labels()).toEqual(new Int32Array([0, 1, 2, 3])); + test("subset", () => { + expect(idx.subset([2]).labels()).toEqual([2]); + expect(idx.subset([2, 3, 4]).labels()).toEqual(new Int32Array([2, 3, 4])); + expect(idx.subset([0, 1, 2, 3]).labels()).toEqual( + new Int32Array([0, 1, 2, 3]) + ); + expect(idx.subset([0, 1, 2, 3, 4])).toBeInstanceOf( + Dataframe.IdentityInt32Index + ); + expect(idx.subset([2, 1, 0])).toBeInstanceOf( + Dataframe.IdentityInt32Index + ); + expect(idx.subset([1, 2, 3, 4])).toBeInstanceOf( + Dataframe.DenseInt32Index + ); + expect(idx.subset([0, 1, 3, 4])).toBeInstanceOf( + Dataframe.DenseInt32Index + ); + expect(idx.subset([0, 1, 2, 3, 10])).toBeInstanceOf( + Dataframe.DenseInt32Index + ); + expect(idx.subset([4, 3, 2, 1])).toBeInstanceOf( + Dataframe.DenseInt32Index + ); + expect(idx.subset([4])).toBeInstanceOf(Dataframe.KeyIndex); + }); - expect(idx.subset([0, 1, 2, 3, 4])).toBeInstanceOf(Dataframe.IdentityInt32Index); - expect(idx.subset([2, 1, 0])).toBeInstanceOf(Dataframe.IdentityInt32Index); - expect(idx.subset([1, 2, 3, 4])).toBeInstanceOf(Dataframe.DenseInt32Index); - expect(idx.subset([0, 1, 3, 4])).toBeInstanceOf(Dataframe.DenseInt32Index); - expect(idx.subset([0, 1, 2, 3, 10])).toBeInstanceOf(Dataframe.DenseInt32Index); - expect(idx.subset([4, 3, 2, 1])).toBeInstanceOf(Dataframe.DenseInt32Index); - expect(idx.subset([4])).toBeInstanceOf(Dataframe.KeyIndex); + test("isubset", () => { + expect(idx.isubset([2]).labels()).toEqual([2]); + expect(idx.isubset([2, 3, 4]).labels()).toEqual( + new Int32Array([2, 3, 4]) + ); + expect(idx.isubset([0, 1, 2, 3]).labels()).toEqual( + new Int32Array([0, 1, 2, 3]) + ); + expect(() => idx.isubset([-1001])).toThrow(RangeError); + expect(() => idx.isubset([1001])).toThrow(RangeError); + }); - expect(idx.withLabel(99).labels()).toEqual(new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99])); - expect(idx.dropLabel(0).labels()).toEqual(new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); - expect(idx.dropLabel(11).labels()).toEqual(new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); - expect(idx.dropLabel(5).labels()).toEqual(new Int32Array([0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11])); + test("isubsetMask", () => { + expect( + idx + .isubsetMask([ + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + ]) + .labels() + ).toEqual(new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); + expect( + idx + .isubsetMask([ + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + ]) + .labels() + ).toEqual(new Int32Array([])); + expect( + idx + .isubsetMask([ + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + ]) + .labels() + ).toEqual(new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])); + expect( + idx + .isubsetMask([ + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true, + ]) + .labels() + ).toEqual(new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 11])); + expect( + idx + .isubsetMask([ + false, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + false, + ]) + .labels() + ).toEqual(new Int32Array([1, 2, 4, 5, 6, 7, 8, 9, 10])); + expect(() => idx.isubsetMask([])).toThrow(RangeError); + }); + + test("withLabel", () => { + expect(idx.withLabel(99).labels()).toEqual( + new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99]) + ); + expect(idx.withLabel(12).labels()).toEqual( + new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + ); + expect(idx.withLabels([12, 13]).labels()).toEqual( + new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) + ); + }); + + test("dropLabel", () => { + expect(idx.dropLabel(0).labels()).toEqual( + new Int32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + ); + expect(idx.dropLabel(11).labels()).toEqual( + new Int32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + ); + expect(idx.dropLabel(5).labels()).toEqual( + new Int32Array([0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11]) + ); + }); }); - test("DenseInt32Index", () => { - + describe("DenseInt32Index", () => { const idx = new Dataframe.DenseInt32Index([99, 1002, 48, 0, 22]); - expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + test("create", () => { + expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + }); - expect(idx.labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22])); - expect(idx.size()).toEqual(5); - expect(idx.getOffset(1002)).toEqual(1); - expect(idx.getOffset(0)).toEqual(3); - expect(idx.getLabel(0)).toEqual(99); - expect(idx.getLabels(new Int32Array([2, 4]))).toEqual(new Int32Array([48, 22])); - expect(idx.getLabels([2, 4])).toEqual([48, 22]); - expect(idx.getOffsets([0, 48])).toEqual([3, 2]); + test("labels", () => { + expect(idx.labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22])); + expect(idx.size()).toEqual(5); + expect(idx.getLabel(0)).toEqual(99); + expect(idx.getLabels(new Int32Array([2, 4]))).toEqual( + new Int32Array([48, 22]) + ); + expect(idx.getLabels([2, 4])).toEqual([48, 22]); + }); - expect(idx.subset([1002, 0, 99]).labels()).toEqual(new Int32Array([1002, 0, 99])) - expect(idx.getOffsets(idx.subset([1002, 0, 99]).labels())).toEqual(new Int32Array([1, 3, 0])); - expect(idx.isubset([4, 1, 2]).labels()).toEqual(new Int32Array([22, 1002, 48])); + test("offsets", () => { + expect(idx.getOffset(1002)).toEqual(1); + expect(idx.getOffset(0)).toEqual(3); + expect(idx.getOffsets([0, 48])).toEqual([3, 2]); + }); - expect(idx.withLabel(88).labels()).toEqual(new Int32Array([99, 1002, 48, 0, 22, 88])); - expect(idx.withLabel(88).getOffset(88)).toEqual(5); - expect(idx.dropLabel(48).labels()).toEqual(new Int32Array([99, 1002, 0, 22])); + test("subset", () => { + expect(idx.subset([1002, 0, 99]).labels()).toEqual( + new Int32Array([1002, 0, 99]) + ); + expect(idx.getOffsets(idx.subset([1002, 0, 99]).labels())).toEqual( + new Int32Array([1, 3, 0]) + ); + expect(() => idx.subset([-1])).toThrow(RangeError); + }); + + test("isubset", () => { + expect(idx.isubset([4, 1, 2]).labels()).toEqual( + new Int32Array([22, 1002, 48]) + ); + expect(() => idx.isubset([-1001])).toThrow(RangeError); + expect(() => idx.isubset([1001])).toThrow(RangeError); + }); + + test("isubsetMask", () => { + expect(idx.isubsetMask([true, true, true, true, true]).labels()).toEqual( + new Int32Array([99, 1002, 48, 0, 22]) + ); + expect( + idx.isubsetMask([false, false, false, false, false]).labels() + ).toEqual(new Int32Array([])); + expect(idx.isubsetMask([true, true, false, true, true]).labels()).toEqual( + new Int32Array([99, 1002, 0, 22]) + ); + expect( + idx.isubsetMask([false, true, true, true, false]).labels() + ).toEqual(new Int32Array([1002, 48, 0])); + expect(() => idx.isubsetMask([])).toThrow(RangeError); + }); + + test("withLabel", () => { + expect(idx.withLabel(88).labels()).toEqual( + new Int32Array([99, 1002, 48, 0, 22, 88]) + ); + expect(idx.withLabel(88).getOffset(88)).toEqual(5); + expect(idx.withLabels([88, 99]).labels()).toEqual( + new Int32Array([99, 1002, 48, 0, 22, 88, 99]) + ); + }); + test("dropLabel", () => { + expect(idx.dropLabel(48).labels()).toEqual( + new Int32Array([99, 1002, 0, 22]) + ); + }); }); - test("KeyIndex", () => { + describe("KeyIndex", () => { const idx = new Dataframe.KeyIndex(["red", "green", "blue"]); - expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + test("create", () => { + expect(Dataframe.isLabelIndex(idx)).toBeTruthy(); + expect(() => new Dataframe.KeyIndex(["dup", "dup"])).toThrow(Error); + expect(new Dataframe.KeyIndex().size()).toEqual(0); + }); - expect(idx.labels()).toEqual(["red", "green", "blue"]); - expect(idx.size()).toEqual(3); - expect(idx.getOffset("blue")).toEqual(2); - expect(idx.getLabel(1)).toEqual("green"); + test("labels", () => { + expect(idx.labels()).toEqual(["red", "green", "blue"]); + expect(idx.size()).toEqual(3); + expect(idx.getLabel(1)).toEqual("green"); + expect(idx.getLabels([2, 0])).toEqual(["blue", "red"]); + }); - expect(idx.subset(["green"]).labels()).toEqual(["green"]); - expect(idx.subset(["green", "red"]).labels()).toEqual(["green", "red"]); - expect(idx.isubset([2, 1, 0]).labels()).toEqual(["blue", "green", "red"]); + test("offsets", () => { + expect(idx.getOffset("blue")).toEqual(2); + }); - expect(idx.withLabel("yo").labels()).toEqual(["red", "green", "blue", "yo"]); - expect(idx.withLabel("yo").getOffset("yo")).toEqual(3); - expect(idx.dropLabel("blue").labels()).toEqual(["red", "green"]); + test("subset", () => { + expect(idx.subset(["green"]).labels()).toEqual(["green"]); + expect(idx.subset(["green", "red"]).labels()).toEqual(["green", "red"]); + }); + + test("isubset", () => { + expect(idx.isubset([2, 1, 0]).labels()).toEqual(["blue", "green", "red"]); + expect(() => idx.isubset([-1001])).toThrow(RangeError); + expect(() => idx.isubset([1001])).toThrow(RangeError); + }); + + test("isubsetMask", () => { + expect(idx.isubsetMask([true, true, true]).labels()).toEqual([ + "red", + "green", + "blue", + ]); + expect(idx.isubsetMask([false, false, false]).labels()).toEqual([]); + expect(idx.isubsetMask([true, false, true]).labels()).toEqual([ + "red", + "blue", + ]); + expect(() => idx.isubsetMask([])).toThrow(RangeError); + }); + + test("withLabel", () => { + expect(idx.withLabel("yo").labels()).toEqual([ + "red", + "green", + "blue", + "yo", + ]); + expect(idx.withLabel("yo").getOffset("yo")).toEqual(3); + expect(idx.withLabels(["hey", "there"]).labels()).toEqual([ + "red", + "green", + "blue", + "hey", + "there", + ]); + }); + + test("dropLabel", () => { + expect(idx.dropLabel("blue").labels()).toEqual(["red", "green"]); + }); + }); }); -}) \ No newline at end of file +describe("corner cases", () => { + /* error/corner cases */ + + test("identity integer index rejects non-integer labels", () => { + const idx = new Dataframe.IdentityInt32Index(10); + expect(idx.getOffset(0)).toBe(0); + expect(idx.getOffset(9)).toBe(9); + expect(idx.getOffset(10)).toBeUndefined(); + expect(idx.getOffset(-1)).toBeUndefined(); + expect(idx.getOffset("sort")).toBeUndefined(); + expect(idx.getOffset("length")).toBeUndefined(); + expect(idx.getOffset(true)).toBeUndefined(); + expect(idx.getOffset(0.001)).toBeUndefined(); + expect(idx.getOffset({})).toBeUndefined(); + expect(idx.getOffset([])).toBeUndefined(); + expect(idx.getOffset(new Float32Array())).toBeUndefined(); + expect(idx.getOffset("__proto__")).toBeUndefined(); + + expect(idx.getLabel(0)).toBe(0); + expect(idx.getLabel(9)).toBe(9); + expect(idx.getLabel(10)).toBeUndefined(); + expect(idx.getLabel(-1)).toBeUndefined(); + expect(idx.getLabel("sort")).toBeUndefined(); + expect(idx.getLabel("length")).toBeUndefined(); + expect(idx.getLabel(true)).toBeUndefined(); + expect(idx.getLabel(0.001)).toBeUndefined(); + expect(idx.getLabel({})).toBeUndefined(); + expect(idx.getLabel([])).toBeUndefined(); + expect(idx.getLabel(new Float32Array())).toBeUndefined(); + expect(idx.getLabel("__proto__")).toBeUndefined(); + }); + + test("dense integer index rejects non-integer labels", () => { + const idx = new Dataframe.DenseInt32Index([-10, 0, 3, 9, 10]); + expect(idx.getOffset(0)).toBe(1); + expect(idx.getOffset(9)).toBe(3); + expect(idx.getOffset(1)).toBeUndefined(); + expect(idx.getOffset(11)).toBeUndefined(); + expect(idx.getOffset(-1)).toBeUndefined(); + expect(idx.getOffset("sort")).toBeUndefined(); + expect(idx.getOffset("length")).toBeUndefined(); + expect(idx.getOffset(true)).toBeUndefined(); + expect(idx.getOffset(0.001)).toBeUndefined(); + expect(idx.getOffset({})).toBeUndefined(); + expect(idx.getOffset([])).toBeUndefined(); + expect(idx.getOffset(new Float32Array())).toBeUndefined(); + expect(idx.getOffset("__proto__")).toBeUndefined(); + + expect(idx.getLabel(0)).toBe(-10); + expect(idx.getLabel(4)).toBe(10); + expect(idx.getLabel(10)).toBeUndefined(); + expect(idx.getLabel(-1)).toBeUndefined(); + expect(idx.getLabel("sort")).toBeUndefined(); + expect(idx.getLabel("length")).toBeUndefined(); + expect(idx.getLabel(true)).toBeUndefined(); + expect(idx.getLabel(0.001)).toBeUndefined(); + expect(idx.getLabel({})).toBeUndefined(); + expect(idx.getLabel([])).toBeUndefined(); + expect(idx.getLabel(new Float32Array())).toBeUndefined(); + expect(idx.getLabel("__proto__")).toBeUndefined(); + }); + + test("Empty dataframe rejects bogus labels", () => { + const df = Dataframe.Dataframe.empty(); + + expect(df.hasCol("sort")).toBeFalsy(); + expect(df.hasCol(0)).toBeFalsy(); + expect(df.hasCol(true)).toBeFalsy(); + expect(df.hasCol(false)).toBeFalsy(); + expect(df.hasCol([])).toBeFalsy(); + expect(df.hasCol({})).toBeFalsy(); + expect(df.hasCol(null)).toBeFalsy(); + expect(df.hasCol(undefined)).toBeFalsy(); + + expect(df.col("sort")).toBeUndefined(); + expect(df.col(0)).toBeUndefined(); + expect(df.col(true)).toBeUndefined(); + expect(df.col(false)).toBeUndefined(); + expect(df.col([])).toBeUndefined(); + expect(df.col({})).toBeUndefined(); + expect(df.col(null)).toBeUndefined(); + expect(df.col(undefined)).toBeUndefined(); + + expect(df.icol("sort")).toBeUndefined(); + expect(df.icol(0)).toBeUndefined(); + expect(df.icol(true)).toBeUndefined(); + expect(df.icol(false)).toBeUndefined(); + expect(df.icol([])).toBeUndefined(); + expect(df.icol({})).toBeUndefined(); + expect(df.icol(null)).toBeUndefined(); + expect(df.icol(undefined)).toBeUndefined(); + + expect(df.ihas("sort", "length")).toBeFalsy(); + expect(df.ihas("0", "0")).toBeFalsy(); + expect(df.ihas("", "")).toBeFalsy(); + expect(df.ihas(null, null)).toBeFalsy(); + expect(df.ihas(undefined, undefined)).toBeFalsy(); + expect(df.ihas(true, true)).toBeFalsy(); + expect(df.ihas([], [])).toBeFalsy(); + expect(df.ihas({}, {})).toBeFalsy(); + }); + + test("Dataframe rejects bogus labels", () => { + const df = new Dataframe.Dataframe( + [2, 2], + [ + [true, false], + [1, 0], + ], + null, + new Dataframe.KeyIndex(["A", "B"]) + ); + + expect(df.hasCol("sort")).toBeFalsy(); + expect(df.hasCol("__proto__")).toBeFalsy(); + expect(df.hasCol(0)).toBeFalsy(); + expect(df.hasCol(true)).toBeFalsy(); + expect(df.hasCol(false)).toBeFalsy(); + expect(df.hasCol([])).toBeFalsy(); + expect(df.hasCol({})).toBeFalsy(); + expect(df.hasCol(null)).toBeFalsy(); + expect(df.hasCol(undefined)).toBeFalsy(); + + expect(df.col("sort")).toBeUndefined(); + expect(df.col("__proto__")).toBeUndefined(); + expect(df.col(0)).toBeUndefined(); + expect(df.col(true)).toBeUndefined(); + expect(df.col(false)).toBeUndefined(); + expect(df.col([])).toBeUndefined(); + expect(df.col({})).toBeUndefined(); + expect(df.col(null)).toBeUndefined(); + expect(df.col(undefined)).toBeUndefined(); + + expect(df.icol("sort")).toBeUndefined(); + expect(df.icol("__proto__")).toBeUndefined(); + expect(df.icol(-1)).toBeUndefined(); + expect(df.icol(true)).toBeUndefined(); + expect(df.icol(false)).toBeUndefined(); + expect(df.icol([])).toBeUndefined(); + expect(df.icol({})).toBeUndefined(); + expect(df.icol(null)).toBeUndefined(); + expect(df.icol(undefined)).toBeUndefined(); + + expect(df.ihas("sort", "length")).toBeFalsy(); + expect(df.ihas("__proto__", "__proto__")).toBeFalsy(); + + expect(df.ihas(-1, 0)).toBeFalsy(); + expect(df.ihas("0", 0)).toBeFalsy(); + expect(df.ihas("", 0)).toBeFalsy(); + expect(df.ihas(null, 0)).toBeFalsy(); + expect(df.ihas(undefined, 0)).toBeFalsy(); + expect(df.ihas([], 0)).toBeFalsy(); + expect(df.ihas({}, 0)).toBeFalsy(); + + expect(df.ihas(0, -1)).toBeFalsy(); + expect(df.ihas(0, "0")).toBeFalsy(); + expect(df.ihas(0, "")).toBeFalsy(); + expect(df.ihas(0, null)).toBeFalsy(); + expect(df.ihas(0, undefined)).toBeFalsy(); + expect(df.ihas(0, [])).toBeFalsy(); + expect(df.ihas(0, {})).toBeFalsy(); + + expect(df.has("sort", "length")).toBeFalsy(); + expect(df.has("length", "sort")).toBeFalsy(); + expect(df.has("__proto__", "__proto__")).toBeFalsy(); + + expect(df.has(-1, "A")).toBeFalsy(); + expect(df.has("0", "A")).toBeFalsy(); + expect(df.has("", "A")).toBeFalsy(); + expect(df.has(null, "A")).toBeFalsy(); + expect(df.has(undefined, "A")).toBeFalsy(); + expect(df.has([], "A")).toBeFalsy(); + expect(df.has({}, "A")).toBeFalsy(); + + expect(df.has(0, -1)).toBeFalsy(); + expect(df.has(0, "0")).toBeFalsy(); + expect(df.has(0, "")).toBeFalsy(); + expect(df.has(0, null)).toBeFalsy(); + expect(df.has(0, undefined)).toBeFalsy(); + expect(df.has(0, [])).toBeFalsy(); + expect(df.has(0, {})).toBeFalsy(); + }); +}); diff --git a/client/__tests__/util/promiseLimit.test.js b/client/__tests__/util/promiseLimit.test.js index 659e2f23..b22ff5b1 100644 --- a/client/__tests__/util/promiseLimit.test.js +++ b/client/__tests__/util/promiseLimit.test.js @@ -1,7 +1,7 @@ import PromiseLimit from "../../src/util/promiseLimit"; import { range } from "../../src/util/range"; -const delay = (t) => new Promise((resolve, reject) => setTimeout(resolve, t)); +const delay = (t) => new Promise((resolve) => setTimeout(resolve, t)); describe("PromiseLimit", () => { test("simple evaluation, concurrency 1", async () => { @@ -28,13 +28,14 @@ describe("PromiseLimit", () => { test("eval in order of insertion", async () => { const plimit = new PromiseLimit(100); - let counter = 0; + const result = await Promise.all([ - plimit.add(() => Promise.resolve((counter += 1))), - plimit.add(() => Promise.resolve((counter += 1))), - plimit.add(() => Promise.resolve((counter += 1))), - plimit.add(() => Promise.resolve((counter += 1))), + plimit.add(() => Promise.resolve(1)), + plimit.add(() => Promise.resolve(2)), + plimit.add(() => Promise.resolve(3)), + plimit.add(() => Promise.resolve(4)), ]); + expect(result).toEqual([1, 2, 3, 4]); }); @@ -43,16 +44,15 @@ describe("PromiseLimit", () => { let running = 0; let maxRunning = 0; - const cbfn = async (i) => { - running = running + 1; + const callback = async () => { + running += 1; maxRunning = running > maxRunning ? running : maxRunning; await delay(100); - running = running - 1; + running -= 1; }; - const result = await Promise.all( - range(10).map((i) => plimit.add(() => cbfn(i))) - ); + await Promise.all(range(10).map((i) => plimit.add(() => callback(i)))); + expect(maxRunning).toEqual(2); }); @@ -60,6 +60,7 @@ describe("PromiseLimit", () => { const plimit = new PromiseLimit(2); const result = await Promise.all([ plimit.add(() => Promise.resolve("OK")), + // eslint-disable-next-line prefer-promise-reject-errors -- unit test plimit.add(() => Promise.reject("not OK")).catch((e) => e), plimit.add(() => Promise.resolve("OK")), plimit @@ -70,4 +71,26 @@ describe("PromiseLimit", () => { ]); expect(result).toEqual(["OK", "not OK", "OK", "not OK"]); }); + + test("priority queue", async () => { + const plimit = new PromiseLimit(1); + + let finishOrder = 0; + const callback = () => async () => { + await delay(100); + const result = finishOrder; + finishOrder += 1; + return result; + }; + + const result = await Promise.all([ + plimit.add(callback()), + plimit.priorityAdd(4, callback()), + plimit.priorityAdd(0, callback()), + plimit.priorityAdd(1, callback()), + plimit.priorityAdd(-1, callback()), + ]); + + expect(result).toEqual([0, 4, 2, 3, 1]); + }); }); diff --git a/client/__tests__/util/stateManager/controlsHelpers.test.js b/client/__tests__/util/stateManager/controlsHelpers.test.js index 923d7f5a..7e003843 100644 --- a/client/__tests__/util/stateManager/controlsHelpers.test.js +++ b/client/__tests__/util/stateManager/controlsHelpers.test.js @@ -4,7 +4,6 @@ test controls helpers import { subsetAndResetGeneLists } from "../../../src/util/stateManager/controlsHelpers"; import * as globals from "../../../src/globals"; - describe("controls helpers", () => { test("subsetAndResetGeneLists", () => { const geneList = []; @@ -28,7 +27,7 @@ describe("controls helpers", () => { ); const expectedNewUserDefinedGenes = [ ...geneList.slice(0, 20), - ...geneList.slice(21) + ...geneList.slice(21), ].slice(0, globals.maxGenes); expect(globals.maxUserDefinedGenes).toBeLessThan(globals.maxGenes); expect(geneList.length).toBeGreaterThan(globals.maxGenes); diff --git a/client/__tests__/util/stateManager/sampleResponses.js b/client/__tests__/util/stateManager/sampleResponses.js index 9e2041d0..ce086056 100644 --- a/client/__tests__/util/stateManager/sampleResponses.js +++ b/client/__tests__/util/stateManager/sampleResponses.js @@ -1,4 +1,3 @@ -/* eslint no-bitwise: "off" */ import _ from "lodash"; import { flatbuffers } from "flatbuffers"; import { NetEncoding } from "../../../src/util/stateManager/matrix_generated"; @@ -79,6 +78,7 @@ const anAnnotationsObsJSONResponse = { `obs${idx}`, 2 * idx, idx + 0.0133, + // eslint-disable-next-line no-bitwise -- idx & 1 to check for odd numbers !!(idx & 1), field4Categories[idx % field4Categories.length], ]) @@ -93,6 +93,7 @@ const anAnnotationsVarJSONResponse = { idx, 10 * idx, idx + 2.90143, + // eslint-disable-next-line no-bitwise -- idx & 1 to check for odd numbers !!(idx & 1), fieldDCategories[idx % fieldDCategories.length], `var${idx}`, @@ -112,7 +113,7 @@ function encodeTypedArray(builder, uType, uData) { function encodeMatrix(columns, colIndex = undefined) { /* IMPORTANT: this is not a general purpose encoder. in particular, - it doesn't correctly handle all column index types, nor does it + it doesn't correctly handle all column index types, nor does it handle all column typedarray types. encodeMatrixFBS in matrix.py is more general. This is used only diff --git a/client/__tests__/util/stateManager/universe.test.js b/client/__tests__/util/stateManager/universe.test.js deleted file mode 100644 index bf89058c..00000000 --- a/client/__tests__/util/stateManager/universe.test.js +++ /dev/null @@ -1,90 +0,0 @@ -import * as Universe from "../../../src/util/stateManager/universe"; -import { matrixFBSToDataframe } from "../../../src/util/stateManager/matrix"; -import * as Dataframe from "../../../src/util/dataframe"; -import * as REST from "./sampleResponses"; - -describe("createUniverseFromResponse", () => { - /* - test createUniverseFromResponse - this function converts - a set of REST 0.2 responses into a "new" Universe. - - createUniverseFromResponse( - configResponse, - schemaResponse, - annotationsObsResponse, - annotationsVarResponse, - layoutObsResponse - ) --> Universe - - where: - configResponse: GET /.../config - schemaResponse: GET /.../schema - annotationsObsResponse: GET /.../annotations/obs - annotationsVarResponse: GET /.../annotations/var - layoutObsResponse: GET /.../layout/obs - - See spec in docs/REST_API.md. - */ - - test("create from test data", () => { - /* - create a universe from sample data nad validate its shape & contents - */ - const { nObs, nVar } = REST.schema.schema.dataframe; - let universe = Universe.createUniverseFromResponse( - REST.config, - REST.schema - ); - expect(universe).toBeDefined(); - expect(universe).toMatchObject( - expect.objectContaining({ - nObs, - nVar, - schema: REST.schema.schema, - obsAnnotations: expect.any(Dataframe.Dataframe), - varAnnotations: expect.any(Dataframe.Dataframe), - obsLayout: expect.any(Dataframe.Dataframe), - varData: expect.any(Dataframe.Dataframe), - }) - ); - - universe = { - ...universe, - ...Universe.addObsAnnotations( - universe, - matrixFBSToDataframe(REST.annotationsObs) - ), - ...Universe.addVarAnnotations( - universe, - matrixFBSToDataframe(REST.annotationsVar) - ), - ...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)), - }; - - expect(universe).toMatchObject( - expect.objectContaining({ - nObs, - nVar, - schema: REST.schema.schema, - obsAnnotations: expect.any(Dataframe.Dataframe), - varAnnotations: expect.any(Dataframe.Dataframe), - obsLayout: expect.any(Dataframe.Dataframe), - varData: expect.any(Dataframe.Dataframe), - }) - ); - - expect(universe.obsAnnotations.dims).toEqual([ - nObs, - REST.schema.schema.annotations.obs.columns.length, - ]); - expect(universe.obsLayout.dims).toEqual([nObs, 2]); - expect(universe.obsLayout.colIndex.labels()).toEqual( - universe.schema.layout.obs[0].dims - ); - expect(universe.varAnnotations.dims).toEqual([ - nVar, - REST.schema.schema.annotations.var.columns.length, - ]); - expect(universe.varData.isEmpty()).toBeTruthy(); - }); -}); diff --git a/client/__tests__/util/stateManager/world.test.js b/client/__tests__/util/stateManager/world.test.js deleted file mode 100644 index ec6adc76..00000000 --- a/client/__tests__/util/stateManager/world.test.js +++ /dev/null @@ -1,202 +0,0 @@ -import _ from "lodash"; -import * as Universe from "../../../src/util/stateManager/universe"; -import { matrixFBSToDataframe } from "../../../src/util/stateManager/matrix"; -import * as World from "../../../src/util/stateManager/world"; -import * as Dataframe from "../../../src/util/dataframe"; -import Crossfilter from "../../../src/util/typedCrossfilter"; -import { DimTypes } from "../../../src/util/typedCrossfilter/crossfilter"; -import * as REST from "./sampleResponses"; -import { - obsAnnoDimensionName, - layoutDimensionName, -} from "../../../src/util/nameCreators"; - -/* -Helper - creates universe, world, corssfilter and dimensionMap from -the default REST test response. -*/ -const defaultBigBang = () => { - /* create unverse, world, crossfilter and dimensionMap */ - /* create universe */ - let universe = Universe.createUniverseFromResponse( - _.cloneDeep(REST.config), - _.cloneDeep(REST.schema) - ); - - universe = { - ...universe, - ...Universe.addObsAnnotations( - universe, - matrixFBSToDataframe(REST.annotationsObs) - ), - ...Universe.addVarAnnotations( - universe, - matrixFBSToDataframe(REST.annotationsVar) - ), - ...Universe.addObsLayout(universe, matrixFBSToDataframe(REST.layoutObs)), - }; - - /* create world */ - const world = World.createWorldFromEntireUniverse(universe); - /* create crossfilter */ - const crossfilter = World.createObsDimensions( - new Crossfilter(world.obsAnnotations), - world, - REST.schema.schema.layout.obs[0].dims - ); - - return { - universe, - world, - crossfilter, - }; -}; - -describe("createWorldFromEntireUniverse", () => { - test("create from REST sample", () => { - const universe = Universe.createUniverseFromResponse( - _.cloneDeep(REST.config), - _.cloneDeep(REST.schema), - matrixFBSToDataframe(_.cloneDeep(REST.annotationsObs)), - matrixFBSToDataframe(_.cloneDeep(REST.annotationsVar)), - matrixFBSToDataframe(_.cloneDeep(REST.layoutObs)) - ); - expect(universe).toBeDefined(); - - const world = World.createWorldFromEntireUniverse(universe); - expect(world).toBeDefined(); - - expect(world).toMatchObject( - expect.objectContaining({ - nObs: universe.nObs, - nVar: universe.nVar, - schema: universe.schema, - obsAnnotations: expect.any(Dataframe.Dataframe), - varAnnotations: expect.any(Dataframe.Dataframe), - obsLayout: expect.any(Dataframe.Dataframe), - varData: expect.any(Dataframe.Dataframe), - clipQuantiles: { min: 0, max: 1 }, - unclipped: { - obsAnnotations: expect.any(Dataframe.Dataframe), - varData: expect.any(Dataframe.Dataframe), - }, - }) - ); - }); -}); - -describe("createWorldFromCurrentSelection", () => { - test("create from REST sample", () => { - const { - universe, - world: originalWorld, - crossfilter: originalCrossfilter, - } = defaultBigBang(); - - /* mock a selection */ - const crossfilter = originalCrossfilter - .select(obsAnnoDimensionName("field1"), { mode: "range", lo: 0, hi: 5 }) - .select(obsAnnoDimensionName("field3"), { - mode: "exact", - values: [false], - }); - - /* create the world from the selection */ - const world = World.createWorldBySelection( - universe, - originalWorld, - crossfilter - ); - expect(world).toBeDefined(); - expect(world.nObs).toEqual(crossfilter.countSelected()); - - /* - calculate expected values and match against result - */ - - /* matchFilter must match the dimension filters above */ - const matchFilter = (df, row) => { - const field1 = df.at(row, "field1"); - const field3 = df.at(row, "field3"); - return field1 >= 0 && field1 < 5 && !field3; - }; - const matchingIndices = _() - .range(universe.nObs) - .filter((idx) => matchFilter(universe.obsAnnotations, idx)) - .value(); - - expect(world).toMatchObject( - expect.objectContaining({ - nObs: matchingIndices.length, - nVar: universe.nVar, - schema: universe.schema, - clipQuantiles: { min: 0, max: 1 }, - obsAnnotations: expect.any(Dataframe.Dataframe), - varAnnotations: expect.any(Dataframe.Dataframe), - obsLayout: expect.any(Dataframe.Dataframe), - varData: expect.any(Dataframe.Dataframe), - unclipped: { - obsAnnotations: expect.any(Dataframe.Dataframe), - varData: expect.any(Dataframe.Dataframe), - }, - }) - ); - - expect(world.obsAnnotations.rowIndex.labels()).toEqual( - new Int32Array(matchingIndices) - ); - expect(world.obsAnnotations.colIndex.labels()).toEqual( - universe.obsAnnotations.colIndex.labels() - ); - expect(world.obsLayout.rowIndex.labels()).toEqual( - new Int32Array(matchingIndices) - ); - expect(world.obsLayout.colIndex.labels()).toEqual( - world.schema.layout.obs[0].dims - ); - }); -}); - -describe("createObsDimensionMap", () => { - test("when universe eq world", () => { - /* - check for: - - creates a dimension for all obsAnnotations, PLUS X/Y layout - - check that dimension typing is sane - */ - - const { crossfilter } = defaultBigBang(); - const annotationNames = _.map( - REST.schema.schema.annotations.obs.columns, - (c) => c.name - ); - const obsIndexColName = REST.schema.schema.annotations.obs.index; - const schemaByObsName = _.keyBy( - REST.schema.schema.annotations.obs.columns, - "name" - ); - expect(crossfilter).toBeDefined(); - annotationNames.forEach((name) => { - const dim = crossfilter.dimensions[obsAnnoDimensionName(name)]; - if (name === obsIndexColName) { - expect(dim).toBeUndefined(); - } else { - const { type } = schemaByObsName[name]; - if (type === "string" || type === "boolean" || type === "categorical") { - expect(dim.dim).toBeInstanceOf(DimTypes.enum); - } else { - expect(dim.dim).toBeInstanceOf(DimTypes.scalar); - } - } - }); - expect( - crossfilter.dimensions[layoutDimensionName("XY")].dim - ).toBeInstanceOf(DimTypes.spatial); - }); -}); - -describe("worldEqUniverse", () => { - const { universe, world } = defaultBigBang(); - const result = World.worldEqUniverse(world, universe); - expect(result).toBe(true); -}); diff --git a/client/__tests__/util/typedCrossfilter/crossfilter.test.js b/client/__tests__/util/typedCrossfilter/crossfilter.test.js index a65d2949..25ccd863 100644 --- a/client/__tests__/util/typedCrossfilter/crossfilter.test.js +++ b/client/__tests__/util/typedCrossfilter/crossfilter.test.js @@ -253,6 +253,11 @@ describe("ImmutableTypedCrossfilter", () => { p.select("quantity", { mode: "exact", values: v }).countSelected() ).toEqual(_.filter(someData, (d) => v.includes(d.quantity)).length) ); + test("single value exact", () => { + expect( + p.select("quantity", { mode: "exact", values: 2 }).countSelected() + ).toEqual(_.filter(someData, (d) => d.quantity === 2).length); + }); test.each([ [0, 1], [1, 2], @@ -295,6 +300,11 @@ describe("ImmutableTypedCrossfilter", () => { p.select("type", { mode: "exact", values: v }).countSelected() ).toEqual(_.filter(someData, (d) => v.includes(d.type)).length) ); + test("single value exact", () => { + expect( + p.select("type", { mode: "exact", values: "tab" }).countSelected() + ).toEqual(_.filter(someData, (d) => d.type === "tab").length); + }); test("range", () => { expect(() => p.select("type", { mode: "range", lo: 0, hi: 9 })).toThrow( Error diff --git a/client/__tests__/util/typedCrossfilter/sort.test.js b/client/__tests__/util/typedCrossfilter/sort.test.js index ea155c29..f7650f4f 100644 --- a/client/__tests__/util/typedCrossfilter/sort.test.js +++ b/client/__tests__/util/typedCrossfilter/sort.test.js @@ -2,9 +2,6 @@ import { sortArray, sortIndex, lowerBound, - upperBound, - lowerBoundIndirect, - upperBoundIndirect, } from "../../../src/util/typedCrossfilter/sort"; /* @@ -68,7 +65,7 @@ describe("sortArray", () => { }); describe("non-finite numbers", () => { - test("inifinity", () => { + test("infinity", () => { expect(sortArray(new Float32Array([pInf, nInf, 0, 1, 2]))).toMatchObject( new Float32Array([nInf, 0, 1, 2, pInf]) ); diff --git a/client/configuration/eslint/eslint.js b/client/configuration/eslint/eslint.js index 5ebc32ef..a1189581 100644 --- a/client/configuration/eslint/eslint.js +++ b/client/configuration/eslint/eslint.js @@ -1,9 +1,23 @@ module.exports = { root: true, parser: "babel-eslint", - extends: ["airbnb", "plugin:prettier/recommended", "prettier/react"], + extends: [ + "airbnb", + "plugin:eslint-comments/recommended", + "plugin:prettier/recommended", + "prettier/react", + ], env: { browser: true, commonjs: true, es6: true }, - globals: { expect: true }, + globals: { + expect: true, + jest: true, + jestPuppeteer: true, + it: true, + page: true, + browser: true, + context: true, + beforeEach: true, + }, parserOptions: { ecmaVersion: 2017, sourceType: "module", @@ -13,6 +27,7 @@ module.exports = { }, }, rules: { + "eslint-comments/require-description": ["error"], "no-magic-numbers": "off", "no-nested-ternary": "off", "func-style": "off", @@ -30,6 +45,13 @@ module.exports = { "space-before-function-paren": "off", "function-paren-newline": "off", "prefer-destructuring": ["error", { object: true, array: false }], + "import/prefer-default-export": "off", + "no-restricted-syntax": [ + "error", + "ForInStatement", + "LabeledStatement", + "WithStatement", + ], }, overrides: [ { diff --git a/client/configuration/lint-staged/lint-staged.config.js b/client/configuration/lint-staged/lint-staged.config.js index 8d9c56b8..e8a2415d 100644 --- a/client/configuration/lint-staged/lint-staged.config.js +++ b/client/configuration/lint-staged/lint-staged.config.js @@ -1,3 +1,3 @@ module.exports = { - "./src/**/*.js": "eslint --fix", + "*.js": "eslint --fix", }; diff --git a/client/jest-puppeteer.config.js b/client/jest-puppeteer.config.js new file mode 100644 index 00000000..a3391a6f --- /dev/null +++ b/client/jest-puppeteer.config.js @@ -0,0 +1,46 @@ +/** + * `client/jest-puppeteer.config.js` is for configuring Puppeteer's launch config options + * `client/__tests__/e2e/puppeteer.setup.js` is for configuring `jest`, `browser`, + * and `page` objects + */ + +const ENV_DEFAULT = require("../environment.default.json"); + +const jestEnv = process.env.JEST_ENV || ENV_DEFAULT.JEST_ENV; +const isHeadful = + process.env.HEADFUL === "true" || process.env.HEADLESS === "false"; + +const DEFAULT_LAUNCH_CONFIG = { + headless: !isHeadful, + args: ["--ignore-certificate-errors", "--ignore-ssl-errors"], + ignoreHTTPSErrors: true, + defaultViewport: { + width: 1280, + height: 960, + }, +}; + +const LAUNCH_CONFIG_BY_ENV = { + [ENV_DEFAULT.DEBUG]: { + ...DEFAULT_LAUNCH_CONFIG, + headless: false, + slowMo: 100, + devtools: true, + defaultViewport: { + width: DEFAULT_LAUNCH_CONFIG.defaultViewport.width, + height: DEFAULT_LAUNCH_CONFIG.defaultViewport.height + 560, + }, + }, + [ENV_DEFAULT.DEV]: { + ...DEFAULT_LAUNCH_CONFIG, + headless: false, + slowMo: 5, + }, +}; + +const launchConfig = LAUNCH_CONFIG_BY_ENV[jestEnv] || DEFAULT_LAUNCH_CONFIG; + +module.exports = { + browserContext: "incognito", + launch: launchConfig, +}; diff --git a/client/package-lock.json b/client/package-lock.json index 8a1d16c6..e9a54d60 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,91 +8,243 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, "requires": { "@babel/highlight": "^7.8.3" } }, "@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.10.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.5.tgz", + "integrity": "sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw==", "dev": true, "requires": { - "browserslist": "^4.9.1", + "browserslist": "^4.12.0", "invariant": "^2.2.4", "semver": "^5.5.0" + }, + "dependencies": { + "browserslist": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "caniuse-lite": { + "version": "1.0.30001100", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001100.tgz", + "integrity": "sha512-0eYdp1+wFCnMlCj2oudciuQn2B9xAFq3WpgpcBIZTxk/1HNA/O2YA7rpeYhnOqsqAJq1AHUgx6i1jtafg7m2zA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.498", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.498.tgz", + "integrity": "sha512-W1hGwaQEU8j9su2jeAr3aabkPuuXw+j8t73eajGAkEJWbfWiwbxBwQN/8Qmv2qCy3uCDm2rOAaZneYQM8VGC4w==", + "dev": true + }, + "node-releases": { + "version": "1.1.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.59.tgz", + "integrity": "sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw==", + "dev": true + } } }, "@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, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz", + "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==", "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", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.5", + "@babel/types": "^7.10.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" }, "dependencies": { - "@babel/generator": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", - "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", - "dev": true, + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "requires": { - "@babel/types": "^7.9.0", + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "requires": { + "@babel/types": "^7.10.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, + "@babel/helper-function-name": { + "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==", + "requires": { + "@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.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==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "requires": { + "@babel/types": "^7.10.5" + } + }, + "@babel/helper-module-imports": { + "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==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", + "requires": { + "@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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "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==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "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==", + "requires": { + "@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.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==", + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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==" + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "requires": { + "@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==", - "dev": true + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==" + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } }, "@babel/traverse": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", - "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.0", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } } } }, @@ -100,7 +252,6 @@ "version": "7.8.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.8.tgz", "integrity": "sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg==", - "dev": true, "requires": { "@babel/types": "^7.8.7", "jsesc": "^2.5.1", @@ -118,87 +269,337 @@ } }, "@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" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@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" }, "dependencies": { - "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "@babel/helper-annotate-as-pure": { + "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/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } } } }, "@babel/helper-builder-react-jsx-experimental": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz", - "integrity": "sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.5.tgz", + "integrity": "sha512-Buewnx6M4ttG+NLkKyt7baQn7ScC/Td+e99G914fRU8fGIUivDDgVIQeDHFa5e4CRSJQt58WpNHhsAZgtzVhsg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-module-imports": "^7.8.3", - "@babel/types": "^7.9.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/types": "^7.10.5" }, "dependencies": { - "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "@babel/helper-annotate-as-pure": { + "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/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-imports": { + "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.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } } } }, "@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" + }, + "dependencies": { + "browserslist": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "caniuse-lite": { + "version": "1.0.30001100", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001100.tgz", + "integrity": "sha512-0eYdp1+wFCnMlCj2oudciuQn2B9xAFq3WpgpcBIZTxk/1HNA/O2YA7rpeYhnOqsqAJq1AHUgx6i1jtafg7m2zA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.498", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.498.tgz", + "integrity": "sha512-W1hGwaQEU8j9su2jeAr3aabkPuuXw+j8t73eajGAkEJWbfWiwbxBwQN/8Qmv2qCy3uCDm2rOAaZneYQM8VGC4w==", + "dev": true + }, + "node-releases": { + "version": "1.1.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.59.tgz", + "integrity": "sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw==", + "dev": true + } } }, "@babel/helper-create-class-features-plugin": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz", - "integrity": "sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg==", + "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.8.3", - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@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" + "@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" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5" + } + }, + "@babel/helper-optimise-call-expression": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-replace-supers": { + "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.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@babel/helper-create-regexp-features-plugin": { @@ -213,31 +614,241 @@ } }, "@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" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@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.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", + "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", "dev": true, "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@babel/helper-function-name": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.8.3", "@babel/template": "^7.8.3", @@ -248,18 +859,36 @@ "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==", - "dev": true, "requires": { "@babel/types": "^7.8.3" } }, "@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" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/helper-member-expression-to-functions": { @@ -320,8 +949,7 @@ "@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==", - "dev": true + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" }, "@babel/helper-regex": { "version": "7.8.3", @@ -371,7 +999,6 @@ "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==", - "dev": true, "requires": { "@babel/types": "^7.8.3" } @@ -395,61 +1022,124 @@ } }, "@babel/helpers": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", - "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", - "dev": true, + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", "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" }, "dependencies": { - "@babel/generator": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", - "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", - "dev": true, + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "requires": { - "@babel/types": "^7.9.0", + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "requires": { + "@babel/types": "^7.10.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" } }, + "@babel/helper-function-name": { + "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==", + "requires": { + "@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.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==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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==" + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "requires": { + "@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==", - "dev": true + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==" + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } }, "@babel/traverse": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", - "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.0", + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", - "dev": true, + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } } } }, @@ -457,7 +1147,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", @@ -468,7 +1157,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -480,8 +1168,7 @@ "@babel/parser": { "version": "7.8.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.8.tgz", - "integrity": "sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA==", - "dev": true + "integrity": "sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA==" }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.8.3", @@ -495,54 +1182,94 @@ } }, "@babel/plugin-proposal-class-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", - "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", + "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.8.3", - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-proposal-decorators": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz", - "integrity": "sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.5.tgz", + "integrity": "sha512-Sc5TAQSZuLzgY0664mMDn24Vw2P8g/VhyLyGPaWiHahhgLqeZvcGeyBZOrJW0oSKIK2mvQ22a1ENXBIQLhrEiQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-decorators": "^7.8.3" + "@babel/helper-create-class-features-plugin": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-decorators": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-proposal-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-WKK+9jz6TWUTX1uej9/EUVOmM1sK7aHv6bZyxbUV3NJjbiIZRqJITeXGMo7D631J72PEnIORh5VOlFCSlrLicg==", + "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.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-proposal-function-bind": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-function-bind/-/plugin-proposal-function-bind-7.8.3.tgz", - "integrity": "sha512-6q7VAHJQa9x4P6Lm6h6KHoJUEhx2r1buFKseHICe0ogb1LWxducO4tsQp3hd/7BVBo485YBsn6tJnpuwWm/9cA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-function-bind/-/plugin-proposal-function-bind-7.10.5.tgz", + "integrity": "sha512-1lYbE2ynV9yN0LCEYCdEBD5pR6GaNkRfjn1z1tWDdWMJgunTFcJBZDJUgiMPcTMqAc3D6Vrm8v2khxjjx6FrCg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-function-bind": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-function-bind": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-proposal-json-strings": { @@ -556,23 +1283,39 @@ } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-proposal-object-rest-spread": { @@ -596,13 +1339,39 @@ } }, "@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.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz", + "integrity": "sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-chaining": "^7.8.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-proposal-unicode-property-regex": { @@ -619,7 +1388,6 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -628,18 +1396,40 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, "requires": { "@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==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + } + } + }, "@babel/plugin-syntax-decorators": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.8.3.tgz", - "integrity": "sha512-8Hg4dNNT9/LcA1zQlfwuKR8BUc/if7Q7NkTam9sGTcJphLwpf2g4S42uhspQrIrR+dpzE0dtTqBVFoHl8GtnnQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz", + "integrity": "sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-syntax-dynamic-import": { @@ -661,55 +1451,104 @@ } }, "@babel/plugin-syntax-function-bind": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-function-bind/-/plugin-syntax-function-bind-7.8.3.tgz", - "integrity": "sha512-gEYag4Q3CfqlQcJQQw/KSWdV2husGOnIsOsRlyzkoaNqj2V/V/CSdSJDCGSl67oJ1bdIYP6TjORWPH561dSJpA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-function-bind/-/plugin-syntax-function-bind-7.10.4.tgz", + "integrity": "sha512-vF/K9yS0dpPNlT7mXSGhbdpb2f4DaLa/AYYbUqlxOggAug/oseIR1+LgAzwci4iJNlqWNmJ7aQ+llUMYjn9uhw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + } } }, "@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", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + } } }, "@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, "@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==", - "dev": true, + "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==", "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + } } }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -718,7 +1557,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -727,18 +1565,25 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-transform-arrow-functions": { @@ -751,105 +1596,556 @@ } }, "@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" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "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.10.4" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-module-imports": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-remap-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", + "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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.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.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz", + "integrity": "sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-transform-classes": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz", - "integrity": "sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==", + "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.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", - "@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", + "@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" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "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.10.4" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5" + } + }, + "@babel/helper-optimise-call-expression": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-replace-supers": { + "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.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-transform-destructuring": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", - "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", + "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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "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.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "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.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-plugin-utils": { + "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.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.19" + } + }, + "@babel/helper-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-transform-function-name": { @@ -863,32 +2159,251 @@ } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5" + } + }, + "@babel/helper-module-imports": { + "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.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", + "dev": true, + "requires": { + "@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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-replace-supers": { + "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.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.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-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "babel-plugin-dynamic-import-node": { + "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" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@babel/plugin-transform-modules-commonjs": { @@ -904,300 +2419,1596 @@ } }, "@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" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5" + } + }, + "@babel/helper-module-imports": { + "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.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", + "dev": true, + "requires": { + "@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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-replace-supers": { + "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.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.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-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "babel-plugin-dynamic-import-node": { + "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" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5" + } + }, + "@babel/helper-module-imports": { + "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.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", + "dev": true, + "requires": { + "@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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-replace-supers": { + "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.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.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-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "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.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "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.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-regex": { + "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.19" + } + }, + "@babel/helper-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/code-frame": { + "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.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5" + } + }, + "@babel/helper-optimise-call-expression": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-replace-supers": { + "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.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } } }, "@babel/plugin-transform-parameters": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz", - "integrity": "sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==", + "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" + }, + "dependencies": { + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-transform-react-constant-elements": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.9.0.tgz", - "integrity": "sha512-wXMXsToAUOxJuBBEHajqKLFWcCkOSLshTI2ChCFFj1zDd7od4IOxiwLCOObNUvOpkxLpjIuaIdBMmNt6ocCPAw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.4.tgz", + "integrity": "sha512-cYmQBW1pXrqBte1raMkAulXmi7rjg3VI6ZLg9QIic8Hq7BtYXaWuZSxsr2siOMI6SWwpxjWfnwhTUrd7JlAV7g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.4.tgz", + "integrity": "sha512-RM3ZAd1sU1iQ7rI2dhrZRZGv0aqzNQMbkIUCS1txYpi9wHQ2ZHNjo5TwX+UD6pvFW4AbWqLVYvKy5qJSAyRGjQ==", "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.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/plugin-transform-runtime": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz", - "integrity": "sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.5.tgz", + "integrity": "sha512-tV4V/FjElJ9lQtyjr5xD2IFFbgY46r7EeVu5a8CpEKT5laheHKSlFeHjpkPppW3PqzGLAuv5k2qZX5LgVZIX5w==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", "resolve": "^1.8.1", "semver": "^5.5.1" + }, + "dependencies": { + "@babel/helper-module-imports": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz", + "integrity": "sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.3" + "@babel/helper-plugin-utils": "^7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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.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.19" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/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" + }, + "dependencies": { + "@babel/helper-annotate-as-pure": { + "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.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "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.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-plugin-utils": { + "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.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.19" + } + }, + "@babel/helper-validator-identifier": { + "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/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } } }, "@babel/preset-env": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", - "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.4.tgz", + "integrity": "sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw==", "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.0", - "@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", + "@babel/compat-data": "^7.10.4", + "@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-json-strings": "^7.10.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.10.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@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-json-strings": "^7.8.0", "@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.0", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@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.8.7", - "@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.10.4", + "@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.0", - "browserslist": "^4.9.1", + "@babel/types": "^7.10.4", + "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", "levenary": "^1.1.1", "semver": "^5.5.0" }, "dependencies": { - "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "@babel/code-frame": { + "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/helper-validator-identifier": "^7.9.0", - "lodash": "^4.17.13", + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "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.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "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.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-function-name": { + "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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "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.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.5" + } + }, + "@babel/helper-module-imports": { + "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.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", + "dev": true, + "requires": { + "@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.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "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.10.4" + } + }, + "@babel/helper-plugin-utils": { + "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.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.19" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", + "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "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.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.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-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", + "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "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.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.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "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.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "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.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "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.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz", + "integrity": "sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.10.4" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "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.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "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.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "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.10.4" + } + }, + "@babel/plugin-transform-function-name": { + "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.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "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.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/template": { + "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.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } + }, + "babel-plugin-dynamic-import-node": { + "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" + } + }, + "browserslist": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz", + "integrity": "sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001093", + "electron-to-chromium": "^1.3.488", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "caniuse-lite": { + "version": "1.0.30001100", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001100.tgz", + "integrity": "sha512-0eYdp1+wFCnMlCj2oudciuQn2B9xAFq3WpgpcBIZTxk/1HNA/O2YA7rpeYhnOqsqAJq1AHUgx6i1jtafg7m2zA==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "electron-to-chromium": { + "version": "1.3.498", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.498.tgz", + "integrity": "sha512-W1hGwaQEU8j9su2jeAr3aabkPuuXw+j8t73eajGAkEJWbfWiwbxBwQN/8Qmv2qCy3uCDm2rOAaZneYQM8VGC4w==", + "dev": true + }, + "node-releases": { + "version": "1.1.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.59.tgz", + "integrity": "sha512-H3JrdUczbdiwxN5FuJPyCHnGHIFqQ0wWxo+9j1kAXAzqNMAHlo+4I/sYYxpyK0irQ73HgdiyzD32oqQDcU2Osw==", + "dev": true } } }, @@ -1215,44 +4026,53 @@ } }, "@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" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "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/register": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.9.0.tgz", - "integrity": "sha512-Tv8Zyi2J2VRR8g7pC5gTeIN8Ihultbmk0ocyNz8H2nEZbmhp1N6q0A1UGsQbDvGP/sNinQKUHf3SqXwqjtFv4Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.10.5.tgz", + "integrity": "sha512-eYHdLv43nyvmPn9bfNfrcC4+iYNwdQ8Pxk1MFJuU/U5LpSYl/PH4dFMazCYZDFVi8ueG3shvO+AQfLrxpYulQw==", "dev": true, "requires": { "find-cache-dir": "^2.0.0", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "make-dir": "^2.1.0", "pirates": "^4.0.0", "source-map-support": "^0.5.16" } }, "@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.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", + "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.8.7.tgz", - "integrity": "sha512-sc7A+H4I8kTd7S61dgB9RomXu/C+F4IrRr4Ytze4dnfx7AXEpCrejSNpjx7vq6y/Bak9S6Kbk65a/WgMLtg43Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.5.tgz", + "integrity": "sha512-RMafpmrNB5E/bwdSphLr8a8++9TosnyJp98RZzI6VOx2R2CCMpsXXXRvmI700O9oEKpXdZat6oEK68/F0zjd4A==", "dev": true, "requires": { "core-js-pure": "^3.0.0", @@ -1263,7 +4083,6 @@ "version": "7.8.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "dev": true, "requires": { "@babel/code-frame": "^7.8.3", "@babel/parser": "^7.8.6", @@ -1274,7 +4093,6 @@ "version": "7.8.6", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", - "dev": true, "requires": { "@babel/code-frame": "^7.8.3", "@babel/generator": "^7.8.6", @@ -1291,7 +4109,6 @@ "version": "7.8.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", - "dev": true, "requires": { "esutils": "^2.0.2", "lodash": "^4.17.13", @@ -1301,20 +4118,19 @@ "@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" }, "@blueprintjs/core": { - "version": "3.24.0", - "resolved": "https://registry.npmjs.org/@blueprintjs/core/-/core-3.24.0.tgz", - "integrity": "sha512-qW29DDPjzYsT27J6n97C0jZ1ifvEEziwNC98UhaKdSE7I8qxbLsb+ft2JOop+pEX4ab67T1lhQKAiQjWCPKZng==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@blueprintjs/core/-/core-3.30.0.tgz", + "integrity": "sha512-/fBhPmQ1AJ7ATGzA9YBlnxN8KV7PjNeW9KL+IOWiERUNcF9kuiuKJ88hTs7gSSHNtHmCF+jheD+X0MQq4WYVjA==", "requires": { - "@blueprintjs/icons": "^3.14.0", + "@blueprintjs/icons": "^3.19.0", "@types/dom4": "^2.0.1", "classnames": "^2.2", "dom4": "^2.1.5", "normalize.css": "^8.0.1", - "popper.js": "^1.15.0", + "popper.js": "^1.16.1", "react-lifecycles-compat": "^3.0.4", "react-popper": "^1.3.7", "react-transition-group": "^2.9.0", @@ -1323,20 +4139,20 @@ } }, "@blueprintjs/icons": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.14.0.tgz", - "integrity": "sha512-cvQ3CSdy0DqVqcXcPqSxoycJw497TVP5goyE6xCFlVs84477ahxh7Uung6J+CCoDVBuI87h576LtuyjwSxorvQ==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.19.0.tgz", + "integrity": "sha512-pFgEdSDj8q8ESofuXXSfonsiiqQNJ0u54vr8zxQyKggSwgMyASM/h9MdFVED7jXaj5nsda4or+hWM1ilUeES4Q==", "requires": { "classnames": "^2.2", "tslib": "~1.10.0" } }, "@blueprintjs/select": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/@blueprintjs/select/-/select-3.12.0.tgz", - "integrity": "sha512-rABlv5M+h7onuoUuNsratyiukPnkdblDm7lt7GT4fbRmJglSsKylNnfHogNDZkMMHqmgmVB05mgzBQ+kcLA1cw==", + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/@blueprintjs/select/-/select-3.13.5.tgz", + "integrity": "sha512-GFdLhmD23JvWueLNDBZjE5+Kii1zT/R7vx/Ta5fPv3gdSnUBMyGZMkQAjKWrn7o7xyOE1im5G2A0sdhcfcs3iw==", "requires": { - "@blueprintjs/core": "^3.24.0", + "@blueprintjs/core": "^3.30.0", "classnames": "^2.2", "tslib": "~1.10.0" } @@ -1345,7 +4161,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, "requires": { "exec-sh": "^0.3.2", "minimist": "^1.2.0" @@ -1391,13 +4206,13 @@ } }, "@istanbuljs/load-nyc-config": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", - "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "requires": { "camelcase": "^5.3.1", "find-up": "^4.1.0", + "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" }, @@ -1406,7 +4221,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -1416,7 +4230,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -1425,7 +4238,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -1433,479 +4245,239 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" } } }, "@istanbuljs/schema": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==" }, "@jest/console": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.2.6.tgz", - "integrity": "sha512-bGp+0PicZVCEhb+ifnW9wpKWONNdkhtJsRE7ap729hiAfTvCN6VhGx0s/l/V/skA2pnyqq+N/7xl9ZWfykDpsg==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.1.0.tgz", + "integrity": "sha512-+0lpTHMd/8pJp+Nd4lyip+/Iyf2dZJvcCqrlkeZQoQid+JlThA4M9vxHtheyrQ99jJTMQam+es4BcvZ5W5cC3A==", "requires": { - "@jest/source-map": "^25.2.6", - "chalk": "^3.0.0", - "jest-util": "^25.2.6", + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", + "jest-message-util": "^26.1.0", + "jest-util": "^26.1.0", "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "@jest/core": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.2.7.tgz", - "integrity": "sha512-Nd6ELJyR+j0zlwhzkfzY70m04hAur0VnMwJXVe4VmmD/SaQ6DEyal++ERQ1sgyKIKKEqRuui6k/R0wHLez4P+g==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.1.0.tgz", + "integrity": "sha512-zyizYmDJOOVke4OO/De//aiv8b07OwZzL2cfsvWF3q9YssfpcKfcnZAwDY8f+A76xXSMMYe8i/f/LPocLlByfw==", "requires": { - "@jest/console": "^25.2.6", - "@jest/reporters": "^25.2.6", - "@jest/test-result": "^25.2.6", - "@jest/transform": "^25.2.6", - "@jest/types": "^25.2.6", + "@jest/console": "^26.1.0", + "@jest/reporters": "^26.1.0", + "@jest/test-result": "^26.1.0", + "@jest/transform": "^26.1.0", + "@jest/types": "^26.1.0", "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", + "chalk": "^4.0.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.3", - "jest-changed-files": "^25.2.6", - "jest-config": "^25.2.7", - "jest-haste-map": "^25.2.6", - "jest-message-util": "^25.2.6", - "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.2.6", - "jest-resolve-dependencies": "^25.2.7", - "jest-runner": "^25.2.7", - "jest-runtime": "^25.2.7", - "jest-snapshot": "^25.2.7", - "jest-util": "^25.2.6", - "jest-validate": "^25.2.6", - "jest-watcher": "^25.2.7", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^26.1.0", + "jest-config": "^26.1.0", + "jest-haste-map": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.1.0", + "jest-resolve-dependencies": "^26.1.0", + "jest-runner": "^26.1.0", + "jest-runtime": "^26.1.0", + "jest-snapshot": "^26.1.0", + "jest-util": "^26.1.0", + "jest-validate": "^26.1.0", + "jest-watcher": "^26.1.0", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", - "realpath-native": "^2.0.0", "rimraf": "^3.0.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" } } }, "@jest/environment": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.2.6.tgz", - "integrity": "sha512-17WIw+wCb9drRNFw1hi8CHah38dXVdOk7ga9exThhGtXlZ9mK8xH4DjSB9uGDGXIWYSHmrxoyS6KJ7ywGr7bzg==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.1.0.tgz", + "integrity": "sha512-86+DNcGongbX7ai/KE/S3/NcUVZfrwvFzOOWX/W+OOTvTds7j07LtC+MgGydH5c8Ri3uIrvdmVgd1xFD5zt/xA==", "requires": { - "@jest/fake-timers": "^25.2.6", - "@jest/types": "^25.2.6", - "jest-mock": "^25.2.6" + "@jest/fake-timers": "^26.1.0", + "@jest/types": "^26.1.0", + "jest-mock": "^26.1.0" } }, "@jest/fake-timers": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.2.6.tgz", - "integrity": "sha512-A6qtDIA2zg/hVgUJJYzQSHFBIp25vHdSxW/s4XmTJAYxER6eL0NQdQhe4+232uUSviKitubHGXXirt5M7blPiA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.1.0.tgz", + "integrity": "sha512-Y5F3kBVWxhau3TJ825iuWy++BAuQzK/xEa+wD9vDH3RytW9f2DbMVodfUQC54rZDX3POqdxCgcKdgcOL0rYUpA==", "requires": { - "@jest/types": "^25.2.6", - "jest-message-util": "^25.2.6", - "jest-mock": "^25.2.6", - "jest-util": "^25.2.6", - "lolex": "^5.0.0" + "@jest/types": "^26.1.0", + "@sinonjs/fake-timers": "^6.0.1", + "jest-message-util": "^26.1.0", + "jest-mock": "^26.1.0", + "jest-util": "^26.1.0" + } + }, + "@jest/globals": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.1.0.tgz", + "integrity": "sha512-MKiHPNaT+ZoG85oMaYUmGHEqu98y3WO2yeIDJrs2sJqHhYOy3Z6F7F/luzFomRQ8SQ1wEkmahFAz2291Iv8EAw==", + "requires": { + "@jest/environment": "^26.1.0", + "@jest/types": "^26.1.0", + "expect": "^26.1.0" } }, "@jest/reporters": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.2.6.tgz", - "integrity": "sha512-DRMyjaxcd6ZKctiXNcuVObnPwB1eUs7xrUVu0J2V0p5/aZJei5UM9GL3s/bmN4hRV8Mt3zXh+/9X2o0Q4ClZIA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.1.0.tgz", + "integrity": "sha512-SVAysur9FOIojJbF4wLP0TybmqwDkdnFxHSPzHMMIYyBtldCW9gG+Q5xWjpMFyErDiwlRuPyMSJSU64A67Pazg==", "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^25.2.6", - "@jest/test-result": "^25.2.6", - "@jest/transform": "^25.2.6", - "@jest/types": "^25.2.6", - "chalk": "^3.0.0", + "@jest/console": "^26.1.0", + "@jest/test-result": "^26.1.0", + "@jest/transform": "^26.1.0", + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.2", + "graceful-fs": "^4.2.4", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-instrument": "^4.0.3", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", - "jest-haste-map": "^25.2.6", - "jest-resolve": "^25.2.6", - "jest-util": "^25.2.6", - "jest-worker": "^25.2.6", - "node-notifier": "^6.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^26.1.0", + "jest-resolve": "^26.1.0", + "jest-util": "^26.1.0", + "jest-worker": "^26.1.0", + "node-notifier": "^7.0.0", "slash": "^3.0.0", "source-map": "^0.6.0", - "string-length": "^3.1.0", + "string-length": "^4.0.1", "terminal-link": "^2.0.0", - "v8-to-istanbul": "^4.0.1" + "v8-to-istanbul": "^4.1.3" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "@jest/source-map": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.2.6.tgz", - "integrity": "sha512-VuIRZF8M2zxYFGTEhkNSvQkUKafQro4y+mwUxy5ewRqs5N/ynSFUODYp3fy1zCnbCMy1pz3k+u57uCqx8QRSQQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.1.0.tgz", + "integrity": "sha512-XYRPYx4eEVX15cMT9mstnO7hkHP3krNtKfxUYd8L7gbtia8JvZZ6bMzSwa6IQJENbudTwKMw5R1BePRD+bkEmA==", "requires": { "callsites": "^3.0.0", - "graceful-fs": "^4.2.3", + "graceful-fs": "^4.2.4", "source-map": "^0.6.0" }, "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "@jest/test-result": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.2.6.tgz", - "integrity": "sha512-gmGgcF4qz/pkBzyfJuVHo2DA24kIgVQ5Pf/VpW4QbyMLSegi8z+9foSZABfIt5se6k0fFj/3p/vrQXdaOgit0w==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.1.0.tgz", + "integrity": "sha512-Xz44mhXph93EYMA8aYDz+75mFbarTV/d/x0yMdI3tfSRs/vh4CqSxgzVmCps1fPkHDCtn0tU8IH9iCKgGeGpfw==", "requires": { - "@jest/console": "^25.2.6", - "@jest/types": "^25.2.6", + "@jest/console": "^26.1.0", + "@jest/types": "^26.1.0", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.2.7.tgz", - "integrity": "sha512-s2uYGOXONDSTJQcZJ9A3Zkg3hwe53RlX1HjUNqjUy3HIqwgwCKJbnAKYsORPbhxXi3ARMKA7JNBi9arsTxXoYw==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.1.0.tgz", + "integrity": "sha512-Z/hcK+rTq56E6sBwMoQhSRDVjqrGtj1y14e2bIgcowARaIE1SgOanwx6gvY4Q9gTKMoZQXbXvptji+q5GYxa6Q==", "requires": { - "@jest/test-result": "^25.2.6", - "jest-haste-map": "^25.2.6", - "jest-runner": "^25.2.7", - "jest-runtime": "^25.2.7" + "@jest/test-result": "^26.1.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.1.0", + "jest-runner": "^26.1.0", + "jest-runtime": "^26.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + } } }, "@jest/transform": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.2.6.tgz", - "integrity": "sha512-rZnjCjZf9avPOf9q/w9RUZ9Uc29JmB53uIXNJmNz04QbDMD5cR/VjfikiMKajBsXe2vnFl5sJ4RTt+9HPicauQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.1.0.tgz", + "integrity": "sha512-ICPm6sUXmZJieq45ix28k0s+d/z2E8CHDsq+WwtWI6kW8m7I8kPqarSEcUN86entHQ570ZBRci5OWaKL0wlAWw==", "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^25.2.6", + "@jest/types": "^26.1.0", "babel-plugin-istanbul": "^6.0.0", - "chalk": "^3.0.0", + "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.3", - "jest-haste-map": "^25.2.6", - "jest-regex-util": "^25.2.6", - "jest-util": "^25.2.6", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.1.0", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.1.0", "micromatch": "^4.0.2", "pirates": "^4.0.1", - "realpath-native": "^2.0.0", "slash": "^3.0.0", "source-map": "^0.6.1", "write-file-atomic": "^3.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "@jest/types": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.2.6.tgz", - "integrity": "sha512-myJTTV37bxK7+3NgKc4Y/DlQ5q92/NOwZsZ+Uch7OXdElxOg61QYc72fPYNAjlvbnJ2YvbXLamIsa9tj48BmyQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.1.0.tgz", + "integrity": "sha512-GXigDDsp6ZlNMhXQDeuy/iYCDsRIHJabWtDzvnn36+aqFfG14JmFV0e/iXxY4SP9vbXSiPNOWdehU5MeqrYHBQ==", "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "chalk": "^4.0.0" } }, "@jimp/bmp": { @@ -2547,19 +5119,27 @@ } } }, - "@samverschueren/stream-to-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", - "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", "dev": true, "requires": { - "any-observable": "^0.3.0" + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } } }, "@sentry/cli": { - "version": "1.52.3", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.52.3.tgz", - "integrity": "sha512-QOSIg5hxAEa6v6H7oEeF6A/Rpa0wloMhbu0Qed6zHv3lyoqf0Z34Kq2jCXdqGsOE3IzkO+3CNy81F6361j5TKg==", + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.55.0.tgz", + "integrity": "sha512-LOpM69Kvfaossq92wgP8cg3+0XBc9lg76udH4OdmpSZhtavS/qxWedBsnTicaT//7rQUNZBUnTPOpmCYnpGEIA==", "dev": true, "requires": { "https-proxy-agent": "^5.0.0", @@ -2591,27 +5171,34 @@ } }, "@sentry/webpack-plugin": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.11.1.tgz", - "integrity": "sha512-7uQiqHXK84GYy2mL/c+cW/QBtr/D8fNmf8U5nUDN4+44aNrQxSJ4vJmYJbxYg1zO6qN/xZyqJt3uKBlgA3FkZg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.12.0.tgz", + "integrity": "sha512-I3W+G2XAPi53sE4eXgxEeGJLc8kYF6+3c1YF79AS8qKc6K4xmbXUVBCZrfrKBQPAZx5mpD1vHIk4+Og/CKejZA==", "dev": true, "requires": { - "@sentry/cli": "^1.52.3" + "@sentry/cli": "^1.55.0" } }, "@sinonjs/commons": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", - "integrity": "sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==", - "dev": true, + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", + "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", "requires": { "type-detect": "4.0.8" } }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, "@types/anymatch": { @@ -2621,10 +5208,9 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", - "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", - "dev": true, + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", + "integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==", "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0", @@ -2637,7 +5223,6 @@ "version": "7.6.1", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", - "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -2646,17 +5231,15 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", - "dev": true, "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "@types/babel__traverse": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.9.tgz", - "integrity": "sha512-jEFQ8L1tuvPjOI8lnpaf73oCJe+aoxL6ygqSy6c8LcW98zaC+4mzWuQIRCEvKeCOu+lbqdXcg4Uqmm1S8AP1tw==", - "dev": true, + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", + "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", "requires": { "@babel/types": "^7.3.0" } @@ -2664,8 +5247,7 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, "@types/dom4": { "version": "2.0.1", @@ -2698,6 +5280,14 @@ "@types/node": "*" } }, + "@types/graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", + "requires": { + "@types/node": "*" + } + }, "@types/html-minifier-terser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz", @@ -2705,25 +5295,22 @@ "dev": true }, "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" }, "@types/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" } }, "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "dev": true, + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", "requires": { "@types/istanbul-lib-coverage": "*", "@types/istanbul-lib-report": "*" @@ -2735,10 +5322,10 @@ "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", "dev": true }, - "@types/mime-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz", - "integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=", + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/minimatch": { @@ -2750,8 +5337,12 @@ "@types/node": { "version": "13.11.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.0.tgz", - "integrity": "sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==", - "dev": true + "integrity": "sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==" + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" }, "@types/parse-json": { "version": "4.0.0", @@ -2760,10 +5351,9 @@ "dev": true }, "@types/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-IkVfat549ggtkZUthUzEX49562eGikhSYeVGX97SkMFn+sTZrgRewXjQ4tPKFPCykZHkX1Zfd9OoELGqKU2jJA==" }, "@types/q": { "version": "1.5.2", @@ -2780,8 +5370,7 @@ "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", - "dev": true + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, "@types/tapable": { "version": "1.0.5", @@ -2848,10 +5437,9 @@ } }, "@types/yargs": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", - "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", - "dev": true, + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", "requires": { "@types/yargs-parser": "*" } @@ -2859,24 +5447,34 @@ "@types/yargs-parser": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } }, "@typescript-eslint/experimental-utils": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.24.0.tgz", - "integrity": "sha512-DXrwuXTdVh3ycNCMYmWhUzn/gfqu9N0VzNnahjiDJvcyhfBy4gb59ncVZVxdp5XzBC77dCncu0daQgOkbvPwBw==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.24.0", - "eslint-scope": "^5.0.0" + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" } }, "@typescript-eslint/typescript-estree": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.24.0.tgz", - "integrity": "sha512-RJ0yMe5owMSix55qX7Mi9V6z2FDuuDpN6eR5fzRJrp+8in9UF41IGNQHbg5aMK4/PjVaEQksLvz0IA8n+Mr/FA==", + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", "dev": true, "requires": { "debug": "^4.1.1", @@ -2884,14 +5482,14 @@ "glob": "^7.1.6", "is-glob": "^4.0.1", "lodash": "^4.17.15", - "semver": "^6.3.0", + "semver": "^7.3.2", "tsutils": "^3.17.1" }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } @@ -3086,8 +5684,7 @@ "abab": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", - "dev": true + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" }, "accepts": { "version": "1.3.7", @@ -3100,27 +5697,17 @@ } }, "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", + "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==" }, "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - } + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" } }, "acorn-jsx": { @@ -3130,10 +5717,9 @@ "dev": true }, "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" }, "agent-base": { "version": "6.0.0", @@ -3166,7 +5752,6 @@ "version": "6.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3244,7 +5829,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, "requires": { "type-fest": "^0.11.0" }, @@ -3252,22 +5836,19 @@ "type-fest": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" } } }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -3278,17 +5859,10 @@ "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", "dev": true }, - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true - }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -3314,7 +5888,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -3326,38 +5899,29 @@ "dev": true }, "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "dev": true, "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" } }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-find-index": { "version": "1.0.2", @@ -3400,8 +5964,7 @@ "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "array.prototype.flat": { "version": "1.2.3", @@ -3413,6 +5976,17 @@ "es-abstract": "^1.17.0-next.1" } }, + "array.prototype.flatmap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", + "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + } + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -3423,7 +5997,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -3437,6 +6010,14 @@ "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^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 + } } }, "assert": { @@ -3469,14 +6050,12 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "ast-types-flow": { "version": "0.0.7", @@ -3494,25 +6073,18 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true + "dev": true, + "optional": true }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "author-regex": { "version": "1.0.0", @@ -3523,19 +6095,23 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "axe-core": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz", + "integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==", "dev": true }, "axobject-query": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", - "integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", "dev": true }, "babel-eslint": { @@ -3553,69 +6129,24 @@ } }, "babel-jest": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.2.6.tgz", - "integrity": "sha512-MDJOAlwtIeIQiGshyX0d2PxTbV73xZMpNji40ivVTPQOm59OdRR9nYCkffqI7ugtsK4JR98HgNKbDbuVf4k5QQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.1.0.tgz", + "integrity": "sha512-Nkqgtfe7j6PxLO6TnCQQlkMm8wdTdnIF8xrdpooHCuD5hXRzVEPbPneTJKknH5Dsv3L8ip9unHDAp48YQ54Dkg==", "requires": { - "@jest/transform": "^25.2.6", - "@jest/types": "^25.2.6", - "@types/babel__core": "^7.1.0", + "@jest/transform": "^26.1.0", + "@jest/types": "^26.1.0", + "@types/babel__core": "^7.1.7", "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^25.2.6", - "chalk": "^3.0.0", + "babel-preset-jest": "^26.1.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", "slash": "^3.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" } } }, @@ -3657,7 +6188,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -3667,23 +6197,41 @@ } }, "babel-plugin-jest-hoist": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.2.6.tgz", - "integrity": "sha512-qE2xjMathybYxjiGFJg0mLFrz0qNp83aNZycWDY/SuHiZNq+vQfRQtuINqyXyue1ELd8Rd+1OhFSLjms8msMbw==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.1.0.tgz", + "integrity": "sha512-qhqLVkkSlqmC83bdMhM8WW4Z9tB+JkjqAqlbbohS9sJLT5Ha2vfzuKqg5yenXrAjOPG2YC0WiXdH3a9PvB+YYw==", "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", "@types/babel__traverse": "^7.0.6" } }, - "babel-preset-jest": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.2.6.tgz", - "integrity": "sha512-Xh2eEAwaLY9+SyMt/xmGZDnXTW/7pSaBPG0EMo7EuhvosFKVWYB6CqwYD31DaEQuoTL090oDZ0FEqygffGRaSQ==", - "dev": true, + "babel-preset-current-node-syntax": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz", + "integrity": "sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ==", "requires": { - "@babel/plugin-syntax-bigint": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^25.2.6" + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.1.0.tgz", + "integrity": "sha512-na9qCqFksknlEj5iSdw1ehMVR06LCCTkZLGKeEtxDDdhg8xpUF09m29Kvh1pRbZ07h7AQ5ttLYUwpXL4tO6w7w==", + "requires": { + "babel-plugin-jest-hoist": "^26.1.0", + "babel-preset-current-node-syntax": "^0.1.2" } }, "babel-preset-modern-browsers": { @@ -3711,14 +6259,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -3733,7 +6279,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -3742,7 +6287,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -3751,7 +6295,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -3760,7 +6303,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -3779,7 +6321,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -3797,10 +6338,11 @@ "dev": true }, "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true, + "optional": true }, "bindings": { "version": "1.5.0", @@ -3859,9 +6401,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.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz", + "integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==", "dev": true }, "body-parser": { @@ -3974,7 +6516,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3984,7 +6525,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -3998,25 +6538,7 @@ "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "browserify-aes": { "version": "1.2.0", @@ -4063,21 +6585,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=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz", + "integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==", "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" + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.2", + "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": { @@ -4105,20 +6656,18 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, "requires": { "node-int64": "^0.4.0" } }, "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", "dev": true, "requires": { "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "ieee754": "^1.1.4" } }, "buffer-alloc": { @@ -4137,6 +6686,12 @@ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", "dev": true }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", @@ -4152,8 +6707,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-json": { "version": "2.0.0", @@ -4217,7 +6771,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -4354,8 +6907,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camelcase": { "version": "5.3.1", @@ -4402,7 +6954,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, "requires": { "rsvp": "^4.8.4" } @@ -4416,14 +6967,12 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4433,7 +6982,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -4443,7 +6991,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -4451,31 +6998,27 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, "cheerio": { "version": "1.0.0-rc.3", @@ -4519,698 +7062,30 @@ } }, "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", + "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", "dev": true, + "optional": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" }, "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "optional": true, "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "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", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "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", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "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", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-glob": "^4.0.1" } } } @@ -5233,8 +7108,7 @@ "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "cipher-base": { "version": "1.0.4", @@ -5250,7 +7124,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -5262,7 +7135,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -5440,6 +7312,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -5450,33 +7334,48 @@ "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } } } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" }, "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "requires": { - "ansi-regex": "^5.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } } } @@ -5537,8 +7436,7 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "coa": { "version": "2.0.2", @@ -5571,9 +7469,9 @@ "dev": true }, "codecov": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.6.5.tgz", - "integrity": "sha512-v48WuDMUug6JXwmmfsMzhCHRnhUf8O3duqXvltaYJKrO1OekZWpB/eH6iIoaxMl8Qli0+u3OxptdsBOYiD7VAQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.7.0.tgz", + "integrity": "sha512-uIixKofG099NbUDyzRk1HdGtaG8O+PBUAg3wfmjwXw2+ek+PZp+puRvbTohqrVfuudaezivJHFgTtSC3M8MXww==", "dev": true, "requires": { "argv": "0.0.2", @@ -5586,14 +7484,12 @@ "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==" }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -5613,7 +7509,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -5621,8 +7516,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.5.3", @@ -5644,7 +7538,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -5669,14 +7562,12 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "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 + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -5787,7 +7678,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -5832,28 +7722,7 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", - "dev": true, - "requires": { - "cacache": "^12.0.3", - "find-cache-dir": "^2.1.0", - "glob-parent": "^3.1.0", - "globby": "^7.1.1", - "is-glob": "^4.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", - "normalize-path": "^3.0.0", - "p-limit": "^2.2.1", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "webpack-log": "^2.0.0" - } + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "core-js": { "version": "2.6.11", @@ -5862,12 +7731,12 @@ "dev": true }, "core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", "dev": true, "requires": { - "browserslist": "^4.8.3", + "browserslist": "^4.8.5", "semver": "7.0.0" }, "dependencies": { @@ -5880,16 +7749,15 @@ } }, "core-js-pure": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", - "integrity": "sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.1", @@ -5939,6 +7807,14 @@ "requires": { "bn.js": "^4.1.0", "elliptic": "^6.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 + } } }, "create-error-class": { @@ -5986,11 +7862,19 @@ "warning": "^4.0.3" } }, + "cross-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz", + "integrity": "sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew==", + "dev": true, + "requires": { + "node-fetch": "2.6.0" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -6041,9 +7925,9 @@ } }, "css-loader": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.4.2.tgz", - "integrity": "sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", "dev": true, "requires": { "camelcase": "^5.3.1", @@ -6051,24 +7935,98 @@ "icss-utils": "^4.1.1", "loader-utils": "^1.2.3", "normalize-path": "^3.0.0", - "postcss": "^7.0.23", + "postcss": "^7.0.32", "postcss-modules-extract-imports": "^2.0.0", "postcss-modules-local-by-default": "^3.0.2", - "postcss-modules-scope": "^2.1.1", + "postcss-modules-scope": "^2.2.0", "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.0.2", - "schema-utils": "^2.6.0" + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" }, "dependencies": { - "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { - "ajv": "^6.12.0", + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", "ajv-keywords": "^3.4.1" } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -6233,14 +8191,12 @@ "cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" }, "cssstyle": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.2.0.tgz", - "integrity": "sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==", - "dev": true, + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", "requires": { "cssom": "~0.3.6" }, @@ -6248,8 +8204,7 @@ "cssom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" } } }, @@ -6552,27 +8507,24 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" } }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, "requires": { "ms": "^2.1.1" } @@ -6580,14 +8532,17 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decimal.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", + "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==" }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress-response": { "version": "4.2.1", @@ -6626,31 +8581,12 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - } - } + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, "define-properties": { "version": "1.1.3", @@ -6664,7 +8600,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -6674,7 +8609,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -6683,7 +8617,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -6692,7 +8625,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -6756,8 +8688,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", @@ -6802,14 +8733,12 @@ "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==" }, "diff-sequences": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", - "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", - "dev": true + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz", + "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==" }, "diffie-hellman": { "version": "5.0.3", @@ -6820,15 +8749,14 @@ "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" - } - }, - "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, - "requires": { - "path-type": "^3.0.0" + }, + "dependencies": { + "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": { @@ -6908,12 +8836,18 @@ "dev": true }, "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", "requires": { - "webidl-conversions": "^4.0.2" + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + } } }, "domhandler": { @@ -6939,7 +8873,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", - "dev": true, "requires": { "no-case": "^3.0.3", "tslib": "^1.10.0" @@ -6949,7 +8882,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", - "dev": true, "requires": { "tslib": "^1.10.0" } @@ -6958,7 +8890,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", - "dev": true, "requires": { "lower-case": "^2.0.1", "tslib": "^1.10.0" @@ -6997,7 +8928,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -7015,12 +8945,6 @@ "integrity": "sha512-zcUd1p/7yzTSdWkCTrqGvbnEOASy96d0RJL/lc5BDJoO23Z3G/VHd0yIPbguDU9n8QNUTCigLO7oEdtOb7fp2A==", "dev": true }, - "elegant-spinner": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-2.0.0.tgz", - "integrity": "sha512-5YRYHhvhYzV/FC4AiMdeSIg3jAYGq9xFvbhZMpPlJoBsfYgrw2DSCYeXfat6tYBu45PWiyRr3+flaCPPmviPaA==", - "dev": true - }, "elliptic": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", @@ -7034,12 +8958,20 @@ "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": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "emojis-list": { @@ -7058,7 +8990,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -7087,12 +9018,20 @@ } }, "enquirer": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", - "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, "requires": { - "ansi-colors": "^3.2.1" + "ansi-colors": "^4.1.1" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + } } }, "entities": { @@ -7114,7 +9053,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -7153,6 +9091,12 @@ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", "dev": true }, + "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-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -7162,14 +9106,12 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "dev": true, + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "requires": { "esprima": "^4.0.1", "estraverse": "^4.2.0", @@ -7178,32 +9120,67 @@ "source-map": "~0.6.1" }, "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } } } }, "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.4.0.tgz", + "integrity": "sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.0", + "eslint-utils": "^2.0.0", + "eslint-visitor-keys": "^1.2.0", + "espree": "^7.1.0", + "esquery": "^1.2.0", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", @@ -7212,41 +9189,45 @@ "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", + "levn": "^0.4.1", "lodash": "^4.17.14", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.3", + "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -7267,49 +9248,79 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, "eslint-config-airbnb": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.1.0.tgz", - "integrity": "sha512-kZFuQC/MPnH7KJp6v95xsLBf63G/w7YqdPfQ0MUanxQ7zcKUNG8j+sSY860g3NwCBOa62apw16J6pRN+AOgXzw==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz", + "integrity": "sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg==", "dev": true, "requires": { - "eslint-config-airbnb-base": "^14.1.0", + "eslint-config-airbnb-base": "^14.2.0", "object.assign": "^4.1.0", - "object.entries": "^1.1.1" + "object.entries": "^1.1.2" } }, "eslint-config-airbnb-base": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.1.0.tgz", - "integrity": "sha512-+XCcfGyCnbzOnktDVhwsCAx+9DmrzEmuwxyHUJpw+kqBVT744OUBrB09khgFKlK1lshVww6qXGsYPZpavoNjJw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz", + "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==", "dev": true, "requires": { "confusing-browser-globals": "^1.0.9", "object.assign": "^4.1.0", - "object.entries": "^1.1.1" + "object.entries": "^1.1.2" } }, "eslint-config-prettier": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz", - "integrity": "sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz", + "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==", "dev": true, "requires": { "get-stdin": "^6.0.0" } }, "eslint-import-resolver-node": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "requires": { "debug": "^2.6.9", @@ -7437,6 +9448,24 @@ } } }, + "eslint-plugin-eslint-comments": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", + "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, "eslint-plugin-filenames": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/eslint-plugin-filenames/-/eslint-plugin-filenames-1.3.2.tgz", @@ -7450,23 +9479,24 @@ } }, "eslint-plugin-import": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz", - "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", + "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", - "object.values": "^1.1.0", + "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { "debug": { @@ -7493,70 +9523,80 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, "eslint-plugin-jest": { - "version": "23.8.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.8.2.tgz", - "integrity": "sha512-xwbnvOsotSV27MtAe7s8uGWOori0nUsrXh2f1EnpmXua8sDfY6VZhHAhHg2sqK7HBNycRQExF074XSZ7DvfoFg==", + "version": "23.18.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.18.0.tgz", + "integrity": "sha512-wLPM/Rm1SGhxrFQ2TKM/BYsYPhn7ch6ZEK92S2o/vGkAAnDXM0I4nTIo745RIX+VlCRMFgBuJEax6XfTHMdeKg==", "dev": true, "requires": { "@typescript-eslint/experimental-utils": "^2.5.0" } }, "eslint-plugin-jsx-a11y": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", - "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz", + "integrity": "sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g==", "dev": true, "requires": { - "@babel/runtime": "^7.4.5", - "aria-query": "^3.0.0", - "array-includes": "^3.0.3", + "@babel/runtime": "^7.10.2", + "aria-query": "^4.2.2", + "array-includes": "^3.1.1", "ast-types-flow": "^0.0.7", - "axobject-query": "^2.0.2", - "damerau-levenshtein": "^1.0.4", - "emoji-regex": "^7.0.2", + "axe-core": "^3.5.4", + "axobject-query": "^2.1.2", + "damerau-levenshtein": "^1.0.6", + "emoji-regex": "^9.0.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.1" + "jsx-ast-utils": "^2.4.1", + "language-tags": "^1.0.5" }, "dependencies": { "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.0.0.tgz", + "integrity": "sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==", "dev": true } } }, "eslint-plugin-prettier": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz", - "integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", + "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" } }, "eslint-plugin-react": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", - "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.3.tgz", + "integrity": "sha512-txbo090buDeyV0ugF3YMWrzLIUqpYTsWSDZV9xLSmExE1P/Kmgg9++PD931r+KEWS66O1c9R4srLVVHmeHpoAg==", "dev": true, "requires": { "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.3", - "object.entries": "^1.1.1", + "jsx-ast-utils": "^2.4.1", + "object.entries": "^1.1.2", "object.fromentries": "^2.0.2", "object.values": "^1.1.1", "prop-types": "^15.7.2", - "resolve": "^1.15.1", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.2", - "xregexp": "^4.3.0" + "resolve": "^1.17.0", + "string.prototype.matchall": "^4.0.2" }, "dependencies": { "doctrine": { @@ -7568,24 +9608,27 @@ "esutils": "^2.0.2" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, "eslint-plugin-react-hooks": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz", - "integrity": "sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.8.tgz", + "integrity": "sha512-6SSb5AiMCPd8FDJrzah+Z4F44P2CdOaK026cXFV+o/xSRzfOiV1FNFeLl2z6xm3yqWOQEZ5OfVgiec90qV2xrQ==", "dev": true }, "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", + "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -7593,9 +9636,9 @@ } }, "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" @@ -7608,29 +9651,44 @@ "dev": true }, "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz", + "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==", "dev": true, "requires": { - "acorn": "^7.1.1", + "acorn": "^7.2.0", "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^1.2.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", - "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } } }, "esrecurse": { @@ -7645,14 +9703,12 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "etag": { "version": "1.8.1", @@ -7679,14 +9735,12 @@ "exec-sh": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", - "dev": true + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==" }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -7706,14 +9760,12 @@ "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -7724,11 +9776,20 @@ "to-regex": "^3.0.1" }, "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -7737,7 +9798,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -7746,7 +9806,6 @@ "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" } @@ -7754,8 +9813,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -7775,24 +9833,22 @@ } }, "expect": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/expect/-/expect-25.2.7.tgz", - "integrity": "sha512-yA+U2Ph0MkMsJ9N8q5hs9WgWI6oJYfecdXta6LkP/alY/jZZL1MHlJ2wbLh60Ucqf3G+51ytbqV3mlGfmxkpNw==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.1.0.tgz", + "integrity": "sha512-QbH4LZXDsno9AACrN9eM0zfnby9G+OsdNgZUohjg/P0mLy1O+/bzTAJGT6VSIjVCe8yKM6SzEl/ckEOFBT7Vnw==", "requires": { - "@jest/types": "^25.2.6", + "@jest/types": "^26.1.0", "ansi-styles": "^4.0.0", - "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.2.7", - "jest-message-util": "^25.2.6", - "jest-regex-util": "^25.2.6" + "jest-get-type": "^26.0.0", + "jest-matcher-utils": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-regex-util": "^26.0.0" }, "dependencies": { "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -7802,7 +9858,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -7810,8 +9865,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, @@ -7879,14 +9933,12 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -7896,29 +9948,16 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } } } }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, "requires": { "array-unique": "^0.3.2", "define-property": "^1.0.0", @@ -7934,7 +9973,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -7943,7 +9981,6 @@ "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" } @@ -7952,7 +9989,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -7961,7 +9997,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -7970,70 +10005,51 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - } - } - }, - "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "extract-zip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.0.tgz", + "integrity": "sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } } } }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "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==", - "dev": true + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" }, "fast-diff": { "version": "1.2.0", @@ -8044,14 +10060,12 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "favicons": { "version": "5.5.0", @@ -8215,15 +10229,14 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, "requires": { "bser": "2.1.1" } }, "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, "requires": { "pend": "~1.2.0" @@ -8263,6 +10276,11 @@ "schema-utils": "^2.6.5" }, "dependencies": { + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -8303,7 +10321,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -8621,11 +10638,19 @@ "integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==" }, "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "flip-toolkit": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/flip-toolkit/-/flip-toolkit-7.0.11.tgz", + "integrity": "sha512-T3iMFg9Rk2WdR+G4mbIljQWs+oRYTQZjox35Wr1JRIVDGFy48Ad7CYv/FKJb6RTVtAB4bSFLi2vDpxSgKWdPYQ==", + "requires": { + "rematrix": "0.2.2" + } + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -8639,8 +10664,7 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, "for-own": { "version": "0.1.5", @@ -8654,14 +10678,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -8678,7 +10700,6 @@ "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" } @@ -8746,14 +10767,12 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", - "dev": true, + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "optional": true }, "function-bind": { @@ -8828,14 +10847,12 @@ "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", - "dev": true + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-own-enumerable-property-symbols": { "version": "3.0.2", @@ -8843,6 +10860,11 @@ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", "dev": true }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" + }, "get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -8853,7 +10875,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -8861,14 +10882,12 @@ "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -8898,7 +10917,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8913,6 +10931,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, + "optional": true, "requires": { "is-glob": "^3.1.0", "path-dirname": "^1.0.0" @@ -8923,9 +10942,15 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, + "optional": true, "requires": { "is-extglob": "^2.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -8997,36 +11022,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - } - } + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "got": { "version": "6.7.1", @@ -9065,7 +11061,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, "optional": true }, "gud": { @@ -9076,14 +11071,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -9100,8 +11093,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.1", @@ -9118,7 +11110,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -9129,7 +11120,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -9139,7 +11129,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -9148,7 +11137,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -9159,7 +11147,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -9167,13 +11154,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": { @@ -9229,8 +11236,7 @@ "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, "hsl-regex": { "version": "1.0.0", @@ -9251,19 +11257,17 @@ "dev": true }, "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", "requires": { - "whatwg-encoding": "^1.0.1" + "whatwg-encoding": "^1.0.5" } }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" }, "html-minifier-terser": { "version": "5.0.5", @@ -9308,29 +11312,10 @@ } } }, - "html-webpack-inline-source-plugin": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/html-webpack-inline-source-plugin/-/html-webpack-inline-source-plugin-1.0.0-beta.2.tgz", - "integrity": "sha512-ydsEKdp0tnbmnqRAH2WSSMXerCNYhjes5b79uvP2BU3p6cyk+6ucNMsw5b5xD1QxphgvBBA3QqVmdcpu8QLlRQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "slash": "^1.0.0", - "source-map-url": "^0.4.0" - }, - "dependencies": { - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - } - } - }, "html-webpack-plugin": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.4.tgz", - "integrity": "sha512-BREQzUbFfIQS39KqxkT2L1Ot0tuu1isako1CaCQLrgEQ43zi2ScHAe3SMTnVBWsStnIsGtl8jprDdxwZkNhrwQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.3.0.tgz", + "integrity": "sha512-C0fzKN8yQoVLTelcJxZfJCE+aAvQiY2VUf3UuKrR4a9k5UMWYOtpDLsaXwATbcVCnI05hUS7L9ULQHWLZhyi3w==", "dev": true, "requires": { "@types/html-minifier-terser": "^5.0.0", @@ -9413,7 +11398,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -9447,8 +11431,7 @@ "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" }, "husky": { "version": "4.2.5", @@ -9573,12 +11556,6 @@ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, "ignore-walk": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", @@ -9607,6 +11584,15 @@ "resolve-from": "^4.0.0" }, "dependencies": { + "param-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", + "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", + "requires": { + "dot-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -9625,7 +11611,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, "requires": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -9635,7 +11620,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -9645,7 +11629,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -9654,7 +11637,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -9662,14 +11644,12 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, "requires": { "find-up": "^4.0.0" } @@ -9679,8 +11659,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { "version": "2.1.0", @@ -9707,7 +11686,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -9716,8 +11694,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -9725,88 +11702,6 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, - "inquirer": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", - "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.5.3", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "internal-slot": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", @@ -9819,9 +11714,9 @@ } }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, "invariant": { @@ -9833,17 +11728,10 @@ "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" }, "ipaddr.js": { "version": "1.9.1", @@ -9861,7 +11749,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -9870,7 +11757,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -9885,23 +11771,22 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "optional": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "^2.0.0" } }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { "version": "1.1.5", @@ -9912,7 +11797,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, "requires": { "ci-info": "^2.0.0" } @@ -9935,7 +11819,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -9944,7 +11827,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -9960,7 +11842,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -9970,8 +11851,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, @@ -9981,11 +11861,16 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "optional": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { "version": "2.1.1", @@ -10000,9 +11885,9 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "is-function": { @@ -10014,8 +11899,7 @@ "is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" }, "is-glob": { "version": "4.0.1", @@ -10087,16 +11971,14 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=" }, "is-redirect": { "version": "1.0.0", @@ -10133,8 +12015,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-string": { "version": "1.0.5", @@ -10162,8 +12043,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-utf8": { "version": "0.2.1", @@ -10174,56 +12054,48 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", - "dev": true, - "optional": true + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "optional": true, + "requires": { + "is-docker": "^2.0.0" + } }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-lib-coverage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==" }, "istanbul-lib-instrument": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", - "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "requires": { "@babel/core": "^7.7.5", - "@babel/parser": "^7.7.5", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.0.0", "semver": "^6.3.0" @@ -10232,8 +12104,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -10241,7 +12112,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, "requires": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", @@ -10251,14 +12121,12 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "requires": { "semver": "^6.0.0" } @@ -10266,14 +12134,12 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -10284,7 +12150,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, "requires": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -10294,8 +12159,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -10303,114 +12167,65 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, "requires": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "jest": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest/-/jest-25.2.7.tgz", - "integrity": "sha512-XV1n/CE2McCikl4tfpCY950RytHYvxdo/wvtgmn/qwA8z1s16fuvgFL/KoPrrmkqJTaPMUlLVE58pwiaTX5TdA==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.1.0.tgz", + "integrity": "sha512-LIti8jppw5BcQvmNJe4w2g1N/3V68HUfAv9zDVm7v+VAtQulGhH0LnmmiVkbNE4M4I43Bj2fXPiBGKt26k9tHw==", "dev": true, "requires": { - "@jest/core": "^25.2.7", + "@jest/core": "^26.1.0", "import-local": "^3.0.2", - "jest-cli": "^25.2.7" + "jest-cli": "^26.1.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "graceful-fs": { + "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 }, "jest-cli": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.2.7.tgz", - "integrity": "sha512-OOAZwY4Jkd3r5WhVM5L3JeLNFaylvHUczMLxQDVLrrVyb1Cy+DNJ6MVsb5TLh6iBklB42m5TOP+IbOgKGGOtMw==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.1.0.tgz", + "integrity": "sha512-Imumvjgi3rU7stq6SJ1JUEMaV5aAgJYXIs0jPqdUnF47N/Tk83EXfmtvNKQ+SnFVI6t6mDOvfM3aA9Sg6kQPSw==", "dev": true, "requires": { - "@jest/core": "^25.2.7", - "@jest/test-result": "^25.2.6", - "@jest/types": "^25.2.6", - "chalk": "^3.0.0", + "@jest/core": "^26.1.0", + "@jest/test-result": "^26.1.0", + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", "exit": "^0.1.2", + "graceful-fs": "^4.2.4", "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^25.2.7", - "jest-util": "^25.2.6", - "jest-validate": "^25.2.6", + "jest-config": "^26.1.0", + "jest-util": "^26.1.0", + "jest-validate": "^26.1.0", "prompts": "^2.0.1", - "realpath-native": "^2.0.0", "yargs": "^15.3.1" - }, - "dependencies": { - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" } } } }, "jest-changed-files": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.2.6.tgz", - "integrity": "sha512-F7l2m5n55jFnJj4ItB9XbAlgO+6umgvz/mdK76BfTd2NGkvGf9x96hUXP/15a1K0k14QtVOoutwpRKl360msvg==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.1.0.tgz", + "integrity": "sha512-HS5MIJp3B8t0NRKGMCZkcDUZo36mVRvrDETl81aqljT1S9tqiHRSpyoOvWg9ZilzZG9TDisDNaN1IXm54fLRZw==", "requires": { - "@jest/types": "^25.2.6", - "execa": "^3.2.0", + "@jest/types": "^26.1.0", + "execa": "^4.0.0", "throat": "^5.0.0" }, "dependencies": { "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", - "dev": true, + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -10418,10 +12233,9 @@ } }, "execa": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", - "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", "requires": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -10430,7 +12244,6 @@ "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", - "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } @@ -10439,7 +12252,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -10447,35 +12259,25 @@ "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, "requires": { "path-key": "^3.0.0" } }, - "p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -10483,95 +12285,73 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } } } }, - "jest-config": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.2.7.tgz", - "integrity": "sha512-rIdPPXR6XUxi+7xO4CbmXXkE6YWprvlKc4kg1SrkCL2YV5m/8MkHstq9gBZJ19Qoa3iz/GP+0sTG/PcIwkFojg==", + "jest-circus": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.1.0.tgz", + "integrity": "sha512-V5h5XJLPf0XXwP92GIOx8n0Q6vdPDcFPBuEVQ9/OPzpsx3gquL8fdxaJGZ5TsvkU3zWM7mDWULAKYJMRkA2e+g==", "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.1.0", + "@jest/test-result": "^26.1.0", + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^26.1.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.1.0", + "jest-matcher-utils": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-runtime": "^26.1.0", + "jest-snapshot": "^26.1.0", + "jest-util": "^26.1.0", + "pretty-format": "^26.1.0", + "stack-utils": "^2.0.2", + "throat": "^5.0.0" + } + }, + "jest-config": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.1.0.tgz", + "integrity": "sha512-ONTGeoMbAwGCdq4WuKkMcdMoyfs5CLzHEkzFOlVvcDXufZSaIWh/OXMLa2fwKXiOaFcqEw8qFr4VOKJQfn4CVw==", "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^25.2.7", - "@jest/types": "^25.2.6", - "babel-jest": "^25.2.6", - "chalk": "^3.0.0", + "@jest/test-sequencer": "^26.1.0", + "@jest/types": "^26.1.0", + "babel-jest": "^26.1.0", + "chalk": "^4.0.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", - "jest-environment-jsdom": "^25.2.6", - "jest-environment-node": "^25.2.6", - "jest-get-type": "^25.2.6", - "jest-jasmine2": "^25.2.7", - "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.2.6", - "jest-util": "^25.2.6", - "jest-validate": "^25.2.6", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.1.0", + "jest-environment-node": "^26.1.0", + "jest-get-type": "^26.0.0", + "jest-jasmine2": "^26.1.0", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.1.0", + "jest-util": "^26.1.0", + "jest-validate": "^26.1.0", "micromatch": "^4.0.2", - "pretty-format": "^25.2.6", - "realpath-native": "^2.0.0" + "pretty-format": "^26.1.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" } } }, @@ -10625,12 +12405,36 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "jest-cli": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.0.1.tgz", + "integrity": "sha512-pFLfSOBcbG9iOZWaMK4Een+tTxi/Wcm34geqZEqrst9cZDkTQ1LZ2CnBrTlHWuYAiTMFr0EQeK52ScyFU8wK+w==", + "requires": { + "@jest/core": "^26.0.1", + "@jest/test-result": "^26.0.1", + "@jest/types": "^26.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^26.0.1", + "jest-util": "^26.0.1", + "jest-validate": "^26.0.1", + "prompts": "^2.0.1", + "yargs": "^15.3.1" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -10643,177 +12447,59 @@ } }, "jest-diff": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.2.6.tgz", - "integrity": "sha512-KuadXImtRghTFga+/adnNrv9s61HudRMR7gVSbP35UKZdn4IK2/0N0PpGZIqtmllK9aUyye54I3nu28OYSnqOg==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.1.0.tgz", + "integrity": "sha512-GZpIcom339y0OXznsEKjtkfKxNdg7bVbEofK8Q6MnevTIiR1jNhDWKhRX6X0SDXJlwn3dy59nZ1z55fLkAqPWg==", "requires": { - "chalk": "^3.0.0", - "diff-sequences": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.2.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "chalk": "^4.0.0", + "diff-sequences": "^26.0.0", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.1.0" } }, "jest-docblock": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-25.2.6.tgz", - "integrity": "sha512-VAYrljEq0upq0oERfIaaNf28gC6p9gORndhHstCYF8NWGNQJnzoaU//S475IxfWMk4UjjVmS9rJKLe5Jjjbixw==", - "dev": true, + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", + "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.2.6.tgz", - "integrity": "sha512-OgQ01VINaRD6idWJOhCYwUc5EcgHBiFlJuw+ON2VgYr7HLtMFyCcuo+3mmBvuLUH4QudREZN7cDCZviknzsaJQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.1.0.tgz", + "integrity": "sha512-lYiSo4Igr81q6QRsVQq9LIkJW0hZcKxkIkHzNeTMPENYYDw/W/Raq28iJ0sLlNFYz2qxxeLnc5K2gQoFYlu2bA==", "requires": { - "@jest/types": "^25.2.6", - "chalk": "^3.0.0", - "jest-get-type": "^25.2.6", - "jest-util": "^25.2.6", - "pretty-format": "^25.2.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.0.0", + "jest-util": "^26.1.0", + "pretty-format": "^26.1.0" } }, "jest-environment-jsdom": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.2.6.tgz", - "integrity": "sha512-/o7MZIhGmLGIEG5j7r5B5Az0umWLCHU+F5crwfbm0BzC4ybHTJZOQTFQWhohBg+kbTCNOuftMcqHlVkVduJCQQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.1.0.tgz", + "integrity": "sha512-dWfiJ+spunVAwzXbdVqPH1LbuJW/kDL+FyqgA5YzquisHqTi0g9hquKif9xKm7c1bKBj6wbmJuDkeMCnxZEpUw==", "requires": { - "@jest/environment": "^25.2.6", - "@jest/fake-timers": "^25.2.6", - "@jest/types": "^25.2.6", - "jest-mock": "^25.2.6", - "jest-util": "^25.2.6", - "jsdom": "^15.2.1" + "@jest/environment": "^26.1.0", + "@jest/fake-timers": "^26.1.0", + "@jest/types": "^26.1.0", + "jest-mock": "^26.1.0", + "jest-util": "^26.1.0", + "jsdom": "^16.2.2" } }, "jest-environment-node": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.2.6.tgz", - "integrity": "sha512-D1Ihj14fxZiMHGeTtU/LunhzSI+UeBvlr/rcXMTNyRMUMSz2PEhuqGbB78brBY6Dk3FhJDk7Ta+8reVaGjLWhA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.1.0.tgz", + "integrity": "sha512-DNm5x1aQH0iRAe9UYAkZenuzuJ69VKzDCAYISFHQ5i9e+2Tbeu2ONGY7YStubCLH8a1wdKBgqScYw85+ySxqxg==", "requires": { - "@jest/environment": "^25.2.6", - "@jest/fake-timers": "^25.2.6", - "@jest/types": "^25.2.6", - "jest-mock": "^25.2.6", - "jest-util": "^25.2.6", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "@jest/environment": "^26.1.0", + "@jest/fake-timers": "^26.1.0", + "@jest/types": "^26.1.0", + "jest-mock": "^26.1.0", + "jest-util": "^26.1.0" } }, "jest-environment-puppeteer": { @@ -10863,12 +12549,77 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", + "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "requires": { + "pump": "^3.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "requires": { + "path-key": "^3.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -10877,40 +12628,61 @@ "requires": { "has-flag": "^4.0.0" } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } } } }, - "jest-get-type": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", - "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", - "dev": true - }, - "jest-haste-map": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.2.6.tgz", - "integrity": "sha512-nom0+fnY8jwzelSDQnrqaKAcDZczYQvMEwcBjeL3PQ4MlcsqeB7dmrsAniUw/9eLkngT5DE6FhnenypilQFsgA==", + "jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", "dev": true, "requires": { - "@jest/types": "^25.2.6", + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "jest-get-type": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", + "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==" + }, + "jest-haste-map": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.1.0.tgz", + "integrity": "sha512-WeBS54xCIz9twzkEdm6+vJBXgRBQfdbbXD0dk8lJh7gLihopABlJmIQFdWSDDtuDe4PRiObsjZSUjbJ1uhWEpA==", + "requires": { + "@jest/types": "^26.1.0", + "@types/graceful-fs": "^4.1.2", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.1.2", - "graceful-fs": "^4.2.3", - "jest-serializer": "^25.2.6", - "jest-util": "^25.2.6", - "jest-worker": "^25.2.6", + "graceful-fs": "^4.2.4", + "jest-serializer": "^26.1.0", + "jest-util": "^26.1.0", + "jest-worker": "^26.1.0", "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7", "which": "^2.0.2" }, "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -10918,237 +12690,83 @@ } }, "jest-jasmine2": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.2.7.tgz", - "integrity": "sha512-HeQxEbonp8fUvik9jF0lkU9ab1u5TQdIb7YSU9Fj7SxWtqHNDGyCpF6ZZ3r/5yuertxi+R95Ba9eA91GMQ38eA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.1.0.tgz", + "integrity": "sha512-1IPtoDKOAG+MeBrKvvuxxGPJb35MTTRSDglNdWWCndCB3TIVzbLThRBkwH9P081vXLgiJHZY8Bz3yzFS803xqQ==", "requires": { "@babel/traverse": "^7.1.0", - "@jest/environment": "^25.2.6", - "@jest/source-map": "^25.2.6", - "@jest/test-result": "^25.2.6", - "@jest/types": "^25.2.6", - "chalk": "^3.0.0", + "@jest/environment": "^26.1.0", + "@jest/source-map": "^26.1.0", + "@jest/test-result": "^26.1.0", + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^25.2.7", + "expect": "^26.1.0", "is-generator-fn": "^2.0.0", - "jest-each": "^25.2.6", - "jest-matcher-utils": "^25.2.7", - "jest-message-util": "^25.2.6", - "jest-runtime": "^25.2.7", - "jest-snapshot": "^25.2.7", - "jest-util": "^25.2.6", - "pretty-format": "^25.2.6", + "jest-each": "^26.1.0", + "jest-matcher-utils": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-runtime": "^26.1.0", + "jest-snapshot": "^26.1.0", + "jest-util": "^26.1.0", + "pretty-format": "^26.1.0", "throat": "^5.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "jest-leak-detector": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.2.6.tgz", - "integrity": "sha512-n+aJUM+j/x1kIaPVxzerMqhAUuqTU1PL5kup46rXh+l9SP8H6LqECT/qD1GrnylE1L463/0StSPkH4fUpkuEjA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.1.0.tgz", + "integrity": "sha512-dsMnKF+4BVOZwvQDlgn3MG+Ns4JuLv8jNvXH56bgqrrboyCbI1rQg6EI5rs+8IYagVcfVP2yZFKfWNZy0rK0Hw==", "requires": { - "jest-get-type": "^25.2.6", - "pretty-format": "^25.2.6" + "jest-get-type": "^26.0.0", + "pretty-format": "^26.1.0" } }, "jest-matcher-utils": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.2.7.tgz", - "integrity": "sha512-jNYmKQPRyPO3ny0KY1I4f0XW4XnpJ3Nx5ovT4ik0TYDOYzuXJW40axqOyS61l/voWbVT9y9nZ1THL1DlpaBVpA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.1.0.tgz", + "integrity": "sha512-PW9JtItbYvES/xLn5mYxjMd+Rk+/kIt88EfH3N7w9KeOrHWaHrdYPnVHndGbsFGRJ2d5gKtwggCvkqbFDoouQA==", "requires": { - "chalk": "^3.0.0", - "jest-diff": "^25.2.6", - "jest-get-type": "^25.2.6", - "pretty-format": "^25.2.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "chalk": "^4.0.0", + "jest-diff": "^26.1.0", + "jest-get-type": "^26.0.0", + "pretty-format": "^26.1.0" } }, "jest-message-util": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.2.6.tgz", - "integrity": "sha512-Hgg5HbOssSqOuj+xU1mi7m3Ti2nwSQJQf/kxEkrz2r2rp2ZLO1pMeKkz2WiDUWgSR+APstqz0uMFcE5yc0qdcg==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.1.0.tgz", + "integrity": "sha512-dY0+UlldiAJwNDJ08SF0HdF32g9PkbF2NRK/+2iMPU40O6q+iSn1lgog/u0UH8ksWoPv0+gNq8cjhYO2MFtT0g==", "requires": { "@babel/code-frame": "^7.0.0", - "@jest/types": "^25.2.6", + "@jest/types": "^26.1.0", "@types/stack-utils": "^1.0.1", - "chalk": "^3.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "slash": "^3.0.0", - "stack-utils": "^1.0.1" + "stack-utils": "^2.0.2" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" } } }, "jest-mock": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.2.6.tgz", - "integrity": "sha512-vc4nibavi2RGPdj/MyZy/azuDjZhpYZLvpfgq1fxkhbyTpKVdG7CgmRVKJ7zgLpY5kuMjTzDYA6QnRwhsCU+tA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.1.0.tgz", + "integrity": "sha512-1Rm8EIJ3ZFA8yCIie92UbxZWj9SuVmUGcyhLHyAhY6WI3NIct38nVcfOPWhJteqSn8V8e3xOMha9Ojfazfpovw==", "requires": { - "@jest/types": "^25.2.6" + "@jest/types": "^26.1.0" } }, "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", - "dev": true + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==" }, "jest-puppeteer": { "version": "4.4.0", @@ -11158,572 +12776,312 @@ "requires": { "expect-puppeteer": "^4.4.0", "jest-environment-puppeteer": "^4.4.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + } } }, "jest-regex-util": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", - "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", - "dev": true + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==" }, "jest-resolve": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.2.6.tgz", - "integrity": "sha512-7O61GVdcAXkLz/vNGKdF+00A80/fKEAA47AEXVNcZwj75vEjPfZbXDaWFmAQCyXj4oo9y9dC9D+CLA11t8ieGw==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.1.0.tgz", + "integrity": "sha512-KsY1JV9FeVgEmwIISbZZN83RNGJ1CC+XUCikf/ZWJBX/tO4a4NvA21YixokhdR9UnmPKKAC4LafVixJBrwlmfg==", "requires": { - "@jest/types": "^25.2.6", - "browser-resolve": "^1.11.3", - "chalk": "^3.0.0", + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", "jest-pnp-resolver": "^1.2.1", - "realpath-native": "^2.0.0", - "resolve": "^1.15.1" + "jest-util": "^26.1.0", + "read-pkg-up": "^7.0.1", + "resolve": "^1.17.0", + "slash": "^3.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "p-locate": "^4.1.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "requires": { - "color-name": "~1.1.4" + "p-limit": "^2.2.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } }, - "has-flag": { + "path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "requires": { - "has-flag": "^4.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" } } } }, "jest-resolve-dependencies": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.2.7.tgz", - "integrity": "sha512-IrnMzCAh11Xd2gAOJL+ThEW6QO8DyqNdvNkQcaCticDrOAr9wtKT7yT6QBFFjqKFgjjvaVKDs59WdgUhgYnHnQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.1.0.tgz", + "integrity": "sha512-fQVEPHHQ1JjHRDxzlLU/buuQ9om+hqW6Vo928aa4b4yvq4ZHBtRSDsLdKQLuCqn5CkTVpYZ7ARh2fbA8WkRE6g==", "requires": { - "@jest/types": "^25.2.6", - "jest-regex-util": "^25.2.6", - "jest-snapshot": "^25.2.7" + "@jest/types": "^26.1.0", + "jest-regex-util": "^26.0.0", + "jest-snapshot": "^26.1.0" } }, "jest-runner": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.2.7.tgz", - "integrity": "sha512-RFEr71nMrtNwcpoHzie5+fe1w3JQCGMyT2xzNwKe3f88+bK+frM2o1v24gEcPxQ2QqB3COMCe2+1EkElP+qqqQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.1.0.tgz", + "integrity": "sha512-elvP7y0fVDREnfqit0zAxiXkDRSw6dgCkzPCf1XvIMnSDZ8yogmSKJf192dpOgnUVykmQXwYYJnCx641uLTgcw==", "requires": { - "@jest/console": "^25.2.6", - "@jest/environment": "^25.2.6", - "@jest/test-result": "^25.2.6", - "@jest/types": "^25.2.6", - "chalk": "^3.0.0", + "@jest/console": "^26.1.0", + "@jest/environment": "^26.1.0", + "@jest/test-result": "^26.1.0", + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.3", - "jest-config": "^25.2.7", - "jest-docblock": "^25.2.6", - "jest-haste-map": "^25.2.6", - "jest-jasmine2": "^25.2.7", - "jest-leak-detector": "^25.2.6", - "jest-message-util": "^25.2.6", - "jest-resolve": "^25.2.6", - "jest-runtime": "^25.2.7", - "jest-util": "^25.2.6", - "jest-worker": "^25.2.6", + "graceful-fs": "^4.2.4", + "jest-config": "^26.1.0", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.1.0", + "jest-jasmine2": "^26.1.0", + "jest-leak-detector": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-resolve": "^26.1.0", + "jest-runtime": "^26.1.0", + "jest-util": "^26.1.0", + "jest-worker": "^26.1.0", "source-map-support": "^0.5.6", "throat": "^5.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" } } }, "jest-runtime": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.2.7.tgz", - "integrity": "sha512-Gw3X8KxTTFylu2T/iDSNKRUQXQiPIYUY0b66GwVYa7W8wySkUljKhibQHSq0VhmCAN7vRBEQjlVQ+NFGNmQeBw==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.1.0.tgz", + "integrity": "sha512-1qiYN+EZLmG1QV2wdEBRf+Ci8i3VSfIYLF02U18PiUDrMbhfpN/EAMMkJtT02jgJUoaEOpHAIXG6zS3QRMzRmA==", "requires": { - "@jest/console": "^25.2.6", - "@jest/environment": "^25.2.6", - "@jest/source-map": "^25.2.6", - "@jest/test-result": "^25.2.6", - "@jest/transform": "^25.2.6", - "@jest/types": "^25.2.6", + "@jest/console": "^26.1.0", + "@jest/environment": "^26.1.0", + "@jest/fake-timers": "^26.1.0", + "@jest/globals": "^26.1.0", + "@jest/source-map": "^26.1.0", + "@jest/test-result": "^26.1.0", + "@jest/transform": "^26.1.0", + "@jest/types": "^26.1.0", "@types/yargs": "^15.0.0", - "chalk": "^3.0.0", + "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", - "graceful-fs": "^4.2.3", - "jest-config": "^25.2.7", - "jest-haste-map": "^25.2.6", - "jest-message-util": "^25.2.6", - "jest-mock": "^25.2.6", - "jest-regex-util": "^25.2.6", - "jest-resolve": "^25.2.6", - "jest-snapshot": "^25.2.7", - "jest-util": "^25.2.6", - "jest-validate": "^25.2.6", - "realpath-native": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-config": "^26.1.0", + "jest-haste-map": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-mock": "^26.1.0", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.1.0", + "jest-snapshot": "^26.1.0", + "jest-util": "^26.1.0", + "jest-validate": "^26.1.0", "slash": "^3.0.0", "strip-bom": "^4.0.0", "yargs": "^15.3.1" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" } } }, "jest-serializer": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.2.6.tgz", - "integrity": "sha512-RMVCfZsezQS2Ww4kB5HJTMaMJ0asmC0BHlnobQC6yEtxiFKIxohFA4QSXSabKwSggaNkqxn6Z2VwdFCjhUWuiQ==", - "dev": true - }, - "jest-snapshot": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.2.7.tgz", - "integrity": "sha512-Rm8k7xpGM4tzmYhB6IeRjsOMkXaU8/FOz5XlU6oYwhy53mq6txVNqIKqN1VSiexzpC80oWVxVDfUDt71M6XPOA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.1.0.tgz", + "integrity": "sha512-eqZOQG/0+MHmr25b2Z86g7+Kzd5dG9dhCiUoyUNJPgiqi38DqbDEOlHcNijyfZoj74soGBohKBZuJFS18YTJ5w==", "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^25.2.6", - "@types/prettier": "^1.19.0", - "chalk": "^3.0.0", - "expect": "^25.2.7", - "jest-diff": "^25.2.6", - "jest-get-type": "^25.2.6", - "jest-matcher-utils": "^25.2.7", - "jest-message-util": "^25.2.6", - "jest-resolve": "^25.2.6", - "make-dir": "^3.0.0", - "natural-compare": "^1.4.0", - "pretty-format": "^25.2.6", - "semver": "^6.3.0" + "graceful-fs": "^4.2.4" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + } + } + }, + "jest-snapshot": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.1.0.tgz", + "integrity": "sha512-YhSbU7eMTVQO/iRbNs8j0mKRxGp4plo7sJ3GzOQ0IYjvsBiwg0T1o0zGQAYepza7lYHuPTrG5J2yDd0CE2YxSw==", + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.1.0", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.1.0", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.1.0", + "jest-get-type": "^26.0.0", + "jest-haste-map": "^26.1.0", + "jest-matcher-utils": "^26.1.0", + "jest-message-util": "^26.1.0", + "jest-resolve": "^26.1.0", + "natural-compare": "^1.4.0", + "pretty-format": "^26.1.0", + "semver": "^7.3.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" } } }, "jest-util": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.2.6.tgz", - "integrity": "sha512-gpXy0H5ymuQ0x2qgl1zzHg7LYHZYUmDEq6F7lhHA8M0eIwDB2WteOcCnQsohl9c/vBKZ3JF2r4EseipCZz3s4Q==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.1.0.tgz", + "integrity": "sha512-rNMOwFQevljfNGvbzNQAxdmXQ+NawW/J72dmddsK0E8vgxXCMtwQ/EH0BiWEIxh0hhMcTsxwAxINt7Lh46Uzbg==", "requires": { - "@jest/types": "^25.2.6", - "chalk": "^3.0.0", + "@jest/types": "^26.1.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", "is-ci": "^2.0.0", - "make-dir": "^3.0.0" + "micromatch": "^4.0.2" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" } } }, "jest-validate": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.2.6.tgz", - "integrity": "sha512-a4GN7hYbqQ3Rt9iHsNLFqQz7HDV7KiRPCwPgo5nqtTIWNZw7gnT8KchG+Riwh+UTSn8REjFCodGp50KX/fRNgQ==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.1.0.tgz", + "integrity": "sha512-WPApOOnXsiwhZtmkDsxnpye+XLb/tUISP+H6cHjfUIXvlG+eKwP+isnivsxlHCPaO9Q5wvbhloIBkdF3qUn+Nw==", "requires": { - "@jest/types": "^25.2.6", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "jest-get-type": "^25.2.6", + "@jest/types": "^26.1.0", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.0.0", "leven": "^3.1.0", - "pretty-format": "^25.2.6" + "pretty-format": "^26.1.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==" } } }, "jest-watcher": { - "version": "25.2.7", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.2.7.tgz", - "integrity": "sha512-RdHuW+f49tahWtluTnUdZ2iPliebleROI2L/J5phYrUS6DPC9RB3SuUtqYyYhGZJsbvRSuLMIlY/cICJ+PIecw==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.1.0.tgz", + "integrity": "sha512-ffEOhJl2EvAIki613oPsSG11usqnGUzIiK7MMX6hE4422aXOcVEG3ySCTDFLn1+LZNXGPE8tuJxhp8OBJ1pgzQ==", "requires": { - "@jest/test-result": "^25.2.6", - "@jest/types": "^25.2.6", + "@jest/test-result": "^26.1.0", + "@jest/types": "^26.1.0", "ansi-escapes": "^4.2.1", - "chalk": "^3.0.0", - "jest-util": "^25.2.6", - "string-length": "^3.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "chalk": "^4.0.0", + "jest-util": "^26.1.0", + "string-length": "^4.0.1" } }, "jest-worker": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.2.6.tgz", - "integrity": "sha512-FJn9XDUSxcOR4cwDzRfL1z56rUofNTFs539FGASpd50RHdb6EVkhxQqktodW2mI49l+W3H+tFJDotCHUQF6dmA==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.1.0.tgz", + "integrity": "sha512-Z9P5pZ6UC+kakMbNJn+tA2RdVdNX5WH1x+5UCBZ9MxIK24pjYtFt96fK+UwBTrjLYm232g1xz0L3eTh51OW+yQ==", "requires": { "merge-stream": "^2.0.0", "supports-color": "^7.0.0" @@ -11732,14 +13090,12 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -11783,7 +13139,6 @@ "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -11792,48 +13147,52 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", - "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", - "dev": true, + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.3.0.tgz", + "integrity": "sha512-zggeX5UuEknpdZzv15+MS1dPYG0J/TftiiNunOeNxSl3qr8Z6cIlQpN0IdJa44z9aFxZRIVqRncvEhQ7X5DtZg==", "requires": { - "abab": "^2.0.0", - "acorn": "^7.1.0", - "acorn-globals": "^4.3.2", - "array-equal": "^1.0.0", - "cssom": "^0.4.1", - "cssstyle": "^2.0.0", - "data-urls": "^1.1.0", - "domexception": "^1.0.1", - "escodegen": "^1.11.1", - "html-encoding-sniffer": "^1.0.2", + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", "nwsapi": "^2.2.0", - "parse5": "5.1.0", - "pn": "^1.1.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.7", - "saxes": "^3.1.9", - "symbol-tree": "^3.2.2", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.1", - "w3c-xmlserializer": "^1.1.2", - "webidl-conversions": "^4.0.2", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", "whatwg-encoding": "^1.0.5", "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^7.0.0", - "ws": "^7.0.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + } } }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-loader": { "version": "0.5.7", @@ -11844,20 +13203,17 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -11868,14 +13224,12 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, "requires": { "minimist": "^1.2.5" } @@ -11899,7 +13253,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -11908,27 +13261,40 @@ } }, "jsx-ast-utils": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", - "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", "dev": true, "requires": { - "array-includes": "^3.0.3", + "array-includes": "^3.1.1", "object.assign": "^4.1.0" } }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + }, + "language-subtag-registry": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", + "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==", "dev": true }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, "last-call-webpack-plugin": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", @@ -11954,20 +13320,10 @@ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" }, "levenary": { "version": "1.1.1", @@ -11979,34 +13335,35 @@ } }, "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, "lint-staged": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.4.tgz", - "integrity": "sha512-doTMGKXQAT34c3S3gwDrTnXmCZp/z1/92D8suPqqh755sKPT18ew1NoPNHxJdrvv1D4WrJ7CEnx79Ns3EdEFbg==", + "version": "10.2.11", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.11.tgz", + "integrity": "sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==", "dev": true, "requires": { "chalk": "^4.0.0", + "cli-truncate": "2.1.0", "commander": "^5.1.0", "cosmiconfig": "^6.0.0", "debug": "^4.1.1", "dedent": "^0.7.0", + "enquirer": "^2.3.5", "execa": "^4.0.1", - "listr2": "^2.0.2", + "listr2": "^2.1.0", "log-symbols": "^4.0.0", "micromatch": "^4.0.2", "normalize-path": "^3.0.0", @@ -12035,9 +13392,9 @@ } }, "cross-spawn": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", - "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -12046,9 +13403,9 @@ } }, "execa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.1.tgz", - "integrity": "sha512-SCjM/zlBdOK8Q5TIjOn6iEHZaPHFsMoTxXQ2nvUvtPnuohz3H2dIozSg+etNR98dGoYUp2ENSKLL/XaMmbxVgw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -12137,25 +13494,19 @@ } }, "listr2": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.0.4.tgz", - "integrity": "sha512-oJaAcplPsa72rKW0eg4P4LbEJjhH+UO2I8uqR/I2wzHrVg16ohSfUy0SlcHS21zfYXxtsUpL8YXGHjyfWMR0cg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.2.0.tgz", + "integrity": "sha512-Q8qbd7rgmEwDo1nSyHaWQeztfGsdL6rb4uh7BA+Q80AZiDET5rVntiU1+13mu2ZTDVaBVbvAD1Db11rnu3l9sg==", "dev": true, "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", "chalk": "^4.0.0", - "cli-cursor": "^3.1.0", "cli-truncate": "^2.1.0", - "elegant-spinner": "^2.0.0", - "enquirer": "^2.3.5", "figures": "^3.2.0", "indent-string": "^4.0.0", "log-update": "^4.0.0", "p-map": "^4.0.0", - "pad": "^3.2.0", "rxjs": "^6.5.5", - "through": "^2.3.8", - "uuid": "^7.0.2" + "through": "^2.3.8" }, "dependencies": { "indent-string": { @@ -12172,21 +13523,6 @@ "requires": { "aggregate-error": "^3.0.0" } - }, - "rxjs": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", - "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "dev": true } } }, @@ -12316,9 +13652,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -12365,8 +13701,7 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, "lodash.template": { "version": "4.5.0", @@ -12451,6 +13786,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", @@ -12464,15 +13805,6 @@ } } }, - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -12520,25 +13852,14 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, "requires": { "tmpl": "1.0.x" } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-obj": { "version": "1.0.1", @@ -12550,7 +13871,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, "requires": { "object-visit": "^1.0.0" } @@ -12578,17 +13898,6 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, "memoize-one": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", @@ -12734,8 +14043,7 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "methods": { "version": "1.1.2", @@ -12747,7 +14055,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.0.5" @@ -12761,6 +14068,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 + } } }, "mime": { @@ -12772,14 +14087,12 @@ "mime-db": { "version": "1.43.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" }, "mime-types": { "version": "2.1.26", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dev": true, "requires": { "mime-db": "1.43.0" } @@ -12787,8 +14100,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "mimic-response": { "version": "2.1.0", @@ -12833,7 +14145,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -12841,8 +14152,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 + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { "version": "3.1.1", @@ -12880,9 +14190,9 @@ } }, "minipass-pipeline": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", - "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz", + "integrity": "sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ==", "dev": true, "requires": { "minipass": "^3.0.0" @@ -12928,7 +14238,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -12938,7 +14247,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -13006,14 +14314,7 @@ "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 - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nan": { "version": "2.14.0", @@ -13025,7 +14326,6 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -13049,8 +14349,7 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" }, "negotiator": { "version": "0.6.2", @@ -13067,8 +14366,7 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node-abi": { "version": "2.15.0", @@ -13088,8 +14386,7 @@ "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" }, "node-libs-browser": { "version": "2.2.1", @@ -13122,6 +14419,17 @@ "vm-browserify": "^1.0.1" }, "dependencies": { + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -13133,29 +14441,42 @@ "node-modules-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" }, "node-notifier": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz", - "integrity": "sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==", - "dev": true, + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-7.0.1.tgz", + "integrity": "sha512-VkzhierE7DBmQEElhTGJIoiZa1oqRijOtgOlsXg32KrJRXsPy0NXFBqWGW/wTswnJlDCs5viRYaqWguqzsKcmg==", "optional": true, "requires": { "growly": "^1.3.0", "is-wsl": "^2.1.1", - "semver": "^6.3.0", + "semver": "^7.2.1", "shellwords": "^0.1.1", - "which": "^1.3.1" + "uuid": "^7.0.3", + "which": "^2.0.2" }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "optional": true + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "optional": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -13175,7 +14496,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -13186,8 +14506,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-url": { "version": "1.9.1", @@ -13210,7 +14529,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, "requires": { "path-key": "^2.0.0" } @@ -13245,14 +14563,12 @@ "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -13263,7 +14579,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -13274,7 +14589,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -13283,7 +14597,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -13302,9 +14615,46 @@ "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" }, "object-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", - "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "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.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } }, "object-keys": { "version": "1.1.1", @@ -13315,7 +14665,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, "requires": { "isobject": "^3.0.0" } @@ -13332,15 +14681,50 @@ } }, "object.entries": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", - "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", + "es-abstract": "^1.17.5", "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "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.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } } }, "object.fromentries": { @@ -13369,7 +14753,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -13405,7 +14788,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -13414,7 +14796,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -13436,17 +14817,17 @@ } }, "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" } }, "os-browserify": { @@ -13461,52 +14842,20 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "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 - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, "p-each-series": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", - "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==", - "dev": true + "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==" }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -13529,8 +14878,7 @@ "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 + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-json": { "version": "4.0.1", @@ -13544,15 +14892,6 @@ "semver": "^5.1.0" } }, - "pad": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pad/-/pad-3.2.0.tgz", - "integrity": "sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==", - "dev": true, - "requires": { - "wcwidth": "^1.0.1" - } - }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -13700,8 +15039,7 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, "path-browserify": { "version": "0.0.1", @@ -13713,7 +15051,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true + "dev": true, + "optional": true }, "path-exists": { "version": "3.0.0", @@ -13724,8 +15063,7 @@ "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 + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -13736,14 +15074,12 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -13751,27 +15087,10 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -13790,8 +15109,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "phin": { "version": "2.9.3", @@ -13802,8 +15120,7 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pify": { "version": "4.0.1", @@ -13830,7 +15147,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, "requires": { "node-modules-regexp": "^1.0.0" } @@ -13916,12 +15232,6 @@ "semver-compare": "^1.0.0" } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, "pngjs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", @@ -13936,8 +15246,7 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { "version": "7.0.27", @@ -14259,9 +15568,9 @@ } }, "postcss-modules-scope": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz", - "integrity": "sha512-OXRUPecnHCg8b9xWvldG/jUpRIGPNRka0r4D4j0ESUU2/5IOnpsjfPPmDprM3Ih8CgZ8FXjWqaniK5v4rWt3oQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", "dev": true, "requires": { "postcss": "^7.0.6", @@ -14570,9 +15879,9 @@ } }, "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, "prepend-http": { @@ -14613,12 +15922,11 @@ } }, "pretty-format": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.2.6.tgz", - "integrity": "sha512-DEiWxLBaCHneffrIT4B+TpMvkV9RNvvJrd3lY9ew1CEQobDzEXmYT1mg0hJhljZty7kCc10z13ohOFAE8jrUDg==", - "dev": true, + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.1.0.tgz", + "integrity": "sha512-GmeO1PEYdM+non4BKCj+XsPJjFOJIPnsLewqhDVoqY1xo0yNmDas7tC2XwpMrRAHR3MaE2hPo37deX5OisJ2Wg==", "requires": { - "@jest/types": "^25.2.6", + "@jest/types": "^26.1.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -14628,7 +15936,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -14638,7 +15945,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -14646,17 +15952,10 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -14681,11 +15980,16 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==", + "dev": true + }, "prompts": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", - "dev": true, "requires": { "kleur": "^3.0.3", "sisteransi": "^1.0.4" @@ -14732,8 +16036,7 @@ "psl": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", - "dev": true + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" }, "public-encrypt": { "version": "4.0.3", @@ -14747,13 +16050,20 @@ "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -14785,50 +16095,31 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "puppeteer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-2.1.1.tgz", - "integrity": "sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.3.0.tgz", + "integrity": "sha512-23zNqRltZ1PPoK28uRefWJ/zKb5Jhnzbbwbpcna2o5+QMn17F0khq5s1bdH3vPlyj+J36pubccR8wiNA/VE0Vw==", "dev": true, "requires": { - "@types/mime-types": "^2.1.0", "debug": "^4.1.0", - "extract-zip": "^1.6.6", + "extract-zip": "^2.0.0", "https-proxy-agent": "^4.0.0", "mime": "^2.0.3", - "mime-types": "^2.1.25", "progress": "^2.0.1", "proxy-from-env": "^1.0.0", - "rimraf": "^2.6.1", - "ws": "^6.1.0" + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" }, "dependencies": { "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } } } }, @@ -14942,6 +16233,11 @@ "prop-types": "^15.6.2" } }, + "react-async": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-async/-/react-async-10.0.1.tgz", + "integrity": "sha512-ORUz5ca0B57QgBIzEZM5SuhJ6xFjkvEEs0gylLNlWf06vuVcLZsjIw3wx58jJkZG38p+0nUAxRgFW2b7mnVZzA==" + }, "react-dom": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", @@ -14959,11 +16255,12 @@ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, "react-flip-toolkit": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/react-flip-toolkit/-/react-flip-toolkit-7.0.6.tgz", - "integrity": "sha512-zZpwNfAGS8/WRrHKOA224NP2aVG4ryVx9vkTkxFsU7tkGUpM5AvjsICYmOVDJhEl5wmR36SjMSmWFicloJVibA==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/react-flip-toolkit/-/react-flip-toolkit-7.0.12.tgz", + "integrity": "sha512-JMACwG+oGSDEvQxXZIlZ64EOIZJhw5XZ+1MLHsxDoNWP+Jzb9ZNfKP13jM2rW/sOuN/rLiVPOayHIFWcksu/BA==", "requires": { - "rematrix": "0.2.2" + "flip-toolkit": "7.0.11", + "prop-types": "^15.5.7" } }, "react-helmet": { @@ -14978,9 +16275,9 @@ } }, "react-icons": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.9.0.tgz", - "integrity": "sha512-gKbYKR+4QsD3PmIHLAM9TDDpnaTsr3XZeK1NTAb6WQQ+gxEdJ0xuCgLq0pxXdS7Utg2AIpcVhM1ut/jlDhcyNg==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.10.0.tgz", + "integrity": "sha512-WsQ5n1JToG9VixWilSo1bHv842Cj5aZqTGiS3Ud47myF6aK7S/IUY2+dHcBdmkQcCFRuHsJ9OMUI0kTDfjyZXQ==", "requires": { "camelcase": "^5.0.0" } @@ -15145,127 +16442,15 @@ } }, "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", "dev": true, + "optional": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } + "picomatch": "^2.2.1" } }, - "realpath-native": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-2.0.0.tgz", - "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==", - "dev": true - }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -15311,20 +16496,18 @@ "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" }, "regenerator-transform": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz", - "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "@babel/runtime": "^7.8.4", - "private": "^0.1.8" + "@babel/runtime": "^7.8.4" } }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" @@ -15340,9 +16523,9 @@ } }, "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, "regexpu-core": { @@ -15402,9 +16585,9 @@ } }, "regl": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regl/-/regl-1.4.2.tgz", - "integrity": "sha512-wc/kE6kGmGfQk3G9f1Pai4TZ0K1pWxkD1Jeaj6CxJwEiB1jwHgEpqD84G2t7F0DmNXfQh7IUnoG1opxoONJ7Xg==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/regl/-/regl-1.6.1.tgz", + "integrity": "sha512-7Z9rmpEqmLNwC9kCYCyfyu47eWZaQWeNpwZfwz99QueXN8B/Ow40DB0N+OeUeM/yu9pZAB01+JgJ+XghGveVoA==" }, "relateurl": { "version": "0.2.7", @@ -15420,8 +16603,7 @@ "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "renderkid": { "version": "2.0.3", @@ -15456,14 +16638,12 @@ "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "repeating": { "version": "2.0.1", @@ -15484,7 +16664,6 @@ "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -15511,14 +16690,12 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -15530,7 +16707,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "dev": true, "requires": { "lodash": "^4.17.15" } @@ -15539,7 +16715,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "dev": true, "requires": { "request-promise-core": "1.1.3", "stealthy-require": "^1.1.1", @@ -15550,7 +16725,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -15561,14 +16735,12 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resize-img": { "version": "1.1.2", @@ -15682,7 +16854,6 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -15691,7 +16862,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, "requires": { "resolve-from": "^5.0.0" } @@ -15709,14 +16879,12 @@ "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "restore-cursor": { "version": "3.1.0", @@ -15731,8 +16899,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "rgb-regex": { "version": "1.0.1", @@ -15750,7 +16917,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -15768,17 +16934,7 @@ "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" }, "run-queue": { "version": "1.0.3", @@ -15801,9 +16957,9 @@ "dev": true }, "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", + "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -15812,14 +16968,12 @@ "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 + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, "requires": { "ret": "~0.1.10" } @@ -15833,7 +16987,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, "requires": { "@cnakazawa/watch": "^1.0.3", "anymatch": "^2.0.0", @@ -15850,7 +17003,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" @@ -15860,7 +17012,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -15878,7 +17029,6 @@ "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" } @@ -15889,7 +17039,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -15901,7 +17050,6 @@ "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" } @@ -15912,7 +17060,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -15921,7 +17068,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -15932,7 +17078,6 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -15953,7 +17098,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, "requires": { "remove-trailing-separator": "^1.0.1" } @@ -15962,7 +17106,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -15977,12 +17120,11 @@ "dev": true }, "saxes": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", - "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", - "dev": true, + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", "requires": { - "xmlchars": "^2.1.1" + "xmlchars": "^2.2.0" } }, "scheduler": { @@ -16008,8 +17150,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-compare": { "version": "1.0.0", @@ -16079,10 +17220,13 @@ } }, "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } }, "serve-favicon": { "version": "2.5.0", @@ -16132,14 +17276,12 @@ "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 + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -16151,7 +17293,6 @@ "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" } @@ -16243,7 +17384,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -16251,14 +17391,12 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, "optional": true }, "side-channel": { @@ -16274,8 +17412,7 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-concat": { "version": "1.0.0", @@ -16314,14 +17451,12 @@ "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "slice-ansi": { "version": "2.1.0", @@ -16332,21 +17467,12 @@ "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } } }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -16362,7 +17488,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -16371,7 +17496,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -16380,7 +17504,6 @@ "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" } @@ -16388,8 +17511,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -16397,7 +17519,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -16408,7 +17529,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -16417,7 +17537,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -16426,7 +17545,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -16435,7 +17553,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -16448,7 +17565,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, "requires": { "kind-of": "^3.2.0" }, @@ -16457,7 +17573,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -16482,14 +17597,12 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, "requires": { "atob": "^2.1.2", "decode-uri-component": "^0.2.0", @@ -16502,7 +17615,6 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -16511,16 +17623,14 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, "spawnd": { "version": "4.4.0", @@ -16538,7 +17648,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -16547,14 +17656,12 @@ "spdx-exceptions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -16563,14 +17670,12 @@ "spdx-license-ids": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, "requires": { "extend-shallow": "^3.0.0" } @@ -16578,14 +17683,12 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -16614,16 +17717,24 @@ "dev": true }, "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg==", + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + } + } }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -16633,7 +17744,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -16649,8 +17759,7 @@ "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, "stream-browserify": { "version": "2.0.2", @@ -16728,33 +17837,38 @@ "dev": true }, "string-length": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz", - "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", + "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^5.2.0" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" } }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^4.1.0" } } } @@ -16773,6 +17887,48 @@ "side-channel": "^1.0.2" } }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "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.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, "string.prototype.trimleft": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", @@ -16791,6 +17947,48 @@ "function-bind": "^1.1.1" } }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "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.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -16812,20 +18010,11 @@ } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } + "ansi-regex": "^5.0.0" } }, "strip-bom": { @@ -16837,14 +18026,12 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, "strip-indent": { "version": "1.0.1", @@ -16864,9 +18051,9 @@ } }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "stubs": { @@ -16876,22 +18063,46 @@ "dev": true }, "style-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz", - "integrity": "sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.2.1.tgz", + "integrity": "sha512-ByHSTQvHLkWE9Ir5+lGbVOXhxX10fbprhLvdg96wedFZb4NDekDPxVKv5Fwmio+QcMlkkNfuK+5W1peQ5CUhZg==", "dev": true, "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.6.4" + "loader-utils": "^2.0.0", + "schema-utils": "^2.6.6" }, "dependencies": { - "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "dev": true, "requires": { - "ajv": "^6.12.0", + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", "ajv-keywords": "^3.4.1" } } @@ -16940,7 +18151,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -16949,7 +18159,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", - "dev": true, "requires": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -16958,14 +18167,12 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -17123,8 +18330,7 @@ "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "table": { "version": "5.4.6", @@ -17136,31 +18342,6 @@ "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" - }, - "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } } }, "tapable": { @@ -17305,7 +18486,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, "requires": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" @@ -17331,48 +18511,65 @@ } }, "terser-webpack-plugin": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.6.tgz", - "integrity": "sha512-I8IDsQwZrqjdmOicNeE8L/MhwatAap3mUrtcAKJuilsemUNcX+Hier/eAzwStVqhlCxq0aG3ni9bK/0BESXkTg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-3.0.7.tgz", + "integrity": "sha512-5JqibUOctE6Ou4T00IVGYTQJBOhu24jz0PpqYeitQJJ3hlZY2ZKSwzzuqjmBH8MzbdWMgIefpmHwTkvwm6Q4CQ==", "dev": true, "requires": { - "cacache": "^13.0.1", + "cacache": "^15.0.5", "find-cache-dir": "^3.3.1", - "jest-worker": "^25.4.0", - "p-limit": "^2.3.0", + "jest-worker": "^26.1.0", + "p-limit": "^3.0.2", "schema-utils": "^2.6.6", - "serialize-javascript": "^3.0.0", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", - "terser": "^4.6.12", + "terser": "^4.8.0", "webpack-sources": "^1.4.3" }, "dependencies": { - "cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "dev": true, + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.2", "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "minipass": "^3.0.0", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^7.0.0", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -17394,22 +18591,6 @@ "path-exists": "^4.0.0" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-worker": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", - "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", - "dev": true, - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -17419,6 +18600,15 @@ "p-locate": "^4.1.0" } }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -17428,10 +18618,16 @@ "semver": "^6.0.0" } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -17444,12 +18640,23 @@ "dev": true, "requires": { "p-limit": "^2.2.0" + }, + "dependencies": { + "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-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "requires": { "aggregate-error": "^3.0.0" @@ -17470,22 +18677,14 @@ "find-up": "^4.0.0" } }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "schema-utils": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", - "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", "dev": true, "requires": { - "ajv": "^6.12.0", + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", "ajv-keywords": "^3.4.1" } }, @@ -17495,12 +18694,6 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, - "serialize-javascript": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz", - "integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -17508,34 +18701,44 @@ "dev": true }, "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", "minipass": "^3.1.1" } }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "tar": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", + "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" } }, "terser": { - "version": "4.6.13", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.13.tgz", - "integrity": "sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", "dev": true, "requires": { "commander": "^2.20.0", "source-map": "~0.6.1", "source-map-support": "~0.5.12" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, @@ -17543,7 +18746,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, "requires": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -17559,8 +18761,7 @@ "throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==" }, "through": { "version": "2.3.8", @@ -17611,20 +18812,15 @@ "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=", "dev": true }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } + "tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" }, "to-arraybuffer": { "version": "1.0.1", @@ -17635,8 +18831,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-ico": { "version": "1.1.5", @@ -17663,7 +18858,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -17672,7 +18866,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -17683,7 +18876,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -17695,7 +18887,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -17710,7 +18901,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, "requires": { "ip-regex": "^2.1.0", "psl": "^1.1.28", @@ -17718,12 +18908,11 @@ } }, "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", "requires": { - "punycode": "^2.1.0" + "punycode": "^2.1.1" } }, "tree-kill": { @@ -17738,6 +18927,29 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -17762,7 +18974,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -17770,29 +18981,26 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "^1.2.1" } }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" }, "type-is": { "version": "1.6.18", @@ -17819,11 +19027,20 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -17856,7 +19073,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -17925,7 +19141,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -17935,7 +19150,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -17946,7 +19160,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, "requires": { "isarray": "1.0.0" } @@ -17956,8 +19169,7 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" } } }, @@ -17971,7 +19183,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true + "dev": true, + "optional": true }, "update-notifier": { "version": "2.5.0", @@ -18023,7 +19236,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -18037,8 +19249,7 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, "url": { "version": "0.11.0", @@ -18059,9 +19270,9 @@ } }, "url-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.0.0.tgz", - "integrity": "sha512-sPsoBs8NkSJt9k/2zLUMDAf0rYaG00EtrFQpHRIphKrR6stGsO92LUJf/uUeQNKEoxqoJ4R4qDLqHl+AOEqolA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.0.tgz", + "integrity": "sha512-IzgAAIC8wRrg6NYkFIJY09vtktQcsvU8V6HhtQj9PTefbYImzLB1hufqo4m+RyM5N3mLx5BqJKccgxJS+W3kqw==", "dev": true, "requires": { "loader-utils": "^2.0.0", @@ -18069,6 +19280,18 @@ "schema-utils": "^2.6.5" }, "dependencies": { + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "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" + } + }, "loader-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", @@ -18081,12 +19304,13 @@ } }, "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", "dev": true, "requires": { - "ajv": "^6.12.0", + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", "ajv-keywords": "^3.4.1" } } @@ -18127,8 +19351,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "utif": { "version": "2.0.1", @@ -18187,20 +19410,18 @@ "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, "v8-to-istanbul": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.3.tgz", - "integrity": "sha512-sAjOC+Kki6aJVbUOXJbcR0MnbfjvBzwKZazEJymA2IX49uoOdEdk+4fBq5cXgYgiyKtAyrrJNtBZdOeDIF+Fng==", - "dev": true, + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz", + "integrity": "sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==", "requires": { "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^1.6.0", @@ -18210,8 +19431,7 @@ "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" } } }, @@ -18219,7 +19439,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -18241,7 +19460,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -18272,19 +19490,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, "requires": { "browser-process-hrtime": "^1.0.0" } }, "w3c-xmlserializer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", - "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", "requires": { - "domexception": "^1.0.1", - "webidl-conversions": "^4.0.2", "xml-name-validator": "^3.0.0" } }, @@ -18335,7 +19549,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, "requires": { "makeerror": "1.0.x" } @@ -18349,42 +19562,240 @@ } }, "watchpack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", - "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", + "integrity": "sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g==", "dev": true, "requires": { - "chokidar": "^2.1.8", + "chokidar": "^3.4.0", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", "dev": true, + "optional": true, "requires": { - "defaults": "^1.0.3" + "chokidar": "^2.1.8" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "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" + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "webpack": { - "version": "4.42.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.1.tgz", - "integrity": "sha512-SGfYMigqEfdGchGhFFJ9KyRpQKnipvEvjc1TwrXEPCM6H5Wywu10ka8o3KGrMzSMxMQKt8aCHUFh5DaQ9UmyRg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", + "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", "@webassemblyjs/wasm-edit": "1.9.0", "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.2.1", + "acorn": "^6.4.1", "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", @@ -18401,7 +19812,7 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.0", + "watchpack": "^1.6.1", "webpack-sources": "^1.4.1" }, "dependencies": { @@ -18520,6 +19931,15 @@ "to-regex": "^3.0.2" } }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -18527,16 +19947,16 @@ "dev": true }, "terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz", + "integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==", "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", + "serialize-javascript": "^3.1.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", @@ -18556,24 +19976,30 @@ } }, "webpack-cli": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz", - "integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", "dev": true, "requires": { - "chalk": "2.4.2", - "cross-spawn": "6.0.5", - "enhanced-resolve": "4.1.0", - "findup-sync": "3.0.0", - "global-modules": "2.0.0", - "import-local": "2.0.0", - "interpret": "1.2.0", - "loader-utils": "1.2.3", - "supports-color": "6.1.0", - "v8-compile-cache": "2.0.3", - "yargs": "13.2.4" + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -18607,29 +20033,6 @@ "wrap-ansi": "^5.1.0" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "enhanced-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", - "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", - "tapable": "^1.0.0" - } - }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -18660,32 +20063,6 @@ "resolve-cwd": "^2.0.0" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - }, "resolve-cwd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", @@ -18701,15 +20078,13 @@ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "ansi-regex": "^4.1.0" } }, "supports-color": { @@ -18721,12 +20096,6 @@ "has-flag": "^3.0.0" } }, - "v8-compile-cache": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", - "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", - "dev": true - }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -18739,22 +20108,21 @@ } }, "yargs": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", - "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.0" + "yargs-parser": "^13.1.2" } }, "yargs-parser": { @@ -18822,7 +20190,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, "requires": { "iconv-lite": "0.4.24" } @@ -18830,25 +20197,29 @@ "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" }, "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.1.0.tgz", + "integrity": "sha512-vEIkwNi9Hqt4TV9RdnaBPNt+E2Sgmo3gePebCRgZ1R7g6d23+53zCTnuB0amKI4AXq6VM8jj2DUAa0S1vjJxkw==", "requires": { "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + "tr46": "^2.0.2", + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + } } }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -18856,8 +20227,7 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "which-pm-runs": { "version": "1.0.0", @@ -18952,8 +20322,7 @@ "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" }, "worker-farm": { "version": "1.7.0", @@ -18968,7 +20337,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -18979,7 +20347,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -18989,7 +20356,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -18997,16 +20363,26 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "requires": { - "ansi-regex": "^5.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } } } @@ -19014,8 +20390,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", @@ -19030,7 +20405,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -19039,10 +20413,9 @@ } }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==", - "dev": true + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" }, "xdg-basedir": { "version": "3.0.0", @@ -19065,8 +20438,7 @@ "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, "xml-parse-from-string": { "version": "1.0.1", @@ -19093,23 +20465,13 @@ "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, "xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" }, - "xregexp": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", - "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", - "dev": true, - "requires": { - "@babel/runtime-corejs3": "^7.8.3" - } - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -19119,8 +20481,7 @@ "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, "yallist": { "version": "3.1.1", @@ -19135,10 +20496,9 @@ "dev": true }, "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -19150,24 +20510,32 @@ "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" + "yargs-parser": "^18.1.2" }, "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -19176,7 +20544,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -19184,28 +20551,37 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } } } }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "dev": true, + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, "requires": { - "fd-slicer": "~1.0.1" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } } } diff --git a/client/package.json b/client/package.json index 545ddadb..8edc8b24 100644 --- a/client/package.json +++ b/client/package.json @@ -5,12 +5,16 @@ "description": "cellxgene is a web application for the interactive exploration of single cell sequence data.", "repository": "https://github.com/chanzuckerberg/cellxgene", "scripts": { - "clean": "rimraf build", "build": "npm run clean && webpack --config", - "prod": "npm run build -- configuration/webpack/webpack.config.prod.js", + "clean": "rimraf build", "dev": "npm run build -- configuration/webpack/webpack.config.dev.js", - "fmt": "eslint --fix src", - "lint": "eslint src" + "e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js", + "e2e-annotations": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2eAnnotations.test.js", + "e2e-prod": "CXG_URL_BASE='https://cellxgene.cziscience.com/d/pbmc3k.cxg/' jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js", + "fmt": "eslint --fix src __tests__", + "lint": "eslint --fix src __tests__", + "prod": "npm run build -- configuration/webpack/webpack.config.prod.js", + "test": "jest --testPathIgnorePatterns e2e" }, "engineStrict": true, "engines": { @@ -26,9 +30,9 @@ "eslint-scope": "3.7.1" }, "dependencies": { - "@blueprintjs/core": "^3.24.0", - "@blueprintjs/icons": "^3.14.0", - "@blueprintjs/select": "^3.12.0", + "@blueprintjs/core": "^3.30.0", + "@blueprintjs/icons": "^3.19.0", + "@blueprintjs/select": "^3.13.5", "d3": "^4.10.0", "d3-scale-chromatic": "^1.5.0", "flatbuffers": "^1.11.0", @@ -37,78 +41,83 @@ "gl-matrix": "^3.3.0", "gl-vec3": "^1.1.3", "is-number": "^7.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "memoize-one": "^5.1.1", "react": "^16.13.1", + "react-async": "^10.0.1", "react-dom": "^16.13.1", - "react-flip-toolkit": "7.0.6", + "react-flip-toolkit": "^7.0.12", "react-helmet": "^5.2.1", - "react-icons": "^3.9.0", + "react-icons": "^3.10.0", "react-redux": "^7.2.0", "redux": "^4.0.5", "redux-thunk": "^2.3.0", - "regl": "^1.4.0" + "regl": "^1.6.1", + "tinyqueue": "^2.0.3" }, "devDependencies": { - "@babel/core": "^7.9.0", - "@babel/plugin-proposal-class-properties": "^7.8.3", - "@babel/plugin-proposal-decorators": "^7.8.3", - "@babel/plugin-proposal-export-namespace-from": "^7.8.3", - "@babel/plugin-proposal-function-bind": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.9.0", - "@babel/plugin-transform-react-constant-elements": "^7.9.0", - "@babel/plugin-transform-runtime": "^7.9.0", - "@babel/preset-env": "^7.9.0", - "@babel/preset-react": "^7.9.4", - "@babel/register": "^7.9.0", - "@babel/runtime": "^7.9.2", - "@sentry/webpack-plugin": "^1.11.1", + "@babel/core": "^7.10.5", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-decorators": "^7.10.5", + "@babel/plugin-proposal-export-namespace-from": "^7.10.4", + "@babel/plugin-proposal-function-bind": "^7.10.5", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@babel/plugin-transform-react-constant-elements": "^7.10.4", + "@babel/plugin-transform-runtime": "^7.10.5", + "@babel/preset-env": "^7.10.4", + "@babel/preset-react": "^7.10.4", + "@babel/register": "^7.10.5", + "@babel/runtime": "^7.10.5", + "@sentry/webpack-plugin": "^1.12.0", "babel-eslint": "^10.1.0", - "babel-jest": "^25.2.6", + "babel-jest": "^26.1.0", "babel-loader": "^8.1.0", "babel-preset-modern-browsers": "^14.2.1", - "chalk": "^4.0.0", + "chalk": "^4.1.0", "cheerio": "^1.0.0-rc.3", "clean-css": "^4.2.3", "clean-webpack-plugin": "^3.0.0", - "codecov": "^3.6.5", + "codecov": "^3.7.0", "connect-history-api-fallback": "^1.6.0", - "copy-webpack-plugin": "^5.1.1", - "css-loader": "^3.4.2", - "eslint": "^6.8.0", - "eslint-config-airbnb": "^18.0.1", - "eslint-config-prettier": "^6.10.1", + "css-loader": "^3.6.0", + "eslint": "^7.4.0", + "eslint-config-airbnb": "^18.2.0", + "eslint-config-prettier": "^6.11.0", "eslint-loader": "^3.0.4", + "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-filenames": "^1.3.2", - "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jest": "^23.8.2", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-prettier": "^3.1.3", - "eslint-plugin-react": "^7.19.0", - "eslint-plugin-react-hooks": "^2.5.1", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jest": "^23.18.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-prettier": "^3.1.4", + "eslint-plugin-react": "^7.20.3", + "eslint-plugin-react-hooks": "^4.0.8", + "expect-puppeteer": "^4.4.0", "express": "^4.17.1", "favicons-webpack-plugin": "^3.0.1", "file-loader": "^6.0.0", - "html-webpack-inline-source-plugin": "^1.0.0-beta.2", - "html-webpack-plugin": "^4.0.0", + "html-webpack-plugin": "^4.3.0", "husky": "^4.2.5", - "jest": "^25.2.7", + "jest": "^26.1.0", + "jest-circus": "^26.1.0", + "jest-environment-puppeteer": "^4.4.0", + "jest-fetch-mock": "^3.0.3", "jest-puppeteer": "^4.4.0", "json-loader": "^0.5.7", - "lint-staged": "^10.2.4", + "lint-staged": "^10.2.11", "mini-css-extract-plugin": "^0.9.0", "optimize-css-assets-webpack-plugin": "^5.0.3", "prettier": "^2.0.5", - "puppeteer": "^2.1.1", + "puppeteer": "^3.3.0", "rimraf": "^3.0.2", "serve-favicon": "^2.5.0", - "style-loader": "^1.1.3", + "style-loader": "^1.2.1", "sw-precache-webpack-plugin": "^1.0.0", - "terser-webpack-plugin": "^2.3.6", - "url-loader": "^4.0.0", - "webpack": "^4.42.1", - "webpack-cli": "^3.3.11", + "terser-webpack-plugin": "^3.0.7", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", "webpack-dev-middleware": "^3.7.2" }, "jest": { diff --git a/client/src/actions/annotation.js b/client/src/actions/annotation.js new file mode 100644 index 00000000..de89f1d1 --- /dev/null +++ b/client/src/actions/annotation.js @@ -0,0 +1,378 @@ +/* +Action creators for user annotation +*/ +import _ from "lodash"; +import * as globals from "../globals"; +import { MatrixFBS, AnnotationsHelpers } from "../util/stateManager"; + +const { isUserAnnotation } = AnnotationsHelpers; + +export const annotationCreateCategoryAction = ( + newCategoryName, + categoryToDuplicate +) => async (dispatch, getState) => { + /* + Add a new user-created category to the obs annotations. + + Arguments: + newCategoryName - string name for the category. + categoryToDuplicate - obs category to use for initial values, or null. + */ + const { + annoMatrix: prevAnnoMatrix, + obsCrossfilter: prevObsCrossfilter, + } = getState(); + if (!prevAnnoMatrix || !prevObsCrossfilter) return; + const { schema } = prevAnnoMatrix; + + /* name must be a string, non-zero length */ + if (typeof newCategoryName !== "string" || newCategoryName.length === 0) + throw new Error("user annotations require string name"); + + /* ensure the name isn't already in use! */ + if (schema.annotations.obsByName[newCategoryName]) + throw new Error("name collision on annotation category create"); + + let initialValue; + let categories; + if (categoryToDuplicate) { + /* if we are duplicating a category, retrieve it */ + const catDupSchema = schema.annotations.obsByName[categoryToDuplicate]; + const catDupType = catDupSchema?.type; + if (catDupType !== "string" && catDupType !== "categorical") + throw new Error("categoryToDuplicate does not exist or has invalid type"); + + const catToDupDf = await prevAnnoMatrix + .base() + .fetch("obs", categoryToDuplicate); + const col = catToDupDf.col(categoryToDuplicate); + initialValue = col.asArray(); + ({ categories } = col.summarize()); + // all user-created annotations must have the unassigned category + if (!categories.includes(globals.unassignedCategoryLabel)) { + categories.push(globals.unassignedCategoryLabel); + } + } else { + /* else assign to the standard default value */ + initialValue = globals.unassignedCategoryLabel; + categories = [globals.unassignedCategoryLabel]; + } + + const obsCrossfilter = prevObsCrossfilter.addObsColumn( + { + name: newCategoryName, + type: "categorical", + categories, + writable: true, + }, + Array, + initialValue + ); + + dispatch({ + type: "annotation: create category", + data: newCategoryName, + categoryToDuplicate, + annoMatrix: obsCrossfilter.annoMatrix, + obsCrossfilter, + }); +}; + +export const annotationRenameCategoryAction = ( + oldCategoryName, + newCategoryName +) => (dispatch, getState) => { + /* + Rename a user-created annotation category + */ + const { + annoMatrix: prevAnnoMatrix, + obsCrossfilter: prevObsCrossfilter, + } = getState(); + if (!prevAnnoMatrix || !prevObsCrossfilter) return; + if (!isUserAnnotation(prevAnnoMatrix, oldCategoryName)) + throw new Error("not a user annotation"); + + /* name must be a string, non-zero length */ + if (typeof newCategoryName !== "string" || newCategoryName.length === 0) + throw new Error("user annotations require string name"); + + if (oldCategoryName === newCategoryName) return; + + const obsCrossfilter = prevObsCrossfilter.renameObsColumn( + oldCategoryName, + newCategoryName + ); + + dispatch({ + type: "annotation: category edited", + annoMatrix: obsCrossfilter.annoMatrix, + obsCrossfilter, + metadataField: oldCategoryName, + newCategoryText: newCategoryName, + data: newCategoryName, + }); +}; + +export const annotationDeleteCategoryAction = (categoryName) => ( + dispatch, + getState +) => { + /* + Delete a user-created category + */ + const { + annoMatrix: prevAnnoMatrix, + obsCrossfilter: prevObsCrossfilter, + } = getState(); + if (!prevAnnoMatrix || !prevObsCrossfilter) return; + if (!isUserAnnotation(prevAnnoMatrix, categoryName)) + throw new Error("not a user annotation"); + + const obsCrossfilter = prevObsCrossfilter.dropObsColumn(categoryName); + dispatch({ + type: "annotation: delete category", + annoMatrix: obsCrossfilter.annoMatrix, + obsCrossfilter, + metadataField: categoryName, + }); +}; + +export const annotationCreateLabelInCategory = ( + categoryName, + labelName, + assignSelected +) => async (dispatch, getState) => { + /* + Add a new label to a user-defined category. If assignSelected is true, assign + the label to all currently selected cells. + */ + const { + annoMatrix: prevAnnoMatrix, + obsCrossfilter: prevObsCrossfilter, + } = getState(); + if (!prevAnnoMatrix || !prevObsCrossfilter) return; + if (!isUserAnnotation(prevAnnoMatrix, categoryName)) + throw new Error("not a user annotation"); + + let obsCrossfilter = prevObsCrossfilter.addObsAnnoCategory( + categoryName, + labelName + ); + if (assignSelected) { + obsCrossfilter = await obsCrossfilter.setObsColumnValues( + categoryName, + prevObsCrossfilter.allSelectedLabels(), + labelName + ); + } + + dispatch({ + type: "annotation: add new label to category", + annoMatrix: obsCrossfilter.annoMatrix, + obsCrossfilter, + metadataField: categoryName, + newLabelText: labelName, + assignSelectedCells: assignSelected, + }); +}; + +export const annotationDeleteLabelFromCategory = ( + categoryName, + labelName +) => async (dispatch, getState) => { + /* + delete a label from a user-defined category + */ + const { + annoMatrix: prevAnnoMatrix, + obsCrossfilter: prevObsCrossfilter, + } = getState(); + if (!prevAnnoMatrix || !prevObsCrossfilter) return; + if (!isUserAnnotation(prevAnnoMatrix, categoryName)) + throw new Error("not a user annotation"); + + const obsCrossfilter = await prevObsCrossfilter.removeObsAnnoCategory( + categoryName, + labelName, + globals.unassignedCategoryLabel + ); + + dispatch({ + type: "annotation: delete label", + metadataField: categoryName, + label: labelName, + annoMatrix: obsCrossfilter.annoMatrix, + obsCrossfilter, + }); +}; + +export const annotationRenameLabelInCategory = ( + categoryName, + oldLabelName, + newLabelName +) => async (dispatch, getState) => { + /* + label name change + */ + const { + annoMatrix: prevAnnoMatrix, + obsCrossfilter: prevObsCrossfilter, + } = getState(); + if (!prevAnnoMatrix || !prevObsCrossfilter) return; + if (!isUserAnnotation(prevAnnoMatrix, categoryName)) + throw new Error("not a user annotation"); + + let obsCrossfilter = await prevObsCrossfilter.resetObsColumnValues( + categoryName, + oldLabelName, + newLabelName + ); + obsCrossfilter = await obsCrossfilter.removeObsAnnoCategory( + categoryName, + oldLabelName, + globals.unassignedCategoryLabel + ); + + dispatch({ + type: "annotation: label edited", + editedLabel: newLabelName, + metadataField: categoryName, + label: oldLabelName, + annoMatrix: obsCrossfilter.annoMatrix, + obsCrossfilter, + }); +}; + +export const annotationLabelCurrentSelection = ( + categoryName, + labelName +) => async (dispatch, getState) => { + /* + set the label on all currently selected + */ + const { + annoMatrix: prevAnnoMatrix, + obsCrossfilter: prevObsCrossfilter, + } = getState(); + if (!prevAnnoMatrix || !prevObsCrossfilter) return; + if (!isUserAnnotation(prevAnnoMatrix, categoryName)) + throw new Error("not a user annotation"); + + const obsCrossfilter = await prevObsCrossfilter.setObsColumnValues( + categoryName, + prevObsCrossfilter.allSelectedLabels(), + labelName + ); + + dispatch({ + type: "annotation: label current cell selection", + metadataField: categoryName, + label: labelName, + obsCrossfilter, + annoMatrix: obsCrossfilter.annoMatrix, + }); +}; + +function writableAnnotations(annoMatrix) { + return annoMatrix.schema.annotations.obs.columns + .filter((s) => s.writable) + .map((s) => s.name); +} + +export const needToSaveObsAnnotations = (annoMatrix, lastSavedAnnoMatrix) => { + /* + Return true if there are LIKELY user-defined annotation modifications between the two + annoMatrices. Technically not an action creator, but intimately intertwined + with the save process. + + Two conditions will trigger a need to save: + * the collection of user-defined columns have changed + * the contents of the user-defined columns have change + */ + + annoMatrix = annoMatrix.base(); + + // if the annoMatrix hasn't changed, we are guaranteed no changes to the matrix schema or contents. + if (annoMatrix === lastSavedAnnoMatrix) return false; + + // if the schema has changed, we need to save + const currentWritable = writableAnnotations(annoMatrix); + if (_.difference(currentWritable, writableAnnotations(lastSavedAnnoMatrix))) { + return true; + } + + // no schema changes; check for change in contents + return currentWritable.some( + (col) => annoMatrix.col(col) !== lastSavedAnnoMatrix.col(col) + ); +}; + +export const saveObsAnnotationsAction = () => async (dispatch, getState) => { + /* + Save the user-created obs annotations IF any have changed. + */ + const state = getState(); + const { annotations, autosave } = state; + const { dataCollectionNameIsReadOnly, dataCollectionName } = annotations; + const { lastSavedAnnoMatrix, saveInProgress } = autosave; + + const annoMatrix = state.annoMatrix.base(); + + if (saveInProgress || annoMatrix === lastSavedAnnoMatrix) return; + if (!needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix)) { + dispatch({ + type: "writable obs annotations - save complete", + lastSavedAnnoMatrix: annoMatrix, + }); + return; + } + + /* + Else, we really do need to save + */ + + dispatch({ + type: "writable obs annotations - save started", + }); + + const df = await annoMatrix.fetch("obs", writableAnnotations(annoMatrix)); + const matrix = MatrixFBS.encodeMatrixFBS(df); + try { + const queryString = + !dataCollectionNameIsReadOnly && !!dataCollectionName + ? `?annotation-collection-name=${encodeURIComponent( + dataCollectionName + )}` + : ""; + const res = await fetch( + `${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`, + { + method: "PUT", + body: matrix, + headers: new Headers({ + "Content-Type": "application/octet-stream", + }), + credentials: "include", + } + ); + if (res.ok) { + dispatch({ + type: "writable obs annotations - save complete", + lastSavedAnnoMatrix: annoMatrix, + }); + } else { + dispatch({ + type: "writable obs annotations - save error", + message: `HTTP error ${res.status} - ${res.statusText}`, + res, + }); + } + } catch (error) { + dispatch({ + type: "writable obs annotations - save error", + message: error.toString(), + error, + }); + } +}; diff --git a/client/src/actions/index.js b/client/src/actions/index.js index ada975d3..2ae586f4 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -1,93 +1,17 @@ import * as globals from "../globals"; -import { Universe, MatrixFBS } from "../util/stateManager"; -import * as Dataframe from "../util/dataframe"; +import { AnnoMatrixLoader, AnnoMatrixObsCrossfilter } from "../annoMatrix"; import { catchErrorsWrap, doJsonRequest, - doBinaryRequest, dispatchNetworkErrorMessageToUser, } from "../util/actionHelpers"; -import PromiseLimit from "../util/promiseLimit"; -import { requestReembed, reembedResetWorldToUniverse } from "./reembed"; +import { + requestReembed /* , reembedResetWorldToUniverse -- disabled temporarily, TODO issue #1606 */, +} from "./reembed"; import { loadUserColorConfig } from "../util/stateManager/colorHelpers"; - -/* -return promise to fetch the OBS annotations we need to load. Omit anything -we don't need. -*/ -async function obsAnnotationFetchAndLoad(dispatch, schema) { - const obsAnnotations = schema?.schema?.annotations?.obs ?? {}; - const index = obsAnnotations.index ?? false; - const columns = (obsAnnotations.columns ?? []).filter( - (col) => col.name !== index - ); - - const plimit = new PromiseLimit(5); - return Promise.all( - columns.map((col) => - plimit.add(() => - fetchBinary( - `annotations/obs?annotation-name=${encodeURIComponent(col.name)}` - ) - .then((buffer) => MatrixFBS.matrixFBSToDataframe(buffer)) - .then((df) => - dispatch({ - type: "universe: column load success", - dim: "obsAnnotations", - dataframe: df, - }) - ) - ) - ) - ); -} - -/* -return promise fetching VAR annotations we need to load. Only index is currently used. -*/ -async function varAnnotationFetchAndLoad(dispatch, schema) { - const varAnnotations = schema?.schema?.annotations?.var ?? {}; - const index = varAnnotations.index ?? false; - const names = index ? [index] : []; - return Promise.all( - names.map((name) => - fetchBinary(`annotations/var?annotation-name=${encodeURIComponent(name)}`) - .then((buffer) => MatrixFBS.matrixFBSToDataframe(buffer)) - .then((df) => - dispatch({ - type: "universe: column load success", - dim: "varAnnotations", - dataframe: df, - }) - ) - ) - ); -} - -/* -return promise fetching layout we need -*/ -function layoutFetchAndLoad(dispatch, schema) { - const embeddings = schema?.schema?.layout?.obs ?? []; - const embNames = embeddings.map((e) => e.name); - - const plimit = new PromiseLimit(5); - return Promise.all( - embNames.map((e) => - plimit.add(() => - fetchBinary( - `layout/obs?layout-name=${encodeURIComponent(e)}` - ).then((buffer) => MatrixFBS.matrixFBSToDataframe(buffer)) - ) - ) - ).then((dfs) => - dispatch({ - type: "universe: column load success", - dim: "obsLayout", - dataframe: Dataframe.Dataframe.empty().withColsFromAll(dfs), - }) - ); -} +import * as selnActions from "./selection"; +import * as annoActions from "./annotation"; +import * as viewActions from "./viewStack"; /* return promise fetching user-configured colors @@ -101,178 +25,62 @@ async function userColorsFetchAndLoad(dispatch) { ); } +async function schemaFetch() { + return fetchJson("schema"); +} + +async function configFetch(dispatch) { + return fetchJson("config").then((response) => { + const config = { ...globals.configDefaults, ...response.config }; + dispatch({ + type: "configuration load complete", + config, + }); + return config; + }); +} + /* -Bootstrap application with the initial data loading. - * /config - application configuration - * /schema - schema of dataframe - * /annotations - all metadata annotation - * /layout - all default layout +Application bootstrap */ const doInitialDataLoad = () => catchErrorsWrap(async (dispatch) => { dispatch({ type: "initial data load start" }); try { - /* - Step 1 - config & schema, all JSON - */ - const requestJson = ["config", "schema"].map(fetchJson); - const [responseConfig, schema] = await Promise.all(requestJson); - /* set config defaults */ - const config = { ...globals.configDefaults, ...responseConfig.config }; - const universe = Universe.createUniverseFromResponse(config, schema); - dispatch({ - type: "universe exists, but loading is still in progress", - universe, - }); - dispatch({ - type: "configuration load complete", - config, - }); - - /* - Step 2 - load the minimum stuff required to display. - */ - await Promise.all([ + const [, schema] = await Promise.all([ + configFetch(dispatch), + schemaFetch(dispatch), userColorsFetchAndLoad(dispatch), - layoutFetchAndLoad(dispatch, schema), - varAnnotationFetchAndLoad(dispatch, schema), ]); - /* - Step 3 - load everything else - */ - await obsAnnotationFetchAndLoad(dispatch, schema); - + const baseDataUrl = `${globals.API.prefix}${globals.API.version}`; + const annoMatrix = new AnnoMatrixLoader(baseDataUrl, schema.schema); + const obsCrossfilter = new AnnoMatrixObsCrossfilter(annoMatrix); dispatch({ - type: "initial data load complete (universe exists)", - universe, + type: "annoMatrix: init complete", + annoMatrix, + obsCrossfilter, }); + dispatch({ type: "initial data load complete" }); } catch (error) { dispatch({ type: "initial data load error", error }); } }, true); -/* -Set the view (world) to current selection. Placeholder for an async action -which also does re-layout. -*/ -const setWorldToSelection = () => (dispatch, getState) => { - const { universe, world, crossfilter } = getState(); - dispatch({ - type: "set World to current selection", - universe, - world, - crossfilter, - }); -}; - -/* double URI encode - needed for query-param filters */ -function dubEncURIComponent(s) { - return encodeURIComponent(encodeURIComponent(s)); -} - -/* -Fetch expression vectors for each gene in genes. This is NOT an action -function, but rather a helper to be called from an action helper that -needs expression data. - -Transparently utilizes cached data if it is already present. -*/ -async function _doRequestExpressionData(dispatch, getState, genes) { - const state = getState(); - const { universe } = state; - const varIndexName = universe.schema.annotations.var.index; - - /* helper for this function only */ - const fetchData = async (geneNames) => { - const query = geneNames - .map( - (g) => - `var:${dubEncURIComponent(varIndexName)}=${dubEncURIComponent(g)}` - ) - .join("&"); - // TODO: why convert to an Object and not a Dataframe? - return fetchBinary(`data/var?${query}`).then((buffer) => - Universe.convertDataFBStoObject(universe, buffer) - ); - }; - - /* preload data already in cache */ - let expressionData = genes.reduce((acc, g) => { - const data = universe.varData.col(g); - if (data) { - acc[g] = data.asArray(); - } - return acc; - }, {}); // --> { gene: data } - - /* make a list of genes for which we do not have data */ - const genesToFetch = genes.filter((g) => expressionData[g] === undefined); - - dispatch({ type: "expression load start" }); - - /* Fetch data for any genes not in cache */ - if (genesToFetch.length) { - try { - const newExpressionData = await fetchData(genesToFetch); - expressionData = { - ...expressionData, - ...newExpressionData, - }; - } catch (error) { - dispatch({ type: "expression load error", error }); - throw error; // rethrow - } - } - - dispatch({ type: "expression load success", expressionData }); - return expressionData; -} - function requestSingleGeneExpressionCountsForColoringPOST(gene) { - return async (dispatch, getState) => { - dispatch({ type: "get single gene expression for coloring started" }); - try { - await _doRequestExpressionData(dispatch, getState, [gene]); - const { world } = getState(); - dispatch({ - type: "color by expression", - gene, - data: { - [gene]: world.varData.col(gene).asArray(), - }, - }); - } catch (error) { - dispatch({ - type: "get single gene expression for coloring error", - error, - }); - } + return { + type: "color by expression", + gene, }; } -const requestUserDefinedGene = (gene) => async (dispatch, getState) => { - dispatch({ type: "request user defined gene started" }); - try { - await await _doRequestExpressionData(dispatch, getState, [gene]); - const { world } = getState(); - - /* then send the success case action through */ - return dispatch({ - type: "request user defined gene success", - data: { - genes: [gene], - expression: world.varData.col(gene).asArray(), - }, - }); - } catch (error) { - return dispatch({ - type: "request user defined gene error", - error, - }); - } -}; +const requestUserDefinedGene = (gene) => ({ + type: "request user defined gene success", + data: { + genes: [gene], + }, +}); const dispatchDiffExpErrors = (dispatch, response) => { switch (response.status) { @@ -308,9 +116,8 @@ const requestDifferentialExpression = (set1, set2, num_genes = 10) => async ( 1. get the most differentially expressed genes 2. get expression data for each */ - const state = getState(); - const { universe } = state; - const varIndexName = universe.schema.annotations.var.index; + const { annoMatrix } = getState(); + const varIndexName = annoMatrix.schema.annotations.var.index; // Legal values are null, Array or TypedArray. Null is initial state. if (!set1) set1 = []; @@ -345,22 +152,12 @@ const requestDifferentialExpression = (set1, set2, num_genes = 10) => async ( return dispatchDiffExpErrors(dispatch, res); } - const data = await res.json(); - // result is [ [varIdx, ...], ... ] - const topNGenes = data.map((r) => - universe.varAnnotations.at(r[0], varIndexName) - ); - - /* - Kick off secondary action to fetch all of the expression data for the - topN expressed genes. - */ - const plimit = new PromiseLimit(5); - await Promise.all( - topNGenes.map((gene) => - plimit.add(() => _doRequestExpressionData(dispatch, getState, [gene])) - ) - ); + const response = await res.json(); + const varIndex = await annoMatrix.fetch("var", varIndexName); + const data = response.map((v) => [ + varIndex.at(v[0], varIndexName), + ...v.slice(1), + ]); /* then send the success case action through */ return dispatch({ @@ -375,87 +172,44 @@ const requestDifferentialExpression = (set1, set2, num_genes = 10) => async ( } }; -const resetWorldToUniverse = () => (dispatch, getState) => { - const { universe } = getState(); - reembedResetWorldToUniverse(dispatch, getState); - dispatch({ - type: "reset World to eq Universe", - universe, - }); -}; - -const saveObsAnnotations = () => async (dispatch, getState) => { - const { universe, annotations } = getState(); - const { obsAnnotations, schema } = universe; - const { dataCollectionNameIsReadOnly, dataCollectionName } = annotations; - - dispatch({ - type: "writable obs annotations - save started", - }); - - const writableAnnotations = schema.annotations.obs.columns - .filter((s) => s.writable) - .map((s) => s.name); - const df = obsAnnotations.subset(null, writableAnnotations); - const matrix = MatrixFBS.encodeMatrixFBS(df); - try { - const queryString = - !dataCollectionNameIsReadOnly && !!dataCollectionName - ? `?annotation-collection-name=${encodeURIComponent( - dataCollectionName - )}` - : ""; - const res = await fetch( - `${globals.API.prefix}${globals.API.version}annotations/obs${queryString}`, - { - method: "PUT", - body: matrix, - headers: new Headers({ - "Content-Type": "application/octet-stream", - }), - credentials: "include", - } - ); - if (res.ok) { - dispatch({ - type: "writable obs annotations - save complete", - obsAnnotations, - }); - } else { - dispatch({ - type: "writable obs annotations - save error", - message: `HTTP error ${res.status} - ${res.statusText}`, - res, - }); - } - } catch (error) { - dispatch({ - type: "writable obs annotations - save error", - message: error.toString(), - error, - }); - } -}; - function fetchJson(pathAndQuery) { return doJsonRequest( `${globals.API.prefix}${globals.API.version}${pathAndQuery}` ); } -function fetchBinary(pathAndQuery) { - return doBinaryRequest( - `${globals.API.prefix}${globals.API.version}${pathAndQuery}` - ); -} - export default { doInitialDataLoad, requestDifferentialExpression, requestSingleGeneExpressionCountsForColoringPOST, requestUserDefinedGene, requestReembed, - resetWorldToUniverse, - saveObsAnnotations, - setWorldToSelection, + selectContinuousMetadataAction: selnActions.selectContinuousMetadataAction, + selectCategoricalMetadataAction: selnActions.selectCategoricalMetadataAction, + selectCategoricalAllMetadataAction: + selnActions.selectCategoricalAllMetadataAction, + graphBrushStartAction: selnActions.graphBrushStartAction, + graphBrushChangeAction: selnActions.graphBrushChangeAction, + graphBrushDeselectAction: selnActions.graphBrushDeselectAction, + graphBrushCancelAction: selnActions.graphBrushCancelAction, + graphBrushEndAction: selnActions.graphBrushEndAction, + graphLassoStartAction: selnActions.graphLassoStartAction, + graphLassoEndAction: selnActions.graphLassoEndAction, + graphLassoCancelAction: selnActions.graphLassoCancelAction, + graphLassoDeselectAction: selnActions.graphLassoDeselectAction, + clipAction: viewActions.clipAction, + subsetAction: viewActions.subsetAction, + resetSubsetAction: viewActions.resetSubsetAction, + annotationCreateCategoryAction: annoActions.annotationCreateCategoryAction, + annotationRenameCategoryAction: annoActions.annotationRenameCategoryAction, + annotationDeleteCategoryAction: annoActions.annotationDeleteCategoryAction, + annotationCreateLabelInCategory: annoActions.annotationCreateLabelInCategory, + annotationDeleteLabelFromCategory: + annoActions.annotationDeleteLabelFromCategory, + annotationRenameLabelInCategory: annoActions.annotationRenameLabelInCategory, + annotationLabelCurrentSelection: annoActions.annotationLabelCurrentSelection, + saveObsAnnotationsAction: annoActions.saveObsAnnotationsAction, + needToSaveObsAnnotations: annoActions.needToSaveObsAnnotations, + layoutChoiceAction: selnActions.layoutChoiceAction, + setCellSetFromSelection: selnActions.setCellSetFromSelection, }; diff --git a/client/src/actions/reembed.js b/client/src/actions/reembed.js index 7d73337e..43b38e1f 100644 --- a/client/src/actions/reembed.js +++ b/client/src/actions/reembed.js @@ -104,6 +104,7 @@ export function requestReembed() { }; } +/* disabled until reimplementation occurs export function reembedResetWorldToUniverse(dispatch, getState) { const { reembedController } = getState(); if (reembedController.pendingFetch) reembedController.pendingFetch.abort(); @@ -111,3 +112,4 @@ export function reembedResetWorldToUniverse(dispatch, getState) { type: "reembed: clear all reembeddings", }); } +*/ diff --git a/client/src/actions/selection.js b/client/src/actions/selection.js new file mode 100644 index 00000000..8afdad8f --- /dev/null +++ b/client/src/actions/selection.js @@ -0,0 +1,224 @@ +/* +Action creators for selection +*/ +export const selectContinuousMetadataAction = ( + type, + query, + range, + oldProps = {} +) => async (dispatch, getState) => { + const { obsCrossfilter: prevObsCrossfilter } = getState(); + + const selection = range + ? { + mode: "range", + lo: range[0], + hi: range[1], + inclusive: true, // [lo, hi] incluisve selection + } + : { mode: "all" }; + + const obsCrossfilter = await prevObsCrossfilter.select(...query, selection); + + dispatch({ + type, + obsCrossfilter, + range, + ...oldProps, + }); +}; + +export const selectCategoricalMetadataAction = ( + type, // action type + metadataField, // annotation category name + labels, + label, // the label being selected/deselected + isSelected, // bool + oldProps = {} +) => async (dispatch, getState) => { + const { + obsCrossfilter: prevObsCrossfilter, + categoricalSelection, + } = getState(); + + const labelSelectionState = new Map(categoricalSelection[metadataField]); + labels.forEach( + (l) => labelSelectionState.has(l) || labelSelectionState.set(l, true) + ); + labelSelectionState.set(label, isSelected); + + const values = Array.from(labelSelectionState.keys()).filter((k) => + labelSelectionState.get(k) + ); + const selection = { + mode: "exact", + values, + }; + const obsCrossfilter = await prevObsCrossfilter.select( + "obs", + metadataField, + selection + ); + + dispatch({ + type, + obsCrossfilter, + metadataField, + labelSelectionState, + ...oldProps, + }); +}; + +export const selectCategoricalAllMetadataAction = ( + type, // action type + metadataField, // annotation category name + labels, + isSelected, // bool, select all or none + oldProps = {} +) => async (dispatch, getState) => { + const { + obsCrossfilter: prevObsCrossfilter, + categoricalSelection, + } = getState(); + + const labelSelectionState = new Map(categoricalSelection[metadataField]); + labels.forEach((label) => labelSelectionState.set(label, isSelected)); + + const selection = { mode: isSelected ? "all" : "none" }; + const obsCrossfilter = await prevObsCrossfilter.select( + "obs", + metadataField, + selection + ); + + dispatch({ + type, + obsCrossfilter, + metadataField, + labelSelectionState, + ...oldProps, + }); +}; + +/** + ** Graph selection-related actions + **/ + +export const graphBrushStartAction = () => + /* no change to crossfilter until a change fires */ + ({ type: "graph brush start" }); + +const _graphBrushWithinRectAction = (type, embName, brushCoords) => async ( + dispatch, + getState +) => { + const { obsCrossfilter: prevObsCrossfilter } = getState(); + + const selection = { mode: "within-rect", ...brushCoords }; + const obsCrossfilter = await prevObsCrossfilter.select( + "emb", + embName, + selection + ); + + dispatch({ + type, + obsCrossfilter, + brushCoords, + }); +}; + +const _graphAllAction = (type, embName) => async (dispatch, getState) => { + const { obsCrossfilter: prevObsCrossfilter } = getState(); + + const obsCrossfilter = await prevObsCrossfilter.select("emb", embName, { + mode: "all", + }); + + dispatch({ + type, + obsCrossfilter, + }); +}; + +export const graphBrushChangeAction = (embName, brushCoords) => + _graphBrushWithinRectAction("graph brush change", embName, brushCoords); + +export const graphBrushEndAction = (embName, brushCoords) => + _graphBrushWithinRectAction("graph brush end", embName, brushCoords); + +export const graphBrushCancelAction = (embName) => + _graphAllAction("graph brush cancel", embName); +export const graphBrushDeselectAction = (embName) => + _graphAllAction("graph brush deselect", embName); + +export const graphLassoStartAction = () => + /* no change to crossfilter until a change fires */ + ({ type: "graph lasso start" }); + +export const graphLassoCancelAction = (embName) => + _graphAllAction("graph lasso cancel", embName); + +export const graphLassoDeselectAction = (embName) => + _graphAllAction("graph lasso cancel", embName); + +export const graphLassoEndAction = (embName, polygon) => async ( + dispatch, + getState +) => { + const { obsCrossfilter: prevObsCrossfilter } = getState(); + + const selection = { + mode: "within-polygon", + polygon, + }; + const obsCrossfilter = await prevObsCrossfilter.select( + "emb", + embName, + selection + ); + + dispatch({ + type: "graph lasso end", + obsCrossfilter, + polygon, + }); +}; + +export const layoutChoiceAction = (newLayoutChoice) => async ( + dispatch, + getState +) => { + /* + On layout choice, make sure we have selected all on the previous layout, AND the new + layout. + */ + const { obsCrossfilter: prevObsCrossfilter, layoutChoice } = getState(); + + let obsCrossfilter = await prevObsCrossfilter.select( + "emb", + layoutChoice.current, + { mode: "all" } + ); + obsCrossfilter = await obsCrossfilter.select("emb", newLayoutChoice, { + mode: "all", + }); + dispatch({ + type: "set layout choice", + layoutChoice: newLayoutChoice, + obsCrossfilter, + }); +}; + +/* +Differential expression set selection +*/ +export const setCellSetFromSelection = (cellSetId) => (dispatch, getState) => { + const { obsCrossfilter } = getState(); + const selected = obsCrossfilter.allSelectedLabels(); + + dispatch({ + type: `store current cell selection as differential set ${cellSetId}`, + data: selected.length > 0 ? selected : null, + }); +}; diff --git a/client/src/actions/viewStack.js b/client/src/actions/viewStack.js new file mode 100644 index 00000000..cc297a82 --- /dev/null +++ b/client/src/actions/viewStack.js @@ -0,0 +1,100 @@ +/* +The following actions manage the view stack for annoMatrix. + +Conventions used and assumed elsewhere in the code base: + * there will be zero or one clip view, and it will be the TOP view always. + * there will be zero or more subset views + +In other words, in our current use, we do not stack multiple clip views but we do +stack multiple subsets. + +If these conventions change, code elsewhere (eg. menubar/clip.js) will need to +change as well. +*/ +import { AnnoMatrixObsCrossfilter, clip, isubsetMask } from "../annoMatrix"; + +export const clipAction = (min, max) => (dispatch, getState) => { + /* + apply a clip to the current annoMatrix. By convention, the clip + view is ALWAYS the top view. + */ + const { annoMatrix: prevAnnoMatrix } = getState(); + const annoMatrix = prevAnnoMatrix.isClipped + ? clip(prevAnnoMatrix.viewOf, min, max) + : clip(prevAnnoMatrix, min, max); + const obsCrossfilter = new AnnoMatrixObsCrossfilter(annoMatrix); + dispatch({ + type: "set clip quantiles", + clipQuantiles: { min, max }, + annoMatrix, + obsCrossfilter, + }); +}; + +export const subsetAction = () => (dispatch, getState) => { + /* + Subset the annoMatrix to the current crossfilter selection by pushing a + subset view. + + By convention, a clip view is ALWAYS the top view, so if present, pop + off and re-apply + */ + const { + annoMatrix: prevAnnoMatrix, + obsCrossfilter: prevObsCrossfilter, + } = getState(); + + let annoMatrix; + if (prevAnnoMatrix.isClipped) { + // if there is a clip view, pop it and reapply after we subset + const { clipRange } = prevAnnoMatrix; + annoMatrix = isubsetMask( + prevAnnoMatrix.viewOf, + prevObsCrossfilter.allSelectedMask() + ); + annoMatrix = clip(annoMatrix, ...clipRange); + } else { + // else just push a subset view. + annoMatrix = isubsetMask( + prevAnnoMatrix, + prevObsCrossfilter.allSelectedMask() + ); + } + + const obsCrossfilter = new AnnoMatrixObsCrossfilter(annoMatrix); + dispatch({ + type: "subset to selection", + annoMatrix, + obsCrossfilter, + }); +}; + +export const resetSubsetAction = () => (dispatch, getState) => { + /* + Reset the annoMatrix to all data. Because we may have multiple views + stacked, we pop them all. By convention, any clip transformation will + be the top of the stack, and must be preserved. + */ + + const { annoMatrix: prevAnnoMatrix } = getState(); + + const clipRange = prevAnnoMatrix.isClipped ? prevAnnoMatrix.clipRange : null; + + /* pop all views */ + let annoMatrix = prevAnnoMatrix; + while (annoMatrix.isView) { + annoMatrix = annoMatrix.viewOf; + } + + /* re-apply the clip, if any */ + if (clipRange !== null) { + annoMatrix = clip(annoMatrix, ...clipRange); + } + + const obsCrossfilter = new AnnoMatrixObsCrossfilter(annoMatrix); + dispatch({ + type: "reset subset", + annoMatrix, + obsCrossfilter, + }); +}; diff --git a/client/src/annoMatrix/annoMatrix.js b/client/src/annoMatrix/annoMatrix.js new file mode 100644 index 00000000..1b24a6bb --- /dev/null +++ b/client/src/annoMatrix/annoMatrix.js @@ -0,0 +1,642 @@ +import { + Dataframe, + IdentityInt32Index, + dataframeMemo, +} from "../util/dataframe"; +import { + _getColumnDimensionNames, + _getColumnSchema, + _schemaColumns, + _getWritableColumns, +} from "./schema"; +import { indexEntireSchema } from "../util/stateManager/schemaHelpers"; +import { _whereCacheGet, _whereCacheMerge } from "./whereCache"; +import _shallowClone from "./clone"; + +const _dataframeCache = dataframeMemo(128); + +export default class AnnoMatrix { + /* + Abstract base class for all AnnoMatrix objects. This class provides a proxy + to the annotated matrix data authoritatively served by the server/back-end. + + AnnoMatrix instances are immutable, meaning that their schema and dimensionality + will not change, and simple object equality can be used to detect structural + changes. The actual data is cached, and not guaranteed to be present -- any + request to access data must be resolved by a fetch() call, which is async, and + may involve a server round-trip. + + Guarantees made by the immutabilty, ie, any of these can be detected by + simple annoMatrix compare: + * schema is the same, including all fields and columns + * dimensionality is the same (nObs, nVar) + * data mapping/transformation, such as clipping, are the same + + AnnoMatrixes also "stack" like filters, allowing for the construction of + views which transform the data in some manner. + + The bootstrap class is AnnoMatrixLoader, which is the caching server proxy, and + is bootstrapped with a API URL: + new AnnoMatirx(url, schema) -> annoMatrix + + There are various "views", such as AnnoMatrixRowSubsetView, which provide + the same interface but with a transformed view of the server data. Utilities in + viewCreators.js can be used to create these views: + clip(annoMatrix, min, max) -> annoMatrix + subset(annoMatrix, rowLabels) -> annoMatrix + etc. + */ + static fields() { + /* + return the fields present in the AnnoMatrix instance. + */ + return ["obs", "var", "emb", "X"]; + } + + constructor(schema, nObs, nVar, rowIndex = null) { + /* + Private constructor - this is an abstract base class. Do not use. + */ + + /* + Public instance fields: + * schema - the matrix schema. IMPORTANT: always the entire schema, for the + base (unfiltered, unclipped, unsubset) annotated matrix, as the server + presents it. + * nObs, nVar - size of each dimension. These will accurately reflect the + size of the current annoMatrix view. For example, if you subset the view, + the nObs will be smaller. + * rowIndex - a rowIndex shared by all data on this view (ie, the list of cells). + The row index labels are as defined by the base dataset from the server. + * isView - true if this is a view, false if not. + * viewOf - pointer to parent annomatrix if a view, undefined/null if not a view. + */ + this.schema = indexEntireSchema(schema); + this.nObs = nObs; + this.nVar = nVar; + this.rowIndex = rowIndex || new IdentityInt32Index(nObs); + this.isView = false; + this.viewOf = undefined; + + /* + Private instance variables. + + These are caches - lazily loaded. The only guarantee is that if they + are loaded, they will conform to the schema & dimensionality constraints. + + Do NOT use directly - instead, use the fetch() and preload() API. + */ + this._cache = { + obs: Dataframe.empty(this.rowIndex), + var: Dataframe.empty(this.rowIndex), + emb: Dataframe.empty(this.rowIndex), + X: Dataframe.empty(this.rowIndex), + }; + this._pendingLoad = { + obs: {}, + var: {}, + emb: {}, + X: {}, + }; + this._whereCache = {}; + this._gcInfo = new Map(); + } + + /** + ** Schema helper/accessors + **/ + getMatrixColumns(field) { + /* + Return array of column names in the field. ONLY supported on the + obs, var and emb fields. X currently unimplemented and will throw. + + For exmaple: + + annoMatrix.getMatrixColumns("obs") -> ["louvain", "n_genes"] + */ + return _schemaColumns(this.schema, field); + } + + // eslint-disable-next-line class-methods-use-this -- need to be able to call this on instances + getMatrixFields() { + /* + Return array of fields in this annoMatrix. Currently hard-wired to + return: ["X", "obs", "var", "emb"]. + + These are the fields from data may be requested. + */ + return AnnoMatrix.fields(); + } + + getColumnSchema(field, col) { + /* + Return the schema for the field & column ,eg, + + anonMatrix.getColumnSchema("obs", "n_genes") -> { type: "int32", name: "n_genes" } + + This is identical to the information in the annoMatrix.schema + instance variable. + */ + return _getColumnSchema(this.schema, field, col); + } + + getColumnDimensions(field, col) { + /* + Return the dimensions on this field / column. For most fields, which are 1D, + this just return the column name. Multi-dimensional columns, such as embeddings, + will return >1 name. + + Examples: + + getColumnDimensions("obs", "louvain") -> ["louvain"] + getColumnDimensions("emb", "umap") -> ["umap_0", "umap_1"] + + */ + return _getColumnDimensionNames(this.schema, field, col); + } + + /** + ** General utility methods + **/ + base() { + /* + return the base of view, or `this` if not a view. + */ + let annoMatrix = this; + while (annoMatrix.isView) annoMatrix = annoMatrix.viewOf; + return annoMatrix; + } + + /** + ** Load / read interfaces + **/ + fetch(field, q) { + /* + Return the given query on a single matrix field as a single dataframe. + Currently supports ONLY full column query. + + Returns a Promise for the query result, which will resolve to a dataframe. + + Field must be one of the matrix fields: 'obs', 'var', 'X', 'emb'. Value + represents the underlying object upon which the query is occuring. + + Query is one of: + * a string, representing a single column name from the field, eg, + "n_genes" + * an object, containing an "value" query (see below). + * an array, containing one or more of the above. + + Columns may have more than one dimension, and all will be fetched + and returned together. This is most commonly seen in an embedding, + which usually has two dimensions. + + A value query allows for fetching based upon the value in another + field/column, similar to a join. Currently only supported on the var + dimension, allowing query of X columns by var value (eg, gene name) + + The query filter is a single value filter: + { "field name": [ + {name: "column name", values: [ list of values ]} + ]} + One and only one value filter is allowed in a value query. + + Examples: + + 1. Fetch the "n_genes" column the "obs": + + const df = await fetch("obs", "n_genes") + console.log("Largest number of genes is: ", df.summarize().max); + + 2. Fetch two separate columns from obs. Returns a single dataframe containing + the columns: + + const df = await fetch("obs", ["n_genes", "louvain"]) + console.log("Cell 0 has category: ", df.at(0, "louvain")); + + 3. Fetch an entire X (expression counts) column that has a var annotation + value "TYMP" in the var index. + + fetch("X", { + where: {field: "var", column: this.schema.annotations.var.index, value: "TYMP"} + }) + + In AnnData & Pandas DataFrame API, this is equivalent to: + adata.X[:, adata.var.index.get_loc("SUMO3")] + + The value query is a recodification and subset of the server REST API + value filter JSON. Range queries and multiple filters are not currently + supported. + + */ + return this._fetch(field, q); + } + + prefetch(field, q) { + /* + Start a data fetch & cache fill. Identical to fetch() except it does + not return a value. + + Primary use is to being a cache load as early as is possible, reducing + overall component rendering latency. + */ + this._fetch(field, q); + return undefined; + } + + /** + ** Save / mutate interfaces - manipulation of "writable" OBS annotations. + ** + ** These are all present to support client-side creation of OBS annotations, aka + ** "user annotations". + ** + ** They implement common manipulations to the AnnoMatrix, maintaining the + ** norma guarantees around correctness of public API, eg, + ** - schema will be correct, including the "writable" attribute + ** - fetch() will return the latest data, even from views + ** - immutability guranteeds + ** + ** As most of these interfaces mutate the annoMatrix, they return a new + ** annoMatrix + ** + ** The actual implementation is in the sub-classes, which MUST override these. + **/ + + // eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements + addObsAnnoCategory(col, category) { + /* + Add a new category value (aka "label") to a writable obs column, and return the new AnnoMatrix. + Typical use is to add a new user-created label to a user-created obs categorical + annotation. + + Will throw column does not exist or is not writable. + + Example: + + addObsAnnoCategory("my cell type", "left toenail") -> AnnoMatrix + + */ + _subclassResponsibility(); + } + + // eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements + async removeObsAnnoCategory(col, category, unassignedCategory) { + /* + Remove a category value from an obs column, reassign any obs having that value + to the 'unassignedCategory' value, and return a promise for a new AnnoMatrix. + Typical use is to remove a user-created label from a user-created obs categorical + annotation. + + Will throw column does not exist or is not writable. + + An `unassignedCategory` value must be provided, for assignment to any obs/cells + that had the now-delete category label as their value. + + Example: + await removeObsAnnoCategory("my-tissue-type", "right earlobe", "unassigned") -> AnnoMatrix + + NOTE: method is async as it may need to fetch data to provide the reassignment. + */ + _subclassResponsibility(); + } + + // eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements + dropObsColumn(col) { + /* + Drop an entire writable column, eg a user-created obs annotation. Typical use + is to provide the "Delete Category" implementation. Returns the new AnnoMatrix. + Will throw if not a writable annotation. + + Will throw column does not exist or is not writable. + + Example: + + dropObsColumn("old annotations") -> AnnoMatrix + */ + _subclassResponsibility(); + } + + // eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements + addObsColumn(colSchema, Ctor, value) { + /* + Add a new writable OBS annotation column, with the caller-specified schema, initial value + type and value. + + Value may be any one of: + * an array of values + * a primitive type, including null or undefined. + If an array, length must be the same as 'this.nObs', and constructor must equal 'Ctor'. + If a primitive, 'Ctor' will be used to create the initial value, which will be filled + with 'value'. + + Throws if the name specified in 'colSchema' duplicates an existing obs column. + + Returns a new AnnoMatrix. + + Examples: + + addObsColumn( + { name: "foo", type: "categorical", categories: "unassigned" }, + Array, + "unassigned" + ) -> AnnoMatrix + + */ + _subclassResponsibility(); + } + + // eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements + renameObsColumn(oldCol, newCol) { + /* + Rename the obs column 'oldCol' to have name 'newCol' and returns new AnnoMatrix. + + Will throw column does not exist or is not writable, or if 'newCol' is not unique. + + Example: + + renameObsColumn('cell type', 'old cell type') -> AnnoMatrix. + + */ + _subclassResponsibility(); + } + + // eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements + async setObsColumnValues(col, obsLabels, value) { + /* + Set all obs with label in array 'obsLabels' to have 'value'. Typical use would be + to set a group of cells to have a label on a user-created categorical anntoation + (eg set all selected cells to have a label). + + NOTE: async method, as it may need to fetch. + + Will throw column does not exist or is not writable. + + Example: + await setObsColmnValues("flavor", [383, 400], "tasty") -> AnnoMtarix + + */ + _subclassResponsibility(); + } + + // eslint-disable-next-line class-methods-use-this, no-unused-vars -- make sure subclass implements + async resetObsColumnValues(col, oldValue, newValue) { + /* + Set by value - all elements in the column with value 'oldValue' are set to 'newValue'. + Async method - returns a promise for a new AnnoMatrix. + + Typical use would be to set all labels of one value to another. + + Will throw column does not exist or is not writable. + + Example: + await resetObsColumnValues("my notes", "good", "not-good") -> AnnoMatrix + + */ + _subclassResponsibility(); + } + + /** + ** Private interfaces below. + **/ + _resolveCachedQueries(field, queries) { + return queries + .map((query) => + _whereCacheGet(this._whereCache, this.schema, field, query).filter( + (cacheKey) => + cacheKey !== undefined && this._cache[field].hasCol(cacheKey) + ) + ) + .flat(); + } + + async _fetch(field, q) { + if (!AnnoMatrix.fields().includes(field)) return undefined; + const queries = Array.isArray(q) ? q : [q]; + + /* find cached columns we need, and GC the rest */ + const cachedColumns = this._resolveCachedQueries(field, queries); + this._gcFetchCleanup(field, cachedColumns); + + /* find any query not already cached */ + const uncachedQueries = queries.filter((query) => + _whereCacheGet(this._whereCache, this.schema, field, query).some( + (cacheKey) => + cacheKey === undefined || !this._cache[field].hasCol(cacheKey) + ) + ); + + /* load uncached queries */ + if (uncachedQueries.length > 0) { + await Promise.all( + uncachedQueries.map((query) => + this._getPendingLoad(field, query, async (_field, _query) => { + /* fetch, then index. _doLoad is subclass interface */ + const [whereCacheUpdate, df] = await this._doLoad(_field, _query); + this._cache[_field] = this._cache[_field].withColsFrom(df); + this._whereCache = _whereCacheMerge( + this._whereCache, + whereCacheUpdate + ); + }) + ) + ); + } + + /* everything we need is in the cache, so just cherry-pick requested columns */ + const requestedCacheKeys = this._resolveCachedQueries(field, queries); + const response = _dataframeCache( + this._cache[field].subset(null, requestedCacheKeys) + ); + this._gcUpdateStats(field, response); + return response; + } + + async _getPendingLoad(field, query, fetchFn) { + /* + Given a query on a field, ensure that we only have a single outstanding + fetch at any given time. If multiple requests occur while a fetch is + outstanding, just wait for the original. + + This is implemented by returning a promise that will await the singular + fetch promise. + */ + const key = _queryCacheKey(field, query); + if (!this._pendingLoad[field][key]) { + this._pendingLoad[field][key] = fetchFn(field, query); + try { + await this._pendingLoad[field][key]; + } finally { + delete this._pendingLoad[field][key]; + } + } + return this._pendingLoad[field][key]; + } + + // eslint-disable-next-line class-methods-use-this -- make sure subclass implements + async _doLoad() { + _subclassResponsibility(); + } + + /** + ** Garbage collection of annomatrix cache to manage memory use. + **/ + + /* + These callbacks implement a GC policy for the cache. Background: + + * For the Loader (base) annomatrix, re-filling the cache is expensive as + it requires an HTTP fetch. + * user-defined / writable columns must not be GC'ed as they may be + still pending a save/commit. + * For views, cost is less and (roughly) proportional with nObs + * obs, var and emb do not grow without bounds, and are needed constantly + for rendering. + a) There is no upside to GC'ing these in the base (loader) + b) The undo/redo cache can hold a large number in views, which is worht GC'ing + * X is often much larger than memory, and the UI allows add/del from + this. Most of the GC potential is here in both the base and views. + + Current policy: + * if in active use ("hot") do not GC obs, var or emb. + * never, ever GC writable obs columns + * For base/loader set a numeric limit on maximum X column count + * For views, apply a fixed limit to the number of columns cached in any field. + Limit will be lower if not hot. + + To be effective, the GC callback needs to be invoked from the undo/redo code, + as much of the cache is pinned by that data structure. + */ + _gcField(field, isHot, pinnedColumns) { + const maxColumns = isHot ? 256 : 10; // maybe to aggessive? + + const cache = this._cache[field]; + if (cache.colIndex.size() < maxColumns) return; // trivial rejection + + const candidates = cache.colIndex + .labels() + .filter((col) => !pinnedColumns.includes(col)); + + const excessCount = candidates.length + pinnedColumns.length - maxColumns; + if (excessCount > 0) { + const { _gcInfo } = this; + candidates.sort((a, b) => { + let atime = _gcInfo.get(_columnCacheKey(field, a)); + if (atime === undefined) atime = 0; + + let btime = _gcInfo.get(_columnCacheKey(field, b)); + if (btime === undefined) btime = 0; + + return atime - btime; + }); + + const toDrop = candidates.slice(0, excessCount); + // helpful debugging - please leave in place. + // console.log( + // `GC: dropping from ${field} hot:${isHot}, columns [${toDrop.join( + // ", " + // )}]` + // ); + this._cache[field] = toDrop.reduce( + (df, col) => df.dropCol(col), + this._cache[field] + ); + toDrop.forEach((col) => _gcInfo.delete(_columnCacheKey(field, col))); + } + } + + _gcFetchCleanup(field, pinnedColumns) { + /* + Called during data load/fetch. By definition, this is 'hot', so we + only want to gc X. + */ + if (field === "X") { + this._gcField( + field, + true, + pinnedColumns.concat(_getWritableColumns(this.schema, field)) + ); + } + } + + _gc(hints) { + /* + Called from middleware, or elsewhere. isHot is true if we are in the active store, + or false if we are in some other context (eg, history state). + */ + const { isHot } = hints; + const candidateFields = isHot ? ["X"] : ["X", "emb", "var", "obs"]; + candidateFields.forEach((field) => + this._gcField(field, isHot, _getWritableColumns(this.schema, field)) + ); + } + + _gcUpdateStats(field, dataframe) { + /* + called each time a query is performed, allowing the gc to update any bookkeeping + information. Currently, this is just a simple last-fetched timestamp, stored + in a Map. + + Map objects preserve order of insertion. This is leveraged as a cheap way to + do LRU, by removing and re-inserting keys. IMPORTANT: the cleanup code assumes + the map insertion order is least-recently-used first. + */ + const cols = dataframe.colIndex.labels(); + const { _gcInfo } = this; + const now = Date.now(); + cols.forEach((c) => { + // gcInfo.delete(c); + _gcInfo.set(_columnCacheKey(field, c), now); + }); + } + + /** + Cloning sublcass protocol - we rely in cloning to preserve immutable + symantics while not causing races or other side effects in internal + cache management. + + Subclasses must override _cloneDeeper() if they have state which requires + something other than a shallow copy. Overrides MUST call super()._cloneDeepr(), + and return its result (after any required modification). _cloneDeeper() + will be called on the OLD object, with the NEW object as an argument. + + Do not override _clone(); + **/ + _cloneDeeper(clone) { + clone._cache = _shallowClone(this._cache); + clone._gcInfo = new Map(); + clone._pendingLoad = { + obs: {}, + var: {}, + emb: {}, + X: {}, + }; + return clone; + } + + _clone() { + const clone = _shallowClone(this); + this._cloneDeeper(clone); + Object.seal(clone); + return clone; + } +} + +/* +private utility functions below +*/ + +function _queryCacheKey(field, query) { + if (typeof query === "object") { + const { field: queryField, column: queryColumn, value: queryValue } = query; + return `${field}/${queryField}/${queryColumn}/${queryValue}`; + } + return `${field}/${query}`; +} + +function _columnCacheKey(field, column) { + return `${field}/${column}`; +} + +function _subclassResponsibility() { + /* protect against bugs in subclass */ + throw new Error("subclass failed to implement required method"); +} diff --git a/client/src/annoMatrix/clone.js b/client/src/annoMatrix/clone.js new file mode 100644 index 00000000..97cad4b5 --- /dev/null +++ b/client/src/annoMatrix/clone.js @@ -0,0 +1,6 @@ +/* +Shallow clone an object, correctly handling prototype +*/ +export default function _shallowClone(orig) { + return Object.assign(Object.create(Object.getPrototypeOf(orig)), orig); +} diff --git a/client/src/annoMatrix/crossfilter.js b/client/src/annoMatrix/crossfilter.js new file mode 100644 index 00000000..b3972978 --- /dev/null +++ b/client/src/annoMatrix/crossfilter.js @@ -0,0 +1,263 @@ +/* +Row crossfilter proxy for an AnnoMatrix. This wraps Crossfilter, +providing a number of services, and ensuring that the crossfilter and +AnnoMatrix stay in sync: + - on-demand index creation as data is loaded + - transparently mapping between queries and crossfilter index names. + - for mutation of the matrix by user annotations, maintain synchronization + between Crossfilter and AnnoMatrix. +*/ +import Crossfilter from "../util/typedCrossfilter"; +import { _getColumnSchema } from "./schema"; + +function _dimensionNameFromDf(field, df) { + const colNames = df.colIndex.labels(); + return _dimensionName(field, colNames); +} + +function _dimensionName(field, colNames) { + if (!Array.isArray(colNames)) return `${field}/${colNames}`; + return `${field}/${colNames.join(":")}`; +} + +export default class AnnoMatrixObsCrossfilter { + constructor(annoMatrix, _obsCrossfilter = null) { + this.annoMatrix = annoMatrix; + this.obsCrossfilter = + _obsCrossfilter || new Crossfilter(annoMatrix._cache.obs); + this.obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs); + } + + size() { + return this.obsCrossfilter.size(); + } + + /** + Managing the associated annoMatrix. These wrappers are necessary to + make coordinated changes to BOTH the crossfilter and annoMatrix, and + ensure that all state stays synchronized. + + See API documentation in annoMatrix.js. + **/ + addObsColumn(colSchema, Ctor, value) { + const annoMatrix = this.annoMatrix.addObsColumn(colSchema, Ctor, value); + const obsCrossfilter = this.obsCrossfilter.setData(annoMatrix._cache.obs); + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + dropObsColumn(col) { + const annoMatrix = this.annoMatrix.dropObsColumn(col); + let { obsCrossfilter } = this; + const dimName = _dimensionName("obs", col); + if (obsCrossfilter.hasDimension(dimName)) { + obsCrossfilter = obsCrossfilter.delDimension(dimName); + } + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + renameObsColumn(oldCol, newCol) { + const annoMatrix = this.annoMatrix.renameObsColumn(oldCol, newCol); + const oldDimName = _dimensionName("obs", oldCol); + const newDimName = _dimensionName("obs", newCol); + let { obsCrossfilter } = this; + if (obsCrossfilter.hasDimension(oldDimName)) { + obsCrossfilter = obsCrossfilter.renameDimension(oldDimName, newDimName); + } + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + addObsAnnoCategory(col, category) { + const annoMatrix = this.annoMatrix.addObsAnnoCategory(col, category); + const dimName = _dimensionName("obs", col); + let { obsCrossfilter } = this; + if (obsCrossfilter.hasDimension(dimName)) { + obsCrossfilter = obsCrossfilter.delDimension(dimName); + } + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + async removeObsAnnoCategory(col, category, unassignedCategory) { + const annoMatrix = await this.annoMatrix.removeObsAnnoCategory( + col, + category, + unassignedCategory + ); + const dimName = _dimensionName("obs", col); + let { obsCrossfilter } = this; + if (obsCrossfilter.hasDimension(dimName)) { + obsCrossfilter = obsCrossfilter.delDimension(dimName); + } + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + async setObsColumnValues(col, rowLabels, value) { + const annoMatrix = await this.annoMatrix.setObsColumnValues( + col, + rowLabels, + value + ); + const dimName = _dimensionName("obs", col); + let { obsCrossfilter } = this; + if (obsCrossfilter.hasDimension(dimName)) { + obsCrossfilter = obsCrossfilter.delDimension(dimName); + } + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + async resetObsColumnValues(col, oldValue, newValue) { + const annoMatrix = await this.annoMatrix.resetObsColumnValues( + col, + oldValue, + newValue + ); + const dimName = _dimensionName("obs", col); + let { obsCrossfilter } = this; + if (obsCrossfilter.hasDimension(dimName)) { + obsCrossfilter = obsCrossfilter.delDimension(dimName); + } + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + /** + Selection state - API is identical to ImmutableTypedCrossfilter, as these + are just wrappers to lazy create indices. + **/ + + async select(field, query, spec) { + const { annoMatrix } = this; + let { obsCrossfilter } = this; + + if (!annoMatrix?._cache?.[field]) { + throw new Error("Unknown field name"); + } + if (field === "var") { + throw new Error("unable to obsSelect upon the var dimension"); + } + + // grab the data, so we can grab the index. + const df = await annoMatrix.fetch(field, query); + + const dimName = _dimensionNameFromDf(field, df); + if (!obsCrossfilter.hasDimension(dimName)) { + // lazy index generation - add dimension when first used + obsCrossfilter = this._addObsCrossfilterDimension( + annoMatrix, + obsCrossfilter, + field, + df + ); + } + + // select + obsCrossfilter = obsCrossfilter.select(dimName, spec); + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + selectAll() { + /* + Select all on any dimension in this field. + */ + const { annoMatrix } = this; + const currentDims = this.obsCrossfilter.dimensionNames(); + const obsCrossfilter = currentDims.reduce((xfltr, dim) => { + return xfltr.select(dim, { mode: "all" }); + }, this.obsCrossfilter); + return new AnnoMatrixObsCrossfilter(annoMatrix, obsCrossfilter); + } + + countSelected() { + /* if no data yet indexed in the crossfilter, just say everything is selected */ + if (this.obsCrossfilter.size() === 0) return this.annoMatrix.nObs; + return this.obsCrossfilter.countSelected(); + } + + allSelectedMask() { + /* if no data yet indexed in the crossfilter, just say everything is selected */ + if ( + this.obsCrossfilter.size() === 0 || + this.obsCrossfilter.dimensionNames().length === 0 + ) { + /* fake the mask */ + return new Uint8Array(this.annoMatrix.nObs).fill(1); + } + return this.obsCrossfilter.allSelectedMask(); + } + + allSelectedLabels() { + /* if no data yet indexed in the crossfilter, just say everything is selected */ + if ( + this.obsCrossfilter.size() === 0 || + this.obsCrossfilter.dimensionNames().length === 0 + ) { + return this.annoMatrix.rowIndex.labels(); + } + + const mask = this.obsCrossfilter.allSelectedMask(); + const index = this.annoMatrix.rowIndex.isubsetMask(mask); + return index.labels(); + } + + fillByIsSelected(array, selectedValue, deselectedValue) { + /* if no data yet indexed in the crossfilter, just say everything is selected */ + if ( + this.obsCrossfilter.size() === 0 || + this.obsCrossfilter.dimensionNames().length === 0 + ) { + return array.fill(selectedValue); + } + return this.obsCrossfilter.fillByIsSelected( + array, + selectedValue, + deselectedValue + ); + } + + /** + ** Private below + **/ + + _addObsCrossfilterDimension(annoMatrix, obsCrossfilter, field, df) { + if (field === "var") return obsCrossfilter; + const dimName = _dimensionNameFromDf(field, df); + const dimParams = this._getObsDimensionParams(field, df); + obsCrossfilter = obsCrossfilter.setData(annoMatrix._cache.obs); + obsCrossfilter = obsCrossfilter.addDimension(dimName, ...dimParams); + return obsCrossfilter; + } + + _getColumnBaseType(field, col) { + /* Look up the primitive type for this field/col */ + const colSchema = _getColumnSchema(this.annoMatrix.schema, field, col); + return colSchema.type; + } + + _getObsDimensionParams(field, df) { + /* return the crossfilter dimensiontype type and params for this field/dataframe */ + + if (field === "emb") { + /* assumed to be 2D */ + return ["spatial", df.icol(0).asArray(), df.icol(1).asArray()]; + } + + /* assumed to be 1D */ + const col = df.icol(0); + const colName = df.colIndex.getLabel(0); + const type = this._getColumnBaseType(field, colName); + if (type === "string" || type === "categorical" || type === "boolean") { + return ["enum", col.asArray()]; + } + if (type === "int32") { + return ["scalar", col.asArray(), Int32Array]; + } + if (type === "float32") { + return ["scalar", col.asArray(), Float32Array]; + } + // Currently not supporting boolean and categorical types. + console.error( + `Warning - unknown metadata schema (${type}) for field ${field} ${colName}.` + ); + // skip it - we don't know what to do with this type + + return undefined; + } +} diff --git a/client/src/annoMatrix/fetchHelpers.js b/client/src/annoMatrix/fetchHelpers.js new file mode 100644 index 00000000..abc67d09 --- /dev/null +++ b/client/src/annoMatrix/fetchHelpers.js @@ -0,0 +1,27 @@ +export { doBinaryRequest } from "../util/actionHelpers"; + +/* double URI encode - needed for query-param filters */ +export function _dubEncURIComp(s) { + return encodeURIComponent(encodeURIComponent(s)); +} + +/* currently unused, consider deleting */ +export function _fetchResult(promise) { + let _status = "pending"; + const res = promise.then( + (r) => { + _status = "success"; + return r; + }, + (e) => { + _status = "error"; + throw e; + } + ); + + res.status = () => { + return _status; + }; + + return res; +} diff --git a/client/src/annoMatrix/index.js b/client/src/annoMatrix/index.js new file mode 100644 index 00000000..9e23aef6 --- /dev/null +++ b/client/src/annoMatrix/index.js @@ -0,0 +1,15 @@ +/* +AnnoMatrix -- Annotated Matrix exported interface + +Public API is defined in: + + annoMatrix.js + viewCreators.js + crossfilter.js + +*/ + +export { default as AnnoMatrixLoader } from "./loader"; +export * from "./viewCreators"; +export { default as AnnoMatrixObsCrossfilter } from "./crossfilter"; +export { default as gcMiddleware } from "./middleware"; diff --git a/client/src/annoMatrix/loader.js b/client/src/annoMatrix/loader.js new file mode 100644 index 00000000..7ee27553 --- /dev/null +++ b/client/src/annoMatrix/loader.js @@ -0,0 +1,287 @@ +import { doBinaryRequest, _dubEncURIComp } from "./fetchHelpers"; +import { matrixFBSToDataframe } from "../util/stateManager/matrix"; +import { _getColumnSchema, _normalizeCategoricalSchema } from "./schema"; +import { + addObsAnnoColumn, + removeObsAnnoColumn, + addObsAnnoCategory, + removeObsAnnoCategory, +} from "../util/stateManager/schemaHelpers"; +import { isArrayOrTypedArray } from "../util/typeHelpers"; +import { _whereCacheCreate } from "./whereCache"; +import AnnoMatrix from "./annoMatrix"; +import PromiseLimit from "../util/promiseLimit"; + +const promiseThrottle = new PromiseLimit(5); + +export default class AnnoMatrixLoader extends AnnoMatrix { + /* + AnnoMatrix implementation which proxies to HTTP server using the CXG REST API. + Used as the base (non-view) instance. + + Public API is same as AnnoMatrix class (refer there for API description), + with the addition of the constructor which bootstraps: + + new AnnoMatrixLoader(serverBaseURL, schema) -> instance + + */ + constructor(baseURL, schema) { + const { nObs, nVar } = schema.dataframe; + super(schema, nObs, nVar); + + if (baseURL[baseURL.length - 1] !== "/") { + // must have trailing slash + baseURL += "/"; + } + this.baseURL = baseURL; + Object.seal(this); + } + + /** + ** Public. API described in base class. + **/ + addObsAnnoCategory(col, category) { + /* + Add a new category (aka label) to the schema for an obs column. + */ + const colSchema = _getColumnSchema(this.schema, "obs", col); + _writableCategoryTypeCheck(colSchema); // throws on error + + const o = this._clone(); + o.schema = addObsAnnoCategory(this.schema, col, category); + return o; + } + + async removeObsAnnoCategory(col, category, unassignedCategory) { + /* + Remove a single "category" (aka "label") from the data & schema of an obs column. + */ + const colSchema = _getColumnSchema(this.schema, "obs", col); + _writableCategoryTypeCheck(colSchema); // throws on error + + const o = await this.resetObsColumnValues( + col, + category, + unassignedCategory + ); + o.schema = removeObsAnnoCategory(o.schema, col, category); + return o; + } + + dropObsColumn(col) { + /* + drop column from field + */ + const colSchema = _getColumnSchema(this.schema, "obs", col); + _writableCheck(colSchema); // throws on error + + const o = this._clone(); + o._cache.obs = this._cache.obs.dropCol(col); + o.schema = removeObsAnnoColumn(this.schema, col); + return o; + } + + addObsColumn(colSchema, Ctor, value) { + /* + add a column to field, initializing with value. Value may + be one of: + * an array of values + * a primitive type, including null or undefined. + If an array, it must be of same size as nObs and same type as Ctor + */ + colSchema.writable = true; + const col = colSchema.name; + if ( + _getColumnSchema(this.schema, "obs", col) || + this._cache.obs.hasCol(col) + ) { + throw new Error("column already exists"); + } + + const o = this._clone(); + let data; + if (isArrayOrTypedArray(value)) { + if (value.constructor !== Ctor) + throw new Error("Mismatched value array type"); + if (value.length !== this.nObs) + throw new Error("Value array has incorrect length"); + data = value.slice(); + } else { + data = new Ctor(this.nObs).fill(value); + } + o._cache.obs = this._cache.obs.withCol(col, data); + o.schema = addObsAnnoColumn(this.schema, col, { + ...colSchema, + writable: true, + }); + return o; + } + + renameObsColumn(oldCol, newCol) { + /* + Rename the obs oldColName to newColName. oldCol must be writable. + */ + const oldColSchema = _getColumnSchema(this.schema, "obs", oldCol); + _writableCheck(oldColSchema); // throws on error + + const value = this._cache.obs.hasCol(oldCol) + ? this._cache.obs.col(oldCol).asArray() + : undefined; + return this.dropObsColumn(oldCol).addObsColumn( + { + ...oldColSchema, + name: newCol, + }, + value.constructor, + value + ); + } + + async setObsColumnValues(col, rowLabels, value) { + /* + Set all rows identified by rowLabels to value. + */ + const colSchema = _getColumnSchema(this.schema, "obs", col); + _writableCategoryTypeCheck(colSchema); // throws on error + + // ensure that we have the data in cache before we manipulate it + await this.fetch("obs", col); + if (!this._cache.obs.hasCol(col)) + throw new Error("Internal error - user annotation data missing"); + + const rowIndices = this.rowIndex.getOffsets(rowLabels); + const data = this._cache.obs.col(col).asArray().slice(); + for (let i = 0, len = rowIndices.length; i < len; i += 1) { + const idx = rowIndices[i]; + if (idx === undefined) throw new Error("Unknown row label"); + data[idx] = value; + } + + const o = this._clone(); + o._cache.obs = this._cache.obs.replaceColData(col, data); + const { categories } = colSchema; + if (!categories?.includes(value)) { + o.schema = addObsAnnoCategory(this.schema, col, value); + } + return o; + } + + async resetObsColumnValues(col, oldValue, newValue) { + /* + Set all rows with value 'oldValue' to 'newValue'. + */ + const colSchema = _getColumnSchema(this.schema, "obs", col); + _writableCategoryTypeCheck(colSchema); // throws on error + + if (!colSchema.categories.includes(oldValue)) { + throw new Error("unknown category"); + } + + // ensure that we have the data in cache before we manipulate it + await this.fetch("obs", col); + if (!this._cache.obs.hasCol(col)) + throw new Error("Internal error - user annotation data missing"); + + const data = this._cache.obs.col(col).asArray().slice(); + for (let i = 0, l = data.length; i < l; i += 1) { + if (data[i] === oldValue) data[i] = newValue; + } + + const o = this._clone(); + o._cache.obs = this._cache.obs.replaceColData(col, data); + const { categories } = colSchema; + if (!categories?.includes(newValue)) { + o.schema = addObsAnnoCategory(this.schema, col, newValue); + } + return o; + } + + /** + ** Private below + **/ + async _doLoad(field, query) { + /* + _doLoad - evaluates the query against the field. Returns: + * whereCache update: column query map mapping the query to the column labels + * Dataframe containing the new colums (one per dimension) + */ + let urlQuery; + let urlBase; + let priority = 10; // default fetch priority + + switch (field) { + case "obs": + case "var": { + urlBase = `${this.baseURL}annotations/${field}`; + urlQuery = _encodeQuery("annotation-name", query); + break; + } + case "X": { + urlBase = `${this.baseURL}data/var`; + urlQuery = _encodeQuery(undefined, query); + break; + } + case "emb": { + urlBase = `${this.baseURL}layout/obs`; + urlQuery = _encodeQuery("layout-name", query); + priority = 0; // high prio load for embeddings + break; + } + default: + throw new Error("Unknown field name"); + } + + const url = `${urlBase}?${urlQuery}`; + const buffer = await promiseThrottle.priorityAdd( + priority, + doBinaryRequest, + url + ); + const result = matrixFBSToDataframe(buffer); + if (!result || result.isEmpty()) throw Error("Unknown field/col"); + + const whereCacheUpdate = _whereCacheCreate( + field, + query, + result.colIndex.labels() + ); + + if (field === "obs") { + /* cough, cough - see comment on method */ + _normalizeCategoricalSchema( + this.schema.annotations.obsByName[query], + result.col(query) + ); + } + + return [whereCacheUpdate, result]; + } +} + +/* +Utility functions below +*/ + +function _encodeQuery(colKey, q) { + if (typeof q === "object") { + const { field: queryField, column: queryColumn, value: queryValue } = q; + return `${_dubEncURIComp(queryField)}:${_dubEncURIComp( + queryColumn + )}=${_dubEncURIComp(queryValue)}`; + } + if (!colKey) throw new Error("Unsupported query by name"); + return `${colKey}=${encodeURIComponent(q)}`; +} + +function _writableCheck(colSchema) { + if (!colSchema?.writable) { + throw new Error("Unknown or readonly obs column"); + } +} + +function _writableCategoryTypeCheck(colSchema) { + _writableCheck(colSchema); + if (colSchema.type !== "categorical") { + throw new Error("column must be categorical"); + } +} diff --git a/client/src/annoMatrix/middleware.js b/client/src/annoMatrix/middleware.js new file mode 100644 index 00000000..d332b79a --- /dev/null +++ b/client/src/annoMatrix/middleware.js @@ -0,0 +1,66 @@ +/* +Garbage collection / cache management support + +Middleware that knows how to pull annoMatrix from the undoable state, +and pass it along to the AnnoMatrix class for possible cache GC. + +Private interface. + +Future work item: this middleware knows internal details of both the +Undoable metareducer and the AnnoMatrix private API. It would be helpful +to make the Undoable interface better factored. +*/ + +const annoMatrixGC = (store) => (next) => (action) => { + if (_itIsTimeForGC()) { + _doGC(store); + } + return next(action); +}; + +let lastGCTime = 0; +const InterGCDelayMS = 30 * 1000; // 30 seconds +function _itIsTimeForGC() { + /* + we don't want to run GC on every dispatch, so throttle it a bit. + + Runs every InterGCDelay period + */ + const now = Date.now(); + if (now - lastGCTime > InterGCDelayMS) { + lastGCTime = now; + return true; + } + return false; +} + +function _doGC(store) { + const state = store.getState(); + + // these should probably be a function imported from undoable.js, etc, as + // they have overly intimiate knowledge of our reducers. + const undoablePast = state["@@undoable/past"]; + const undoableFuture = state["@@undoable/future"]; + const undoableStack = undoablePast + .concat(undoableFuture) + .flatMap((snapshot) => + snapshot.filter((v) => v[0] === "annoMatrix").map((v) => v[1]) + ); + const currentAnnoMatrix = state.annoMatrix; + + /* + We want to identify those matrixes currently "hot", ie, linked from the current annoMatrix, + as our current gc algo is more aggressive with those not hot. + */ + const allAnnoMatrices = new Map( + undoableStack.map((m) => [m, { isHot: false }]) + ); + let am = currentAnnoMatrix; + while (am) { + allAnnoMatrices.set(am, { isHot: true }); + am = am.viewOf; + } + allAnnoMatrices.forEach((hints, annoMatrix) => annoMatrix._gc(hints)); +} + +export default annoMatrixGC; diff --git a/client/src/annoMatrix/schema.js b/client/src/annoMatrix/schema.js new file mode 100644 index 00000000..9ce03765 --- /dev/null +++ b/client/src/annoMatrix/schema.js @@ -0,0 +1,82 @@ +/* +Private helper functions related to schema +*/ +import catLabelSort from "../util/catLabelSort"; +import { unassignedCategoryLabel } from "../globals"; + +export function _getColumnSchema(schema, field, col) { + /* look up the column definition */ + switch (field) { + case "obs": + if (typeof col === "object") + throw new Error("unable to get column schema by query"); + return schema.annotations.obsByName[col]; + case "var": + if (typeof col === "object") + throw new Error("unable to get column schema by query"); + return schema.annotations.varByName[col]; + case "emb": + if (typeof col === "object") + throw new Error("unable to get column schema by query"); + return schema.layout.obsByName[col]; + case "X": + return schema.dataframe; + default: + throw new Error(`unknown field name: ${field}`); + } +} + +export function _getColumnDimensionNames(schema, field, col) { + /* + field/col may be an alias for multiple columns. Currently used to map ND + values to 1D dataframe columns for embeddings/layout. Signfied by the presence + of the "dims" value in the schema. + */ + const colSchema = _getColumnSchema(schema, field, col); + if (!colSchema) { + return undefined; + } + return colSchema.dims || [col]; +} + +export function _schemaColumns(schema, field) { + switch (field) { + case "obs": + return Object.keys(schema.annotations.obsByName); + case "var": + return Object.keys(schema.annotations.varByName); + case "emb": + return Object.keys(schema.layout.obsByName); + default: + throw new Error(`unknown field name: ${field}`); + } +} + +export function _getWritableColumns(schema, field) { + if (field !== "obs") return []; + return schema.annotations.obs.columns + .filter((v) => v.writable) + .map((v) => v.name); +} + +export function _isContinuousType(schema) { + const { type } = schema; + return !(type === "string" || type === "boolean" || type === "categorical"); +} + +export function _normalizeCategoricalSchema(colSchema, col) { + const { type, writable } = colSchema; + if (type === "string" || type === "boolean" || type === "categorical") { + const categorySet = new Set( + col.summarize().categories.concat(colSchema.categories ?? []) + ); + if (writable && !categorySet.has(unassignedCategoryLabel)) { + categorySet.add(unassignedCategoryLabel); + } + colSchema.categories = Array.from(categorySet); + } + + if (colSchema.categories) { + colSchema.categories = catLabelSort(writable, colSchema.categories); + } +} diff --git a/client/src/annoMatrix/viewCreators.js b/client/src/annoMatrix/viewCreators.js new file mode 100644 index 00000000..d3488699 --- /dev/null +++ b/client/src/annoMatrix/viewCreators.js @@ -0,0 +1,63 @@ +/* +View creators. These are helper functions which create new views from existing +instances of AnnoMatrix, implementing common UI functions. +*/ + +import { AnnoMatrixRowSubsetView, AnnoMatrixClipView } from "./views"; + +export function isubsetMask(annoMatrix, obsMask) { + /* + Subset annomatrix to contain the rows which have truish value in the mask. + Maks length must equal annoMatrix.nObs (row count). + */ + return isubset(annoMatrix, _maskToList(obsMask)); +} + +export function isubset(annoMatrix, obsOffsets) { + /* + Subset annomatrix to contain the positions contained in the obsOffsets array + + Example: + + isubset(annoMatrix, [0, 1]) -> annoMatrix with only the first two rows + */ + const obsIndex = annoMatrix.rowIndex.isubset(obsOffsets); + return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex); +} + +export function subset(annoMatrix, obsLabels) { + /* + subset based on labels + */ + const obsIndex = annoMatrix.rowIndex.subset(obsLabels); + return new AnnoMatrixRowSubsetView(annoMatrix, obsIndex); +} + +export function clip(annoMatrix, qmin, qmax) { + /* + Create a view that clips all continuous data to the [min, max] range. + The matrix shape does not change, but the continuous values outside the + specified range will become a NaN. + */ + return new AnnoMatrixClipView(annoMatrix, qmin, qmax); +} + +/* +Private utility functions below +*/ + +function _maskToList(mask) { + /* convert masks to lists - method wastes space, but is fast */ + if (!mask) { + return null; + } + const list = new Int32Array(mask.length); + let elems = 0; + for (let i = 0, l = mask.length; i < l; i += 1) { + if (mask[i]) { + list[elems] = i; + elems += 1; + } + } + return new Int32Array(list.buffer, 0, elems); +} diff --git a/client/src/annoMatrix/views.js b/client/src/annoMatrix/views.js new file mode 100644 index 00000000..f97e05a0 --- /dev/null +++ b/client/src/annoMatrix/views.js @@ -0,0 +1,161 @@ +/* eslint-disable max-classes-per-file -- Classes are interrelated*/ + +/* +Views on the annomatrix. all API here is defined in viewCreators.js and annoMatrix.js. +*/ +import clip from "../util/clip"; +import AnnoMatrix from "./annoMatrix"; +import { _whereCacheCreate } from "./whereCache"; +import { _isContinuousType, _getColumnSchema } from "./schema"; + +class AnnoMatrixView extends AnnoMatrix { + constructor(viewOf, rowIndex = null) { + const nObs = rowIndex ? rowIndex.size() : viewOf.nObs; + super(viewOf.schema, nObs, viewOf.nVar, rowIndex || viewOf.rowIndex); + this.viewOf = viewOf; + this.isView = true; + } + + addObsAnnoCategory(col, category) { + const o = this._clone(); + o.viewOf = this.viewOf.addObsAnnoCategory(col, category); + o.schema = o.viewOf.schema; + return o; + } + + async removeObsAnnoCategory(col, category, unassignedCategory) { + const o = this._clone(); + o.viewOf = await this.viewOf.removeObsAnnoCategory( + col, + category, + unassignedCategory + ); + o.schema = o.viewOf.schema; + return o; + } + + dropObsColumn(col) { + const o = this._clone(); + o.viewOf = this.viewOf.dropObsColumn(col); + o._cache.obs = this._cache.obs.dropCol(col); + o.schema = o.viewOf.schema; + return o; + } + + addObsColumn(colSchema, Ctor, value) { + const o = this._clone(); + o.viewOf = this.viewOf.addObsColumn(colSchema, Ctor, value); + o.schema = o.viewOf.schema; + return o; + } + + renameObsColumn(oldCol, newCol) { + const o = this._clone(); + o.viewOf = this.viewOf.renameObsColumn(oldCol, newCol); + o.schema = o.viewOf.schema; + return o; + } + + async setObsColumnValues(col, rowLabels, value) { + const o = this._clone(); + o.viewOf = await this.viewOf.setObsColumnValues(col, rowLabels, value); + o._cache.obs = this._cache.obs.dropCol(col); + o.schema = o.viewOf.schema; + return o; + } + + async resetObsColumnValues(col, oldValue, newValue) { + const o = this._clone(); + o.viewOf = await this.viewOf.resetObsColumnValues(col, oldValue, newValue); + o._cache.obs = this._cache.obs.dropCol(col); + o.schema = o.viewOf.schema; + return o; + } +} + +class AnnoMatrixMapView extends AnnoMatrixView { + /* + A view which knows how to transform its data. + */ + constructor(viewOf, mapFn) { + super(viewOf); + this.mapFn = mapFn; + } + + async _doLoad(field, query) { + const df = await this.viewOf._fetch(field, query); + const dfMapped = df.mapColumns((colData, colIdx) => { + const colLabel = df.colIndex.getLabel(colIdx); + const colSchema = _getColumnSchema(this.schema, field, colLabel); + return this.mapFn(field, colLabel, colSchema, colData, df); + }); + const whereCacheUpdate = _whereCacheCreate( + field, + query, + dfMapped.colIndex.labels() + ); + return [whereCacheUpdate, dfMapped]; + } +} + +export class AnnoMatrixClipView extends AnnoMatrixMapView { + /* + A view which is a clipped transformation of its parent + */ + constructor(viewOf, qmin, qmax) { + super(viewOf, (field, colLabel, colSchema, colData, df) => + _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax) + ); + this.isClipped = true; + this.clipRange = [qmin, qmax]; + Object.seal(this); + } +} + +export class AnnoMatrixRowSubsetView extends AnnoMatrixView { + /* + A view which is a subset of total rows. + */ + constructor(viewOf, rowIndex) { + super(viewOf, rowIndex); + Object.seal(this); + } + + async _doLoad(field, query) { + const df = await this.viewOf._fetch(field, query); + + // don't try to row-subset the var dimension. + if (field === "var") { + return [null, df]; + } + + const dfSubset = df.subset(null, null, this.rowIndex); + const whereCacheUpdate = _whereCacheCreate( + field, + query, + dfSubset.colIndex.labels() + ); + return [whereCacheUpdate, dfSubset]; + } +} + +/* +Utility functions below +*/ + +function _clipAnnoMatrix(field, colLabel, colSchema, colData, df, qmin, qmax) { + /* only clip obs and var scalar columns */ + if (field !== "obs" && field !== "X") return colData; + if (!_isContinuousType(colSchema)) return colData; + if (qmin < 0) qmin = 0; + if (qmax > 1) qmax = 1; + if (qmin === 0 && qmax === 1) return colData; + + const quantiles = df.col(colLabel).summarize().percentiles; + const lower = quantiles[100 * qmin]; + const upper = quantiles[100 * qmax]; + const clippedData = clip(colData.slice(), lower, upper, Number.NaN); + return clippedData; +} + +/* eslint-enable max-classes-per-file -- enable*/ diff --git a/client/src/annoMatrix/whereCache.js b/client/src/annoMatrix/whereCache.js new file mode 100644 index 00000000..d3c7a004 --- /dev/null +++ b/client/src/annoMatrix/whereCache.js @@ -0,0 +1,92 @@ +/* +Private support functions. + +Support for a "where" query, eg, + + { where: { field: "var", column: "gene", value: "FOXP2" }} + +These evaluate to a given column label. + +The "where cache" is a map that saves evaluated queries and points +to the column label they resolve to. + +Data structure, using X as the example field being queried, and var as +the index. + +{ + X: { + var: Map( + column_label_in_var => Map(value_in_var_column => [column_label_in_X, ...]) + ) + } +} +*/ +import { _getColumnDimensionNames } from "./schema"; + +export function _whereCacheGet(whereCache, schema, field, query) { + /* + query will either be an where query (object) or a column name (string). + + Return array of column labels or undefined. + */ + + if (typeof query === "object") { + const { field: queryField, column: queryColumn, value: queryValue } = query; + + const columnMap = whereCache?.[field]?.[queryField]; + if (columnMap === undefined) return [undefined]; + + const valueMap = columnMap.get(queryColumn); + if (valueMap === undefined) return [undefined]; + + const columnLabels = valueMap.get(queryValue); + return columnLabels === undefined ? [undefined] : columnLabels; + } + + const colDims = _getColumnDimensionNames(schema, field, query); + return colDims === undefined ? [undefined] : colDims; +} + +export function _whereCacheCreate(field, query, columnLabels) { + /* + Create a new whereCache + */ + if (typeof query !== "object") return null; + + const { field: queryField, column: queryColumn, value: queryValue } = query; + const whereCache = { + [field]: { + [queryField]: new Map([ + [queryColumn, new Map([[queryValue, columnLabels]])], + ]), + }, + }; + return whereCache; +} + +function __whereCacheMerge(dst, src) { + /* + merge src into dst (modifies dst) + */ + if (!dst) dst = {}; + if (!src || typeof src !== "object") return dst; + Object.entries(src).forEach(([field, query]) => { + if (!Object.prototype.hasOwnProperty.call(dst, field)) dst[field] = {}; + Object.entries(query).forEach(([queryField, columnMap]) => { + if (!Object.prototype.hasOwnProperty.call(dst[field], queryField)) + dst[field][queryField] = new Map(); + columnMap.forEach((valueMap, queryColumn) => { + if (!dst[field][queryField].has(queryColumn)) + dst[field][queryField].set(queryColumn, new Map()); + valueMap.forEach((columnLabels, queryValue) => { + dst[field][queryField].get(queryColumn).set(queryValue, columnLabels); + }); + }); + }); + }); + return dst; +} + +export function _whereCacheMerge(...caches) { + return caches.reduce((dst, src) => __whereCacheMerge(dst, src), {}); +} diff --git a/client/src/components/app.js b/client/src/components/app.js index 14ea5dc2..fe1de5e7 100644 --- a/client/src/components/app.js +++ b/client/src/components/app.js @@ -64,10 +64,10 @@ class App extends React.Component { left: window.innerWidth / 2 - 50, }} > - error loading + error loading cellxgene ) : null} - {loading ? null : ( + {loading || error ? null : ( {(viewportRef) => ( diff --git a/client/src/components/autosave/filenameDialog.js b/client/src/components/autosave/filenameDialog.js index c2f02e64..6ede84b5 100644 --- a/client/src/components/autosave/filenameDialog.js +++ b/client/src/components/autosave/filenameDialog.js @@ -11,13 +11,8 @@ import { } from "@blueprintjs/core"; @connect((state) => ({ - universe: state.universe, idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null, annotations: state.annotations, - obsAnnotations: state.universe.obsAnnotations, - saveInProgress: state.autosave?.saveInProgress ?? false, - lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations, - error: state.autosave?.error, writableCategoriesEnabled: state.config?.parameters?.annotations ?? false, })) class FilenameDialog extends React.Component { @@ -113,7 +108,7 @@ class FilenameDialog extends React.Component { this.handleCreateFilename(); }} > -
+

Name your annotations collection:

Create annotations collection diff --git a/client/src/components/autosave/index.js b/client/src/components/autosave/index.js index d77dcc65..c03cdfd5 100644 --- a/client/src/components/autosave/index.js +++ b/client/src/components/autosave/index.js @@ -4,14 +4,12 @@ import actions from "../../actions"; import FilenameDialog from "./filenameDialog"; @connect((state) => ({ - universe: state.universe, annotations: state.annotations, - obsAnnotations: state.universe.obsAnnotations, saveInProgress: state.autosave?.saveInProgress ?? false, - lastSavedObsAnnotations: state.autosave?.lastSavedObsAnnotations, error: state.autosave?.error, writableCategoriesEnabled: state.config?.parameters?.annotations ?? false, - initialDataLoadComplete: state.autosave?.initialDataLoadComplete, + annoMatrix: state.annoMatrix, + lastSavedAnnoMatrix: state.autosave?.lastSavedAnnoMatrix, })) class Autosave extends React.Component { constructor(props) { @@ -42,16 +40,14 @@ class Autosave extends React.Component { tick = () => { const { dispatch, saveInProgress } = this.props; if (this.needToSave() && !saveInProgress) { - dispatch(actions.saveObsAnnotations()); + dispatch(actions.saveObsAnnotationsAction()); } }; needToSave = () => { /* return true if we need to save, false if we don't */ - const { obsAnnotations, lastSavedObsAnnotations } = this.props; - return ( - lastSavedObsAnnotations && obsAnnotations !== lastSavedObsAnnotations - ); + const { annoMatrix, lastSavedAnnoMatrix } = this.props; + return actions.needToSaveObsAnnotations(annoMatrix, lastSavedAnnoMatrix); }; statusMessage() { @@ -66,9 +62,13 @@ class Autosave extends React.Component { const { writableCategoriesEnabled, saveInProgress, - initialDataLoadComplete, + lastSavedAnnoMatrix, } = this.props; - return writableCategoriesEnabled ? ( + const initialDataLoadComplete = lastSavedAnnoMatrix; + + if (!writableCategoriesEnabled) return null; + + return (

- ) : null; + ); } } diff --git a/client/src/components/brushableHistogram/index.js b/client/src/components/brushableHistogram/index.js index fce92ddb..71175b2d 100644 --- a/client/src/components/brushableHistogram/index.js +++ b/client/src/components/brushableHistogram/index.js @@ -4,13 +4,13 @@ https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172 https://bl.ocks.org/SpaceActuary/2f004899ea1b2bd78d6f1dbb2febf771 https://bl.ocks.org/mbostock/3019563 */ -// jshint esversion: 6 -import React from "react"; +import React, { useEffect, useRef, useState, useCallback } from "react"; import { Button, ButtonGroup, Tooltip } from "@blueprintjs/core"; import { connect } from "react-redux"; import * as d3 from "d3"; -import memoize from "memoize-one"; import { interpolateCool } from "d3-scale-chromatic"; +import Async from "react-async"; +import memoize from "memoize-one"; import * as globals from "../../globals"; import actions from "../../actions"; import { histogramContinuous } from "../../util/dataframe/histogram"; @@ -21,6 +21,425 @@ function clamp(val, rng) { return Math.max(Math.min(val, rng[1]), rng[0]); } +function maybeScientific(x) { + let format = ","; + const _ticks = x.ticks(4); + + if (x.domain().some((n) => Math.abs(n) >= 10000)) { + /* + heuristic: if the last tick d3 wants to render has one significant + digit ie., 2000, render 2e+3, but if it's anything else ie., 42000000 render + 4.20e+n + */ + format = significantDigits(_ticks[_ticks.length - 1]) === 1 ? ".0e" : ".2e"; + } + + return format; +} + +const StillLoading = ({ zebra, displayName }) => { + /* + Render a loading indicator for the field. + */ + return ( +
+
+
+
+ {displayName} +
+
+
+
+
+ ); +}; + +const ErrorLoading = ({ displayName, error, zebra }) => { + console.log(error); // log to console as this is unexpected + return ( +
+ {`Failure loading ${displayName}`} +
+ ); +}; + +const HistogramFooter = React.memo( + ({ + displayName, + hideRanges, + rangeMin, + rangeMax, + rangeColorMin, + rangeColorMax, + logFoldChange, + pvalAdj, + }) => { + /* + Footer of each histogram. Will render range, title, and optionally + differential expression info. + + Required props: + * displayName - the displayName, aka "n_genes", "FOXP2", etc. + * hideRanges - true/false, enables/disable rendering of ranges + * range - length two array, [min, max], containing the range values to display + * rangeColor - length two array, [mincolor, maxcolor], each a CSS color + * logFoldChange - lfc to display, optional. + * pValue - pValue to display, optional. + */ + return ( +
+
+ + min {rangeMin.toPrecision(4)} + + + {displayName} + +
+ : {rangeMin} +
+ + max {rangeMax.toPrecision(4)} + +
+ + {logFoldChange && pvalAdj ? ( +
+ + log fold change: + {` ${logFoldChange.toPrecision(4)}`} + + + p-value (adj): + {pvalAdj < 0.0001 ? " < 0.0001" : ` ${pvalAdj.toFixed(4)}`} + +
+ ) : null} +
+ ); + } +); + +const HistogramHeader = React.memo( + ({ + fieldId, + isColorBy, + onColorByClick, + onRemoveClick, + isScatterPlotX, + isScatterPlotY, + onScatterPlotXClick, + onScatterPlotYClick, + isObs, + }) => { + /* + Render the toolbar for the histogram. Props: + * fieldId - field identifier, used for various IDs + * isColorBy - true/false, is this the current color-by + * onColorByClick - color-by click handler + * onRemoveClick - optional handler for remove. Button will not render if not defined. + * isScatterPlotX - optional, true/false if currently the X scatterplot field + * isScatterPlotY - optional, true/false if currently the Y scatterplot field + * onScatterPlotXClick - optional, handler for scatterPlot X button. + * onScatterPlotYClick - optional, handler for scatterPlot X button. + + Scatterplot controls will not render if either handler unspecified. + */ + + const memoizedColorByCallback = useCallback( + () => onColorByClick(fieldId, isObs), + [fieldId, isObs] + ); + + return ( +
+ {onScatterPlotXClick && onScatterPlotYClick ? ( + + + + + + + + ) : null} + {onRemoveClick ? ( + + ) : null} + +
+ ); + } +); + +const Histogram = ({ + field, + fieldForId, + display, + histogram, + width, + height, + onBrush, + onBrushEnd, + margin, + isColorBy, + selectionRange, +}) => { + const svgRef = useRef(null); + const [brush, setBrush] = useState(null); + + useEffect(() => { + /* + Create the d3 histogram + */ + const { marginLeft, marginRight, marginBottom, marginTop } = margin; + const { x, y, bins, binStart, binEnd, binWidth } = histogram; + const svg = d3.select(svgRef.current); + + /* Remove everything */ + svg.selectAll("*").remove(); + + /* Set margins within the SVG */ + const container = svg + .attr("width", width + marginLeft + marginRight) + .attr("height", height + marginTop + marginBottom) + .append("g") + .attr("class", "histogram-container") + .attr("transform", `translate(${marginLeft},${marginTop})`); + + const colorScale = d3 + .scaleSequential(interpolateCool) + .domain([0, bins.length]); + + const histogramScale = d3 + .scaleLinear() + .domain(x.domain()) + .range([ + colorScale.domain()[1], + colorScale.domain()[0], + ]); /* we flip this to make colors dark if high in the color scale */ + + if (binWidth > 0) { + /* BINS */ + container + .insert("g", "*") + .selectAll("rect") + .data(bins) + .enter() + .append("rect") + .attr("x", (d, i) => x(binStart(i)) + 1) + .attr("y", (d) => y(d)) + .attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - 1) + .attr("height", (d) => y(0) - y(d)) + .style( + "fill", + isColorBy ? (d, i) => colorScale(histogramScale(binStart(i))) : "#bbb" + ); + } + + // BRUSH + // Note the brushable area is bounded by the data on three sides, but goes down to cover the x-axis + const brushX = d3 + .brushX() + .extent([ + [x.range()[0], y.range()[1]], + [x.range()[1], marginTop + height + marginBottom], + ]) + /* + emit start so that the Undoable history can save an undo point + upon drag start, and ignore the subsequent intermediate drag events. + */ + .on("start", onBrush(field, x.invert, "start")) + .on("brush", onBrush(field, x.invert, "brush")) + .on("end", onBrushEnd(field, x.invert)); + + const brushXselection = container + .insert("g") + .attr("class", "brush") + .attr("data-testid", `${svgRef.current.dataset.testid}-brushable-area`) + .call(brushX); + + /* X AXIS */ + container + .insert("g") + .attr("class", "axis axis--x") + .attr("transform", `translate(0,${marginTop + height})`) + .call( + d3 + .axisBottom(x) + .ticks(4) + .tickFormat(d3.format(maybeScientific(x))) + ); + + /* Y AXIS */ + container + .insert("g") + .attr("class", "axis axis--y") + .attr("transform", `translate(${marginLeft + width},0)`) + .call( + d3 + .axisRight(y) + .ticks(3) + .tickFormat( + d3.format( + y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : "," + ) + ) + ); + + /* axis style */ + svg.selectAll(".axis text").style("fill", "rgb(80,80,80)"); + svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)"); + svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)"); + + setBrush({ brushX, brushXselection }); + }, [histogram, isColorBy]); + + useEffect(() => { + /* + paint/update selection brush + */ + if (!brush) return; + const { brushX, brushXselection } = brush; + const selection = d3.brushSelection(brushXselection.node()); + if (!selectionRange && selection) { + /* no active selection - clear brush */ + brushXselection.call(brushX.move, null); + } else if (selectionRange) { + const { x, domain } = histogram; + const [min, max] = domain; + const x0 = x(clamp(selectionRange[0], [min, max])); + const x1 = x(clamp(selectionRange[1], [min, max])); + if (!selection) { + /* there is an active selection, but no brush - set the brush */ + brushXselection.call(brushX.move, [x0, x1]); + } else { + /* there is an active selection and a brush - make sure they match */ + const moveDeltaThreshold = 1; + const dX0 = Math.abs(x0 - selection[0]); + const dX1 = Math.abs(x1 - selection[1]); + /* + only update the brush if it is grossly incorrect, + as defined by the moveDeltaThreshold + */ + if (dX0 > moveDeltaThreshold || dX1 > moveDeltaThreshold) { + brushXselection.call(brushX.move, [x0, x1]); + } + } + } + }, [brush, selectionRange]); + + return ( + + ); +}; + @connect((state, ownProps) => { const { isObs, isUserDefined, isDiffExp, field } = ownProps; const myName = makeContinuousDimensionName( @@ -28,7 +447,7 @@ function clamp(val, rng) { field ); return { - world: state.world, + annoMatrix: state.annoMatrix, isScatterplotXXaccessor: state.controls.scatterplotXXaccessor === field, isScatterplotYYaccessor: state.controls.scatterplotYYaccessor === field, continuousSelectionRange: state.continuousSelection[myName], @@ -36,139 +455,36 @@ function clamp(val, rng) { }; }) class HistogramBrush extends React.PureComponent { - static getColumn(world, field, clipped = true) { - /* - Return the underlying Dataframe column for our field. By default, - returns the clipped column. If clipped===false, will return the - unclipped column. - */ - const obsAnnotations = clipped - ? world.obsAnnotations - : world.unclipped.obsAnnotations; - const varData = clipped ? world.varData : world.unclipped.varData; - if (obsAnnotations.hasCol(field)) { - return obsAnnotations.col(field); + /* memoized closure to prevent HistogramHeader unecessary repaint */ + handleColorAction = memoize((dispatch) => (field, isObs) => { + if (isObs) { + dispatch({ + type: "color by continuous metadata", + colorAccessor: field, + }); + } else { + dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field)); } - return varData.col(field); - } - - calcHistogramCache = memoize((col) => { - /* - recalculate expensive stuff, notably bins, summaries, etc. - */ - const histogramCache = {}; - const summary = col.summarize(); - const { min: domainMin, max: domainMax } = summary; - const numBins = 40; - - histogramCache.x = d3 - .scaleLinear() - .domain([domainMin, domainMax]) - .range([this.marginLeft, this.marginLeft + this.width]); - - histogramCache.bins = histogramContinuous(col, numBins, [ - domainMin, - domainMax, - ]); - histogramCache.binWidth = (domainMax - domainMin) / numBins; - - histogramCache.binStart = (i) => domainMin + i * histogramCache.binWidth; - histogramCache.binEnd = (i) => - domainMin + (i + 1) * histogramCache.binWidth; - - const yMax = histogramCache.bins.reduce((l, r) => (l > r ? l : r)); - - histogramCache.y = d3 - .scaleLinear() - .domain([0, yMax]) - .range([this.marginTop + this.height, this.marginTop]); - - return histogramCache; }); constructor(props) { super(props); - this.marginLeft = 10; // Space for 0 tick label on X axis - this.marginRight = 54; // space for Y axis & labels - this.marginBottom = 25; // space for X axis & labels - this.marginTop = 3; - - this.width = 340 - this.marginLeft - this.marginRight; - this.height = 135 - this.marginTop - this.marginBottom; + const marginLeft = 10; // Space for 0 tick label on X axis + const marginRight = 54; // space for Y axis & labels + const marginBottom = 25; // space for X axis & labels + const marginTop = 3; + this.margin = { + marginLeft, + marginRight, + marginBottom, + marginTop, + }; + this.width = 340 - marginLeft - marginRight; + this.height = 135 - marginTop - marginBottom; } - componentDidMount() { - const { field, isColorAccessor } = this.props; - - this.renderHistogram(this._histogram, field, isColorAccessor); - } - - componentDidUpdate(prevProps) { - const { - field, - world, - continuousSelectionRange: range, - isColorAccessor, - } = this.props; - const { x } = this._histogram; - let { brushX, brushXselection } = this.state; - - const dfColumn = HistogramBrush.getColumn(world, field); - const oldDfColumn = HistogramBrush.getColumn( - prevProps.world, - prevProps.field - ); - - const rangeChanged = range !== prevProps.continuousSelectionRange; - const dfChanged = dfColumn !== oldDfColumn; - const colorSelectionChanged = prevProps.isColorAccessor !== isColorAccessor; - - if (dfChanged || colorSelectionChanged) { - ({ brushX, brushXselection } = this.renderHistogram( - this._histogram, - field, - isColorAccessor - )); - } - - /* - if the selection has changed, ensure that the brush correctly reflects - the underlying selection. - */ - if ( - (dfChanged || rangeChanged || colorSelectionChanged) && - brushXselection - ) { - const selection = d3.brushSelection(brushXselection.node()); - if (!range && selection) { - /* no active selection - clear brush */ - brushXselection.call(brushX.move, null); - } else if (range) { - const { min, max } = dfColumn.summarize(); - const x0 = x(clamp(range[0], [min, max])); - const x1 = x(clamp(range[1], [min, max])); - if (!selection) { - /* there is an active selection, but no brush - set the brush */ - brushXselection.call(brushX.move, [x0, x1]); - } else { - /* there is an active selection and a brush - make sure they match */ - const moveDeltaThreshold = 1; - const dX0 = Math.abs(x0 - selection[0]); - const dX1 = Math.abs(x1 - selection[1]); - /* - only update the brush if it is grossly incorrect, - as defined by the moveDeltaThreshold - */ - if (dX0 > moveDeltaThreshold || dX1 > moveDeltaThreshold) { - brushXselection.call(brushX.move, [x0, x1]); - } - } - } - } - } - - onBrush(selection, x, eventType) { + onBrush = (selection, x, eventType) => { const type = `continuous metadata histogram ${eventType}`; return () => { const { dispatch, field, isObs, isUserDefined, isDiffExp } = this.props; @@ -178,33 +494,25 @@ class HistogramBrush extends React.PureComponent { // ignore cascading events, which are programmatically generated if (d3.event.sourceEvent.sourceEvent) return; - if (d3.event.selection) { - dispatch({ - type, - selection: field, - continuousNamespace: { - isObs, - isUserDefined, - isDiffExp, - }, - range: [x(d3.event.selection[0]), x(d3.event.selection[1])], - }); - } else { - dispatch({ - type, - selection: field, - continuousNamespace: { - isObs, - isUserDefined, - isDiffExp, - }, - range: null, - }); - } + const query = this.createQuery(); + const range = d3.event.selection + ? [x(d3.event.selection[0]), x(d3.event.selection[1])] + : null; + const otherProps = { + selection: field, + continuousNamespace: { + isObs, + isUserDefined, + isDiffExp, + }, + }; + dispatch( + actions.selectContinuousMetadataAction(type, query, range, otherProps) + ); }; - } + }; - onBrushEnd(selection, x) { + onBrushEnd = (selection, x) => { return () => { const { dispatch, field, isObs, isUserDefined, isDiffExp } = this.props; const minAllowedBrushSize = 10; @@ -215,14 +523,15 @@ class HistogramBrush extends React.PureComponent { // ignore cascading events, which are programmatically generated if (d3.event.sourceEvent.sourceEvent) return; + let type; + let range = null; if (d3.event.selection) { - let _range; - + type = "continuous metadata histogram end"; if ( d3.event.selection[1] - d3.event.selection[0] > minAllowedBrushSize ) { - _range = [x(d3.event.selection[0]), x(d3.event.selection[1])]; + range = [x(d3.event.selection[0]), x(d3.event.selection[1])]; } else { /* the user selected range is too small and will be hidden #587, so take control of it procedurally */ /* https://stackoverflow.com/questions/12354729/d3-js-limit-size-of-brush */ @@ -232,32 +541,26 @@ class HistogramBrush extends React.PureComponent { minAllowedBrushSize + smallAmountToAvoidInfiniteLoop; // - _range = [x(d3.event.selection[0]), x(procedurallyResizedBrushWidth)]; + range = [x(d3.event.selection[0]), x(procedurallyResizedBrushWidth)]; } - - dispatch({ - type: "continuous metadata histogram end", - selection: field, - continuousNamespace: { - isObs, - isUserDefined, - isDiffExp, - }, - range: _range, - }); } else { - dispatch({ - type: "continuous metadata histogram cancel", - selection: field, - continuousNamespace: { - isObs, - isUserDefined, - isDiffExp, - }, - }); + type = "continuous metadata histogram cancel"; } + + const query = this.createQuery(); + const otherProps = { + selection: field, + continuousNamespace: { + isObs, + isUserDefined, + isDiffExp, + }, + }; + dispatch( + actions.selectContinuousMetadataAction(type, query, range, otherProps) + ); }; - } + }; handleSetGeneAsScatterplotX = () => { const { dispatch, field } = this.props; @@ -275,37 +578,6 @@ class HistogramBrush extends React.PureComponent { }); }; - handleColorAction = () => { - const { dispatch, field, world, ranges } = this.props; - - if (world.obsAnnotations.hasCol(field)) { - dispatch({ - type: "color by continuous metadata", - colorAccessor: field, - rangeForColorAccessor: ranges, - }); - } else if (world.varData.hasCol(field)) { - dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(field)); - } - }; - - maybeScientific = (x) => { - let format = ","; - const _ticks = x.ticks(4); - - if (x.domain().some((n) => Math.abs(n) >= 10000)) { - /* - heuristic: if the last tick d3 wants to render has one significant - digit ie., 2000, render 2e+3, but if it's anything else ie., 42000000 render - 4.20e+n - */ - format = - significantDigits(_ticks[_ticks.length - 1]) === 1 ? ".0e" : ".2e"; - } - - return format; - }; - removeHistogram = () => { const { dispatch, @@ -337,126 +609,122 @@ class HistogramBrush extends React.PureComponent { } }; - drawHistogram(svgRef) { - const { field, world } = this.props; - const col = HistogramBrush.getColumn(world, field); - this._histogram = { - ...this.calcHistogramCache(col), - svgRef, - }; - } + fetchAsyncProps = async () => { + const { annoMatrix } = this.props; + const { isClipped } = annoMatrix; - renderHistogram(histogram, field, isColorAccessor) { - const { x, y, bins, svgRef, binStart, binEnd, binWidth } = histogram; - const svg = d3.select(svgRef); + const query = this.createQuery(); + const df = await annoMatrix.fetch(...query); + const column = df.icol(0); - /* Remove everything */ - svg.selectAll("*").remove(); + // if we are clipped, fetch both our value and our unclipped value, + // as we need the absolute min/max range, not just the clipped min/max. + const summary = column.summarize(); + const range = [summary.min, summary.max]; - /* Set margins within the SVG */ - const container = svg - .attr("width", this.width + this.marginLeft + this.marginRight) - .attr("height", this.height + this.marginTop + this.marginBottom) - .append("g") - .attr("class", "histogram-container") - .attr("transform", `translate(${this.marginLeft},${this.marginTop})`); - - const colorScale = d3 - .scaleSequential(interpolateCool) - .domain([0, bins.length]); - - const histogramScale = d3 - .scaleLinear() - .domain(x.domain()) - .range([ - colorScale.domain()[1], - colorScale.domain()[0], - ]); /* we flip this to make colors dark if high in the color scale */ - - if (binWidth > 0) { - /* BINS */ - container - .insert("g", "*") - .selectAll("rect") - .data(bins) - .enter() - .append("rect") - .attr("x", (d, i) => x(binStart(i)) + 1) - .attr("y", (d) => y(d)) - .attr("width", (d, i) => x(binEnd(i)) - x(binStart(i)) - 1) - .attr("height", (d) => y(0) - y(d)) - .style( - "fill", - isColorAccessor - ? (d, i) => colorScale(histogramScale(binStart(i))) - : "#bbb" - ); + let unclippedRange = [...range]; + if (isClipped) { + const parent = await annoMatrix.viewOf.fetch(...query); + const { min, max } = parent.icol(0).summarize(); + unclippedRange = [min, max]; } - // BRUSH - // Note the brushable area is bounded by the data on three sides, but goes down to cover the x-axis - const brushX = d3 - .brushX() - .extent([ - [x.range()[0], y.range()[1]], - [x.range()[1], this.marginTop + this.height + this.marginBottom], - ]) - /* - emit start so that the Undoable history can save an undo point - upon drag start, and ignore the subsequent intermediate drag events. - */ - .on("start", this.onBrush(field, x.invert, "start").bind(this)) - .on("brush", this.onBrush(field, x.invert, "brush").bind(this)) - .on("end", this.onBrushEnd(field, x.invert).bind(this)); + const unclippedRangeColor = [ + !annoMatrix.isClipped || annoMatrix.clipRange[0] === 0 + ? "#bbb" + : globals.blue, + !annoMatrix.isClipped || annoMatrix.clipRange[1] === 1 + ? "#bbb" + : globals.blue, + ]; - const brushXselection = container - .insert("g") - .attr("class", "brush") - .attr("data-testid", `${svgRef.dataset.testid}-brushable-area`) - .call(brushX); + const histogram = this.calcHistogramCache( + column, + this.margin, + this.width, + this.height + ); - /* X AXIS */ - container - .insert("g") - .attr("class", "axis axis--x") - .attr("transform", `translate(0,${this.marginTop + this.height})`) - .call( - d3 - .axisBottom(x) - .ticks(4) - .tickFormat(d3.format(this.maybeScientific(x))) - ); + const isSingleValue = summary.min === summary.max; + const nonFiniteExtent = + summary.min === undefined || + summary.max === undefined || + Number.isNaN(summary.min) || + Number.isNaN(summary.max); - /* Y AXIS */ - container - .insert("g") - .attr("class", "axis axis--y") - .attr("transform", `translate(${this.marginLeft + this.width},0)`) - .call( - d3 - .axisRight(y) - .ticks(3) - .tickFormat( - d3.format( - y.domain().some((n) => Math.abs(n) >= 10000) ? ".0e" : "," - ) - ) - ); + const OK2Render = !summary.categorical && !nonFiniteExtent; - /* axis style */ - svg.selectAll(".axis text").style("fill", "rgb(80,80,80)"); - svg.selectAll(".axis path").style("stroke", "rgb(230,230,230)"); - svg.selectAll(".axis line").style("stroke", "rgb(230,230,230)"); + return { + histogram, + range, + unclippedRange, + unclippedRangeColor, + isSingleValue, + OK2Render, + }; + }; - const newState = { brushX, brushXselection }; - this.setState(newState); - return newState; + // eslint-disable-next-line class-methods-use-this -- instance method allows for memoization per annotation + calcHistogramCache(col, margin, width, height) { + /* + recalculate expensive stuff, notably bins, summaries, etc. + */ + const histogramCache = {}; + const summary = col.summarize(); + const { min: domainMin, max: domainMax } = summary; + const numBins = 40; + const { marginTop, marginLeft } = margin; + + histogramCache.domain = [domainMin, domainMax]; + + histogramCache.x = d3 + .scaleLinear() + .domain([domainMin, domainMax]) + .range([marginLeft, marginLeft + width]); + + histogramCache.bins = histogramContinuous(col, numBins, [ + domainMin, + domainMax, + ]); + histogramCache.binWidth = (domainMax - domainMin) / numBins; + + histogramCache.binStart = (i) => domainMin + i * histogramCache.binWidth; + histogramCache.binEnd = (i) => + domainMin + (i + 1) * histogramCache.binWidth; + + const yMax = histogramCache.bins.reduce((l, r) => (l > r ? l : r)); + + histogramCache.y = d3 + .scaleLinear() + .domain([0, yMax]) + .range([marginTop + height, marginTop]); + + return histogramCache; + } + + createQuery() { + const { isObs, field, annoMatrix } = this.props; + const { schema } = annoMatrix; + if (isObs) { + return ["obs", field]; + } + const varIndex = schema?.annotations?.var?.index; + if (!varIndex) return null; + return [ + "X", + { + field: "var", + column: varIndex, + value: field, + }, + ]; } render() { const { + dispatch, + annoMatrix, field, - world, isColorAccessor, isUserDefined, isDiffExp, @@ -465,146 +733,83 @@ class HistogramBrush extends React.PureComponent { isScatterplotXXaccessor, isScatterplotYYaccessor, zebra, + continuousSelectionRange, + isObs, } = this.props; const fieldForId = field.replace(/\s/g, "_"); - const { - min: unclippedRangeMin, - max: unclippedRangeMax, - } = HistogramBrush.getColumn(world, field, false).summarize(); - const unclippedRangeMinColor = - world.clipQuantiles.min === 0 ? "#bbb" : globals.blue; - const unclippedRangeMaxColor = - world.clipQuantiles.max === 1 ? "#bbb" : globals.blue; + const showScatterPlot = isDiffExp || isUserDefined; return ( -
-
- {isDiffExp || isUserDefined ? ( - - - - - - - - ) : null} - {isUserDefined ? ( - - ) : null} - -
- this.drawHistogram(svgRef)} - /> -
- - min {unclippedRangeMin.toPrecision(4)} - - - {field} - - - max {unclippedRangeMax.toPrecision(4)} - -
- - {isDiffExp ? ( -
- - log fold change: - {` ${logFoldChange.toPrecision(4)}`} - - - p-value (adj): - {pvalAdj < 0.0001 ? " < 0.0001" : ` ${pvalAdj.toFixed(4)}`} - -
- ) : null} -
+ + + + + + {(error) => ( + + )} + + + {(asyncProps) => + asyncProps.OK2Render ? ( +
+ + + +
+ ) : null + } +
+
); } } diff --git a/client/src/components/categorical/annoDialog.js b/client/src/components/categorical/annoDialog.js index 8a169969..fdb07e77 100644 --- a/client/src/components/categorical/annoDialog.js +++ b/client/src/components/categorical/annoDialog.js @@ -1,13 +1,6 @@ import React from "react"; -import { connect } from "react-redux"; import { Button, Tooltip, Dialog, Classes, Colors } from "@blueprintjs/core"; -@connect((state) => ({ - colorAccessor: state.colors.colorAccessor, - categoricalSelection: state.categoricalSelection, - annotations: state.annotations, - universe: state.universe, -})) class AnnoDialog extends React.PureComponent { constructor(props) { super(props); @@ -72,7 +65,7 @@ class AnnoDialog extends React.PureComponent { ) : null}
-
-
- ); - } - - render() { - const { isChecked } = this.state; - const { - metadataField, - categoricalSelection, - isColorAccessor, - isExpanded, - schema, - } = this.props; - - const isStillLoading = !(categoricalSelection?.[metadataField] ?? false); - if (isStillLoading) { - return this.renderIsStillLoading(); - } - - const checkboxID = `category-select-${metadataField}`; - - const isUserAnno = - schema?.annotations?.obsByName[metadataField]?.writable ?? false; - const isTruncated = _.get( - categoricalSelection, - [metadataField, "isTruncated"], - false - ); - - if ( - !isUserAnno && - schema?.annotations?.obsByName[metadataField]?.categories?.length === 1 - ) { - return ( -
+ - + {metadataField} - - - {`: ${schema.annotations.obsByName[metadataField].categories[0]}`} - -
- ); - } +
+
+
+ + ); +}; + +const ErrorLoading = ({ metadataField, error }) => { + console.error(error); // log error to console as it is unexpected. + return ( +
+ + {`Failure loading ${metadataField}`} + +
+ ); +}; + +const CategoryHeader = React.memo( + ({ + metadataField, + checkboxID, + isUserAnno, + isTruncated, + isColorAccessor, + isExpanded, + selectionState, + onColorChangeClick, + onCategoryMenuClick, + onCategoryMenuKeyPress, + onCategoryToggleAllClick, + }) => { + /* + Render category name and controls (eg, color-by button). + */ + const checkboxRef = useRef(null); + + useEffect(() => { + checkboxRef.current.indeterminate = selectionState === "some"; + }, [checkboxRef.current, selectionState]); return ( - + <>
{ - this.checkbox = el; - return el; - }} - checked={isChecked} + onChange={onCategoryToggleAllClick} + ref={checkboxRef} + checked={selectionState === "all"} type="checkbox" /> @@ -241,16 +390,13 @@ class Category extends React.Component { { - if (e.key === "Enter") { - this.handleCategoryClick(); - } - }} + onKeyPress={onCategoryMenuKeyPress} style={{ cursor: "pointer", }} - onClick={this.handleCategoryClick} + onClick={onCategoryMenuClick} >
-
+ ); } -} +); -export default Category; +const CategoryRender = React.memo( + ({ + metadataField, + checkboxID, + isUserAnno, + isTruncated, + isColorAccessor, + isExpanded, + selectionState, + categoryData, + categorySummary, + colorAccessor, + colorData, + colorTable, + onColorChangeClick, + onCategoryMenuClick, + onCategoryMenuKeyPress, + onCategoryToggleAllClick, + }) => { + /* + Render the core of the category, including checkboxes, controls, etc. + */ + const { numCategoryValues } = categorySummary; + const isSingularValue = !isUserAnno && numCategoryValues === 1; + + if (isSingularValue) { + /* + Entire category has a single value, special case. + */ + const theOneValue = categorySummary.categoryValues[0]; + return ( +
+ + + {metadataField} + + + + {`: ${theOneValue}`} + +
+ ); + } + + /* + Otherwise, our normal multi-layout layout + */ + return ( +
+
+ +
+
+ { + /* values*/ + isExpanded ? ( + + ) : null + } +
+
+ {isExpanded && isTruncated ? ( +

... truncated list ...

+ ) : null} +
+
+ ); + } +); + +const CategoryValueList = React.memo( + ({ + isUserAnno, + metadataField, + categoryData, + categorySummary, + colorAccessor, + colorData, + colorTable, + }) => { + const tuples = [...categorySummary.categoryValueIndices]; + + /* + Render the value list. If this is a user annotation, we use a flipper + animation, if read-only, we don't bother and save a few bits of perf. + */ + if (!isUserAnno) { + return ( + <> + {tuples.map(([value, index]) => ( + + ))} + + ); + } + + /* User annotation */ + const flipKey = tuples.map((t) => t[0]).join(""); + return ( + + {tuples.map(([value, index]) => ( + + + + ))} + + ); + } +); diff --git a/client/src/components/categorical/categoryContext.js b/client/src/components/categorical/categoryContext.js new file mode 100644 index 00000000..5ec1b29f --- /dev/null +++ b/client/src/components/categorical/categoryContext.js @@ -0,0 +1,7 @@ +import React from "react"; + +/* +CategoryCrossfilterContext is used to pass a snapshot of the crossfilter +matching the current category summary. +*/ +export const CategoryCrossfilterContext = React.createContext(null); diff --git a/client/src/components/categorical/index.js b/client/src/components/categorical/index.js index d7da2a41..0e48eba8 100644 --- a/client/src/components/categorical/index.js +++ b/client/src/components/categorical/index.js @@ -9,11 +9,11 @@ import AnnoDialog from "./annoDialog"; import AnnoSelect from "./annoSelect"; import LabelInput from "./labelInput"; import { labelPrompt } from "./labelUtil"; +import actions from "../../actions"; @connect((state) => ({ writableCategoriesEnabled: state.config?.parameters?.annotations ?? false, - schema: state.world?.schema, - config: state.config, + schema: state.annoMatrix?.schema, ontology: state.ontology, })) class Categories extends React.Component { @@ -30,11 +30,12 @@ class Categories extends React.Component { handleCreateUserAnno = (e) => { const { dispatch } = this.props; const { newCategoryText, categoryToDuplicate } = this.state; - dispatch({ - type: "annotation: create category", - data: newCategoryText, - categoryToDuplicate, - }); + dispatch( + actions.annotationCreateCategoryAction( + newCategoryText, + categoryToDuplicate + ) + ); this.setState({ createAnnoModeActive: false, categoryToDuplicate: null, @@ -126,12 +127,11 @@ class Categories extends React.Component { newCategoryText, expandedCats, } = this.state; - const { writableCategoriesEnabled, schema, config, ontology } = this.props; + const { writableCategoriesEnabled, schema, ontology } = this.props; const ontologyEnabled = ontology?.enabled ?? false; /* all names, sorted in display order. Will be rendered in this order */ const allCategoryNames = ControlsHelpers.selectableCategoryNames( - schema, - ControlsHelpers.maxCategoryItems(config) + schema ).sort(); return ( diff --git a/client/src/components/categorical/labelInput.js b/client/src/components/categorical/labelInput.js index 27c57729..be460a6d 100644 --- a/client/src/components/categorical/labelInput.js +++ b/client/src/components/categorical/labelInput.js @@ -142,7 +142,7 @@ export default class LabelInput extends React.PureComponent { return ( diff --git a/client/src/components/categorical/value/index.js b/client/src/components/categorical/value/index.js index c6c2a6a9..f40a9a99 100644 --- a/client/src/components/categorical/value/index.js +++ b/client/src/components/categorical/value/index.js @@ -1,5 +1,6 @@ import { connect } from "react-redux"; import React from "react"; +import * as d3 from "d3"; import { Button, @@ -10,7 +11,6 @@ import { Icon, PopoverInteractionKind, } from "@blueprintjs/core"; -import Occupancy from "./occupancy"; import * as globals from "../../../globals"; import styles from "../categorical.css"; import AnnoDialog from "../annoDialog"; @@ -19,110 +19,106 @@ import Truncate from "../../util/truncate"; import { AnnotationsHelpers } from "../../../util/stateManager"; import { labelPrompt, isLabelErroneous } from "../labelUtil"; +import actions from "../../../actions"; +import MiniHistogram from "../../miniHistogram"; +import MiniStackedBar from "../../miniStackedBar"; +import { CategoryCrossfilterContext } from "../categoryContext"; + +const VALUE_HEIGHT = 11; +const CHART_WIDTH = 100; /* this is defined outside of the class so we can use it in connect() */ -function _currentLabel(ownProps, categoricalSelection) { - const { metadataField, categoryIndex } = ownProps; - return String( - categoricalSelection[metadataField].categoryValues[categoryIndex] - ).valueOf(); +function _currentLabelAsString(ownProps) { + const { label } = ownProps; + // when called as a function, the String() constructor performs type conversion, + // and returns a primitive string. + return String(label); } @connect((state, ownProps) => { const { pointDilation, categoricalSelection } = state; - const { metadataField } = ownProps; + const { metadataField, categorySummary, categoryIndex } = ownProps; const isDilated = pointDilation.metadataField === metadataField && - pointDilation.categoryField === - _currentLabel(ownProps, categoricalSelection); + pointDilation.categoryField === _currentLabelAsString(ownProps); + + const category = categoricalSelection[metadataField]; + const label = categorySummary.categoryValues[categoryIndex]; + const isSelected = category.get(label) ?? true; + return { - categoricalSelection, annotations: state.annotations, - colorScale: state.colors.scale, - colorAccessor: state.colors.colorAccessor, - schema: state.world?.schema, - world: state.world, - crossfilter: state.crossfilter, + schema: state.annoMatrix?.schema, ontology: state.ontology, isDilated, + isSelected, + label, }; }) class CategoryValue extends React.Component { constructor(props) { super(props); this.state = { - editedLabelText: this.currentLabel(), + editedLabelText: this.currentLabelAsString(), }; } componentDidUpdate(prevProps) { - const { categoricalSelection, metadataField, categoryIndex } = this.props; + const { metadataField, categoryIndex, categorySummary } = this.props; if ( - prevProps.categoricalSelection !== categoricalSelection || prevProps.metadataField !== metadataField || - prevProps.categoryIndex !== categoryIndex + prevProps.categoryIndex !== categoryIndex || + prevProps.categorySummary !== categorySummary ) { - // adequately checked to prevent looping - // eslint-disable-next-line react/no-did-update-set-state + // eslint-disable-next-line react/no-did-update-set-state --- adequately checked to prevent looping this.setState({ - editedLabelText: this.currentLabel(), + editedLabelText: this.currentLabelAsString(), }); } } - handleDeleteValue = () => { - const { dispatch, metadataField } = this.props; - const label = this.getLabel(); + // If coloring by and this isn't the colorAccessor and it isn't being edited + get shouldRenderStackedBarOrHistogram() { + const { colorAccessor, isColorBy, annotations } = this.props; - dispatch({ - type: "annotation: delete label", - metadataField, - label, - }); + return colorAccessor && !isColorBy && !annotations.isEditingLabelName; + } + + handleDeleteValue = () => { + const { dispatch, metadataField, label } = this.props; + dispatch(actions.annotationDeleteLabelFromCategory(metadataField, label)); }; handleAddCurrentSelectionToThisLabel = () => { - const { dispatch, metadataField, categoryIndex } = this.props; - const label = this.getLabel(); - dispatch({ - type: "annotation: label current cell selection", - metadataField, - categoryIndex, - label, - }); + const { dispatch, metadataField, label } = this.props; + dispatch(actions.annotationLabelCurrentSelection(metadataField, label)); }; handleEditValue = (e) => { - const { dispatch, metadataField, categoryIndex } = this.props; + const { dispatch, metadataField, label } = this.props; const { editedLabelText } = this.state; - const label = this.getLabel(); this.cancelEditMode(); - dispatch({ - type: "annotation: label edited", - editedLabel: editedLabelText, - metadataField, - categoryIndex, - label, - }); + dispatch( + actions.annotationRenameLabelInCategory( + metadataField, + label, + editedLabelText + ) + ); e.preventDefault(); }; handleCreateArbitraryLabel = (txt) => { - const { dispatch, metadataField, categoryIndex } = this.props; - const label = this.getLabel(); + const { dispatch, metadataField, label } = this.props; this.cancelEditMode(); - dispatch({ - type: "annotation: label edited", - metadataField, - editedLabel: txt, - categoryIndex, - label, - }); + dispatch( + actions.annotationRenameLabelInCategory(metadataField, label, txt) + ); }; labelNameError = (name) => { const { metadataField, ontology, schema } = this.props; - if (name === this.currentLabel()) return false; + if (name === this.currentLabelAsString()) return false; return isLabelErroneous(name, metadataField, ontology, schema); }; @@ -131,33 +127,45 @@ class CategoryValue extends React.Component { }; activateEditLabelMode = () => { - const { dispatch, metadataField, categoryIndex } = this.props; + const { dispatch, metadataField, categoryIndex, label } = this.props; dispatch({ type: "annotation: activate edit label mode", metadataField, categoryIndex, + label, }); }; cancelEditMode = () => { - const { dispatch, metadataField, categoryIndex } = this.props; + const { dispatch, metadataField, categoryIndex, label } = this.props; this.setState({ - editedLabelText: this.currentLabel(), + editedLabelText: this.currentLabelAsString(), }); dispatch({ type: "annotation: cancel edit label mode", metadataField, categoryIndex, + label, }); }; toggleOff = () => { - const { dispatch, metadataField, categoryIndex } = this.props; - dispatch({ - type: "categorical metadata filter deselect", + const { + dispatch, metadataField, categoryIndex, - }); + categorySummary, + } = this.props; + const label = categorySummary.categoryValues[categoryIndex]; + dispatch( + actions.selectCategoricalMetadataAction( + "categorical metadata filter deselect", + metadataField, + categorySummary.allCategoryValues, + label, + false + ) + ); }; shouldComponentUpdate = (nextProps, nextState) => { @@ -171,60 +179,74 @@ class CategoryValue extends React.Component { If and only if true, update the component */ const { props, state } = this; - const { metadataField, categoryIndex, categoricalSelection } = props; - const { categoricalSelection: newCategoricalSelection } = nextProps; + const { categoryIndex, categorySummary, isSelected } = props; + const { + categoryIndex: newCategoryIndex, + categorySummary: newCategorySummary, + isSelected: newIsSelected, + } = nextProps; - const valueSelectionChange = - categoricalSelection[metadataField].categoryValueSelected[ - categoryIndex - ] !== - newCategoricalSelection[metadataField].categoryValueSelected[ - categoryIndex - ]; + const label = categorySummary.categoryValues[categoryIndex]; + const newLabel = newCategorySummary.categoryValues[newCategoryIndex]; + const labelChanged = label !== newLabel; + const valueSelectionChange = isSelected !== newIsSelected; - const worldChange = props.world !== nextProps.world; const colorAccessorChange = props.colorAccessor !== nextProps.colorAccessor; const annotationsChange = props.annotations !== nextProps.annotations; - const crossfilterChange = - props.isUserAnno && props.crossfilter !== nextProps.crossfilter; const editingLabel = state.editedLabelText !== nextState.editedLabelText; const dilationChange = props.isDilated !== nextProps.isDilated; + const count = categorySummary.categoryValueCounts[categoryIndex]; + const newCount = newCategorySummary.categoryValueCounts[newCategoryIndex]; + const countChanged = count !== newCount; + return ( + labelChanged || valueSelectionChange || - worldChange || colorAccessorChange || annotationsChange || - crossfilterChange || editingLabel || - dilationChange + dilationChange || + countChanged ); }; toggleOn = () => { - const { dispatch, metadataField, categoryIndex } = this.props; - dispatch({ - type: "categorical metadata filter select", + const { + dispatch, metadataField, categoryIndex, - }); + categorySummary, + } = this.props; + const label = categorySummary.categoryValues[categoryIndex]; + dispatch( + actions.selectCategoricalMetadataAction( + "categorical metadata filter select", + metadataField, + categorySummary.allCategoryValues, + label, + true + ) + ); }; handleMouseEnter = () => { - const { dispatch, metadataField, categoryIndex } = this.props; + const { dispatch, metadataField, categoryIndex, label } = this.props; dispatch({ type: "category value mouse hover start", metadataField, categoryIndex, + label, }); }; handleMouseExit = () => { - const { dispatch, metadataField, categoryIndex } = this.props; + const { dispatch, metadataField, categoryIndex, label } = this.props; dispatch({ type: "category value mouse hover end", metadataField, categoryIndex, + label, }); }; @@ -237,26 +259,103 @@ class CategoryValue extends React.Component { this.setState({ editedLabelText: e.target }); }; - getLabel = () => { - const { metadataField, categoryIndex, categoricalSelection } = this.props; - const category = categoricalSelection[metadataField]; - const label = category.categoryValues[categoryIndex]; + createHistogramBins = ( + metadataField, + categoryData, + colorAccessor, + colorData, + categoryValue, + width, + height + ) => { + /* + Knowing that colorScale is based off continuous data, + createHistogramBins fetches the continuous data in relation to the cells relevant to the category value. + It then separates that data into 50 bins for drawing the mini-histogram + */ + const groupBy = categoryData.col(metadataField); + const col = colorData.icol(0); + const range = col.summarize(); - return label; + const histogramMap = col.histogram( + 50, + [range.min, range.max], + groupBy + ); /* Because the signature changes we really need different names for histogram to differentiate signatures */ + + const bins = histogramMap.has(categoryValue) + ? histogramMap.get(categoryValue) + : new Array(50).fill(0); + + const xScale = d3.scaleLinear().domain([0, bins.length]).range([0, width]); + + const largestBin = Math.max(...bins); + + const yScale = d3.scaleLinear().domain([0, largestBin]).range([0, height]); + + return { + xScale, + yScale, + bins, + }; }; - currentLabel() { - const { categoricalSelection } = this.props; - return _currentLabel(this.props, categoricalSelection); + createStackedGraphBins = ( + metadataField, + categoryData, + colorAccessor, + colorData, + categoryValue, + colorTable, + schema, + width + ) => { + /* + Knowing that the color scale is based off of categorical data, + createOccupancyStack obtains a map showing the number if cells per colored value + Using the colorScale a stack of colored bars is drawn representing the map + */ + const groupBy = categoryData.col(metadataField); + const occupancyMap = colorData + .col(colorAccessor) + .histogramCategorical(groupBy); + + const occupancy = occupancyMap.get(categoryValue); + + if (occupancy && occupancy.size > 0) { + // not all categories have occupancy, so occupancy may be undefined. + const scale = d3 + .scaleLinear() + /* get all the keys d[1] as an array, then find the sum */ + .domain([0, d3.sum(Array.from(occupancy.values()))]) + .range([0, width]); + const categories = + schema.annotations.obsByName[colorAccessor]?.categories; + + const dfColumn = colorData.col(colorAccessor); + const categoryValues = dfColumn.summarizeCategorical().categories; + + return { + domainValues: categoryValues, + scale, + domain: categories, + occupancy, + }; + } + return null; + }; + + currentLabelAsString() { + return _currentLabelAsString(this.props); } - isAddCurrentSelectionDisabled(category, value) { + isAddCurrentSelectionDisabled(crossfilter, category, value) { /* disable "add current selection to label", if one of the following is true: 1. no cells are selected 2. all currently selected cells already have this label, on this category */ - const { crossfilter, world } = this.props; + const { categoryData } = this.props; // 1. no cells selected? if (crossfilter.countSelected() === 0) { @@ -265,12 +364,7 @@ class CategoryValue extends React.Component { // 2. all selected cells already have the label const mask = crossfilter.allSelectedMask(); if ( - AnnotationsHelpers.allHaveLabelByMask( - world.obsAnnotations, - category, - value, - mask - ) + AnnotationsHelpers.allHaveLabelByMask(categoryData, category, value, mask) ) { return true; } @@ -278,43 +372,132 @@ class CategoryValue extends React.Component { return false; } + renderMiniStackedBar = () => { + const { + colorAccessor, + metadataField, + categoryData, + colorData, + colorTable, + schema, + label, + } = this.props; + const isColorBy = metadataField === colorAccessor; + + if ( + !this.shouldRenderStackedBarOrHistogram || + !AnnotationsHelpers.isCategoricalAnnotation(schema, colorAccessor) || + isColorBy + ) { + return null; + } + + const { domainValues, scale, domain, occupancy } = + this.createStackedGraphBins( + metadataField, + categoryData, + colorAccessor, + colorData, + label, + colorTable, + schema, + CHART_WIDTH + ) ?? {}; + + if (!domainValues || !scale || !domain || !occupancy) { + return null; + } + + return ( + + ); + }; + + renderMiniHistogram = () => { + const { + colorAccessor, + metadataField, + colorData, + categoryData, + colorTable, + schema, + label, + } = this.props; + const colorScale = colorTable?.scale; + + if ( + !this.shouldRenderStackedBarOrHistogram || + !AnnotationsHelpers.isContinuousAnnotation(schema, colorAccessor) + ) { + return null; + } + + const { xScale, yScale, bins } = + this.createHistogramBins( + metadataField, + categoryData, + colorAccessor, + colorData, + label, + CHART_WIDTH, + VALUE_HEIGHT + ) ?? {}; + + return ( + + ); + }; + render() { const { - categoricalSelection, metadataField, categoryIndex, colorAccessor, - colorScale, - i, - schema, + colorTable, isUserAnno, annotations, ontology, - // flippedProps is potentially brittle, their docs want {...flippedProps} on our div, - // our lint doesn't like jsx spread, we are version pinned to prevent api change on their part - flippedProps, isDilated, - world, + isSelected, + categorySummary, + label, } = this.props; + const colorScale = colorTable?.scale; const ontologyEnabled = ontology?.enabled ?? false; const { editedLabelText } = this.state; - if (!categoricalSelection) return null; - - const category = categoricalSelection[metadataField]; - const selected = category.categoryValueSelected[categoryIndex]; - const count = category.categoryValueCounts[categoryIndex]; - const value = category.categoryValues[categoryIndex]; - const displayString = this.currentLabel(); + const count = categorySummary.categoryValueCounts[categoryIndex]; + const displayString = this.currentLabelAsString(); /* this is the color scale, so add swatches below */ const isColorBy = metadataField === colorAccessor; - let categories = null; - - if (isColorBy && schema) { - categories = schema.annotations.obsByName[colorAccessor]?.categories; - } + const { categoryValueIndices } = categorySummary; const editModeActive = isUserAnno && @@ -322,13 +505,14 @@ class CategoryValue extends React.Component { annotations.isEditingLabelName && annotations.labelEditable.label === categoryIndex; - const valueToggleLabel = `value-toggle-checkbox-${displayString}`; + const valueToggleLabel = `value-toggle-checkbox-${metadataField}-${displayString}`; - const LEFT_MARGIN = 33; + const LEFT_MARGIN = 60; const CHECKBOX = 26; - const CELL_NUMBER = 61; + const CELL_NUMBER = 50; const ANNO_MENU = 26; - const LABEL_MARGIN = 24; + const LABEL_MARGIN = 16; + const CHART_MARGIN = 24; const otherElementsWidth = LEFT_MARGIN + @@ -337,19 +521,16 @@ class CategoryValue extends React.Component { LABEL_MARGIN + (isUserAnno ? ANNO_MENU : 0); - const OCCUPANCY_WIDTH = 100; - const labelWidth = colorAccessor && !isColorBy - ? globals.leftSidebarWidth - otherElementsWidth - OCCUPANCY_WIDTH + ? globals.leftSidebarWidth - + otherElementsWidth - + CHART_WIDTH - + CHART_MARGIN : globals.leftSidebarWidth - otherElementsWidth; return (
- {colorAccessor && !isColorBy && !annotations.isEditingLabelName ? ( - - ) : null} + {this.renderMiniStackedBar()} + {this.renderMiniHistogram()}
@@ -492,14 +665,14 @@ class CategoryValue extends React.Component { @@ -514,32 +687,37 @@ class CategoryValue extends React.Component { position={Position.RIGHT_TOP} content={ - - Re-label currently selected cells as - - {` ${displayString}`} - - - } - disabled={this.isAddCurrentSelectionDisabled( - metadataField, - value + + {(crossfilter) => ( + + Re-label currently selected cells as + + {` ${displayString}`} + + + } + disabled={this.isAddCurrentSelectionDisabled( + crossfilter, + metadataField, + label + )} + /> )} - /> + {displayString !== globals.unassignedCategoryLabel ? ( ({ + schema: state.annoMatrix?.schema, +})) class Occupancy extends React.PureComponent { _WIDTH = 100; @@ -21,16 +23,17 @@ class Occupancy extends React.PureComponent { createHistogram fetches the continous data in relation to the cells releveant to the catagory value. It then seperates that data into 50 bins for drawing the mini-histogram */ - const { world, metadataField, colorAccessor, categoryValue } = this.props; + const { + metadataField, + categoryData, + colorData, + categoryValue, + } = this.props; if (!this.canvas) return; - const groupBy = world.obsAnnotations.col(metadataField); - - const col = - world.obsAnnotations.col(colorAccessor) || - world.varData.col(colorAccessor); - + const groupBy = categoryData.col(metadataField); + const col = colorData.icol(0); const range = col.summarize(); const histogramMap = col.histogram( @@ -39,7 +42,6 @@ class Occupancy extends React.PureComponent { groupBy ); /* Because the signature changes we really need different names for histogram to differentiate signatures */ - // const categoryValue = category.categoryValues[categoryIndex]; const bins = histogramMap.has(categoryValue) ? histogramMap.get(categoryValue) : new Array(50).fill(0); @@ -79,20 +81,22 @@ class Occupancy extends React.PureComponent { Using the colorScale a stack of colored bars is drawn representing the map */ const { - world, metadataField, + categoryData, colorAccessor, categoryValue, - colorScale, + colorTable, + schema, + colorData, } = this.props; - const { schema } = world; + const { scale: colorScale } = colorTable; const ctx = this.canvas?.getContext("2d"); if (!ctx) return; - const groupBy = world.obsAnnotations.col(metadataField); - const occupancyMap = world.obsAnnotations + const groupBy = categoryData.col(metadataField); + const occupancyMap = colorData .col(colorAccessor) .histogramCategorical(groupBy); @@ -109,7 +113,7 @@ class Occupancy extends React.PureComponent { schema.annotations.obsByName[colorAccessor]?.categories; let currentOffset = 0; - const dfColumn = world.obsAnnotations.col(colorAccessor); + const dfColumn = colorData.col(colorAccessor); const categoryValues = dfColumn.summarizeCategorical().categories; let o; diff --git a/client/src/components/continuous/continuous.js b/client/src/components/continuous/continuous.js index c0a170b3..3752aee6 100644 --- a/client/src/components/continuous/continuous.js +++ b/client/src/components/continuous/continuous.js @@ -1,97 +1,28 @@ -// jshint esversion: 6 /* rc slider https://www.npmjs.com/package/rc-slider */ import React from "react"; import { connect } from "react-redux"; -import { Button } from "@blueprintjs/core"; - -import * as globals from "../../globals"; import HistogramBrush from "../brushableHistogram"; @connect((state) => ({ - obsAnnotations: state.world?.obsAnnotations, - colorAccessor: state.colors.colorAccessor, - colorScale: state.colors.scale, - schema: state.world?.schema, + schema: state.annoMatrix?.schema, })) class Continuous extends React.PureComponent { - static renderIsStillLoading(zebra, key) { - return ( -
-
-
-
- {key} -
-
-
-
-
- ); - } - render() { - const { obsAnnotations, schema } = this.props; - + /* initial value for iterator to simulate index, ranges is an object */ + const { schema } = this.props; + if (!schema) return null; const obsIndex = schema.annotations.obs.index; const allContinuousNames = schema.annotations.obs.columns .filter((col) => col.type === "int32" || col.type === "float32") .filter((col) => col.name !== obsIndex) .map((col) => col.name); - /* initial value for iterator to simulate index, ranges is an object */ - let zebra = 0; - return (
- {allContinuousNames.map((key) => { - if (!obsAnnotations.hasCol(key)) { - // still loading! - zebra += 1; - return Continuous.renderIsStillLoading(zebra, key); - } - - // data loaded and available - const summary = obsAnnotations.col(key).summarize(); - const nonFiniteExtent = - summary.min === undefined || - summary.max === undefined || - Number.isNaN(summary.min) || - Number.isNaN(summary.max); - if (!summary.categorical && !nonFiniteExtent) { - zebra += 1; - return ( - - ); - } - - return null; - })} + {allContinuousNames.map((key, zebra) => ( + + ))}
); } diff --git a/client/src/components/continuousLegend/index.js b/client/src/components/continuousLegend/index.js index e13d63eb..c1153b22 100644 --- a/client/src/components/continuousLegend/index.js +++ b/client/src/components/continuousLegend/index.js @@ -4,6 +4,11 @@ import { connect } from "react-redux"; import * as d3 from "d3"; import { interpolateCool } from "d3-scale-chromatic"; +import { + createColorTable, + createColorQuery, +} from "../../util/stateManager/colorHelpers"; + // create continuous color legend // http://bl.ocks.org/syntagmatic/e8ccca52559796be775553b467593a9f const continuous = (selectorId, colorscale, colorAccessor) => { @@ -101,34 +106,78 @@ const continuous = (selectorId, colorscale, colorAccessor) => { }; @connect((state) => ({ - colorAccessor: state.colors.colorAccessor, - colorScale: state.colors.scale, + annoMatrix: state.annoMatrix, + colors: state.colors, })) class ContinuousLegend extends React.Component { + constructor(props) { + super(props); + this.ref = null; + this.state = { + colorAccessor: null, + colorScale: null, + }; + } + + componentDidMount() { + this.updateState(null); + } + componentDidUpdate(prevProps) { - const { colorAccessor, colorScale } = this.props; - if ( - prevProps.colorAccessor !== colorAccessor || - prevProps.colorScale !== colorScale - ) { + this.updateState(prevProps); + } + + async updateState(prevProps) { + const { annoMatrix, colors } = this.props; + if (!colors || !annoMatrix) return; + + if (colors !== prevProps?.colors || annoMatrix !== prevProps?.annoMatrix) { + const { schema } = annoMatrix; + const { colorMode, colorAccessor, userColors } = colors; + const colorQuery = createColorQuery(colorMode, colorAccessor, schema); + const colorDf = colorQuery ? await annoMatrix.fetch(...colorQuery) : null; + const colorTable = createColorTable( + colorMode, + colorAccessor, + colorDf, + schema, + userColors + ); + + const colorScale = colorTable.scale; + const range = colorScale?.range; + const [domainMin, domainMax] = colorScale?.domain?.() ?? [0, 0]; + /* always remove it, if it's not continuous we don't put it back. */ d3.select("#continuous_legend").selectAll("*").remove(); - } - if (colorAccessor && colorScale && colorScale.range) { - /* fragile! continuous range is 0 to 1, not [#fa4b2c, ...], make this a flag? */ - if (colorScale.range()[0][0] !== "#") { - continuous( - "#continuous_legend", - d3.scaleSequential(interpolateCool).domain(colorScale.domain()), - colorAccessor - ); + if (colorAccessor && colorScale && range && domainMin < domainMax) { + /* fragile! continuous range is 0 to 1, not [#fa4b2c, ...], make this a flag? */ + if (range()[0][0] !== "#") { + continuous( + "#continuous_legend", + d3.scaleSequential(interpolateCool).domain(colorScale.domain()), + colorAccessor + ); + } } + + this.setState({ + colorAccessor, + colorScale: colorTable.scale, + }); } } render() { - const { colorAccessor } = this.props; + const { colorAccessor, colorScale } = this.state; + if ( + colorScale?.domain && + colorScale.domain()[1] === colorScale.domain()[0] + ) { + /* it's a single value, not a distribution, min max are the same */ + return null; + } return (
/* a hard network error */ -export const postNetworkErrorToast = (message) => - ToastTopCenter.show({ - message, - timeout: 30000, - intent: Intent.DANGER, - }); +export const postNetworkErrorToast = (message, key = undefined) => + ToastTopCenter.show( + { + message, + timeout: 30000, + intent: Intent.DANGER, + }, + key + ); /* Async message to user diff --git a/client/src/components/geneExpression/addGenes.js b/client/src/components/geneExpression/addGenes.js index 7c845a7b..bc3e2f82 100644 --- a/client/src/components/geneExpression/addGenes.js +++ b/client/src/components/geneExpression/addGenes.js @@ -1,4 +1,3 @@ -// jshint esversion: 6 /* rc slider https://www.npmjs.com/package/rc-slider */ import React from "react"; @@ -34,9 +33,6 @@ const renderGene = (fuzzySortResult, { handleClick, modifiers }) => { active={modifiers.active} disabled={modifiers.disabled} data-testid={`suggest-menu-item-${geneName}`} - // Use of annotations in this way is incorrect and dataset specific. - // See https://github.com/chanzuckerberg/cellxgene/issues/483 - // label={gene.n_counts} key={geneName} onClick={(g) => /* this fires when user clicks a menu item */ @@ -56,11 +52,9 @@ const filterGenes = (query, genes) => @connect((state) => { return { - obsAnnotations: state.world?.obsAnnotations, + annoMatrix: state.annoMatrix, userDefinedGenes: state.controls.userDefinedGenes, userDefinedGenesLoading: state.controls.userDefinedGenesLoading, - world: state.world, - colorAccessor: state.colors.colorAccessor, differential: state.differential, }; }) @@ -71,9 +65,19 @@ class AddGenes extends React.Component { bulkAdd: "", tab: "autosuggest", activeItem: null, + geneNames: [], + status: "pending", }; } + componentDidMount() { + this.updateState(); + } + + componentDidUpdate(prevProps) { + this.updateState(prevProps); + } + _genesToUpper = (listGenes) => { // Has to be a Map to preserve index const upperGenes = new Map(); @@ -84,13 +88,12 @@ class AddGenes extends React.Component { return upperGenes; }; - // eslint-disable-next-line react/sort-comp + // eslint-disable-next-line react/sort-comp -- memo requires a defined _genesToUpper _memoGenesToUpper = memoize(this._genesToUpper, (arr) => arr); handleBulkAddClick = () => { - const { world, dispatch, userDefinedGenes } = this.props; - const varIndexName = world.schema.annotations.var.index; - const { bulkAdd } = this.state; + const { dispatch, userDefinedGenes } = this.props; + const { bulkAdd, geneNames } = this.state; /* test: @@ -98,18 +101,14 @@ class AddGenes extends React.Component { */ if (bulkAdd !== "") { const genes = _.pull(_.uniq(bulkAdd.split(/[ ,]+/)), ""); - console.log("geneExpression genes", genes); if (genes.length === 0) { return keepAroundErrorToast("Must enter a gene name."); } - const worldGenes = - world.varAnnotations?.col(varIndexName)?.asArray() || []; - // These gene lists are unique enough where memoization is useless const upperGenes = this._genesToUpper(genes); const upperUserDefinedGenes = this._genesToUpper(userDefinedGenes); - const upperWorldGenes = this._memoGenesToUpper(worldGenes); + const upperGeneNames = this._memoGenesToUpper(geneNames); dispatch({ type: "bulk user defined gene start" }); @@ -119,7 +118,7 @@ class AddGenes extends React.Component { return keepAroundErrorToast("That gene already exists"); } - const indexOfGene = upperWorldGenes.get(upperGene); + const indexOfGene = upperGeneNames.get(upperGene); if (indexOfGene === undefined) { return keepAroundErrorToast( @@ -129,7 +128,7 @@ class AddGenes extends React.Component { ); } return dispatch( - actions.requestUserDefinedGene(worldGenes[indexOfGene]) + actions.requestUserDefinedGene(geneNames[indexOfGene]) ); }) ).then( @@ -142,6 +141,27 @@ class AddGenes extends React.Component { return undefined; }; + async updateState(prevProps) { + const { annoMatrix } = this.props; + if (!annoMatrix) return; + if (annoMatrix !== prevProps?.annoMatrix) { + const { schema } = annoMatrix; + const varIndex = schema.annotations.var.index; + + this.setState({ status: "pending" }); + try { + const df = await annoMatrix.fetch("var", varIndex); + this.setState({ + status: "success", + geneNames: df.col(varIndex).asArray(), + }); + } catch (error) { + this.setState({ status: "error" }); + throw error; + } + } + } + placeholderGeneNames() { /* return a string containing gene name suggestions for use as a user hint. @@ -151,10 +171,7 @@ class AddGenes extends React.Component { NOTE: the random selection means it will re-render constantly. */ - const { world } = this.props; - const { varAnnotations } = world; - const varIndexName = world.schema.annotations.var.index; - const geneNames = varAnnotations.col(varIndexName).asArray(); + const { geneNames } = this.state; if (geneNames.length > 0) { const placeholder = []; let len = geneNames.length; @@ -175,8 +192,8 @@ class AddGenes extends React.Component { } handleClick(g) { - const { world, dispatch, userDefinedGenes } = this.props; - const varIndexName = world.schema.annotations.var.index; + const { dispatch, userDefinedGenes } = this.props; + const { geneNames } = this.state; if (!g) return; const gene = g.target; if (userDefinedGenes.indexOf(gene) !== -1) { @@ -185,27 +202,21 @@ class AddGenes extends React.Component { postUserErrorToast( `That's too many genes, you can have at most ${globals.maxUserDefinedGenes} user defined genes` ); - } else if ( - world.varAnnotations.col(varIndexName).indexOf(gene) === undefined - ) { + } else if (geneNames.indexOf(gene) === undefined) { postUserErrorToast("That doesn't appear to be a valid gene name."); } else { dispatch({ type: "single user defined gene start" }); - dispatch(actions.requestUserDefinedGene(gene)).then( - () => dispatch({ type: "single user defined gene complete" }), - () => dispatch({ type: "single user defined gene error" }) - ); + dispatch(actions.requestUserDefinedGene(gene)); + dispatch({ type: "single user defined gene complete" }); } } render() { - const { world, userDefinedGenesLoading } = this.props; - const varIndexName = world?.schema?.annotations?.var?.index; - const varIndex = world?.varAnnotations?.col(varIndexName)?.asArray(); - const { tab, bulkAdd, activeItem } = this.state; + const { userDefinedGenesLoading } = this.props; + const { tab, bulkAdd, activeItem, status, geneNames } = this.state; // may still be loading! - if (!varIndex) return null; + if (status !== "success") return null; return (
@@ -263,7 +274,7 @@ class AddGenes extends React.Component { itemListPredicate={filterGenes} onActiveItemChange={(item) => this.setState({ activeItem: item })} itemRenderer={renderGene} - items={varIndex || ["No genes"]} + items={geneNames || ["No genes"]} popoverProps={{ minimal: true }} />
+
+ ); +}; + export default Graph; diff --git a/client/src/components/graph/overlays/centroidLabels.js b/client/src/components/graph/overlays/centroidLabels.js index 5d85b452..65f22215 100644 --- a/client/src/components/graph/overlays/centroidLabels.js +++ b/client/src/components/graph/overlays/centroidLabels.js @@ -1,118 +1,217 @@ import React, { PureComponent } from "react"; -import { connect } from "react-redux"; +import { connect, shallowEqual } from "react-redux"; +import Async from "react-async"; import { categoryLabelDisplayStringLongLength } from "../../../globals"; +import calcCentroid from "../../../util/centroid"; +import { createColorQuery } from "../../../util/stateManager/colorHelpers"; export default @connect((state) => ({ - colorAccessor: state.colors.colorAccessor, + annoMatrix: state.annoMatrix, + colors: state.colors, + layoutChoice: state.layoutChoice, dilatedValue: state.pointDilation.categoryField, - labels: state.centroidLabels.labels, categoricalSelection: state.categoricalSelection, + showLabels: state.centroidLabels?.showLabels, })) class CentroidLabels extends PureComponent { - // Check to see if centroids have either just been displayed or removed from the overlay + static watchAsync(props, prevProps) { + return !shallowEqual(props.watchProps, prevProps.watchProps); + } - componentDidUpdate(prevProps) { - const { labels, overlayToggled } = this.props; - const prevSize = prevProps.labels.size; - const { size } = labels; + fetchAsyncProps = async (props) => { + const { + annoMatrix, + colors, + layoutChoice, + categoricalSelection, + showLabels, + } = props.watchProps; + const { schema } = annoMatrix; + const { colorAccessor } = colors; - const displayChangeOff = prevSize > 0 && size === undefined; - const displayChangeOn = prevSize === undefined && size > 0; - - if (displayChangeOn || displayChangeOff) { - // Notify overlay layer of display change - overlayToggled("centroidLabels", displayChangeOn); + const [layoutDf, colorDf] = await this.fetchData(); + let labels; + if (colorDf) { + labels = calcCentroid( + schema, + colorAccessor, + colorDf, + layoutChoice, + layoutDf + ); + } else { + labels = new Map(); } + + const { overlaySetShowing } = this.props; + overlaySetShowing("centroidLabels", showLabels && labels.size > 0); + + return { + labels, + colorAccessor, + category: categoricalSelection[colorAccessor], + }; + }; + + handleMouseEnter = (e, colorAccessor, label) => { + const { dispatch } = this.props; + dispatch({ + type: "category value mouse hover start", + metadataField: colorAccessor, + categoryField: label, + }); + }; + + handleMouseOut = (e, colorAccessor, label) => { + const { dispatch } = this.props; + dispatch({ + type: "category value mouse hover end", + metadataField: colorAccessor, + categoryField: label, + }); + }; + + colorByQuery() { + const { annoMatrix, colors } = this.props; + const { schema } = annoMatrix; + const { colorMode, colorAccessor } = colors; + return createColorQuery(colorMode, colorAccessor, schema); + } + + async fetchData() { + const { annoMatrix, layoutChoice } = this.props; + // fetch all data we need: layout, category + const promises = []; + // layout + promises.push(annoMatrix.fetch("emb", layoutChoice.current)); + // category to label - we ONLY label on obs, never on X, etc. + const query = this.colorByQuery(); + if (query && query[0] === "obs") { + promises.push(annoMatrix.fetch(...query)); + } else { + promises.push(Promise.resolve(null)); + } + + return Promise.all(promises); } render() { const { - labels, inverseTransform, dilatedValue, - dispatch, - colorAccessor, categoricalSelection, + showLabels, + colors, + annoMatrix, + layoutChoice, } = this.props; - if (!colorAccessor || labels.size === undefined || labels.size === 0) - return null; + return ( + + + {(asyncProps) => { + if (!showLabels) return null; - const { - categoryValueIndices, - categoryValueSelected, - } = categoricalSelection?.[colorAccessor]; + const labelSVGS = []; + const deselectOpacity = 0.375; + const { category, colorAccessor, labels } = asyncProps; - const labelSVGS = []; - let fontSize = "15px"; - let fontWeight = null; - const deselectOpacity = 0.375; - labels.forEach((coords, label) => { - fontSize = "15px"; - fontWeight = null; - if (label === dilatedValue) { - fontSize = "18px"; - fontWeight = "800"; - } + labels.forEach((coords, label) => { + const selected = category.get(label) ?? true; - const selected = categoryValueSelected[categoryValueIndices.get(label)]; + // Mirror LSB middle truncation + let displayLabel = label; + if (displayLabel.length > categoryLabelDisplayStringLongLength) { + displayLabel = `${label.slice( + 0, + categoryLabelDisplayStringLongLength / 2 + )}…${label.slice(-categoryLabelDisplayStringLongLength / 2)}`; + } - // Mirror LSB middle truncation - let displayLabel = label; - if (displayLabel.length > categoryLabelDisplayStringLongLength) { - displayLabel = `${label.slice( - 0, - categoryLabelDisplayStringLongLength / 2 - )}…${label.slice(-categoryLabelDisplayStringLongLength / 2)}`; - } + labelSVGS.push( + // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events -- the mouse actions for centroid labels do not have a screen reader alternative + + + ); } } + +const Label = ({ + label, + dilatedValue, + coords, + inverseTransform, + opacity, + colorAccessor, + displayLabel, + onMouseEnter, + onMouseOut, +}) => { + /* + Render a label at a given coordinate. + */ + let fontSize = "15px"; + let fontWeight = null; + if (label === dilatedValue) { + fontSize = "18px"; + fontWeight = "800"; + } + + return ( + + {/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events --- the mouse actions for centroid labels do not have a screen reader alternative*/} + onMouseEnter(e, colorAccessor, label)} + onMouseOut={(e) => onMouseOut(e, colorAccessor, label)} + pointerEvents="visiblePainted" + > + {displayLabel} + + + ); +}; diff --git a/client/src/components/graph/overlays/graphOverlayLayer.js b/client/src/components/graph/overlays/graphOverlayLayer.js index 751575d4..eb8d43bb 100644 --- a/client/src/components/graph/overlays/graphOverlayLayer.js +++ b/client/src/components/graph/overlays/graphOverlayLayer.js @@ -33,7 +33,7 @@ export default class GraphOverlayLayer extends PureComponent { }; // This is passed to all children, should be called when an overlay's display state is toggled along with the overlay name and its new display state in boolean form - overlayToggled = (overlay, displaying) => { + overlaySetShowing = (overlay, displaying) => { this.setState((state) => { return { ...state, display: { ...state.display, [overlay]: displaying } }; }); @@ -67,7 +67,7 @@ export default class GraphOverlayLayer extends PureComponent { const newChildren = React.Children.map(children, (child) => cloneElement(child, { inverseTransform, - overlayToggled: this.overlayToggled, + overlaySetShowing: this.overlaySetShowing, }) ); diff --git a/client/src/components/graph/setupLasso.js b/client/src/components/graph/setupLasso.js index 492d9cca..526d47a5 100644 --- a/client/src/components/graph/setupLasso.js +++ b/client/src/components/graph/setupLasso.js @@ -1,24 +1,26 @@ // https://bl.ocks.org/pbeshai/8008075f9ce771ee8be39e8c38907570 import * as d3 from "d3"; +import { Colors } from "@blueprintjs/core"; const Lasso = () => { const dispatch = d3.dispatch("start", "end", "cancel"); - const polygonToPath = (polygon) => - `M${polygon.map((d) => d.join(",")).join("L")}`; - - const distance = (pt1, pt2) => - Math.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2); - - // distance last point has to be to first point before it auto closes when mouse is released - const closeDistance = 75; - const lasso = (svg) => { let lassoPolygon; let lassoPath; let closePath; + const polygonToPath = (polygon) => + `M${polygon.map((d) => d.join(",")).join("L")}`; + + const distance = (pt1, pt2) => + Math.sqrt((pt2[0] - pt1[0]) ** 2 + (pt2[1] - pt1[1]) ** 2); + + // distance last point has to be to first point before it auto closes when mouse is released + const closeDistance = 75; + const lassoPathColor = Colors.BLUE5; + const handleDragStart = () => { lassoPolygon = [d3.mouse(svg.node())]; // current x y of mouse within element @@ -29,18 +31,14 @@ const Lasso = () => { lassoPath = g .append("path") .attr("data-testid", "lasso-element") - .attr("fill", "#0bb") .attr("fill-opacity", 0.1) - .attr("stroke", "#0bb") .attr("stroke-dasharray", "3, 3"); closePath = g .append("line") .attr("x2", lassoPolygon[0][0]) .attr("y2", lassoPolygon[0][1]) - .attr("stroke", "#0bb") - .attr("stroke-dasharray", "3, 3") - .attr("opacity", 0); + .attr("stroke-dasharray", "3, 3"); dispatch.call("start", lasso, lassoPolygon); }; @@ -55,9 +53,17 @@ const Lasso = () => { distance(lassoPolygon[0], lassoPolygon[lassoPolygon.length - 1]) < closeDistance ) { - closePath.attr("x1", point[0]).attr("y1", point[1]).attr("opacity", 1); + const closePathColor = Colors.GREEN5; + closePath + .attr("x1", point[0]) + .attr("y1", point[1]) + .attr("opacity", 1) + .attr("stroke", closePathColor) + .attr("fill", closePathColor); + lassoPath.attr("stroke", closePathColor).attr("fill", closePathColor); } else { closePath.attr("opacity", 0); + lassoPath.attr("stroke", lassoPathColor).attr("fill", lassoPathColor); } }; @@ -121,9 +127,9 @@ const Lasso = () => { lassoPath = g .append("path") .attr("data-testid", "lasso-element") - .attr("fill", "#0bb") + .attr("fill", lassoPathColor) .attr("fill-opacity", 0.1) - .attr("stroke", "#0bb") + .attr("stroke", lassoPathColor) .attr("stroke-dasharray", "3, 3"); lassoPath.attr("d", `${polygonToPath(lassoPolygon)}Z`); diff --git a/client/src/components/graph/setupSVGandBrush.js b/client/src/components/graph/setupSVGandBrush.js index 4351bdcf..d6a77bc4 100644 --- a/client/src/components/graph/setupSVGandBrush.js +++ b/client/src/components/graph/setupSVGandBrush.js @@ -17,6 +17,7 @@ export default ( viewport ) => { const svg = d3.select("#graph-wrapper").select("#lasso-layer"); + if (svg.empty()) return {}; if (selectionToolType === "brush") { const brush = d3 diff --git a/client/src/components/menubar/cellSetButtons.js b/client/src/components/menubar/cellSetButtons.js index d1d87412..ae4bb95c 100644 --- a/client/src/components/menubar/cellSetButtons.js +++ b/client/src/components/menubar/cellSetButtons.js @@ -2,31 +2,19 @@ import React from "react"; import { AnchorButton, Tooltip } from "@blueprintjs/core"; import { connect } from "react-redux"; -import { World } from "../../util/stateManager"; import { tooltipHoverOpenDelay } from "../../globals"; +import actions from "../../actions"; -@connect() +@connect((state) => ({ + differential: state.differential, +})) class CellSetButton extends React.PureComponent { set() { - const { - differential, - crossfilter, - dispatch, - eitherCellSetOneOrTwo, - } = this.props; - - // Reducer and components assume that value will be null if - // no selection made. World..getSelectedByIndex() returns a - // zero length TypedArray when nothing is selected. - let set = World.getSelectedByIndex(crossfilter); - if (set.length === 0) set = null; + const { differential, dispatch, eitherCellSetOneOrTwo } = this.props; if (!differential.diffExp) { - /* diffexp needs to be cleared before we store a new set */ - dispatch({ - type: `store current cell selection as differential set ${eitherCellSetOneOrTwo}`, - data: set, - }); + // disallow this action if the user has active differential expression results + dispatch(actions.setCellSetFromSelection(eitherCellSetOneOrTwo)); } } diff --git a/client/src/components/menubar/clip.js b/client/src/components/menubar/clip.js index 729d0dbc..5b866bd0 100644 --- a/client/src/components/menubar/clip.js +++ b/client/src/components/menubar/clip.js @@ -1,4 +1,3 @@ -// jshint esversion: 6 import React from "react"; import { Position, @@ -11,7 +10,7 @@ import { import { tooltipHoverOpenDelay } from "../../globals"; import styles from "./menubar.css"; -function Clip(props) { +const Clip = React.memo((props) => { const { pendingClipPercentiles, clipPercentileMin, @@ -129,6 +128,6 @@ function Clip(props) { />
); -} +}); export default Clip; diff --git a/client/src/components/menubar/diffexpButtons.js b/client/src/components/menubar/diffexpButtons.js index 93bb7736..4212014c 100644 --- a/client/src/components/menubar/diffexpButtons.js +++ b/client/src/components/menubar/diffexpButtons.js @@ -1,4 +1,3 @@ -// jshint esversion: 6 import React from "react"; import { connect } from "react-redux"; import { Button, ButtonGroup, AnchorButton, Tooltip } from "@blueprintjs/core"; @@ -8,15 +7,13 @@ import actions from "../../actions"; import CellSetButton from "./cellSetButtons"; @connect((state) => ({ - config: state.config, - crossfilter: state.crossfilter, differential: state.differential, celllist1: state.differential?.celllist1, celllist2: state.differential?.celllist2, diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false, diffexpCellcountMax: state.config?.limits?.["diffexp_cellcount_max"], })) -class DiffexpButtons extends React.Component { +class DiffexpButtons extends React.PureComponent { computeDiffExp = () => { const { dispatch, differential } = this.props; if (differential.celllist1 && differential.celllist2) { @@ -66,14 +63,8 @@ class DiffexpButtons extends React.Component { return ( - - + + {!differential.diffExp ? ( ({ - universe: state.universe, - world: state.world, layoutChoice: state.layoutChoice, - reembedController: state.reembedController, - enableReembedding: state.config?.parameters?.["enable-reembedding"] ?? false, + // disabled temporarily. TODO - issue #1606 + // reembedController: state.reembedController, + // enableReembedding: state.config?.parameters?.["enable-reembedding"] ?? false, + enableReembedding: false, })) class Embedding extends React.PureComponent { handleLayoutChoiceChange = (e) => { const { dispatch } = this.props; - dispatch({ - type: "set layout choice", - layoutChoice: e.currentTarget.value, - }); + dispatch(actions.layoutChoiceAction(e.currentTarget.value)); }; + // eslint-disable-next-line class-methods-use-this -- temporary disable renderReembedding() { + return null; + /* disabled pending rewrite. TODO - issue #1606 const { enableReembedding, world, @@ -63,6 +61,7 @@ class Embedding extends React.PureComponent { /> ); +*/ } render() { diff --git a/client/src/components/menubar/index.js b/client/src/components/menubar/index.js index aff8ada6..dbd1bdd4 100644 --- a/client/src/components/menubar/index.js +++ b/client/src/components/menubar/index.js @@ -1,4 +1,3 @@ -// jshint esversion: 6 import React from "react"; import { connect } from "react-redux"; import { ButtonGroup, AnchorButton, Tooltip } from "@blueprintjs/core"; @@ -13,33 +12,44 @@ import Subset from "./subset"; import UndoRedoReset from "./undoRedo"; import DiffexpButtons from "./diffexpButtons"; -@connect((state) => ({ - universe: state.universe, - world: state.world, - crossfilter: state.crossfilter, - differential: state.differential, - graphInteractionMode: state.controls.graphInteractionMode, - clipPercentileMin: Math.round(100 * (state.world?.clipQuantiles?.min ?? 0)), - clipPercentileMax: Math.round(100 * (state.world?.clipQuantiles?.max ?? 1)), - userDefinedGenes: state.controls.userDefinedGenes, - diffexpGenes: state.controls.diffexpGenes, - colorAccessor: state.colors.colorAccessor, - scatterplotXXaccessor: state.controls.scatterplotXXaccessor, - scatterplotYYaccessor: state.controls.scatterplotYYaccessor, - celllist1: state.differential.celllist1, - celllist2: state.differential.celllist2, - libraryVersions: state.config?.["library_versions"], - undoDisabled: state["@@undoable/past"].length === 0, - redoDisabled: state["@@undoable/future"].length === 0, - aboutLink: state.config?.links?.["about-dataset"], - disableDiffexp: state.config?.parameters?.["disable-diffexp"] ?? false, - diffexpMayBeSlow: state.config?.parameters?.["diffexp-may-be-slow"] ?? false, - showCentroidLabels: state.centroidLabels.showLabels, - tosURL: state.config?.parameters?.["about_legal_tos"], - privacyURL: state.config?.parameters?.["about_legal_privacy"], - categoricalSelection: state.categoricalSelection, -})) -class MenuBar extends React.Component { +@connect((state) => { + const { annoMatrix } = state; + const crossfilter = state.obsCrossfilter; + const selectedCount = crossfilter.countSelected(); + + const subsetPossible = + selectedCount !== 0 && selectedCount !== crossfilter.size(); // ie, not all are selected + const subsetResetPossible = + annoMatrix.nObs !== annoMatrix.schema.dataframe.nObs; + + return { + subsetPossible, + subsetResetPossible, + differential: state.differential, + graphInteractionMode: state.controls.graphInteractionMode, + clipPercentileMin: Math.round(100 * (annoMatrix?.clipRange?.[0] ?? 0)), + clipPercentileMax: Math.round(100 * (annoMatrix?.clipRange?.[1] ?? 1)), + userDefinedGenes: state.controls.userDefinedGenes, + diffexpGenes: state.controls.diffexpGenes, + colorAccessor: state.colors.colorAccessor, + scatterplotXXaccessor: state.controls.scatterplotXXaccessor, + scatterplotYYaccessor: state.controls.scatterplotYYaccessor, + celllist1: state.differential.celllist1, + celllist2: state.differential.celllist2, + libraryVersions: state.config?.["library_versions"], + undoDisabled: state["@@undoable/past"].length === 0, + redoDisabled: state["@@undoable/future"].length === 0, + aboutLink: state.config?.links?.["about-dataset"], + disableDiffexp: state.config?.parameters?.["disable-diffexp"] ?? false, + diffexpMayBeSlow: + state.config?.parameters?.["diffexp-may-be-slow"] ?? false, + showCentroidLabels: state.centroidLabels.showLabels, + tosURL: state.config?.parameters?.["about_legal_tos"], + privacyURL: state.config?.parameters?.["about_legal_privacy"], + categoricalSelection: state.categoricalSelection, + }; +}) +class MenuBar extends React.PureComponent { static isValidDigitKeyEvent(e) { /* Return true if this event is necessary to enter a percent number input. @@ -78,10 +88,10 @@ class MenuBar extends React.Component { const { pendingClipPercentiles } = this.state; const clipPercentileMin = pendingClipPercentiles?.clipPercentileMin; const clipPercentileMax = pendingClipPercentiles?.clipPercentileMax; - - const { world } = this.props; - const currentClipMin = 100 * world?.clipQuantiles?.min; - const currentClipMax = 100 * world?.clipQuantiles?.max; + const { + clipPercentileMin: currentClipMin, + clipPercentileMax: currentClipMax, + } = this.props; // if you change this test, be careful with logic around // comparisons between undefined / NaN handling. @@ -150,10 +160,7 @@ class MenuBar extends React.Component { const { clipPercentileMin, clipPercentileMax } = pendingClipPercentiles; const min = clipPercentileMin / 100; const max = clipPercentileMax / 100; - dispatch({ - type: "set clip quantiles", - clipQuantiles: { min, max }, - }); + dispatch(actions.clipAction(min, max)); }; handleClipOpening = () => { @@ -176,17 +183,14 @@ class MenuBar extends React.Component { }); }; - subsetPossible = () => { - const { crossfilter } = this.props; - return ( - crossfilter.countSelected() !== 0 && - crossfilter.countSelected() !== crossfilter.size() - ); + handleSubset = () => { + const { dispatch } = this.props; + dispatch(actions.subsetAction()); }; - subsetResetPossible = () => { - const { world, universe } = this.props; - return world.nObs !== universe.nObs; + handleSubsetReset = () => { + const { dispatch } = this.props; + dispatch(actions.resetSubsetAction()); }; render() { @@ -206,6 +210,8 @@ class MenuBar extends React.Component { tosURL, categoricalSelection, colorAccessor, + subsetPossible, + subsetResetPossible, } = this.props; const { pendingClipPercentiles } = this.state; @@ -314,16 +320,10 @@ class MenuBar extends React.Component { { - dispatch(actions.setWorldToSelection()); - dispatch({ type: "increment graph render counter" }); - }} - handleSubsetReset={() => { - dispatch(actions.resetWorldToUniverse()); - dispatch({ type: "increment graph render counter" }); - }} + subsetPossible={subsetPossible} + subsetResetPossible={subsetResetPossible} + handleSubset={this.handleSubset} + handleSubsetReset={this.handleSubsetReset} /> {disableDiffexp ? null : }
diff --git a/client/src/components/menubar/infoMenu.js b/client/src/components/menubar/infoMenu.js index c0eed627..ab803ac9 100644 --- a/client/src/components/menubar/infoMenu.js +++ b/client/src/components/menubar/infoMenu.js @@ -3,7 +3,7 @@ import React from "react"; import { Button, Popover, Menu, MenuItem, Position } from "@blueprintjs/core"; import styles from "./menubar.css"; -function InformationMenu(props) { +const InformationMenu = React.memo((props) => { const { libraryVersions, aboutLink, tosURL, privacyURL } = props; return (
@@ -41,11 +41,11 @@ function InformationMenu(props) { /> {tosURL ? ( @@ -72,6 +72,6 @@ function InformationMenu(props) {
); -} +}); export default InformationMenu; diff --git a/client/src/components/menubar/subset.js b/client/src/components/menubar/subset.js index 270ef205..03025cb3 100644 --- a/client/src/components/menubar/subset.js +++ b/client/src/components/menubar/subset.js @@ -3,7 +3,7 @@ import { AnchorButton, ButtonGroup, Tooltip } from "@blueprintjs/core"; import styles from "./menubar.css"; import * as globals from "../../globals"; -function Subset(props) { +const Subset = React.memo((props) => { const { subsetPossible, subsetResetPossible, @@ -41,6 +41,6 @@ function Subset(props) { ); -} +}); export default Subset; diff --git a/client/src/components/menubar/undoRedo.js b/client/src/components/menubar/undoRedo.js index dba4d048..601dba32 100644 --- a/client/src/components/menubar/undoRedo.js +++ b/client/src/components/menubar/undoRedo.js @@ -1,10 +1,9 @@ -// jshint esversion: 6 import React from "react"; import { AnchorButton, Tooltip } from "@blueprintjs/core"; import { tooltipHoverOpenDelay } from "../../globals"; import styles from "./menubar.css"; -function InformationMenu(props) { +const UndoRedo = React.memo((props) => { const { undoDisabled, redoDisabled, dispatch } = props; return (
@@ -46,6 +45,6 @@ function InformationMenu(props) {
); -} +}); -export default InformationMenu; +export default UndoRedo; diff --git a/client/src/components/miniHistogram/index.js b/client/src/components/miniHistogram/index.js new file mode 100644 index 00000000..fb10e6cd --- /dev/null +++ b/client/src/components/miniHistogram/index.js @@ -0,0 +1,100 @@ +import React from "react"; +import { + Popover, + PopoverInteractionKind, + Position, + Classes, +} from "@blueprintjs/core"; + +export default class MiniHistogram extends React.PureComponent { + constructor(props) { + super(props); + this.canvasRef = React.createRef(); + } + + drawHistogram = () => { + const { xScale, yScale, bins, width, height } = this.props; + + if (!bins) return; + + const ctx = this.canvasRef.current.getContext("2d"); + + ctx.clearRect(0, 0, width, height); + + ctx.fillStyle = "#000"; + + let x; + let y; + + const rectWidth = width / bins.length; + + for (let i = 0, { length } = bins; i < length; i += 1) { + x = xScale(i); + y = yScale(bins[i]); + ctx.fillRect(x, height - y, rectWidth, y); + } + }; + + componentDidMount = () => { + this.drawHistogram(); + }; + + componentDidUpdate = (prevProps) => { + const { obsOrVarContinuousFieldDisplayName } = this.props; + if ( + prevProps.obsOrVarContinuousFieldDisplayName !== + obsOrVarContinuousFieldDisplayName + ) + this.drawHistogram(); + }; + + render() { + const { + domainLabel, + obsOrVarContinuousFieldDisplayName, + width, + height, + } = this.props; + + return ( + + +
+

+ This histograms shows the distribution of{" "} + {obsOrVarContinuousFieldDisplayName} within{" "} + {domainLabel}. +
+
+ The x axis is the same for each histogram, while the y axis is + scaled to the largest bin within this histogram instead of the + largest bin within the whole category. +

+
+
+ ); + } +} diff --git a/client/src/components/miniStackedBar/index.js b/client/src/components/miniStackedBar/index.js new file mode 100644 index 00000000..bec086c9 --- /dev/null +++ b/client/src/components/miniStackedBar/index.js @@ -0,0 +1,74 @@ +// jshint esversion: 6 +import React from "react"; + +export default class MiniStackedBar extends React.PureComponent { + constructor(props) { + super(props); + this.canvasRef = React.createRef(); + } + + drawStacks = () => { + const { + domainValues, + scale, + domain, + colorTable, + occupancy, + width, + height, + } = this.props; + + if (!colorTable || !domainValues) return; + + const { scale: colorScale } = colorTable; + + const ctx = this.canvasRef?.current.getContext("2d"); + + ctx.clearRect(0, 0, width, height); + let currentOffset = 0; + + let occupancyValue; + let scaledValue; + let value; + + for (let i = 0, { length } = domainValues; i < length; i += 1) { + value = domainValues[i]; + occupancyValue = occupancy.get(value); + scaledValue = scale(occupancyValue); + ctx.fillStyle = occupancyValue + ? colorScale(domain.indexOf(value)) + : "rgb(255,255,255)"; + ctx.fillRect(currentOffset, 0, occupancyValue ? scaledValue : 0, height); + currentOffset += occupancyValue ? scaledValue : 0; + } + }; + + componentDidUpdate = (prevProps) => { + const { occupancy } = this.props; + if (occupancy !== prevProps.occupancy) this.drawStacks(); + }; + + componentDidMount = () => { + this.drawStacks(); + }; + + render() { + const { width, height } = this.props; + const { canvas } = this; + if (canvas) canvas.getContext("2d").clearRect(0, 0, width, height); + + return ( + + ); + } +} diff --git a/client/src/components/scatterplot/drawPointsRegl.js b/client/src/components/scatterplot/drawPointsRegl.js index a9e8a40a..4f70e9ed 100644 --- a/client/src/components/scatterplot/drawPointsRegl.js +++ b/client/src/components/scatterplot/drawPointsRegl.js @@ -1,6 +1,6 @@ import { glPointFlags, glPointSize } from "../../util/glHelpers"; -export default function (regl) { +export default function drawPointsRegl(regl) { return regl({ vert: ` precision mediump float; diff --git a/client/src/components/scatterplot/scatterplot.js b/client/src/components/scatterplot/scatterplot.js index 8d209001..43e5b0f1 100644 --- a/client/src/components/scatterplot/scatterplot.js +++ b/client/src/components/scatterplot/scatterplot.js @@ -1,18 +1,25 @@ -import React from "react"; -import { connect } from "react-redux"; +import React, { useEffect, useRef } from "react"; +import { connect, shallowEqual } from "react-redux"; import { Button, ButtonGroup } from "@blueprintjs/core"; import _regl from "regl"; import * as d3 from "d3"; import { mat3 } from "gl-matrix"; import memoize from "memoize-one"; -import { isTypedArray } from "../../util/typeHelpers"; +import Async from "react-async"; import * as globals from "../../globals"; -import setupScatterplot from "./setupScatterplot"; import styles from "./scatterplot.css"; import _drawPoints from "./drawPointsRegl"; import { margin, width, height } from "./util"; -import finiteExtent from "../../util/finiteExtent"; +import { + createColorTable, + createColorQuery, +} from "../../util/stateManager/colorHelpers"; +import renderThrottle from "../../util/renderThrottle"; + +const flagSelected = 1; +const flagNaN = 2; +const flagHighlight = 4; function createProjectionTF(viewportWidth, viewportHeight) { /* @@ -22,40 +29,63 @@ function createProjectionTF(viewportWidth, viewportHeight) { return mat3.projection(m, viewportWidth, viewportHeight); } +function getScale(col, rangeMin, rangeMax) { + if (!col) return null; + const { min, max } = col.summarize(); + return d3.scaleLinear().domain([min, max]).range([rangeMin, rangeMax]); +} +const getXScale = memoize(getScale); +const getYScale = memoize(getScale); + @connect((state) => { - const { world, crossfilter, universe } = state; + const { obsCrossfilter: crossfilter } = state; const { scatterplotXXaccessor, scatterplotYYaccessor } = state.controls; - const expressionX = scatterplotXXaccessor - ? world.varData.col(scatterplotXXaccessor)?.asArray() - : null; - const expressionY = scatterplotYYaccessor - ? world.varData.col(scatterplotYYaccessor)?.asArray() - : null; return { - world, - universe, - - colorRGB: state.colors.rgb, - colorScale: state.colors.scale, - colorAccessor: state.colors.colorAccessor, - + annoMatrix: state.annoMatrix, + colors: state.colors, pointDilation: state.pointDilation, // Accessors are var/gene names (strings) scatterplotXXaccessor, scatterplotYYaccessor, - opacityForDeselectedCells: state.controls.opacityForDeselectedCells, differential: state.differential, - - expressionX, - expressionY, - crossfilter, }; }) class Scatterplot extends React.PureComponent { + static createReglState(canvas) { + /* + Must be created for each canvas + */ + + // regl will create a top-level, full-screen canvas if we pass it a null. + // canvas should never be null, so protect against that. + if (!canvas) return {}; + + // setup canvas, webgl draw function and camera + const regl = _regl(canvas); + const drawPoints = _drawPoints(regl); + + // preallocate webgl buffers + const pointBuffer = regl.buffer(); + const colorBuffer = regl.buffer(); + const flagBuffer = regl.buffer(); + + return { + regl, + drawPoints, + pointBuffer, + colorBuffer, + flagBuffer, + }; + } + + static watchAsync(props, prevProps) { + return !shallowEqual(props.watchProps, prevProps.watchProps); + } + computePointPositions = memoize((X, Y, xScale, yScale) => { const positions = new Float32Array(2 * X.length); for (let i = 0, len = X.length; i < len; i += 1) { @@ -77,46 +107,23 @@ class Scatterplot extends React.PureComponent { }); computeSelectedFlags = memoize( - (crossfilter, flagSelected, flagUnselected) => { + (crossfilter, _flagSelected, _flagUnselected) => { const x = crossfilter.fillByIsSelected( new Float32Array(crossfilter.size()), - flagSelected, - flagUnselected + _flagSelected, + _flagUnselected ); return x; } ); - computePointFlags = memoize( - (world, crossfilter, colorAccessor, pointDilation) => { - const flagSelected = 1; - const flagNaN = 2; - const flagHighlight = 4; - - const flags = this.computeSelectedFlags( - crossfilter, - flagSelected, - 0 - ).slice(); - - const { metadataField, categoryField } = pointDilation; - const highlightData = metadataField - ? world.obsAnnotations.col(metadataField)?.asArray() - : null; - const colorByColumn = colorAccessor - ? world.obsAnnotations.col(colorAccessor)?.asArray() || - world.varData.col(colorAccessor)?.asArray() - : null; - const colorByData = - colorByColumn && isTypedArray(colorByColumn) ? colorByColumn : null; - - if (colorByData || highlightData) { + computeHighlightFlags = memoize( + (nObs, pointDilationData, pointDilationLabel) => { + const flags = new Float32Array(nObs); + if (pointDilationData) { for (let i = 0, len = flags.length; i < len; i += 1) { - if (highlightData) { - flags[i] += highlightData[i] === categoryField ? flagHighlight : 0; - } - if (colorByData) { - flags[i] += Number.isFinite(colorByData[i]) ? 0 : flagNaN; + if (pointDilationData[i] === pointDilationLabel) { + flags[i] = flagHighlight; } } } @@ -124,189 +131,97 @@ class Scatterplot extends React.PureComponent { } ); + computeColorByFlags = memoize((nObs, colorByData) => { + const flags = new Float32Array(nObs); + if (colorByData) { + for (let i = 0, len = flags.length; i < len; i += 1) { + if (!Number.isFinite(colorByData[i])) { + flags[i] = flagNaN; + } + } + } + return flags; + }); + + computePointFlags = memoize( + (crossfilter, colorByData, pointDilationData, pointDilationLabel) => { + /* + We communicate with the shader using three flags: + - isNaN -- the value is a NaN. Only makes sense when we have a colorAccessor + - isSelected -- the value is selected + - isHightlighted -- the value is highlighted in the UI (orthogonal from selection highlighting) + + Due to constraints in webgl vertex shader attributes, these are encoded in a float, "kinda" + like bitmasks. + + We also have separate code paths for generating flags for categorical and + continuous metadata, as they rely on different tests, and some of the flags + (eg, isNaN) are meaningless in the face of categorical metadata. + */ + const nObs = crossfilter.size(); + + const selectedFlags = this.computeSelectedFlags( + crossfilter, + flagSelected, + 0 + ); + const highlightFlags = this.computeHighlightFlags( + nObs, + pointDilationData, + pointDilationLabel + ); + const colorByFlags = this.computeColorByFlags(nObs, colorByData); + + const flags = new Float32Array(nObs); + for (let i = 0; i < nObs; i += 1) { + flags[i] = selectedFlags[i] + highlightFlags[i] + colorByFlags[i]; + } + + return flags; + } + ); + constructor(props) { super(props); - this.count = 0; + const viewport = this.getViewportDimensions(); this.axes = false; - this.renderCache = { - positions: null, - colors: null, - flags: null, - xScale: null, - yScale: null, - }; + this.reglCanvas = null; + this.renderCache = null; this.state = { - svg: null, + regl: null, + drawPoints: null, minimized: null, - viewport: { - height: null, - width: null, - }, + viewport, + projectionTF: createProjectionTF(width, height), }; } componentDidMount() { - const { svg } = setupScatterplot(width, height, margin); - let scales; - const { expressionX, expressionY } = this.props; - - if (svg && expressionX && expressionY) { - scales = Scatterplot.setupScales(expressionX, expressionY); - this.drawAxesSVG(scales.xScale, scales.yScale, svg); - this.renderCache = { ...this.renderCache, ...scales }; - } - - const regl = _regl(this.reglCanvas); - const drawPoints = _drawPoints(regl); - - // Create render transform - const projectionTF = createProjectionTF( - this.reglCanvas.width, - this.reglCanvas.height - ); - - // preallocate buffers - const pointBuffer = regl.buffer(); - const colorBuffer = regl.buffer(); - const flagBuffer = regl.buffer(); - - this.renderPoints( - regl, - drawPoints, - flagBuffer, - colorBuffer, - pointBuffer, - projectionTF - ); - + // this affect point render size for the scatterplot window.addEventListener("resize", this.handleResize); - const viewport = this.getViewportDimensions(); - - this.setState({ - regl, - flagBuffer, - pointBuffer, - colorBuffer, - svg, - drawPoints, - projectionTF, - viewport, - }); - } - - componentDidUpdate(prevProps) { - const { - world, - crossfilter, - scatterplotXXaccessor, - scatterplotYYaccessor, - expressionX, - expressionY, - colorRGB, - colorAccessor, - pointDilation, - } = this.props; - const { - regl, - pointBuffer, - colorBuffer, - flagBuffer, - svg, - drawPoints, - projectionTF, - } = this.state; - - if ( - scatterplotXXaccessor !== prevProps.scatterplotXXaccessor || - scatterplotYYaccessor !== prevProps.scatterplotYYaccessor || - world !== prevProps.world // shape or clip of world changed - ) { - const scales = Scatterplot.setupScales(expressionX, expressionY); - this.drawAxesSVG(scales.xScale, scales.yScale, svg); - this.renderCache = { ...this.renderCache, ...scales }; - } - - if (world && regl) { - const { renderCache } = this; - const { xScale, yScale } = this.renderCache; - let needsRepaint = false; - - const newPositions = this.computePointPositions( - expressionX, - expressionY, - xScale, - yScale - ); - if (renderCache.positions !== newPositions) { - renderCache.positions = newPositions; - pointBuffer({ data: renderCache.positions, dimension: 2 }); - needsRepaint = true; - } - - /* colors for each point */ - const newColors = this.computePointColors(colorRGB); - if (renderCache.colors !== newColors) { - renderCache.colors = newColors; - colorBuffer({ data: renderCache.colors, dimension: 3 }); - needsRepaint = true; - } - - const newFlags = this.computePointFlags( - world, - crossfilter, - colorAccessor, - pointDilation - ); - if (renderCache.flags !== newFlags) { - renderCache.flags = newFlags; - flagBuffer({ data: renderCache.flags, dimension: 1 }); - needsRepaint = true; - } - - this.count = expressionX.length; - - if (needsRepaint) { - this.renderPoints( - regl, - drawPoints, - flagBuffer, - colorBuffer, - pointBuffer, - projectionTF - ); - } - } } componentWillUnmount() { - window.removeEventListener("resize", this.updateViewportDimensions); + window.removeEventListener("resize", this.handleResize); } + setReglCanvas = (canvas) => { + this.reglCanvas = canvas; + if (canvas) { + // no need to update this state if we are detaching. + this.setState({ + ...Scatterplot.createReglState(canvas), + }); + } + }; + getViewportDimensions = () => { return { - viewport: { - height: window.height, - width: window.width, - }, + height: window.innerHeight, + width: window.innerWidth, }; }; - static setupScales(expressionX, expressionY) { - const xScale = d3 - .scaleLinear() - .domain(finiteExtent(expressionX)) - .range([0, width]); - const yScale = d3 - .scaleLinear() - .domain(finiteExtent(expressionY)) - .range([height, 0]); - - return { - xScale, - yScale, - }; - } - handleResize = () => { const { state } = this.state; const viewport = this.getViewportDimensions(); @@ -316,52 +231,167 @@ class Scatterplot extends React.PureComponent { }); }; - updateViewportDimensions = () => { - this.setState(this.getViewportDimensions()); + fetchAsyncProps = async (props) => { + const { + scatterplotXXaccessor, + scatterplotYYaccessor, + colors: colorsProp, + crossfilter, + pointDilation, + } = props.watchProps; + + const [ + expressionXDf, + expressionYDf, + colorDf, + pointDilationDf, + ] = await this.fetchData( + scatterplotXXaccessor, + scatterplotYYaccessor, + colorsProp, + pointDilation + ); + const colorTable = this.updateColorTable(colorsProp, colorDf); + + const xCol = expressionXDf.icol(0); + const yCol = expressionYDf.icol(0); + const xScale = getXScale(xCol, 0, width); + const yScale = getYScale(yCol, height, 0); + const positions = this.computePointPositions( + xCol.asArray(), + yCol.asArray(), + xScale, + yScale + ); + + const colors = this.computePointColors(colorTable.rgb); + + const { colorAccessor } = colorsProp; + const colorByData = colorDf?.col(colorAccessor)?.asArray(); + const { + metadataField: pointDilationCategory, + categoryField: pointDilationLabel, + } = pointDilation; + const pointDilationData = pointDilationDf + ?.col(pointDilationCategory) + ?.asArray(); + const flags = this.computePointFlags( + crossfilter, + colorByData, + pointDilationData, + pointDilationLabel + ); + + return { + positions, + colors, + flags, + width, + height, + xScale, + yScale, + }; }; - drawAxesSVG(xScale, yScale, svg) { - const { scatterplotYYaccessor, scatterplotXXaccessor } = this.props; - svg.selectAll("*").remove(); + createXQuery(geneName) { + const { annoMatrix } = this.props; + const { schema } = annoMatrix; + const varIndex = schema?.annotations?.var?.index; + if (!varIndex) return null; + return [ + "X", + { + field: "var", + column: varIndex, + value: geneName, + }, + ]; + } - // the axes are much cleaner and easier now. No need to rotate and orient - // the axis, just call axisBottom, axisLeft etc. - const xAxis = d3.axisBottom().ticks(7).scale(xScale); + createColorByQuery(colors) { + const { annoMatrix } = this.props; + const { schema } = annoMatrix; + const { colorMode, colorAccessor } = colors; + return createColorQuery(colorMode, colorAccessor, schema); + } - const yAxis = d3.axisLeft().ticks(7).scale(yScale); + updateColorTable(colors, colorDf) { + /* update color table state */ + const { annoMatrix } = this.props; + const { schema } = annoMatrix; + const { colorAccessor, userColors, colorMode } = colors; + return createColorTable( + colorMode, + colorAccessor, + colorDf, + schema, + userColors + ); + } - // adding axes is also simpler now, just translate x-axis to (0,height) - // and it's alread defined to be a bottom axis. - svg - .append("g") - .attr("transform", `translate(0,${height})`) - .attr("class", "x axis") - .call(xAxis); + async fetchData( + scatterplotXXaccessor, + scatterplotYYaccessor, + colors, + pointDilation + ) { + const { annoMatrix } = this.props; + const { metadataField: pointDilationAccessor } = pointDilation; - // y-axis is translated to (0,0) - svg - .append("g") - .attr("transform", "translate(0,0)") - .attr("class", "y axis") - .call(yAxis); + const promises = []; + // X and Y dimensions + promises.push( + annoMatrix.fetch(...this.createXQuery(scatterplotXXaccessor)) + ); + promises.push( + annoMatrix.fetch(...this.createXQuery(scatterplotYYaccessor)) + ); - // adding label. For x-axis, it's at (10, 10), and for y-axis at (width, height-10). - svg - .append("text") - .attr("x", 10) - .attr("y", 10) - .attr("class", "label") - .style("font-style", "italic") - .text(scatterplotYYaccessor); + // color + const query = this.createColorByQuery(colors); + if (query) { + promises.push(annoMatrix.fetch(...query)); + } else { + promises.push(Promise.resolve(null)); + } - svg - .append("text") - .attr("x", width) - .attr("y", height - 10) - .attr("text-anchor", "end") - .attr("class", "label") - .style("font-style", "italic") - .text(scatterplotXXaccessor); + // point highlighting + if (pointDilationAccessor) { + promises.push(annoMatrix.fetch("obs", pointDilationAccessor)); + } else { + promises.push(Promise.resolve(null)); + } + + return Promise.all(promises); + } + + renderCanvas = renderThrottle(() => { + const { + regl, + drawPoints, + colorBuffer, + pointBuffer, + flagBuffer, + projectionTF, + } = this.state; + this.renderPoints( + regl, + drawPoints, + flagBuffer, + colorBuffer, + pointBuffer, + projectionTF + ); + }); + + updateReglAndRender(newRenderCache) { + const { positions, colors, flags } = newRenderCache; + this.renderCache = newRenderCache; + const { pointBuffer, colorBuffer, flagBuffer } = this.state; + pointBuffer({ data: positions, dimension: 2 }); + colorBuffer({ data: colors, dimension: 3 }); + flagBuffer({ data: flags, dimension: 1 }); + this.renderCanvas(); } renderPoints( @@ -372,8 +402,10 @@ class Scatterplot extends React.PureComponent { pointBuffer, projectionTF ) { - if (!this.reglCanvas) return; - const { universe } = this.props; + const { annoMatrix } = this.props; + if (!this.reglCanvas || !annoMatrix) return; + + const { schema } = annoMatrix; const { viewport } = this.state; regl.poll(); regl.clear({ @@ -385,8 +417,8 @@ class Scatterplot extends React.PureComponent { color: colorBuffer, position: pointBuffer, projection: projectionTF, - count: this.count, - nPoints: universe.nObs, + count: annoMatrix.nObs, + nPoints: schema.dataframe.nObs, minViewportDimension: Math.min( viewport.width - globals.leftSidebarWidth || width, viewport.height || height @@ -396,8 +428,16 @@ class Scatterplot extends React.PureComponent { } render() { - const { dispatch } = this.props; - const { minimized } = this.state; + const { + dispatch, + annoMatrix, + scatterplotXXaccessor, + scatterplotYYaccessor, + colors, + crossfilter, + pointDilation, + } = this.props; + const { minimized, regl, viewport } = this.state; return (
{ - this.reglCanvas = canvas; - }} + ref={this.setReglCanvas} /> + + Loading... + {(error) => error.message} + + {(asyncProps) => { + if (regl && !shallowEqual(asyncProps, this.renderCache)) { + this.updateReglAndRender(asyncProps); + } + return ( + + ); + }} + +
); @@ -470,3 +542,75 @@ class Scatterplot extends React.PureComponent { } export default Scatterplot; + +const ScatterplotAxis = React.memo( + ({ scatterplotYYaccessor, scatterplotXXaccessor, xScale, yScale }) => { + /* + Axis for the scatterplot, rendered with SVG/D3. Props: + * scatterplotXXaccessor - name of X axis + * scatterplotXXaccessor - name of Y axis + * xScale - D3 scale for X axis (domain to range) + * yScale - D3 scale for Y axis (domain to range) + + This also relies on the GLOBAL width/height/margin constants. If those become + become variables, may need to add the params. + */ + + const svgRef = useRef(null); + + useEffect(() => { + if (!svgRef.current) return; + const svg = d3.select(svgRef.current); + + svg.selectAll("*").remove(); + + // the axes are much cleaner and easier now. No need to rotate and orient + // the axis, just call axisBottom, axisLeft etc. + const xAxis = d3.axisBottom().ticks(7).scale(xScale); + const yAxis = d3.axisLeft().ticks(7).scale(yScale); + + // adding axes is also simpler now, just translate x-axis to (0,height) + // and it's alread defined to be a bottom axis. + svg + .append("g") + .attr("transform", `translate(0,${height})`) + .attr("class", "x axis") + .call(xAxis); + + // y-axis is translated to (0,0) + svg + .append("g") + .attr("transform", "translate(0,0)") + .attr("class", "y axis") + .call(yAxis); + + // adding label. For x-axis, it's at (10, 10), and for y-axis at (width, height-10). + svg + .append("text") + .attr("x", 10) + .attr("y", 10) + .attr("class", "label") + .style("font-style", "italic") + .text(scatterplotYYaccessor); + + svg + .append("text") + .attr("x", width) + .attr("y", height - 10) + .attr("text-anchor", "end") + .attr("class", "label") + .style("font-style", "italic") + .text(scatterplotXXaccessor); + }, [scatterplotXXaccessor, scatterplotYYaccessor, xScale, yScale]); + + return ( + + + + ); + } +); diff --git a/client/src/components/scatterplot/setupScatterplot.js b/client/src/components/scatterplot/setupScatterplot.js deleted file mode 100644 index 99a339a3..00000000 --- a/client/src/components/scatterplot/setupScatterplot.js +++ /dev/null @@ -1,26 +0,0 @@ -// jshint esversion: 6 -/***************************************** -****************************************** - Setup SVG & Canvas elements -****************************************** -******************************************/ - -import * as d3 from "d3"; - -const setupScatterplot = (width, height, margin) => { - const container = d3.select("#scatterplot"); - - const svg = container - .append("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .attr("data-testid", "scatterplot-svg") - .append("g") - .attr("transform", `translate(${margin.left},${margin.top})`); - - return { - svg, - }; -}; - -export default setupScatterplot; diff --git a/client/src/components/util/truncate.js b/client/src/components/util/truncate.js index 618c7831..da06a58f 100644 --- a/client/src/components/util/truncate.js +++ b/client/src/components/util/truncate.js @@ -16,17 +16,19 @@ const FIRST_HALF_STYLE = { flexShrink: 1, minWidth: "5px", }; +const SECOND_HALF_STYLE = { + position: "relative", + overflow: "hidden", + whiteSpace: "nowrap", +}; +const SECOND_HALF_SPACING_STYLE = { + color: "transparent", +}; const SECOND_HALF_INNER_STYLE = { position: "absolute", right: 0, }; -const SECOND_HALF_STYLE = { - color: "transparent", - position: "relative", - overflow: "hidden", - whiteSpace: "nowrap", -}; export default (props) => { const { children } = props; @@ -40,23 +42,36 @@ export default (props) => { } const originalString = children.props.children; - const firstString = originalString.substr(0, originalString.length / 2); - const secondString = originalString.substr(originalString.length / 2); + let firstString; + let secondString; + + if (originalString.length === 1) { + firstString = originalString; + } else { + firstString = originalString.substr(0, originalString.length / 2); + secondString = originalString.substr(originalString.length / 2); + if (firstString.charAt(firstString.length - 1) === " ") { + firstString = `${firstString.substr(0, firstString.length - 1)}\u00a0`; + } + if (secondString.charAt(0) === " ") { + secondString = `\u00a0${secondString.substr(1)}`; + } + } const inheritedColor = children.props.style.color; const splitStyle = { ...children.props.style, ...SPLIT_STYLE }; - const secondHalfInnerStyle = { + const secondHalfContentStyle = { ...SECOND_HALF_INNER_STYLE, - color: inheritedColor || "initial", + color: inheritedColor || "inherit", }; const truncatedJSX = ( {firstString} - {secondString} - {secondString} + {secondString} + {secondString} ); diff --git a/client/src/globals.js b/client/src/globals.js index e3c9a896..a39d7846 100644 --- a/client/src/globals.js +++ b/client/src/globals.js @@ -1,5 +1,6 @@ import { Colors } from "@blueprintjs/core"; import { dispatchNetworkErrorMessageToUser } from "./util/actionHelpers"; +import * as ENV_DEFAULT from "../../environment.default.json"; /* if a categorical metadata field has more options than this, truncate */ export const maxCategoricalOptionsToDisplay = 200; @@ -15,7 +16,6 @@ export const configDefaults = { features: {}, displayNames: {}, parameters: { - "max-category-items": 1000, "disable-diffexp": false, "diffexp-may-be-slow": false, }, @@ -83,21 +83,25 @@ export const maxGenes = 100; export const tooltipHoverOpenDelay = 1000; /* ms delay before a tooltip displays */ export const tooltipHoverOpenDelayQuick = 500; +const CXG_SERVER_PORT = + process.env.CXG_SERVER_PORT || ENV_DEFAULT.CXG_SERVER_PORT; + let _API; if (window.CELLXGENE && window.CELLXGENE.API) { _API = window.CELLXGENE.API; } else { - if (process.env.CXG_SERVER_PORT === undefined) { + if (CXG_SERVER_PORT === undefined) { const errorMessage = "Please set the CXG_SERVER_PORT environment variable."; dispatchNetworkErrorMessageToUser(errorMessage); throw new Error(errorMessage); } + _API = { // prefix: "http://api.clustering.czi.technology/api/", // prefix: "http://tabulamuris.cxg.czi.technology/api/", // prefix: "http://api-staging.clustering.czi.technology/api/", - prefix: `http://localhost:${process.env.CXG_SERVER_PORT}/api/`, + prefix: `http://localhost:${CXG_SERVER_PORT}/api/`, version: "v0.2/", }; } diff --git a/client/src/reducers/annoMatrix.js b/client/src/reducers/annoMatrix.js new file mode 100644 index 00000000..830e1834 --- /dev/null +++ b/client/src/reducers/annoMatrix.js @@ -0,0 +1,12 @@ +/* +Reducer for the annoMatrix +*/ + +const AnnoMatrix = (state = null, action) => { + if (action.annoMatrix) { + return action.annoMatrix; + } + return state; +}; + +export default AnnoMatrix; diff --git a/client/src/reducers/autosave.js b/client/src/reducers/autosave.js index b10039c1..929be152 100644 --- a/client/src/reducers/autosave.js +++ b/client/src/reducers/autosave.js @@ -2,22 +2,17 @@ const Autosave = ( state = { saveInProgress: false, error: false, - lastSavedObsAnnotations: null, - initialDataLoadComplete: false, + lastSavedAnnoMatrix: null, }, - action, - nextSharedState + action ) => { switch (action.type) { - case "initial data load complete (universe exists)": { - /* don't save on init */ - const { universe } = nextSharedState; + case "annoMatrix: init complete": { return { ...state, error: false, saveInProgress: false, - lastSavedObsAnnotations: universe.obsAnnotations, - initialDataLoadComplete: true, + lastSavedAnnoMatrix: action.annoMatrix, }; } @@ -29,21 +24,20 @@ const Autosave = ( } case "writable obs annotations - save error": { - const { message } = action; return { ...state, - error: message, + error: action.message, saveInProgress: false, }; } case "writable obs annotations - save complete": { - const lastSavedObsAnnotations = action.obsAnnotations; + const { lastSavedAnnoMatrix } = action; return { ...state, saveInProgress: false, error: false, - lastSavedObsAnnotations, + lastSavedAnnoMatrix, }; } diff --git a/client/src/reducers/categoricalSelection.js b/client/src/reducers/categoricalSelection.js index 3f3589f2..d1080699 100644 --- a/client/src/reducers/categoricalSelection.js +++ b/client/src/reducers/categoricalSelection.js @@ -1,121 +1,45 @@ import { ControlsHelpers as CH } from "../util/stateManager"; -const CategoricalSelection = ( - state, - action, - nextSharedState, - prevSharedState -) => { +/* +State is an object, with a key for each categorical annotation, and a +value which is a Map of label->t/f, reflecting selection state for the label. + +Label state default (if missing) is up to the component, but typically true. + +{ + "louvain": Map(), + ... +} +*/ +const CategoricalSelection = (state, action, nextSharedState) => { switch (action.type) { - case "initial data load complete (universe exists)": - case "set World to current selection": - case "reset World to eq Universe": + case "initial data load complete": + case "subset to selection": + case "reset subset": case "set clip quantiles": { - const { world } = nextSharedState; + const { annoMatrix } = nextSharedState; const newState = CH.createCategoricalSelection( - world, - CH.selectableCategoryNames( - world.schema, - CH.maxCategoryItems(prevSharedState.config) - ) + CH.selectableCategoryNames(annoMatrix.schema) ); return newState; } - case "universe: column load success": { - const { dim } = action; - if (dim !== "obsAnnotations") return state; - - const { dataframe } = action; - const { world } = nextSharedState; - const names = CH.selectableCategoryNames( - world.schema, - CH.maxCategoryItems(prevSharedState.config), - dataframe.colIndex.labels() - ); - if (names.length === 0) return state; + case "categorical metadata filter select": + case "categorical metadata filter deselect": + case "categorical metadata filter none of these": + case "categorical metadata filter all of these": { + const { metadataField, labelSelectionState } = action; return { ...state, - ...CH.createCategoricalSelection(world, names), + [metadataField]: labelSelectionState, }; } - case "categorical metadata filter select": { - /* - Set the specific category in this field to false - */ - const newCategoryValueSelected = Array.from( - state[action.metadataField].categoryValueSelected - ); - newCategoryValueSelected[action.categoryIndex] = true; - const newCategoricalSelection = { - ...state, - [action.metadataField]: { - ...state[action.metadataField], - categoryValueSelected: newCategoryValueSelected, - }, - }; - return newCategoricalSelection; - } - - case "categorical metadata filter deselect": { - /* - Set the specific category in this field to false - */ - const newCategoryValueSelected = Array.from( - state[action.metadataField].categoryValueSelected - ); - newCategoryValueSelected[action.categoryIndex] = false; - const newCategoricalSelection = { - ...state, - [action.metadataField]: { - ...state[action.metadataField], - categoryValueSelected: newCategoryValueSelected, - }, - }; - return newCategoricalSelection; - } - - case "categorical metadata filter none of these": { - /* - set all categories in this field to false. - */ - const newCategoricalSelection = { - ...state, - [action.metadataField]: { - ...state[action.metadataField], - categorySelected: false, - categoryValueSelected: Array.from( - state[action.metadataField].categoryValueSelected - ).fill(false), - }, - }; - return newCategoricalSelection; - } - - case "categorical metadata filter all of these": { - /* - set all categories in this field to true. - */ - const newCategoricalSelection = { - ...state, - [action.metadataField]: { - ...state[action.metadataField], - categorySelected: true, - categoryValueSelected: Array.from( - state[action.metadataField].categoryValueSelected - ).fill(true), - }, - }; - return newCategoricalSelection; - } - case "annotation: create category": { - const { world } = nextSharedState; const name = action.data; return { ...state, - ...CH.createCategoricalSelection(world, [name]), + ...CH.createCategoricalSelection([name]), }; } @@ -138,12 +62,11 @@ const CategoricalSelection = ( case "annotation: label edited": case "annotation: delete label": { /* need to rebuild the state for this annotation */ - const { world } = nextSharedState; const name = action.metadataField; const { [name]: _, ...partialState } = state; return { ...partialState, - ...CH.createCategoricalSelection(world, [name]), + ...CH.createCategoricalSelection([name]), }; } diff --git a/client/src/reducers/centroidLabels.js b/client/src/reducers/centroidLabels.js index 25d1d2d1..6481ec1d 100644 --- a/client/src/reducers/centroidLabels.js +++ b/client/src/reducers/centroidLabels.js @@ -1,71 +1,24 @@ -import calcCentroid from "../util/centroid"; - const initialState = { - labels: [], showLabels: false, }; const centroidLabels = (state = initialState, action, sharedNextState) => { const { - world, - layoutChoice, - categoricalSelection, colors: { colorAccessor }, } = sharedNextState; const showLabels = action.showLabels ?? state.showLabels; switch (action.type) { - case "annotation: label current cell selection": - case "annotation: label edited": - case "annotation: delete label": - case "set layout choice": - case "set World to current selection": - case "reset World to eq Universe": - return { - ...state, - labels: - !!colorAccessor && showLabels && !!categoricalSelection[colorAccessor] - ? calcCentroid( - world.obsAnnotations, - world.obsLayout, - colorAccessor, - layoutChoice.currentDimNames, - categoricalSelection, - world.schema.annotations.obsByName - ) - : [], - }; - case "color by categorical metadata": case "show centroid labels for category": // If colorby is not enabled or labels are not toggled to show // then clear the labels and make sure the toggle is off - if (!colorAccessor || !showLabels) { - return { - ...state, - labels: [], - showLabels, - }; - } - return { ...state, - labels: calcCentroid( - world.obsAnnotations, - world.obsLayout, - colorAccessor, - layoutChoice.currentDimNames, - categoricalSelection, - world.schema.annotations.obsByName - ), - showLabels, + showLabels: colorAccessor && showLabels, }; - case "color by continuous metadata": - case "color by expression": - return { ...state, labels: [] }; - case "reset centroid labels": return initialState; diff --git a/client/src/reducers/colors.js b/client/src/reducers/colors.js index a7c566fe..a95b51ce 100644 --- a/client/src/reducers/colors.js +++ b/client/src/reducers/colors.js @@ -1,32 +1,17 @@ -import { ColorHelpers } from "../util/stateManager"; +/* +Color By UI state +*/ const ColorsReducer = ( state = { colorMode: null, colorAccessor: null, - rgb: null, - scale: null, }, action, nextSharedState, prevSharedState ) => { switch (action.type) { - case "universe exists, but loading is still in progress": { - /* initialize everything with default colors, no mode, no color-by accessor */ - const { world } = nextSharedState; - const colorMode = null; - const colorAccessor = null; - const { rgb, scale } = ColorHelpers.createColors(world); - return { - ...state, - colorAccessor, - colorMode, - rgb, - scale, - }; - } - case "universe: user color load success": { const { userColors } = action; return { @@ -35,45 +20,28 @@ const ColorsReducer = ( }; } - case "reset World to eq Universe": { - /* need to rebuild colors as world may have changed, but don't switch modes */ - const { world } = nextSharedState; - const { colorMode, colorAccessor } = state; - const { rgb, scale } = ColorHelpers.createColors( - world, - colorMode, - colorAccessor - ); - return { - ...state, - rgb, - scale, - }; + case "clear differential expression": + case "set clip quantiles": + case "subset to selection": { + const { controls: prevControls } = prevSharedState; + if (prevControls.diffexpGenes.includes(state.colorAccessor)) { + return { + colorMode: null, + colorAccessor: null, + }; + } + return state; } - case "set clip quantiles": - case "set World to current selection": { - const { world: prevWorld, controls: prevControls } = prevSharedState; - const resetColorState = ColorHelpers.checkIfColorByDiffexpAndResetColors( - prevControls, - state, - prevWorld - ); - if (resetColorState) { - return resetColorState; + case "annotation: category edited": { + const { colorAccessor } = state; + if (action.metadataField !== colorAccessor) { + return state; } - - const { colorMode, colorAccessor } = state; - const { world } = nextSharedState; - const { rgb, scale } = ColorHelpers.createColors( - world, - colorMode, - colorAccessor - ); + /* else update colorAccessor */ return { ...state, - rgb, - scale, + colorAccessor: action.newCategoryText, }; } @@ -85,21 +53,21 @@ const ColorsReducer = ( /* else reset */ return { ...state, - ...ColorHelpers.resetColors(prevSharedState.world), + colorMode: null, + colorAccessor: null, }; } case "reset colorscale": { return { ...state, - ...ColorHelpers.resetColors(prevSharedState.world), + colorMode: null, + colorAccessor: null, }; } case "color by categorical metadata": case "color by continuous metadata": { - const { world, colors } = prevSharedState; - /* toggle between this mode and reset */ const resetCurrent = action.type === state.colorMode && @@ -107,78 +75,27 @@ const ColorsReducer = ( const colorMode = !resetCurrent ? action.type : null; const colorAccessor = !resetCurrent ? action.colorAccessor : null; - const { rgb, scale } = ColorHelpers.createColors( - world, - colorMode, - colorAccessor, - colors.userColors - ); return { ...state, colorMode, colorAccessor, - rgb, - scale, }; } case "color by expression": { - const { world } = prevSharedState; - /* toggle between this mode and reset */ const resetCurrent = action.type === state.colorMode && action.gene === state.colorAccessor; const colorMode = !resetCurrent ? action.type : null; const colorAccessor = !resetCurrent ? action.gene : null; - const { rgb, scale } = ColorHelpers.createColors( - world, - colorMode, - colorAccessor - ); return { ...state, colorMode, colorAccessor, - rgb, - scale, }; } - case "annotation: add new label to category": - case "annotation: label current cell selection": - case "annotation: delete label": { - const { world } = nextSharedState; - const { colorMode, colorAccessor } = state; - const { metadataField } = action; - if ( - colorMode !== "color by categorical metadata" || - colorAccessor !== metadataField - ) - return state; - - /* else, we need to rebuild colors as labels have changed! */ - const { rgb, scale } = ColorHelpers.createColors( - world, - colorMode, - colorAccessor - ); - return { ...state, rgb, scale }; - } - - case "clear differential expression": { - const { world: prevWorld, controls: prevControls } = prevSharedState; - const resetColorState = ColorHelpers.checkIfColorByDiffexpAndResetColors( - prevControls, - state, - prevWorld - ); - if (resetColorState) { - return resetColorState; - } - return state; - } - default: { return state; } diff --git a/client/src/reducers/continuousSelection.js b/client/src/reducers/continuousSelection.js index 98884983..6f823ca5 100644 --- a/client/src/reducers/continuousSelection.js +++ b/client/src/reducers/continuousSelection.js @@ -2,7 +2,7 @@ import { makeContinuousDimensionName } from "../util/nameCreators"; const ContinuousSelection = (state = {}, action) => { switch (action.type) { - case "reset World to eq Universe": + case "reset subset": case "set clip quantiles": { return {}; } diff --git a/client/src/reducers/controls.js b/client/src/reducers/controls.js index cdec252f..f8bfc37f 100644 --- a/client/src/reducers/controls.js +++ b/client/src/reducers/controls.js @@ -21,9 +21,7 @@ const Controls = ( scatterplotYYaccessor: null, graphRenderCounter: 0 /* integer as { /* For now, log anything looking like an error to the console. @@ -33,37 +31,10 @@ const Controls = ( } switch (action.type) { - /***************************************************** - Initialization, World/Universe management - and data loading. - ******************************************************/ case "initial data load start": { return { ...state, loading: true }; } - case "universe: column load success": { - /* - we are in a loading state until the following are true: - * universe exists (if partially) - * embeddings (obsLayout) are loaded - * varAnnotations index is loaded - */ - const universeExists = !!nextSharedState.universe; - const embeddingsExist = - !nextSharedState.universe?.obsLayout?.isEmpty() ?? false; - const varIndex = nextSharedState.universe.schema.annotations.var.index; - const varAnnotationsIndexExists = - universeExists && - nextSharedState.universe.varAnnotations.hasCol(varIndex); - return { - ...state, - loading: !( - universeExists && - embeddingsExist && - varAnnotationsIndexExists - ), - }; - } - case "initial data load complete (universe exists)": { + case "initial data load complete": { /* now fully loaded */ return { ...state, @@ -72,7 +43,7 @@ const Controls = ( resettingInterface: false, }; } - case "reset World to eq Universe": { + case "reset subset": { const [newUserDefinedGenes, newDiffExpGenes] = subsetAndResetGeneLists( state ); @@ -83,7 +54,7 @@ const Controls = ( diffexpGenes: newDiffExpGenes, }; } - case "set World to current selection": { + case "subset to selection": { const [newUserDefinedGenes, newDiffExpGenes] = subsetAndResetGeneLists( state ); @@ -119,15 +90,10 @@ const Controls = ( }; } case "request differential expression success": { - const { world } = prevSharedState; - const varIndexName = world.schema.annotations.var.index; - const _diffexpGenes = []; - action.data.forEach((d) => { - _diffexpGenes.push(world.varAnnotations.at(d[0], varIndexName)); - }); + const diffexpGenes = action.data.map((v) => v[0]); return { ...state, - diffexpGenes: _diffexpGenes, + diffexpGenes, }; } case "clear differential expression": { @@ -147,12 +113,6 @@ const Controls = ( userDefinedGenes: newUserDefinedGenes, }; } - case "clear all user defined genes": { - return { - ...state, - userDefinedGenes: [], - }; - } case "initial data load error": { return { ...state, @@ -181,12 +141,6 @@ const Controls = ( graphRenderCounter: c, }; } - case "interface reset started": { - return { - ...state, - resettingInterface: true, - }; - } /******************************* Scatterplot diff --git a/client/src/reducers/crossfilter.js b/client/src/reducers/crossfilter.js deleted file mode 100644 index f4488965..00000000 --- a/client/src/reducers/crossfilter.js +++ /dev/null @@ -1,325 +0,0 @@ -import _ from "lodash"; - -import Crossfilter from "../util/typedCrossfilter"; -import { - World, - ControlsHelpers, - AnnotationsHelpers, -} from "../util/stateManager"; -import { - layoutDimensionName, - obsAnnoDimensionName, - userDefinedDimensionName, - diffexpDimensionName, - makeContinuousDimensionName, -} from "../util/nameCreators"; - -const XYDimName = layoutDimensionName("XY"); - -const CrossfilterReducerBase = ( - state = null, - action, - nextSharedState, - prevSharedState -) => { - switch (action.type) { - case "universe: column load success": { - const { world, layoutChoice } = nextSharedState; - const { obsAnnotations, obsLayout } = world; - - // ignore var dimension loads as these are not currently selectable - if (action.dim === "varAnnotations") return state; - - /* - during bootstrap loading, we don't know if obsLayout or obsAnnotations - will load first. Take whichever arrives and is not empty (so that our - crossfilter has the right dimensionality). - */ - let crossfilter = - state ?? - new Crossfilter(obsAnnotations.isEmpty() ? obsLayout : obsAnnotations); - - // add layout dimension, if not already present - if ( - obsLayout.hasCol(layoutChoice.currentDimNames[0]) && - !crossfilter.hasDimension(XYDimName) - ) { - crossfilter = crossfilter.addDimension( - XYDimName, - "spatial", - obsLayout.col(layoutChoice.currentDimNames[0]).asArray(), - obsLayout.col(layoutChoice.currentDimNames[1]).asArray() - ); - } - - // add any missing obsAnnotations - crossfilter = World.addObsDimensions(crossfilter, world); - return crossfilter; - } - - case "reset World to eq Universe": { - const { userDefinedGenes, diffexpGenes } = nextSharedState.controls; - const { world } = nextSharedState; - let { crossfilter } = prevSharedState.resetCache; - crossfilter = ControlsHelpers.createGeneDimensions( - userDefinedGenes, - diffexpGenes, - world, - crossfilter - ); - crossfilter = AnnotationsHelpers.createWritableAnnotationDimensions( - world, - crossfilter - ); - return crossfilter; - } - - case "set clip quantiles": - case "set World to current selection": { - const { userDefinedGenes, diffexpGenes } = nextSharedState.controls; - const { world, layoutChoice } = nextSharedState; - let crossfilter = new Crossfilter(world.obsAnnotations); - crossfilter = World.createObsDimensions( - crossfilter, - world, - layoutChoice.currentDimNames - ); - crossfilter = ControlsHelpers.createGeneDimensions( - userDefinedGenes, - diffexpGenes, - world, - crossfilter - ); - return crossfilter; - } - - case "set layout choice": { - /* - when switching layouts: - - delete the existing XY index - - add the new XY index (which implicitly selects all on it) - */ - const { world, layoutChoice } = nextSharedState; - return state - .delDimension(layoutDimensionName("XY")) - .addDimension( - layoutDimensionName("XY"), - "spatial", - world.obsLayout.col(layoutChoice.currentDimNames[0]).asArray(), - world.obsLayout.col(layoutChoice.currentDimNames[1]).asArray() - ); - } - - case "request user defined gene success": { - const { world } = nextSharedState; - const gene = action.data.genes[0]; - return state.addDimension( - userDefinedDimensionName(gene), - "scalar", - world.varData.col(gene).asArray(), - Float32Array - ); - } - - case "request differential expression success": { - const { world } = nextSharedState; - const varIndexName = world.schema.annotations.var.index; - const genes = _.map(action.data, (d) => - world.varAnnotations.at(d[0], varIndexName) - ); - const crossfilter = _.reduce( - genes, - (xfltr, gene) => - xfltr.addDimension( - diffexpDimensionName(gene), - "scalar", - world.varData.col(gene).asArray(), - Float32Array - ), - state - ); - return crossfilter; - } - - case "clear differential expression": { - const { world } = nextSharedState; - const varIndexName = world.schema.annotations.var.index; - const crossfilter = _.reduce( - action.diffExp, - (xfltr, values) => { - const name = world.varAnnotations.at(values[0], varIndexName); - return xfltr.delDimension(diffexpDimensionName(name)); - }, - state - ); - return crossfilter; - } - - case "clear user defined gene": { - return state.delDimension(userDefinedDimensionName(action.data)); - } - - case "clear all user defined genes": { - const { userDefinedGenes } = nextSharedState.controls; - const crossfilter = _.reduce( - userDefinedGenes, - (xfltr, gene) => xfltr.delDimension(userDefinedDimensionName(gene)), - state - ); - return crossfilter; - } - - case "annotation: create category": { - const name = action.data; - const { world } = nextSharedState; - const colData = world.obsAnnotations.col(name).asArray(); - return state.addDimension(obsAnnoDimensionName(name), "enum", colData); - } - - case "annotation: category edited": { - const name = action.metadataField; - const newName = action.newCategoryText; - return state.renameDimension( - obsAnnoDimensionName(name), - obsAnnoDimensionName(newName) - ); - } - - case "annotation: delete category": { - return state.delDimension(obsAnnoDimensionName(action.metadataField)); - } - - case "annotation: add new label to category": - case "annotation: label current cell selection": - case "annotation: label edited": - case "annotation: delete label": { - if ( - action.type === "annotation: add new label to category" && - !action.assignSelectedCells - ) - return state; - - /* we need to reindex the dimension. For now, just drop it and add another */ - const name = action.metadataField; - const dimName = obsAnnoDimensionName(name); - const { world } = nextSharedState; - const colData = world.obsAnnotations.col(name).asArray(); - return state.delDimension(dimName).addDimension(dimName, "enum", colData); - } - - case "graph brush end": - case "graph brush change": { - const [minX, maxY] = action.brushCoords.northwest; - const [maxX, minY] = action.brushCoords.southeast; - return state.select(XYDimName, { - mode: "within-rect", - minX, - minY, - maxX, - maxY, - }); - } - - case "graph lasso end": { - const { polygon } = action; - return state.select(XYDimName, { - mode: "within-polygon", - polygon, - }); - } - - case "graph lasso cancel": - case "graph brush cancel": - case "graph lasso deselect": - case "graph brush deselect": { - return state.select(XYDimName, { mode: "all" }); - } - - case "continuous metadata histogram start": - case "continuous metadata histogram brush": - case "continuous metadata histogram cancel": - case "continuous metadata histogram end": { - const name = makeContinuousDimensionName( - action.continuousNamespace, - action.selection - ); - // action.selection: metadata name being selected - // action.range: filter range, or null if deselected - if (!action.range) { - return state.select(name, { mode: "all" }); - } - const [lo, hi] = action.range; - const newState = state.select(name, { - mode: "range", - lo, - hi, - inclusive: true, // [lo, hi] incluisve selection - }); - return newState; - } - - case "categorical metadata filter select": - case "categorical metadata filter deselect": { - const { categoricalSelection } = nextSharedState; - const cat = categoricalSelection[action.metadataField]; - const { categoryValues, categoryValueSelected } = cat; - const values = categoryValues.filter((v, i) => categoryValueSelected[i]); - return state.select(obsAnnoDimensionName(action.metadataField), { - mode: "exact", - values, - }); - } - - case "categorical metadata filter none of these": { - return state.select(obsAnnoDimensionName(action.metadataField), { - mode: "none", - }); - } - - case "categorical metadata filter all of these": { - return state.select(obsAnnoDimensionName(action.metadataField), { - mode: "all", - }); - } - - default: { - return state; - } - } -}; - -/* - IMPORTANT: the system assumes that crossfilter.data() will point at the - same value as world.obsAnnotations. For actions handled in this reducer, - make sure that this remains true. - - This wrapper performs only this function. -*/ -const CrossfilterReducer = ( - state, - action, - nextSharedState, - prevSharedState -) => { - const nextState = CrossfilterReducerBase( - state, - action, - nextSharedState, - prevSharedState - ); - /* - update the data in the crossfilter to point at the current obsAnnotations, IF - they are not empty. If empty, leave it alone (can occur during boostrap loading). - */ - const nextObsAnnotations = nextSharedState.world?.obsAnnotations; - if ( - !nextState || - nextState.all() === nextObsAnnotations || - nextObsAnnotations.isEmpty() - ) { - return nextState; - } - return nextState.setData(nextObsAnnotations); -}; - -export default CrossfilterReducer; diff --git a/client/src/reducers/differential.js b/client/src/reducers/differential.js index 1f9c35ff..8ef7e2b6 100644 --- a/client/src/reducers/differential.js +++ b/client/src/reducers/differential.js @@ -47,8 +47,8 @@ const Differential = ( celllist1: null, celllist2: null, }; - case "reset World to eq Universe": - case "set World to current selection": + case "reset subset": + case "subset to selection": return { ...state, diffExp: null, diff --git a/client/src/reducers/graphSelection.js b/client/src/reducers/graphSelection.js index fce495e4..7b5b47c6 100644 --- a/client/src/reducers/graphSelection.js +++ b/client/src/reducers/graphSelection.js @@ -7,7 +7,7 @@ const GraphSelection = ( ) => { switch (action.type) { case "set clip quantiles": - case "reset World to eq Universe": + case "reset subset": case "set layout choice": { return { ...state, diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index e0703c9e..d910cef0 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -4,31 +4,30 @@ import thunk from "redux-thunk"; import cascadeReducers from "./cascade"; import undoable from "./undoable"; import config from "./config"; -import universe from "./universe"; -import world from "./world"; +import annoMatrix from "./annoMatrix"; +import obsCrossfilter from "./obsCrossfilter"; import categoricalSelection from "./categoricalSelection"; import continuousSelection from "./continuousSelection"; import graphSelection from "./graphSelection"; -import crossfilter from "./crossfilter"; import colors from "./colors"; import differential from "./differential"; import layoutChoice from "./layoutChoice"; import controls from "./controls"; -import resetCache from "./resetCache"; import annotations from "./annotations"; import autosave from "./autosave"; import ontology from "./ontology"; import centroidLabels from "./centroidLabels"; import pointDialation from "./pointDilation"; import { reembedController, reembedding } from "./reembed"; +import { gcMiddleware as annoMatrixGC } from "../annoMatrix"; import undoableConfig from "./undoableConfig"; const Reducer = undoable( cascadeReducers([ ["config", config], - ["universe", universe], - ["world", world], + ["annoMatrix", annoMatrix], + ["obsCrossfilter", obsCrossfilter], ["ontology", ontology], ["annotations", annotations], ["reembedding", reembedding], @@ -38,21 +37,18 @@ const Reducer = undoable( ["graphSelection", graphSelection], ["colors", colors], ["controls", controls], - ["crossfilter", crossfilter], ["differential", differential], ["centroidLabels", centroidLabels], ["pointDilation", pointDialation], ["reembedController", reembedController], ["autosave", autosave], - ["resetCache", resetCache], ]), [ - "universe", - "world", + "annoMatrix", + "obsCrossfilter", "categoricalSelection", "continuousSelection", "graphSelection", - "crossfilter", "colors", "controls", "differential", @@ -64,6 +60,6 @@ const Reducer = undoable( undoableConfig ); -const store = createStore(Reducer, applyMiddleware(thunk)); +const store = createStore(Reducer, applyMiddleware(thunk, annoMatrixGC)); export default store; diff --git a/client/src/reducers/layoutChoice.js b/client/src/reducers/layoutChoice.js index fa04f6f7..48cff07e 100644 --- a/client/src/reducers/layoutChoice.js +++ b/client/src/reducers/layoutChoice.js @@ -14,8 +14,7 @@ function bestDefaultLayout(layouts) { return layouts[0]; } -function setToDefaultLayout(world) { - const { schema } = world; +function setToDefaultLayout(schema) { const available = schema.layout.obs.map((v) => v.name).sort(); const current = bestDefaultLayout(available); const currentDimNames = schema.layout.obsByName[current].dims; @@ -32,17 +31,17 @@ const LayoutChoice = ( nextSharedState ) => { switch (action.type) { - case "universe exists, but loading is still in progress": { + case "initial data load complete": { // set default to default - const { universe } = nextSharedState; + const { annoMatrix } = nextSharedState; return { ...state, - ...setToDefaultLayout(universe), + ...setToDefaultLayout(annoMatrix.schema), }; } case "set layout choice": { - const { schema } = nextSharedState.world; + const { schema } = nextSharedState.annoMatrix; const current = action.layoutChoice; const currentDimNames = schema.layout.obsByName[current].dims; return { ...state, current, currentDimNames }; @@ -58,9 +57,9 @@ const LayoutChoice = ( } case "reembed: clear all reembeddings": { - const { universe } = nextSharedState; + const { annoMatrix } = nextSharedState; const { current } = state; - const dflt = setToDefaultLayout(universe); + const dflt = setToDefaultLayout(annoMatrix.schema); if (dflt.available.includes(current)) { return { ...state, diff --git a/client/src/reducers/obsCrossfilter.js b/client/src/reducers/obsCrossfilter.js new file mode 100644 index 00000000..5fc2199a --- /dev/null +++ b/client/src/reducers/obsCrossfilter.js @@ -0,0 +1,12 @@ +/* +Reducer for the obsCrossfilter +*/ + +const ObsCrossfilter = (state = null, action) => { + if (action.obsCrossfilter) { + return action.obsCrossfilter; + } + return state; +}; + +export default ObsCrossfilter; diff --git a/client/src/reducers/pointDilation.js b/client/src/reducers/pointDilation.js index f9aed56c..f8155545 100644 --- a/client/src/reducers/pointDilation.js +++ b/client/src/reducers/pointDilation.js @@ -3,12 +3,8 @@ const initialState = { categoryField: "", }; -const pointDialation = (state = initialState, action, sharedNextState) => { - const { categoricalSelection } = sharedNextState; - const { metadataField, categoryIndex } = action; - const categoryField = - action.categoryField || - categoricalSelection?.[metadataField]?.categoryValues[categoryIndex]; +const pointDialation = (state = initialState, action) => { + const { metadataField, label: categoryField } = action; switch (action.type) { case "category value mouse hover start": diff --git a/client/src/reducers/resetCache.js b/client/src/reducers/resetCache.js deleted file mode 100644 index a698f3f8..00000000 --- a/client/src/reducers/resetCache.js +++ /dev/null @@ -1,31 +0,0 @@ -/* -Reducer which caches derived state to be used in a reset or other -recomputation. Add stuff here you want stashed at init time (or whenever), -for later use. - -Currently this only caches the baseline (full universe) crossfilter, -which improves Reset UI performance. -*/ -const ResetCacheReducer = ( - state = { - crossfilter: null, - }, - action, - nextSharedState -) => { - switch (action.type) { - case "initial data load complete (universe exists)": - case "set layout choice": { - const { crossfilter } = nextSharedState; - return { - ...state, - crossfilter, - }; - } - default: { - return state; - } - } -}; - -export default ResetCacheReducer; diff --git a/client/src/reducers/undoableConfig.js b/client/src/reducers/undoableConfig.js index 96a069e7..1f298eb1 100644 --- a/client/src/reducers/undoableConfig.js +++ b/client/src/reducers/undoableConfig.js @@ -8,12 +8,10 @@ const stateKey = "@@undoable/filterState"; these actions will not affect history */ const skipOnActions = new Set([ + "annoMatrix: init complete", "url changed", - "interface reset started", "initial data load start", - "universe: column load success", "universe: user color load success", - "universe exists, but loading is still in progress", "configuration load complete", "increment graph render counter", "window resize", @@ -23,16 +21,7 @@ const skipOnActions = new Set([ "graph brush change", "continuous metadata histogram brush", - "expression load start", - "expression load success", - "expression load error", - - "request user defined gene started", "request user defined gene success", - "clear all user defined genes", - - "get single gene expression for coloring started", - "get single gene expression for coloring error", "category value mouse hover start", "category value mouse hover end", @@ -62,8 +51,7 @@ const debounceOnActions = new Set([]); history will be cleared when these actions occur */ const clearOnActions = new Set([ - "initial data load complete (universe exists)", - "reset World to eq Universe", + "initial data load complete", "initial data load error", ]); @@ -88,7 +76,7 @@ const saveOnActions = new Set([ "store current cell selection as differential set 1", "store current cell selection as differential set 2", - "set World to current selection", + "subset to selection", "set clip quantiles", "set layout choice", diff --git a/client/src/reducers/universe.js b/client/src/reducers/universe.js deleted file mode 100644 index f3df95c2..00000000 --- a/client/src/reducers/universe.js +++ /dev/null @@ -1,308 +0,0 @@ -import { unassignedCategoryLabel } from "../globals"; -import { - addObsAnnotations, - addVarAnnotations, - addObsLayout, -} from "../util/stateManager/universe"; -import { - World, - ControlsHelpers, - AnnotationsHelpers, -} from "../util/stateManager"; - -const Universe = (state = null, action, nextSharedState, prevSharedState) => { - switch (action.type) { - case "universe exists, but loading is still in progress": { - const { universe } = action; - return universe; - } - - case "universe: column load success": { - const { dim, dataframe } = action; - switch (dim) { - case "obsAnnotations": { - return { - ...state, - ...addObsAnnotations(state, dataframe), - }; - } - case "varAnnotations": { - return { - ...state, - ...addVarAnnotations(state, dataframe), - }; - } - case "obsLayout": { - return { - ...state, - ...addObsLayout(state, dataframe), - }; - } - default: { - throw new Error("action handler not implemented"); - } - } - } - - case "expression load success": { - let { varData } = state; - - // Lazy load new expression data into the varData dataframe, if - // not already present. - // - Object.entries(action.expressionData).forEach(([key, val]) => { - // If not already in universe.varData, save entire expression column - if (!varData.hasCol(key)) { - varData = varData.withCol(key, val); - } - }); - - // Prune size of varData "cache" if getting out of hand.... - // - const { userDefinedGenes, diffexpGenes } = prevSharedState; - const allTheGenesWeNeed = [ - ...new Set( - [userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)] - .filter((ele) => ele) - .flat() - ), - ]; - varData = ControlsHelpers.pruneVarDataCache(varData, allTheGenesWeNeed); - return { ...state, varData }; - } - - case "annotation: create category": { - /* create a new annotation category, with all values set to 'unassigned' */ - const name = action.data; - const { categoryToDuplicate } = action; - - /* name must be a string, non-zero length */ - if (typeof name !== "string" || name.length === 0) - throw new Error("user annotations require string name"); - - /* ensure the name isn't already in use! */ - if (state.obsAnnotations.hasCol(name)) - throw new Error("name collision on annotation category create"); - - /* ensure the duplicate col exists */ - if ( - categoryToDuplicate && - !state.obsAnnotations.hasCol(categoryToDuplicate) - ) - throw new Error("categoryToDuplicate does not exist"); - - let schema; - let data; - if (categoryToDuplicate) { - /* duplicate the named annotation */ - schema = AnnotationsHelpers.dupObsAnnoSchema( - state.schema, - categoryToDuplicate, - name, - { - writable: true, - } - ); - /* if we are duplicating a non-writable annotation, it may not have an unassigned category */ - const s = schema.annotations.obsByName[name]; - if (s.categories.indexOf(unassignedCategoryLabel) === -1) { - s.categories = s.categories.concat(unassignedCategoryLabel); - } - data = state.obsAnnotations.col(categoryToDuplicate).asArray(); - } else { - /* else, all are unassined */ - const categories = [unassignedCategoryLabel]; - schema = AnnotationsHelpers.addObsAnnoSchema(state.schema, name, { - name, - categories, - type: "categorical", - writable: true, - }); - data = new Array(state.nObs).fill(unassignedCategoryLabel); - } - - const obsAnnotations = state.obsAnnotations.withCol(name, data); - return { ...state, obsAnnotations, schema }; - } - - case "annotation: category edited": { - /* change the name of an obs annotation category */ - const name = action.metadataField; - const newName = action.newCategoryText; - if (!AnnotationsHelpers.isUserAnnotation(state, name)) - throw new Error("unable to edit read-only annotation"); - if (typeof newName !== "string" || newName.length === 0) - throw new Error("user annotations require string name"); - - const colSchema = { - ...state.schema.annotations.obsByName[name], - name: newName, - }; - const schema = AnnotationsHelpers.addObsAnnoSchema( - AnnotationsHelpers.removeObsAnnoSchema(state.schema, name), - newName, - colSchema - ); - const obsAnnotations = state.obsAnnotations.renameCol(name, newName); - return { ...state, schema, obsAnnotations }; - } - - case "annotation: delete category": { - /* delete annotation category from schema and obsAnnotations */ - const name = action.metadataField; - if (!AnnotationsHelpers.isUserAnnotation(state, name)) - throw new Error("unable to delete read-only annotation"); - - const schema = AnnotationsHelpers.removeObsAnnoSchema(state.schema, name); - const obsAnnotations = state.obsAnnotations.dropCol(name); - return { ...state, schema, obsAnnotations }; - } - - case "annotation: add new label to category": { - const annotationName = action.metadataField; - const newLabelName = action.newLabelText; - if (!AnnotationsHelpers.isUserAnnotation(state, annotationName)) - throw new Error("unable to modify read-only annotation"); - if (typeof newLabelName !== "string" || newLabelName.length === 0) - throw new Error( - "user annotations require a non-zero length string name" - ); - - /* add the new label to the annotation schema */ - const schema = AnnotationsHelpers.addObsAnnoCategory( - state.schema, - annotationName, - newLabelName - ); - - /* if so requested, label the current selection */ - const { world, crossfilter } = prevSharedState; - const { metadataField, newLabelText } = action; - const obsAnnotations = !action.assignSelectedCells - ? state.obsAnnotations - : setLabelOnCurrentSelection( - state, - world, - crossfilter, - metadataField, - newLabelText - ); - - return { ...state, schema, obsAnnotations }; - } - - case "annotation: label edited": { - const annotationName = action.metadataField; - const oldLabelName = action.label; - const newLabelName = action.editedLabel; - if (!AnnotationsHelpers.isUserAnnotation(state, annotationName)) - throw new Error("unable to modify read-only annotation"); - if (typeof newLabelName !== "string" || newLabelName.length === 0) - throw new Error( - "user annotations require a non-zero length string name" - ); - - /* remove old label, add new label */ - const schema = AnnotationsHelpers.addObsAnnoCategory( - AnnotationsHelpers.removeObsAnnoCategory( - state.schema, - annotationName, - oldLabelName - ), - annotationName, - newLabelName - ); - - /* change all values in obsAnnotation */ - const obsAnnotations = AnnotationsHelpers.setLabelByValue( - state.obsAnnotations, - annotationName, - oldLabelName, - newLabelName - ); - - return { ...state, schema, obsAnnotations }; - } - - case "annotation: delete label": { - /* delete the label from the annotation, and set all cells with this value to unassigned */ - const annotationName = action.metadataField; - const labelName = action.label; - if (!AnnotationsHelpers.isUserAnnotation(state, annotationName)) - throw new Error("unable to modify read-only annotation"); - if (labelName === unassignedCategoryLabel) - throw new Error("may not remove the unassigned label"); - - /* remove the category from the schema */ - const schema = AnnotationsHelpers.removeObsAnnoCategory( - state.schema, - annotationName, - labelName - ); - - /* set all values to unassigned in obsAnnotations */ - const obsAnnotations = AnnotationsHelpers.setLabelByValue( - state.obsAnnotations, - annotationName, - labelName, - unassignedCategoryLabel - ); - - return { ...state, schema, obsAnnotations }; - } - - case "annotation: label current cell selection": { - const { metadataField, label } = action; - const { world, crossfilter } = prevSharedState; - const obsAnnotations = setLabelOnCurrentSelection( - state, - world, - crossfilter, - metadataField, - label - ); - return { ...state, obsAnnotations }; - } - - default: { - return state; - } - } -}; - -function setLabelOnCurrentSelection( - universe, - world, - crossfilter, - metadataField, - label -) { - /* - Set category `metadataField` to value `label` for anything currently selected. - Used by several action type reducers. - - Returns the new obsAnnotations dataframe. - */ - - /* - selection state is relative to world. We need to convert it - to a mask for Universe before applying it. - */ - const worldMask = crossfilter.allSelectedMask(); - const mask = World.worldEqUniverse(world, universe) - ? worldMask - : AnnotationsHelpers.worldToUniverseMask( - worldMask, - world.obsAnnotations, - universe.nObs - ); - const obsAnnotations = AnnotationsHelpers.setLabelByMask( - universe.obsAnnotations, - metadataField, - mask, - label - ); - return obsAnnotations; -} - -export default Universe; diff --git a/client/src/reducers/world.js b/client/src/reducers/world.js deleted file mode 100644 index b1f2e410..00000000 --- a/client/src/reducers/world.js +++ /dev/null @@ -1,370 +0,0 @@ -import { unassignedCategoryLabel } from "../globals"; -import { - World, - ControlsHelpers, - AnnotationsHelpers, -} from "../util/stateManager"; -import { - addObsLayout, - removeObsLayout, -} from "../util/stateManager/schemaHelpers"; -import clip from "../util/clip"; -import quantile from "../util/quantile"; - -/* -important note: much of this code assumes that wriable (user) annotations -will NOT contain scalar data (ie, will only contain categorical labelled -data), and therefore will never need to be clipped. Put another way, it -assumes that for these annotations, the clipped & unclipped data is equal. - -If we ever start allowing user editable scalar data, this assumption will -need to be revisited. -*/ - -const WorldReducer = ( - state = null, - action, - nextSharedState, - prevSharedState -) => { - switch (action.type) { - case "universe exists, but loading is still in progress": - case "reset World to eq Universe": { - const { universe } = nextSharedState; - const world = World.createWorldFromEntireUniverse(universe); - return world; - } - - case "universe: column load success": { - /* incremental initial data load - always assumes world == universe */ - const { universe } = nextSharedState; - const { dim } = action; - - // we don't clip anything except for varData and obsAnnotations - let { unclipped } = state; - if (dim === "varData" || dim === "obsAnnotations") { - unclipped = { - ...unclipped, - [dim]: universe[dim].clone(), - }; - } - return { - ...state, - schema: universe.schema, - [dim]: universe[dim].clone(), - unclipped, - }; - } - - case "set World to current selection": { - /* Set viewable world to be the currently selected data */ - const world = World.createWorldBySelection( - action.universe, - action.world, - action.crossfilter - ); - return world; - } - - case "set clip quantiles": { - const world = World.createWorldWithNewClip( - prevSharedState.universe, - state, - prevSharedState.crossfilter, - action.clipQuantiles - ); - return world; - } - - case "expression load success": { - const { universe } = nextSharedState; - const universeVarData = universe.varData; - let unclippedVarData = state.unclipped.varData; - - // Lazy load new expression data into the unclipped varData dataframe, if - // not already present. - // - Object.entries(action.expressionData).forEach(([key, val]) => { - // If not already in world.varData, save sliced expression column - if (!unclippedVarData.hasCol(key)) { - // Slice if world !== universe, else just use whole column. - // Use the obsAnnotation index as the cut key, as we keep - // all world dataframes in sync. - let worldValSlice = val; - if (!World.worldEqUniverse(state, universe)) { - worldValSlice = universeVarData - .subset(state.obsAnnotations.rowIndex.labels(), [key], null) - .icol(0) - .asArray(); - } - - // Now build world's varData dataframe - unclippedVarData = unclippedVarData.withCol( - key, - worldValSlice, - state.obsAnnotations.rowIndex - ); - } - }); - - // Prune size of varData unclipped dataframe if getting out of hand.... - // - const { userDefinedGenes, diffexpGenes } = prevSharedState; - const allTheGenesWeNeed = [ - ...new Set( - [userDefinedGenes, diffexpGenes, Object.keys(action.expressionData)] - .filter((ele) => ele) - .flat() - ), - ]; - unclippedVarData = ControlsHelpers.pruneVarDataCache( - unclippedVarData, - allTheGenesWeNeed - ); - - // at this point, we have the unclipped data in unclippedVarData. - // Now create clipped. - // - Drop columns no longer needed - // - Add new columns - // - let clippedVarData = state.varData; - const keysToDrop = clippedVarData.colIndex - .labels() - .filter((k) => !unclippedVarData.hasCol(k)); - const keysToAdd = unclippedVarData.colIndex - .labels() - .filter((k) => !clippedVarData.hasCol(k)); - keysToDrop.forEach((k) => { - clippedVarData = clippedVarData.dropCol(k); - }); - keysToAdd.forEach((k) => { - const data = unclippedVarData.col(k).asArray(); - const q = [state.clipQuantiles.min, state.clipQuantiles.max]; - const [qMinVal, qMaxVal] = quantile(q, data); - const clippedData = clip(data, qMinVal, qMaxVal, Number.NaN); - clippedVarData = clippedVarData.withCol( - k, - clippedData, - state.obsAnnotations.rowIndex - ); - }); - - return { - ...state, - varData: clippedVarData, - unclipped: { - ...state.unclipped, - varData: unclippedVarData, - }, - }; - } - - case "annotation: create category": { - const name = action.data; - const { universe } = nextSharedState; - const { schema } = universe; - - /* - if world !== universe, we have to subset the newly created annotation, - else, just use it as is. - */ - let newAnnotation = null; - if (!World.worldEqUniverse(state, universe)) { - newAnnotation = universe.obsAnnotations - .subset(state.obsAnnotations.rowIndex.labels(), [name], null) - .icol(0) - .asArray(); - } else { - newAnnotation = universe.obsAnnotations.col(name).asArray(); - } - const obsAnnotations = state.obsAnnotations.withCol( - name, - newAnnotation, - state.obsAnnotations.rowIndex - ); - const unclipped = { - ...state.unclipped, - obsAnnotations: state.unclipped.obsAnnotations.withCol( - name, - newAnnotation, - state.unclipped.obsAnnotations.rowIndex - ), - }; - return { ...state, schema, obsAnnotations, unclipped }; - } - - case "annotation: category edited": { - /* change the name of an obs annotation */ - const name = action.metadataField; - const newName = action.newCategoryText; - const { schema } = nextSharedState.universe; - const obsAnnotations = state.obsAnnotations.renameCol(name, newName); - const unclipped = { - ...state.unclipped, - obsAnnotations: state.unclipped.obsAnnotations.renameCol(name, newName), - }; - return { ...state, schema, obsAnnotations, unclipped }; - } - - case "annotation: delete category": { - /* remove a category from obs annotation */ - const { schema } = nextSharedState.universe; - const name = action.metadataField; - const obsAnnotations = state.obsAnnotations.dropCol(name); - const unclipped = { - ...state.unclipped, - obsAnnotations: state.unclipped.obsAnnotations.dropCol(name), - }; - return { ...state, schema, obsAnnotations, unclipped }; - } - - case "annotation: add new label to category": { - /* add a new label to the schema - schema updated by universe reducer, we just need to note it */ - const { schema } = nextSharedState.universe; - const { metadataField, newLabelText } = action; - const { crossfilter } = prevSharedState; - - if (action.assignSelectedCells) { - return { - ...state, - schema, - ...setLabelOnCurrentSelection( - state, - crossfilter, - metadataField, - newLabelText - ), - }; - } - return { ...state, schema }; - } - - case "annotation: label edited": { - const { schema } = nextSharedState.universe; - const { metadataField } = action; - const oldLabelName = action.label; - const newLabelName = action.editedLabel; - - /* set all values to to new label */ - const unclipped = { - ...state.unclipped, - obsAnnotations: AnnotationsHelpers.setLabelByValue( - state.unclipped.obsAnnotations, - metadataField, - oldLabelName, - newLabelName - ), - }; - const obsAnnotations = state.obsAnnotations.replaceColData( - metadataField, - unclipped.obsAnnotations.col(metadataField).asArray() - ); - return { ...state, schema, obsAnnotations, unclipped }; - } - - case "annotation: delete label": { - const { schema } = nextSharedState.universe; - const { label, metadataField } = action; - - /* set all values to unassigned in obsAnnotations */ - const unclipped = { - ...state.unclipped, - obsAnnotations: AnnotationsHelpers.setLabelByValue( - state.unclipped.obsAnnotations, - metadataField, - label, - unassignedCategoryLabel - ), - }; - const obsAnnotations = state.obsAnnotations.replaceColData( - metadataField, - unclipped.obsAnnotations.col(metadataField).asArray() - ); - return { ...state, schema, obsAnnotations, unclipped }; - } - - case "annotation: label current cell selection": { - const { metadataField, label } = action; - const { crossfilter } = prevSharedState; - return { - ...state, - ...setLabelOnCurrentSelection(state, crossfilter, metadataField, label), - }; - } - - case "reembed: add reembedding": { - // new embedding loaded, which *only* affects world's layout. - // It may be new, or it may replace a previous re-embedding. - const { obsLayout: origObsLayout, schema: origSchema } = state; - const { embedding, schema: embeddingSchema } = action; - - const { dims } = embeddingSchema; - let obsLayout = origObsLayout; - let schema = origSchema; - - // alias the names the server sent us, in case they were not the same as the schema - const embedingLabels = embedding.colIndex.labels(); - const labels = { - [embedingLabels[0]]: dims[0], - [embedingLabels[1]]: dims[1], - }; - obsLayout = obsLayout.withColsFrom(embedding, labels); - schema = addObsLayout(schema, embeddingSchema); - return { - ...state, - obsLayout, - schema, - }; - } - - case "reembed: clear all reembeddings": { - // reembedding was cleared -- remove from layout - const { obsLayout: origObsLayout, schema: origSchema } = state; - const { reembedding } = prevSharedState; - - let schema = origSchema; - let obsLayout = origObsLayout; - - reembedding.reembeddings.forEach((emb, name) => { - const { dims } = emb.schema; - obsLayout = obsLayout.dropCol(dims[0]); - obsLayout = obsLayout.dropCol(dims[1]); - schema = removeObsLayout(schema, name); - }); - return { - ...state, - obsLayout, - schema, - }; - } - - default: { - return state; - } - } -}; - -function setLabelOnCurrentSelection(world, crossfilter, metadataField, label) { - /* - Set category `metadataField` to value `label` for anything currently selected. - Used by several action type reducers. - */ - const mask = crossfilter.allSelectedMask(); - const unclipped = { - ...world.unclipped, - obsAnnotations: AnnotationsHelpers.setLabelByMask( - world.unclipped.obsAnnotations, - metadataField, - mask, - label - ), - }; - const obsAnnotations = world.obsAnnotations.replaceColData( - metadataField, - unclipped.obsAnnotations.col(metadataField).asArray() - ); - - return { obsAnnotations, unclipped }; -} - -export default WorldReducer; diff --git a/client/src/util/actionHelpers.js b/client/src/util/actionHelpers.js index 4c89be41..85523ae3 100644 --- a/client/src/util/actionHelpers.js +++ b/client/src/util/actionHelpers.js @@ -6,8 +6,14 @@ import { postNetworkErrorToast } from "../components/framework/toasters"; dispatch an action error to the user. Currently we use async toasts. */ -export const dispatchNetworkErrorMessageToUser = (message) => - postNetworkErrorToast(message); +let networkErrorToastKey = null; +export const dispatchNetworkErrorMessageToUser = (message) => { + if (!networkErrorToastKey) { + networkErrorToastKey = postNetworkErrorToast(message); + } else { + postNetworkErrorToast(message, networkErrorToastKey); + } +}; /* Catch unexpected errors and make sure we don't lose them! diff --git a/client/src/util/camera.js b/client/src/util/camera.js index d745a96f..659ea121 100644 --- a/client/src/util/camera.js +++ b/client/src/util/camera.js @@ -151,11 +151,10 @@ class Camera { let viewChanged = false; switch (e.type) { case "mousemove": { - /* eslint-disable no-bitwise */ + /* eslint-disable-next-line no-bitwise --- MouseEvent.buttons exposes a bitmask, best acted on with bitops */ if (e.buttons & 0x1) { viewChanged = this.mousePan(e, projectionTF); } - /* eslint-enable no-bitwise */ this.flush(e); break; } diff --git a/client/src/util/centroid.js b/client/src/util/centroid.js index 3b718968..ea515eb4 100644 --- a/client/src/util/centroid.js +++ b/client/src/util/centroid.js @@ -1,6 +1,10 @@ import quantile from "./quantile"; import { memoize } from "./dataframe/util"; import { unassignedCategoryLabel } from "../globals"; +import { + createCategorySummaryFromDfCol, + isSelectableCategoryName, +} from "./stateManager/controlsHelpers"; /* Centroid coordinate calculation @@ -19,30 +23,33 @@ label -> { } */ const getCoordinatesByLabel = ( - obsAnnotations, - obsLayout, + schema, categoryName, - layoutDimNames, - categoricalSelection, - schemaObsByName + categoryDf, + layoutChoice, + layoutDf ) => { const coordsByCategoryLabel = new Map(); - - const categoryArray = obsAnnotations.col(categoryName).asArray(); - - const layoutXArray = obsLayout.col(layoutDimNames[0]).asArray(); - const layoutYArray = obsLayout.col(layoutDimNames[1]).asArray(); - - const { categoryValueIndices, categoryValueCounts } = - categoricalSelection?.[categoryName] || {}; - // If the coloredBy is not a categorical col - if (categoryValueIndices === undefined) { + if (!isSelectableCategoryName(schema, categoryName)) { return coordsByCategoryLabel; } - // Check to see if the current category is a user created annotation - const isUserAnno = schemaObsByName[categoryName].writable; + const categoryArray = categoryDf.col(categoryName).asArray(); + const layoutDimNames = layoutChoice.currentDimNames; + const layoutXArray = layoutDf.col(layoutDimNames[0]).asArray(); + const layoutYArray = layoutDf.col(layoutDimNames[1]).asArray(); + + const categorySummary = createCategorySummaryFromDfCol( + categoryDf.col(categoryName), + schema.annotations.obsByName[categoryName] + ); + + const { + isUserAnno, + categoryValueIndices, + categoryValueCounts, + } = categorySummary; // Iterate over all cells for (let i = 0, len = categoryArray.length; i < len; i += 1) { @@ -97,21 +104,19 @@ const getCoordinatesByLabel = ( */ const calcMedianCentroid = ( - obsAnnotations, - obsLayout, + schema, categoryName, - layoutDimNames, - categoricalSelection, - schemaObsByName + categoryDf, + layoutChoice, + layoutDf ) => { // generate a map describing the coordinates for each label within the given category const dataMap = getCoordinatesByLabel( - obsAnnotations, - obsLayout, + schema, categoryName, - layoutDimNames, - categoricalSelection, - schemaObsByName + categoryDf, + layoutChoice, + layoutDf ); // label => [medianXCoordinate, medianYCoordinate] @@ -138,18 +143,17 @@ const calcMedianCentroid = ( // A simple function to hash the parameters const hashMedianCentroid = ( - obsAnnotations, - obsLayout, + schema, categoryName, - layoutDimNames, - categorySelection, - schemaObsByName + categoryDf, + layoutChoice, + layoutDf ) => { - return `${obsAnnotations.__id}+${ - obsLayout.__id - }:${categoryName}:${layoutDimNames}:${Object.keys( - categorySelection - )}:${Object.keys(schemaObsByName)}`; + const category = categoryDf.col(categoryName); + const layoutDimNames = layoutChoice.currentDimNames; + const layoutX = layoutDf.col(layoutDimNames[0]); + const layoutY = layoutDf.col(layoutDimNames[1]); + return `${category.__id}+${layoutX.__id}:${layoutY.__id}`; }; // export the memoized calculation function export default memoize(calcMedianCentroid, hashMedianCentroid); diff --git a/client/src/util/dataframe/cache.js b/client/src/util/dataframe/cache.js new file mode 100644 index 00000000..bfffeef3 --- /dev/null +++ b/client/src/util/dataframe/cache.js @@ -0,0 +1,26 @@ +/* + +DataframeCache - to improve memoization, we often want to have the same +Dataframe object returned when the contents of the dataframe are identical. +The core dataframe class does not support this, but it does attempt to maintain +strict eq and immutability of columns indices. + +This is a function that will maintain a least-recently created cache of Dataframe +objects. +*/ + +import { memoize } from "./util"; + +function hashDataframe(df) { + if (df.isEmpty()) return ""; + return df.__columnsAccessor.map((c) => c.__id).join(","); +} + +function noop(df) { + return df; +} + +const dataframeMemo = (capacity = 100) => + memoize(noop, hashDataframe, capacity); + +export default dataframeMemo; diff --git a/client/src/util/dataframe/dataframe.js b/client/src/util/dataframe/dataframe.js index 85a01e6d..98693bd0 100644 --- a/client/src/util/dataframe/dataframe.js +++ b/client/src/util/dataframe/dataframe.js @@ -5,6 +5,7 @@ import { isArrayOrTypedArray, callOnceLazy, memoize, + __getMemoId, } from "./util"; import { summarizeContinuous, @@ -72,17 +73,6 @@ Dataframe **/ class Dataframe { - /** - memoization helpers. - **/ - static __DataframeId__ = 0; - - static __getId() { - const id = Dataframe.__DataframeId__; - Dataframe.__DataframeId__ += 1; - return id; - } - /** Constructors & factories **/ @@ -126,7 +116,7 @@ class Dataframe { this.length = nRows; // convenience accessor for row dimension this.rowIndex = rowIndex; this.colIndex = colIndex; - this.__id = Dataframe.__getId(); + this.__id = __getMemoId(); this.__compile(__columnsAccessor); Object.freeze(this); @@ -202,7 +192,7 @@ class Dataframe { */ const { length } = column; - const __id = Dataframe.__getId(); + const __id = __getMemoId(); /* get value by row label */ const get = function get(rlabel) { @@ -290,10 +280,8 @@ class Dataframe { Use an existing accessor if provided, else compile a new one. */ - const { - getOffset: getRowByOffset, - getLabel: getRowByLabel, - } = this.rowIndex; + const getRowByOffset = this.rowIndex.getOffset.bind(this.rowIndex); + const getRowByLabel = this.rowIndex.getLabel.bind(this.rowIndex); this.__columnsAccessor = this.__columns.map((column, idx) => { if (accessors[idx]) { return accessors[idx]; @@ -559,12 +547,14 @@ class Dataframe { const dims = [...this.dims]; /* subset columns */ - let { __columns, colIndex } = this; + let { __columns, colIndex, __columnsAccessor } = this; if (newColIndex) { const colOffsets = this.colIndex.getOffsets(newColIndex.labels()); __columns = new Array(colOffsets.length); + __columnsAccessor = new Array(colOffsets.length); for (let i = 0, l = colOffsets.length; i < l; i += 1) { __columns[i] = this.__columns[colOffsets[i]]; + __columnsAccessor[i] = this.__columnsAccessor[colOffsets[i]]; } colIndex = newColIndex; dims[1] = colOffsets.length; @@ -582,10 +572,17 @@ class Dataframe { }); rowIndex = newRowIndex; dims[0] = rowOffsets.length; + __columnsAccessor = []; // force a recompile } if (dims[0] === 0 || dims[1] === 0) return Dataframe.empty(); - return new Dataframe(dims, __columns, rowIndex, colIndex); + return new Dataframe( + dims, + __columns, + rowIndex, + colIndex, + __columnsAccessor + ); } subset(rowLabels, colLabels = null, withRowIndex = null) { @@ -704,7 +701,9 @@ class Dataframe { /* Return column accessor by offset. */ - return this.__columnsAccessor[columnOffset]; + return Number.isInteger(columnOffset) + ? this.__columnsAccessor[columnOffset] + : undefined; } at(r, c) { @@ -753,7 +752,14 @@ class Dataframe { dataframe - returns true/false */ const [nRows, nCols] = this.dims; - return c >= 0 && c < nCols && r >= 0 && r < nRows; + return ( + Number.isInteger(r) && + Number.isInteger(c) && + c >= 0 && + c < nCols && + r >= 0 && + r < nRows + ); } hasCol(c) { @@ -785,7 +791,9 @@ class Dataframe { callback MUST not modify the column, but instead return a mutated copy. */ - const columns = this.__columns.map(callback); + const columns = this.__columns.map((colData, colIdx) => + callback(colData, colIdx, this) + ); const columnsAccessor = columns.map((c, idx) => this.__columns[idx] === c ? this.__columnsAccessor[idx] : undefined ); diff --git a/client/src/util/dataframe/index.js b/client/src/util/dataframe/index.js index 15d95c1b..0432a6e5 100644 --- a/client/src/util/dataframe/index.js +++ b/client/src/util/dataframe/index.js @@ -5,3 +5,4 @@ export { KeyIndex, isLabelIndex, } from "./labelIndex"; +export { default as dataframeMemo } from "./cache"; diff --git a/client/src/util/dataframe/labelIndex.js b/client/src/util/dataframe/labelIndex.js index c3eae9ed..fe8a48fe 100644 --- a/client/src/util/dataframe/labelIndex.js +++ b/client/src/util/dataframe/labelIndex.js @@ -1,17 +1,18 @@ -/* eslint-disable max-classes-per-file */ +/* eslint-disable max-classes-per-file -- Classes are interrelated*/ /** Label indexing - map a label to & from an integer offset. See Dataframe for how this is used. **/ import { rangeFill as fillRange } from "../range"; +import { __getMemoId } from "./util"; /* Private utility functions */ function extent(tarr) { let min = 0x7fffffff; - let max = ~min; // eslint-disable-line no-bitwise + let max = ~min; // eslint-disable-line no-bitwise -- Establishes 0 of same size for (let i = 0, l = tarr.length; i < l; i += 1) { const v = tarr[i]; if (v < min) { @@ -32,6 +33,10 @@ class IdentityInt32Index { this.maxOffset = maxOffset; } + get __id() { + return `IdentityInt32Index_${this.maxOffset}`; + } + labels() { // memoize const k = fillRange(new Int32Array(this.maxOffset)); @@ -41,28 +46,24 @@ class IdentityInt32Index { return k; } - // eslint-disable-next-line class-methods-use-this getOffset(i) { // label to offset - return i; + return Number.isInteger(i) && i >= 0 && i < this.maxOffset ? i : undefined; } - // eslint-disable-next-line class-methods-use-this getOffsets(arr) { // labels to offsets - return arr; + return arr.map((i) => this.getOffset(i)); } - // eslint-disable-next-line class-methods-use-this getLabel(i) { // offset to label - return i; + return Number.isInteger(i) && i >= 0 && i < this.maxOffset ? i : undefined; } - // eslint-disable-next-line class-methods-use-this getLabels(arr) { // offsets to labels - return arr; + return arr.map((i) => this.getLabel(i)); } size() { @@ -91,7 +92,7 @@ class IdentityInt32Index { const { maxOffset } = this; for (let i = 0, l = labels.length; i < l; i += 1) { const label = labels[i]; - if (label < 0 || label >= maxOffset) + if (!Number.isInteger(label) || label < 0 || label >= maxOffset) throw new RangeError(`offset or label: ${label}`); } return this.__promote(labels); @@ -102,6 +103,23 @@ class IdentityInt32Index { return this.subset(offsets); } + /* identity index - labels are offsets */ + isubsetMask(mask) { + let count = 0; + if (mask.length !== this.maxOffset) { + throw new RangeError("mask has invalid length for index"); + } + let labels = new Int32Array(mask.length); + for (let i = 0, l = mask.length; i < l; i += 1) { + if (mask[i]) { + labels[count] = i; + count += 1; + } + } + labels = labels.slice(0, count); + return this.subset(labels); + } + withLabel(label) { if (label === this.maxOffset) { return new IdentityInt32Index(label + 1); @@ -149,33 +167,28 @@ class DenseInt32Index { this.minLabel = minLabel; this.rindex = labels; this.index = index; + this.__id = __getMemoId(); this.__compile(); } __compile() { const { minLabel, index, rindex } = this; this.getOffset = function getOffset(l) { - return index[l - minLabel]; + if (!Number.isInteger(l)) return undefined; + const offset = index[l - minLabel]; + return offset === -1 ? undefined : offset; }; this.getOffsets = function getOffsets(arr) { - const res = new arr.constructor(arr.length); - for (let i = 0, len = arr.length; i < len; i += 1) { - res[i] = index[arr[i] - minLabel]; - } - return res; + return arr.map((i) => this.getOffset(i)); }; this.getLabel = function getLabel(i) { - return rindex[i]; + return Number.isInteger(i) ? rindex[i] : undefined; }; this.getLabels = function getLabels(arr) { - const res = new arr.constructor(arr.length); - for (let i = 0, len = arr.length; i < len; i += 1) { - res[i] = rindex[arr[i]]; - } - return res; + return arr.map((i) => this.getLabel(i)); }; } @@ -211,11 +224,9 @@ class DenseInt32Index { if (offset === undefined || offset === -1) throw new RangeError(`unknown label: ${label}`); } - return this.__promote(labels); } - // eslint-disable-next-line class-methods-use-this isubset(offsets) { /* validate subset */ const { rindex } = this; @@ -227,7 +238,22 @@ class DenseInt32Index { throw new RangeError(`out of bounds offset: ${offset}`); labels[i] = rindex[offset]; } + return this.__promote(labels); + } + isubsetMask(mask) { + const { rindex } = this; + if (mask.length !== rindex.length) + throw new RangeError("mask has invalid length for index"); + let count = 0; + let labels = new Int32Array(mask.length); + for (let i = 0, l = mask.length; i < l; i += 1) { + if (mask[i]) { + labels[count] = rindex[i]; + count += 1; + } + } + labels = labels.slice(0, count); return this.__promote(labels); } @@ -268,6 +294,7 @@ class KeyIndex { this.index = index; this.rindex = rindex; + this.__id = __getMemoId(); this.__compile(); } @@ -278,23 +305,15 @@ class KeyIndex { }; this.getOffsets = function getOffsets(arr) { - const res = new arr.constructor(arr.length); - for (let i = 0, len = arr.length; i < len; i += 1) { - res[i] = index.get(arr[i]); - } - return res; + return arr.map((l) => this.getOffset(l)); }; this.getLabel = function getLabel(i) { - return rindex[i]; + return Number.isInteger(i) ? rindex[i] : undefined; }; this.getLabels = function getLabels(arr) { - const res = new arr.constructor(arr.length); - for (let i = 0, len = arr.length; i < len; i += 1) { - res[i] = rindex[arr[i]]; - } - return res; + return arr.map((i) => this.getLabel(i)); }; } @@ -311,13 +330,14 @@ class KeyIndex { for (let i = 0, l = labels.length; i < l; i += 1) { const label = labels[i]; const offset = this.getOffset(label); - if (offset === undefined) throw new RangeError(`unknown label: ${label}`); + if (offset === undefined || offset === -1) { + throw new RangeError(`unknown label: ${label}`); + } } return new KeyIndex(labels); } - // eslint-disable-next-line class-methods-use-this isubset(offsets) { const { rindex } = this; const maxOffset = rindex.length; @@ -332,6 +352,22 @@ class KeyIndex { return new KeyIndex(labels); } + isubsetMask(mask) { + const { rindex } = this; + if (mask.length !== rindex.length) + throw new RangeError("mask has invalid length for index"); + let labels = new Array(mask.length); + let count = 0; + for (let i = 0, l = mask.length; i < l; i += 1) { + if (mask[i]) { + labels[count] = rindex[i]; + count += 1; + } + } + labels = labels.slice(0, count); + return new KeyIndex(labels); + } + withLabel(label) { return new KeyIndex([...this.rindex, label]); } @@ -347,7 +383,6 @@ class KeyIndex { return new KeyIndex(labelArray); } } -/* eslint-enable class-methods-use-this */ function isLabelIndex(i) { return ( @@ -358,3 +393,4 @@ function isLabelIndex(i) { } export { DenseInt32Index, IdentityInt32Index, KeyIndex, isLabelIndex }; +/* eslint-enable max-classes-per-file -- enable*/ diff --git a/client/src/util/dataframe/util.js b/client/src/util/dataframe/util.js index 09be54af..aa00e0c8 100644 --- a/client/src/util/dataframe/util.js +++ b/client/src/util/dataframe/util.js @@ -52,3 +52,13 @@ export function memoize(fn, hashFn, maxResultsCached = -1) { return wrap; } + +/** +memoization helpers - just a global counter. +**/ +let __DataframeMemoId__ = 0; +export function __getMemoId() { + const id = __DataframeMemoId__; + __DataframeMemoId__ += 1; + return id; +} diff --git a/client/src/util/promiseLimit.js b/client/src/util/promiseLimit.js index 1b1731b3..dde85bd8 100644 --- a/client/src/util/promiseLimit.js +++ b/client/src/util/promiseLimit.js @@ -12,19 +12,58 @@ return Promise.all([ plimit.add(() => fetch('/bar')), plimit.add(() => fetch('/baz')) ]) + +if you want a priority queue based implementation, just +use priorityAdd() instead of add(): + +const plimit = new PromiseLimit(2); +return Promise.all([ + plimit.priorityAdd(0, () => fetch('/foo')), + plimit.priorityAdd(10, () => fetch('/bar')), + plimit.priorityAdd(-1, () => fetch('/baz')) +]) + +Priority is a numeric value. Lower first. Stable ordering. */ + +import TinyQueue from "tinyqueue"; + +function compare(a, b) { + const diff = a.priority - b.priority; + if (diff) return diff; + return a.order - b.order; +} + export default class PromiseLimit { - constructor(maxConcurrency) { - this.queue = new Set(); + constructor(maxConcurrency = 5) { + this.queue = new TinyQueue([], compare); this.maxConcurrency = maxConcurrency; this.pending = 0; + this.insertCounter = 0; + } + + priorityAdd(p, fn, ...args) { + // p - numermic priority (lower first) + // fn - must return a promise + // args - will be passed to fn + return this._push(p, fn, args); } add(fn, ...args) { // fn - must return a promise // args - will be passed to fn + return this._push(0, fn, args); + } + + /** + Private below + **/ + + _push(priority, fn, args) { + const order = this.insertCount; + this.insertCount += 1; return new Promise((resolve, reject) => { - this.queue.add({ fn, args, resolve, reject }); + this.queue.push({ priority, order, fn, args, resolve, reject }); this._resolveNext(false); }); } @@ -32,15 +71,17 @@ export default class PromiseLimit { _resolveNext = (completed = true) => { if (completed) this.pending -= 1; - while (this.queue.size > 0 && this.pending < this.maxConcurrency) { - const task = this.queue.values().next().value; // order of insertion + while (this.queue.length > 0 && this.pending < this.maxConcurrency) { + const task = this.queue.pop(); // order of insertion this.pending += 1; - this.queue.delete(task); const { resolve, reject, fn, args } = task; try { const result = fn(...args); - result.then(this._resolveNext, this._resolveNext); + result.then( + () => this._resolveNext(true), + () => this._resolveNext(true) + ); result.then(resolve, reject); } catch (err) { reject(err); diff --git a/client/src/util/renderThrottle.js b/client/src/util/renderThrottle.js new file mode 100644 index 00000000..36c813c1 --- /dev/null +++ b/client/src/util/renderThrottle.js @@ -0,0 +1,17 @@ +export default function renderThrottle(callback) { + /* + This wraps a call to requestAnimationFrame(), enforcing a single + render callback at any given time (ie, you can call this any number + of times, and it will coallesce multiple inter-frame calls into a + single render). + */ + let rafCurrentlyInProgress = null; + return function f() { + if (rafCurrentlyInProgress) return; + const context = this; + rafCurrentlyInProgress = window.requestAnimationFrame(() => { + callback.apply(context); + rafCurrentlyInProgress = null; + }); + }; +} diff --git a/client/src/util/stateManager/annotationsHelpers.js b/client/src/util/stateManager/annotationsHelpers.js index 525e5b3b..999ee961 100644 --- a/client/src/util/stateManager/annotationsHelpers.js +++ b/client/src/util/stateManager/annotationsHelpers.js @@ -2,16 +2,13 @@ Helper functions for user-editable annotations state management. See also reducers/annotations.js */ -import { unassignedCategoryLabel } from "../../globals"; -import * as SchemaHelpers from "./schemaHelpers"; -import { obsAnnoDimensionName } from "../nameCreators"; /* There are a number of state constraints assumed throughout the application: - - all obs annotations are in {world|universe}.obsAnnotations, + - all obs annotations are in annoMatrix, regardless of whether or not they are user editable. - - the {world|universe}.schema is always up to date and matches + - the annoMatrix.schema is always up to date and matches the data - the schema flag `writable` correctly indicates whether the annotation is editable/mutable. @@ -21,115 +18,32 @@ categorical annotations to be writable. */ export function isCategoricalAnnotation(schema, name) { - /* we treat any string, categorical or boolean as a categorical */ - const { type } = schema.annotations.obsByName[name]; + /* + we treat any string, categorical or boolean as a categorical. + Return true/false/undefined (for unkonwn fields) + */ + const colSchema = schema.annotations.obsByName[name]; + if (colSchema === undefined) return undefined; + const { type } = colSchema; return type === "string" || type === "boolean" || type === "categorical"; } export function isContinuousAnnotation(schema, name) { - return !isCategoricalAnnotation(schema, name); + /* + Return true/false/undefined + */ + const colSchema = schema.annotations.obsByName[name]; + if (colSchema === undefined) return undefined; + const { type } = colSchema; + return !(type === "string" || type === "boolean" || type === "categorical"); } function _isUserAnnotation(schema, name) { - return schema.annotations.obsByName[name]?.writable; + return schema.annotations.obsByName[name]?.writable || false; } -export function isUserAnnotation(worldOrUniverse, name) { - return _isUserAnnotation(worldOrUniverse.schema, name); -} - -export function removeObsAnnoSchema(schema, name) { - /* - remove named annotation from obs annotation schema - */ - - /* only remove if it exists and is a user annotation */ - if (!_isUserAnnotation(schema, name)) - throw new Error("removing non-user-defined schema"); - return SchemaHelpers.removeObsAnnoColumn(schema, name); -} - -export function addObsAnnoSchema(schema, name, colSchema) { - /* - add a categorical type to the obs annotation schema - */ - - /* collision detection */ - if (schema.annotations.obs.columns.some((v) => v.name === name)) - throw Error("annotations may not contain duplicate category names"); - if (name !== colSchema.name) throw Error("column schema does not match"); - return SchemaHelpers.addObsAnnoColumn(schema, name, colSchema); -} - -export function dupObsAnnoSchema(schema, sourceName, dupName, defaultSchema) { - /* - duplicate the obs annotation `sourceName` schema, but with the name `dupName` - */ - const colSchema = { - ...schema.annotations.obsByName[sourceName], - ...defaultSchema, - name: dupName, - }; - /* existance check */ - if (!colSchema) throw Error("source annotation does not exist"); - /* collision detection */ - if (schema.annotations.obs.columns.some((v) => v.name === dupName)) - throw Error("annotations may not contain duplicate category names"); - return SchemaHelpers.addObsAnnoColumn(schema, dupName, colSchema); -} - -export function removeObsAnnoCategory(schema, name, category) { - /* don't allow deletion of unassigned category on writable annotations */ - - if (!_isUserAnnotation(schema, name)) - throw new Error("unable to modify read-only schema"); - if (category === unassignedCategoryLabel) - throw new Error("may not remove unassigned category label"); - - return SchemaHelpers.removeObsAnnoCategory(schema, name, category); -} - -export function addObsAnnoCategory(schema, name, category) { - if (!_isUserAnnotation(schema, name)) - throw new Error("unable to modify read-only schema"); - - return SchemaHelpers.addObsAnnoCategory(schema, name, category); -} - -export function setLabelByValue(df, colName, fromLabel, toLabel) { - /* - in the dataframe column `colName`, set any value of `fromLabel` to `toLabel` - */ - const keys = df.colIndex.labels(); - const ndf = df.mapColumns((col, colIdx) => { - if (colName !== keys[colIdx]) return col; - - /* clone data and return it. */ - const newCol = col.slice(); - for (let i = 0, l = newCol.length; i < l; i += 1) { - if (newCol[i] === fromLabel) newCol[i] = toLabel; - } - return newCol; - }); - return ndf; -} - -export function setLabelByMask(df, colName, mask, label) { - /* - in the dataframe column `colName`, set the masked rows to 'label' - */ - const keys = df.colIndex.labels(); - const ndf = df.mapColumns((col, colIdx) => { - if (colName !== keys[colIdx]) return col; - - /* clone data and return it. */ - const newCol = col.slice(); - for (let i = 0, l = newCol.length; i < l; i += 1) { - if (mask[i]) newCol[i] = label; - } - return newCol; - }); - return ndf; +export function isUserAnnotation(annoMatrix, name) { + return _isUserAnnotation(annoMatrix.schema, name); } export function allHaveLabelByMask(df, colName, label, mask) { @@ -148,41 +62,6 @@ export function allHaveLabelByMask(df, colName, label, mask) { return true; } -export function worldToUniverseMask(worldMask, worldObsAnnotations, nObs) { - /* - given world seleciton mask, return a selection mask for entire universe - that has same selection state. - */ - const mask = new Uint8Array(nObs); - const { rowIndex } = worldObsAnnotations; - - for (let i = 0, l = worldMask.length; i < l; i += 1) { - if (worldMask[i]) { - const label = rowIndex.getLabel(i); - mask[label] = 1; - } - } - return mask; -} - -export function createWritableAnnotationDimensions(world, crossfilter) { - const { obsAnnotations, schema } = world; - const writableAnnotations = schema.annotations.obs.columns - .filter((s) => s.writable) - .map((s) => s.name); - - crossfilter = writableAnnotations.reduce((xflt, anno) => { - const dimName = obsAnnoDimensionName(anno); - if (xflt.hasDimension(dimName)) xflt = xflt.delDimension(dimName); - return xflt.addDimension( - dimName, - "enum", - obsAnnotations.col(anno).asArray() - ); - }, crossfilter); - return crossfilter; -} - const legalCharacters = /^(\w|[ .()-])+$/; export function annotationNameIsErroneous(name) { /* diff --git a/client/src/util/stateManager/colorHelpers.js b/client/src/util/stateManager/colorHelpers.js index 0150f105..be5fe113 100644 --- a/client/src/util/stateManager/colorHelpers.js +++ b/client/src/util/stateManager/colorHelpers.js @@ -3,46 +3,93 @@ Helper functions for the embedded graph colors */ import * as d3 from "d3"; import { interpolateRainbow, interpolateCool } from "d3-scale-chromatic"; +import memoize from "memoize-one"; import * as globals from "../../globals"; import parseRGB from "../parseRGB"; -import finiteExtent from "../finiteExtent"; import { range } from "../range"; /* -create new colors state object. Paramters: - - world - current world object - - colorMode - color-by mode. One of {null, "color by expression", "color by continuous metadata", - "color by categorical metadata"} - - colorAccessor - the obs annotations used for color-by +given a color mode & accessor, generate an annoMatrix query that will +fulfill it */ -export function createColors( - world, - colorMode = null, - colorAccessor = null, +export function createColorQuery(colorMode, colorByAccessor, schema) { + if (!colorMode || !colorByAccessor || !schema) return null; + switch (colorMode) { + case "color by categorical metadata": + case "color by continuous metadata": { + return ["obs", colorByAccessor]; + } + case "color by expression": { + const varIndex = schema?.annotations?.var?.index; + if (!varIndex) return null; + return [ + "X", + { + field: "var", + column: varIndex, + value: colorByAccessor, + }, + ]; + } + default: { + return null; + } + } +} + +function _defaultColors(nObs) { + const defaultCellColor = parseRGB(globals.defaultCellColor); + return { + rgb: new Array(nObs).fill(defaultCellColor), + scale: undefined, + }; +} +const defaultColors = memoize(_defaultColors); + +/* +create colors scale and RGB array and return as object. Parameters: + * colorMode - categorical, etc. + * colorByAccessor - the annotation label name + * colorByDataframe - the actual color-by data + * schema - the entire schema + * userColors - optional user color table +Returns: + { + scale: color scale + rgb: cell to color mapping + } +*/ +function _createColorTable( + colorMode, + colorByAccessor, + colorByData, + schema, userColors = null ) { switch (colorMode) { case "color by categorical metadata": { - if (userColors && colorAccessor in userColors) { - return createUserColors(world, colorAccessor, userColors); + const data = colorByData.col(colorByAccessor).asArray(); + if (userColors && colorByAccessor in userColors) { + return createUserColors(data, colorByAccessor, userColors); } - return createColorsByCategoricalMetadata(world, colorAccessor); + return createColorsByCategoricalMetadata(data, colorByAccessor, schema); } case "color by continuous metadata": { - return createColorsByContinuousMetadata(world, colorAccessor); + const col = colorByData.col(colorByAccessor); + const { min, max } = col.summarize(); + return createColorsByContinuousMetadata(col.asArray(), min, max); } case "color by expression": { - return createColorsByExpression(world, colorAccessor); + const col = colorByData.icol(0); + const { min, max } = col.summarize(); + return createColorsByContinuousMetadata(col.asArray(), min, max); } default: { - const defaultCellColor = parseRGB(globals.defaultCellColor); - return { - rgb: new Array(world.nObs).fill(defaultCellColor), - scale: undefined, - }; + return defaultColors(schema.dataframe.nObs); } } } +export const createColorTable = memoize(_createColorTable); export function loadUserColorConfig(userColors) { const convertedUserColors = {}; @@ -62,14 +109,15 @@ export function loadUserColorConfig(userColors) { return convertedUserColors; } -function createUserColors(world, colorAccessor, userColors) { +function _createUserColors(data, colorAccessor, userColors) { const { colors, scale } = userColors[colorAccessor]; - const rgb = createRgbArray(world, colors, colorAccessor); + const rgb = createRgbArray(data, colors); return { rgb, scale }; } +const createUserColors = memoize(_createUserColors); -function createColorsByCategoricalMetadata(world, colorAccessor) { - const { categories } = world.schema.annotations.obsByName[colorAccessor]; +function _createColorsByCategoricalMetadata(data, colorAccessor, schema) { + const { categories } = schema.annotations.obsByName[colorAccessor]; const scale = d3 .scaleSequential(interpolateRainbow) @@ -81,25 +129,24 @@ function createColorsByCategoricalMetadata(world, colorAccessor) { return acc; }, {}); - const rgb = createRgbArray(world, colors, colorAccessor); + const rgb = createRgbArray(data, colors); return { rgb, scale }; } +const createColorsByCategoricalMetadata = memoize( + _createColorsByCategoricalMetadata +); -export function createRgbArray(world, colors, colorAccessor) { - const rgb = new Array(world.nObs); - const df = world.obsAnnotations; - const data = df.col(colorAccessor).asArray(); - for (let i = 0, len = df.length; i < len; i += 1) { +function createRgbArray(data, colors) { + const rgb = new Array(data.length); + for (let i = 0, len = data.length; i < len; i += 1) { const label = data[i]; rgb[i] = colors[label]; } return rgb; } -function createColorsByContinuousMetadata(world, accessor) { +function _createColorsByContinuousMetadata(data, min, max) { const colorBins = 100; - const col = world.obsAnnotations.col(accessor); - const { min, max } = col.summarize(); const scale = d3 .scaleQuantile() .domain([min, max]) @@ -112,9 +159,8 @@ function createColorsByContinuousMetadata(world, accessor) { } const nonFiniteColor = parseRGB(globals.nonFiniteCellColor); - const rgb = new Array(world.nObs); - const data = col.asArray(); - for (let i = 0, len = world.obsAnnotations.length; i < len; i += 1) { + const rgb = new Array(data.length); + for (let i = 0, len = data.length; i < len; i += 1) { const val = data[i]; if (Number.isFinite(val)) { const c = scale(val); @@ -125,56 +171,6 @@ function createColorsByContinuousMetadata(world, accessor) { } return { rgb, scale }; } - -function createColorsByExpression(world, accessor) { - const expression = world.varData.col(accessor).asArray(); - const colorBins = 100; - const [min, max] = finiteExtent(expression); - const scale = d3 - .scaleQuantile() - .domain([min, max]) - .range(range(colorBins - 1, -1, -1)); - - /* pre-create colors - much faster than doing it for each obs */ - const colors = new Array(colorBins); - for (let i = 0; i < colorBins; i += 1) { - colors[i] = parseRGB(interpolateCool(i / colorBins)); - } - const nonFiniteColor = parseRGB(globals.nonFiniteCellColor); - - const rgb = new Array(world.nObs); - for (let i = 0, len = expression.length; i < len; i += 1) { - const e = expression[i]; - if (Number.isFinite(e)) { - const c = scale(e); - rgb[i] = colors[c]; - } else { - rgb[i] = nonFiniteColor; - } - } - return { rgb, scale }; -} - -export const resetColors = (world) => { - const { rgb, scale } = createColors(world); - return { - colorMode: null, - colorAccessor: null, - rgb, - scale, - }; -}; - -export const checkIfColorByDiffexpAndResetColors = ( - prevControls, - state, - prevWorld -) => { - if (prevControls.diffexpGenes.includes(state.colorAccessor)) { - return { - ...state, - ...resetColors(prevWorld), - }; - } - return null; -}; +export const createColorsByContinuousMetadata = memoize( + _createColorsByContinuousMetadata +); diff --git a/client/src/util/stateManager/controlsHelpers.js b/client/src/util/stateManager/controlsHelpers.js index 3206b63f..a0091138 100644 --- a/client/src/util/stateManager/controlsHelpers.js +++ b/client/src/util/stateManager/controlsHelpers.js @@ -6,17 +6,8 @@ import _ from "lodash"; import * as globals from "../../globals"; import { rangeFill as fillRange } from "../range"; -import { - userDefinedDimensionName, - diffexpDimensionName, -} from "../nameCreators"; - -export function maxCategoryItems(config) { - return ( - config.parameters?.["max-category-items"] ?? - globals.configDefaults.parameters["max-category-items"] - ); -} +import fromEntries from "../fromEntries"; +import { isCategoricalAnnotation } from "./annotationsHelpers"; /* Selection state for categoricals are tracked in an Object that @@ -45,31 +36,38 @@ Remember that option values can be ANY js type, except undefined/null. } */ function topNCategories(colSchema, summary, N) { - /* return top N by occurrences in the data, preserving original category order */ - const { categories } = colSchema; - const counts = categories.map((cat) => summary.categoryCounts.get(cat) ?? 0); + /* return top N categories by occurrences in the data */ + const { categories: allCategories } = colSchema; + const counts = allCategories.map( + (cat) => summary.categoryCounts.get(cat) ?? 0 + ); - if (categories.length <= N) { - return [categories, counts]; + if (allCategories.length <= N) { + return [allCategories, allCategories, counts]; } - const sortIndex = fillRange(new Array(categories.length)).sort( + const sortIndex = fillRange(new Array(allCategories.length)).sort( (a, b) => counts[b] - counts[a] ); const topNindices = new Set(sortIndex.slice(0, N)); const _topNCategories = []; const topNCounts = []; - for (let i = 0; i < categories.length; i += 1) { + for (let i = 0; i < allCategories.length; i += 1) { if (topNindices.has(i)) { - _topNCategories.push(categories[i]); + _topNCategories.push(allCategories[i]); topNCounts.push(counts[i]); } } - return [_topNCategories, topNCounts]; + return [allCategories, _topNCategories, topNCounts]; } -export function selectableCategoryNames(schema, maxCatItems, names) { +export function isSelectableCategoryName(schema, name) { + const { index } = schema.annotations.obs; + return name && name !== index && isCategoricalAnnotation(schema, name); +} + +export function selectableCategoryNames(schema, names) { /* return all obs annotation names that are categorical AND have a "reasonably" small number of categories AND are not the index column. @@ -77,88 +75,42 @@ export function selectableCategoryNames(schema, maxCatItems, names) { If the initial name list not provided, use everything in the schema. */ if (!schema) return []; - const { index, columns } = schema.annotations.obs; - - return columns - .filter((colSchema) => !names || names.indexOf(colSchema.name) !== -1) - .filter((colSchema) => { - const { type, name } = colSchema; - const isSelectableType = - type === "string" || type === "boolean" || type === "categorical"; - return isSelectableType && name !== index; - }) - .map((v) => v.name); + if (!names) names = schema.annotations.obs.columns.map((c) => c.name); + return names.filter((name) => isSelectableCategoryName(schema, name)); } -export function createCategoricalSelection(world, names) { +export function createCategorySummaryFromDfCol(dfCol, colSchema) { const N = globals.maxCategoricalOptionsToDisplay; - const { obsAnnotations, schema } = world; + const { writable: isUserAnno } = colSchema; - const res = names.reduce((acc, name) => { - const colSchema = schema.annotations.obsByName[name]; - const { writable: isUserAnno } = colSchema; + /* + Summarize the annotation data currently in dataframe column. Must return + categoryValues in sorted order, and must include all category values even + if they are not actively used in the current annoMatrix view. + */ + const summary = dfCol.summarizeCategorical(); + const [ + allCategoryValues, + categoryValues, + categoryValueCounts, + ] = topNCategories(colSchema, summary, N); + const categoryValueIndices = new Map(categoryValues.map((v, i) => [v, i])); + const numCategoryValues = categoryValueIndices.size; + const isTruncated = categoryValues.length < summary.numCategories; - /* - Summarize the annotation data currently in world. Must return categoryValues - in sorted order, and must include all category values even if they are not - actively used in the current world. - */ - const summary = obsAnnotations.col(name).summarizeCategorical(); - const [categoryValues, categoryValueCounts] = topNCategories( - colSchema, - summary, - N - ); - const categoryValueIndices = new Map(categoryValues.map((v, i) => [v, i])); - const numCategoryValues = categoryValueIndices.size; - const categoryValueSelected = new Array(numCategoryValues).fill(true); - const isTruncated = categoryValues.length < summary.numCategories; - - acc[name] = { - categoryValues, // array: of natively typed category values - categoryValueIndices, // map: category value (native type) -> category index - categoryValueSelected, // array: t/f selection state - numCategoryValues, // number: of values in the category - isTruncated, // bool: true if list was truncated - categoryValueCounts, // array: cardinality of each category, - categorySelected: true, // bool - default state for entire category - isUserAnno, // bool - }; - return acc; - }, {}); - return res; + return { + allCategoryValues, // array: of natively typed category values (all of them) + categoryValues, // array: of natively typed category values (top N only) + categoryValueIndices, // map: category value (native type) -> category index (top N only) + numCategoryValues, // number: of values in the category (top N) + isTruncated, // bool: true if list was truncated (ie, if topN != all) + categoryValueCounts, // array: cardinality of each category, (top N) + isUserAnno, // bool + }; } -/* -build a crossfilter dimensions for all gene expression related dimensions. -*/ -export function createGeneDimensions( - userDefinedGenes, - diffexpGenes, - world, - crossfilter -) { - crossfilter = userDefinedGenes.reduce( - (xflt, gene) => - xflt.addDimension( - userDefinedDimensionName(gene), - "scalar", - world.varData.col(gene).asArray(), - Float32Array - ), - crossfilter - ); - crossfilter = diffexpGenes.reduce( - (xflt, gene) => - xflt.addDimension( - diffexpDimensionName(gene), - "scalar", - world.varData.col(gene).asArray(), - Float32Array - ), - crossfilter - ); - return crossfilter; +export function createCategoricalSelection(names) { + return fromEntries(names.map((name) => [name, new Map()])); } export function pruneVarDataCache(varData, needed) { diff --git a/client/src/util/stateManager/index.js b/client/src/util/stateManager/index.js index c73f9518..b20d2f8b 100644 --- a/client/src/util/stateManager/index.js +++ b/client/src/util/stateManager/index.js @@ -1,22 +1,10 @@ -// jshint esversion: 6 - /* Model manager providing an abstraction for the use of the reducer code. -This module provides several buckets of functionality: - - schema and config driven tranformation of the wire protocol - into a format that is easy for the UI code to use. - - manage the universe/world abstraction: - + universe: all of the server-provided, read-only data - + world: subset of universe - - lazy access and caching of dataframe contents as needed - This is all VERY tightly integrated with reducers and actions, and exists to support those concepts. */ export * as ColorHelpers from "./colorHelpers"; -export * as Universe from "./universe"; -export * as World from "./world"; export * as ControlsHelpers from "./controlsHelpers"; export * as AnnotationsHelpers from "./annotationsHelpers"; export * as SchemaHelpers from "./schemaHelpers"; diff --git a/client/src/util/stateManager/schemaHelpers.js b/client/src/util/stateManager/schemaHelpers.js index 3312f9db..c59ac1a9 100644 --- a/client/src/util/stateManager/schemaHelpers.js +++ b/client/src/util/stateManager/schemaHelpers.js @@ -13,22 +13,21 @@ import catLabelSort from "../catLabelSort"; System wide schema assumptions: - schema and data wil be consistent (eg, for user-created annotations) - schema will be internally self-consistent (eg, index matches columns) - - world & universe schema are same - only data is subset */ export function indexEntireSchema(schema) { /* Index schema for ease of use */ schema.annotations.obsByName = fromEntries( - schema.annotations.obs.columns.map((v) => [v.name, v]) + schema.annotations?.obs?.columns?.map((v) => [v.name, v]) ?? [] ); schema.annotations.varByName = fromEntries( - schema.annotations.var.columns.map((v) => [v.name, v]) + schema.annotations?.var?.columns?.map((v) => [v.name, v]) ?? [] ); schema.layout.obsByName = fromEntries( - schema.layout.obs.map((v) => [v.name, v]) + schema.layout?.obs?.map((v) => [v.name, v]) ?? [] ); schema.layout.varByName = fromEntries( - schema.layout.var.map((v) => [v.name, v]) + schema.layout?.var?.map((v) => [v.name, v]) ?? [] ); return schema; diff --git a/client/src/util/stateManager/universe.js b/client/src/util/stateManager/universe.js deleted file mode 100644 index 1b23b85d..00000000 --- a/client/src/util/stateManager/universe.js +++ /dev/null @@ -1,156 +0,0 @@ -import { unassignedCategoryLabel } from "../../globals"; -import { decodeMatrixFBS } from "./matrix"; -import * as Dataframe from "../dataframe"; -import { isFpTypedArray } from "../typeHelpers"; -import { indexEntireSchema } from "./schemaHelpers"; -import catLabelSort from "../catLabelSort"; - -/* -Private helper function - create and return a template Universe -*/ -function templateUniverse() { - /* default universe template */ - return { - nObs: 0, - nVar: 0, - schema: {}, - - /* - annotations - */ - obsAnnotations: Dataframe.Dataframe.empty(), - varAnnotations: Dataframe.Dataframe.empty(), - /* - layout - */ - obsLayout: Dataframe.Dataframe.empty(), - - /* - Var data columns - subset of all - */ - varData: Dataframe.Dataframe.empty(null, new Dataframe.KeyIndex()), - }; -} - -/* -This module implements functions that support storage of "Universe", -aka all of the var/obs data and annotations. - -These functions are used exclusively by the actions and reducers to -build an internal POJO for use by the rendering components. -*/ - -export function createUniverseFromResponse(configResponse, schemaResponse) { - /* - build & return universe from a REST 0.2 /config, /schema and /annotations/obs response - */ - const { schema } = schemaResponse; - const universe = templateUniverse(); - - /* schema related */ - universe.schema = schema; - universe.nObs = schema.dataframe.nObs; - universe.nVar = schema.dataframe.nVar; - - /* add defaults, as we can't assume back-end will fully populate schema */ - if (!schema.layout.var) schema.layout.var = []; - if (!schema.layout.obs) schema.layout.obs = []; - indexEntireSchema(universe.schema); - normalizeEntireSchema(universe.schema); - - return universe; -} - -function normalizeSchemaCategory(colSchema, col = undefined) { - const { type, writable } = colSchema; - if (type === "string" || type === "boolean" || type === "categorical") { - let categories = [ - ...new Set([ - ...(colSchema.categories ?? []), - ...(col?.summarize?.().categories ?? []), - ]), - ]; - if (writable && categories.indexOf(unassignedCategoryLabel) === -1) { - categories = categories.concat(unassignedCategoryLabel); - } - colSchema.categories = categories; - } else if (writable) { - throw new Error( - "Writable continuous obs annotations are not supported - failed to load" - ); - } - - if (colSchema.categories) { - colSchema.categories = catLabelSort(writable, colSchema.categories); - } -} - -function normalizeEntireSchema(schema) { - // currently only needed for obsAnnotations - schema.annotations.obs.columns.forEach((colSchema) => - normalizeSchemaCategory(colSchema) - ); -} - -export function addObsAnnotations(universe, df) { - const obsAnnotations = universe.obsAnnotations.withColsFromAll(df); - if (universe.nObs !== obsAnnotations.length) { - throw new Error("Universe dimensionality mismatch - failed to load"); - } - - // for all of the new data, reconcile with schema and sort categories. - const dfs = Array.isArray(df) ? df : [df]; - const keys = dfs.map((d) => d.colIndex.labels()).flat(); - const { schema } = universe; - keys.forEach((k) => { - const colSchema = schema.annotations.obsByName[k]; - const col = obsAnnotations.col(k); - normalizeSchemaCategory(colSchema, col); - }); - - return { obsAnnotations, schema }; -} - -export function addVarAnnotations(universe, df) { - const varAnnotations = universe.varAnnotations.withColsFromAll(df); - if (universe.nVar !== varAnnotations.length) { - throw new Error("Universe dimensionality mismatch - failed to load"); - } - return { varAnnotations }; -} - -export function addObsLayout(universe, df) { - const obsLayout = universe.obsLayout.withColsFromAll(df); - if (universe.nObs !== obsLayout.length) { - throw new Error("Universe dimensionality mismatch - failed to load"); - } - return { obsLayout }; -} - -export function convertDataFBStoObject(universe, arrayBuffer) { - /* - /data/var returns a flatbuffer (FBS) as described by cellxgene/fbs/matrix.fbs - - This routine converts the binary wire encoding into a JS object: - - { - gene: Float32Array, - ... - } - */ - const fbs = decodeMatrixFBS(arrayBuffer); - const { colIdx, columns } = fbs; - const result = {}; - - if (!columns.every(isFpTypedArray)) { - // We have strong assumptions that all var data is float - throw new Error("Unexpected non-floating point response from server."); - } - - const varIndexName = universe.schema.annotations.var.index; - for (let c = 0; c < colIdx.length; c += 1) { - const varName = universe.varAnnotations.at(colIdx[c], varIndexName); - result[varName] = columns[c]; - } - return result; -} diff --git a/client/src/util/stateManager/world.js b/client/src/util/stateManager/world.js deleted file mode 100644 index da219e0f..00000000 --- a/client/src/util/stateManager/world.js +++ /dev/null @@ -1,335 +0,0 @@ -import clip from "../clip"; -import { layoutDimensionName, obsAnnoDimensionName } from "../nameCreators"; -import * as Dataframe from "../dataframe"; -import { isContinuousAnnotation } from "./annotationsHelpers"; - -/* - -World is a subset of universe. Most code should use world, and should -(generally) not use Universe. World contains any per-obs or per-var data -that must be consistent acorss the app when we view/manipulate subsets -of Universe. - -Private API indicated by leading underscore in key name (eg, _foo). Anything else -is public. - -Notable keys in the world object: - -* nObs, nVar: dimensions - -* schema: data schema from the server - -* clipQuantiles: the quantiles used to clip all data in world. - -* obsAnnotations: - - Dataframe containing obs annotations. Columns are indexed by annotation - name (eg, 'tissue type'), and rows are indexed by the REST API obsIndex - (ie, the offset into the underlying server-side dataframe). - - This indexing means that you can access data by _either_ the server's - obxIndex, or the offset into the client-side column array . Be careful - to know which you want and are using. - -* obsLayout: - - A dataframe containing the X/Y layout for all obs. Columns are named - 'X' and 'Y', and rows are indexed in the same way as obsAnnotation. - -* varData: a cache of expression columns, stored in a Dataframe. Cache - managed by controls reducer. - -* unclipped: will contain unclipped variants of all potentiall clipped - dataframes (obsAnnotations, varData). - -*/ - -function templateWorld() { - const obsAnnotations = Dataframe.Dataframe.empty(); - const varAnnotations = Dataframe.Dataframe.empty(); - const obsLayout = Dataframe.Dataframe.empty(); - const varData = Dataframe.Dataframe.empty(null, new Dataframe.KeyIndex()); - return { - /* schema/version related */ - schema: null, - nObs: 0, - nVar: 0, - clipQuantiles: { min: 0, max: 1 }, - - /* annotations */ - obsAnnotations, - varAnnotations, - - /* layout of graph. Dataframe. */ - obsLayout, - - /* Var data columns - subset of all data (may be empty) */ - varData, - - /* unclipped dataframes - subset, but not value clipped */ - unclipped: { - obsAnnotations, - varData, - }, - }; -} - -function clipDataframe( - df, - lowerQuantile, - upperQuantile, - quantileF, - clipPredicate = () => true, - value = Number.NaN -) { - /* - For all columns in the dataframe, clip all values above or below specified - quantiles to `value` if clipPredicate returns True for that column (if it - returns false, skip the column entirely). - - Returns a clipped copy - does not mutate original. - - clipPredicate must have signature: (dataframe, colIndex, colLabel) => boolean - True signifies that the column should be clipped; false indicates that the - column should be left intact/unchanged. - - quantileF must have signature: (label, qval) => number - */ - if (lowerQuantile < 0) lowerQuantile = 0; - if (upperQuantile > 1) upperQuantile = 1; - if (lowerQuantile === 0 && upperQuantile === 1) return df; - - const keys = df.colIndex.labels(); - return df.mapColumns((col, colIdx) => { - const colLabel = keys[colIdx]; - if (!clipPredicate(df, colIdx, colLabel)) return col; - - const colMin = quantileF(colLabel, lowerQuantile); - const colMax = quantileF(colLabel, upperQuantile); - const newCol = clip(col.slice(), colMin, colMax, value); - return newCol; - }); -} - -/* -Create World with contents eq entire universe. Commonly used to initialize World. -*/ -export function createWorldFromEntireUniverse(universe) { - const world = templateWorld(); - - /* Schema related */ - world.schema = universe.schema; - world.nObs = universe.nObs; - world.nVar = universe.nVar; - world.clipQuantiles = { min: 0, max: 1 }; - - /* dataframes: annotations and layout */ - world.obsAnnotations = universe.obsAnnotations.clone(); - world.varAnnotations = universe.varAnnotations.clone(); - world.obsLayout = universe.obsLayout.clone(); - - /* Var dataframe - contains a subset of all var columns */ - world.varData = universe.varData.clone(); - - /* save unclipped copies of potentially clipped dataframes */ - world.unclipped = { - obsAnnotations: world.obsAnnotations.clone(), - varData: world.varData.clone(), - }; - - return world; -} - -/* -clip dataframes based on quantiles. - -This is an in-place operation on the world object provided as an argument. -The values in world.unclipped are clipped and assigned to world.obsAnnotations -and world.varData. -*/ -function setClippedDataframes(world) { - const { schema } = world; - const isContinuousObsAnnotation = (df, idx, label) => - isContinuousAnnotation(schema, label); - const obsQuantile = (label, q) => - world.unclipped.obsAnnotations.col(label).summarize().percentiles[100 * q]; - world.obsAnnotations = clipDataframe( - world.unclipped.obsAnnotations, - world.clipQuantiles.min, - world.clipQuantiles.max, - obsQuantile, - isContinuousObsAnnotation - ); - - const varDataQuantile = (label, q) => - world.unclipped.varData.col(label).summarize().percentiles[100 * q]; - world.varData = clipDataframe( - world.unclipped.varData, - world.clipQuantiles.min, - world.clipQuantiles.max, - varDataQuantile, - () => true - ); -} - -/* -Subset the current world based upon the current selection, maintaining any existing -clip. Returns new world. Parameters: - * universe - * world - the current world - * crossfilter - the selection state -*/ -export function createWorldBySelection(universe, world, crossfilter) { - const newWorld = { ...world, obsLayout: null, unclipped: {}, varData: null }; - - /* subset unclipped dataframes based upon current selection */ - const mask = crossfilter.allSelectedMask(); - newWorld.obsLayout = world.obsLayout.isubsetMask(mask); - newWorld.unclipped.obsAnnotations = world.unclipped.obsAnnotations.isubsetMask( - mask - ); - if (world.unclipped.varData.isEmpty()) { - newWorld.unclipped.varData = world.unclipped.varData.clone(); - } else { - newWorld.unclipped.varData = world.unclipped.varData.isubsetMask(mask); - } - /* subsetting changings dimension size */ - newWorld.nObs = newWorld.unclipped.obsAnnotations.dims[0]; - - /* and now clip */ - setClippedDataframes(newWorld); - return newWorld; -} - -/* -Change clip quantiles on the current world, returning a new world. -Parameters: - * universe - * world - current world - * clipQuantiles - new clip -*/ -export function createWorldWithNewClip( - universe, - world, - crossfilter, - clipQuantiles -) { - const newWorld = { ...world, obsAnnotation: null, varData: null }; - newWorld.clipQuantiles = clipQuantiles; - newWorld.obsLayout = world.obsLayout.clone(); - newWorld.unclipped = { - obsAnnotations: world.unclipped.obsAnnotations.clone(), - varData: world.unclipped.varData.clone(), - }; - - /* and now clip */ - setClippedDataframes(newWorld); - return newWorld; -} - -/* - Deduce the correct crossfilter dimension type from a metadata - schema description. -*/ -function deduceDimensionType(attributes, fieldName) { - let dimensionType; - const { type } = attributes; - if (type === "string" || type === "categorical" || type === "boolean") { - dimensionType = "enum"; - } else if (type === "int32") { - dimensionType = Int32Array; - } else if (type === "float32") { - dimensionType = Float32Array; - } else { - /* - Currently not supporting boolean and categorical types. - */ - console.error( - `Warning - REST API returned unknown metadata schema (${type}) for field ${fieldName}.` - ); - // skip it - we don't know what to do with this type - } - return dimensionType; -} - -function addObsDimension(crossfilter, world, anno) { - /* - add single dimension to the crosfilter - */ - const { obsAnnotations } = world; - if (obsAnnotations.hasCol(anno.name)) { - const dimType = deduceDimensionType(anno, anno.name); - const colData = obsAnnotations.col(anno.name).asArray(); - const name = obsAnnoDimensionName(anno.name); - if (dimType === "enum") { - return crossfilter.addDimension(name, "enum", colData); - } - if (dimType) { - return crossfilter.addDimension(name, "scalar", colData, dimType); - } - } - return crossfilter; -} - -export function addObsDimensions(crossfilter, world) { - /* - Add to crossfilter any dimension present in world.obsAnnotations - but not yet in the crossfilter - */ - const schema = world.schema.annotations.obsByName; - const dimsWeNeed = world.obsAnnotations.colIndex.labels(); - crossfilter = dimsWeNeed.reduce((xfltr, name) => { - const dimName = obsAnnoDimensionName(name); - if (xfltr.hasDimension(dimName)) return xfltr; - return addObsDimension(xfltr, world, schema[name]); - }, crossfilter); - return crossfilter; -} - -export function createObsDimensions(crossfilter, world, XYdimNames) { - /* - create and return a crossfilter with a dimension for every obs annotation - for which we have a supported type, *except* for the index column, indicated - by schema.annotations.obs.index. - */ - const { schema, obsLayout } = world; - const indexName = schema.annotations.obs.index; - const annoList = schema.annotations.obs.columns.filter( - (anno) => anno.name !== indexName - ); - crossfilter = annoList.reduce((xfltr, anno) => { - return addObsDimension(xfltr, world, anno); - }, crossfilter); - - return crossfilter.addDimension( - layoutDimensionName("XY"), - "spatial", - obsLayout.col(XYdimNames[0]).asArray(), - obsLayout.col(XYdimNames[1]).asArray() - ); -} - -export function worldEqUniverse(world, universe) { - return ( - world.obsAnnotations === universe.obsAnnotations || - world.obsAnnotations.rowIndex === universe.obsAnnotations.rowIndex - ); -} - -export function getSelectedByIndex(crossfilter) { - /* - return array of obsIndex, containing all selected obs/cells. - */ - const selected = crossfilter.allSelectedMask(); // array of bool-ish - const keys = crossfilter.data.rowIndex.labels(); // row keys, aka universe rowIndex - - const set = new Int32Array(selected.length); - let numElems = 0; - for (let i = 0, l = selected.length; i < l; i += 1) { - if (selected[i]) { - set[numElems] = keys[i]; - numElems += 1; - } - } - return new Int32Array(set.buffer, 0, numElems); -} diff --git a/client/src/util/typedCrossfilter/bitArray.js b/client/src/util/typedCrossfilter/bitArray.js index 89470e22..73f7aa78 100644 --- a/client/src/util/typedCrossfilter/bitArray.js +++ b/client/src/util/typedCrossfilter/bitArray.js @@ -1,5 +1,4 @@ -// jshint esversion: 6 -/* eslint no-bitwise: "off" */ +/* eslint-disable no-bitwise -- crossfilter relies on bitwise ops */ // BitArray is a 2D bitarray with size [length, nBitWidth]. // Each bit is referred to as a `dimension`. Dimensions may be @@ -291,3 +290,4 @@ class BitArray { } export default BitArray; +/* eslint-enable no-bitwise -- enable */ diff --git a/client/src/util/typedCrossfilter/crossfilter.js b/client/src/util/typedCrossfilter/crossfilter.js index c1ccaa41..6e51d331 100644 --- a/client/src/util/typedCrossfilter/crossfilter.js +++ b/client/src/util/typedCrossfilter/crossfilter.js @@ -1,4 +1,4 @@ -// eslint-disable-next-line max-classes-per-file +// eslint-disable-next-line max-classes-per-file -- classes are interrelated import PositiveIntervals from "./positiveIntervals"; import BitArray from "./bitArray"; import { @@ -60,6 +60,9 @@ export default class ImmutableTypedCrossfilter { } setData(data) { + if (this.data === data) return this; + // please leave, WIP + // console.log("...crossfilter set data, will drop cache"); return new ImmutableTypedCrossfilter(data, this.dimensions); } @@ -192,6 +195,7 @@ export default class ImmutableTypedCrossfilter { adds = PositiveIntervals.difference(newSeln.ranges, oldSeln.ranges); dels = PositiveIntervals.difference(oldSeln.ranges, newSeln.ranges); } else { + // please leave, WIP // console.log("suboptimal selection update - index changed"); adds = newSeln.ranges; dels = oldSeln.ranges; @@ -408,7 +412,7 @@ class ImmutableScalarDimension extends _ImmutableBaseDimension { this.index = makeSortIndex(array); } - // eslint-disable-next-line class-methods-use-this + // eslint-disable-next-line class-methods-use-this -- needed for polymorphism _createValueArray(data, mapf, array) { // create dimension value array const len = data.length; @@ -503,7 +507,10 @@ class ImmutableEnumDimension extends ImmutableScalarDimension { selectExact(spec) { const { enumIndex } = this; - const { values } = spec; + let { values } = spec; + if (!Array.isArray(values)) { + values = [values]; + } return super.selectExact({ mode: spec.mode, values: values.map((v) => @@ -512,7 +519,7 @@ class ImmutableEnumDimension extends ImmutableScalarDimension { }); } - // eslint-disable-next-line class-methods-use-this + // eslint-disable-next-line class-methods-use-this -- enables polymorphism selectRange() { throw new Error("range selection unsupported on Enumerated dimension"); } diff --git a/client/src/util/typedCrossfilter/positiveIntervals.js b/client/src/util/typedCrossfilter/positiveIntervals.js index bafc68d1..fa89e70c 100644 --- a/client/src/util/typedCrossfilter/positiveIntervals.js +++ b/client/src/util/typedCrossfilter/positiveIntervals.js @@ -30,7 +30,8 @@ class PositiveIntervals { res.push(copy[i]); } else if (copy[i][1] > res[res.length - 1][1]) { // merge this into previous - res[res.length - 1][1] = copy[i][1]; // eslint-disable-line prefer-destructuring + // eslint-disable-line prefer-destructuring -- destructuring impedes readability + res[res.length - 1][1] = copy[i][1]; } } return res; diff --git a/client/src/util/typedCrossfilter/sort.js b/client/src/util/typedCrossfilter/sort.js index eb1c76a3..700d1de4 100644 --- a/client/src/util/typedCrossfilter/sort.js +++ b/client/src/util/typedCrossfilter/sort.js @@ -1,6 +1,6 @@ import { isTypedArray, isFpTypedArray } from "../typeHelpers"; -/* eslint no-bitwise: "off" */ +/* eslint-disable no-bitwise -- code relies on bitwise ops */ /* ** fast sort and search, with separate code paths for floats (NaN ordering), @@ -426,3 +426,4 @@ export function binarySearch(valueArray, value, first, last) { if (index !== last && value === valueArray[index]) return index; return last; } +/* eslint-enable no-bitwise -- enable */ diff --git a/common.mk b/common.mk index dc1d3bd2..ee2e4b0b 100644 --- a/common.mk +++ b/common.mk @@ -3,24 +3,24 @@ PATH := $(PATH):$(PROJECT_ROOT)/scripts SHELL := env PATH='$(PATH)' /bin/bash -# get_or_else_dev_env_default -# - If a variable is defined, return its value -# - Else return the default value from environment.dev -define get_or_else_dev_env_default -$(if $($(1)),$($(1)),$(shell VAR=$$(sed -n 's/$(1)=\(.*\)/\1/p' $(PROJECT_ROOT)/environment.default); eval "echo \"$$VAR\"")) -endef - -# https://stackoverflow.com/a/14777895/9587410 -ifeq ($(shell uname),Darwin) # is Windows_NT on XP, 2000, 7, Vista, 10... - IS_DARWIN := "true" +ifeq ($(shell which jq),) +$(error Please install jq using "apt-get install jq" or "brew install jq") endif -export CELLXGENE_COMMIT := $(shell git rev-parse --short HEAD) -export CXG_SERVER_PORT := $(call get_or_else_dev_env_default,CXG_SERVER_PORT) -export CXG_CLIENT_PORT := $(call get_or_else_dev_env_default,CXG_CLIENT_PORT) -export JEST_ENV := $(call get_or_else_dev_env_default,JEST_ENV) -export DATASET := $(call get_or_else_dev_env_default,DATASET) -export CXG_OPTIONS := $(call get_or_else_dev_env_default,CXG_OPTIONS) +define env_or_else_default +$(if $($(1)),$($(1)),$(shell jq -r '.$(1)' "$(PROJECT_ROOT)/environment.default.json")) +endef + +# if not a full path, create a full path relative to the project root +define full_path +$(shell [[ $(1) = /* ]] && echo $(1) || echo $(PROJECT_ROOT)/$(1)) +endef + +export CXG_SERVER_PORT := $(call env_or_else_default,CXG_SERVER_PORT) +export CXG_CLIENT_PORT := $(call env_or_else_default,CXG_CLIENT_PORT) +export CXG_OPTIONS := $(call env_or_else_default,CXG_OPTIONS) +export DATASET := $(call full_path,$(call env_or_else_default,DATASET)) +export JEST_ENV := $(call env_or_else_default,JEST_ENV) .PHONY: start-server start-server: diff --git a/dev_docs/developer_guidelines.md b/dev_docs/developer_guidelines.md index 93c03d63..e7a4dce7 100644 --- a/dev_docs/developer_guidelines.md +++ b/dev_docs/developer_guidelines.md @@ -1,6 +1,7 @@ # Developer guidelines -### Requirements +## Requirements + - npm - Python 3.6+ - Chrome @@ -11,20 +12,24 @@ ### Environment -For all `make` commands, `common.mk` automatically checks whether required environment variables are set and, if they are not set, assigns them default values from `environment.default`. +For all `make` commands, `common.mk` automatically checks whether required environment variables are set and, if they are not set, assigns them default values from `environment.default.json`. -You can set these environment variables manually with the `export` shell command, as in `export JEST_ENV=debug`. +You can set these environment variables manually with the `export` shell command, as in `export JEST_ENV=debug`, or you can just pass the variables as part of the command. E.g., `HEADFUL=true make e2e` or `JEST_ENV=debug npm run e2e` ## Running test suite -Client and server tests run on Travis CI for every push, PR, and commit to master on github. End to end tests run nightly on master only. + +Client and server tests run on Travis CI for every push, PR, and commit to `main` on github. End to end tests run nightly on `main` only. ### Unit tests + Steps to run the all unit tests: + 1. Start in the project root directory 1. `make dev-env` 1. `make unit-test` To run unit tests for the `client` code only: + 1. Start in the project root directory 1. `cd client` 1. `make unit-test` @@ -33,37 +38,58 @@ To run unit tests for the `client` code only: To run E2E tests, run `cd client` and `make smoke-test` -The `JEST_ENV` environment variable enables the following E2E test options: -* `dev` - opens chromimum, runs tests with minimal slowdown, close on exit. -* `debug` - opens chromium, runs tests with 100ms slowdown, dev tools open, chrome stays open on exit. -* `prod` - run headless with no slowdown, chromium will not open. +#### Flags -Run end to end tests interactively during development -1. cellxgene should be installed as [specified in client dev](#install-1) -1. Follow [launch](#launch-1) instructions for client dev with dataset `example-dataset/pbmc3k` -1. Run `npm run e2e` or `make e2e` from the `client` directory -1. To debug a failing test `export JEST_ENV='debug'` and re-run. +1. `JEST_ENV`: This enables the following E2E test options. You can find their corresponding configs in [`jest-puppeteer.config.js`](../client/jest-puppeteer.config.js): -To run end to end tests _exactly_ as they will be run on CI use the following command: -``` + - `dev` - opens window, runs tests with minimal slowdown, close on exit. + - `debug` - opens window, runs tests with 100ms slowdown, dev tools open, chrome stays open on exit. + - `prod`[default] - run headless with no slowdown, window will not open. + +2. `HEADFUL`: Default is `false`. When set to `true`, it will launch the Chrome window for visual inspection. E.g., `HEADFUL=true npm run e2e` + +3. `HEADLESS`: Default is `true`. When set to `false`, it will launch the Chrome window for visual inspection. E.g., `HEADLESS=false npm run e2e` + +#### Run end to end tests interactively during development + +1. cellxgene should be installed as [specified in client dev](#install) + +1. Follow [launch](#launch) instructions for client dev with dataset `example-dataset/pbmc3k` + +1. Run `npm run e2e` from the `client` directory + +1. To debug a failing test, add `debugger` in any line of JS code as breakpoint, and launch the test again with [`ndb`](https://github.com/GoogleChromeLabs/ndb). E.g., `ndb make e2e` or `ndb npm run e2e`. + + 1. Please make sure to install `ndb` via `npm install -g ndb` + + 1. Check out [Debugging Tips](e2e_tests.md#debugging-tips) for more ideas! + +#### To run end to end tests _exactly_ as they will be run on CI use the following command + +```shell JEST_ENV=prod make pydist install-dist dev-env smoke-test ``` ## Server dev + ### Install To install from the source tree -* Build the client and put static files in place: `make build-for-server-dev` -* Install from local files: `make install-dev` + +- Build the client and put static files in place: `make build-for-server-dev` +- Install from local files: `make install-dev` To install from a candidate python distribution -* Make the distribution: `make pydist` -* Install it: `make install-dist` + +- Make the distribution: `make pydist` +- Install it: `make install-dist` ### Launch -* `cellxgene launch [options] ` or `make start-server` + +- `cellxgene launch [options] ` or `make start-server` ### Reloading + If you install cellxgene using `make install-dev` the server will be restarted every time you make changes on the server code. If changes affects the client, the browser must be reloaded. ### Linter @@ -73,41 +99,55 @@ We use [`flake8`](https://github.com/PyCQA/flake8) to lint python and [`black`]( To auto-format code run `make fmt`. To run lint checks on the code run `make lint`. ### Test + If you would like to run the server tests individually, follow the steps below + 1. Install development requirements `make dev-env` 1. Run `make unit-test` in the `server` directory or `make unit-test-server` in the root directory. ### Tips -* Install in a virtualenv -* May need to rebuild/reinstall when you make client changes + +- Install in a virtualenv +- May need to rebuild/reinstall when you make client changes ## Client dev + ### Install + 1. Install prereqs for client: `make dev-env` 2. Install cellxgene server as described in the [server install](#install) instructions above. ### Launch + To launch with hot reloading, you need to launch the server and the client separately. Node's hot reloading starts the client on its own node server and auto-refreshes when changes are made to source files. + 1. Launch server (the client relies on the REST API being available): `cellxgene launch --debug [other_options] ` or `make start-server` 2. Launch client: in `client/` directory run `make start-frontend` 3. Client will be served on `localhost:3000` ### Build + To build only the client: `make build-client` ### Linter + We use `eslint` to lint the code and `prettier` as our code formatter. ### Test If you would like to run the client tests individually, follow the steps below in the `client` directory + 1. For unit tests run `make unit-test` 1. For the smoke test run `make smoke-test` for the standard smoke test suite and `make smoke-test-annotations` for the annotations test suite. If you would like to run the smoke tests against a hot-reloaded version of the client: + 1. Start the hot-reloading servers as described in the [Client dev section](#client-dev). If you plan to run the standard test suite (without annotations), you'll have to start the backend server with annotations disabled (e.g. `CXG_OPTIONS='--debug --disable-annotations' make start-server`). 1. From the project root, `cd client` -1. Run either the standard E2E test suite with `CXG_CLIENT_PORT=3000 make e2e` or the annotations test suite with `CXG_CLIENT_PORT=3000 make e2e-annotations` +1. Run either the standard E2E test suite with `npm run e2e` or the annotations test suite with `npm run e2e-annotations` ### Tips -* You can also install/launch the server side code from npm scrips (requires python3.6 with virtualenv) with the `scripts/backend_dev` script. + +- You can also install/launch the server side code from npm scrips (requires python3.6 with virtualenv) with the `scripts/backend_dev` script. + +- Check out [e2e Tests](e2e_tests.md) for more details diff --git a/dev_docs/developer_scripts.md b/dev_docs/developer_scripts.md index e739262e..864a2e58 100644 --- a/dev_docs/developer_scripts.md +++ b/dev_docs/developer_scripts.md @@ -109,8 +109,8 @@ Methods used to test the client javascript code `backend-dev` targets. One starts the server, the other runs the tests. If developing a front-end feature and just checking if tests pass, this is probabaly the one you want to run. -* `make e2e` Runs backend tests without starting the server. You will need to +* `npm run e2e` Runs backend tests without starting the server. You will need to start the rest api separately with the pbmc3k.h5ad file. Note you can use the `JEST_ENV` environment variable to change how JEST runs in the browser. - The test runs against `localhost:5005` by default. You can use the + The test runs against `localhost:3000` by default. You can use the `CXG_URL_BASE` env variable to test non-localhost deployments of cellxgene. diff --git a/dev_docs/e2e_tests.md b/dev_docs/e2e_tests.md index 2c5ff687..c2730fed 100644 --- a/dev_docs/e2e_tests.md +++ b/dev_docs/e2e_tests.md @@ -1,47 +1,88 @@ -## End to end testing thesis +# End to end testing + +## Thesis + End to end (e2e) tests, when done correctly, can surface errors and regressions in the application before it is released to production. The tests are run against a version of the web app that is running the full stack. By checking the fully functional app, you are able to surface issues with logic and data at every level of the stack (you still have to figure out which level that error is occurring at). You are also able to test how features that are added affect the whole application. -The major problem most developers have with e2e tests are that they the tests can be flaky and thus not trusted. Failing tests wind up being disabled instead of fixed. To guard against that we need to establish principals about what features to test and have guidelines for writing tests to ensure that they are as robust as possible. By ensuring each test has a high value and is unlikely to fail when it shouldn't it increases the motivation to fix the test as opposed to deleting it. +The major problem most developers have with e2e tests are that the tests can be flaky and thus not trusted. Failing tests wind up being disabled instead of fixed. To guard against that we need to establish principals about what features to test and have guidelines for writing tests to ensure that they are as robust as possible. By ensuring each test has a high value and is unlikely to fail when it shouldn't it increases the motivation to fix the test as opposed to deleting it. -### Choosing what to test: +Additionally, we try to combat flaky tests by re-running failed tests two more times before declaring a test is truly failing. Even though this mediation doesn't solve the root cause of the flakiness, it does help decrease the false positive cases caused by the flakiness. -* Common actions - Features and actions that are highly used will cause more impact if they break -* High value - Features that may be rarely used, but cause a major business impact if they break -* Depend on the whole stack and not just the FE - We want to test the backend and data as well as the FE. Features that only depend on the javascript code should be tested in unit tests instead. -* Feature is in a stable state - If the feature is in active development, it's likely that the test will have to be constantly updated. Wait until it is in a steady state before adding an e2e test. -* Feature is a likely target of regressions - If a feature has caused regressions in the past, it's good to add some monitoring to it. +### Choosing what to test -### Guidelines for writing tests: +- Common actions - Features and actions that are highly used will cause more impact if they break +- High value - Features that may be rarely used, but cause a major business impact if they break +- Depend on the whole stack and not just the FE - We want to test the backend and data as well as the FE. Features that only depend on the javascript code should be tested in unit tests instead. +- Feature is in a stable state - If the feature is in active development, it's likely that the test will have to be constantly updated. Wait until it is in a steady state before adding an e2e test. +- Feature is a likely target of regressions - If a feature has caused regressions in the past, it's good to add some monitoring to it. -* Use data attributes as selectors instead of positional, classname, or id selectors. Add `data-testid` or `data-testclass` attributes to components that are inputs and outputs. The advantages are 1) You can move the component around and even change it's html element type and it will not break the test and 2) It is self documenting what the attribute is used by as opposed to a class name or id that could be used by CSS or something else. -* Use the helper functions in `puppeteerUtils.js` and ` for performing common actions. Puppeteer operates at a low level and these abstract it to a level of user actions. +### Guidelines for writing tests + +- Use data attributes as selectors instead of positional, classname, or id selectors. Add `data-testid` or `data-testclass` attributes to components that are inputs and outputs. The advantages are 1) You can move the component around and even change its html element type and it will not break the test and 2) It is self documenting what the attribute is used by as opposed to a class name or id that could be used by CSS or something else. + +- Use the helper functions in `puppeteerUtils.js` and `cellxgeneActions.js` for performing common actions. Puppeteer operates at a low level and these abstract it to a level of user actions. + +- We use [`expect-puppeteer`](https://github.com/smooth-code/jest-puppeteer/tree/master/packages/expect-puppeteer#api)'s API to avoid directly using Puppeteer's API where possible. So **please only resort to Puppeteer's API if you cannot achieve an interaction you need via `expect-puppeteer`.** For actions that don't meet these guidelines, you can create a test in feature.test.js that does not run on PRs so if the feature breaks it doesn't block anyone. ## Where/How + Where: client/\_\_tests\_\_/e2e/e2e.test.js -How: `npm run e2e` +How: `npm run e2e` or `make e2e` -Tests are written using the Jest testing framework. Browser automation is handled by puppeteer. Puppeteer provides an API to control Chrome over the DevTools Protocol. This allows us to write code that mimics user actions and check page state. +Tests are written using the Jest testing framework. Browser automation is handled by Puppeteer. Puppeteer provides an API to control Chrome over the DevTools Protocol. This allows us to write code that mimics user actions and check page state. -Tests are running on CI with every PR, they can also be run locally (see [developer guidelines](developer_guidelines.md)). +Tests are running on CI with every PR, they can also be run locally (see [developer guidelines](developer_guidelines.md)). -The cellxgene instance is running with the pbmc3k dataset. Tests for additional datasets can be added, the data.js file contains the values for inputs and outputs expected for different datasets. Choose values that make sense for that dataset. +The cellxgene instance is running with the `pbmc3k` dataset. Tests for additional datasets can be added, the `data.js` file contains the values for inputs and outputs expected for different datasets. Choose values that make sense for that dataset. ## Setting up e2e tests locally + See [developer guidelines](developer_guidelines.md) ## How to write a new test -1. Ensure that your new test meets the guidelines above for high-value robust user actions to test. -2. Clearly define the series of user actions and outputs you want to test -3. Add testid (and/or testclass if element is not unique) data attributes to inputs and outputs you want to interact with. -4. Create a new test block possible nested in a describe block if you want to add more than one related test client/\_\_tests\_\_/e2e/e2e.test.js -5. Write the test code. -6. Run tests locally to ensure they pass. -**Debugging Tips** -* Mystery bugs are often caused by a missing `async` or `await`. Double check every function and every line (twice!). -* Switch from to debug mode by running `export JEST_ENV='debug'` on the CLI before running the tests -* Put a javascript breakpoint in by adding this line `await jestPuppeteer.debug()` to the test at the point which you want to pause execution. -* Limit the tests that are run by adding `.only` after `describe` or `test` to limit execution to just that block of tests. (Remember to take the `.only` out before you commit!) \ No newline at end of file +1. Ensure that your new test meets the guidelines above for high-value robust user actions to test. + +1. Clearly define the series of user actions and outputs you want to test + +1. Add `testid` (and/or `testclass` if element is not unique) data attributes to inputs and outputs you want to interact with. + E.g., `
` or `
` + +1. Create a new test block possible nested in a describe block if you want to add more than one related test client/\_\_tests\_\_/e2e/e2e.test.js + +1. Write the test code. + +1. Run tests locally to ensure they pass. + +## Debugging Tips + +- Mystery bugs are often caused by a missing `async` or `await`. Double check every function and every line (twice!). + +- Use `ndb` for debugging: Put a javascript breakpoint in by adding this line `debugger` to the test at the point which you want to pause execution. And then run `ndb npm run e2e` or `ndb make e2e`. + + NOTE: You will need to global install [`ndb`](https://github.com/GoogleChromeLabs/ndb) via `npm install -g ndb`, which provides an improved debugging experience for Node.js, enabled by Chrome DevTools + +- Limit the tests that are run by adding `.only` after `describe` or `test` to limit execution to just that block of tests. (Remember to take the `.only` out before you commit!) + +- Avoid using `beforeEach() => {}` to set up test environment for each test, since Jest/Jasmine doesn't fail a test when a set up step fails, and we will end up getting obscure errors and don't know why a test fails. Another reason is for readability as explained [here](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing/) + +- Use [`takeScreenshot()`](../client/__tests__/e2e/takeScreenshot.js) to timestamp and store screenshots in the already git ignored folder `client/__tests__/screenshots/`, so you don't accidentally check in your screenshot in your commit + +## Config Files + +1. [jest-puppeteer.config.js](../client/jest-puppeteer.config.js) is for configuring Puppeteer's launch options as specified [here](https://github.com/puppeteer/puppeteer/blob/v3.1.0/docs/api.md#puppeteerlaunchoptions). Note: The link is to the doc for **v3.1.0** + +1. [e2eJestConfig.json](../client/__tests__/e2e/e2eJestConfig.json) is for configuring `jest` when running e2e tests. For example, in `package.json` we have: + + ```ts + { + "e2e": "jest --config __tests__/e2e/e2eJestConfig.json e2e/e2e.test.js", + } + ``` + + which tells `jest` to use `e2eJestConfig.json` as the config file to run e2e test file `e2e.test.js` + +1. [puppeteer.setup.js](../client/__tests__/e2e/puppeteer.setup.js) is for configuring `jest`, `browser`, and `page` objects at runtime diff --git a/dev_docs/release_process.md b/dev_docs/release_process.md index 2b67adfc..6a02280c 100644 --- a/dev_docs/release_process.md +++ b/dev_docs/release_process.md @@ -33,7 +33,7 @@ Follow these steps to create a release. 4. Commit and push the new branch 5. Create a PR for the release. - [optional] As needed, conduct PR review. -6. Merge to master +6. Merge to the `main` branch 7. Publish to pypi by performing the following steps (assumes you that you have registered for pypi, and that you have write access to the cellxgene pypi package): - Build the distribution and upload to test pypi `make release-stage-2` @@ -44,7 +44,7 @@ Follow these steps to create a release. ([instructions](https://help.github.com/articles/creating-releases/)). - Draft new release - Type version name matching release version number from (1) - - Select `master` as release branch (ensure you merged the release PR) + - Select `main` as release branch (ensure you merged the release PR) - Type title `Release {version num}` - [optional] Check pre-release if this release is not ready for production - Publish Release @@ -55,7 +55,7 @@ change the packaging (e.g. new bundled files, new dependencies, etc.) ### Point release (special case) -To make a bugfix release (a point release) when there are already other changes in master we need to do a modified version of our release process. The difference is that instead of using master we are going make our release branch off of the tag for the release we want to patch. We cherrypick the commits that we want to include in the patch. Then instead of merging to master, we create the release directly off of the branch. +To make a bugfix release (a point release) when there are already other changes in `main` we need to do a modified version of our release process. The difference is that instead of using `main` we are going make our release branch off of the tag for the release we want to patch. We cherrypick the commits that we want to include in the patch. Then instead of merging to `main`, we create the release directly off of the branch. 1. (same as above) Preparation: - python3.6 environment, and a cellxgene clone @@ -68,10 +68,10 @@ To make a bugfix release (a point release) when there are already other changes - Create a branch from that tag. `git branch release-0.9.1` 3. Cherrypick the commits that you want included in this patch. - Test that the cherrypicked commits landed and fixed the issue - - We WILL NOT merge this branch back into master, these commits should already exist in master. + - We WILL NOT merge this branch back into `main`, these commits should already exist in `main`. 4. In the release branch: - Run `make release-stage-1 PART=patch`. -5. Commit and push the new branch. DO NOT MAKE A PR OR MERGE TO MASTER. +5. Commit and push the new branch. DO NOT MAKE A PR OR MERGE TO `main`. - wait for release to pass the tests 6. Publish to pypi by performing the following steps (assumes you that you have registered for pypi, and that you have write access to the cellxgene pypi package): - Build the distribution and upload to test pypi `make release-stage-2` - Test the test installation in a fresh virtual environment using `make install-release-test` - Upload the package to real pypi using `make release-stage-final` - Test the installation in a fresh virtual environment using diff --git a/docs/_site/index.html b/docs/_site/index.html index 51bd2b56..51b1d42b 100644 --- a/docs/_site/index.html +++ b/docs/_site/index.html @@ -16,10 +16,10 @@ +{"@type":"WebSite","headline":"Index","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/","name":"cellxgene","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/annotations.html b/docs/_site/posts/annotations.html index aff4fa54..57a31083 100644 --- a/docs/_site/posts/annotations.html +++ b/docs/_site/posts/annotations.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"annotations","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Creating annotations","url":"https://chanzuckerberg.github.io/cellxgene/posts/annotations.html","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/contact.html b/docs/_site/posts/contact.html index dd696127..59412b03 100644 --- a/docs/_site/posts/contact.html +++ b/docs/_site/posts/contact.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"Contact","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Contact","url":"https://chanzuckerberg.github.io/cellxgene/posts/contact.html","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/contribute.html b/docs/_site/posts/contribute.html index 587af5cb..8e793a58 100644 --- a/docs/_site/posts/contribute.html +++ b/docs/_site/posts/contribute.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"Code of conduct","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/posts/contribute.html","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/demo-data.html b/docs/_site/posts/demo-data.html index 967fbf9e..3879a512 100644 --- a/docs/_site/posts/demo-data.html +++ b/docs/_site/posts/demo-data.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"demo-data","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Demo datasets","url":"https://chanzuckerberg.github.io/cellxgene/posts/demo-data.html","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/gallery.html b/docs/_site/posts/gallery.html index 87e458be..976c88f6 100644 --- a/docs/_site/posts/gallery.html +++ b/docs/_site/posts/gallery.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"Gallery","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/posts/gallery.html","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/hosted.html b/docs/_site/posts/hosted.html index fa8a8e0f..ec586a6f 100644 --- a/docs/_site/posts/hosted.html +++ b/docs/_site/posts/hosted.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"Hosting cellxgene on the web","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/posts/hosted.html","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/install.html b/docs/_site/posts/install.html index 147a82af..c7614f4b 100644 --- a/docs/_site/posts/install.html +++ b/docs/_site/posts/install.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"Install","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/posts/install.html","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/launch.html b/docs/_site/posts/launch.html index 63b5d415..ac4bea95 100644 --- a/docs/_site/posts/launch.html +++ b/docs/_site/posts/launch.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"demo-data","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Demo datasets","url":"https://chanzuckerberg.github.io/cellxgene/posts/launch.html","@context":"http://schema.org"} - + @@ -114,7 +114,7 @@

You can also launch from a URL directly like this:

-
cellxgene launch https://github.com/chanzuckerberg/cellxgene/blob/master/example-dataset/pbmc3k.h5ad
+
cellxgene launch https://github.com/chanzuckerberg/cellxgene/blob/main/example-dataset/pbmc3k.h5ad
 

Support for S3 and GCS is not enabled by default. If you wish to directly access S3 or GFS, install one or both of the following packages:

diff --git a/docs/_site/posts/methods.html b/docs/_site/posts/methods.html index a69bdeec..2840a3d6 100644 --- a/docs/_site/posts/methods.html +++ b/docs/_site/posts/methods.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"Methods","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"An interactive explorer for single-cell transcriptomics data","url":"https://chanzuckerberg.github.io/cellxgene/posts/methods.html","@context":"http://schema.org"} - + @@ -114,7 +114,7 @@

Differential expression

We’re actively working on how to improve differential expression within the app. -N.B.: the current implementation assumes normally distributed values on a linear scale.

+N.B.: the current implementation assumes normally distributed values on a linear scale.

Currently, we use a Welch’s t-test, which assumes that the two populations are each normally distributed, but may have unequal variance. We use a two-sided t-test against the null hypothesis that the two populations have equal means. P-values are adjusted with the Bonferroni corrrection.

diff --git a/docs/_site/posts/methods.md b/docs/_site/posts/methods.md index 17a72da2..22a5407e 100644 --- a/docs/_site/posts/methods.md +++ b/docs/_site/posts/methods.md @@ -17,7 +17,7 @@ cellxgene looks for embeddings (e.g., tSNE, UMAP, PCA, spatial coordinates) in ` ## Differential expression We're actively working on how to improve differential expression within the app. -**N.B.: the [current implementation](https://github.com/chanzuckerberg/cellxgene/blob/master/server/app/scanpy_engine/diffexp.py#L40) assumes normally distributed values on a linear scale.** +**N.B.: the [current implementation](https://github.com/chanzuckerberg/cellxgene/blob/main/server/app/scanpy_engine/diffexp.py#L40) assumes normally distributed values on a linear scale.** Currently, we use a [Welch's _t_-test](https://en.wikipedia.org/wiki/Welch%27s_t-test), which assumes that the two populations are each normally distributed, but may have unequal variance. We use a two-sided t-test against the null hypothesis that the two populations have **equal** means. P-values are adjusted with the [Bonferroni corrrection](https://en.wikipedia.org/wiki/Bonferroni_correction). diff --git a/docs/_site/posts/prepare.html b/docs/_site/posts/prepare.html index 05f88230..adf0c529 100644 --- a/docs/_site/posts/prepare.html +++ b/docs/_site/posts/prepare.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"prepare","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Preparing your data","url":"https://chanzuckerberg.github.io/cellxgene/posts/prepare.html","@context":"http://schema.org"} - + @@ -103,7 +103,7 @@
  • Expression values (raw or normalized) in anndata.X
  • At least one embedding (e.g., tSNE, UMAP) in anndata.obsm, specified with the prefix X_ (e.g., by default scanpy stores UMAP coordinates in anndata.obsm['X_umap'])
  • A unique identifier is required for each cell, which by default will be pulled from the obs DataFrame index. If the index is not unique or does not contain the cell ID, an alternative column can be specified with --obs-names
  • -
  • A unique identifier is required for each gene, which by default will be pulled from the var DataFrame index. If the index is not unique or does not contain the cell ID, an alternative column can be specified with --var-names
  • +
  • A unique identifier is required for each gene, which by default will be pulled from the var DataFrame index. If the index is not unique or does not contain the gene ID, an alternative column can be specified with --var-names
  • What about R objects from seurat / bioconductor!?

    diff --git a/docs/_site/posts/roadmap.html b/docs/_site/posts/roadmap.html index 1d053152..5a795d78 100644 --- a/docs/_site/posts/roadmap.html +++ b/docs/_site/posts/roadmap.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"roadmap","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Roadmap","url":"https://chanzuckerberg.github.io/cellxgene/posts/roadmap.html","@context":"http://schema.org"} - + diff --git a/docs/_site/posts/troubleshooting.html b/docs/_site/posts/troubleshooting.html index f0586b56..8132fce3 100644 --- a/docs/_site/posts/troubleshooting.html +++ b/docs/_site/posts/troubleshooting.html @@ -16,10 +16,10 @@ +{"@type":"WebPage","headline":"Troubleshooting","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://chanzuckerberg.github.io/cellxgene/cellxgene-logo.png"}},"description":"Troubleshooting","url":"https://chanzuckerberg.github.io/cellxgene/posts/troubleshooting.html","@context":"http://schema.org"} - + diff --git a/docs/posts/cellxgene_cziscience_com.md b/docs/posts/cellxgene_cziscience_com.md index 51fa3c25..833ae0ce 100644 --- a/docs/posts/cellxgene_cziscience_com.md +++ b/docs/posts/cellxgene_cziscience_com.md @@ -191,5 +191,125 @@ with a link to embed on your own site, please drop us a note at Single Cell Portal + + Selective Neuronal Vulnerability in Alzheimer's Disease + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Superior Frontal Gyrus + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Astrocytes in EC + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Excitatory Neurons in EC + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Inhibitory Neurons in EC + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Microglia in EC + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Astrocytes in SFG + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Excitatory Neurons in SFG + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Inhibitory Neurons in SFG + + Kampmann Lab, + BioRxiv preprint + + + + Selective Neuronal Vulnerability in Alzheimer's Disease: Microglia in SFG + + Kampmann Lab, + BioRxiv preprint + + + + Single-cell gene expression profiling of SARS-CoV-2 infected human cell lines - H1299 + + Landthaler Lab, + BioRxiv preprint + + + + Single-cell gene expression profiling of SARS-CoV-2 infected human cell lines - Calu-3 + + Landthaler Lab, + BioRxiv preprint + + + + Single-cell drug screening - A549 + + Trapnell Lab Github, + Science + + + + Single-cell drug screening - K562 + + Trapnell Lab Github, + Science + + + + Single-cell drug screening - MCF7 + + Trapnell Lab Github, + Science + + + + A molecular atlas of cell types and zonation in the brain vasculature + + Betsholtz Lab, + Nature + + diff --git a/docs/posts/launch.md b/docs/posts/launch.md index 5046eb71..7a486ae2 100644 --- a/docs/posts/launch.md +++ b/docs/posts/launch.md @@ -25,7 +25,7 @@ You should see your web browser open with the following You can also launch from a URL directly like this: ``` -cellxgene launch https://github.com/chanzuckerberg/cellxgene/blob/master/example-dataset/pbmc3k.h5ad +cellxgene launch https://github.com/chanzuckerberg/cellxgene/blob/main/example-dataset/pbmc3k.h5ad ``` Support for S3 and GCS is not enabled by default. If you wish to directly access S3 or GFS, install one or both of the following packages: diff --git a/docs/posts/methods.md b/docs/posts/methods.md index 17a72da2..22a5407e 100644 --- a/docs/posts/methods.md +++ b/docs/posts/methods.md @@ -17,7 +17,7 @@ cellxgene looks for embeddings (e.g., tSNE, UMAP, PCA, spatial coordinates) in ` ## Differential expression We're actively working on how to improve differential expression within the app. -**N.B.: the [current implementation](https://github.com/chanzuckerberg/cellxgene/blob/master/server/app/scanpy_engine/diffexp.py#L40) assumes normally distributed values on a linear scale.** +**N.B.: the [current implementation](https://github.com/chanzuckerberg/cellxgene/blob/main/server/app/scanpy_engine/diffexp.py#L40) assumes normally distributed values on a linear scale.** Currently, we use a [Welch's _t_-test](https://en.wikipedia.org/wiki/Welch%27s_t-test), which assumes that the two populations are each normally distributed, but may have unequal variance. We use a two-sided t-test against the null hypothesis that the two populations have **equal** means. P-values are adjusted with the [Bonferroni corrrection](https://en.wikipedia.org/wiki/Bonferroni_correction). diff --git a/docs/posts/prepare.md b/docs/posts/prepare.md index 46483904..c7f30d3d 100644 --- a/docs/posts/prepare.md +++ b/docs/posts/prepare.md @@ -10,7 +10,7 @@ If your data is in `h5ad` file (from the [`anndata`](https://anndata.readthedocs - Expression values (raw or normalized) in `anndata.X` - At least one embedding (e.g., tSNE, UMAP) in `anndata.obsm`, specified with the prefix `X_` (e.g., by default scanpy stores UMAP coordinates in `anndata.obsm['X_umap']`) - A unique identifier is required for each cell, which by default will be pulled from the `obs` DataFrame index. If the index is not unique or does not contain the cell ID, an alternative column can be specified with `--obs-names` -- A unique identifier is required for each gene, which by default will be pulled from the `var` DataFrame index. If the index is not unique or does not contain the cell ID, an alternative column can be specified with `--var-names` +- A unique identifier is required for each gene, which by default will be pulled from the `var` DataFrame index. If the index is not unique or does not contain the gene ID, an alternative column can be specified with `--var-names` #### What about R objects from seurat / bioconductor!? We hear you! We'd also love to be able to ingest these files directly. This isn't currently possible, but in the meantime, you can use [sceasy](https://bioconda.github.io/recipes/r-sceasy/README.html) ([docs](https://cellgeni.readthedocs.io/en/latest/visualisations.html)) to convert to `h5ad`. Seurat also has some [handy conversion tools](https://satijalab.org/seurat/v3.0/conversion_vignette.html) that you can try out. diff --git a/environment.default b/environment.default deleted file mode 100644 index c309d3d2..00000000 --- a/environment.default +++ /dev/null @@ -1,6 +0,0 @@ -# Default environment variables for cellxgene development -CXG_SERVER_PORT=5005 -CXG_CLIENT_PORT=3000 -JEST_ENV=dev -DATASET="$(git rev-parse --show-toplevel)/example-dataset/pbmc3k.h5ad" -CXG_OPTIONS=--debug diff --git a/environment.default.json b/environment.default.json new file mode 100644 index 00000000..c5b32e0d --- /dev/null +++ b/environment.default.json @@ -0,0 +1,10 @@ +{ + "CXG_CLIENT_PORT": 3000, + "CXG_OPTIONS": "--debug", + "CXG_SERVER_PORT": 5005, + "DATASET": "example-dataset/pbmc3k.h5ad", + "DEBUG": "debug", + "DEV": "dev", + "JEST_ENV": "prod", + "RETRY_ATTEMPTS": 2 +} diff --git a/experiments/heroku/README.md b/experiments/heroku/README.md index 7f98a7ea..24668a35 100644 --- a/experiments/heroku/README.md +++ b/experiments/heroku/README.md @@ -4,7 +4,7 @@ Clicking on the following button will forward you to Heroku to begin the deployment process: - + Deploy diff --git a/server/Makefile b/server/Makefile index 20e95af1..74c44f7b 100644 --- a/server/Makefile +++ b/server/Makefile @@ -15,4 +15,3 @@ unit-test: --start-directory test/ \ --top-level-directory ../ \ --verbose - diff --git a/server/__init__.py b/server/__init__.py index dcafa944..27ce5322 100644 --- a/server/__init__.py +++ b/server/__init__.py @@ -1,6 +1,13 @@ from server.common.utils import import_plugins +import logging +import sys __version__ = "0.15.0" +display_version = "cellxgene v" + __version__ - -import_plugins("server.plugins") +try: + import_plugins("server.plugins") +except Exception as e: + # Make sure to exit in this case, as the server may not be configured as expected. + logging.critical(f"Error in import_plugins: {str(e)}") + sys.exit(1) diff --git a/server/app/app.py b/server/app/app.py index 911ddf6e..e290ca35 100644 --- a/server/app/app.py +++ b/server/app/app.py @@ -9,7 +9,7 @@ from server_timing import Timing as ServerTiming from http import HTTPStatus import server.common.rest as common_rest -from server.common.errors import DatasetAccessError +from server.common.errors import DatasetAccessError, RequestException from server.common.utils import path_join, Float32JSONEncoder from server.common.data_locator import DataLocator from server.common.health import health_check @@ -33,7 +33,7 @@ def _cache_control(always, **cache_kwargs): @wraps(f) def wrapper(*args, **kwargs): response = make_response(f(*args, **kwargs)) - if not always and not current_app.app_config.server__generate_cache_control_headers: + if not always and not current_app.app_config.server_config.app__generate_cache_control_headers: return response if response.status_code >= 400: return response @@ -47,7 +47,7 @@ def _cache_control(always, **cache_kwargs): def cache_control(**cache_kwargs): - """ configu driven """ + """ config driven """ return _cache_control(False, **cache_kwargs) @@ -56,34 +56,42 @@ def cache_control_always(**cache_kwargs): return _cache_control(True, **cache_kwargs) +# tell the client not to cache the index.html page so that changes to the app work on redeployment +# note that the bulk of the data needed by the client (datasets) will still be cached @webbp.route("/", methods=["GET"]) -@cache_control(public=True, max_age=ONE_WEEK) +@cache_control_always(public=True, max_age=0, no_store=True, no_cache=True, must_revalidate=True) def dataset_index(url_dataroot=None, dataset=None): - config = current_app.app_config + app_config = current_app.app_config + server_config = app_config.server_config if dataset is None: - if config.single_dataset__datapath: - location = config.single_dataset__datapath - else: + if app_config.is_multi_dataset(): return dataroot_index() + else: + location = server_config.single_dataset__datapath else: - dataroot = config.multi_dataset__dataroot.get(url_dataroot) + dataroot = None + for key, dataroot_dict in server_config.multi_dataset__dataroot.items(): + if dataroot_dict["base_url"] == url_dataroot: + dataroot = dataroot_dict["dataroot"] + break if dataroot is None: abort(HTTPStatus.NOT_FOUND) location = path_join(dataroot, dataset) - scripts = config.server__scripts - inline_scripts = config.server__inline_scripts + dataset_config = app_config.get_dataset_config(url_dataroot) + scripts = dataset_config.app__scripts + inline_scripts = dataset_config.app__inline_scripts try: cache_manager = current_app.matrix_data_cache_manager - with cache_manager.data_adaptor(location, config) as data_adaptor: - dataset_title = config.get_title(data_adaptor) + with cache_manager.data_adaptor(url_dataroot, location, app_config) as data_adaptor: + dataset_title = app_config.get_title(data_adaptor) return render_template( "index.html", datasetTitle=dataset_title, SCRIPTS=scripts, INLINE_SCRIPTS=inline_scripts ) - except DatasetAccessError: + except DatasetAccessError as e: return common_rest.abort_and_log( - HTTPStatus.BAD_REQUEST, f"Invalid dataset {dataset}", loglevel=logging.INFO, include_exc_info=True + e.status_code, f"Invalid dataset {dataset}: {e.message}", loglevel=logging.INFO, include_exc_info=True ) @@ -94,13 +102,26 @@ def health(): return health_check(config) +@webbp.errorhandler(RequestException) +def handle_request_exception(error): + return common_rest.abort_and_log(error.status_code, error.message, loglevel=logging.INFO, include_exc_info=True) + + def get_data_adaptor(url_dataroot=None, dataset=None): config = current_app.app_config + server_config = config.server_config + dataset_key = None if dataset is None: - datapath = config.single_dataset__datapath + datapath = server_config.single_dataset__datapath else: - dataroot = config.multi_dataset__dataroot.get(url_dataroot) + dataroot = None + for key, dataroot_dict in server_config.multi_dataset__dataroot.items(): + if dataroot_dict["base_url"] == url_dataroot: + dataroot = dataroot_dict["dataroot"] + dataset_key = key + break + if dataroot is None: raise DatasetAccessError(f"Invalid dataset {url_dataroot}/{dataset}") datapath = path_join(dataroot, dataset) @@ -114,7 +135,7 @@ def get_data_adaptor(url_dataroot=None, dataset=None): return common_rest.abort_and_log(HTTPStatus.BAD_REQUEST, "Invalid dataset NONE", loglevel=logging.INFO) cache_manager = current_app.matrix_data_cache_manager - return cache_manager.data_adaptor(datapath, config) + return cache_manager.data_adaptor(dataset_key, datapath, config) def rest_get_data_adaptor(func): @@ -123,9 +144,9 @@ def rest_get_data_adaptor(func): try: with get_data_adaptor(self.url_dataroot, dataset) as data_adaptor: return func(self, data_adaptor) - except DatasetAccessError: + except DatasetAccessError as e: return common_rest.abort_and_log( - HTTPStatus.BAD_REQUEST, f"Invalid dataset {dataset}", loglevel=logging.INFO, include_exc_info=True + e.status_code, f"Invalid dataset {dataset}: {e.message}", loglevel=logging.INFO, include_exc_info=True ) return wrapped_function @@ -138,9 +159,12 @@ def dataroot_test_index(): data += "

    Welcome to cellxgene

    " config = current_app.app_config + server_config = config.server_config datasets = [] - for url_dataroot, dataroot in config.multi_dataset__dataroot.items(): - locator = DataLocator(dataroot, region_name=config.data_locator__s3__region_name) + for dataroot_dict in server_config.multi_dataset__dataroot.values(): + dataroot = dataroot_dict["dataroot"] + url_dataroot = dataroot_dict["base_url"] + locator = DataLocator(dataroot, region_name=server_config.data_locator__s3__region_name) for fname in locator.ls(): location = path_join(dataroot, fname) try: @@ -164,12 +188,12 @@ def dataroot_test_index(): def dataroot_index(): # Handle the base url for the cellxgene server when running in multi dataset mode config = current_app.app_config - if not config.multi_dataset__index: + if not config.server_config.multi_dataset__index: abort(HTTPStatus.NOT_FOUND) - elif config.multi_dataset__index is True: + elif config.server_config.multi_dataset__index is True: return dataroot_test_index() else: - return redirect(config.multi_dataset__index) + return redirect(config.server_config.multi_dataset__index) class DatasetResource(Resource): @@ -184,33 +208,33 @@ class SchemaAPI(DatasetResource): @cache_control(public=True, max_age=ONE_WEEK) @rest_get_data_adaptor def get(self, data_adaptor): - return common_rest.schema_get(data_adaptor, current_app.annotations) + return common_rest.schema_get(data_adaptor) class ConfigAPI(DatasetResource): @cache_control(public=True, max_age=ONE_WEEK) @rest_get_data_adaptor def get(self, data_adaptor): - return common_rest.config_get(current_app.app_config, data_adaptor, current_app.annotations) + return common_rest.config_get(current_app.app_config, data_adaptor) class AnnotationsObsAPI(DatasetResource): @cache_control(public=True, max_age=ONE_WEEK) @rest_get_data_adaptor def get(self, data_adaptor): - return common_rest.annotations_obs_get(request, data_adaptor, current_app.annotations) + return common_rest.annotations_obs_get(request, data_adaptor) @cache_control(no_store=True) @rest_get_data_adaptor def put(self, data_adaptor): - return common_rest.annotations_obs_put(request, data_adaptor, current_app.annotations) + return common_rest.annotations_obs_put(request, data_adaptor) class AnnotationsVarAPI(DatasetResource): @cache_control(public=True, max_age=ONE_WEEK) @rest_get_data_adaptor def get(self, data_adaptor): - return common_rest.annotations_var_get(request, data_adaptor, current_app.annotations) + return common_rest.annotations_var_get(request, data_adaptor) class DataVarAPI(DatasetResource): @@ -283,29 +307,26 @@ class Server: self.app = Flask(__name__, static_folder="../common/web/static") self._before_adding_routes(self.app, app_config) self.app.json_encoder = Float32JSONEncoder - if app_config.server__server_timing_headers: + server_config = app_config.server_config + if server_config.app__server_timing_headers: ServerTiming(self.app, force_debug=True) # enable session data self.app.permanent_session_lifetime = datetime.timedelta(days=50 * 365) # Config - secret_key = app_config.server__flask_secret_key + secret_key = server_config.app__flask_secret_key self.app.config.update(SECRET_KEY=secret_key) self.app.register_blueprint(webbp) api_version = "/api/v0.2" - if app_config.single_dataset__datapath: - bp_api = Blueprint("api", __name__, url_prefix=api_version) - resources = get_api_resources(bp_api) - self.app.register_blueprint(resources.blueprint) - - else: + if app_config.is_multi_dataset(): # NOTE: These routes only allow the dataset to be in the directory # of the dataroot, and not a subdirectory. We may want to change # the route format at some point - for url_dataroot in app_config.multi_dataset__dataroot.keys(): + for dataroot_dict in server_config.multi_dataset__dataroot.values(): + url_dataroot = dataroot_dict["base_url"] bp_api = Blueprint( f"api_dataset_{url_dataroot}", __name__, url_prefix=f"/{url_dataroot}/" + api_version ) @@ -314,9 +335,13 @@ class Server: self.app.add_url_rule( f"/{url_dataroot}//", f"dataset_index_{url_dataroot}", - lambda dataset: dataset_index(url_dataroot, dataset), + lambda dataset, url_dataroot=url_dataroot: dataset_index(url_dataroot, dataset), methods=["GET"], ) - self.app.matrix_data_cache_manager = app_config.matrix_data_cache_manager - self.app.annotations = app_config.user_annotations + else: + bp_api = Blueprint("api", __name__, url_prefix=api_version) + resources = get_api_resources(bp_api) + self.app.register_blueprint(resources.blueprint) + + self.app.matrix_data_cache_manager = server_config.matrix_data_cache_manager self.app.app_config = app_config diff --git a/server/cli/launch.py b/server/cli/launch.py index a0cf1643..00900092 100644 --- a/server/cli/launch.py +++ b/server/cli/launch.py @@ -22,13 +22,13 @@ def annotation_args(func): @click.option( "--disable-annotations", is_flag=True, - default=not DEFAULT_CONFIG.user_annotations__enable, + default=not DEFAULT_CONFIG.default_dataset_config.user_annotations__enable, show_default=True, help="Disable user annotation of data.", ) @click.option( "--annotations-file", - default=DEFAULT_CONFIG.user_annotations__local_file_csv__file, + default=DEFAULT_CONFIG.default_dataset_config.user_annotations__local_file_csv__file, show_default=True, multiple=False, metavar="", @@ -37,7 +37,7 @@ def annotation_args(func): ) @click.option( "--annotations-dir", - default=DEFAULT_CONFIG.user_annotations__local_file_csv__directory, + default=DEFAULT_CONFIG.default_dataset_config.user_annotations__local_file_csv__directory, show_default=False, multiple=False, metavar="", @@ -47,13 +47,13 @@ def annotation_args(func): @click.option( "--experimental-annotations-ontology", is_flag=True, - default=DEFAULT_CONFIG.user_annotations__ontology__enable, + default=DEFAULT_CONFIG.default_dataset_config.user_annotations__ontology__enable, show_default=True, help="When creating annotations, optionally autocomplete names from ontology terms.", ) @click.option( "--experimental-annotations-ontology-obo", - default=DEFAULT_CONFIG.user_annotations__ontology__obo_location, + default=DEFAULT_CONFIG.default_dataset_config.user_annotations__ontology__obo_location, show_default=True, metavar="", help="Location of OBO file defining cell annotation autosuggest terms.", @@ -68,7 +68,7 @@ def annotation_args(func): def config_args(func): @click.option( "--max-category-items", - default=DEFAULT_CONFIG.presentation__max_categories, + default=DEFAULT_CONFIG.default_dataset_config.presentation__max_categories, metavar="", show_default=True, help="Will not display categories with more distinct values than specified.", @@ -83,7 +83,7 @@ def config_args(func): @click.option( "--diffexp-lfc-cutoff", "-de", - default=DEFAULT_CONFIG.diffexp__lfc_cutoff, + default=DEFAULT_CONFIG.default_dataset_config.diffexp__lfc_cutoff, show_default=True, metavar="", help="Minimum log fold change threshold for differential expression.", @@ -91,14 +91,14 @@ def config_args(func): @click.option( "--disable-diffexp", is_flag=True, - default=not DEFAULT_CONFIG.diffexp__enable, + default=not DEFAULT_CONFIG.default_dataset_config.diffexp__enable, show_default=False, help="Disable on-demand differential expression.", ) @click.option( "--embedding", "-e", - default=DEFAULT_CONFIG.embeddings__names, + default=DEFAULT_CONFIG.default_dataset_config.embeddings__names, multiple=True, show_default=False, metavar="", @@ -107,7 +107,7 @@ def config_args(func): @click.option( "--experimental-enable-reembedding", is_flag=True, - default=DEFAULT_CONFIG.embeddings__enable_reembedding, + default=DEFAULT_CONFIG.default_dataset_config.embeddings__enable_reembedding, show_default=False, hidden=True, help="Enable experimental on-demand re-embedding using UMAP. WARNING: may be very slow.", @@ -123,14 +123,14 @@ def dataset_args(func): @click.option( "--obs-names", "-obs", - default=DEFAULT_CONFIG.single_dataset__obs_names, + default=DEFAULT_CONFIG.server_config.single_dataset__obs_names, metavar="", help="Name of annotation field to use for observations. If not specified cellxgene will use the the obs index.", ) @click.option( "--var-names", "-var", - default=DEFAULT_CONFIG.single_dataset__var_names, + default=DEFAULT_CONFIG.server_config.single_dataset__var_names, metavar="", help="Name of annotation to use for variables. If not specified cellxgene will use the the var index.", ) @@ -138,20 +138,20 @@ def dataset_args(func): "--backed", "-b", is_flag=True, - default=DEFAULT_CONFIG.adaptor__anndata_adaptor__backed, + default=DEFAULT_CONFIG.server_config.adaptor__anndata_adaptor__backed, show_default=False, help="Load anndata in file-backed mode. " "This may save memory, but may result in slower overall performance.", ) @click.option( "--title", "-t", - default=DEFAULT_CONFIG.single_dataset__title, + default=DEFAULT_CONFIG.server_config.single_dataset__title, metavar="", help="Title to display. If omitted will use file name.", ) @click.option( "--about", - default=DEFAULT_CONFIG.single_dataset__about, + default=DEFAULT_CONFIG.server_config.single_dataset__about, metavar="", help="URL providing more information about the dataset (hint: must be a fully specified absolute URL).", ) @@ -167,7 +167,7 @@ def server_args(func): "--debug", "-d", is_flag=True, - default=DEFAULT_CONFIG.server__debug, + default=DEFAULT_CONFIG.server_config.app__debug, show_default=True, help="Run in debug mode. This is helpful for cellxgene developers, " "or when you want more information about an error condition.", @@ -176,7 +176,7 @@ def server_args(func): "--verbose", "-v", is_flag=True, - default=DEFAULT_CONFIG.server__verbose, + default=DEFAULT_CONFIG.server_config.app__verbose, show_default=True, help="Provide verbose output, including warnings and all server requests.", ) @@ -184,7 +184,7 @@ def server_args(func): "--port", "-p", metavar="", - default=DEFAULT_CONFIG.server__port, + default=DEFAULT_CONFIG.server_config.app__port, type=int, show_default=True, help="Port to run server on. If not specified cellxgene will find an available port.", @@ -192,14 +192,14 @@ def server_args(func): @click.option( "--host", metavar="", - default=DEFAULT_CONFIG.server__host, + default=DEFAULT_CONFIG.server_config.app__host, show_default=False, help="Host IP address. By default cellxgene will use localhost (e.g. 127.0.0.1).", ) @click.option( "--scripts", "-s", - default=DEFAULT_CONFIG.server__scripts, + default=DEFAULT_CONFIG.default_dataset_config.app__scripts, multiple=True, metavar="", help="Additional script files to include in HTML page. If not specified, " @@ -220,7 +220,7 @@ def launch_args(func): @server_args @click.option( "--dataroot", - default=DEFAULT_CONFIG.multi_dataset__dataroot, + default=DEFAULT_CONFIG.server_config.multi_dataset__dataroot, metavar="", help="Enable cellxgene to serve multiple files. Supply path (local directory or URL)" " to folder containing H5AD and/or CXG datasets.", @@ -232,7 +232,7 @@ def launch_args(func): "-o", "open_browser", is_flag=True, - default=DEFAULT_CONFIG.server__open_browser, + default=DEFAULT_CONFIG.server_config.app__open_browser, show_default=True, help="Open web browser after launch.", ) @@ -296,7 +296,7 @@ class CliLaunchServer(Server): "application/octet-stream", ] Compress(app) - if app_config.server__debug: + if app_config.server_config.app__debug: CORS(app, supports_credentials=True) @@ -362,6 +362,7 @@ def launch( # app config app_config = AppConfig() + server_config = app_config.server_config try: if config_file: @@ -370,19 +371,22 @@ def launch( # Determine which config options were give on the command line. # Those will override the ones provided in the config file (if provided). cli_config = AppConfig() - cli_config.update( - server__verbose=verbose, - server__debug=debug, - server__host=host, - server__port=port, - server__scripts=scripts, - server__open_browser=open_browser, + cli_config.update_server_config( + app__verbose=verbose, + app__debug=debug, + app__host=host, + app__port=port, + app__open_browser=open_browser, single_dataset__datapath=datapath, single_dataset__title=title, single_dataset__about=about, single_dataset__obs_names=obs_names, single_dataset__var_names=var_names, multi_dataset__dataroot=dataroot, + adaptor__anndata_adaptor__backed=backed, + ) + cli_config.update_default_dataset_config( + app__scripts=scripts, user_annotations__enable=not disable_annotations, user_annotations__local_file_csv__file=annotations_file, user_annotations__local_file_csv__directory=annotations_dir, @@ -394,13 +398,15 @@ def launch( embeddings__enable_reembedding=experimental_enable_reembedding, diffexp__enable=not disable_diffexp, diffexp__lfc_cutoff=diffexp_lfc_cutoff, - adaptor__anndata_adaptor__backed=backed, ) - diff = cli_config.changes_from_default() - changes = {} - for key, val, defval in diff: - changes[key] = val - app_config.update(**changes) + + diff = cli_config.server_config.changes_from_default() + changes = {key: val for key, val, _ in diff} + app_config.update_server_config(**changes) + + diff = cli_config.default_dataset_config.changes_from_default() + changes = {key: val for key, val, _ in diff} + app_config.update_default_dataset_config(**changes) # process the configuration # any errors will be thrown as an exception. @@ -410,8 +416,8 @@ def launch( click.echo("[cellxgene] " + message) # Use a default secret if one is not provided - if not app_config.server__flask_secret_key: - app_config.update(server__flask_secret_key="SparkleAndShine") + if not server_config.app__flask_secret_key: + app_config.update_server_config(app__flask_secret_key="SparkleAndShine") app_config.complete_config(messagefn) @@ -423,12 +429,12 @@ def launch( # create the server server = CliLaunchServer(app_config) - if not app_config.server__verbose: + if not server_config.app__verbose: log = logging.getLogger("werkzeug") log.setLevel(logging.ERROR) - cellxgene_url = f"http://{app_config.server__host}:{app_config.server__port}" - if app_config.server__open_browser: + cellxgene_url = f"http://{app_config.server_config.app__host}:{app_config.server_config.app__port}" + if server_config.app__open_browser: click.echo(f"[cellxgene] Launching! Opening your browser to {cellxgene_url} now.") webbrowser.open(cellxgene_url) else: @@ -436,16 +442,16 @@ def launch( click.echo("[cellxgene] Type CTRL-C at any time to exit.") - if not app_config.server__verbose: + if not server_config.app__verbose: f = open(devnull, "w") sys.stdout = f try: server.app.run( - host=app_config.server__host, - debug=app_config.server__debug, - port=app_config.server__port, - threaded=not app_config.server__debug, + host=server_config.app__host, + debug=server_config.app__debug, + port=server_config.app__port, + threaded=not server_config.app__debug, use_debugger=False, use_reloader=False, ) diff --git a/server/common/app_config.py b/server/common/app_config.py index d0510b6a..7e8a3635 100644 --- a/server/common/app_config.py +++ b/server/common/app_config.py @@ -1,4 +1,4 @@ -from server import __version__ as cellxgene_version +from server import display_version as cellxgene_display_version from flatten_dict import flatten, unflatten import os from os.path import splitext, basename, isdir @@ -17,7 +17,7 @@ from server.common.utils import custom_format_warning import server.compute.diffexp_cxg as diffexp_tiledb from server.common.data_locator import discover_s3_region_name -DEFAULT_SERVER_PORT = int(os.environ.get("CXG_SERVER_PORT", "5005")) +DEFAULT_SERVER_PORT = 5005 # anything bigger than this will generate a special message BIG_FILE_SIZE_THRESHOLD = 100 * 2 ** 20 # 100MB @@ -38,170 +38,110 @@ class AppFeature(object): class AppConfig(object): + """AppConfig stores all the configuration for cellxgene. The configuration is divided into two main parts: + server attributes, and dataset attributes. The server_config contains attributes that refer to the server process + as a whole. The default_dataset_config referes to attributes that are associated with the features and + presentations of a dataset. The dataset config attributes can be overridden depending on the url by which the + dataset was accessed. These are stored in dataroot_config. + AppConfig has methods to initialize, modify, and access the configuration. + """ + def __init__(self): + # the default configuration (see default_config.py) self.default_config = get_default_config() - self.attr_checked = {k: False for k in self.__mapping(self.default_config).keys()} - - dc = self.default_config - try: - self.server__verbose = dc["server"]["verbose"] - self.server__debug = dc["server"]["debug"] - self.server__host = dc["server"]["host"] - self.server__port = dc["server"]["port"] - self.server__scripts = dc["server"]["scripts"] - self.server__inline_scripts = dc["server"]["inline_scripts"] - self.server__open_browser = dc["server"]["open_browser"] - self.server__about_legal_tos = dc["server"]["about_legal_tos"] - self.server__about_legal_privacy = dc["server"]["about_legal_privacy"] - self.server__force_https = dc["server"]["force_https"] - self.server__flask_secret_key = dc["server"]["flask_secret_key"] - self.server__generate_cache_control_headers = dc["server"]["generate_cache_control_headers"] - self.server__server_timing_headers = dc["server"]["server_timing_headers"] - self.server__csp_directives = dc["server"]["csp_directives"] - - self.multi_dataset__dataroot = dc["multi_dataset"]["dataroot"] - self.multi_dataset__index = dc["multi_dataset"]["index"] - self.multi_dataset__allowed_matrix_types = dc["multi_dataset"]["allowed_matrix_types"] - self.multi_dataset__matrix_cache__max_datasets = dc["multi_dataset"]["matrix_cache"]["max_datasets"] - self.multi_dataset__matrix_cache__timelimit_s = dc["multi_dataset"]["matrix_cache"]["timelimit_s"] - - self.single_dataset__datapath = dc["single_dataset"]["datapath"] - self.single_dataset__obs_names = dc["single_dataset"]["obs_names"] - self.single_dataset__var_names = dc["single_dataset"]["var_names"] - self.single_dataset__about = dc["single_dataset"]["about"] - self.single_dataset__title = dc["single_dataset"]["title"] - - self.user_annotations__enable = dc["user_annotations"]["enable"] - self.user_annotations__type = dc["user_annotations"]["type"] - self.user_annotations__local_file_csv__directory = dc["user_annotations"]["local_file_csv"]["directory"] - self.user_annotations__local_file_csv__file = dc["user_annotations"]["local_file_csv"]["file"] - self.user_annotations__ontology__enable = dc["user_annotations"]["ontology"]["enable"] - self.user_annotations__ontology__obo_location = dc["user_annotations"]["ontology"]["obo_location"] - - self.presentation__max_categories = dc["presentation"]["max_categories"] - self.presentation__custom_colors = dc["presentation"]["custom_colors"] - - self.embeddings__names = dc["embeddings"]["names"] - self.embeddings__enable_reembedding = dc["embeddings"]["enable_reembedding"] - - self.diffexp__enable = dc["diffexp"]["enable"] - self.diffexp__lfc_cutoff = dc["diffexp"]["lfc_cutoff"] - self.diffexp__top_n = dc["diffexp"]["top_n"] - self.diffexp__alg_cxg__max_workers = dc["diffexp"]["alg_cxg"]["max_workers"] - self.diffexp__alg_cxg__cpu_multiplier = dc["diffexp"]["alg_cxg"]["cpu_multiplier"] - self.diffexp__alg_cxg__target_workunit = dc["diffexp"]["alg_cxg"]["target_workunit"] - - self.data_locator__s3__region_name = dc["data_locator"]["s3"]["region_name"] - - self.adaptor__cxg_adaptor__tiledb_ctx = dc["adaptor"]["cxg_adaptor"]["tiledb_ctx"] - self.adaptor__anndata_adaptor__backed = dc["adaptor"]["anndata_adaptor"]["backed"] - - self.limits__diffexp_cellcount_max = dc["limits"]["diffexp_cellcount_max"] - self.limits__column_request_max = dc["limits"]["column_request_max"] - - except KeyError as e: - raise ConfigurationError(f"Unexpected config: {str(e)}") - - # The annotation object is created during complete_config and stored here. - self.user_annotations = None - - # The matrix data cache manager is created during the complete_config and stored here. - self.matrix_data_cache_manager = None + # the server configuration + self.server_config = ServerConfig(self, self.default_config["server"]) + # the dataset config, unless overridden by an entry in dataroot_config + self.default_dataset_config = DatasetConfig(None, self, self.default_config["dataset"]) + # a dictionary of keys to DatasetConfig objects. Each key must exist in the multi_dataset__dataroot + # attribute of the server_config. + self.dataroot_config = {} # Set to true when config_completed is called self.is_completed = False + def get_dataset_config(self, dataroot_key): + if self.server_config.single_dataset__datapath: + return self.default_dataset_config + else: + return self.dataroot_config.get(dataroot_key, self.default_dataset_config) + def check_config(self): + """Verify all the attributes have been checked""" if not self.is_completed: raise ConfigurationError("The configuration has not been completed") - mapping = self.__mapping(self.default_config) - for key in mapping.keys(): - if not self.attr_checked[key]: - raise ConfigurationError(f"The attr '{key}' has not been checked") + self.server_config.check_config() + self.default_dataset_config.check_config() + for dataset_config in self.dataroot_config.values(): + dataset_config.check_config() - def __mapping(self, config): - """Create a mapping from attribute names to (location in the config tree, value)""" - dc = copy.deepcopy(config) - mapping = {} + def update_server_config(self, **kw): + self.server_config.update(**kw) + self.is_complete = False - # special cases where the value could be a dict. - # If its value is not None, the entry is added to the mapping, and not included - # in the flattening below. - dictval_cases = [ - ("adaptor", "cxg_adaptor", "tiledb_ctx"), - ("server", "csp_directives"), - ("multi_dataset", "dataroot"), - ] - for dictval_case in dictval_cases: - cur = dc - for part in dictval_case[:-1]: - cur = cur.get(part, {}) - val = cur.get(dictval_case[-1]) - if val is not None: - key = "__".join(dictval_case) - mapping[key] = (dictval_case, val) - del cur[dictval_case[-1]] - - flat_config = flatten(dc) - for key, value in flat_config.items(): - # name of the attribute - attr = "__".join(key) - mapping[attr] = (key, value) - - return mapping + def update_default_dataset_config(self, **kw): + self.default_dataset_config.update(**kw) + # update all the other dataset configs, if any + for value in self.dataroot_config.values(): + value.update(**kw) + self.is_complete = False def update_from_config_file(self, config_file): with open(config_file) as fyaml: config = yaml.load(fyaml, Loader=yaml.FullLoader) - mapping = self.__mapping(config) - for attr, (key, value) in mapping.items(): - if not hasattr(self, attr): - raise ConfigurationError(f"Unknown key from config file: {key}") - try: - setattr(self, attr, value) - except KeyError: - raise ConfigurationError(f"Unable to set config attribute: {key}") + self.server_config.update_from_config(config["server"], "server") + self.default_dataset_config.update_from_config(config["dataset"], "dataset") - self.attr_checked[attr] = False + per_dataset_config = config.get("per_dataset_config", {}) + for key, dataroot_config in per_dataset_config.items(): + self.add_dataroot_config(key, **dataroot_config) - self.is_completed = False + self.is_complete = False def write_config(self, config_file): """output the config to a yaml file""" - mapping = self.__mapping(self.default_config) - for attrname in mapping.keys(): - mapping[attrname] = getattr(self, attrname) - config = unflatten(mapping, splitter=lambda key: key.split("__")) + server = self.server_config.create_mapping(self.server_config.default_config) + dataset = self.default_dataset_config.create_mapping(self.default_dataset_config.default_config) + config = dict(server={}, dataset={}) + for attrname in server.keys(): + config["server__" + attrname] = getattr(self.server_config, attrname) + for attrname in dataset.keys(): + config["dataset__" + attrname] = getattr(self.default_dataset_config, attrname) + if self.dataroot_config: + config["per_dataset_config"] = {} + for dataroot_tag, dataroot_config in self.dataroot_config.items(): + dataset = dataroot_config.create_mapping(dataroot_config.default_config) + for attrname in dataset.keys(): + config[f"per_dataset_config__{dataroot_tag}__" + attrname] = getattr(dataroot_config, attrname) + + config = unflatten(config, splitter=lambda key: key.split("__")) yaml.dump(config, open(config_file, "w")) - def update(self, **kw): - for key, value in kw.items(): - if not hasattr(self, key): - raise ConfigurationError(f"unknown config parameter {key}.") - try: - if type(value) == tuple: - # convert tuple values to list values - value = list(value) - setattr(self, key, value) - except KeyError: - raise ConfigurationError(f"Unable to set config parameter {key}.") - - self.attr_checked[key] = False - - self.is_completed = False - def changes_from_default(self): """Return all the attribute that are different from the default""" - mapping = self.__mapping(self.default_config) - diff = [] - for attrname, (key, defval) in mapping.items(): - curval = getattr(self, attrname) - if curval != defval: - diff.append((attrname, curval, defval)) + diff_server = self.server_config.changes_from_default() + diff_dataset = self.default_dataset_config.changes_from_default() + diff = dict(server=diff_server, dataset=diff_dataset) return diff + def add_dataroot_config(self, dataroot_tag, **kw): + """Create a new dataset config object based on the default dataset config, and kw parameters""" + if dataroot_tag in self.dataroot_config: + raise ConfigurationError(f"dataroot config already exists: {dataroot_tag}") + if type(self.server_config.multi_dataset__dataroot) != dict: + raise ConfigurationError("The server__multi_dataset__dataroot must be a dictionary") + if dataroot_tag not in self.server_config.multi_dataset__dataroot: + raise ConfigurationError(f"The dataroot_tag ({dataroot_tag}) not found in server__multi_dataset__dataroot") + + self.is_completed = False + self.dataroot_config[dataroot_tag] = DatasetConfig(dataroot_tag, self, self.default_config["dataset"]) + flat_config = self.default_dataset_config.create_mapping(self.default_dataset_config.default_config) + config = {key: value[1] for key, value in flat_config.items()} + self.dataroot_config[dataroot_tag].update(**config) + self.dataroot_config[dataroot_tag].update_from_config(kw, dataroot_tag) + def complete_config(self, messagefn=None): """The configure options are checked, and any additional setup based on the config parameters is done""" @@ -218,22 +158,149 @@ class AppConfig(object): # messages we can give correct context for attributes with bad value. context = dict(messagefn=messagefn) - self.handle_server(context) - self.handle_adaptor(context) - self.handle_data_locator(context) - self.handle_adaptor(context) # may depend on data_locator - self.handle_presentation(context) - self.handle_single_dataset(context) # may depend on adaptor - self.handle_multi_dataset(context) # may depend on adaptor - self.handle_user_annotations(context) - self.handle_embeddings(context) - self.handle_diffexp(context) - self.handle_limits(context) + self.server_config.complete_config(context) + self.default_dataset_config.complete_config(context) + for dataroot_config in self.dataroot_config.values(): + dataroot_config.complete_config(context) self.is_completed = True self.check_config() - def __check_attr(self, attrname, vtype): + def get_matrix_data_cache_manager(self): + return self.server_config.matrix_data_cache_manager + + def is_multi_dataset(self): + return self.server_config.multi_dataset__dataroot is not None + + def get_title(self, data_adaptor): + return ( + self.server_config.single_dataset__title + if self.server_config.single_dataset__title + else data_adaptor.get_title() + ) + + def get_about(self, data_adaptor): + return ( + self.server_config.single_dataset__about + if self.server_config.single_dataset__about + else data_adaptor.get_about() + ) + + def get_client_config(self, data_adaptor): + """ + Return the configuration as required by the /config REST route + """ + + server_config = self.server_config + dataset_config = data_adaptor.dataset_config + annotation = dataset_config.user_annotations + + # FIXME The current set of config is not consistently presented: + # we have camalCase, hyphen-text, and underscore_text + + # make sure the configuration has been checked. + self.check_config() + + # features + features = [f.todict() for f in data_adaptor.get_features(annotation)] + + # display_names + title = self.get_title(data_adaptor) + about = self.get_about(data_adaptor) + + display_names = dict(engine=data_adaptor.get_name(), dataset=title) + + # library_versions + library_versions = {} + library_versions.update(data_adaptor.get_library_versions()) + library_versions["cellxgene"] = cellxgene_display_version + + # links + links = {"about-dataset": about} + + # parameters + parameters = { + "layout": dataset_config.embeddings__names, + "max-category-items": dataset_config.presentation__max_categories, + "obs_names": server_config.single_dataset__obs_names, + "var_names": server_config.single_dataset__var_names, + "diffexp_lfc_cutoff": dataset_config.diffexp__lfc_cutoff, + "backed": server_config.adaptor__anndata_adaptor__backed, + "disable-diffexp": not dataset_config.diffexp__enable, + "enable-reembedding": dataset_config.embeddings__enable_reembedding, + "annotations": False, + "annotations_file": None, + "annotations_dir": None, + "annotations_cell_ontology_enabled": False, + "annotations_cell_ontology_obopath": None, + "annotations_cell_ontology_terms": None, + "custom_colors": dataset_config.presentation__custom_colors, + "diffexp-may-be-slow": False, + "about_legal_tos": dataset_config.app__about_legal_tos, + "about_legal_privacy": dataset_config.app__about_legal_privacy, + } + + data_adaptor.update_parameters(parameters) + if annotation: + annotation.update_parameters(parameters, data_adaptor) + + # gather it all together + c = {} + config = c["config"] = {} + config["features"] = features + config["displayNames"] = display_names + config["library_versions"] = library_versions + config["links"] = links + config["parameters"] = parameters + config["limits"] = { + "column_request_max": server_config.limits__column_request_max, + "diffexp_cellcount_max": server_config.limits__diffexp_cellcount_max, + } + + return c + + +class BaseConfig(object): + """This class handles the mechanics of updating and checking attributes. + Derived classes are expected to store the actual attributes""" + + def __init__(self, app_config, default_config, dictval_cases={}): + # reference back to the app_config + self.app_config = app_config + # the complete set of attribute and their default values (unflattened) + self.default_config = default_config + # attributes where the value may be a dict (and therefore are not flattened) + self.dictval_cases = dictval_cases + # used to make sure every attribute value is checked + self.attr_checked = {k: False for k in self.create_mapping(default_config).keys()} + + def create_mapping(self, config): + """Create a mapping from attribute names to (location in the config tree, value)""" + dc = copy.deepcopy(config) + mapping = {} + + # special cases where the value could be a dict. + # If its value is not None, the entry is added to the mapping, and not included + # in the flattening below. + for dictval_case in self.dictval_cases: + cur = dc + for part in dictval_case[:-1]: + cur = cur.get(part, {}) + val = cur.get(dictval_case[-1]) + if val is not None: + key = "__".join(dictval_case) + mapping[key] = (dictval_case, val) + del cur[dictval_case[-1]] + + flat_config = flatten(dc) + for key, value in flat_config.items(): + # name of the attribute + attr = "__".join(key) + mapping[attr] = (key, value) + + return mapping + + def check_attr(self, attrname, vtype): val = getattr(self, attrname) if type(vtype) in (list, tuple): if type(val) not in vtype: @@ -250,48 +317,164 @@ class AppConfig(object): self.attr_checked[attrname] = True - def handle_server(self, context): - self.__check_attr("server__verbose", bool) - self.__check_attr("server__debug", bool) - self.__check_attr("server__host", str) - self.__check_attr("server__port", (type(None), int)) - self.__check_attr("server__scripts", list) - self.__check_attr("server__inline_scripts", list) - self.__check_attr("server__open_browser", bool) - self.__check_attr("server__force_https", bool) - self.__check_attr("server__flask_secret_key", (type(None), str)) - self.__check_attr("server__generate_cache_control_headers", bool) - self.__check_attr("server__about_legal_tos", (type(None), str)) - self.__check_attr("server__about_legal_privacy", (type(None), str)) - self.__check_attr("server__server_timing_headers", bool) - self.__check_attr("server__csp_directives", (type(None), dict)) + def check_config(self): + mapping = self.create_mapping(self.default_config) + for key in mapping.keys(): + if not self.attr_checked[key]: + raise ConfigurationError(f"The attr '{key}' has not been checked") - if self.server__port: - if not is_port_available(self.server__host, self.server__port): - raise ConfigurationError( - f"The port selected {self.server__port} is in use, please configure an open port." - ) + def update(self, **kw): + for key, value in kw.items(): + if not hasattr(self, key): + raise ConfigurationError(f"unknown config parameter {key}.") + try: + if type(value) == tuple: + # convert tuple values to list values + value = list(value) + setattr(self, key, value) + except KeyError: + raise ConfigurationError(f"Unable to set config parameter {key}.") + + self.attr_checked[key] = False + + def update_from_config(self, config, prefix): + mapping = self.create_mapping(config) + for attr, (key, value) in mapping.items(): + if not hasattr(self, attr): + raise ConfigurationError(f"Unknown key from config file: {prefix}__{attr}") + try: + setattr(self, attr, value) + except KeyError: + raise ConfigurationError(f"Unable to set config attribute: {prefix}__{attr}") + + self.attr_checked[attr] = False + + def changes_from_default(self): + """Return all the attribute that are different from the default""" + mapping = self.create_mapping(self.default_config) + diff = [] + for attrname, (key, defval) in mapping.items(): + curval = getattr(self, attrname) + if curval != defval: + diff.append((attrname, curval, defval)) + return diff + + +class ServerConfig(BaseConfig): + """Manages the config attribute associated with the server.""" + + def __init__(self, app_config, default_config): + dictval_cases = [ + ("app", "csp_directives"), + ("adaptor", "cxg_adaptor", "tiledb_ctx"), + ("multi_dataset", "dataroot"), + ] + super().__init__(app_config, default_config, dictval_cases) + + dc = default_config + try: + self.app__verbose = dc["app"]["verbose"] + self.app__debug = dc["app"]["debug"] + self.app__host = dc["app"]["host"] + self.app__port = dc["app"]["port"] + self.app__open_browser = dc["app"]["open_browser"] + self.app__force_https = dc["app"]["force_https"] + self.app__flask_secret_key = dc["app"]["flask_secret_key"] + self.app__generate_cache_control_headers = dc["app"]["generate_cache_control_headers"] + self.app__server_timing_headers = dc["app"]["server_timing_headers"] + self.app__csp_directives = dc["app"]["csp_directives"] + + self.multi_dataset__dataroot = dc["multi_dataset"]["dataroot"] + self.multi_dataset__index = dc["multi_dataset"]["index"] + self.multi_dataset__allowed_matrix_types = dc["multi_dataset"]["allowed_matrix_types"] + self.multi_dataset__matrix_cache__max_datasets = dc["multi_dataset"]["matrix_cache"]["max_datasets"] + self.multi_dataset__matrix_cache__timelimit_s = dc["multi_dataset"]["matrix_cache"]["timelimit_s"] + + self.single_dataset__datapath = dc["single_dataset"]["datapath"] + self.single_dataset__obs_names = dc["single_dataset"]["obs_names"] + self.single_dataset__var_names = dc["single_dataset"]["var_names"] + self.single_dataset__about = dc["single_dataset"]["about"] + self.single_dataset__title = dc["single_dataset"]["title"] + + self.diffexp__alg_cxg__max_workers = dc["diffexp"]["alg_cxg"]["max_workers"] + self.diffexp__alg_cxg__cpu_multiplier = dc["diffexp"]["alg_cxg"]["cpu_multiplier"] + self.diffexp__alg_cxg__target_workunit = dc["diffexp"]["alg_cxg"]["target_workunit"] + + self.data_locator__s3__region_name = dc["data_locator"]["s3"]["region_name"] + + self.adaptor__cxg_adaptor__tiledb_ctx = dc["adaptor"]["cxg_adaptor"]["tiledb_ctx"] + self.adaptor__anndata_adaptor__backed = dc["adaptor"]["anndata_adaptor"]["backed"] + + self.limits__diffexp_cellcount_max = dc["limits"]["diffexp_cellcount_max"] + self.limits__column_request_max = dc["limits"]["column_request_max"] + + except KeyError as e: + raise ConfigurationError(f"Unexpected config: {str(e)}") + + # The matrix data cache manager is created during the complete_config and stored here. + self.matrix_data_cache_manager = None + + def complete_config(self, context): + self.handle_app(context) + self.handle_data_locator(context) + self.handle_adaptor(context) # may depend on data_locator + self.handle_single_dataset(context) # may depend on adaptor + self.handle_multi_dataset(context) # may depend on adaptor + self.handle_diffexp(context) + self.handle_limits(context) + + self.check_config() + + def handle_app(self, context): + self.check_attr("app__verbose", bool) + self.check_attr("app__debug", bool) + self.check_attr("app__host", str) + self.check_attr("app__port", (type(None), int)) + self.check_attr("app__open_browser", bool) + self.check_attr("app__force_https", bool) + self.check_attr("app__flask_secret_key", (type(None), str)) + self.check_attr("app__generate_cache_control_headers", bool) + self.check_attr("app__server_timing_headers", bool) + self.check_attr("app__csp_directives", (type(None), dict)) + + if self.app__port: + try: + if not is_port_available(self.app__host, self.app__port): + raise ConfigurationError( + f"The port selected {self.app__port} is in use, please configure an open port." + ) + except OverflowError: + raise ConfigurationError(f"Invalid port: {self.app__port}") else: - self.server__port = find_available_port(self.server__host, DEFAULT_SERVER_PORT) + try: + default_server_port = int(os.environ.get("CXG_SERVER_PORT", DEFAULT_SERVER_PORT)) + except ValueError: + raise ConfigurationError( + "Invalid port from environment variable CXG_SERVER_PORT: " + os.environ.get("CXG_SERVER_PORT") + ) + try: + self.app__port = find_available_port(self.app__host, default_server_port) + except OverflowError: + raise ConfigurationError(f"Invalid port: {default_server_port}") - if self.server__debug: + if self.app__debug: context["messagefn"]("in debug mode, setting verbose=True and open_browser=False") - self.server__verbose = True - self.server__open_browser = False + self.app__verbose = True + self.app__open_browser = False else: warnings.formatwarning = custom_format_warning - if not self.server__verbose: + if not self.app__verbose: sys.tracebacklimit = 0 # secret key: # first, from CXG_SECRET_KEY environment variable # second, from config file - self.server__flask_secret_key = os.environ.get("CXG_SECRET_KEY", self.server__flask_secret_key) + self.app__flask_secret_key = os.environ.get("CXG_SECRET_KEY", self.app__flask_secret_key) # CSP Directives are a dict of string: list(string) or string: string - if self.server__csp_directives is not None: - for k, v in self.server__csp_directives.items(): + if self.app__csp_directives is not None: + for k, v in self.app__csp_directives.items(): if not isinstance(k, str): raise ConfigurationError("CSP directive names must be a string.") if isinstance(v, list): @@ -301,26 +484,15 @@ class AppConfig(object): elif not isinstance(v, str): raise ConfigurationError("CSP directive value must be a string or list of strings.") - # scripts can be string (filename) or dict (attributes). Convert string to dict. - scripts = [] - for s in self.server__scripts: - if isinstance(s, str): - scripts.append({"src": s}) - elif isinstance(s, dict) and isinstance(s["src"], str): - scripts.append(s) - else: - raise ConfigurationError("Scripts must be string or dict") - self.server__scripts = scripts - def handle_data_locator(self, context): - self.__check_attr("data_locator__s3__region_name", (type(None), bool, str)) + self.check_attr("data_locator__s3__region_name", (type(None), bool, str)) if self.data_locator__s3__region_name is True: path = self.single_dataset__datapath or self.multi_dataset__dataroot if type(path) == dict: # if multi_dataset__dataroot is a dict, then use the first key # that is in s3. NOTE: it is not supported to have dataroots # in different regions. - paths = path.values() + paths = [val.get("dataroot") for val in path.values()] for path in paths: if path.startswith("s3://"): break @@ -332,16 +504,12 @@ class AppConfig(object): region_name = None self.data_locator__s3__region_name = region_name - def handle_presentation(self, context): - self.__check_attr("presentation__max_categories", int) - self.__check_attr("presentation__custom_colors", bool) - def handle_single_dataset(self, context): - self.__check_attr("single_dataset__datapath", (str, type(None))) - self.__check_attr("single_dataset__title", (str, type(None))) - self.__check_attr("single_dataset__about", (str, type(None))) - self.__check_attr("single_dataset__obs_names", (str, type(None))) - self.__check_attr("single_dataset__var_names", (str, type(None))) + self.check_attr("single_dataset__datapath", (str, type(None))) + self.check_attr("single_dataset__title", (str, type(None))) + self.check_attr("single_dataset__about", (str, type(None))) + self.check_attr("single_dataset__obs_names", (str, type(None))) + self.check_attr("single_dataset__var_names", (str, type(None))) if self.single_dataset__datapath is None: if self.multi_dataset__dataroot is None: @@ -357,7 +525,7 @@ class AppConfig(object): self.matrix_data_cache_manager = MatrixDataCacheManager(max_cached=1, timelimit_s=None) # preload this data set - matrix_data_loader = MatrixDataLoader(self.single_dataset__datapath, app_config=self) + matrix_data_loader = MatrixDataLoader(self.single_dataset__datapath, app_config=self.app_config) try: matrix_data_loader.pre_load_validation() except DatasetAccessError as e: @@ -388,26 +556,46 @@ class AppConfig(object): ) def handle_multi_dataset(self, context): - self.__check_attr("multi_dataset__dataroot", (type(None), dict, str)) - self.__check_attr("multi_dataset__index", (type(None), bool, str)) - self.__check_attr("multi_dataset__allowed_matrix_types", list) - self.__check_attr("multi_dataset__matrix_cache__max_datasets", int) - self.__check_attr("multi_dataset__matrix_cache__timelimit_s", (type(None), int, float)) + self.check_attr("multi_dataset__dataroot", (type(None), dict, str)) + self.check_attr("multi_dataset__index", (type(None), bool, str)) + self.check_attr("multi_dataset__allowed_matrix_types", list) + self.check_attr("multi_dataset__matrix_cache__max_datasets", int) + self.check_attr("multi_dataset__matrix_cache__timelimit_s", (type(None), int, float)) if self.multi_dataset__dataroot is None: return if type(self.multi_dataset__dataroot) == str: - self.multi_dataset__dataroot = dict(d=self.multi_dataset__dataroot) + default_dict = dict(base_url="d", dataroot=self.multi_dataset__dataroot) + self.multi_dataset__dataroot = dict(d=default_dict) - for key in self.multi_dataset__dataroot.keys(): - # sanity check for well formed keys - if type(key) != str: - raise ConfigurationError(f"error in multi_dataset__dataroot {key}") - if quote_plus(key) != key: - raise ConfigurationError(f"error in multi_dataset__dataroot {key}") - if os.path.split(os.path.normpath(key))[-1] != key: - raise ConfigurationError(f"error in multi_dataset__dataroot {key}") + for tag, dataroot_dict in self.multi_dataset__dataroot.items(): + if "base_url" not in dataroot_dict: + raise ConfigurationError(f"error in multi_dataset__dataroot: missing base_url for tag {tag}") + if "dataroot" not in dataroot_dict: + raise ConfigurationError(f"error in multi_dataset__dataroot: missing dataroot, for tag {tag}") + + base_url = dataroot_dict["base_url"] + + # sanity check for well formed base urls + bad = False + if type(base_url) != str: + bad = True + elif os.path.normpath(base_url) != base_url: + bad = True + else: + base_url_parts = base_url.split("/") + if [quote_plus(part) for part in base_url_parts] != base_url_parts: + bad = True + if ".." in base_url_parts: + bad = True + if bad: + raise ConfigurationError(f"error in multi_dataset__dataroot base_url {base_url} for tag {tag}") + + # verify all the base_urls are unique + base_urls = [d["base_url"] for d in self.multi_dataset__dataroot.values()] + if len(base_urls) > len(set(base_urls)): + raise ConfigurationError("error in multi_dataset__dataroot: base_urls must be unique") # error checking for mtype in self.multi_dataset__allowed_matrix_types: @@ -423,13 +611,114 @@ class AppConfig(object): timelimit_s=self.multi_dataset__matrix_cache__timelimit_s, ) + def handle_diffexp(self, context): + self.check_attr("diffexp__alg_cxg__max_workers", (str, int)) + self.check_attr("diffexp__alg_cxg__cpu_multiplier", int) + self.check_attr("diffexp__alg_cxg__target_workunit", int) + + max_workers = self.diffexp__alg_cxg__max_workers + cpu_multiplier = self.diffexp__alg_cxg__cpu_multiplier + cpu_count = os.cpu_count() + max_workers = min(max_workers, cpu_multiplier * cpu_count) + diffexp_tiledb.set_config(max_workers, self.diffexp__alg_cxg__target_workunit) + + def handle_adaptor(self, context): + # cxg + self.check_attr("adaptor__cxg_adaptor__tiledb_ctx", dict) + regionkey = "vfs.s3.region" + if regionkey not in self.adaptor__cxg_adaptor__tiledb_ctx: + if type(self.data_locator__s3__region_name) == str: + self.adaptor__cxg_adaptor__tiledb_ctx[regionkey] = self.data_locator__s3__region_name + + from server.data_cxg.cxg_adaptor import CxgAdaptor + + CxgAdaptor.set_tiledb_context(self.adaptor__cxg_adaptor__tiledb_ctx) + + # anndata + self.check_attr("adaptor__anndata_adaptor__backed", bool) + + def handle_limits(self, context): + self.check_attr("limits__diffexp_cellcount_max", (type(None), int)) + self.check_attr("limits__column_request_max", (type(None), int)) + + def exceeds_limit(self, limit_name, value): + limit_value = getattr(self, "limits__" + limit_name, None) + if limit_value is None: # disabled + return False + return value > limit_value + + +class DatasetConfig(BaseConfig): + """Manages the config attribute associated with a dataset.""" + + def __init__(self, tag, app_config, default_config): + super().__init__(app_config, default_config) + self.tag = tag + dc = default_config + try: + self.app__scripts = dc["app"]["scripts"] + self.app__inline_scripts = dc["app"]["inline_scripts"] + self.app__about_legal_tos = dc["app"]["about_legal_tos"] + self.app__about_legal_privacy = dc["app"]["about_legal_privacy"] + + self.presentation__max_categories = dc["presentation"]["max_categories"] + self.presentation__custom_colors = dc["presentation"]["custom_colors"] + + self.user_annotations__enable = dc["user_annotations"]["enable"] + self.user_annotations__type = dc["user_annotations"]["type"] + self.user_annotations__local_file_csv__directory = dc["user_annotations"]["local_file_csv"]["directory"] + self.user_annotations__local_file_csv__file = dc["user_annotations"]["local_file_csv"]["file"] + self.user_annotations__ontology__enable = dc["user_annotations"]["ontology"]["enable"] + self.user_annotations__ontology__obo_location = dc["user_annotations"]["ontology"]["obo_location"] + + self.embeddings__names = dc["embeddings"]["names"] + self.embeddings__enable_reembedding = dc["embeddings"]["enable_reembedding"] + + self.diffexp__enable = dc["diffexp"]["enable"] + self.diffexp__lfc_cutoff = dc["diffexp"]["lfc_cutoff"] + self.diffexp__top_n = dc["diffexp"]["top_n"] + + except KeyError as e: + raise ConfigurationError(f"Unexpected config: {str(e)}") + + # The annotation object is created during complete_config and stored here. + self.user_annotations = None + + def complete_config(self, context): + self.handle_app(context) + self.handle_presentation(context) + self.handle_user_annotations(context) + self.handle_embeddings(context) + self.handle_diffexp(context) + + def handle_app(self, context): + self.check_attr("app__scripts", list) + self.check_attr("app__inline_scripts", list) + self.check_attr("app__about_legal_tos", (type(None), str)) + self.check_attr("app__about_legal_privacy", (type(None), str)) + + # scripts can be string (filename) or dict (attributes). Convert string to dict. + scripts = [] + for s in self.app__scripts: + if isinstance(s, str): + scripts.append({"src": s}) + elif isinstance(s, dict) and isinstance(s["src"], str): + scripts.append(s) + else: + raise ConfigurationError("Scripts must be string or dict") + self.app__scripts = scripts + + def handle_presentation(self, context): + self.check_attr("presentation__max_categories", int) + self.check_attr("presentation__custom_colors", bool) + def handle_user_annotations(self, context): - self.__check_attr("user_annotations__enable", bool) - self.__check_attr("user_annotations__type", str) - self.__check_attr("user_annotations__local_file_csv__directory", (type(None), str)) - self.__check_attr("user_annotations__local_file_csv__file", (type(None), str)) - self.__check_attr("user_annotations__ontology__enable", bool) - self.__check_attr("user_annotations__ontology__obo_location", (type(None), str)) + self.check_attr("user_annotations__enable", bool) + self.check_attr("user_annotations__type", str) + self.check_attr("user_annotations__local_file_csv__directory", (type(None), str)) + self.check_attr("user_annotations__local_file_csv__file", (type(None), str)) + self.check_attr("user_annotations__ontology__enable", bool) + self.check_attr("user_annotations__ontology__obo_location", (type(None), str)) if self.user_annotations__enable: # TODO, replace this with a factory pattern once we have more than one way @@ -458,8 +747,11 @@ class AppConfig(object): # if the user has specified a fixed label file, go ahead and validate it # so that we can remove errors early in the process. - if self.single_dataset__datapath and self.user_annotations__local_file_csv__file: - with self.matrix_data_cache_manager.data_adaptor(self.single_dataset__datapath, self) as data_adaptor: + server_config = self.app_config.server_config + if server_config.single_dataset__datapath and self.user_annotations__local_file_csv__file: + with server_config.matrix_data_cache_manager.data_adaptor( + self.tag, server_config.single_dataset__datapath, self.app_config + ) as data_adaptor: data_adaptor.check_new_labels(self.user_annotations.read_labels(data_adaptor)) if self.user_annotations__ontology__enable or self.user_annotations__ontology__obo_location: @@ -487,135 +779,29 @@ class AppConfig(object): ) def handle_embeddings(self, context): - self.__check_attr("embeddings__names", list) - self.__check_attr("embeddings__enable_reembedding", bool) + self.check_attr("embeddings__names", list) + self.check_attr("embeddings__enable_reembedding", bool) - if self.single_dataset__datapath: + if self.app_config.server_config.single_dataset__datapath: if self.embeddings__enable_reembedding: - matrix_data_loader = MatrixDataLoader(self.single_dataset__datapath, app_config=self) + matrix_data_loader = MatrixDataLoader(self.single_dataset__datapath, app_config=self.app_config) if matrix_data_loader.matrix_data_type() != MatrixDataType.H5AD: raise ConfigurationError("'enable-reembedding is only supported with H5AD files.") if self.adaptor__anndata_adaptor__backed: raise ConfigurationError("enable-reembedding is not supported when run in --backed mode.") def handle_diffexp(self, context): - self.__check_attr("diffexp__enable", bool) - self.__check_attr("diffexp__lfc_cutoff", float) - self.__check_attr("diffexp__top_n", int) - self.__check_attr("diffexp__alg_cxg__max_workers", (str, int)) - self.__check_attr("diffexp__alg_cxg__cpu_multiplier", int) - self.__check_attr("diffexp__alg_cxg__target_workunit", int) + self.check_attr("diffexp__enable", bool) + self.check_attr("diffexp__lfc_cutoff", float) + self.check_attr("diffexp__top_n", int) - if self.single_dataset__datapath: - with self.matrix_data_cache_manager.data_adaptor(self.single_dataset__datapath, self) as data_adaptor: + server_config = self.app_config.server_config + if server_config.single_dataset__datapath: + with server_config.matrix_data_cache_manager.data_adaptor( + self.tag, server_config.single_dataset__datapath, self.app_config + ) as data_adaptor: if self.diffexp__enable and data_adaptor.parameters.get("diffexp_may_be_slow", False): context["messagefn"]( "CAUTION: due to the size of your dataset, " "running differential expression may take longer or fail." ) - - max_workers = self.diffexp__alg_cxg__max_workers - cpu_multiplier = self.diffexp__alg_cxg__cpu_multiplier - cpu_count = os.cpu_count() - max_workers = min(max_workers, cpu_multiplier * cpu_count) - diffexp_tiledb.set_config(max_workers, self.diffexp__alg_cxg__target_workunit) - - def handle_adaptor(self, context): - # cxg - self.__check_attr("adaptor__cxg_adaptor__tiledb_ctx", dict) - regionkey = "vfs.s3.region" - if regionkey not in self.adaptor__cxg_adaptor__tiledb_ctx: - if type(self.data_locator__s3__region_name) == str: - self.adaptor__cxg_adaptor__tiledb_ctx[regionkey] = self.data_locator__s3__region_name - - from server.data_cxg.cxg_adaptor import CxgAdaptor - - CxgAdaptor.set_tiledb_context(self.adaptor__cxg_adaptor__tiledb_ctx) - - # anndata - self.__check_attr("adaptor__anndata_adaptor__backed", bool) - - def handle_limits(self, context): - self.__check_attr("limits__diffexp_cellcount_max", (type(None), int)) - self.__check_attr("limits__column_request_max", (type(None), int)) - - def get_title(self, data_adaptor): - return self.single_dataset__title if self.single_dataset__title else data_adaptor.get_title() - - def get_about(self, data_adaptor): - return self.single_dataset__about if self.single_dataset__about else data_adaptor.get_about() - - def get_client_config(self, data_adaptor, annotation=None): - """ - Return the configuration as required by the /config REST route - """ - - # FIXME The current set of config is not consistently presented: - # we have camalCase, hyphen-text, and underscore_text - - # make sure the configuration has been checked. - self.check_config() - - # features - features = [f.todict() for f in data_adaptor.get_features(annotation)] - - # display_names - title = self.get_title(data_adaptor) - about = self.get_about(data_adaptor) - - display_names = dict(engine=data_adaptor.get_name(), dataset=title) - - # library_versions - library_versions = {} - library_versions.update(data_adaptor.get_library_versions()) - library_versions["cellxgene"] = cellxgene_version - - # links - links = {"about-dataset": about} - - # parameters - parameters = { - "layout": self.embeddings__names, - "max-category-items": self.presentation__max_categories, - "obs_names": self.single_dataset__obs_names, - "var_names": self.single_dataset__var_names, - "diffexp_lfc_cutoff": self.diffexp__lfc_cutoff, - "backed": self.adaptor__anndata_adaptor__backed, - "disable-diffexp": not self.diffexp__enable, - "enable-reembedding": self.embeddings__enable_reembedding, - "annotations": False, - "annotations_file": None, - "annotations_dir": None, - "annotations_cell_ontology_enabled": False, - "annotations_cell_ontology_obopath": None, - "annotations_cell_ontology_terms": None, - "custom_colors": self.presentation__custom_colors, - "diffexp-may-be-slow": False, - "about_legal_tos": self.server__about_legal_tos, - "about_legal_privacy": self.server__about_legal_privacy, - } - - data_adaptor.update_parameters(parameters) - if annotation: - annotation.update_parameters(parameters, data_adaptor) - - # gather it all together - c = {} - config = c["config"] = {} - config["features"] = features - config["displayNames"] = display_names - config["library_versions"] = library_versions - config["links"] = links - config["parameters"] = parameters - config["limits"] = { - "column_request_max": self.limits__column_request_max, - "diffexp_cellcount_max": self.limits__diffexp_cellcount_max, - } - - return c - - def exceeds_limit(self, limit_name, value): - limit_value = getattr(self, "limits__" + limit_name, None) - if limit_value is None: # disabled - return False - return value > limit_value diff --git a/server/common/default_config.py b/server/common/default_config.py index 5e93149c..0b0c5118 100644 --- a/server/common/default_config.py +++ b/server/common/default_config.py @@ -1,129 +1,159 @@ import yaml default_config = """ -# cellxgene configuration - server: - verbose: false - debug: false - host: "127.0.0.1" - port : null + app: + verbose: false + debug: false + host: "127.0.0.1" + port : null + open_browser: false + force_https: false + flask_secret_key: null + generate_cache_control_headers: false + server_timing_headers: false + csp_directives: null - # Scripts can be a list of either file names (string) or dicts containing keys src, integrity and crossorigin. - # these will be injected into the index template as script tags with these attributes set. - scripts: [] - # Inline scripts are a list of file names, where the contents of the file will be injected into the index. - inline_scripts: [] + multi_dataset: + # If dataroot is set, then cellxgene may serve multiple datasets. This parameter is not + # compatible with single_dataset/datapath. + # dataroot may be a string, representing the path to a directory or S3 prefix. In this + # case the datasets in that location are accessed from /d/. + # example: + # dataroot: /path/to/datasets/ + # or + # dataroot: s3://bucket/prefix/ + # + # As an alternative, dataroot can be a dictionary, where a dataset key is associated with a base_url + # and a dataroot. + # example: + # dataroot: + # d1: + # base_url: set1 + # dataroot: /path/to/set1_datasets/ + # d2: + # base_url: set2/subdir + # dataroot: /path/to/set2_datasets/ + # + # In this case, datasets can be accessed from /set1/ or + # /set2/subdir/. It is possible to have different dataset configurations + # for datasets accessed through different dataroots. For example, in one dataroot, the + # user annotations could be enabled, and in another dataroot they could be disabled. + # To specify dataroot configurations, add a new top level dictionary to the config named + # per_dataset_config. Within per_dataset_config create a dictionary for each dataroot to specialize + # ("d1" or "d2" from the example). Each of these dictionaries has the exact same form as the "dataset" + # dictionary (see below). + # When this approach is used, the values for each configuration option are checked in + # this order: per_dataset_config/, dataset, then the default values. + # + # example: + # + # per_dataset_config: + # d1: + # user_annotations: + # enable: false + # d2: + # user_annotations: + # enable: true - open_browser: false - about_legal_tos: null - about_legal_privacy: null - force_https: false - flask_secret_key: null - generate_cache_control_headers: false - server_timing_headers: false - csp_directives: null + dataroot: null -presentation: - max_categories: 1000 - custom_colors: true + # The index page when in multi-dataset mode: + # false or null: this returns a 404 code + # true: loads a test index page, which links to the datasets that are available in the dataroot + # string/URL: redirect to this URL: flask.redirect(config.multi_dataset__index) + index: false -multi_dataset: - # If dataroot is set, then cellxgene may serve multiple datasets. This parameter is not - # compatable with single_dataset/datapath. - # dataroot may be a string, representing the path to a directory or S3 prefix. In this - # case the datasets in that location are accessed from /d/. - # example: - # dataroot: /path/to/datasets/ - # or - # dataroot: s3://bucket/prefix/ - # - # As an alternative, dataroot can be a dictionary, mapping url prefixes to dataroot paths. - # example: - # dataroot: - # set1 : /path/to/set1_datasets/ - # set2 : /path/to/set2_datasets/ - # In this case, datasets can be accessed from /set1/ or - # /set2/. + # A list of allowed matrix types. If an empty list, then all matrix types are allowed + allowed_matrix_types: [] - dataroot: null + matrix_cache: + # The maximum number of datasets that may be opened at one time. The least recently used dataset + # is evicted from the cache first. + max_datasets: 5 - # The index page when in multi-dataset mode: - # false or null: this returns a 404 code - # true: loads a test index page, which links to the datasets that are available in the dataroot - # string/URL: redirect to this URL: flask.redirect(config.multi_dataset__index) - index: false + # A matrix is automatically removed from the cache after timelimit_s number of seconds. + # If timelimit_s is set to None, then there is no time limit. + timelimit_s: 30 - # A list of allowed matrix types. If an empty list, then all matrix types are allowed - allowed_matrix_types: [] + single_dataset: + # If datapath is set, then cellxgene with serve a single dataset located at datapath. This parameter is not + # compatible with multi_dataset/dataroot. + datapath: null + obs_names: null + var_names: null + about: null + title: null - matrix_cache: - # The maximum number of datasets that may be opened at one time. The least recently used dataset - # is evicted from the cache first. - max_datasets: 5 + diffexp: + alg_cxg: + # The number of threads to use is computed from: min(max_workers, cpu_multipler * cpu_count). + # Where cpu_count is determined at runtime. + max_workers: 64 + cpu_multiplier: 4 - # A matrix is automatically removed from the cache after timelimit_s number of seconds. - # If timelimit_s is set to None, then there is no time limit. - timelimit_s: 30 + # The target number of matrix elements that are evaluated + # together in one thread. + target_workunit: 16_000_000 -single_dataset: - datapath: null - obs_names: null - var_names: null - about: null - title: null + data_locator: + s3: + # s3 region name. + # if true, then the s3 location is automatically determined from the datapath or dataroot. + # if false/null, then do not set. + # if a string, then use that value (e.g. us-east-1). + region_name: true -user_annotations: - enable: true - type: local_file_csv - local_file_csv: - directory: null - file: null - ontology: - enable: false - obo_location: null + adaptor: + cxg_adaptor: + # The key/values under tiledb_ctx will be used to initialize the tiledb Context. + # If 'vfs.s3.region' is not set, then it will automatically use the setting from + # data_locator / s3 / region_name. + tiledb_ctx: + sm.tile_cache_size: 8589934592 + sm.num_reader_threads: 32 -embeddings: - names : [] - enable_reembedding: false - -diffexp: - enable: true - lfc_cutoff: 0.01 - top_n: 10 - alg_cxg: - # The number of threads to use is computed from: min(max_workers, cpu_multipler * cpu_count). - # Where cpu_count is determined at runtime. - max_workers: 64 - cpu_multiplier: 4 - - # The target number of matrix elements that are evaluated - # together in one thread. - target_workunit: 16_000_000 - -data_locator: - s3: - # s3 region name. - # if true, then the s3 location is automatically determined from the datapath or dataroot. - # if false/null, then do not set. - # if a string, then use that value (e.g. us-east-1). - region_name: true - -adaptor: - cxg_adaptor: - # The key/values under tiledb_ctx will be used to initialize the tiledb Context. - # If 'vfs.s3.region' is not set, then it will automatically use the setting from - # data_locator / s3 / region_name. - tiledb_ctx: - sm.tile_cache_size: 8589934592 - sm.num_reader_threads: 32 - - anndata_adaptor: + anndata_adaptor: backed: false -limits: - column_request_max: 32 - diffexp_cellcount_max: null + limits: + column_request_max: 32 + diffexp_cellcount_max: null + + +dataset: + app: + # Scripts can be a list of either file names (string) or dicts containing keys src, integrity and crossorigin. + # these will be injected into the index template as script tags with these attributes set. + scripts: [] + # Inline scripts are a list of file names, where the contents of the file will be injected into the index. + inline_scripts: [] + + about_legal_tos: null + about_legal_privacy: null + + presentation: + max_categories: 1000 + custom_colors: true + + user_annotations: + enable: true + type: local_file_csv + local_file_csv: + directory: null + file: null + ontology: + enable: false + obo_location: null + + embeddings: + names : [] + enable_reembedding: false + + diffexp: + enable: true + lfc_cutoff: 0.01 + top_n: 10 """ diff --git a/server/common/errors.py b/server/common/errors.py index 9b31ee6d..d1dda2cf 100644 --- a/server/common/errors.py +++ b/server/common/errors.py @@ -1,92 +1,85 @@ -class FilterError(Exception): - """ - Raised when filter is malformed - """ +from http import HTTPStatus + + +class RequestException(Exception): + """Baseclass for exceptions that can be raised from a request.""" + + # The default status code is 400 (Bad Request) + default_status_code = HTTPStatus.BAD_REQUEST + + def __init__(self, message, status_code=None): + Exception.__init__(self) + self.message = message + self.status_code = status_code or self.default_status_code + + +class FilterError(RequestException): + """Raised when filter is malformed""" pass -class JSONEncodingValueError(Exception): - """ - Raised when data cannot be encoded into json - """ +class JSONEncodingValueError(RequestException): + """Raised when data cannot be encoded into json""" pass -class MimeTypeError(Exception): - """ - Raised when incompatible MIME type selected - """ +class MimeTypeError(RequestException): + """Raised when incompatible MIME type selected""" pass -class PrepareError(Exception): - """ - Raised when data is misprepared - """ +class DatasetAccessError(RequestException): + """Raised when file loaded into a DataAdaptor is misformatted""" pass -class DatasetAccessError(Exception): - """ - Raised when file loaded into a DataAdaptor is misformatted - """ +class DisabledFeatureError(RequestException): + """Raised when an attempt to use a disabled feature occurs""" pass -class DisabledFeatureError(Exception): - """ - Raised when an attempt to use a disabled feature occurs - """ +class AnnotationsError(RequestException): + """Raised when an attempt to use the annotations feature fails""" pass -class AnnotationsError(Exception): - """ - Raised when an attempt to use the annotations feature fails - """ +class ComputeError(RequestException): + """Raised when an error occurs during a compute algorithm (such as diffexp)""" + + default_status_code = HTTPStatus.INTERNAL_SERVER_ERROR + + +class ExceedsLimitError(RequestException): + """Raised when an HTTP request exceeds a limit/quota""" + + pass + + +class ColorFormatException(RequestException): + """Raised when color helper functions encounter an unknown color format""" pass class OntologyLoadFailure(Exception): - """ - Raised when reading the ontology file fails - """ + """Raised when reading the ontology file fails""" pass class ConfigurationError(Exception): - """ - Raised when checking configuration errors - """ + """Raised when checking configuration errors""" pass -class ExceedsLimitError(Exception): - """ - Raised when an HTTP request exceeds a limit/quota - """ - - pass - - -class ComputeError(Exception): - """ - Raised when an error occurs during a compute algorithm (such as diffexp) - """ - - pass - - -class ColorFormatException(Exception): - """Raised when color helper functions encounter an unknown color format""" +class PrepareError(Exception): + """Raised when data is misprepared""" pass diff --git a/server/common/health.py b/server/common/health.py index 939a670f..5edaec59 100644 --- a/server/common/health.py +++ b/server/common/health.py @@ -24,10 +24,12 @@ def health_check(config): health = {"status": None, "version": "1", "releaseID": cellxgene_version} checks = False - if config.single_dataset__datapath is not None: - checks = _is_accessible(config.single_dataset__datapath, config) - elif config.multi_dataset__dataroot is not None: - checks = all([_is_accessible(datapath, config) for datapath in config.multi_dataset__dataroot.values()]) + server_config = config.server_config + if config.is_multi_dataset(): + dataroots = [datapath_dict["dataroot"] for datapath_dict in server_config.multi_dataset__dataroot.values()] + checks = all([_is_accessible(dataroot, server_config) for dataroot in dataroots]) + else: + checks = _is_accessible(server_config.single_dataset__datapath, server_config) health["status"] = "pass" if checks else "fail" code = HTTPStatus.OK if health["status"] == "pass" else HTTPStatus.BAD_REQUEST diff --git a/server/common/rest.py b/server/common/rest.py index 60a2bedf..a1e1a9ae 100644 --- a/server/common/rest.py +++ b/server/common/rest.py @@ -97,12 +97,13 @@ def _query_parameter_to_filter(args): return result -def schema_get_helper(data_adaptor, annotations): +def schema_get_helper(data_adaptor): """helper function to gather the schema from the data source and annotations""" schema = data_adaptor.get_schema() schema = copy.deepcopy(schema) # add label obs annotations as needed + annotations = data_adaptor.dataset_config.user_annotations if annotations is not None: label_schema = annotations.get_schema(data_adaptor) schema["annotations"]["obs"]["columns"].extend(label_schema) @@ -110,20 +111,20 @@ def schema_get_helper(data_adaptor, annotations): return schema -def schema_get(data_adaptor, annotations): - schema = schema_get_helper(data_adaptor, annotations) +def schema_get(data_adaptor): + schema = schema_get_helper(data_adaptor) return make_response(jsonify({"schema": schema}), HTTPStatus.OK) -def config_get(app_config, data_adaptor, annotations): - config = app_config.get_client_config(data_adaptor, annotations) +def config_get(app_config, data_adaptor): + config = app_config.get_client_config(data_adaptor) return make_response(jsonify(config), HTTPStatus.OK) -def annotations_obs_get(request, data_adaptor, annotations): +def annotations_obs_get(request, data_adaptor): fields = request.args.getlist("annotation-name", None) num_columns_requested = len(data_adaptor.get_obs_keys()) if len(fields) == 0 else len(fields) - if data_adaptor.config.exceeds_limit("column_request_max", num_columns_requested): + if data_adaptor.server_config.exceeds_limit("column_request_max", num_columns_requested): return abort(HTTPStatus.BAD_REQUEST) preferred_mimetype = request.accept_mimetypes.best_match(["application/octet-stream"]) if preferred_mimetype != "application/octet-stream": @@ -131,6 +132,7 @@ def annotations_obs_get(request, data_adaptor, annotations): try: labels = None + annotations = data_adaptor.dataset_config.user_annotations if annotations: labels = annotations.read_labels(data_adaptor) fbs = data_adaptor.annotation_to_fbs_matrix(Axis.OBS, fields, labels) @@ -139,8 +141,9 @@ def annotations_obs_get(request, data_adaptor, annotations): return abort_and_log(HTTPStatus.BAD_REQUEST, str(e), include_exc_info=True) -def annotations_put_fbs_helper(data_adaptor, annotations, fbs): +def annotations_put_fbs_helper(data_adaptor, fbs): """helper function to write annotations from fbs""" + annotations = data_adaptor.dataset_config.user_annotations if annotations is None: raise DisabledFeatureError("Writable annotations are not enabled") @@ -150,7 +153,8 @@ def annotations_put_fbs_helper(data_adaptor, annotations, fbs): annotations.write_labels(new_label_df, data_adaptor) -def annotations_obs_put(request, data_adaptor, annotations): +def annotations_obs_put(request, data_adaptor): + annotations = data_adaptor.dataset_config.user_annotations if annotations is None: return abort(HTTPStatus.NOT_IMPLEMENTED) @@ -163,17 +167,17 @@ def annotations_obs_put(request, data_adaptor, annotations): annotations.set_collection(anno_collection) try: - annotations_put_fbs_helper(data_adaptor, annotations, fbs) + annotations_put_fbs_helper(data_adaptor, fbs) res = json.dumps({"status": "OK"}) return make_response(res, HTTPStatus.OK, {"Content-Type": "application/json"}) except (ValueError, DisabledFeatureError, KeyError) as e: return abort_and_log(HTTPStatus.BAD_REQUEST, str(e), include_exc_info=True) -def annotations_var_get(request, data_adaptor, annotations): +def annotations_var_get(request, data_adaptor): fields = request.args.getlist("annotation-name", None) num_columns_requested = len(data_adaptor.get_var_keys()) if len(fields) == 0 else len(fields) - if data_adaptor.config.exceeds_limit("column_request_max", num_columns_requested): + if data_adaptor.server_config.exceeds_limit("column_request_max", num_columns_requested): return abort(HTTPStatus.BAD_REQUEST) preferred_mimetype = request.accept_mimetypes.best_match(["application/octet-stream"]) if preferred_mimetype != "application/octet-stream": @@ -181,6 +185,7 @@ def annotations_var_get(request, data_adaptor, annotations): try: labels = None + annotations = data_adaptor.dataset_config.user_annotations if annotations is not None: labels = annotations.read_labels(data_adaptor) return make_response( @@ -226,7 +231,7 @@ def data_var_get(request, data_adaptor): def colors_get(data_adaptor): - if not data_adaptor.config.presentation__custom_colors: + if not data_adaptor.dataset_config.presentation__custom_colors: return make_response(jsonify({}), HTTPStatus.OK) try: return make_response(jsonify(data_adaptor.get_colors()), HTTPStatus.OK) @@ -235,7 +240,7 @@ def colors_get(data_adaptor): def diffexp_obs_post(request, data_adaptor): - if not data_adaptor.config.diffexp__enable: + if not data_adaptor.dataset_config.diffexp__enable: return abort(HTTPStatus.NOT_IMPLEMENTED) args = request.get_json() @@ -273,7 +278,7 @@ def diffexp_obs_post(request, data_adaptor): def layout_obs_get(request, data_adaptor): fields = request.args.getlist("layout-name", None) num_columns_requested = len(data_adaptor.get_embedding_names()) if len(fields) == 0 else len(fields) - if data_adaptor.config.exceeds_limit("column_request_max", num_columns_requested): + if data_adaptor.server_config.exceeds_limit("column_request_max", num_columns_requested): return abort(HTTPStatus.BAD_REQUEST) preferred_mimetype = request.accept_mimetypes.best_match(["application/octet-stream"]) @@ -296,7 +301,7 @@ def layout_obs_get(request, data_adaptor): def layout_obs_put(request, data_adaptor): - if not data_adaptor.config.embedding__enable_reembedding: + if not data_adaptor.dataset_config.embedding__enable_reembedding: return abort(HTTPStatus.NOT_IMPLEMENTED) preferred_mimetype = request.accept_mimetypes.best_match(["application/octet-stream"]) diff --git a/server/common/utils.py b/server/common/utils.py index 7671cbf5..7e876194 100644 --- a/server/common/utils.py +++ b/server/common/utils.py @@ -11,6 +11,7 @@ from flask import json from urllib.parse import urlsplit, urljoin import numpy as np import pandas as pd +from server.common.errors import ConfigurationError def find_available_port(host, port=5005): @@ -161,9 +162,13 @@ def import_plugins(plugin_module): pkg = importlib.import_module(plugin_module) for loader, name, is_pkg in pkgutil.walk_packages(pkg.__path__): full_name = f"{plugin_module}.{name}" - module = importlib.import_module(full_name) - logging.info(f"Imported plugin {full_name}") + try: + module = importlib.import_module(full_name) + except Exception as e: + raise ConfigurationError(f"Unexpected error while importing plugin: {plugin_module}.{name}: {str(e)}") loaded_modules.append(module) - except ModuleNotFoundError: - logging.debug(f"No plugins found in module: {plugin_module}") + except ModuleNotFoundError as e: + # This exception occurs when the plugin_module does not exist (not an error). + logging.debug(f"No plugins found in module: {plugin_module}: {str(e)}") + return loaded_modules diff --git a/server/converters/cxgtool.py b/server/converters/cxgtool.py index 504e99b6..0b8fb3a4 100644 --- a/server/converters/cxgtool.py +++ b/server/converters/cxgtool.py @@ -391,13 +391,13 @@ def create_emb(e_name, emb): def is_valid_embedding(adata, name, arr): """ return True if this layout data is a valid array for front-end presentation: * ndarray, with shape (n_obs, >= 2), dtype float/int/uint - * contains only finite values * follows ScanPy embedding naming conventions + * with all values finite or NaN (no +Inf or -Inf) """ is_valid = type(name) == str and name.startswith("X_") and len(name) > 2 is_valid = is_valid and type(arr) == np.ndarray and arr.dtype.kind in "fiu" is_valid = is_valid and arr.shape[0] == adata.n_obs and arr.shape[1] >= 2 - is_valid = is_valid and np.all(np.isfinite(arr)) + is_valid = is_valid and not np.any(np.isinf(arr)) and not np.all(np.isnan(arr)) return is_valid @@ -493,8 +493,7 @@ def evaluate_for_sparse_column_shift_encoding(xdata, sparse_threshold): nnz += shape[0] * (lim - col) - np.sum(m.count) if nnz > maxnnz: return (None, nnz, shape[0] * lim) - log(2, "\t...cols", lim, "of", shape[1], "nnz", - nnz, "nnz percent %5.2f%%" % (100 * nnz / (lim * shape[0]))) + log(2, "\t...cols", lim, "of", shape[1], "nnz", nnz, "nnz percent %5.2f%%" % (100 * nnz / (lim * shape[0]))) is_sparse = (100.0 * nnz / (shape[0] * shape[1])) < sparse_threshold return (col_shift if is_sparse else None, nnz, shape[0] * shape[1]) diff --git a/server/converters/to_sparse.py b/server/converters/to_sparse.py index ef704c4d..69602526 100644 --- a/server/converters/to_sparse.py +++ b/server/converters/to_sparse.py @@ -38,8 +38,7 @@ def main(): print("input is not a directory", args.input) sys.exit(1) - shutil.copytree(args.input, args.output, - ignore=shutil.ignore_patterns("X", "X_col_shift")) + shutil.copytree(args.input, args.output, ignore=shutil.ignore_patterns("X", "X_col_shift")) ctx = tiledb.Ctx( { diff --git a/server/data_anndata/anndata_adaptor.py b/server/data_anndata/anndata_adaptor.py index 60113b6d..6022928e 100644 --- a/server/data_anndata/anndata_adaptor.py +++ b/server/data_anndata/anndata_adaptor.py @@ -28,10 +28,9 @@ def anndata_version_is_pre_070(): class AnndataAdaptor(DataAdaptor): - def __init__(self, data_locator, config=None): - super().__init__(config) + def __init__(self, data_locator, app_config=None, dataset_config=None): + super().__init__(data_locator, app_config, dataset_config) self.data = None - self.data_locator = data_locator self._load_data(data_locator) self._validate_and_initialize() @@ -55,14 +54,8 @@ class AnndataAdaptor(DataAdaptor): return data_locator.size() if data_locator.islocal() else 0 @staticmethod - def open(data_locator, config): - return AnndataAdaptor(data_locator, config) - - def get_location(self): - return self.data_locator.uri_or_path - - def get_data_locator(self): - return self.data_locator + def open(data_locator, app_config, dataset_config=None): + return AnndataAdaptor(data_locator, app_config, dataset_config) def get_name(self): return "cellxgene anndata adaptor version" @@ -100,7 +93,7 @@ class AnndataAdaptor(DataAdaptor): for (ax_name, var_name) in ((Axis.OBS, "obs"), (Axis.VAR, "var")): config_name = f"single_dataset__{var_name}_names" parameter_name = f"{var_name}_names" - name = getattr(self.config, config_name) + name = getattr(self.server_config, config_name) df_axis = getattr(self.data, str(ax_name)) if name is None: # Default: create unique names from index @@ -161,7 +154,7 @@ class AnndataAdaptor(DataAdaptor): with data_locator.local_handle() as lh: # as of AnnData 0.6.19, backed mode performs initial load fast, but at the # cost of significantly slower access to X data. - backed = "r" if self.config.adaptor__anndata_adaptor__backed else None + backed = "r" if self.server_config.adaptor__anndata_adaptor__backed else None self.data = anndata.read_h5ad(lh, backed=backed) except ValueError: @@ -181,7 +174,7 @@ class AnndataAdaptor(DataAdaptor): ) def _validate_and_initialize(self): - if anndata_version_is_pre_070() and self.config.adaptor__anndata_adaptor__backed: + if anndata_version_is_pre_070() and self.server_config.adaptor__anndata_adaptor__backed: warnings.warn( "Use of --backed mode with anndata versions older than 0.7 will have serious " "performance issues. Please update to at least anndata 0.7 or later." @@ -199,17 +192,18 @@ class AnndataAdaptor(DataAdaptor): # heuristic n_values = self.data.shape[0] * self.data.shape[1] - if (n_values > 1e8 and self.config.adaptor__anndata_adaptor__backed is True) or (n_values > 5e8): + if (n_values > 1e8 and self.server_config.adaptor__anndata_adaptor__backed is True) or (n_values > 5e8): self.parameters.update({"diffexp_may_be_slow": True}) def _is_valid_layout(self, arr): """ return True if this layout data is a valid array for front-end presentation: - * ndarray, with shape (n_obs, >= 2), dtype float/int/uint - * contains only finite values + * ndarray, dtype float/int/uint + * with shape (n_obs, >= 2) + * with all values finite or NaN (no +Inf or -Inf) """ is_valid = type(arr) == np.ndarray and arr.dtype.kind in "fiu" is_valid = is_valid and arr.shape[0] == self.data.n_obs and arr.shape[1] >= 2 - is_valid = is_valid and np.all(np.isfinite(arr)) + is_valid = is_valid and not np.any(np.isinf(arr)) and not np.all(np.isnan(arr)) return is_valid def _validate_data_types(self): @@ -246,7 +240,7 @@ class AnndataAdaptor(DataAdaptor): ) if isinstance(datatype, CategoricalDtype): category_num = len(curr_axis[ann].dtype.categories) - if category_num > 500 and category_num > self.config.presentation__max_categories: + if category_num > 500 and category_num > self.dataset_config.presentation__max_categories: warnings.warn( f"{str(ax).title()} annotation '{ann}' has {category_num} categories, this may be " f"cumbersome or slow to display. We recommend setting the " @@ -277,7 +271,7 @@ class AnndataAdaptor(DataAdaptor): c) cap total list of layouts at global const MAX_LAYOUTS """ # load default layouts from the data. - layouts = self.config.embeddings__names + layouts = self.dataset_config.embeddings__names if layouts is None or len(layouts) == 0: layouts = [key[2:] for key in self.data.obsm_keys() if type(key) == str and key.startswith("X_")] @@ -298,7 +292,7 @@ class AnndataAdaptor(DataAdaptor): raise PrepareError("No valid layout data.") # cap layouts to MAX_LAYOUTS - return layouts[0:MAX_LAYOUTS] + return valid_layouts[0:MAX_LAYOUTS] def get_embedding_array(self, ename, dims=2): full_embedding = self.data.obsm[f"X_{ename}"] @@ -329,9 +323,9 @@ class AnndataAdaptor(DataAdaptor): def compute_diffexp_ttest(self, maskA, maskB, top_n=None, lfc_cutoff=None): if top_n is None: - top_n = self.config.diffexp__top_n + top_n = self.dataset_config.diffexp__top_n if lfc_cutoff is None: - lfc_cutoff = self.config.diffexp__lfc_cutoff + lfc_cutoff = self.dataset_config.diffexp__lfc_cutoff return diffexp_generic.diffexp_ttest(self, maskA, maskB, top_n, lfc_cutoff) def get_colors(self): @@ -355,7 +349,7 @@ class AnndataAdaptor(DataAdaptor): return getattr(self.data.obs, term_name) def get_obs_index(self): - name = self.config.single_dataset__obs_names + name = self.server_config.single_dataset__obs_names if name is None: return self.original_obs_index else: diff --git a/server/data_common/data_adaptor.py b/server/data_common/data_adaptor.py index 617addad..eabec8d1 100644 --- a/server/data_common/data_adaptor.py +++ b/server/data_common/data_adaptor.py @@ -14,12 +14,17 @@ from server.common.app_config import AppFeature, AppConfig class DataAdaptor(metaclass=ABCMeta): """Base class for loading and accessing matrix data""" - def __init__(self, config): - if type(config) != AppConfig: + def __init__(self, data_locator, app_config, dataset_config=None): + if type(app_config) != AppConfig: raise TypeError("config expected to be of type AppConfig") + # location to the dataset + self.data_locator = data_locator + # config is the application configuration - self.config = config + self.app_config = app_config + self.server_config = self.app_config.server_config + self.dataset_config = dataset_config or app_config.default_dataset_config # parameters set by this data adaptor based on the data. self.parameters = {} @@ -31,7 +36,7 @@ class DataAdaptor(metaclass=ABCMeta): @staticmethod @abstractmethod - def open(data_locator, config): + def open(data_locator, app_config, dataset_config): pass @staticmethod @@ -109,13 +114,11 @@ class DataAdaptor(metaclass=ABCMeta): def cleanup(self): pass - @abstractmethod - def get_location(self): - pass - - @abstractmethod def get_data_locator(self): - pass + return self.data_locator + + def get_location(self): + return self.data_locator.uri_or_path def get_about(self): return None @@ -149,8 +152,8 @@ class DataAdaptor(metaclass=ABCMeta): features = [ AppFeature("/cluster/", method="POST", available=False), AppFeature("/layout/obs", method="GET", available=self.get_embedding_names() is not None), - AppFeature("/layout/obs", method="PUT", available=self.config.embeddings__enable_reembedding), - AppFeature("/diffexp/", method="POST", available=self.config.diffexp__enable), + AppFeature("/layout/obs", method="PUT", available=self.dataset_config.embeddings__enable_reembedding), + AppFeature("/diffexp/", method="POST", available=self.dataset_config.diffexp__enable), AppFeature("/annotations/obs", method="PUT", available=annotations is not None), ] return features @@ -260,7 +263,6 @@ class DataAdaptor(metaclass=ABCMeta): * currently only supports access on VAR axis * currently only supports filtering on VAR axis """ - if axis != Axis.VAR: raise ValueError("Only VAR dimension access is supported") @@ -273,7 +275,7 @@ class DataAdaptor(metaclass=ABCMeta): raise FilterError("filtering on obs unsupported") num_columns = self.get_shape()[1] if var_selector is None else np.count_nonzero(var_selector) - if self.config.exceeds_limit("column_request_max", num_columns): + if self.server_config.exceeds_limit("column_request_max", num_columns): raise ExceedsLimitError("Requested dataframe columns exceed column request limit") X = self.get_X_array(obs_selector, var_selector) @@ -301,14 +303,14 @@ class DataAdaptor(metaclass=ABCMeta): except (KeyError, IndexError): raise FilterError("Error parsing filter") if top_n is None: - top_n = self.config.diffexp__top_n + top_n = self.dataset_config.diffexp__top_n - if self.config.exceeds_limit( + if self.server_config.exceeds_limit( "diffexp_cellcount_max", np.count_nonzero(obs_mask_A) + np.count_nonzero(obs_mask_B) ): raise ExceedsLimitError("Diffexp request exceeds max cell count limit") - result = self.compute_diffexp_ttest(obs_mask_A, obs_mask_B, top_n, self.config.diffexp__lfc_cutoff) + result = self.compute_diffexp_ttest(obs_mask_A, obs_mask_B, top_n, self.dataset_config.diffexp__lfc_cutoff) try: return jsonify_numpy(result) @@ -326,8 +328,14 @@ class DataAdaptor(metaclass=ABCMeta): """ # scale isotropically - min = embedding.min(axis=0) - max = embedding.max(axis=0) + try: + min = np.nanmin(embedding, axis=0) + max = np.nanmax(embedding, axis=0) + except RuntimeError: + # indicates entire array was NaN, which should propagate + min = np.NaN + max = np.NaN + scale = np.amax(max - min) normalized_layout = (embedding - min) / scale diff --git a/server/data_common/matrix_loader.py b/server/data_common/matrix_loader.py index fae95b5a..233e7b86 100644 --- a/server/data_common/matrix_loader.py +++ b/server/data_common/matrix_loader.py @@ -5,6 +5,7 @@ from server.data_common.rwlock import RWLock from server.common.errors import DatasetAccessError from server.common.data_locator import DataLocator from contextlib import contextmanager +from http import HTTPStatus class MatrixDataCacheItem(object): @@ -28,7 +29,7 @@ class MatrixDataCacheItem(object): self.data_lock.r_release() return None - def acquire_and_open(self, app_config): + def acquire_and_open(self, app_config, dataset_config=None): """returns the data_adaptor if cached. opens the data_adaptor if not. In either case, the a reader lock is taken. Must call release when the data_adaptor is no longer needed""" @@ -42,7 +43,7 @@ class MatrixDataCacheItem(object): if not self.data_adaptor: try: self.loader.pre_load_validation() - self.data_adaptor = self.loader.open(app_config) + self.data_adaptor = self.loader.open(app_config, dataset_config) except Exception as e: # necessary to hold the reader lock after an exception, since # the release will occur when the context exits. @@ -114,7 +115,7 @@ class MatrixDataCacheManager(object): # will automatically be refreshed. def __init__(self, max_cached, timelimit_s=None): - # key is location, value is a MatrixDataCacheInfo + # key is tuple(url_dataroot, location), value is a MatrixDataCacheInfo self.datasets = {} # lock to protect the datasets @@ -130,20 +131,21 @@ class MatrixDataCacheManager(object): self.timelimit_s = timelimit_s @contextmanager - def data_adaptor(self, location, app_config): + def data_adaptor(self, url_dataroot, location, app_config): # create a loader for to this location if it does not already exist delete_adaptor = None data_adaptor = None cache_item = None + key = (url_dataroot, location) with self.lock: self.evict_old_datasets() - info = self.datasets.get(location) + info = self.datasets.get(key) if info is not None: info.last_access = time.time() info.num_access += 1 - self.datasets[location] = info + self.datasets[key] = info data_adaptor = info.cache_item.acquire_existing() cache_item = info.cache_item @@ -164,19 +166,20 @@ class MatrixDataCacheManager(object): loader = MatrixDataLoader(location, app_config=app_config) cache_item = MatrixDataCacheItem(loader) item = MatrixDataCacheInfo(cache_item, time.time()) - self.datasets[location] = item + self.datasets[key] = item try: assert cache_item if delete_adaptor: delete_adaptor.delete() if data_adaptor is None: - data_adaptor = cache_item.acquire_and_open(app_config) + dataset_config = app_config.get_dataset_config(url_dataroot) + data_adaptor = cache_item.acquire_and_open(app_config, dataset_config) yield data_adaptor except DatasetAccessError: cache_item.release() with self.lock: - del self.datasets[location] + del self.datasets[key] cache_item.delete() cache_item = None raise @@ -214,10 +217,10 @@ class MatrixDataType(Enum): class MatrixDataLoader(object): def __init__(self, location, matrix_data_type=None, app_config=None): """ location can be a string or DataLocator """ - region_name = None if app_config is None else app_config.data_locator__s3__region_name + region_name = None if app_config is None else app_config.server_config.data_locator__s3__region_name self.location = DataLocator(location, region_name=region_name) if not self.location.exists(): - raise DatasetAccessError("Dataset does not exist.") + raise DatasetAccessError("Dataset does not exist.", HTTPStatus.NOT_FOUND) # matrix_data_type is an enum value of type MatrixDataType self.matrix_data_type = matrix_data_type @@ -253,12 +256,12 @@ class MatrixDataLoader(object): if not app_config: return True - if not app_config.multi_dataset__dataroot: + if not app_config.is_multi_dataset(): return True - if len(app_config.multi_dataset__allowed_matrix_types) == 0: + if len(app_config.server_config.multi_dataset__allowed_matrix_types) == 0: return True - for val in app_config.multi_dataset__allowed_matrix_types: + for val in app_config.server_config.multi_dataset__allowed_matrix_types: try: if self.matrix_data_type == MatrixDataType(val): return True @@ -278,6 +281,6 @@ class MatrixDataLoader(object): def file_size(self): return self.matrix_type.file_size(self.location) - def open(self, app_config): + def open(self, app_config, dataset_config=None): # create and return a DataAdaptor object - return self.matrix_type.open(self.location, app_config) + return self.matrix_type.open(self.location, app_config, dataset_config) diff --git a/server/data_cxg/cxg_adaptor.py b/server/data_cxg/cxg_adaptor.py index a5a75813..2705f43b 100644 --- a/server/data_cxg/cxg_adaptor.py +++ b/server/data_cxg/cxg_adaptor.py @@ -24,11 +24,10 @@ class CxgAdaptor(DataAdaptor): {"sm.tile_cache_size": 8 * 1024 * 1024 * 1024, "sm.num_reader_threads": 32, "vfs.s3.region": "us-east-1"} ) - def __init__(self, data_locator, config=None): - super().__init__(config) + def __init__(self, data_locator, app_config=None, dataset_config=None): + super().__init__(data_locator, app_config, dataset_config) self.lock = threading.Lock() - self.data_locator = data_locator self.url = data_locator.uri_or_path if self.url[-1] != "/": self.url += "/" @@ -66,8 +65,8 @@ class CxgAdaptor(DataAdaptor): return 0 @staticmethod - def open(data_locator, args): - return CxgAdaptor(data_locator, args) + def open(data_locator, app_config, dataset_config=None): + return CxgAdaptor(data_locator, app_config, dataset_config) def get_about(self): return self.about if self.about else super().get_about() @@ -75,12 +74,6 @@ class CxgAdaptor(DataAdaptor): def get_title(self): return self.title if self.title else super().get_title() - def get_location(self): - return self.url - - def get_data_locator(self): - return self.data_locator - def get_name(self): return "cellxgene cxg adaptor version" @@ -196,9 +189,9 @@ class CxgAdaptor(DataAdaptor): def compute_diffexp_ttest(self, maskA, maskB, top_n=None, lfc_cutoff=None): if top_n is None: - top_n = self.config.diffexp__top_n + top_n = self.dataset_config.diffexp__top_n if lfc_cutoff is None: - lfc_cutoff = self.config.diffexp__lfc_cutoff + lfc_cutoff = self.dataset_config.diffexp__lfc_cutoff return diffexp_cxg.diffexp_ttest(self, maskA, maskB, top_n, lfc_cutoff) def get_colors(self): @@ -400,7 +393,7 @@ class CxgAdaptor(DataAdaptor): embeddings = self.get_embedding_names() for ename in embeddings: A = self.open_array(f"emb/{ename}") - obs_layout.append({"name": ename, "type": A.dtype.name, "dims": [f"{ename}_{d}" for d in range(0, A.ndim)]}) + obs_layout.append({"name": ename, "type": "float32", "dims": [f"{ename}_{d}" for d in range(0, A.ndim)]}) schema = {"dataframe": dataframe, "annotations": annotations, "layout": {"obs": obs_layout}} return schema diff --git a/server/eb/Makefile b/server/eb/Makefile index 5e5a8a66..a51ccdce 100644 --- a/server/eb/Makefile +++ b/server/eb/Makefile @@ -11,11 +11,13 @@ clean: # Presumes that a top-level `make build-client` has been done to # create the client static assets. +cwd := $(shell pwd) + .PHONY: build build: clean mkdir artifact.dir; \ (cd ../.. ; \ - git ls-files server/ | cpio -pdm server/eb/artifact.dir ; ); \ + git ls-files server/ | cpio -pdm $(cwd)/artifact.dir ; ); \ $(call copy_client_assets,../../client/build,artifact.dir/server) ; \ set -e ; \ cp app.py artifact.dir/application.py; \ @@ -36,7 +38,6 @@ build: clean fi; \ if [ -d customize/inline_scripts ] ; then \ cp -r customize/inline_scripts/* artifact.dir/server/common/web/templates; \ - find artifact.dir/server/common/web/templates/ -type f -exec sed -i$(if $(IS_DARWIN), '',) 's/__CELLXGENE_COMMIT__/$(CELLXGENE_COMMIT)/g' {} \;; \ fi; \ if [ -d customize/ebextensions ] ; then \ cp -r customize/ebextensions/* artifact.dir/.ebextensions; \ @@ -44,7 +45,6 @@ build: clean fi; \ if [ -d customize/plugins ] ; then \ cp -r customize/plugins artifact.dir/server/; \ - find artifact.dir/server/plugins/ -type f -exec sed -i$(if $(IS_DARWIN), '',) 's/__CELLXGENE_COMMIT__/$(CELLXGENE_COMMIT)/g' {} \;; \ fi; \ (cd artifact.dir; \ cp -r server/common/web/static static; \ diff --git a/server/eb/app.py b/server/eb/app.py index dcc91641..438cd9bb 100644 --- a/server/eb/app.py +++ b/server/eb/app.py @@ -58,6 +58,7 @@ class WSGIServer(Server): @staticmethod def _before_adding_routes(app, app_config): script_hashes, style_hashes = WSGIServer.get_csp_hashes(app, app_config) + server_config = app_config.server_config csp = { "default-src": ["'self'"], "connect-src": ["'self'"], @@ -72,14 +73,14 @@ class WSGIServer(Server): if not app.debug: csp["upgrade-insecure-requests"] = "" - if app_config.server__csp_directives: - for k, v in app_config.server__csp_directives.items(): + if server_config.app__csp_directives: + for k, v in server_config.app__csp_directives.items(): if not isinstance(v, list): v = [v] csp[k] = csp.get(k, []) + v Talisman( - app, force_https=app_config.server__force_https, frame_options="DENY", content_security_policy=csp, + app, force_https=server_config.app__force_https, frame_options="DENY", content_security_policy=csp, ) @staticmethod @@ -102,16 +103,18 @@ class WSGIServer(Server): @staticmethod def compute_inline_scp_hashes(app, app_config): - inline_scripts = app_config.server__inline_scripts + dataset_configs = [app_config.default_dataset_config] + list(app_config.dataroot_config.values()) hashes = [] - for script in inline_scripts: - with app.open_resource(f"../common/web/templates/{script}") as f: - content = f.read() - # we use jinja2 template include, which trims final newline if present. - if content[-1] == 0x0A: - content = content[0:-1] - hash = base64.b64encode(hashlib.sha256(content).digest()) - hashes.append(f"'sha256-{hash.decode('utf-8')}'") + for dataset_config in dataset_configs: + inline_scripts = dataset_config.app__inline_scripts + for script in inline_scripts: + with app.open_resource(f"../common/web/templates/{script}") as f: + content = f.read() + # we use jinja2 template include, which trims final newline if present. + if content[-1] == 0x0A: + content = content[0:-1] + hash = base64.b64encode(hashlib.sha256(content).digest()) + hashes.append(f"'sha256-{hash.decode('utf-8')}'") return hashes @staticmethod @@ -156,7 +159,7 @@ try: dataroot = os.getenv("CXG_DATAROOT") if dataroot: logging.info("Configuration from CXG_DATAROOT") - app_config.update(multi_dataset__dataroot=dataroot) + app_config.update_server_config(multi_dataset__dataroot=dataroot) secret_name = os.getenv("CXG_AWS_SECRET_NAME") if secret_name: @@ -174,26 +177,23 @@ try: sys.exit(1) flask_secret_key = get_flask_secret_key(secret_region_name, secret_name) - app_config.update(server__flask_secret_key=flask_secret_key) + app_config.update_server_config(app__flask_secret_key=flask_secret_key) # features are unsupported in the current hosted server - app_config.update( - user_annotations__enable=False, - embeddings__enable_reembedding=False, - multi_dataset__allowed_matrix_types=["cxg"], + app_config.update_default_dataset_config( + user_annotations__enable=False, embeddings__enable_reembedding=False, ) + app_config.update_server_config(multi_dataset__allowed_matrix_types=["cxg"],) app_config.complete_config(logging.info) - if not app_config.server__flask_secret_key: + if not app_config.server_config.app__flask_secret_key: logging.critical( "flask_secret_key is not provided. Either set in config file, CXG_SECRET_KEY environment variable, " "or in AWS Secret Manager" ) sys.exit(1) - user_annotations = app_config.user_annotations - server = WSGIServer(app_config) debug = False @@ -203,10 +203,10 @@ except Exception: logging.critical("Caught exception during initialization", exc_info=True) sys.exit(1) -if app_config.multi_dataset__dataroot: - logging.info(f"starting server with multi_dataset__dataroot={app_config.multi_dataset__dataroot}") -elif app_config.single_dataset__datapath: - logging.info(f"starting server with single_dataset__datapath={app_config.single_dataset__datapath}") +if app_config.is_multi_dataset(): + logging.info(f"starting server with multi_dataset__dataroot={app_config.server_config.multi_dataset__dataroot}") +else: + logging.info(f"starting server with single_dataset__datapath={app_config.server_config.single_dataset__datapath}") if __name__ == "__main__": try: diff --git a/server/requirements.txt b/server/requirements.txt index 488713a1..d47e383f 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -18,6 +18,6 @@ pandas>=0.24.2 PyYAML>=5.3 scipy>=1.3.0 requests>=2.22.0 -tiledb>=0.5.9,!=0.6.0 +tiledb>=0.5.9,>=0.6.2 s3fs>=0.4.2 gunicorn>=20.0.4 diff --git a/server/test/__init__.py b/server/test/__init__.py index c84e381b..bc82be90 100644 --- a/server/test/__init__.py +++ b/server/test/__init__.py @@ -27,21 +27,18 @@ def data_with_tmp_annotations(ext: MatrixDataType, annotations_fixture=False): annotations_file = path.join(tmp_dir, "test_annotations.csv") if annotations_fixture: shutil.copyfile(f"{PROJECT_ROOT}/server/test/test_datasets/pbmc3k-annotations.csv", annotations_file) - args = { - "embeddings__names": ["umap"], - "presentation__max_categories": 100, - "single_dataset__obs_names": None, - "single_dataset__var_names": None, - "diffexp__lfc_cutoff": 0.01, - } fname = { MatrixDataType.H5AD: f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad", MatrixDataType.CXG: "test/test_datasets/pbmc3k.cxg", }[ext] data_locator = DataLocator(fname) config = AppConfig() - config.update(**args) - config.update(single_dataset__datapath=data_locator.path) + config.update_server_config( + single_dataset__obs_names=None, single_dataset__var_names=None, single_dataset__datapath=data_locator.path + ) + config.update_default_dataset_config( + embeddings__names=["umap"], presentation__max_categories=100, diffexp__lfc_cutoff=0.01, + ) config.complete_config() data = MatrixDataLoader(data_locator.abspath()).open(config) annotations = AnnotationsLocalFile(None, annotations_file) @@ -66,21 +63,21 @@ def skip_if(condition, reason: str): return decorator -def app_config(data_locator, backed=False, extra={}): - args = { - "embeddings__names": ["umap", "tsne", "pca"], - "presentation__max_categories": 100, - "single_dataset__obs_names": None, - "single_dataset__var_names": None, - "diffexp__lfc_cutoff": 0.01, - "adaptor__anndata_adaptor__backed": backed, - "single_dataset__datapath": data_locator, - "limits__diffexp_cellcount_max": None, - "limits__column_request_max": None, - } +def app_config(data_locator, backed=False, extra_server_config={}, extra_dataset_config={}): config = AppConfig() - config.update(**args) - config.update(**extra) + config.update_server_config( + single_dataset__obs_names=None, + single_dataset__var_names=None, + adaptor__anndata_adaptor__backed=backed, + single_dataset__datapath=data_locator, + limits__diffexp_cellcount_max=None, + limits__column_request_max=None, + ) + config.update_default_dataset_config( + embeddings__names=["umap", "tsne", "pca"], presentation__max_categories=100, diffexp__lfc_cutoff=0.01 + ) + config.update_server_config(**extra_server_config) + config.update_default_dataset_config(**extra_dataset_config) config.complete_config() return config @@ -107,7 +104,7 @@ def test_server(command_line_args=[], app_config=None): yaml config file, which this server will read and parse. """ - port = DEFAULT_SERVER_PORT + port = int(os.environ.get("CXG_SERVER_PORT", DEFAULT_SERVER_PORT)) port = find_available_port("localhost", port) command = ["cellxgene", "--no-upgrade-check", "launch", "--verbose", "--port=%d" % port] + command_line_args diff --git a/server/test/run_diffexp.py b/server/test/run_diffexp.py index d3fad73a..bbbb3094 100644 --- a/server/test/run_diffexp.py +++ b/server/test/run_diffexp.py @@ -32,8 +32,8 @@ def main(): args = parser.parse_args() app_config = AppConfig() - app_config.single_dataset__datapath = args.dataset - app_config.server__verbose = True + app_config.update_server_config(single_dataset__datapath=args.dataset) + app_config.update_server_config(app__verbose=True) app_config.complete_config() loader = MatrixDataLoader(args.dataset) diff --git a/server/test/test_anndata_adaptor.py b/server/test/test_anndata_adaptor.py index 1cec773c..3afe2ea3 100644 --- a/server/test/test_anndata_adaptor.py +++ b/server/test/test_anndata_adaptor.py @@ -107,9 +107,9 @@ class AdaptorTest(unittest.TestCase): self.assertEqual(len(feature), 1) check_feature("POST", "/cluster/", False) - check_feature("POST", "/diffexp/", self.data.config.diffexp__enable) + check_feature("POST", "/diffexp/", self.data.dataset_config.diffexp__enable) check_feature("GET", "/layout/obs", True) - check_feature("PUT", "/layout/obs", self.data.config.embeddings__enable_reembedding) + check_feature("PUT", "/layout/obs", self.data.dataset_config.embeddings__enable_reembedding) check_feature("PUT", "/annotations/obs", False) def test_layout(self): diff --git a/server/test/test_anndata_adaptor_data_load.py b/server/test/test_anndata_adaptor_data_load.py index f7848b9f..e5c01a03 100644 --- a/server/test/test_anndata_adaptor_data_load.py +++ b/server/test/test_anndata_adaptor_data_load.py @@ -15,7 +15,7 @@ class DataLoadAdaptorTest(unittest.TestCase): def setUp(self): self.data_file = DataLocator(f"{PROJECT_ROOT}/example-dataset/pbmc3k.h5ad") config = AppConfig() - config.update(single_dataset__datapath=self.data_file.path) + config.update_server_config(single_dataset__datapath=self.data_file.path) config.complete_config() self.data = AnndataAdaptor(self.data_file, config) @@ -40,14 +40,18 @@ class DataLocatorAdaptorTest(unittest.TestCase): Test various types of data locators we expect to consume """ - def setUp(self): - self.args = { - "embeddings__names": ["umap"], - "presentation__max_categories": 100, - "single_dataset__obs_names": None, - "single_dataset__var_names": None, - "diffexp__lfc_cutoff": 0.01, - } + def get_basic_config(self): + config = AppConfig() + config.update_server_config( + single_dataset__obs_names=None, + single_dataset__var_names=None, + ) + config.update_default_dataset_config( + embeddings__names=["umap"], + presentation__max_categories=100, + diffexp__lfc_cutoff=0.01, + ) + return config def stdAsserts(self, data): """ run these each time we load the data """ @@ -57,25 +61,22 @@ class DataLocatorAdaptorTest(unittest.TestCase): def test_posix_file(self): locator = DataLocator("../example-dataset/pbmc3k.h5ad") - config = AppConfig() - config.update(**self.args) - config.update(single_dataset__datapath=locator.path) + config = self.get_basic_config() + config.update_server_config(single_dataset__datapath=locator.path) config.complete_config() data = AnndataAdaptor(locator, config) self.stdAsserts(data) def test_url_https(self): - url = "https://raw.githubusercontent.com/chanzuckerberg/cellxgene/master/example-dataset/pbmc3k.h5ad" + url = "https://raw.githubusercontent.com/chanzuckerberg/cellxgene/main/example-dataset/pbmc3k.h5ad" locator = DataLocator(url) - config = AppConfig() - config.update(**self.args) + config = self.get_basic_config() data = AnndataAdaptor(locator, config) self.stdAsserts(data) def test_url_http(self): - url = "http://raw.githubusercontent.com/chanzuckerberg/cellxgene/master/example-dataset/pbmc3k.h5ad" + url = "http://raw.githubusercontent.com/chanzuckerberg/cellxgene/main/example-dataset/pbmc3k.h5ad" locator = DataLocator(url) - config = AppConfig() - config.update(**self.args) + config = self.get_basic_config() data = AnndataAdaptor(locator, config) self.stdAsserts(data) diff --git a/server/test/test_app_config.py b/server/test/test_app_config.py index f4c109da..c55f927a 100644 --- a/server/test/test_app_config.py +++ b/server/test/test_app_config.py @@ -11,61 +11,90 @@ import requests class AppConfigTest(unittest.TestCase): def test_update(self): c = AppConfig() - c.update(server__verbose=True, multi_dataset__dataroot="datadir") - v = c.changes_from_default() - self.assertCountEqual(v, [("server__verbose", True, False), ("multi_dataset__dataroot", "datadir", None)]) + c.update_server_config(app__verbose=True, multi_dataset__dataroot="datadir") + v = c.server_config.changes_from_default() + self.assertCountEqual(v, [("app__verbose", True, False), ("multi_dataset__dataroot", "datadir", None)]) c = AppConfig() - c.update(server__scripts=(), server__inline_scripts=()) - v = c.changes_from_default() + c.update_default_dataset_config(app__scripts=(), app__inline_scripts=()) + v = c.server_config.changes_from_default() self.assertCountEqual(v, []) c = AppConfig() - c.update(server__scripts=[], server__inline_scripts=[]) - v = c.changes_from_default() + c.update_default_dataset_config(app__scripts=[], app__inline_scripts=[]) + v = c.default_dataset_config.changes_from_default() self.assertCountEqual(v, []) c = AppConfig() - c.update(server__scripts=("a", "b"), server__inline_scripts=["c", "d"]) - v = c.changes_from_default() - self.assertCountEqual(v, [("server__scripts", ["a", "b"], []), ("server__inline_scripts", ["c", "d"], [])]) + c.update_default_dataset_config(app__scripts=("a", "b"), app__inline_scripts=["c", "d"]) + v = c.default_dataset_config.changes_from_default() + self.assertCountEqual(v, [("app__scripts", ["a", "b"], []), ("app__inline_scripts", ["c", "d"], [])]) def test_multi_dataset(self): c = AppConfig() # test for illegal url_dataroots - for illegal in ("a/b", "../b", "!$*", "\\n", "", "(bad)"): - c.update(multi_dataset__dataroot={illegal: f"{PROJECT_ROOT}/example-dataset"}) + for illegal in ("../b", "!$*", "\\n", "", "(bad)"): + c.update_server_config( + multi_dataset__dataroot={"tag": {"base_url": illegal, "dataroot": "{PROJECT_ROOT}/example-dataset"}} + ) with self.assertRaises(ConfigurationError): c.complete_config() # test for legal url_dataroots - for legal in ( - "d", - "this.is-okay_", - ): - c.update(multi_dataset__dataroot={legal: f"{PROJECT_ROOT}/example-dataset"}) + for legal in ("d", "this.is-okay_", "a/b"): + c.update_server_config( + multi_dataset__dataroot={"tag": {"base_url": legal, "dataroot": "{PROJECT_ROOT}/example-dataset"}} + ) c.complete_config() # test that multi dataroots work end to end - c.update( + c.update_server_config( multi_dataset__dataroot=dict( - set1=f"{PROJECT_ROOT}/example-dataset", - set2=f"{PROJECT_ROOT}/server/test/test_datasets" + s1=dict(dataroot=f"{PROJECT_ROOT}/example-dataset", base_url="set1/1/2"), + s2=dict(dataroot=f"{PROJECT_ROOT}/server/test/test_datasets", base_url="set2"), + s3=dict(dataroot=f"{PROJECT_ROOT}/server/test/test_datasets", base_url="set3"), ) ) + + # Change this default to test if the dataroot overrides below work. + c.update_default_dataset_config(app__about_legal_tos="tos_default.html") + + # specialize the configs for set1 + c.add_dataroot_config( + "s1", user_annotations__enable=False, diffexp__enable=True, app__about_legal_tos="tos_set1.html" + ) + + # specialize the configs for set2 + c.add_dataroot_config( + "s2", user_annotations__enable=True, diffexp__enable=False, app__about_legal_tos="tos_set2.html" + ) + + # no specializations for set3 (they get the default dataset config) c.complete_config() with test_server(app_config=c) as server: session = requests.Session() - r = session.get(f"{server}/set1/pbmc3k.h5ad/api/v0.2/config") + r = session.get(f"{server}/set1/1/2/pbmc3k.h5ad/api/v0.2/config") data_config = r.json() assert data_config["config"]["displayNames"]["dataset"] == "pbmc3k" + assert data_config["config"]["parameters"]["annotations"] is False + assert data_config["config"]["parameters"]["disable-diffexp"] is False + assert data_config["config"]["parameters"]["about_legal_tos"] == "tos_set1.html" r = session.get(f"{server}/set2/pbmc3k.cxg/api/v0.2/config") data_config = r.json() assert data_config["config"]["displayNames"]["dataset"] == "pbmc3k" + assert data_config["config"]["parameters"]["annotations"] is True + assert data_config["config"]["parameters"]["about_legal_tos"] == "tos_set2.html" + + r = session.get(f"{server}/set3/pbmc3k.cxg/api/v0.2/config") + data_config = r.json() + assert data_config["config"]["displayNames"]["dataset"] == "pbmc3k" + assert data_config["config"]["parameters"]["annotations"] is True + assert data_config["config"]["parameters"]["disable-diffexp"] is False + assert data_config["config"]["parameters"]["about_legal_tos"] == "tos_default.html" r = session.get(f"{server}/health") assert r.json()["status"] == "pass" diff --git a/server/test/test_diffexp.py b/server/test/test_diffexp.py index c69365ac..abe1fc57 100644 --- a/server/test/test_diffexp.py +++ b/server/test/test_diffexp.py @@ -15,8 +15,9 @@ class DiffExpTest(unittest.TestCase): """Tests the diffexp returns the expected results for one test case, using different adaptor types and different algorithms.""" - def load_dataset(self, path, extra={}): - config = app_config(path, extra=extra) + def load_dataset(self, path, extra_server_config={}, extra_dataset_config={}): + config = app_config(path, extra_server_config=extra_server_config, + extra_dataset_config=extra_dataset_config) loader = MatrixDataLoader(path) adaptor = loader.open(config) return adaptor @@ -100,7 +101,7 @@ class DiffExpTest(unittest.TestCase): # create a sparse matrix h5adfile = os.path.join(dirname, "sparse.h5ad") create_test_h5ad(h5adfile, 2000, 2000, 10, apply_col_shift) - adaptor_anndata = self.load_dataset(h5adfile, extra=dict(embeddings__names=[])) + adaptor_anndata = self.load_dataset(h5adfile, extra_dataset_config=dict(embeddings__names=[])) adata = adaptor_anndata.data sparsename = os.path.join(dirname, "sparse.cxg") diff --git a/server/test/test_eb.py b/server/test/test_eb.py new file mode 100644 index 00000000..f1e1ef9d --- /dev/null +++ b/server/test/test_eb.py @@ -0,0 +1,54 @@ +import unittest +import tempfile +import requests +import subprocess +from server.test import PROJECT_ROOT +from server.common.app_config import AppConfig +from contextlib import contextmanager +import time + + +@contextmanager +def run_eb_app(tempdirname): + ps = subprocess.Popen(["python", "artifact.dir/application.py"], cwd=tempdirname) + server = "http://localhost:5000" + for _ in range(10): + try: + requests.get(f"{server}/health") + break + except requests.exceptions.ConnectionError: + time.sleep(1) + + try: + yield server + finally: + try: + ps.terminate() + except ProcessLookupError: + pass + + +class Elastic_Beanstalk_Test(unittest.TestCase): + def test_run(self): + + tempdir = tempfile.TemporaryDirectory(dir=f"{PROJECT_ROOT}/server") + tempdirname = tempdir.name + + c = AppConfig() + # test that eb works + c.update_server_config( + multi_dataset__dataroot=f"{PROJECT_ROOT}/server/test/test_datasets", app__flask_secret_key="open sesame" + ) + + c.complete_config() + c.write_config(f"{tempdirname}/config.yaml") + + subprocess.check_call(f"git ls-files . | cpio -pdm {tempdirname}", cwd=f"{PROJECT_ROOT}/server/eb", shell=True) + subprocess.check_call(["make", "build"], cwd=tempdirname) + + with run_eb_app(tempdirname) as server: + session = requests.Session() + + r = session.get(f"{server}/d/pbmc3k.cxg/api/v0.2/config") + data_config = r.json() + assert data_config["config"]["displayNames"]["dataset"] == "pbmc3k" diff --git a/server/test/test_matrixcache.py b/server/test/test_matrixcache.py index 7a1362ae..6c68cfc3 100644 --- a/server/test/test_matrixcache.py +++ b/server/test/test_matrixcache.py @@ -21,13 +21,13 @@ class MatrixCacheTest(unittest.TestCase): shutil.copytree(source, target) def use_dataset(self, matrix_cache, dirname, app_config, dataset_index): - with matrix_cache.data_adaptor(os.path.join(dirname, str(dataset_index) + ".cxg"), app_config) as adaptor: + with matrix_cache.data_adaptor(None, os.path.join(dirname, str(dataset_index) + ".cxg"), app_config) as adaptor: pass return adaptor def use_dataset_with_error(self, matrix_cache, dirname, app_config, dataset_index): try: - with matrix_cache.data_adaptor(os.path.join(dirname, str(dataset_index) + ".cxg"), app_config): + with matrix_cache.data_adaptor(None, os.path.join(dirname, str(dataset_index) + ".cxg"), app_config): raise DatasetAccessError("something bad happened") except DatasetAccessError: # the MatrixDataCacheManager rethrows the exception, so catch and ignore @@ -38,7 +38,7 @@ class MatrixCacheTest(unittest.TestCase): result = {} for k, v in datasets.items(): # filter out the dirname and the .cxg from the name - newk = int(k[len(dirname) + 1 : -4]) + newk = int(k[1][len(dirname) + 1 : -4]) result[newk] = v return result diff --git a/server/test/test_writable_annotation.py b/server/test/test_writable_annotation.py index d4cf15b4..6ca0419d 100644 --- a/server/test/test_writable_annotation.py +++ b/server/test/test_writable_annotation.py @@ -15,12 +15,13 @@ from server.data_common.matrix_loader import MatrixDataType class WritableAnnotationTest(unittest.TestCase): def setUp(self): self.data, self.tmp_dir, self.annotations = data_with_tmp_annotations(MatrixDataType.H5AD) + self.data.dataset_config.user_annotations = self.annotations def tearDown(self): shutil.rmtree(self.tmp_dir) def annotation_put_fbs(self, fbs): - annotations_put_fbs_helper(self.data, self.annotations, fbs) + annotations_put_fbs_helper(self.data, fbs) res = json.dumps({"status": "OK"}) return res @@ -112,7 +113,7 @@ class WritableAnnotationTest(unittest.TestCase): # get labels = self.annotations.read_labels(None) fbsAll = self.data.annotation_to_fbs_matrix("obs", None, labels) - schema = schema_get_helper(self.data, self.annotations) + schema = schema_get_helper(self.data) annotations = decode_fbs.decode_matrix_FBS(fbsAll) obs_index_col_name = schema["annotations"]["obs"]["index"] self.assertEqual(annotations["n_rows"], n_rows) @@ -149,7 +150,7 @@ class WritableAnnotationTest(unittest.TestCase): self.assertEqual(len(feature), 1) check_feature("POST", "/cluster/", False) - check_feature("POST", "/diffexp/", self.data.config.diffexp__enable) + check_feature("POST", "/diffexp/", self.data.dataset_config.diffexp__enable) check_feature("GET", "/layout/obs", True) - check_feature("PUT", "/layout/obs", self.data.config.embeddings__enable_reembedding) + check_feature("PUT", "/layout/obs", self.data.dataset_config.embeddings__enable_reembedding) check_feature("PUT", "/annotations/obs", True)