diff --git a/.eslintignore b/.eslintignore index 5d00576d..4c784429 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,3 +14,4 @@ src/lib/music-metadata-browser webpack.config.js jest.config.js src/lib/secret-sauce/ +playwright.config.ts diff --git a/jest.config.js b/jest.config.js index df92b787..1b4680a8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,6 +5,7 @@ module.exports = { '/tests/staticFileMock.js', }, testPathIgnorePatterns: [ + '/tests/playwright/', '/node_modules/', '/legacy_notes_and_workbook/', '/client/src/stylesheets/', diff --git a/package-lock.json b/package-lock.json index 326890fa..4f61c34d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "@babel/preset-react": "^7.16.7", "@babel/preset-typescript": "^7.16.7", "@peculiar/webcrypto": "^1.3.3", + "@playwright/test": "^1.18.1", "@statoscope/cli": "^5.20.1", "@statoscope/webpack-plugin": "^5.20.1", "@testing-library/jest-dom": "^5.16.4", @@ -79,6 +80,7 @@ "replace-in-file": "^6.3.2", "sass": "^1.50.0", "sass-loader": "^12.6.0", + "serve": "^13.0.2", "style-loader": "^3.3.1", "stylelint": "^14.6.1", "stylelint-config-recommended-scss": "^6.0.0", @@ -2921,6 +2923,287 @@ "node": ">=10.12.0" } }, + "node_modules/@playwright/test": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.21.1.tgz", + "integrity": "sha512-XkkTXl5gvEm4fciqeHvY5IuSS/OfQef0MO6RpBNmtm6EuYSdtUvP/sDVuWRKsDqyVdB3WSA0az7iSw79f2//JQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.16.7", + "@babel/core": "7.16.12", + "@babel/helper-plugin-utils": "7.16.7", + "@babel/plugin-proposal-class-properties": "7.16.7", + "@babel/plugin-proposal-dynamic-import": "7.16.7", + "@babel/plugin-proposal-export-namespace-from": "7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7", + "@babel/plugin-proposal-numeric-separator": "7.16.7", + "@babel/plugin-proposal-optional-chaining": "7.16.7", + "@babel/plugin-proposal-private-methods": "7.16.11", + "@babel/plugin-proposal-private-property-in-object": "7.16.7", + "@babel/plugin-syntax-async-generators": "7.8.4", + "@babel/plugin-syntax-json-strings": "7.8.3", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "7.8.3", + "@babel/plugin-transform-modules-commonjs": "7.16.8", + "@babel/preset-typescript": "7.16.7", + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "expect": "27.2.5", + "jest-matcher-utils": "27.2.5", + "json5": "2.2.1", + "mime": "3.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "open": "8.4.0", + "pirates": "4.0.4", + "playwright-core": "1.21.1", + "rimraf": "3.0.2", + "source-map-support": "0.4.18", + "stack-utils": "2.0.5", + "yazl": "2.5.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@playwright/test/node_modules/@babel/core": { + "version": "7.16.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", + "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.16.7", + "@babel/parser": "^7.16.12", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.10", + "@babel/types": "^7.16.8", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@playwright/test/node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@playwright/test/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@playwright/test/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@playwright/test/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@playwright/test/node_modules/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@playwright/test/node_modules/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 + }, + "node_modules/@playwright/test/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@playwright/test/node_modules/debug/node_modules/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 + }, + "node_modules/@playwright/test/node_modules/expect": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.5.tgz", + "integrity": "sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA==", + "dev": true, + "dependencies": { + "@jest/types": "^27.2.5", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.6", + "jest-matcher-utils": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-regex-util": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@playwright/test/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@playwright/test/node_modules/jest-matcher-utils": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.5.tgz", + "integrity": "sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.2.5", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.5" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@playwright/test/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@playwright/test/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@playwright/test/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/@playwright/test/node_modules/pirates": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", + "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@playwright/test/node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "dependencies": { + "source-map": "^0.5.6" + } + }, + "node_modules/@playwright/test/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz", @@ -3852,6 +4135,16 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.19.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.19.0.tgz", @@ -4279,6 +4572,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@zeit/schemas": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", + "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==", + "dev": true + }, "node_modules/abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -4429,6 +4728,44 @@ "ajv": "^8.8.2" } }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/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 + }, + "node_modules/ansi-align/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -4490,12 +4827,38 @@ "node": ">= 8" } }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "node_modules/arg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", + "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5111,6 +5474,151 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/node_modules/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 + }, + "node_modules/boxen/node_modules/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 + }, + "node_modules/boxen/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5213,6 +5721,15 @@ "ieee754": "^1.2.1" } }, + "node_modules/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, + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -5487,6 +6004,18 @@ "node": ">=6" } }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5515,6 +6044,138 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clipboardy": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", + "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "dev": true, + "dependencies": { + "arch": "^2.1.1", + "execa": "^1.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clipboardy/node_modules/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, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/clipboardy/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clipboardy/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clipboardy/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clipboardy/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clipboardy/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/clipboardy/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/clipboardy/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clipboardy/node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clipboardy/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -5636,6 +6297,15 @@ "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6328,6 +6998,15 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", "dev": true }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6636,6 +7315,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "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, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", @@ -7819,6 +8507,41 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7865,6 +8588,21 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", + "dev": true, + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fast-url-parser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -7901,6 +8639,15 @@ "bser": "2.1.1" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -8964,6 +9711,12 @@ "node": ">= 0.10" } }, + "node_modules/ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, "node_modules/ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", @@ -11415,6 +12168,12 @@ "node": ">=8.10.0" } }, + "node_modules/jpeg-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz", + "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==", + "dev": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -12567,6 +13326,12 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -12911,6 +13676,15 @@ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -13083,6 +13857,12 @@ "node": ">=0.10.0" } }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -13113,6 +13893,12 @@ "node": ">=8" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -13152,6 +13938,27 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", + "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", + "dev": true, + "dependencies": { + "pngjs": "^4.0.1" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", + "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -13234,6 +14041,110 @@ "node": ">=8" } }, + "node_modules/playwright-core": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.21.1.tgz", + "integrity": "sha512-SbK5dEsai9ZUKlxcinqegorBq4GnftXd4/GfW+pLsdQIQWrLCM/JNh6YQ2Rf2enVykXCejtoXW8L5vJXBBVSJQ==", + "dev": true, + "dependencies": { + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "jpeg-js": "0.4.3", + "mime": "3.0.0", + "pixelmatch": "5.2.1", + "pngjs": "6.0.0", + "progress": "2.0.3", + "proper-lockfile": "4.1.2", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "socks-proxy-agent": "6.1.1", + "stack-utils": "2.0.5", + "ws": "8.4.2", + "yauzl": "2.10.0", + "yazl": "2.5.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/playwright-core/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/playwright-core/node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/playwright-core/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/playwright-core/node_modules/ws": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", + "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true, + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -14098,6 +15009,15 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -14128,6 +15048,26 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14150,12 +15090,28 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, + "node_modules/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, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -14279,6 +15235,30 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz", @@ -14574,6 +15554,28 @@ "node": ">=4" } }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/regjsgen": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", @@ -15139,6 +16141,99 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz", + "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==", + "dev": true, + "dependencies": { + "@zeit/schemas": "2.6.0", + "ajv": "6.12.6", + "arg": "2.0.0", + "boxen": "5.1.2", + "chalk": "2.4.1", + "clipboardy": "2.3.0", + "compression": "1.7.3", + "serve-handler": "6.1.3", + "update-check": "1.5.2" + }, + "bin": { + "serve": "bin/serve.js" + } + }, + "node_modules/serve-handler": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", + "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", + "dev": true, + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.0.4", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", + "dev": true + }, + "node_modules/serve-handler/node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", @@ -15214,6 +16309,75 @@ "node": ">= 0.8.0" } }, + "node_modules/serve/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/serve/node_modules/chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve/node_modules/compression": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve/node_modules/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 + }, + "node_modules/serve/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -15316,6 +16480,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -15327,6 +16501,34 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/socks": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "dev": true, + "dependencies": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -15661,6 +16863,15 @@ "node": ">=8" } }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -17097,6 +18308,16 @@ "node": ">= 0.8" } }, + "node_modules/update-check": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", + "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==", + "dev": true, + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -17786,6 +19007,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/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 + }, + "node_modules/widest-line/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -18031,6 +19293,25 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, "node_modules/zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", @@ -20065,6 +21346,221 @@ "webcrypto-core": "^1.7.2" } }, + "@playwright/test": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.21.1.tgz", + "integrity": "sha512-XkkTXl5gvEm4fciqeHvY5IuSS/OfQef0MO6RpBNmtm6EuYSdtUvP/sDVuWRKsDqyVdB3WSA0az7iSw79f2//JQ==", + "dev": true, + "requires": { + "@babel/code-frame": "7.16.7", + "@babel/core": "7.16.12", + "@babel/helper-plugin-utils": "7.16.7", + "@babel/plugin-proposal-class-properties": "7.16.7", + "@babel/plugin-proposal-dynamic-import": "7.16.7", + "@babel/plugin-proposal-export-namespace-from": "7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7", + "@babel/plugin-proposal-numeric-separator": "7.16.7", + "@babel/plugin-proposal-optional-chaining": "7.16.7", + "@babel/plugin-proposal-private-methods": "7.16.11", + "@babel/plugin-proposal-private-property-in-object": "7.16.7", + "@babel/plugin-syntax-async-generators": "7.8.4", + "@babel/plugin-syntax-json-strings": "7.8.3", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "7.8.3", + "@babel/plugin-transform-modules-commonjs": "7.16.8", + "@babel/preset-typescript": "7.16.7", + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "expect": "27.2.5", + "jest-matcher-utils": "27.2.5", + "json5": "2.2.1", + "mime": "3.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "open": "8.4.0", + "pirates": "4.0.4", + "playwright-core": "1.21.1", + "rimraf": "3.0.2", + "source-map-support": "0.4.18", + "stack-utils": "2.0.5", + "yazl": "2.5.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.16.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", + "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.16.7", + "@babel/parser": "^7.16.12", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.10", + "@babel/types": "^7.16.8", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "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 + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "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 + } + } + }, + "expect": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.5.tgz", + "integrity": "sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA==", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.6", + "jest-matcher-utils": "^27.2.5", + "jest-message-util": "^27.2.5", + "jest-regex-util": "^27.0.6" + } + }, + "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-matcher-utils": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.5.tgz", + "integrity": "sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.2.5", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.5" + } + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "pirates": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", + "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@rushstack/eslint-patch": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz", @@ -20911,6 +22407,16 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.19.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.19.0.tgz", @@ -21218,6 +22724,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@zeit/schemas": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", + "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==", + "dev": true + }, "abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -21327,6 +22839,40 @@ "fast-deep-equal": "^3.1.3" } }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + }, + "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==", + "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 + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -21367,12 +22913,24 @@ "picomatch": "^2.0.4" } }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true + }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "arg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", + "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -21835,6 +23393,108 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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 + }, + "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 + }, + "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-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 + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -21901,6 +23561,12 @@ "ieee754": "^1.2.1" } }, + "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-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -22097,6 +23763,12 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -22116,6 +23788,107 @@ "string-width": "^5.0.0" } }, + "clipboardy": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", + "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "dev": true, + "requires": { + "arch": "^2.1.1", + "execa": "^1.0.0", + "is-wsl": "^2.1.1" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "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" + } + } + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -22222,6 +23995,12 @@ "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -22732,6 +24511,12 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", "dev": true }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -22967,6 +24752,15 @@ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "dev": true }, + "end-of-stream": { + "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" + } + }, "enhanced-resolve": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", @@ -23858,6 +25652,29 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "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.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -23900,6 +25717,23 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=", + "dev": true, + "requires": { + "punycode": "^1.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, "fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -23933,6 +25767,15 @@ "bser": "2.1.1" } }, + "fd-slicer": { + "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" + } + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -24705,6 +26548,12 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, "ipaddr.js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", @@ -26518,6 +28367,12 @@ "integrity": "sha512-hPJKQyF0eiCqQOwfgIuQa+8wIn+WcEcjjyeOchuiXEUnt6zbV0tHKsUqRRwJY47ZtBiGcJQNr/BGuYW1Sfwbvg==", "dev": true }, + "jpeg-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz", + "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -27373,6 +29228,12 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -27629,6 +29490,12 @@ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, + "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-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -27761,6 +29628,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -27785,6 +29658,12 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -27809,6 +29688,23 @@ "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true }, + "pixelmatch": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", + "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", + "dev": true, + "requires": { + "pngjs": "^4.0.1" + }, + "dependencies": { + "pngjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", + "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", + "dev": true + } + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -27869,6 +29765,72 @@ } } }, + "playwright-core": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.21.1.tgz", + "integrity": "sha512-SbK5dEsai9ZUKlxcinqegorBq4GnftXd4/GfW+pLsdQIQWrLCM/JNh6YQ2Rf2enVykXCejtoXW8L5vJXBBVSJQ==", + "dev": true, + "requires": { + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "jpeg-js": "0.4.3", + "mime": "3.0.0", + "pixelmatch": "5.2.1", + "pngjs": "6.0.0", + "progress": "2.0.3", + "proper-lockfile": "4.1.2", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "socks-proxy-agent": "6.1.1", + "stack-utils": "2.0.5", + "ws": "8.4.2", + "yauzl": "2.10.0", + "yazl": "2.5.1" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true + }, + "ws": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", + "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", + "dev": true, + "requires": {} + } + } + }, + "pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "dev": true + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -28463,6 +30425,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -28492,6 +30460,25 @@ } } }, + "proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + } + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -28510,12 +30497,28 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -28602,6 +30605,26 @@ } } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, "react": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz", @@ -28829,6 +30852,25 @@ "unicode-match-property-value-ecmascript": "^2.0.0" } }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, "regjsgen": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", @@ -29242,6 +31284,144 @@ "randombytes": "^2.1.0" } }, + "serve": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.2.tgz", + "integrity": "sha512-71R6fKvNgKrqARAag6lYJNnxDzpH7DCNrMuvPY5PLVaC2PDhJsGTj/34o4o4tPWhTuLgEXqvgnAWbATQ9zGZTQ==", + "dev": true, + "requires": { + "@zeit/schemas": "2.6.0", + "ajv": "6.12.6", + "arg": "2.0.0", + "boxen": "5.1.2", + "chalk": "2.4.1", + "clipboardy": "2.3.0", + "compression": "1.7.3", + "serve-handler": "6.1.3", + "update-check": "1.5.2" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "compression": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.14", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "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" + } + }, + "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 + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "serve-handler": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", + "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.0.4", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + }, + "dependencies": { + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "~1.33.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + } + } + }, "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", @@ -29387,6 +31567,12 @@ } } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, "sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -29398,6 +31584,27 @@ "websocket-driver": "^0.7.4" } }, + "socks": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -29662,6 +31869,12 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -30736,6 +32949,16 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "update-check": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", + "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==", + "dev": true, + "requires": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -31237,6 +33460,40 @@ "is-symbol": "^1.0.3" } }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + }, + "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==", + "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 + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -31421,6 +33678,25 @@ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3" + } + }, "zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", diff --git a/package.json b/package.json index 74117ee3..b90c4a83 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "index.js", "scripts": { "dev": "cross-env APP_ENV=development webpack serve --env mode=dev --env isDevServer --mode development --config ./webpack.config.js", + "dev:mocked": "cross-env APP_ENV=test APP_MOCKED_CLIENT=1 webpack serve --env mode=dev --env isDevServer --mode development --config ./webpack.config.js --port 1235", "build": "webpack --mode production", + "build:mocked": "cross-env APP_ENV=test APP_MOCKED_CLIENT=1 webpack --env mode=dev --mode development --config ./webpack.config.js", "build:staging": "rm -rf dist/ && APP_ENV=staging APP_VERSION=$(npm run print_version --silent) npm run build && ./deploy/copy_to_dist.sh", "build:production": "npm i && rm -rf dist/ && APP_VERSION=$(npm run inc_version --silent) APP_ENV=production npm run build && ./deploy/copy_to_dist.sh", "deploy:production": "npm run build:production && git add -A && git commit -a -m '[Build]' --no-verify && git push", @@ -17,6 +19,8 @@ "gramjs:tl": "node ./src/lib/gramjs/tl/generateModules.js", "gramjs:lint:fix": "eslint ./src/lib/gramjs --fix", "test": "cross-env APP_ENV=test jest --verbose --forceExit", + "test:playwright": "playwright test", + "test:record": "playwright codegen localhost:1235", "prepare": "husky install", "statoscope:validate": "statoscope validate --input public/build-stats.json", "statoscope:validate-diff": "statoscope validate --input input.json --reference reference.json" @@ -47,6 +51,7 @@ "@peculiar/webcrypto": "^1.3.3", "@statoscope/cli": "^5.20.1", "@statoscope/webpack-plugin": "^5.20.1", + "@playwright/test": "^1.18.1", "@testing-library/jest-dom": "^5.16.4", "@types/croppie": "^2.6.1", "@types/jest": "^27.4.1", @@ -92,6 +97,7 @@ "replace-in-file": "^6.3.2", "sass": "^1.50.0", "sass-loader": "^12.6.0", + "serve": "^13.0.2", "style-loader": "^3.3.1", "stylelint": "^14.6.1", "stylelint-config-recommended-scss": "^6.0.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..6d9b52b8 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,38 @@ +import { PlaywrightTestConfig, devices } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + testDir: 'tests/playwright', + timeout: process.env.CI ? 60 * 5 * 1000 : 30 * 1000, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + webServer: { + command: 'npm run build:mocked && serve -l 1235 dist', + port: 1235, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + }, + use: { + baseURL: 'http://localhost:1235/', + video: 'retain-on-failure', + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'ios', + use: { ...devices['iPhone X'] }, + }, + ], +}; +export default config; diff --git a/src/api/gramjs/methods/client.ts b/src/api/gramjs/methods/client.ts index aecdb0b8..430c2e42 100644 --- a/src/api/gramjs/methods/client.ts +++ b/src/api/gramjs/methods/client.ts @@ -1,6 +1,8 @@ import { - TelegramClient, sessions, Api as GramJs, connection, + sessions, Api as GramJs, connection, } from '../../../lib/gramjs'; +import TelegramClient from '../../../lib/gramjs/client/TelegramClient'; + import { Logger as GramJsLogger } from '../../../lib/gramjs/extensions/index'; import { TwoFaParams } from '../../../lib/gramjs/client/2fa'; diff --git a/src/components/auth/Auth.tsx b/src/components/auth/Auth.tsx index 920112db..fe9e0c59 100644 --- a/src/components/auth/Auth.tsx +++ b/src/components/auth/Auth.tsx @@ -42,10 +42,11 @@ const Auth: FC = ({ } }; - useHistoryBack( - (!isMobile && authState === 'authorizationStateWaitPhoneNumber') - || (isMobile && authState === 'authorizationStateWaitQrCode'), handleChangeAuthorizationMethod, - ); + useHistoryBack({ + isActive: (!isMobile && authState === 'authorizationStateWaitPhoneNumber') + || (isMobile && authState === 'authorizationStateWaitQrCode'), + onBack: handleChangeAuthorizationMethod, + }); // Prevent refresh when rotating device useEffect(() => { diff --git a/src/components/auth/AuthCode.tsx b/src/components/auth/AuthCode.tsx index c32fe5d1..904fd0f7 100644 --- a/src/components/auth/AuthCode.tsx +++ b/src/components/auth/AuthCode.tsx @@ -45,7 +45,10 @@ const AuthCode: FC = ({ } }, []); - useHistoryBack(true, returnToAuthPhoneNumber); + useHistoryBack({ + isActive: true, + onBack: returnToAuthPhoneNumber, + }); const onCodeChange = useCallback((e: FormEvent) => { if (authError) { diff --git a/src/components/left/ArchivedChats.tsx b/src/components/left/ArchivedChats.tsx index 30bdd876..707d73c6 100644 --- a/src/components/left/ArchivedChats.tsx +++ b/src/components/left/ArchivedChats.tsx @@ -15,10 +15,13 @@ export type OwnProps = { onContentChange: (content: LeftColumnContent) => void; }; -const ArchivedChats: FC = ({ isActive, onReset, onContentChange }) => { +const ArchivedChats: FC = ({ isActive, onReset }) => { const lang = useLang(); - useHistoryBack(isActive, onReset, onContentChange, LeftColumnContent.Archived); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/main/ChatFolders.tsx b/src/components/left/main/ChatFolders.tsx index cf8c6a94..3bb620c3 100644 --- a/src/components/left/main/ChatFolders.tsx +++ b/src/components/left/main/ChatFolders.tsx @@ -134,7 +134,10 @@ const ChatFolders: FC = ({ } }) : undefined), [activeChatFolder, setActiveChatFolder]); - useHistoryBack(activeChatFolder !== 0, () => setActiveChatFolder(0, { forceOnHeavyAnimation: true })); + useHistoryBack({ + isActive: activeChatFolder !== 0, + onBack: () => setActiveChatFolder(0, { forceOnHeavyAnimation: true }), + }); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { diff --git a/src/components/left/main/ContactList.tsx b/src/components/left/main/ContactList.tsx index 04210ba0..c0a43289 100644 --- a/src/components/left/main/ContactList.tsx +++ b/src/components/left/main/ContactList.tsx @@ -58,7 +58,10 @@ const ContactList: FC = ({ }); }); - useHistoryBack(isActive, onReset); + useHistoryBack({ + isActive, + onBack: onReset, + }); const handleClick = useCallback((id: string) => { openChat({ id, shouldReplaceHistory: true }); diff --git a/src/components/left/main/LeftMainHeader.tsx b/src/components/left/main/LeftMainHeader.tsx index 7e35b301..40ac21cc 100644 --- a/src/components/left/main/LeftMainHeader.tsx +++ b/src/components/left/main/LeftMainHeader.tsx @@ -16,6 +16,7 @@ import { DEBUG, FEEDBACK_URL, IS_BETA, + IS_TEST, } from '../../../config'; import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment'; import buildClassName from '../../../util/buildClassName'; @@ -26,7 +27,6 @@ import { clearWebsync } from '../../../util/websync'; import { selectCurrentMessageList, selectTheme } from '../../../global/selectors'; import { isChatArchived } from '../../../global/helpers'; import useLang from '../../../hooks/useLang'; -import { disableHistoryBack } from '../../../hooks/useHistoryBack'; import useConnectionStatus from '../../../hooks/useConnectionStatus'; import DropdownMenu from '../../ui/DropdownMenu'; @@ -129,7 +129,7 @@ const LeftMainHeader: FC = ({ lang, connectionState, isSyncing, isMessageListOpen, isConnectionStatusMinimized, !areChatsLoaded, ); - const withOtherVersions = window.location.hostname === PRODUCTION_HOSTNAME; + const withOtherVersions = window.location.hostname === PRODUCTION_HOSTNAME || IS_TEST; const MainButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { return ({ onTrigger, isOpen }) => ( @@ -191,7 +191,6 @@ const LeftMainHeader: FC = ({ const handleSwitchToWebK = useCallback(() => { setPermanentWebVersion('K'); clearWebsync(); - disableHistoryBack(); }, []); const handleOpenTipsChat = useCallback(() => { @@ -302,7 +301,6 @@ const LeftMainHeader: FC = ({ Switch to Old Version diff --git a/src/components/left/newChat/NewChatStep1.tsx b/src/components/left/newChat/NewChatStep1.tsx index ccf43b35..3a2324e8 100644 --- a/src/components/left/newChat/NewChatStep1.tsx +++ b/src/components/left/newChat/NewChatStep1.tsx @@ -64,7 +64,10 @@ const NewChatStep1: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset); + useHistoryBack({ + isActive, + onBack: onReset, + }); const handleFilterChange = useCallback((query: string) => { setGlobalSearchQuery({ query }); diff --git a/src/components/left/newChat/NewChatStep2.tsx b/src/components/left/newChat/NewChatStep2.tsx index 05608c3a..2efdb331 100644 --- a/src/components/left/newChat/NewChatStep2.tsx +++ b/src/components/left/newChat/NewChatStep2.tsx @@ -46,7 +46,10 @@ const NewChatStep2: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset); + useHistoryBack({ + isActive, + onBack: onReset, + }); const [title, setTitle] = useState(''); const [about, setAbout] = useState(''); diff --git a/src/components/left/search/LeftSearch.tsx b/src/components/left/search/LeftSearch.tsx index fb87ae16..214c91d2 100644 --- a/src/components/left/search/LeftSearch.tsx +++ b/src/components/left/search/LeftSearch.tsx @@ -76,7 +76,10 @@ const LeftSearch: FC = ({ setGlobalSearchDate({ date: value.getTime() / 1000 }); }, [setGlobalSearchDate]); - useHistoryBack(isActive, onReset, undefined, undefined, true); + useHistoryBack({ + isActive, + onBack: onReset, + }); // eslint-disable-next-line no-null/no-null const containerRef = useRef(null); diff --git a/src/components/left/settings/Settings.tsx b/src/components/left/settings/Settings.tsx index 1a7943ac..861b7ef5 100644 --- a/src/components/left/settings/Settings.tsx +++ b/src/components/left/settings/Settings.tsx @@ -169,7 +169,6 @@ const Settings: FC = ({ case SettingsScreens.EditProfile: return ( @@ -188,15 +187,15 @@ const Settings: FC = ({ ); case SettingsScreens.QuickReaction: return ( - + ); case SettingsScreens.Notifications: return ( - + ); case SettingsScreens.DataStorage: return ( - + ); case SettingsScreens.Privacy: return ( @@ -208,7 +207,7 @@ const Settings: FC = ({ ); case SettingsScreens.Language: return ( - + ); case SettingsScreens.GeneralChatBackground: return ( @@ -221,7 +220,6 @@ const Settings: FC = ({ case SettingsScreens.GeneralChatBackgroundColor: return ( @@ -229,7 +227,6 @@ const Settings: FC = ({ case SettingsScreens.ActiveSessions: return ( @@ -237,7 +234,6 @@ const Settings: FC = ({ case SettingsScreens.PrivacyBlockedUsers: return ( diff --git a/src/components/left/settings/SettingsActiveSessions.tsx b/src/components/left/settings/SettingsActiveSessions.tsx index 03174e7f..453db1e0 100644 --- a/src/components/left/settings/SettingsActiveSessions.tsx +++ b/src/components/left/settings/SettingsActiveSessions.tsx @@ -5,7 +5,6 @@ import React, { import { getActions, withGlobal } from '../../../global'; import { ApiSession } from '../../../api/types'; -import { SettingsScreens } from '../../../types'; import { formatPastTimeShort } from '../../../util/dateFormat'; import useFlag from '../../../hooks/useFlag'; @@ -22,7 +21,6 @@ import RadioGroup from '../../ui/RadioGroup'; type OwnProps = { isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -34,7 +32,6 @@ type StateProps = { const SettingsActiveSessions: FC = ({ isActive, - onScreenSelect, onReset, byHash, orderedHashes, @@ -119,7 +116,10 @@ const SettingsActiveSessions: FC = ({ }, [byHash, orderedHashes]); const hasOtherSessions = Boolean(otherSessionHashes.length); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.ActiveSessions); + useHistoryBack({ + isActive, + onBack: onReset, + }); function renderCurrentSession(session: ApiSession) { return ( diff --git a/src/components/left/settings/SettingsDataStorage.tsx b/src/components/left/settings/SettingsDataStorage.tsx index c829ba58..d804605d 100644 --- a/src/components/left/settings/SettingsDataStorage.tsx +++ b/src/components/left/settings/SettingsDataStorage.tsx @@ -1,7 +1,7 @@ import React, { FC, memo, useCallback } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import { SettingsScreens, ISettings } from '../../../types'; +import { ISettings } from '../../../types'; import { AUTODOWNLOAD_FILESIZE_MB_LIMITS } from '../../../config'; import { pick } from '../../../util/iteratees'; @@ -13,7 +13,6 @@ import RangeSlider from '../../ui/RangeSlider'; type OwnProps = { isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -37,7 +36,6 @@ type StateProps = Pick = ({ isActive, - onScreenSelect, onReset, canAutoLoadPhotoFromContacts, canAutoLoadPhotoInPrivateChats, @@ -59,7 +57,10 @@ const SettingsDataStorage: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.General); + useHistoryBack({ + isActive, + onBack: onReset, + }); const renderFileSizeCallback = useCallback((value: number) => { return lang('AutodownloadSizeLimitUpTo', lang('FileSize.MB', String(AUTODOWNLOAD_FILESIZE_MB_LIMITS[value]), 'i')); diff --git a/src/components/left/settings/SettingsEditProfile.tsx b/src/components/left/settings/SettingsEditProfile.tsx index fc4d0939..75079da6 100644 --- a/src/components/left/settings/SettingsEditProfile.tsx +++ b/src/components/left/settings/SettingsEditProfile.tsx @@ -5,7 +5,7 @@ import React, { import { getActions, withGlobal } from '../../../global'; import { ApiMediaFormat } from '../../../api/types'; -import { ProfileEditProgress, SettingsScreens } from '../../../types'; +import { ProfileEditProgress } from '../../../types'; import { throttle } from '../../../util/schedulers'; import { selectUser } from '../../../global/selectors'; @@ -23,7 +23,6 @@ import useHistoryBack from '../../../hooks/useHistoryBack'; type OwnProps = { isActive: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -46,7 +45,6 @@ const ERROR_BIO_TOO_LONG = 'Bio can\' be longer than 70 characters'; const SettingsEditProfile: FC = ({ isActive, - onScreenSelect, onReset, currentAvatarHash, currentFirstName, @@ -87,7 +85,10 @@ const SettingsEditProfile: FC = ({ return Boolean(photo) || isProfileFieldsTouched || isUsernameAvailable === true; }, [photo, isProfileFieldsTouched, isUsernameError, isUsernameAvailable]); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.EditProfile); + useHistoryBack({ + isActive, + onBack: onReset, + }); // Due to the parent Transition, this component never gets unmounted, // that's why we use throttled API call on every update. diff --git a/src/components/left/settings/SettingsGeneral.tsx b/src/components/left/settings/SettingsGeneral.tsx index 8231f39f..d7b3e444 100644 --- a/src/components/left/settings/SettingsGeneral.tsx +++ b/src/components/left/settings/SettingsGeneral.tsx @@ -167,7 +167,10 @@ const SettingsGeneral: FC = ({ return stickerSetsById?.[id]?.installedDate ? stickerSetsById[id] : false; }).filter(Boolean as any); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.General); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/SettingsGeneralBackground.tsx b/src/components/left/settings/SettingsGeneralBackground.tsx index 2df6d5fc..80d9492b 100644 --- a/src/components/left/settings/SettingsGeneralBackground.tsx +++ b/src/components/left/settings/SettingsGeneralBackground.tsx @@ -108,7 +108,10 @@ const SettingsGeneralBackground: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.GeneralChatBackground); + useHistoryBack({ + isActive, + onBack: onReset, + }); const isUploading = loadedWallpapers?.[0] && loadedWallpapers[0].slug === UPLOADING_WALLPAPER_SLUG; diff --git a/src/components/left/settings/SettingsGeneralBackgroundColor.tsx b/src/components/left/settings/SettingsGeneralBackgroundColor.tsx index 43a3104d..3db73c2b 100644 --- a/src/components/left/settings/SettingsGeneralBackgroundColor.tsx +++ b/src/components/left/settings/SettingsGeneralBackgroundColor.tsx @@ -4,7 +4,7 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import { SettingsScreens, ThemeKey } from '../../../types'; +import { ThemeKey } from '../../../types'; import { pick } from '../../../util/iteratees'; import { @@ -22,7 +22,6 @@ import './SettingsGeneralBackgroundColor.scss'; type OwnProps = { isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -52,7 +51,6 @@ const PREDEFINED_COLORS = [ const SettingsGeneralBackground: FC = ({ isActive, - onScreenSelect, onReset, theme, backgroundColor, @@ -205,7 +203,10 @@ const SettingsGeneralBackground: FC = ({ isDragging && 'is-dragging', ); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.GeneralChatBackgroundColor); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/SettingsLanguage.tsx b/src/components/left/settings/SettingsLanguage.tsx index e0f3714d..9cceec0a 100644 --- a/src/components/left/settings/SettingsLanguage.tsx +++ b/src/components/left/settings/SettingsLanguage.tsx @@ -3,7 +3,7 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import { ISettings, LangCode, SettingsScreens } from '../../../types'; +import { ISettings, LangCode } from '../../../types'; import { ApiLanguage } from '../../../api/types'; import { setLanguage } from '../../../util/langProvider'; @@ -15,7 +15,6 @@ import useHistoryBack from '../../../hooks/useHistoryBack'; type OwnProps = { isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -23,7 +22,6 @@ type StateProps = Pick; const SettingsLanguage: FC = ({ isActive, - onScreenSelect, onReset, languages, language, @@ -56,7 +54,10 @@ const SettingsLanguage: FC = ({ return languages ? buildOptions(languages) : undefined; }, [languages]); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Language); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/SettingsMain.tsx b/src/components/left/settings/SettingsMain.tsx index 00274146..dc3944a5 100644 --- a/src/components/left/settings/SettingsMain.tsx +++ b/src/components/left/settings/SettingsMain.tsx @@ -43,7 +43,10 @@ const SettingsMain: FC = ({ } }, [lastSyncTime, profileId, loadProfilePhotos]); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Main); + useHistoryBack({ + isActive, + onBack: onReset, + }); useEffect(() => { if (lastSyncTime) { diff --git a/src/components/left/settings/SettingsNotifications.tsx b/src/components/left/settings/SettingsNotifications.tsx index a9169fe7..0a293090 100644 --- a/src/components/left/settings/SettingsNotifications.tsx +++ b/src/components/left/settings/SettingsNotifications.tsx @@ -5,8 +5,6 @@ import React, { } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import { SettingsScreens } from '../../../types'; - import useLang from '../../../hooks/useLang'; import useHistoryBack from '../../../hooks/useHistoryBack'; import { playNotifySound } from '../../../util/notifications'; @@ -16,7 +14,6 @@ import RangeSlider from '../../ui/RangeSlider'; type OwnProps = { isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -35,7 +32,6 @@ type StateProps = { const SettingsNotifications: FC = ({ isActive, - onScreenSelect, onReset, hasPrivateChatsNotifications, hasPrivateChatsMessagePreview, @@ -136,7 +132,10 @@ const SettingsNotifications: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Notifications); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/SettingsPrivacy.tsx b/src/components/left/settings/SettingsPrivacy.tsx index b8142395..22738267 100644 --- a/src/components/left/settings/SettingsPrivacy.tsx +++ b/src/components/left/settings/SettingsPrivacy.tsx @@ -59,7 +59,10 @@ const SettingsPrivacy: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Privacy); + useHistoryBack({ + isActive, + onBack: onReset, + }); function getVisibilityValue(visibility?: PrivacyVisibility) { switch (visibility) { diff --git a/src/components/left/settings/SettingsPrivacyBlockedUsers.tsx b/src/components/left/settings/SettingsPrivacyBlockedUsers.tsx index b28d8e10..4b37aa7f 100644 --- a/src/components/left/settings/SettingsPrivacyBlockedUsers.tsx +++ b/src/components/left/settings/SettingsPrivacyBlockedUsers.tsx @@ -4,7 +4,6 @@ import React, { import { getActions, withGlobal } from '../../../global'; import { ApiChat, ApiCountryCode, ApiUser } from '../../../api/types'; -import { SettingsScreens } from '../../../types'; import { CHAT_HEIGHT_PX } from '../../../config'; import { formatPhoneNumberWithCode } from '../../../util/phoneNumber'; @@ -25,7 +24,6 @@ import BlockUserModal from './BlockUserModal'; type OwnProps = { isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -38,7 +36,6 @@ type StateProps = { const SettingsPrivacyBlockedUsers: FC = ({ isActive, - onScreenSelect, onReset, chatsByIds, usersByIds, @@ -53,7 +50,10 @@ const SettingsPrivacyBlockedUsers: FC = ({ unblockContact({ contactId }); }, [unblockContact]); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.PrivacyBlockedUsers); + useHistoryBack({ + isActive, + onBack: onReset, + }); function renderContact(contactId: string, i: number, viewportOffset: number) { const isPrivate = isUserId(contactId); diff --git a/src/components/left/settings/SettingsPrivacyVisibility.tsx b/src/components/left/settings/SettingsPrivacyVisibility.tsx index d75c5e3d..65a4a3ed 100644 --- a/src/components/left/settings/SettingsPrivacyVisibility.tsx +++ b/src/components/left/settings/SettingsPrivacyVisibility.tsx @@ -84,7 +84,10 @@ const SettingsPrivacyVisibility: FC = ({ } }, [lang, screen]); - useHistoryBack(isActive, onReset, onScreenSelect, screen); + useHistoryBack({ + isActive, + onBack: onReset, + }); const descriptionText = useMemo(() => { switch (screen) { diff --git a/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx b/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx index c2aa65ff..fd65c83e 100644 --- a/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx +++ b/src/components/left/settings/SettingsPrivacyVisibilityExceptionList.tsx @@ -92,7 +92,10 @@ const SettingsPrivacyVisibilityExceptionList: FC = ({ onScreenSelect(SettingsScreens.Privacy); }, [isAllowList, newSelectedContactIds, onScreenSelect, screen, setPrivacySettings]); - useHistoryBack(isActive, onReset, onScreenSelect, screen); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/SettingsQuickReaction.tsx b/src/components/left/settings/SettingsQuickReaction.tsx index 47200359..2b780d9e 100644 --- a/src/components/left/settings/SettingsQuickReaction.tsx +++ b/src/components/left/settings/SettingsQuickReaction.tsx @@ -1,7 +1,6 @@ import React, { FC, memo, useCallback } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; -import { SettingsScreens } from '../../../types'; import { ApiAvailableReaction } from '../../../api/types'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -11,7 +10,6 @@ import RadioGroup from '../../ui/RadioGroup'; type OwnProps = { isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -23,12 +21,15 @@ type StateProps = { const SettingsQuickReaction: FC = ({ isActive, onReset, - onScreenSelect, availableReactions, selectedReaction, }) => { const { setDefaultReaction } = getActions(); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.General); + + useHistoryBack({ + isActive, + onBack: onReset, + }); const options = availableReactions?.filter((l) => !l.isInactive).map((l) => { return { diff --git a/src/components/left/settings/folders/SettingsFolders.tsx b/src/components/left/settings/folders/SettingsFolders.tsx index e384483f..2769c8dc 100644 --- a/src/components/left/settings/folders/SettingsFolders.tsx +++ b/src/components/left/settings/folders/SettingsFolders.tsx @@ -91,7 +91,6 @@ const SettingsFolders: FC = ({ = ({ onAddIncludedChats={handleAddIncludedChats} onAddExcludedChats={handleAddExcludedChats} onReset={handleReset} - onScreenSelect={onScreenSelect} isActive={isActive || [ SettingsScreens.FoldersIncludedChats, SettingsScreens.FoldersExcludedChats, @@ -127,7 +125,6 @@ const SettingsFolders: FC = ({ state={state} dispatch={dispatch} onReset={handleReset} - onScreenSelect={onScreenSelect} isActive={isActive} /> ); @@ -139,7 +136,6 @@ const SettingsFolders: FC = ({ state={state} dispatch={dispatch} onReset={handleReset} - onScreenSelect={onScreenSelect} isActive={isActive} /> ); diff --git a/src/components/left/settings/folders/SettingsFoldersChatFilters.tsx b/src/components/left/settings/folders/SettingsFoldersChatFilters.tsx index 4e80530e..d65575f7 100644 --- a/src/components/left/settings/folders/SettingsFoldersChatFilters.tsx +++ b/src/components/left/settings/folders/SettingsFoldersChatFilters.tsx @@ -3,8 +3,6 @@ import React, { } from '../../../../lib/teact/teact'; import { getGlobal } from '../../../../global'; -import { SettingsScreens } from '../../../../types'; - import { unique } from '../../../../util/iteratees'; import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID } from '../../../../config'; @@ -26,7 +24,6 @@ type OwnProps = { state: FoldersState; dispatch: FolderEditDispatch; isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -35,7 +32,6 @@ const SettingsFoldersChatFilters: FC = ({ state, dispatch, isActive, - onScreenSelect, onReset, }) => { const { chatFilter } = state; @@ -103,12 +99,10 @@ const SettingsFoldersChatFilters: FC = ({ } }, [mode, selectedChatIds, dispatch]); - useHistoryBack( + useHistoryBack({ isActive, - onReset, - onScreenSelect, - mode === 'included' ? SettingsScreens.FoldersIncludedChats : SettingsScreens.FoldersExcludedChats, - ); + onBack: onReset, + }); if (!displayedIds) { return ; diff --git a/src/components/left/settings/folders/SettingsFoldersEdit.tsx b/src/components/left/settings/folders/SettingsFoldersEdit.tsx index 837414af..959ce000 100644 --- a/src/components/left/settings/folders/SettingsFoldersEdit.tsx +++ b/src/components/left/settings/folders/SettingsFoldersEdit.tsx @@ -3,8 +3,6 @@ import React, { } from '../../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../../global'; -import { SettingsScreens } from '../../../../types'; - import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config'; import { findIntersectionWithSet } from '../../../../util/iteratees'; import { isUserId } from '../../../../global/helpers'; @@ -34,7 +32,6 @@ type OwnProps = { onAddIncludedChats: () => void; onAddExcludedChats: () => void; isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; onBack: () => void; }; @@ -57,7 +54,6 @@ const SettingsFoldersEdit: FC = ({ onAddIncludedChats, onAddExcludedChats, isActive, - onScreenSelect, onReset, onBack, loadedActiveChatIds, @@ -120,9 +116,10 @@ const SettingsFoldersEdit: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onBack, onScreenSelect, state.mode === 'edit' - ? SettingsScreens.FoldersEditFolder - : SettingsScreens.FoldersCreateFolder); + useHistoryBack({ + isActive, + onBack, + }); const handleChange = useCallback((event: React.ChangeEvent) => { const { currentTarget } = event; diff --git a/src/components/left/settings/folders/SettingsFoldersMain.tsx b/src/components/left/settings/folders/SettingsFoldersMain.tsx index f55da959..8d393279 100644 --- a/src/components/left/settings/folders/SettingsFoldersMain.tsx +++ b/src/components/left/settings/folders/SettingsFoldersMain.tsx @@ -4,7 +4,6 @@ import React, { import { getActions, withGlobal } from '../../../../global'; import { ApiChatFolder } from '../../../../api/types'; -import { SettingsScreens } from '../../../../types'; import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config'; import { throttle } from '../../../../util/schedulers'; @@ -23,7 +22,6 @@ type OwnProps = { isActive?: boolean; onCreateFolder: () => void; onEditFolder: (folder: ApiChatFolder) => void; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -41,7 +39,6 @@ const SettingsFoldersMain: FC = ({ isActive, onCreateFolder, onEditFolder, - onScreenSelect, onReset, orderedFolderIds, foldersById, @@ -88,7 +85,10 @@ const SettingsFoldersMain: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Folders); + useHistoryBack({ + isActive, + onBack: onReset, + }); const chatsCountByFolderId = useFolderManagerForChatsCount(); const userFolders = useMemo(() => { diff --git a/src/components/left/settings/twoFa/SettingsTwoFa.tsx b/src/components/left/settings/twoFa/SettingsTwoFa.tsx index e0e7e872..f8bab2dc 100644 --- a/src/components/left/settings/twoFa/SettingsTwoFa.tsx +++ b/src/components/left/settings/twoFa/SettingsTwoFa.tsx @@ -161,7 +161,6 @@ const SettingsTwoFa: FC = ({ return ( = ({ case SettingsScreens.TwoFaNewPassword: return ( = ({ case SettingsScreens.TwoFaNewPasswordConfirm: return ( = ({ icon="hint" placeholder={lang('PasswordHintPlaceholder')} onSubmit={handleNewPasswordHint} - screen={currentScreen} - onScreenSelect={onScreenSelect} isActive={isActive || [ SettingsScreens.TwoFaNewPasswordEmail, SettingsScreens.TwoFaNewPasswordEmailCode, @@ -240,8 +233,6 @@ const SettingsTwoFa: FC = ({ placeholder={lang('RecoveryEmailTitle')} shouldConfirm onSubmit={handleNewPasswordEmail} - screen={currentScreen} - onScreenSelect={onScreenSelect} isActive={isActive || [ SettingsScreens.TwoFaNewPasswordEmailCode, SettingsScreens.TwoFaCongratulations, @@ -257,8 +248,6 @@ const SettingsTwoFa: FC = ({ error={error} clearError={clearTwoFaError} onSubmit={handleEmailCode} - screen={currentScreen} - onScreenSelect={onScreenSelect} isActive={isActive || shownScreen === SettingsScreens.TwoFaCongratulations} onReset={onReset} /> @@ -295,13 +284,11 @@ const SettingsTwoFa: FC = ({ case SettingsScreens.TwoFaChangePasswordCurrent: return ( = ({ case SettingsScreens.TwoFaChangePasswordNew: return ( = ({ case SettingsScreens.TwoFaChangePasswordConfirm: return ( = ({ icon="hint" placeholder={lang('PasswordHintPlaceholder')} onSubmit={handleChangePasswordHint} - onScreenSelect={onScreenSelect} isActive={isActive || shownScreen === SettingsScreens.TwoFaCongratulations} onReset={onReset} - screen={currentScreen} /> ); @@ -368,23 +349,19 @@ const SettingsTwoFa: FC = ({ clearError={clearTwoFaError} hint={hint} onSubmit={handleTurnOff} - onScreenSelect={onScreenSelect} isActive={isActive} onReset={onReset} - screen={currentScreen} /> ); case SettingsScreens.TwoFaRecoveryEmailCurrentPassword: return ( = ({ case SettingsScreens.TwoFaRecoveryEmail: return ( = ({ case SettingsScreens.TwoFaRecoveryEmailCode: return ( diff --git a/src/components/left/settings/twoFa/SettingsTwoFaCongratulations.tsx b/src/components/left/settings/twoFa/SettingsTwoFaCongratulations.tsx index b62a5200..e3c590a8 100644 --- a/src/components/left/settings/twoFa/SettingsTwoFaCongratulations.tsx +++ b/src/components/left/settings/twoFa/SettingsTwoFaCongratulations.tsx @@ -30,7 +30,10 @@ const SettingsTwoFaCongratulations: FC = ({ onScreenSelect(SettingsScreens.Privacy); }, [onScreenSelect]); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaCongratulations); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/twoFa/SettingsTwoFaEmailCode.tsx b/src/components/left/settings/twoFa/SettingsTwoFaEmailCode.tsx index d8afc319..c83d68e4 100644 --- a/src/components/left/settings/twoFa/SettingsTwoFaEmailCode.tsx +++ b/src/components/left/settings/twoFa/SettingsTwoFaEmailCode.tsx @@ -4,7 +4,6 @@ import React, { import { withGlobal } from '../../../../global'; import { ApiSticker } from '../../../../api/types'; -import { SettingsScreens } from '../../../../types'; import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment'; import { selectAnimatedEmoji } from '../../../../global/selectors'; @@ -21,9 +20,7 @@ type OwnProps = { clearError: NoneToVoidFunction; onSubmit: (hint: string) => void; isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; - screen: SettingsScreens; }; type StateProps = { @@ -41,9 +38,7 @@ const SettingsTwoFaEmailCode: FC = ({ clearError, onSubmit, isActive, - onScreenSelect, onReset, - screen, }) => { // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); @@ -60,7 +55,10 @@ const SettingsTwoFaEmailCode: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, screen); + useHistoryBack({ + isActive, + onBack: onReset, + }); const handleInputChange = useCallback((e: React.ChangeEvent) => { if (error && clearError) { diff --git a/src/components/left/settings/twoFa/SettingsTwoFaEnabled.tsx b/src/components/left/settings/twoFa/SettingsTwoFaEnabled.tsx index 5bd707ea..cab359dc 100644 --- a/src/components/left/settings/twoFa/SettingsTwoFaEnabled.tsx +++ b/src/components/left/settings/twoFa/SettingsTwoFaEnabled.tsx @@ -27,7 +27,10 @@ const SettingsTwoFaEnabled: FC = ({ }) => { const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaEnabled); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/twoFa/SettingsTwoFaPassword.tsx b/src/components/left/settings/twoFa/SettingsTwoFaPassword.tsx index 949307eb..822d1021 100644 --- a/src/components/left/settings/twoFa/SettingsTwoFaPassword.tsx +++ b/src/components/left/settings/twoFa/SettingsTwoFaPassword.tsx @@ -2,8 +2,6 @@ import React, { FC, memo, useCallback, useState, } from '../../../../lib/teact/teact'; -import { SettingsScreens } from '../../../../types'; - import useLang from '../../../../hooks/useLang'; import useHistoryBack from '../../../../hooks/useHistoryBack'; @@ -11,7 +9,6 @@ import PasswordMonkey from '../../../common/PasswordMonkey'; import PasswordForm from '../../../common/PasswordForm'; type OwnProps = { - screen: SettingsScreens; error?: string; isLoading?: boolean; expectedPassword?: string; @@ -21,16 +18,13 @@ type OwnProps = { clearError?: NoneToVoidFunction; onSubmit: (password: string) => void; isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; const EQUAL_PASSWORD_ERROR = 'Passwords Should Be Equal'; const SettingsTwoFaPassword: FC = ({ - screen, isActive, - onScreenSelect, onReset, error, isLoading, @@ -61,7 +55,10 @@ const SettingsTwoFaPassword: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, screen); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/twoFa/SettingsTwoFaSkippableForm.tsx b/src/components/left/settings/twoFa/SettingsTwoFaSkippableForm.tsx index e495a543..9fcacfbd 100644 --- a/src/components/left/settings/twoFa/SettingsTwoFaSkippableForm.tsx +++ b/src/components/left/settings/twoFa/SettingsTwoFaSkippableForm.tsx @@ -4,7 +4,6 @@ import React, { import { withGlobal } from '../../../../global'; import { ApiSticker } from '../../../../api/types'; -import { SettingsScreens } from '../../../../types'; import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment'; import { selectAnimatedEmoji } from '../../../../global/selectors'; @@ -28,9 +27,7 @@ type OwnProps = { clearError?: NoneToVoidFunction; onSubmit: (value?: string) => void; isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; - screen: SettingsScreens; }; type StateProps = { @@ -49,9 +46,7 @@ const SettingsTwoFaSkippableForm: FC = ({ clearError, onSubmit, isActive, - onScreenSelect, onReset, - screen, }) => { // eslint-disable-next-line no-null/no-null const inputRef = useRef(null); @@ -96,7 +91,10 @@ const SettingsTwoFaSkippableForm: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, screen); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/left/settings/twoFa/SettingsTwoFaStart.tsx b/src/components/left/settings/twoFa/SettingsTwoFaStart.tsx index dcc331e7..0b080549 100644 --- a/src/components/left/settings/twoFa/SettingsTwoFaStart.tsx +++ b/src/components/left/settings/twoFa/SettingsTwoFaStart.tsx @@ -2,7 +2,6 @@ import React, { FC, memo } from '../../../../lib/teact/teact'; import { withGlobal } from '../../../../global'; import { ApiSticker } from '../../../../api/types'; -import { SettingsScreens } from '../../../../types'; import { selectAnimatedEmoji } from '../../../../global/selectors'; import useLang from '../../../../hooks/useLang'; @@ -14,7 +13,6 @@ import AnimatedEmoji from '../../../common/AnimatedEmoji'; type OwnProps = { onStart: NoneToVoidFunction; isActive?: boolean; - onScreenSelect: (screen: SettingsScreens) => void; onReset: () => void; }; @@ -23,11 +21,14 @@ type StateProps = { }; const SettingsTwoFaStart: FC = ({ - isActive, onScreenSelect, onReset, animatedEmoji, onStart, + isActive, onReset, animatedEmoji, onStart, }) => { const lang = useLang(); - useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaDisabled); + useHistoryBack({ + isActive, + onBack: onReset, + }); return (
diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index d8609f36..e7f8c73b 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -54,6 +54,7 @@ import HistoryCalendar from './HistoryCalendar.async'; import GroupCall from '../calls/group/GroupCall.async'; import ActiveCallHeader from '../calls/ActiveCallHeader.async'; import PhoneCall from '../calls/phone/PhoneCall.async'; +import MessageListHistoryHandler from '../middle/MessageListHistoryHandler'; import NewContactModal from './NewContactModal.async'; import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async'; import WebAppModal from './WebAppModal.async'; @@ -387,6 +388,7 @@ const Main: FC = ({ +
); }; diff --git a/src/components/mediaViewer/MediaViewer.tsx b/src/components/mediaViewer/MediaViewer.tsx index 15970052..7fc38083 100644 --- a/src/components/mediaViewer/MediaViewer.tsx +++ b/src/components/mediaViewer/MediaViewer.tsx @@ -397,12 +397,9 @@ const MediaViewer: FC = ({ const lang = useLang(); - useHistoryBack(isOpen, closeMediaViewer, openMediaViewer, { - chatId, - threadId, - messageId, - origin, - avatarOwnerId: avatarOwner && avatarOwner.id, + useHistoryBack({ + isActive: isOpen, + onBack: closeMediaViewer, }); useEffect(() => { diff --git a/src/components/middle/MessageListHistoryHandler.tsx b/src/components/middle/MessageListHistoryHandler.tsx new file mode 100644 index 00000000..7267f67a --- /dev/null +++ b/src/components/middle/MessageListHistoryHandler.tsx @@ -0,0 +1,49 @@ +import React, { FC, memo } from '../../lib/teact/teact'; +import { getActions, withGlobal } from '../../lib/teact/teactn'; + +import { createMessageHash } from '../../util/routing'; +import useHistoryBack from '../../hooks/useHistoryBack'; +import { MessageList as GlobalMessageList } from '../../global/types'; + +type StateProps = { + messageLists?: GlobalMessageList[]; +}; + +// Actual `MessageList` components are unmounted when deep in the history, +// so we need a separate component just for handling history +const MessageListHistoryHandler: FC = ({ messageLists }) => { + const { openChat } = getActions(); + + const closeChat = () => { + openChat({ id: undefined }, { forceSyncOnIOs: true }); + }; + + const MessageHistoryRecord: FC = ({ chatId, type, threadId }) => { + useHistoryBack({ + isActive: true, + hash: createMessageHash(chatId, type, threadId), + onBack: closeChat, + }); + }; + + return ( +
+ {messageLists?.map((messageList, i) => ( + + ))} +
+ ); +}; + +export default memo(withGlobal( + (global): StateProps => { + return { + messageLists: global.messages.messageLists, + }; + }, +)(MessageListHistoryHandler)); diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index 90c8cccf..7fb66231 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -6,7 +6,6 @@ import { getActions, withGlobal } from '../../global'; import { ApiChatBannedRights, MAIN_THREAD_ID } from '../../api/types'; import { MessageListType, - MessageList as GlobalMessageList, ActiveEmojiInteraction, } from '../../global/types'; import { ThemeKey } from '../../types'; @@ -48,7 +47,6 @@ import { } from '../../global/helpers'; import captureEscKeyListener from '../../util/captureEscKeyListener'; import buildClassName from '../../util/buildClassName'; -import { createMessageHash } from '../../util/routing'; import useCustomBackground from '../../hooks/useCustomBackground'; import useWindowSize from '../../hooks/useWindowSize'; import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation'; @@ -104,7 +102,6 @@ type StateProps = { animationLevel?: number; shouldSkipHistoryAnimations?: boolean; currentTransitionKey: number; - messageLists?: GlobalMessageList[]; isChannel?: boolean; areChatSettingsLoaded?: boolean; canSubscribe?: boolean; @@ -126,7 +123,6 @@ const MiddleColumn: FC = ({ messageListType, isPrivate, isPinnedMessageList, - messageLists, canPost, currentUserBannedRights, defaultBannedRights, @@ -158,6 +154,7 @@ const MiddleColumn: FC = ({ }) => { const { openChat, + openPreviousChat, unpinAllMessages, loadUser, loadChatSettings, @@ -293,8 +290,8 @@ const MiddleColumn: FC = ({ const handleUnpinAllMessages = useCallback(() => { unpinAllMessages({ chatId }); closeUnpinModal(); - openChat({ id: chatId }); - }, [unpinAllMessages, openChat, closeUnpinModal, chatId]); + openPreviousChat(); + }, [unpinAllMessages, chatId, closeUnpinModal, openPreviousChat]); const handleTabletFocus = useCallback(() => { openChat({ id: chatId }); @@ -347,21 +344,15 @@ const MiddleColumn: FC = ({ renderingCanPost && isNotchShown && !isSelectModeActive && 'with-notch', ); - const closeChat = () => { - openChat({ id: undefined }, { forceSyncOnIOs: true }); - }; + useHistoryBack({ + isActive: isSelectModeActive, + onBack: exitMessageSelectMode, + }); - useHistoryBack( - renderingChatId && renderingThreadId, - closeChat, - undefined, - undefined, - undefined, - messageLists?.map(createMessageHash) || [], - ); - - useHistoryBack(isMobileSearchActive, closeLocalTextSearch); - useHistoryBack(isSelectModeActive, exitMessageSelectMode); + useHistoryBack({ + isActive: isMobileSearchActive, + onBack: closeLocalTextSearch, + }); const isMessagingDisabled = Boolean( !isPinnedMessageList && !renderingCanPost && !renderingCanRestartBot && !renderingCanStartBot @@ -573,7 +564,7 @@ export default memo(withGlobal( isSeenByModalOpen: Boolean(global.seenByModal), isReactorListModalOpen: Boolean(global.reactorModal), animationLevel: global.settings.byKey.animationLevel, - currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1), + currentTransitionKey: Math.max(0, messageLists.length - 1), activeEmojiInteractions, lastSyncTime, }; @@ -620,7 +611,6 @@ export default memo(withGlobal( ), pinnedMessagesCount: pinnedIds ? pinnedIds.length : 0, shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations, - messageLists, isChannel, canSubscribe, canStartBot, diff --git a/src/components/right/AddChatMembers.tsx b/src/components/right/AddChatMembers.tsx index 3e7bc364..ea8e2b7b 100644 --- a/src/components/right/AddChatMembers.tsx +++ b/src/components/right/AddChatMembers.tsx @@ -73,7 +73,10 @@ const AddChatMembers: FC = ({ } }, [connectionState, isActive, loadContactList]); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const memberIds = useMemo(() => { return members ? members.map((member) => member.userId) : []; diff --git a/src/components/right/GifSearch.tsx b/src/components/right/GifSearch.tsx index 343ba865..57ef416d 100644 --- a/src/components/right/GifSearch.tsx +++ b/src/components/right/GifSearch.tsx @@ -91,7 +91,10 @@ const GifSearch: FC = ({ const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); function renderContent() { if (query === undefined) { diff --git a/src/components/right/PollResults.tsx b/src/components/right/PollResults.tsx index a7f52786..5d95501e 100644 --- a/src/components/right/PollResults.tsx +++ b/src/components/right/PollResults.tsx @@ -33,7 +33,10 @@ const PollResults: FC = ({ lastSyncTime, }) => { const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); if (!message || !chat) { return ; diff --git a/src/components/right/RightColumn.tsx b/src/components/right/RightColumn.tsx index 3413e89f..b9785dd5 100644 --- a/src/components/right/RightColumn.tsx +++ b/src/components/right/RightColumn.tsx @@ -223,11 +223,13 @@ const RightColumn: FC = ({ } }, [chatId]); - useHistoryBack(isChatSelected && ( - contentKey === RightColumnContent.ChatInfo - || contentKey === RightColumnContent.Management - || contentKey === RightColumnContent.AddingMembers - ), () => close(false), toggleChatInfo); + useHistoryBack({ + isActive: isChatSelected && ( + contentKey === RightColumnContent.ChatInfo + || contentKey === RightColumnContent.Management + || contentKey === RightColumnContent.AddingMembers), + onBack: () => close(false), + }); // eslint-disable-next-line consistent-return function renderContent(isActive: boolean) { diff --git a/src/components/right/RightSearch.tsx b/src/components/right/RightSearch.tsx index 58969cd5..f0b83223 100644 --- a/src/components/right/RightSearch.tsx +++ b/src/components/right/RightSearch.tsx @@ -66,7 +66,10 @@ const RightSearch: FC = ({ const containerRef = useRef(null); const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const [viewportIds, getMore] = useInfiniteScroll(searchTextMessagesLocal, foundIds); diff --git a/src/components/right/StickerSearch.tsx b/src/components/right/StickerSearch.tsx index 6935caf3..7d1bf385 100644 --- a/src/components/right/StickerSearch.tsx +++ b/src/components/right/StickerSearch.tsx @@ -57,7 +57,10 @@ const StickerSearch: FC = ({ }); }); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); function renderContent() { if (query === undefined) { diff --git a/src/components/right/management/ManageChannel.tsx b/src/components/right/management/ManageChannel.tsx index 065281c0..8321cdac 100644 --- a/src/components/right/management/ManageChannel.tsx +++ b/src/components/right/management/ManageChannel.tsx @@ -84,7 +84,10 @@ const ManageChannel: FC = ({ const currentAvatarBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl); const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); useEffect(() => { if (lastSyncTime) { diff --git a/src/components/right/management/ManageChatAdministrators.tsx b/src/components/right/management/ManageChatAdministrators.tsx index a4eb8b57..ba7bd29d 100644 --- a/src/components/right/management/ManageChatAdministrators.tsx +++ b/src/components/right/management/ManageChatAdministrators.tsx @@ -40,7 +40,10 @@ const ManageChatAdministrators: FC = ({ }) => { const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const handleRecentActionsClick = useCallback(() => { onScreenSelect(ManagementScreens.GroupRecentActions); diff --git a/src/components/right/management/ManageChatPrivacyType.tsx b/src/components/right/management/ManageChatPrivacyType.tsx index eaecf21c..c48860d0 100644 --- a/src/components/right/management/ManageChatPrivacyType.tsx +++ b/src/components/right/management/ManageChatPrivacyType.tsx @@ -65,7 +65,10 @@ const ManageChatPrivacyType: FC = ({ || (privacyType === 'private' && isPublic), ); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); useEffect(() => { if (privacyType && !privateLink) { diff --git a/src/components/right/management/ManageChatRemovedUsers.tsx b/src/components/right/management/ManageChatRemovedUsers.tsx index caccd8a2..077a4bbc 100644 --- a/src/components/right/management/ManageChatRemovedUsers.tsx +++ b/src/components/right/management/ManageChatRemovedUsers.tsx @@ -42,7 +42,10 @@ const ManageChatRemovedUsers: FC = ({ const lang = useLang(); const [isRemoveUserModalOpen, openRemoveUserModal, closeRemoveUserModal] = useFlag(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const removedMembers = useMemo(() => { if (!chat || !chat.fullInfo || !chat.fullInfo.kickedMembers) { diff --git a/src/components/right/management/ManageDiscussion.tsx b/src/components/right/management/ManageDiscussion.tsx index 7ff1af4e..84300068 100644 --- a/src/components/right/management/ManageDiscussion.tsx +++ b/src/components/right/management/ManageDiscussion.tsx @@ -63,7 +63,10 @@ const ManageDiscussion: FC = ({ const lang = useLang(); const linkedChatId = linkedChat?.id; - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); useEffect(() => { loadGroupsForDiscussion(); diff --git a/src/components/right/management/ManageGroup.tsx b/src/components/right/management/ManageGroup.tsx index 723fc38e..a855e320 100644 --- a/src/components/right/management/ManageGroup.tsx +++ b/src/components/right/management/ManageGroup.tsx @@ -96,7 +96,10 @@ const ManageGroup: FC = ({ const isPublicGroup = chat.username || hasLinkedChannel; const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); useEffect(() => { if (lastSyncTime && canInvite) { diff --git a/src/components/right/management/ManageGroupAdminRights.tsx b/src/components/right/management/ManageGroupAdminRights.tsx index 06cbc91b..08423f6b 100644 --- a/src/components/right/management/ManageGroupAdminRights.tsx +++ b/src/components/right/management/ManageGroupAdminRights.tsx @@ -63,7 +63,10 @@ const ManageGroupAdminRights: FC = ({ const [customTitle, setCustomTitle] = useState(''); const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const selectedChatMember = useMemo(() => { const selectedAdminMember = chat.fullInfo?.adminMembers?.find(({ userId }) => userId === selectedUserId); diff --git a/src/components/right/management/ManageGroupMembers.tsx b/src/components/right/management/ManageGroupMembers.tsx index 5cff982b..b4cce639 100644 --- a/src/components/right/management/ManageGroupMembers.tsx +++ b/src/components/right/management/ManageGroupMembers.tsx @@ -139,7 +139,10 @@ const ManageGroupMembers: FC = ({ } }, '.ListItem-button', true); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); function renderSearchField() { return ( diff --git a/src/components/right/management/ManageGroupPermissions.tsx b/src/components/right/management/ManageGroupPermissions.tsx index 77926903..a389d8c3 100644 --- a/src/components/right/management/ManageGroupPermissions.tsx +++ b/src/components/right/management/ManageGroupPermissions.tsx @@ -69,7 +69,10 @@ const ManageGroupPermissions: FC = ({ const [isLoading, setIsLoading] = useState(false); const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const handleRemovedUsersClick = useCallback(() => { onScreenSelect(ManagementScreens.GroupRemovedUsers); diff --git a/src/components/right/management/ManageGroupRecentActions.tsx b/src/components/right/management/ManageGroupRecentActions.tsx index 4257b015..86da6249 100644 --- a/src/components/right/management/ManageGroupRecentActions.tsx +++ b/src/components/right/management/ManageGroupRecentActions.tsx @@ -25,7 +25,10 @@ type StateProps = { const ManageGroupRecentActions: FC = ({ chat, onClose, isActive }) => { const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const adminMembers = useMemo(() => { if (!chat || !chat.fullInfo || !chat.fullInfo.adminMembers) { diff --git a/src/components/right/management/ManageGroupUserPermissions.tsx b/src/components/right/management/ManageGroupUserPermissions.tsx index 88063ea6..6f3eaffe 100644 --- a/src/components/right/management/ManageGroupUserPermissions.tsx +++ b/src/components/right/management/ManageGroupUserPermissions.tsx @@ -48,7 +48,10 @@ const ManageGroupUserPermissions: FC = ({ const [isBanConfirmationDialogOpen, openBanConfirmationDialog, closeBanConfirmationDialog] = useFlag(); const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const selectedChatMember = useMemo(() => { if (!chat || !chat.fullInfo || !chat.fullInfo.members) { diff --git a/src/components/right/management/ManageGroupUserPermissionsCreate.tsx b/src/components/right/management/ManageGroupUserPermissionsCreate.tsx index be7f4905..ce1a03ef 100644 --- a/src/components/right/management/ManageGroupUserPermissionsCreate.tsx +++ b/src/components/right/management/ManageGroupUserPermissionsCreate.tsx @@ -41,7 +41,10 @@ const ManageGroupUserPermissionsCreate: FC = ({ isActive, serverTimeOffset, }) => { - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const memberIds = useMemo(() => { if (!members || !usersById) { diff --git a/src/components/right/management/ManageInvite.tsx b/src/components/right/management/ManageInvite.tsx index 5e325fc2..41992544 100644 --- a/src/components/right/management/ManageInvite.tsx +++ b/src/components/right/management/ManageInvite.tsx @@ -61,7 +61,10 @@ const ManageInvite: FC = ({ const [selectedUsageOption, setSelectedUsageOption] = useState('0'); const [isSubmitBlocked, setIsSubmitBlocked] = useState(false); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); useOnChange(([oldEditingInvite]) => { if (oldEditingInvite === editingInvite) return; diff --git a/src/components/right/management/ManageInviteInfo.tsx b/src/components/right/management/ManageInviteInfo.tsx index 14630d6b..ddab5119 100644 --- a/src/components/right/management/ManageInviteInfo.tsx +++ b/src/components/right/management/ManageInviteInfo.tsx @@ -71,7 +71,10 @@ const ManageInviteInfo: FC = ({ }); }, [invite, lang, showNotification]); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const renderImporters = () => { if (!importers?.length && requesters?.length) return undefined; diff --git a/src/components/right/management/ManageInvites.tsx b/src/components/right/management/ManageInvites.tsx index 12783358..f237ae11 100644 --- a/src/components/right/management/ManageInvites.tsx +++ b/src/components/right/management/ManageInvites.tsx @@ -91,7 +91,10 @@ const ManageInvites: FC = ({ } }, [animationData]); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const hasDetailedCountdown = useMemo(() => { if (!exportedInvites) return undefined; diff --git a/src/components/right/management/ManageJoinRequests.tsx b/src/components/right/management/ManageJoinRequests.tsx index 10ff3603..3c2584e2 100644 --- a/src/components/right/management/ManageJoinRequests.tsx +++ b/src/components/right/management/ManageJoinRequests.tsx @@ -54,7 +54,10 @@ const ManageJoinRequests: FC = ({ } }, [animationData]); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); useEffect(() => { if (!chat?.joinRequests && !isUserId(chatId)) { diff --git a/src/components/right/management/ManageReactions.tsx b/src/components/right/management/ManageReactions.tsx index 0b879764..ac373549 100644 --- a/src/components/right/management/ManageReactions.tsx +++ b/src/components/right/management/ManageReactions.tsx @@ -40,7 +40,10 @@ const ManageReactions: FC = ({ const [isLoading, setIsLoading] = useState(false); const [localEnabledReactions, setLocalEnabledReactions] = useState(enabledReactions || []); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const handleSaveReactions = useCallback(() => { if (!chat) return; diff --git a/src/components/right/management/ManageUser.tsx b/src/components/right/management/ManageUser.tsx index 50b128c5..2dfa6b29 100644 --- a/src/components/right/management/ManageUser.tsx +++ b/src/components/right/management/ManageUser.tsx @@ -58,7 +58,10 @@ const ManageUser: FC = ({ const [error, setError] = useState(); const lang = useLang(); - useHistoryBack(isActive, onClose); + useHistoryBack({ + isActive, + onBack: onClose, + }); const currentFirstName = user ? (user.firstName || '') : ''; const currentLastName = user ? (user.lastName || '') : ''; diff --git a/src/components/ui/Menu.tsx b/src/components/ui/Menu.tsx index 010de856..8226a712 100644 --- a/src/components/ui/Menu.tsx +++ b/src/components/ui/Menu.tsx @@ -34,7 +34,7 @@ type OwnProps = { noCompact?: boolean; onKeyDown?: (e: React.KeyboardEvent) => void; onCloseAnimationEnd?: () => void; - onClose?: () => void; + onClose: () => void; onMouseEnter?: (e: React.MouseEvent) => void; onMouseLeave?: (e: React.MouseEvent) => void; children: React.ReactNode; @@ -84,11 +84,15 @@ const Menu: FC = ({ ); useEffect( - () => (isOpen && onClose ? captureEscKeyListener(onClose) : undefined), + () => (isOpen ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose], ); - useHistoryBack(isOpen, onClose, undefined, undefined, autoClose); + useHistoryBack({ + isActive: isOpen, + onBack: onClose, + shouldBeReplaced: true, + }); useEffectWithPrevDeps(([prevIsOpen]) => { if (isOpen || (!isOpen && prevIsOpen === true)) { diff --git a/src/components/ui/MenuItem.tsx b/src/components/ui/MenuItem.tsx index 7d3a799d..f96da748 100644 --- a/src/components/ui/MenuItem.tsx +++ b/src/components/ui/MenuItem.tsx @@ -1,5 +1,6 @@ import React, { FC, useCallback } from '../../lib/teact/teact'; +import { IS_TEST } from '../../config'; import buildClassName from '../../util/buildClassName'; import useLang from '../../hooks/useLang'; import { IS_COMPACT_MENU } from '../../util/environment'; @@ -91,7 +92,7 @@ const MenuItem: FC = (props) => { download={download} aria-label={ariaLabel} title={ariaLabel} - target={href.startsWith(window.location.origin) ? '_self' : '_blank'} + target={href.startsWith(window.location.origin) || IS_TEST ? '_self' : '_blank'} rel="noopener noreferrer" dir={lang.isRtl ? 'rtl' : undefined} onClick={onClick} diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index 477b2cc2..705a7fcf 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -67,17 +67,10 @@ const Modal: FC = ({ : undefined), [isOpen, onClose, onEnter]); useEffect(() => (isOpen && modalRef.current ? trapFocus(modalRef.current) : undefined), [isOpen]); - const { forceClose } = useHistoryBack(isOpen, onClose); - - // For modals that are closed by unmounting without changing `isOpen` to `false` - useEffect(() => { - return () => { - if (isOpen) { - forceClose(); - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + useHistoryBack({ + isActive: isOpen, + onBack: onClose, + }); useEffectWithPrevDeps(([prevIsOpen]) => { document.body.classList.toggle('has-open-dialog', isOpen); diff --git a/src/config.ts b/src/config.ts index b7ddea42..16781e3b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ export const APP_VERSION = process.env.APP_VERSION!; export const DEBUG = process.env.APP_ENV !== 'production'; export const DEBUG_MORE = false; +export const IS_MOCKED_CLIENT = process.env.APP_MOCKED_CLIENT === '1'; export const IS_TEST = process.env.APP_ENV === 'test'; export const IS_PERF = process.env.APP_ENV === 'perf'; export const IS_BETA = process.env.APP_ENV === 'staging'; diff --git a/src/global/init.ts b/src/global/init.ts index cad5c209..27c6cd4e 100644 --- a/src/global/init.ts +++ b/src/global/init.ts @@ -3,10 +3,13 @@ import { addActionHandler } from './index'; import { INITIAL_STATE } from './initialState'; import { initCache, loadCache } from './cache'; import { cloneDeep } from '../util/iteratees'; +import { IS_MOCKED_CLIENT } from '../config'; initCache(); addActionHandler('init', () => { const initial = cloneDeep(INITIAL_STATE); - return loadCache(initial) || initial; + const state = loadCache(initial) || initial; + if (IS_MOCKED_CLIENT) state.authState = 'authorizationStateReady'; + return state; }); diff --git a/src/global/reducers/messages.ts b/src/global/reducers/messages.ts index 692f3c9e..02d533c1 100644 --- a/src/global/reducers/messages.ts +++ b/src/global/reducers/messages.ts @@ -7,6 +7,7 @@ import { import { FocusDirection } from '../../types'; import { + IS_MOCKED_CLIENT, IS_TEST, MESSAGE_LIST_SLICE, MESSAGE_LIST_VIEWPORT_LIMIT, TMP_CHAT_ID, } from '../../config'; import { @@ -41,7 +42,7 @@ export function updateCurrentMessageList( ): GlobalState { const { messageLists } = global.messages; let newMessageLists: MessageList[] = messageLists; - if (shouldReplaceHistory || IS_TEST) { + if (shouldReplaceHistory || (IS_TEST && !IS_MOCKED_CLIENT)) { newMessageLists = chatId ? [{ chatId, threadId, type }] : []; } else if (chatId) { const last = messageLists[messageLists.length - 1]; diff --git a/src/hooks/useHistoryBack.ts b/src/hooks/useHistoryBack.ts index 78aae5e1..f9120067 100644 --- a/src/hooks/useHistoryBack.ts +++ b/src/hooks/useHistoryBack.ts @@ -1,54 +1,78 @@ -import { useCallback, useEffect, useRef } from '../lib/teact/teact'; - +import useOnChange from './useOnChange'; +import { useEffect, useRef } from '../lib/teact/teact'; +import { IS_TEST } from '../config'; +import { fastRaf } from '../util/schedulers'; import { IS_IOS } from '../util/environment'; -import usePrevious from './usePrevious'; -import { getActions } from '../global'; -import { areSortedArraysEqual } from '../util/iteratees'; - -type HistoryState = { - currentIndex: number; - nextStateIndexToReplace: number; - isHistoryAltered: boolean; - isDisabled: boolean; - isEdge: boolean; - currentIndexes: number[]; -}; +import { getActions } from '../lib/teact/teactn'; +export const LOCATION_HASH = window.location.hash; +const PATH_BASE = `${window.location.pathname}${window.location.search}`; // Carefully selected by swiping and observing visual changes // TODO: may be different on other devices such as iPad, maybe take dpi into account? const SAFARI_EDGE_BACK_GESTURE_LIMIT = 300; const SAFARI_EDGE_BACK_GESTURE_DURATION = 350; -export const LOCATION_HASH = window.location.hash; -const PATH_BASE = `${window.location.pathname}${window.location.search}`; -const historyState: HistoryState = { - currentIndex: 0, - nextStateIndexToReplace: -1, - isHistoryAltered: false, - isDisabled: false, - isEdge: false, - currentIndexes: [], +type HistoryRecord = { + index: number; + // Should this record be replaced by the next record (for example Menu) + shouldBeReplaced?: boolean; + // Mark this record as replaced by the next record. Only used to check if needed to perform effectBack + markReplaced?: VoidFunction; + onBack?: VoidFunction; + // Set if the element is closed in the UI, but not in the real history + isClosed?: boolean; }; -export const disableHistoryBack = () => { - historyState.isDisabled = true; +type HistoryOperationGo = { + type: 'go'; + delta: number; }; -const handleTouchStart = (event: TouchEvent) => { +type HistoryOperationState = { + type: 'pushState' | 'replaceState'; + data: any; + hash?: string; +}; + +type HistoryOperation = HistoryOperationGo | HistoryOperationState; + +// Needed to dismiss any 'trashed' history records from the previous page reloads. +const historyUniqueSessionId = Number(new Date()); +// Reflects real history state, but also contains information on which records should be replaced by the next record and +// which records are deferred to close on the next operation +let historyState: HistoryRecord[]; +// Reflects current real history index +let historyCursor: number; +// If we alter real history programmatically, the popstate event will be fired, which we don't need +let isAlteringHistory = false; +// Unfortunately Safari doesn't really like when there's 2+ consequent history operations in one frame, so we need +// to delay them to the next raf +let deferredHistoryOperations: HistoryOperation[] = []; +let isSafariGestureAnimation = false; + +// Do not remove: used for history unit tests +if (IS_TEST) { + (window as any).TEST_getHistoryState = () => historyState; + (window as any).TEST_getHistoryCursor = () => historyCursor; +} + +function handleTouchStart(event: TouchEvent) { const x = event.touches[0].pageX; if (x <= SAFARI_EDGE_BACK_GESTURE_LIMIT || x >= window.innerWidth - SAFARI_EDGE_BACK_GESTURE_LIMIT) { - historyState.isEdge = true; + isSafariGestureAnimation = true; } -}; +} -const handleTouchEnd = () => { - if (historyState.isEdge) { - setTimeout(() => { - historyState.isEdge = false; - }, SAFARI_EDGE_BACK_GESTURE_DURATION); +function handleTouchEnd() { + if (!isSafariGestureAnimation) { + return; } -}; + + setTimeout(() => { + isSafariGestureAnimation = false; + }, SAFARI_EDGE_BACK_GESTURE_DURATION); +} if (IS_IOS) { window.addEventListener('touchstart', handleTouchStart); @@ -56,197 +80,209 @@ if (IS_IOS) { window.addEventListener('popstate', handleTouchEnd); } -window.history.replaceState({ index: historyState.currentIndex }, '', PATH_BASE); +function applyDeferredHistoryOperations() { + const goOperations = deferredHistoryOperations.filter((op) => op.type === 'go') as HistoryOperationGo[]; + const stateOperations = deferredHistoryOperations.filter((op) => op.type !== 'go') as HistoryOperationState[]; + const goCount = goOperations.reduce((acc, op) => acc + op.delta, 0); + if (goCount) { + window.history.go(goCount); + } -export default function useHistoryBack( - isActive: boolean | undefined, - onBack: ((noDisableAnimation: boolean) => void) | undefined, - onForward?: (state: any) => void, - currentState?: any, - shouldReplaceNext = false, - hashes?: string[], -) { - const indexRef = useRef(-1); - const isForward = useRef(false); - const prevIsActive = usePrevious(isActive); - const isClosed = useRef(true); - const indexHashRef = useRef<{ index: number; hash: string }[]>([]); - const prevHashes = usePrevious(hashes); - const isHashChangedFromEvent = useRef(false); + stateOperations.forEach((op) => window.history[op.type](op.data, '', op.hash)); - const handleChange = useCallback((isForceClose = false) => { - if (!hashes) { - if (isActive && !isForceClose) { - isClosed.current = false; + deferredHistoryOperations = []; +} - if (isForward.current) { - isForward.current = false; - historyState.currentIndexes.push(indexRef.current); - } else { - setTimeout(() => { - const index = ++historyState.currentIndex; +function deferHistoryOperation(historyOperation: HistoryOperation) { + if (!deferredHistoryOperations.length) fastRaf(applyDeferredHistoryOperations); + deferredHistoryOperations.push(historyOperation); +} - historyState.currentIndexes.push(index); +// Resets history to the `root` state +function resetHistory() { + historyCursor = 0; + historyState = [{ + index: 0, + onBack: () => window.history.back(), + }]; - window.history[( - ( - historyState.currentIndexes.includes(historyState.nextStateIndexToReplace - 1) - && window.history.state.index !== 0 - && historyState.nextStateIndexToReplace === index - && !shouldReplaceNext - ) - ? 'replaceState' - : 'pushState' - )]({ - index, - state: currentState, - }, ''); + window.history.replaceState({ index: 0, historyUniqueSessionId }, PATH_BASE); +} - indexRef.current = index; +resetHistory(); - if (shouldReplaceNext) { - historyState.nextStateIndexToReplace = historyState.currentIndex + 1; - } - }, 0); - } - } +function cleanupClosed(alreadyClosedCount = 1) { + let countClosed = alreadyClosedCount; + for (let i = historyCursor - 1; i > 0; i--) { + if (!historyState[i].isClosed) break; + countClosed++; + } + if (countClosed) { + isAlteringHistory = true; + deferHistoryOperation({ + type: 'go', + delta: -countClosed, + }); + } + return countClosed; +} - if ((isForceClose || !isActive) && !isClosed.current) { - if ((indexRef.current === historyState.currentIndex || !shouldReplaceNext)) { - historyState.isHistoryAltered = true; - window.history.back(); - - setTimeout(() => { - historyState.nextStateIndexToReplace = -1; - }, 400); - } - historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(indexRef.current), 1); - - isClosed.current = true; - } - } else { - const prev = prevHashes || []; - if (prev.length < hashes.length) { - setTimeout(() => { - const index = ++historyState.currentIndex; - historyState.currentIndexes.push(index); - - window.history.pushState({ - index, - state: currentState, - }, '', `#${hashes[hashes.length - 1]}`); - - indexHashRef.current.push({ - index, - hash: hashes[hashes.length - 1], - }); - }, 0); - } else { - const delta = prev.length - hashes.length; - if (isHashChangedFromEvent.current) { - isHashChangedFromEvent.current = false; - } else { - if (hashes.length !== indexHashRef.current.length) { - if (delta > 0) { - const last = indexHashRef.current[indexHashRef.current.length - delta - 1]; - let realDelta = delta; - if (last) { - const indexLast = historyState.currentIndexes.findIndex( - (l) => l === last.index, - ); - realDelta = historyState.currentIndexes.length - indexLast - 1; - } - historyState.isHistoryAltered = true; - window.history.go(-realDelta); - const removed = indexHashRef.current.splice(indexHashRef.current.length - delta - 1, delta); - removed.forEach(({ index }) => { - historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(index), 1); - }); - } - } - - if (hashes.length > 0) { - setTimeout(() => { - const index = ++historyState.currentIndex; - historyState.currentIndexes[historyState.currentIndexes.length - 1] = index; - - window.history.replaceState({ - index, - state: currentState, - }, '', `${PATH_BASE}#${hashes[hashes.length - 1]}`); - - indexHashRef.current[indexHashRef.current.length - 1] = { - index, - hash: hashes[hashes.length - 1], - }; - }, 0); - } - } - } +function cleanupTrashedState() { + // Navigation to previous page reload, state of which was trashed by reload + for (let i = historyState.length - 1; i > 0; i--) { + if (historyState[i].isClosed) { + continue; } - }, [currentState, hashes, isActive, prevHashes, shouldReplaceNext]); + if (isSafariGestureAnimation) { + getActions().disableHistoryAnimations(); + } + historyState[i].onBack?.(); + } - useEffect(() => { - const handlePopState = (event: PopStateEvent) => { - if (historyState.isHistoryAltered) { - setTimeout(() => { - historyState.isHistoryAltered = false; - }, 0); - return; + resetHistory(); +} + +window.addEventListener('popstate', ({ state }: PopStateEvent) => { + if (isAlteringHistory) { + isAlteringHistory = false; + return; + } + + if (!state) { + cleanupTrashedState(); + + if (!window.location.hash) { + return; + } + return; + } + + const { index, historyUniqueSessionId: previousUniqueSessionId } = state; + if (previousUniqueSessionId !== historyUniqueSessionId) { + cleanupTrashedState(); + return; + } + + // New real history state matches the old virtual one. Not possible in theory, but in practice we have Safari + if (index === historyCursor) { + return; + } + + if (index < historyCursor) { + // Navigating back + let alreadyClosedCount = 0; + for (let i = historyCursor; i > index - alreadyClosedCount; i--) { + if (historyState[i].isClosed) { + alreadyClosedCount++; + continue; } - const { index: i } = event.state; - const index = i || 0; - try { - const currIndex = hashes ? indexHashRef.current[indexHashRef.current.length - 1].index : indexRef.current; - - const prev = historyState.currentIndexes[historyState.currentIndexes.indexOf(currIndex) - 1]; - - if (historyState.isDisabled) return; - - if ((!isClosed.current && (index === 0 || index === prev)) || (hashes && (index === 0 || index === prev))) { - if (hashes) { - isHashChangedFromEvent.current = true; - indexHashRef.current.pop(); - } - - historyState.currentIndexes.splice(historyState.currentIndexes.indexOf(currIndex), 1); - - if (onBack) { - if (historyState.isEdge) { - getActions() - .disableHistoryAnimations(); - } - onBack(!historyState.isEdge); - isClosed.current = true; - } - } else if (index === currIndex && isClosed.current && onForward && !hashes) { - isForward.current = true; - if (historyState.isEdge) { - getActions() - .disableHistoryAnimations(); - } - onForward(event.state.state); - } - } catch (e) { - // Forward navigation for hashed is not supported + if (isSafariGestureAnimation) { + getActions().disableHistoryAnimations(); } + historyState[i].onBack?.(); + } + + const countClosed = cleanupClosed(alreadyClosedCount); + historyCursor += index - historyCursor - countClosed; + + // Can happen when we have deferred a real back for some element (for example Menu), closed via UI, + // pressed back button and caused a pushState. + if (historyCursor < 0) { + historyCursor = 0; + } + } else if (index > historyCursor) { + // Forward navigation is not yet supported + isAlteringHistory = true; + deferHistoryOperation({ + type: 'go', + delta: -(index - historyCursor), + }); + } +}); + +export default function useHistoryBack({ + isActive, + shouldBeReplaced, + hash, + onBack, +}: { + isActive?: boolean; + shouldBeReplaced?: boolean; + hash?: string; + title?: string; + onBack: VoidFunction; +}) { + // Active index of the record + const indexRef = useRef(); + const wasReplaced = useRef(false); + + const isFirstRender = useRef(true); + + const pushState = (forceReplace = false) => { + // Check if the old state should be replaced + const shouldReplace = forceReplace || historyState[historyCursor].shouldBeReplaced; + indexRef.current = shouldReplace ? historyCursor : ++historyCursor; + + historyCursor = indexRef.current; + + // Mark the previous record as replaced so effectBack doesn't perform back operation on the new record + const previousRecord = historyState[indexRef.current]; + if (previousRecord && !previousRecord.isClosed) { + previousRecord.markReplaced?.(); + } + + historyState[indexRef.current] = { + index: indexRef.current, + onBack, + shouldBeReplaced, + markReplaced: () => { + wasReplaced.current = true; + }, }; - const hasChanged = hashes - ? (!prevHashes || !areSortedArraysEqual(prevHashes, hashes)) - : prevIsActive !== isActive; - - if (!historyState.isDisabled && hasChanged) { - handleChange(); + // Delete forward navigation in the virtual history. Not really needed, just looks better when debugging `logState` + for (let i = indexRef.current + 1; i < historyState.length; i++) { + delete historyState[i]; } - window.addEventListener('popstate', handlePopState); - return () => window.removeEventListener('popstate', handlePopState); - }, [ - currentState, handleChange, hashes, isActive, onBack, onForward, prevHashes, prevIsActive, shouldReplaceNext, - ]); - - return { - forceClose: () => handleChange(true), + deferHistoryOperation({ + type: shouldReplace ? 'replaceState' : 'pushState', + data: { + index: indexRef.current, + historyUniqueSessionId, + }, + hash: hash ? `#${hash}` : undefined, + }); }; + + const processBack = () => { + // Only process back on open records + if (indexRef.current && historyState[indexRef.current] && !wasReplaced.current) { + historyState[indexRef.current].isClosed = true; + wasReplaced.current = true; + if (indexRef.current === historyCursor && !shouldBeReplaced) { + historyCursor -= cleanupClosed(); + } + } + }; + + // Process back navigation when element is unmounted + useEffect(() => { + isFirstRender.current = false; + return () => { + if (!isActive || wasReplaced.current) return; + processBack(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useOnChange(() => { + if (isFirstRender.current && !isActive) return; + + if (isActive) { + pushState(); + } else { + processBack(); + } + }, [isActive]); } diff --git a/src/lib/gramjs/client/MockClient.ts b/src/lib/gramjs/client/MockClient.ts new file mode 100644 index 00000000..e0a21db4 --- /dev/null +++ b/src/lib/gramjs/client/MockClient.ts @@ -0,0 +1,331 @@ +import BigInt from 'big-integer'; +import { UpdateConnectionState } from '../network'; +import Request from '../tl/api'; +import { default as GramJs } from "../tl/api"; + +type Peer = { + peer: GramJs.Chat | GramJs.Channel | GramJs.User; + TEST_messages: GramJs.Message[]; + TEST_sendMessage: (data: CreateMessageParams) => GramJs.Message | undefined; +}; + +type CreateMessageParams = { + fromId?: any; + repliesChannelId?: any; + replyingTo?: GramJs.MessageReplyHeader; +}; + +class TelegramClient { + addEventHandler(callback: any, event: any) { + callback(event.build(new UpdateConnectionState(UpdateConnectionState.connected))); + } + + private lastId = 0; + + private peers: Peer[] = []; + + private dialogs: GramJs.Dialog[] = []; + + start() { + } + + constructor() { + const user = this.createUser({ + firstName: 'Test', + lastName: 'Account', + }); + user.TEST_sendMessage({}); + + const chat = this.createChat({}); + chat.TEST_sendMessage({}); + + const channel = this.createChannel({ + title: 'Test Channel', + username: 'testchannel', + }); + + const discussion = this.createChannel({ + title: 'Test Discussion', + username: 'testdiscuss', + isMegagroup: true, + }); + + const message = channel.TEST_sendMessage({ + repliesChannelId: discussion.peer.id, + }); + + const { id } = discussion.TEST_sendMessage({})!; + + discussion.TEST_sendMessage({ + fromId: new GramJs.PeerUser({ + userId: user.peer.id, + }), + replyingTo: new GramJs.MessageReplyHeader({ + replyToMsgId: id, + replyToPeerId: new GramJs.PeerChannel({ + channelId: channel.peer.id, + }), + replyToTopId: message!.id, + }), + }); + } + + createDialog(peer: GramJs.TypePeer) { + return new GramJs.Dialog({ + peer, + topMessage: 0, + readInboxMaxId: 0, + readOutboxMaxId: 0, + unreadCount: 0, + unreadMentionsCount: 0, + unreadReactionsCount: 0, + notifySettings: new GramJs.PeerNotifySettings({}), + }); + } + + createMessage(peer: GramJs.TypePeer) { + return ({ + fromId, + repliesChannelId, + replyingTo, + }: CreateMessageParams) => { + const pi = this.getPeerIndex(peer); + const p = this.getPeer(peer); + if (!p || pi === undefined) return; + + const message = new GramJs.Message({ + id: p.TEST_messages.length + 1, + fromId, + peerId: peer, + date: Number(new Date()) / 1000 + pi * 60, + message: 'lol @channel', + entities: [new GramJs.MessageEntityMention({ + offset: 4, + length: 8, + })], + replyTo: replyingTo, + replies: new GramJs.MessageReplies({ + comments: true, + replies: 0, + repliesPts: 0, + channelId: repliesChannelId ? BigInt(repliesChannelId) : undefined, + }), + }); + this.peers[pi].TEST_messages.push(message); + return message; + }; + } + + createChat({}) { + const chat = new GramJs.Chat({ + id: BigInt(this.lastId++), + title: 'Some chat', + photo: new GramJs.ChatPhotoEmpty(), + participantsCount: 1, + date: 1000, + version: 1, + }); + + const peerChat = new GramJs.PeerChat({ + chatId: chat.id, + }); + + this.dialogs.push(this.createDialog(peerChat)); + + const testChat: Peer = { peer: chat, TEST_messages: [], TEST_sendMessage: this.createMessage(peerChat) }; + + this.peers.push(testChat); + + return testChat; + } + + createChannel({ title, username, isMegagroup }: { + title: string; + username: string; + isMegagroup?: boolean; + }) { + const channel = new GramJs.Channel({ + username, + id: BigInt(this.lastId++), + megagroup: isMegagroup ? true : undefined, + title, + photo: new GramJs.ChatPhotoEmpty(), + participantsCount: 1, + date: 1000, + creator: true, + }); + + const peerChannel = new GramJs.PeerChannel({ + channelId: channel.id, + }); + + this.dialogs.push(this.createDialog(peerChannel)); + + const testChat: Peer = { peer: channel, TEST_messages: [], TEST_sendMessage: this.createMessage(peerChannel) }; + + this.peers.push(testChat); + + return testChat; + } + + createUser({ + firstName, + lastName, + }: { + firstName: string; + lastName: string; + }): Peer { + const user = new GramJs.User({ + // self: true, + verified: true, + id: BigInt(this.lastId++), + // accessHash?: long; + firstName, + lastName, + username: 'man', + // phone?: string; + // photo?: Api.TypeUserProfilePhoto; + // status?: Api.TypeUserStatus; + // botInfoVersion?: int; + // restrictionReason?: Api.//TypeRestrictionReason[]; + // botInlinePlaceholder?: string; + // langCode?: string; + }); + + const peerUser = new GramJs.PeerUser({ + userId: user.id, + }); + + this.dialogs.push(this.createDialog(peerUser)); + + const testChat: Peer = { peer: user, TEST_messages: [], TEST_sendMessage: this.createMessage(peerUser) }; + + this.peers.push(testChat); + + return testChat; + } + + async invoke(request: Request) { + // await new Promise(resolve => setTimeout(resolve, 1000)) + if (request instanceof GramJs.messages.GetDiscussionMessage) { + return new GramJs.messages.DiscussionMessage({ + messages: [ + this.peers[3].TEST_messages[0], + ], + maxId: 2, + unreadCount: 1, + chats: [], + users: [], + }); + } + if (request instanceof GramJs.messages.GetHistory) { + const peer = this.getPeer(request.peer); + if (!peer) return; + + return new GramJs.messages.Messages({ + messages: peer.TEST_messages, + chats: [], + users: [], + }); + } + if (request instanceof GramJs.messages.GetReplies) { + const peer = this.peers[3]; + if (!peer) return; + + return new GramJs.messages.ChannelMessages({ + messages: peer.TEST_messages, + pts: 0, + count: peer.TEST_messages.length, + chats: [], + users: [], + }); + } + if (request instanceof GramJs.messages.GetDialogFilters) { + return [new GramJs.DialogFilter({ + contacts: true, + nonContacts: true, + groups: true, + broadcasts: true, + bots: true, + // excludeMuted?: true; + // excludeRead?: true; + // excludeArchived?: true; + id: 1, + title: 'Dialog Filter', + // emoticon?: string; + pinnedPeers: [], + includePeers: [], + excludePeers: [], + })]; + } + if (request instanceof GramJs.messages.GetPinnedDialogs) { + return new GramJs.messages.PeerDialogs({ + dialogs: [], + chats: [], + messages: [], + users: [], + state: new GramJs.updates.State({ + pts: 0, + qts: 0, + date: 0, + seq: 0, + unreadCount: 0, + }), + }); + } + if (request instanceof GramJs.messages.GetDialogs) { + if (request.folderId || !(request.offsetPeer instanceof GramJs.InputPeerEmpty)) { + return new GramJs.messages.Dialogs({ + dialogs: [], + users: [], + chats: [], + messages: [], + }); + } + + return new GramJs.messages.Dialogs({ + dialogs: this.dialogs, + messages: this.getAllMessages(), + chats: this.getChats(), + users: this.getUsers(), + }); + } + // console.log(request.className, request); + } + + private getPeerIndex(peer: GramJs.TypeInputPeer) { + const id = 'channelId' in peer ? peer.channelId : ( + 'userId' in peer ? peer.userId : ( + 'chatId' in peer ? peer.chatId : undefined + ) + ); + + if (!id) return undefined; + + return this.peers.findIndex((l) => l.peer.id.toString() === id.toString()); + } + + private getPeer(peer: GramJs.TypeInputPeer) { + const index = this.getPeerIndex(peer); + if (index === undefined) return undefined; + + return this.peers[index]; + } + + private getAllMessages() { + return this.peers.reduce((acc: GramJs.Message[], el) => { + acc.push(...el.TEST_messages); + return acc; + }, []); + } + + private getChats() { + return this.peers.filter((l) => !(l.peer instanceof GramJs.User)).map((l) => l.peer); + } + + private getUsers() { + return this.peers.filter((l) => l.peer instanceof GramJs.User).map((l) => l.peer); + } +} + +export default TelegramClient; diff --git a/src/util/routing.ts b/src/util/routing.ts index b3a49311..eeb02ddf 100644 --- a/src/util/routing.ts +++ b/src/util/routing.ts @@ -1,17 +1,12 @@ -import { MessageList, MessageListType } from '../global/types'; +import { MessageListType } from '../global/types'; import { MAIN_THREAD_ID } from '../api/types'; - import { LOCATION_HASH } from '../hooks/useHistoryBack'; -export function createMessageHash(messageList: MessageList) { - const typeOrThreadId = messageList.type !== 'thread' ? ( - `_${messageList.type}` - ) : messageList.threadId !== -1 ? ( - `_${messageList.threadId}` - ) : ''; - - return `${messageList.chatId}${typeOrThreadId}`; -} +export const createMessageHash = (chatId: string, type: string, threadId: number): string => ( + chatId.toString() + + (type !== 'thread' ? `_${type}` + : (threadId !== -1 ? `_${threadId}` : '')) +); export function parseLocationHash() { if (!LOCATION_HASH) return undefined; diff --git a/src/util/setupServiceWorker.ts b/src/util/setupServiceWorker.ts index 36815bbe..76ecea1a 100644 --- a/src/util/setupServiceWorker.ts +++ b/src/util/setupServiceWorker.ts @@ -1,4 +1,4 @@ -import { DEBUG, DEBUG_MORE } from '../config'; +import { DEBUG, DEBUG_MORE, IS_TEST } from '../config'; import { getActions } from '../global'; import { IS_ANDROID, IS_IOS, IS_SERVICE_WORKER_SUPPORTED } from './environment'; import { notifyClientReady, playNotifySoundDebounced } from './notifications'; @@ -77,7 +77,7 @@ if (IS_SERVICE_WORKER_SUPPORTED) { console.error('[SW] ServiceWorker not available'); } - if (!IS_IOS && !IS_ANDROID) { + if (!IS_IOS && !IS_ANDROID && !IS_TEST) { getActions().showDialog({ data: { message: 'SERVICE_WORKER_DISABLED', hasErrorKey: true } }); } } diff --git a/src/util/websync.ts b/src/util/websync.ts index ddebbe1a..860274a6 100644 --- a/src/util/websync.ts +++ b/src/util/websync.ts @@ -1,4 +1,4 @@ -import { APP_VERSION, DEBUG } from '../config'; +import { APP_VERSION, DEBUG, IS_MOCKED_CLIENT } from '../config'; import { getGlobal } from '../global'; import { hasStoredSession } from './sessions'; @@ -25,6 +25,7 @@ const saveSync = (authed: boolean) => { let lastTimeout: NodeJS.Timeout | undefined; export const forceWebsync = (authed: boolean) => { + if (IS_MOCKED_CLIENT) return undefined; const currentTs = getTs(); const { canRedirect, ts } = JSON.parse(localStorage.getItem(WEBSYNC_KEY) || '{}'); diff --git a/webpack.config.js b/webpack.config.js index 47a15649..f476f40b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,8 @@ const { DefinePlugin, EnvironmentPlugin, ProvidePlugin, + + NormalModuleReplacementPlugin, } = require('webpack'); const HtmlWebackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); @@ -118,6 +120,10 @@ module.exports = (env = {}, argv = {}) => { }, }, plugins: [ + ...(process.env.APP_MOCKED_CLIENT === '1' ? [new NormalModuleReplacementPlugin( + /src\/lib\/gramjs\/client\/TelegramClient\.js/, + './MockClient.ts' + )] : []), new HtmlWebackPlugin({ appName: process.env.APP_ENV === 'production' ? 'Telegram Web' : 'Telegram Web Beta', appleIcon: process.env.APP_ENV === 'production' ? 'apple-touch-icon' : './apple-touch-icon-dev', @@ -130,6 +136,7 @@ module.exports = (env = {}, argv = {}) => { }), new EnvironmentPlugin({ APP_ENV: 'production', + APP_MOCKED_CLIENT: '', APP_NAME: null, APP_VERSION: appVersion, TELEGRAM_T_API_ID: undefined,