diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..f8c3042 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,34 @@ +module.exports = { + parser: '@typescript-eslint/parser', // Specifies the ESLint parser + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: 'module', // Allows for the use of imports + }, + extends: [ + 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react + 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin + 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + 'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + // e.g. "@typescript-eslint/explicit-function-return-type": "off", + '@typescript-eslint/no-this-alias': 0, + 'react/jsx-key': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/no-unused-vars': 0, + '@typescript-eslint/explicit-module-boundary-types': 0, + '@typescript-eslint/ban-types': 0, + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + }, + ], + }, + settings: { + react: { + version: 'detect', + }, + }, +}; diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..01f0352 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,55 @@ +# Workflow to run on pull request and push to master + +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ master ] + pull_request: + branches: [ master ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + node-version: + - 10 + - 12 + - 14 + architecture: + - x64 + + name: Node ${{ matrix.node_version }} - ${{ matrix.os }} + steps: + - name: Checkout branch + uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node_version }} + + - name: Install Packages + run: npm install + + - name: Check linting + run: npm run check-lint + + - name: It can build successfully + run: npm run build + + - name: It can package into electron app + run: | + npm run package-darwin + npm run package-linux + npm run package-win32 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5f78d30..59a9d7b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ AOMAnalyzer-linux-x64 AOMAnalyzer-win32-x64 *.zip dist -\.* release_builds node_modules inspect.js diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..3ed1fe9 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + semi: true, + trailingComma: "all", + singleQuote: true, + printWidth: 120, + tabWidth: 2 + }; \ No newline at end of file diff --git a/index.html b/index.html index c934951..296e2f9 100644 --- a/index.html +++ b/index.html @@ -69,6 +69,12 @@ font-size: 11px; } + .grainGroup { + display: flex; + justify-content: space-around; + margin-bottom: 15px; + } + .contentContainer { width: 100%; overflow: auto; diff --git a/package-lock.json b/package-lock.json index 8cd8c1d..2ecbe8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,29 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==" + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, "@babel/runtime": { "version": "7.14.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz", @@ -83,6 +106,47 @@ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, + "@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + } + } + }, "@material-ui/core": { "version": "4.11.4", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.4.tgz", @@ -159,6 +223,224 @@ "react-is": "^16.8.0 || ^17.0.0" } }, + "@nivo/annotations": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.72.0.tgz", + "integrity": "sha512-jbEzEXu8cmVgsJq+1RN6K5kUXhsj1zSR1h+LET1R7LsZrgfpUjy0MrF6OgvmUXwhu1e0NWh5BgGAaf7YN1ISxg==", + "requires": { + "@nivo/colors": "0.72.0", + "@react-spring/web": "9.2.0", + "lodash": "^4.17.21" + } + }, + "@nivo/arcs": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/arcs/-/arcs-0.72.0.tgz", + "integrity": "sha512-HanzRUbC7io7KPnqfQOoFs+K99q2SN1XOCjeKG31XA59ITYfEcoqlghRh6BuVR6VdMZba79hPniH61+rnHXH0A==", + "requires": { + "@nivo/colors": "0.72.0", + "@react-spring/web": "9.2.0", + "d3-shape": "^1.3.5" + } + }, + "@nivo/axes": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.72.0.tgz", + "integrity": "sha512-ragftEbQaChO+SorbaUsGcy+pesTWopRItU81Z2kiJPNV211CTG+NNJP3jBYX2GwTDDXh7TkUA1U5itJKykbRQ==", + "requires": { + "@nivo/scales": "0.72.0", + "@react-spring/web": "9.2.0", + "d3-format": "^1.4.4", + "d3-time": "^1.0.11", + "d3-time-format": "^3.0.0" + }, + "dependencies": { + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + } + } + }, + "@nivo/colors": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.72.0.tgz", + "integrity": "sha512-E1HAXmk/6uwgfHkzWeEMgsu5G8d+cAXqs3qcRawZ2vBI7/lPWqYf0KiDvGpjl508Pry2DZyyAgy/inKCWC2dKw==", + "requires": { + "d3-color": "^2.0.0", + "d3-scale": "^3.2.3", + "d3-scale-chromatic": "^2.0.0", + "lodash": "^4.17.21", + "react-motion": "^0.5.2" + } + }, + "@nivo/core": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.72.0.tgz", + "integrity": "sha512-TtL/m5ItQUqVJm6IOtpa5W7EKp7kwbqCQ4eYxgZtqGWFkt/e8tezYLdLDBpC6Mx0smVU+TlrFvObM603UxIamA==", + "requires": { + "@nivo/recompose": "0.72.0", + "@react-spring/web": "9.2.0", + "d3-color": "^2.0.0", + "d3-format": "^1.4.4", + "d3-hierarchy": "^1.1.8", + "d3-interpolate": "^2.0.1", + "d3-scale": "^3.2.3", + "d3-scale-chromatic": "^2.0.0", + "d3-shape": "^1.3.5", + "d3-time-format": "^3.0.0", + "lodash": "^4.17.21", + "resize-observer-polyfill": "^1.5.1" + } + }, + "@nivo/legends": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.72.0.tgz", + "integrity": "sha512-QJsTyURfpxe1mk6vM8oxGWdbvH/Pf4HgxtQ3D5/VJ2KeXEcRtTJ2ptavqUOxOfOTCBXo5wbtTDI4ni2ff4RRpg==" + }, + "@nivo/line": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.72.0.tgz", + "integrity": "sha512-KfDu5KVeBnbNZUw55rzxsHsuCJ8YVLcuIXFAyPKOSjgkK2SWzUUL8ZifPyYXr2+7Ugbyzyw8qDvXBdt/Koa1+g==", + "requires": { + "@nivo/annotations": "0.72.0", + "@nivo/axes": "0.72.0", + "@nivo/colors": "0.72.0", + "@nivo/legends": "0.72.0", + "@nivo/scales": "0.72.0", + "@nivo/tooltip": "0.72.0", + "@nivo/voronoi": "0.72.0", + "@react-spring/web": "9.2.0", + "d3-shape": "^1.3.5" + } + }, + "@nivo/pie": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/pie/-/pie-0.72.0.tgz", + "integrity": "sha512-WCEZ8ctbM48EW1pQDi407hC0zWXmQmA1QGU/4p9ZJGdVfrOIPJ9g3gHHPaj4demrEmuXPgqCmvu+4lmUFhzeQQ==", + "requires": { + "@nivo/arcs": "0.72.0", + "@nivo/colors": "0.72.0", + "@nivo/legends": "0.72.0", + "@nivo/tooltip": "0.72.0", + "d3-shape": "^1.3.5" + } + }, + "@nivo/recompose": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/recompose/-/recompose-0.72.0.tgz", + "integrity": "sha512-1yhb3wZOaC16nX//eiET6Q1e2dq5ctMt0s0D59NiNK08zZGMaTXnNoJKQAFjGW4M/jDndJxsOkMO90/RvIZWKA==", + "requires": { + "react-lifecycles-compat": "^3.0.4" + } + }, + "@nivo/scales": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.72.0.tgz", + "integrity": "sha512-R190g+tAvBSDS3PWwvRc8Av3tcKf7OZVC4rb0skz0XuT0OyDxoF+ilaoZYekjuUD2o6aDH6MaqzWkQFx96iJqw==", + "requires": { + "d3-scale": "^3.2.3", + "d3-time": "^1.0.11", + "d3-time-format": "^3.0.0", + "lodash": "^4.17.21" + }, + "dependencies": { + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + } + } + }, + "@nivo/tooltip": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.72.0.tgz", + "integrity": "sha512-hVfCg8e1KNaDNY3WbPiZq2MYIJoq8mneKLUwMWvhv5JsBGnuEnTsMqNDHltWU7Mr2mfB3fBVGpUMiu4ta4pvtw==", + "requires": { + "@react-spring/web": "9.2.0" + } + }, + "@nivo/voronoi": { + "version": "0.72.0", + "resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.72.0.tgz", + "integrity": "sha512-Yu5IBQoywxNyU9A5SL734NDm5cpDic0cYWvfrZg48SOEvlv48jz30cMzcXyd786uvbd9hUhxboI6ZGB29EVLJw==", + "requires": { + "d3-delaunay": "^5.3.0", + "d3-scale": "^3.2.3" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", + "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@react-spring/animated": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.2.3.tgz", + "integrity": "sha512-xxYOxxrk4r+yy218lVnkR027GXGvHcadDnnXRW0l6atcL+1AGYwimMwIuvzlvnsVnyoK0YUABEeGjk9ENOrVMQ==", + "requires": { + "@react-spring/shared": "~9.2.0", + "@react-spring/types": "~9.2.0" + } + }, + "@react-spring/core": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.2.3.tgz", + "integrity": "sha512-8qJbj0xjjoYGVqdmNd2bVaFzKSAwrMVLweHkaYJiTY6p0g7LhSdt5KSl1MjYA4Rj6S4wO1KgefAPK6mH6MtGtA==", + "requires": { + "@react-spring/animated": "~9.2.0", + "@react-spring/shared": "~9.2.0", + "@react-spring/types": "~9.2.0" + } + }, + "@react-spring/rafz": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.2.3.tgz", + "integrity": "sha512-ThBI3HWp1Y82uRSFVpoykwgM3M9s3SJD6ilKKp9C2OTPcGhWiRGt1IMjzKPwB+F1NX3psbPTt30Bev8WzA/DQQ==" + }, + "@react-spring/shared": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.2.3.tgz", + "integrity": "sha512-MehdmKao1oUAwSEJo8BXOz1OGgsSav/dimD1Vz920hirShj9s/I4zKnWtkdK92xQ4n35D/xD3hATHkXbt/WSvg==", + "requires": { + "@react-spring/rafz": "~9.2.0", + "@react-spring/types": "~9.2.0" + } + }, + "@react-spring/types": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.2.3.tgz", + "integrity": "sha512-G7AWUJavwsvvvprnYmqUXE5N1XKINktc8V72ipvkFTE3DD3GApYpX/ORNmieJljKJYvBSBzpRSIzk2qELUs30Q==" + }, + "@react-spring/web": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.2.0.tgz", + "integrity": "sha512-YWNPRCmC1MiCH+3MZQUTfhf4OzKR1w+0BVxpw7HkHgCz8lJNlvlDeiBdQzYhh+YmmbaF4y1swhCYmI0y4VCZXQ==", + "requires": { + "@react-spring/animated": "~9.2.0", + "@react-spring/core": "~9.2.0", + "@react-spring/shared": "~9.2.0", + "@react-spring/types": "~9.2.0" + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -185,6 +467,11 @@ "@types/node": "*" } }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" + }, "@types/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", @@ -242,6 +529,142 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" }, + "@typescript-eslint/eslint-plugin": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.28.1.tgz", + "integrity": "sha512-9yfcNpDaNGQ6/LQOX/KhUFTR1sCKH+PBr234k6hI9XJ0VP5UqGxap0AnNwBnWFk1MNyWBylJH9ZkzBXC+5akZQ==", + "requires": { + "@typescript-eslint/experimental-utils": "4.28.1", + "@typescript-eslint/scope-manager": "4.28.1", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.28.1.tgz", + "integrity": "sha512-n8/ggadrZ+uyrfrSEchx3jgODdmcx7MzVM2sI3cTpI/YlfSm0+9HEUaWw3aQn2urL2KYlWYMDgn45iLfjDYB+Q==", + "requires": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.28.1", + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/typescript-estree": "4.28.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.1.tgz", + "integrity": "sha512-UjrMsgnhQIIK82hXGaD+MCN8IfORS1CbMdu7VlZbYa8LCZtbZjJA26De4IPQB7XYZbL8gJ99KWNj0l6WD0guJg==", + "requires": { + "@typescript-eslint/scope-manager": "4.28.1", + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/typescript-estree": "4.28.1", + "debug": "^4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.1.tgz", + "integrity": "sha512-o95bvGKfss6705x7jFGDyS7trAORTy57lwJ+VsYwil/lOUxKQ9tA7Suuq+ciMhJc/1qPwB3XE2DKh9wubW8YYA==", + "requires": { + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/visitor-keys": "4.28.1" + } + }, + "@typescript-eslint/types": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.1.tgz", + "integrity": "sha512-4z+knEihcyX7blAGi7O3Fm3O6YRCP+r56NJFMNGsmtdw+NCdpG5SgNz427LS9nQkRVTswZLhz484hakQwB8RRg==" + }, + "@typescript-eslint/typescript-estree": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.1.tgz", + "integrity": "sha512-GhKxmC4sHXxHGJv8e8egAZeTZ6HI4mLU6S7FUzvFOtsk7ZIDN1ksA9r9DyOgNqowA9yAtZXV0Uiap61bIO81FQ==", + "requires": { + "@typescript-eslint/types": "4.28.1", + "@typescript-eslint/visitor-keys": "4.28.1", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.1.tgz", + "integrity": "sha512-K4HMrdFqr9PFquPu178SaSb92CaWe2yErXyPumc8cYWxFmhgJsNY9eSePmO05j0JhBvf2Cdhptd6E6Yv9HVHcg==", + "requires": { + "@typescript-eslint/types": "4.28.1", + "eslint-visitor-keys": "^2.0.0" + } + }, "acorn": { "version": "5.7.4", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", @@ -265,6 +688,11 @@ } } }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==" + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -291,8 +719,24 @@ "kind-of": "^3.0.2", "longest": "^1.0.1", "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -302,7 +746,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -352,6 +795,23 @@ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, + "array-includes": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.5" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -359,6 +819,17 @@ "dev": true, "optional": true }, + "array.prototype.flatmap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", + "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "function-bind": "^1.1.1" + } + }, "asar": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/asar/-/asar-2.1.0.tgz", @@ -442,6 +913,11 @@ "dev": true, "optional": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" + }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -489,8 +965,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", @@ -549,13 +1024,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "optional": true } } }, @@ -609,7 +1077,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -619,8 +1086,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, "requires": { "fill-range": "^7.0.1" } @@ -839,6 +1304,20 @@ } } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", @@ -872,7 +1351,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -882,8 +1360,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" } } }, @@ -1001,7 +1478,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -1009,8 +1485,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "combined-stream": { "version": "1.0.8", @@ -1042,8 +1517,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -1177,6 +1651,16 @@ "sha.js": "^2.4.8" } }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "cross-zip": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cross-zip/-/cross-zip-3.1.0.tgz", @@ -1244,6 +1728,95 @@ "array-find-index": "^1.0.1" } }, + "d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "requires": { + "internmap": "^1.0.0" + } + }, + "d3-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz", + "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" + }, + "d3-delaunay": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", + "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "requires": { + "delaunator": "4" + } + }, + "d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + }, + "d3-interpolate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz", + "integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==", + "requires": { + "d3-color": "1 - 2" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-scale": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", + "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", + "requires": { + "d3-array": "^2.3.0", + "d3-format": "1 - 2", + "d3-interpolate": "1.2.0 - 2", + "d3-time": "^2.1.1", + "d3-time-format": "2 - 3" + } + }, + "d3-scale-chromatic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", + "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", + "requires": { + "d3-color": "1 - 2", + "d3-interpolate": "1 - 2" + } + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", + "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", + "requires": { + "d3-array": "2" + } + }, + "d3-time-format": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", + "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", + "requires": { + "d3-time": "1 - 2" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1286,6 +1859,11 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -1296,8 +1874,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "optional": true, "requires": { "object-keys": "^1.0.12" }, @@ -1305,9 +1881,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "optional": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" } } }, @@ -1353,16 +1927,14 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "optional": true } } }, + "delaunator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", + "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1404,6 +1976,29 @@ } } }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + } + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, "dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -1630,6 +2225,11 @@ } } }, + "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==" + }, "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -1664,6 +2264,14 @@ "tapable": "^0.2.7" } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "requires": { + "ansi-colors": "^4.1.1" + } + }, "env-paths": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", @@ -1686,6 +2294,46 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -1696,9 +2344,297 @@ "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "eslint": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.29.0.tgz", + "integrity": "sha512-82G/JToB9qIy/ArBzIWG9xvvwL3R86AlCjtGw+A29OMZDqhTybz/MByORSukGxeI+YPCR4coYyITKk8BFH9nDA==", + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "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==", + "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==" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "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==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==" + }, + "eslint-plugin-prettier": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-react": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz", + "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==", + "requires": { + "array-includes": "^3.1.3", + "array.prototype.flatmap": "^1.2.4", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.0.4", + "object.entries": "^1.1.4", + "object.fromentries": "^2.0.4", + "object.values": "^1.1.4", + "prop-types": "^15.7.2", + "resolve": "^2.0.0-next.3", + "string.prototype.matchall": "^4.0.5" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "2.0.0-next.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", + "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "events": { "version": "3.3.0", @@ -1867,13 +2803,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "optional": true } } }, @@ -1913,11 +2842,52 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" + }, + "fast-glob": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", + "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fastq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "requires": { + "reusify": "^1.0.4" + } + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -1926,6 +2896,14 @@ "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", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "requires": { + "flat-cache": "^3.0.4" + } + }, "file-saver": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz", @@ -1935,8 +2913,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, "requires": { "to-regex-range": "^5.0.1" } @@ -1960,6 +2936,30 @@ } } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==" + }, "flora-colossus": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-1.0.1.tgz", @@ -2043,8 +3043,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.3.2", @@ -2058,6 +3057,11 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, "galactus": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.1.tgz", @@ -2075,6 +3079,16 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-package-info": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz", @@ -2194,7 +3208,6 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2208,8 +3221,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "optional": true, "requires": { "is-glob": "^4.0.1" } @@ -2255,6 +3266,21 @@ "tunnel": "^0.0.6" } }, + "globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "requires": { + "type-fest": "^0.20.2" + }, + "dependencies": { + "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==" + } + } + }, "globalthis": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", @@ -2265,6 +3291,19 @@ "define-properties": "^1.1.3" } }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -2311,11 +3350,20 @@ "function-bind": "^1.1.1" } }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, "has-value": { "version": "1.0.0", @@ -2481,6 +3529,25 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, "indefinite-observable": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-2.0.1.tgz", @@ -2501,7 +3568,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2517,11 +3583,20 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" }, "invert-kv": { "version": "1.0.0", @@ -2537,6 +3612,18 @@ "optional": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-arrayish": { @@ -2544,6 +3631,11 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, + "is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2554,12 +3646,25 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "requires": { + "call-bind": "^1.0.2" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" + }, "is-core-module": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", @@ -2576,8 +3681,25 @@ "optional": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, + "is-date-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", + "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==" + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -2609,9 +3731,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "optional": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { "version": "1.1.0", @@ -2630,8 +3750,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "optional": true, "requires": { "is-extglob": "^2.1.1" } @@ -2641,12 +3759,20 @@ "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==" }, "is-plain-object": { "version": "2.0.4", @@ -2658,6 +3784,28 @@ "isobject": "^3.0.1" } }, + "is-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==" + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -2689,6 +3837,11 @@ "buffer-alloc": "^1.2.0" } }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -2706,6 +3859,15 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -2742,6 +3904,11 @@ "jsonify": "~0.0.0" } }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -2867,6 +4034,15 @@ "jss": "10.6.0" } }, + "jsx-ast-utils": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", + "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "requires": { + "array-includes": "^3.1.2", + "object.assign": "^4.1.2" + } + }, "junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", @@ -2883,13 +4059,11 @@ } }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "optional": true }, "lazy-cache": { "version": "1.0.4", @@ -2906,6 +4080,15 @@ "invert-kv": "^1.0.0" } }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -2948,8 +4131,12 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, "lodash.get": { "version": "4.4.2", @@ -2957,6 +4144,16 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=" + }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -2990,8 +4187,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "optional": true, "requires": { "yallist": "^4.0.0" } @@ -3104,6 +4299,11 @@ "trim-newlines": "^1.0.0" } }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -3204,13 +4404,6 @@ } } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "optional": true - }, "to-regex-range": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", @@ -3277,7 +4470,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3341,17 +4533,13 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "optional": true - } } }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -3561,9 +4749,24 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } } } }, + "object-inspect": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==" + }, "object-keys": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", @@ -3579,6 +4782,45 @@ "isobject": "^3.0.0" } }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + } + } + }, + "object.entries": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz", + "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.2" + } + }, + "object.fromentries": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz", + "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "has": "^1.0.3" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -3589,15 +4831,37 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", + "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.2" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -3649,6 +4913,14 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, "parse-asn1": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", @@ -3707,8 +4979,12 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.7", @@ -3751,9 +5027,7 @@ "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "optional": true + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "pify": { "version": "2.3.0", @@ -3796,12 +5070,30 @@ "dev": true, "optional": true }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, "prepend-http": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==" + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-bytes": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", @@ -3825,8 +5117,7 @@ "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, "progress-stream": { "version": "1.2.0", @@ -3926,6 +5217,19 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3993,6 +5297,28 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-motion": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz", + "integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==", + "requires": { + "performance-now": "^0.2.0", + "prop-types": "^15.5.8", + "raf": "^3.1.0" + }, + "dependencies": { + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" + } + } + }, "react-select": { "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/react-select/-/react-select-1.0.0-rc.10.tgz", @@ -4089,6 +5415,20 @@ "safe-regex": "^1.1.0" } }, + "regexp.prototype.flags": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", + "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -4150,12 +5490,22 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -4165,6 +5515,11 @@ "path-parse": "^1.0.6" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -4188,6 +5543,11 @@ "dev": true, "optional": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -4240,6 +5600,14 @@ } } }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4347,6 +5715,29 @@ "safe-buffer": "^5.0.1" } }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -4360,6 +5751,49 @@ "string-width": "^1.0.1" } }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.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==", + "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==", + "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==" + }, + "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==" + } + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -4469,13 +5903,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "optional": true } } }, @@ -4487,6 +5914,18 @@ "optional": true, "requires": { "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "source-list-map": { @@ -4724,6 +6163,39 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.matchall": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", + "integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.2", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.3.1", + "side-channel": "^1.0.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -4785,7 +6257,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -4795,12 +6266,76 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "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==" + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "tapable": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", "dev": true }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, "throttleit": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", @@ -4862,6 +6397,18 @@ "optional": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "to-readable-stream": { @@ -4887,8 +6434,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "optional": true, "requires": { "is-number": "^7.0.0" } @@ -4928,6 +6473,19 @@ "semver": "^5.0.1" } }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "requires": { + "tslib": "^1.8.1" + } + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -4954,6 +6512,14 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -5010,6 +6576,17 @@ "dev": true, "optional": true }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -5168,6 +6745,11 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -5365,6 +6947,16 @@ "dev": true, "optional": true }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -5480,6 +7072,12 @@ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, "json5": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", @@ -5527,6 +7125,26 @@ } } }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", @@ -5539,6 +7157,11 @@ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", "dev": true }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", @@ -5558,8 +7181,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xmlbuilder": { "version": "9.0.7", @@ -5590,9 +7212,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "optional": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "6.6.0", diff --git a/package.json b/package.json index 359c58e..77efbbc 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "build-release": "webpack -p --config config/webpack.config.js", "package-darwin": "electron-packager . --platform=darwin --arch=x64 --electron-version=2.0.8 --overwrite --icon=vomit.icns --out=release_builds --tmpdir=/tmp", "package-linux": "electron-packager . --platform=linux --arch=x64 --electron-version=2.0.8 --overwrite --out=release_builds --tmpdir=/tmp", - "package-win32": "electron-packager . --platform=win32 --arch=x64 --electron-version=2.0.8 --overwrite --out=release_builds --tmpdir=/tmp" + "package-win32": "electron-packager . --platform=win32 --arch=x64 --electron-version=2.0.8 --overwrite --out=release_builds --tmpdir=/tmp", + "check-lint": "eslint \"src/**/*.{js,ts,tsx}\"", + "lint": "eslint \"src/**/*.{js,ts,tsx}\" --quiet --fix" }, "author": "Michael Bebenita", "license": "ISC", @@ -27,10 +29,20 @@ "dependencies": { "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", + "@nivo/core": "^0.72.0", + "@nivo/line": "^0.72.0", + "@nivo/pie": "^0.72.0", + "@typescript-eslint/eslint-plugin": "^4.26.0", + "@typescript-eslint/parser": "^4.26.0", "argparse": "^1.0.9", "electron": "^5.0.7", "electron-packager-dummy-wine": "^1.0.2", + "eslint": "^7.27.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-react": "^7.24.0", "file-saver": "^1.3.3", + "prettier": "^2.3.0", "react": "^16.14.0", "react-dom": "^16.14.0", "react-input-autosize": "1.1.0", diff --git a/src/YUVCanvas.ts b/src/YUVCanvas.ts index 9765a15..87cdf58 100644 --- a/src/YUVCanvas.ts +++ b/src/YUVCanvas.ts @@ -1,31 +1,31 @@ const trace = false; -function assert(c: any, message: string = "") { +function assert(c: any, message = '') { if (!c) { throw new Error(message); } } function compileShader(gl: any, type: number, source: string) { - let shader = gl.createShader(type); + const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - let err = gl.getShaderInfoLog(shader); + const err = gl.getShaderInfoLog(shader); gl.deleteShader(shader); throw new Error('GL shader compilation for ' + type + ' failed: ' + err); } return shader; } function compileProgram(gl: any, vertSource: string, fragSource: string) { - let vs = compileShader(gl, gl.VERTEX_SHADER, vertSource); - let fs = compileShader(gl, gl.FRAGMENT_SHADER, fragSource); - let program = gl.createProgram(); + const vs = compileShader(gl, gl.VERTEX_SHADER, vertSource); + const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragSource); + const program = gl.createProgram(); gl.attachShader(program, vs); gl.attachShader(program, fs); gl.deleteShader(vs); gl.deleteShader(fs); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - var err = gl.getProgramInfoLog(program); + const err = gl.getProgramInfoLog(program); gl.deleteProgram(program); throw new Error('GL program linking failed: ' + err); } @@ -47,10 +47,10 @@ export interface YCbCrBuffer { export class YUVCanvas { gl: any; - firstRun: boolean = true; - useWebGL2: boolean = true; + firstRun = true; + useWebGL2 = true; constructor(public canvas: HTMLCanvasElement) { - let creationAttribs = { + const creationAttribs = { antialias: false, alpha: false, depth: false, @@ -58,14 +58,14 @@ export class YUVCanvas { }; this.gl = canvas.getContext('webgl2', creationAttribs); if (!this.gl) { - console.log("WebGL2 is not available, falling back on WebGL1.") + console.log('WebGL2 is not available, falling back on WebGL1.'); this.gl = canvas.getContext('webgl', creationAttribs); this.useWebGL2 = false; } - assert(this.gl, "WebGL 2 is Unavailable"); - let gl = this.gl; + assert(this.gl, 'WebGL 2 is Unavailable'); + const gl = this.gl; - let vertSource = ` + const vertSource = ` attribute vec2 aPosition; // [0, 1] varying vec2 vTexCoord; void main() { @@ -80,7 +80,7 @@ void main() { [G] = [1.16438, -0.21325, -0.53291] x [Cb - 0.50196] [B] [1.16438, 2.11240, 0.00000] [Cr - 0.50196] */ - let fragSource = ` + const fragSource = ` precision mediump float; uniform sampler2D uTextureY; uniform sampler2D uTextureCb; @@ -101,7 +101,7 @@ void main() { } `; - let program = compileProgram(gl, vertSource, fragSource); + const program = compileProgram(gl, vertSource, fragSource); program.aPosition = gl.getAttribLocation(program, 'aPosition'); program.uTexture = [ gl.getUniformLocation(program, 'uTextureY'), @@ -112,7 +112,7 @@ void main() { this.checkError(); for (let i = 0; i < 3; i++) { - assert(program.uTexture[i], "missing program.uTexture[" + i + "]"); + assert(program.uTexture[i], 'missing program.uTexture[' + i + ']'); gl.uniform1i(program.uTexture[i], i); gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, gl.createTexture()); @@ -123,12 +123,7 @@ void main() { } this.checkError(); - let vertData = [ - 0, 0, - 1, 0, - 0, 1, - 1, 1, - ]; + const vertData = [0, 0, 1, 0, 0, 1, 1, 1]; gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertData), gl.STATIC_DRAW); gl.vertexAttribPointer(program.aPosition, 2, gl.FLOAT, false, 0, 0); @@ -136,54 +131,48 @@ void main() { this.checkError(); } checkError() { - let err = this.gl.getError(); + const err = this.gl.getError(); if (err != 0) { - console.error("WebGL Error " + err); + console.error('WebGL Error ' + err); } } drawFrame(yCbCrBuffer: YCbCrBuffer) { - let gl = this.gl; + const gl = this.gl; - let format = this.useWebGL2 ? gl.RED : gl.LUMINANCE; - let internalFormat = this.useWebGL2 ? gl.R8 : gl.LUMINANCE; - let width = yCbCrBuffer.width; - let height = yCbCrBuffer.height; - let hdec = yCbCrBuffer.hdec; - let vdec = yCbCrBuffer.vdec; + const format = this.useWebGL2 ? gl.RED : gl.LUMINANCE; + const internalFormat = this.useWebGL2 ? gl.R8 : gl.LUMINANCE; + const width = yCbCrBuffer.width; + const height = yCbCrBuffer.height; + const hdec = yCbCrBuffer.hdec; + const vdec = yCbCrBuffer.vdec; - if (this.firstRun || - gl.drawingBufferWidth != yCbCrBuffer.width || - gl.drawingBufferHeight != yCbCrBuffer.height) - { + if (this.firstRun || gl.drawingBufferWidth != yCbCrBuffer.width || gl.drawingBufferHeight != yCbCrBuffer.height) { this.firstRun = false; trace && console.log('Resizing to:', yCbCrBuffer.width, yCbCrBuffer.height); gl.canvas.width = width; gl.canvas.height = height; - assert(gl.drawingBufferWidth == yCbCrBuffer.width, "bad drawingbufferWidth"); - assert(gl.drawingBufferHeight == yCbCrBuffer.height, "bad drawingbufferHeight"); + assert(gl.drawingBufferWidth == yCbCrBuffer.width, 'bad drawingbufferWidth'); + assert(gl.drawingBufferHeight == yCbCrBuffer.height, 'bad drawingbufferHeight'); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.activeTexture(gl.TEXTURE0); - gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, - width, height, 0, format, gl.UNSIGNED_BYTE, null); + gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, gl.UNSIGNED_BYTE, null); gl.activeTexture(gl.TEXTURE1); - gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, - width >> hdec, height >> vdec, 0, format, gl.UNSIGNED_BYTE, null); + gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width >> hdec, height >> vdec, 0, format, gl.UNSIGNED_BYTE, null); gl.activeTexture(gl.TEXTURE2); - gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, - width >> hdec, height >> vdec, 0, format, gl.UNSIGNED_BYTE, null); + gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width >> hdec, height >> vdec, 0, format, gl.UNSIGNED_BYTE, null); } const start = performance.now(); let lastLapTime = start; function lap(name: string) { - let now = performance.now(); + const now = performance.now(); const diff = now - lastLapTime; lastLapTime = now; - trace && console.log(diff.toFixed(2) + " ms"); + trace && console.log(diff.toFixed(2) + ' ms'); } // Update @@ -194,31 +183,48 @@ void main() { // We can't specify a stride with WebGL1, so make sure it's tightly packed. assert(yCbCrBuffer.strideY === width); } - gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, - format, gl.UNSIGNED_BYTE, yCbCrBuffer.bytesY); - lap("Upload Y"); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, gl.UNSIGNED_BYTE, yCbCrBuffer.bytesY); + lap('Upload Y'); gl.activeTexture(gl.TEXTURE1); if (this.useWebGL2) { gl.pixelStorei(gl.UNPACK_ROW_LENGTH, yCbCrBuffer.strideCb); } else { assert(yCbCrBuffer.strideCb === width >> hdec); } - gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width >> hdec, height >> vdec, - format, gl.UNSIGNED_BYTE, yCbCrBuffer.bytesCb); - lap("Upload Cb"); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + width >> hdec, + height >> vdec, + format, + gl.UNSIGNED_BYTE, + yCbCrBuffer.bytesCb, + ); + lap('Upload Cb'); gl.activeTexture(gl.TEXTURE2); if (this.useWebGL2) { gl.pixelStorei(gl.UNPACK_ROW_LENGTH, yCbCrBuffer.strideCr); } else { assert(yCbCrBuffer.strideCr === width >> hdec); } - gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width >> hdec, height >> vdec, - format, gl.UNSIGNED_BYTE, yCbCrBuffer.bytesCr); - lap("Upload Cr"); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + width >> hdec, + height >> vdec, + format, + gl.UNSIGNED_BYTE, + yCbCrBuffer.bytesCr, + ); + lap('Upload Cr'); // Draw gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - lap("Draw"); + lap('Draw'); trace && console.log('total:', (performance.now() - start).toFixed(2)); this.checkError(); - }; + } } diff --git a/src/components/Analyzer.tsx b/src/components/Analyzer.tsx index 989330d..af8088d 100644 --- a/src/components/Analyzer.tsx +++ b/src/components/Analyzer.tsx @@ -1,43 +1,88 @@ -import * as React from "react"; +import * as React from 'react'; -import { getColor, makePattern, reverseMap, palette, hashString, makeBlockSizeLog2MapByValue, HEAT_COLORS, Decoder, Rectangle, Size, AnalyzerFrame, loadFramesFromJson, downloadFile, Histogram, Accounting, AccountingSymbolMap, clamp, Vector, localFiles, localFileProtocol } from "./analyzerTools"; -import { HistogramComponent } from "./Histogram"; -import { TRACE_RENDERING, padLeft, log2, assert, unreachable } from "./analyzerTools"; +import { + getColor, + makePattern, + reverseMap, + palette, + hashString, + makeBlockSizeLog2MapByValue, + HEAT_COLORS, + Decoder, + Rectangle, + Size, + AnalyzerFrame, + loadFramesFromJson, + downloadFile, + Histogram, + Accounting, + AccountingSymbolMap, + clamp, + Vector, + localFiles, + localFileProtocol, + FilmGrainParams, +} from './analyzerTools'; +import { HistogramComponent } from './Histogram'; +import { TRACE_RENDERING, padLeft, log2, assert, unreachable } from './analyzerTools'; import { Checkbox, Dialog, DialogActions, - DialogContent, DialogTitle, Divider, FormControlLabel, FormGroup, IconButton, Menu, MenuItem, Select, Slider, Tab, + DialogContent, + DialogTitle, + Divider, + FormControlLabel, + FormGroup, + RadioGroup, + Radio, + IconButton, + Menu, + MenuItem, + Select, + Slider, + Tab, Table, TableBody, TableCell, TableHead, - TableRow, Tabs, TextField, Toolbar, Tooltip, Typography -} from "@material-ui/core"; -import Button from "@material-ui/core/Button"; -import { TextAlignProperty } from "csstype"; -import LayersIcon from "@material-ui/icons/Layers"; -import Replay30Icon from "@material-ui/icons/Replay30"; -import ZoomInIcon from "@material-ui/icons/ZoomIn"; -import ZoomOutIcon from "@material-ui/icons/ZoomOut"; -import StopIcon from "@material-ui/icons/Stop"; -import SkipNextIcon from "@material-ui/icons/SkipNext"; -import PlayArrowIcon from "@material-ui/icons/PlayArrow"; -import SkipPreviousIcon from "@material-ui/icons/SkipPrevious"; -import ImageIcon from "@material-ui/icons/Image"; -import ClearIcon from "@material-ui/icons/Clear"; -import ShareIcon from "@material-ui/icons/Share"; -import {red, grey} from "@material-ui/core/colors"; + TableRow, + Tabs, + TextField, + Toolbar, + Tooltip, + Typography, + TableContainer, +} from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import { TextAlignProperty } from 'csstype'; +import LayersIcon from '@material-ui/icons/Layers'; +import Replay30Icon from '@material-ui/icons/Replay30'; +import ZoomInIcon from '@material-ui/icons/ZoomIn'; +import ZoomOutIcon from '@material-ui/icons/ZoomOut'; +import StopIcon from '@material-ui/icons/Stop'; +import SkipNextIcon from '@material-ui/icons/SkipNext'; +import PlayArrowIcon from '@material-ui/icons/PlayArrow'; +import SkipPreviousIcon from '@material-ui/icons/SkipPrevious'; +import ImageIcon from '@material-ui/icons/Image'; +import ClearIcon from '@material-ui/icons/Clear'; +import ShareIcon from '@material-ui/icons/Share'; +import { red, grey } from '@material-ui/core/colors'; + +import { theme } from '../theme'; +import { LineGraph } from './LineGraph'; +import { PieGraph } from './PieGraph'; declare const Mousetrap; -declare var shortenUrl; -declare var document; -declare var window; +declare let shortenUrl; +declare let document; +declare let window; const SUPER_BLOCK_SIZE = 64; const ZOOM_WIDTH = 500; const ZOOM_SOURCE = 64; -const DEFAULT_CONFIG = "--disable-multithread --disable-runtime-cpu-detect --target=generic-gnu --enable-accounting --enable-analyzer --enable-aom_highbitdepth --extra-cflags=-D_POSIX_SOURCE --enable-inspection --disable-docs --disable-webm-io --enable-experimental"; +const DEFAULT_CONFIG = + '--disable-multithread --disable-runtime-cpu-detect --target=generic-gnu --enable-accounting --enable-analyzer --enable-aom_highbitdepth --extra-cflags=-D_POSIX_SOURCE --enable-inspection --disable-docs --disable-webm-io --enable-experimental'; const DERING_STRENGTHS = 21; const CLPF_STRENGTHS = 4; @@ -45,7 +90,7 @@ enum VisitMode { Block, SuperBlock, TransformBlock, - Tile + Tile, } enum HistogramTab { @@ -57,7 +102,23 @@ enum HistogramTab { PredictionMode, UVPredictionMode, Skip, - DualFilterType + DualFilterType, + CompoundType, + MotionMode, +} + +enum GraphTab { + Bits, + Symbols, + BlockSize, + TransformSize, + TransformType, + PredictionMode, + UVPredictionMode, + Skip, + DualFilterType, + CompoundType, + MotionMode, } function colorScale(v, colors) { @@ -66,7 +127,7 @@ function colorScale(v, colors) { function keyForValue(o: Object, value: any): string { if (o) { - for (let k in o) { + for (const k in o) { if (o[k] === value) { return k; } @@ -78,9 +139,9 @@ function keyForValue(o: Object, value: any): string { function shuffle(array: any[], count: number) { // Shuffle Indices for (let j = 0; j < count; j++) { - let a = Math.random() * array.length | 0; - let b = Math.random() * array.length | 0; - let t = array[a]; + const a = (Math.random() * array.length) | 0; + const b = (Math.random() * array.length) | 0; + const t = array[a]; array[a] = array[b]; array[b] = t; } @@ -90,7 +151,7 @@ function blockSizeArea(frame: AnalyzerFrame, size: number) { return (1 << map[size][0]) * (1 << map[size][1]); } function forEachValue(o: any, fn: (v: any) => void) { - for (let n in o) { + for (const n in o) { fn(o[n]); } } @@ -107,7 +168,7 @@ function withCommas(v: number) { return v.toLocaleString(); } function toByteSize(v: number) { - return withCommas(v) + " Bytes"; + return withCommas(v) + ' Bytes'; } function getLineOffset(lineWidth: number) { @@ -117,12 +178,12 @@ function getLineOffset(lineWidth: number) { function toCflAlphas(cfl_alpha_idx: number, cfl_alpha_sign: number) { cfl_alpha_idx &= 255; cfl_alpha_sign &= 7; - let sign_u = ((cfl_alpha_sign + 1) * 11) >> 5; - let sign_v = cfl_alpha_sign + 1 - 3 * sign_u; - let alpha_u = 1 + (cfl_alpha_idx >> 4); - let alpha_v = 1 + (cfl_alpha_idx & 15); - let cfl_alpha_u = [0, -1, 1][sign_u] * alpha_u; - let cfl_alpha_v = [0, -1, 1][sign_v] * alpha_v; + const sign_u = ((cfl_alpha_sign + 1) * 11) >> 5; + const sign_v = cfl_alpha_sign + 1 - 3 * sign_u; + const alpha_u = 1 + (cfl_alpha_idx >> 4); + const alpha_v = 1 + (cfl_alpha_idx & 15); + const cfl_alpha_u = [0, -1, 1][sign_u] * alpha_u; + const cfl_alpha_v = [0, -1, 1][sign_v] * alpha_v; return [cfl_alpha_u, cfl_alpha_v]; } @@ -148,12 +209,12 @@ interface BlockVisitor { } interface AnalyzerViewProps { - groups: AnalyzerFrame[][], - groupNames?: string[], + groups: AnalyzerFrame[][]; + groupNames?: string[]; playbackFrameRate?: number; blind?: number; onDecodeAdditionalFrames: (count: number) => void; - decoderVideoUrlPairs?: { decoderUrl: string, videoUrl: string, decoderName: string }[]; + decoderVideoUrlPairs?: { decoderUrl: string; videoUrl: string; decoderName: string }[]; } interface AlertProps { @@ -162,8 +223,7 @@ interface AlertProps { title: string; description: string; } -export class Alert extends React.Component { +export class Alert extends React.Component { constructor(props: AlertProps) { super(props); } @@ -171,272 +231,561 @@ export class Alert extends React.Component - - {this.props.title} - - {this.props.description} - - - - - - - + return ( +
+ + {this.props.title} + {this.props.description} + + + + + +
+ ); } } -export class AccountingComponent extends React.Component<{ - symbols: AccountingSymbolMap; -}, { - - }> { +export class AccountingComponent extends React.Component< + { + symbols: AccountingSymbolMap; + }, + {} +> { render() { - let symbols = this.props.symbols; + const symbols = this.props.symbols; let total = 0; forEachValue(symbols, (symbol) => { total += symbol.bits; }); - let rows = [] - for (let name in symbols) { - let symbol = symbols[name]; - rows.push( - {name} - {fractionalBitsToString(symbol.bits)} - {toPercent(symbol.bits / total)} - {withCommas(symbol.samples)} - ); + const rows = []; + for (const name in symbols) { + const symbol = symbols[name]; + rows.push( + + {name} + {fractionalBitsToString(symbol.bits)} + {toPercent(symbol.bits / total)} + {withCommas(symbol.samples)} + , + ); } - return
- - - - Symbol - Bits {fractionalBitsToString(total)} - % - Samples - - - - {rows} - -
-
+ return ( +
+ + + + Symbol + + Bits {fractionalBitsToString(total)} + + + % + + + Samples + + + + {rows} +
+
+ ); } } -export class FrameInfoComponent extends React.Component<{ - frame: AnalyzerFrame; - activeFrame: number; - activeGroup: number; -}, { - - }> { +export class FrameInfoComponent extends React.Component< + { + frame: AnalyzerFrame; + activeFrame: number; + activeGroup: number; + }, + {} +> { render() { - let frame = this.props.frame; - let valueStyle = { textAlign: "right" as TextAlignProperty, fontSize: "12px" }; - return
- - - - Video{this.props.activeGroup} - - - Frame{this.props.activeFrame + 1} - - - Frame Type{frame.json.frameType} - - - Show Frame{frame.json.showFrame} - - - BaseQIndex{frame.json.baseQIndex} - - - Frame Size{frame.image.width} x {frame.image.height} - - - MI Size{1 << frame.miSizeLog2} - - - DeltaQ Res / Present Flag{frame.json.deltaQRes} / {frame.json.deltaQPresentFlag} - - -
-
+ const frame = this.props.frame; + const valueStyle = { textAlign: 'right' as TextAlignProperty, fontSize: '12px' }; + return ( +
+ + + + Video + {this.props.activeGroup} + + + Frame + {this.props.activeFrame + 1} + + + Frame Type + {frame.json.frameType} + + + Show Frame + {frame.json.showFrame} + + + BaseQIndex + {frame.json.baseQIndex} + + + Frame Size + + {frame.image.width} x {frame.image.height} + + + + MI Size + {1 << frame.miSizeLog2} + + + DeltaQ Res / Present Flag + + {frame.json.deltaQRes} / {frame.json.deltaQPresentFlag} + + + +
+
+ ); } } -export class ModeInfoComponent extends React.Component<{ - frame: AnalyzerFrame; - position: Vector; -}, { - - }> { +export class ModeInfoComponent extends React.Component< + { + frame: AnalyzerFrame; + position: Vector; + }, + {} +> { render() { - let c = this.props.position.x; - let r = this.props.position.y; - let json = this.props.frame.json; + const c = this.props.position.x; + const r = this.props.position.y; + const json = this.props.frame.json; function getProperty(name: string): string { - if (!json[name]) return "N/A"; - let v = json[name][r][c]; - if (!json[name + "Map"]) return String(v); - return keyForValue(json[name + "Map"], v); + if (!json[name]) return 'N/A'; + const v = json[name][r][c]; + if (!json[name + 'Map']) return String(v); + return keyForValue(json[name + 'Map'], v); } function getSuperBlockProperty(name: string): string { - if (!json[name]) return "N/A"; - let v = json[name][r & ~7][c & ~7]; - if (!json[name + "Map"]) return String(v); - return keyForValue(json[name + "Map"], v); + if (!json[name]) return 'N/A'; + const v = json[name][r & ~7][c & ~7]; + if (!json[name + 'Map']) return String(v); + return keyForValue(json[name + 'Map'], v); } function getMotionVector() { - let motionVectors = json["motionVectors"]; - if (!motionVectors) return "N/A"; - let v = motionVectors[r][c]; - return `${v[0]},${v[1]} ${v[2]},${v[3]}`; + const motionVectors = json['motionVectors']; + if (!motionVectors) return 'N/A'; + const v = motionVectors[r][c]; + return `<${v[0]},${v[1]}>, <${v[2]},${v[3]}>`; } function getReferenceFrame() { - let referenceFrame = json["referenceFrame"]; - if (!referenceFrame) return "N/A"; - let map = json["referenceFrameMap"]; - let v = referenceFrame[r][c]; - let a = v[0] >= 0 ? keyForValue(map, v[0]) : "N/A"; - let b = v[1] >= 0 ? keyForValue(map, v[1]) : "N/A"; + const referenceFrame = json['referenceFrame']; + if (!referenceFrame) return 'N/A'; + const map = json['referenceFrameMap']; + const v = referenceFrame[r][c]; + const a = v[0] >= 0 ? keyForValue(map, v[0]) : 'N/A'; + const b = v[1] >= 0 ? keyForValue(map, v[1]) : 'N/A'; return `${a}, ${b}`; } function getCFL() { - if (json["cfl_alpha_idx"] === undefined) { - return "N/A"; + if (json['cfl_alpha_idx'] === undefined) { + return 'N/A'; } - let cfl_alpha_idx = json["cfl_alpha_idx"][r][c]; - let cfl_alpha_sign = json["cfl_alpha_sign"][r][c]; - let [cfl_alpha_u, cfl_alpha_v] = toCflAlphas(cfl_alpha_idx, cfl_alpha_sign); + const cfl_alpha_idx = json['cfl_alpha_idx'][r][c]; + const cfl_alpha_sign = json['cfl_alpha_sign'][r][c]; + const [cfl_alpha_u, cfl_alpha_v] = toCflAlphas(cfl_alpha_idx, cfl_alpha_sign); return `${cfl_alpha_u},${cfl_alpha_v}`; } function getDualFilterType() { - if (json["dualFilterType"] === undefined) { - return "N/A"; + if (json['dualFilterType'] === undefined) { + return 'N/A'; } - let map = json["dualFilterTypeMap"]; - return keyForValue(map, json["dualFilterType"][r][c]); + const map = json['dualFilterTypeMap']; + return keyForValue(map, json['dualFilterType'][r][c]); } function getDeltaQIndex() { - if (json["delta_q"] === undefined) { - return "N/A"; + if (json['delta_q'] === undefined) { + return 'N/A'; } - return json["delta_q"][r][c]; + return json['delta_q'][r][c]; } function getSegId() { - if (json["seg_id"] === undefined) { - return "N/A"; + if (json['seg_id'] === undefined) { + return 'N/A'; } - return json["seg_id"][r][c]; + return json['seg_id'][r][c]; } - let valueStyle = { textAlign: "right" as TextAlignProperty, fontSize: "12px" }; - return
- - - - Block Position: MI (col, row)({c}, {r}) - - - Block Size{getProperty("blockSize")} - - - Transform Size{getProperty("transformSize")} - - - Transform Type{getProperty("transformType")} - - - Mode{getProperty("mode")} - - - UV Mode{getProperty("uv_mode")} - - - Skip{getProperty("skip")} - - - CDEF{getSuperBlockProperty("cdef_level")} / {getSuperBlockProperty("cdef_strength")} - - - Motion Vectors{getMotionVector()} - - - Reference Frame{getReferenceFrame()} - - - CFL{getCFL()} - - - Dual Filter Type{getDualFilterType()} - - - DeltaQ Index{getDeltaQIndex()} - - - Segment ID{getSegId()} - - -
-
+ const valueStyle = { textAlign: 'right' as TextAlignProperty, fontSize: '12px' }; + return ( +
+ + + + Block Position: MI (col, row) + + ({c}, {r}) + + + + Block Size + {getProperty('blockSize')} + + + Transform Size + {getProperty('transformSize')} + + + Transform Type + {getProperty('transformType')} + + + Mode + {getProperty('mode')} + + + UV Mode + {getProperty('uv_mode')} + + + Motion Mode + {getProperty('motion_mode')} + + + Compound Type + {getProperty('compound_type')} + + + Skip + {getProperty('skip')} + + + Motion Mode + {getProperty('motion_mode')} + + + CDEF + + {getSuperBlockProperty('cdef_level')} / {getSuperBlockProperty('cdef_strength')} + + + + Motion Vectors + {getMotionVector()} + + + Reference Frame + {getReferenceFrame()} + + + CFL + {getCFL()} + + + Dual Filter Type + {getDualFilterType()} + + + DeltaQ Index + {getDeltaQIndex()} + + + Segment ID + {getSegId()} + + +
+
+ ); } } -export class AnalyzerView extends React.Component { +export class FilmInfoComponent extends React.Component< + { + filmGrainParams?: FilmGrainParams; + containsFilm: boolean; + }, + {} +> { + filmCanvas: HTMLCanvasElement[]; + filmContext: CanvasRenderingContext2D[]; + ratio: number; + + constructor(props) { + super(props); + + this.filmCanvas = [null, null, null]; + this.filmContext = [null, null, null]; + const ratio = window.devicePixelRatio || 1; + this.ratio = ratio; + } + + resetFilmCanvas(canvas: HTMLCanvasElement, index: number) { + this.filmCanvas[index] = canvas; + if (!this.filmCanvas[index]) { + this.filmContext[index] = null; + return; + } + + if (index == 0) { + this.filmCanvas[index].style.width = '64px'; + this.filmCanvas[index].style.height = '64px'; + this.filmCanvas[index].width = 64 * this.ratio; // * this.ratio; + this.filmCanvas[index].height = 64 * this.ratio; // * this.ratio; + } else { + this.filmCanvas[index].style.width = '32px'; + this.filmCanvas[index].style.height = '32px'; + this.filmCanvas[index].width = 32 * this.ratio; // * this.ratio; + this.filmCanvas[index].height = 32 * this.ratio; // * this.ratio; + } + this.filmContext[index] = this.filmCanvas[index].getContext('2d'); + } + + drawFilm(filmIndex: number) { + if (!this.filmCanvas[filmIndex]) { + return; + } + TRACE_RENDERING && console.log('drawFilm'); + + let normalizedGrain; + switch (filmIndex) { + case 0: + normalizedGrain = this.props.filmGrainParams.normalizedGrain_y; + break; + case 1: + normalizedGrain = this.props.filmGrainParams.normalizedGrain_cb; + break; + case 2: + normalizedGrain = this.props.filmGrainParams.normalizedGrain_cr; + break; + } + + assert(normalizedGrain.length > 0); + + const imageVal = new ImageData(normalizedGrain.length, normalizedGrain[0].length); + this.filmCanvas[filmIndex].width = imageVal.width; + this.filmCanvas[filmIndex].height = imageVal.height; + const I = imageVal.data; + for (let i = 0; i < normalizedGrain.length; i++) { + for (let j = 0; j < normalizedGrain[i].length; j++) { + const index = (Math.imul(i, normalizedGrain[i].length) + j) << 2; + // const index = i * normalizedGrain.length + j; + const y = normalizedGrain[i][j]; + I[index + 0] = y; + I[index + 1] = y; + I[index + 2] = y; + I[index + 3] = 255; + } + } + + this.filmContext[filmIndex].putImageData(imageVal, 0, 0); + this.filmContext[filmIndex].drawImage(this.filmCanvas[filmIndex], 0, 0, 100 * this.ratio, 100 * this.ratio); + } + + componentDidUpdate() { + if (this.props.containsFilm) { + this.drawFilm(0); + this.drawFilm(1); + this.drawFilm(2); + } + } + + componentDidMount() { + if (this.props.containsFilm) { + this.drawFilm(0); + this.drawFilm(1); + this.drawFilm(2); + } + } + + getFilmGrainLUT(filmGrainParams: FilmGrainParams) { + const data = [ + { + id: 'scaling_lut_y', + data: filmGrainParams.scaling_lut_y.map((value, index) => ({ + x: index, + y: value, + })), + }, + { + id: 'scaling_lut_cb', + data: filmGrainParams.scaling_lut_cb.map((value, index) => ({ + x: index, + y: value, + })), + }, + { + id: 'scaling_lut_cr', + data: filmGrainParams.scaling_lut_cr.map((value, index) => ({ + x: index, + y: value, + })), + }, + ]; + return data; + } + + renderTable() { + const filmGrainParams = this.props.filmGrainParams; + const tableData = [ + { + name: 'AR Coeff Lag', + value: filmGrainParams.ar_coeff_lag, + }, + { + name: 'AR Coeff Shift', + value: filmGrainParams.ar_coeff_shift, + }, + { + name: 'Overlap Flag', + value: filmGrainParams.overlap_flag, + }, + { + name: 'Random Seed', + value: filmGrainParams.random_seed, + }, + { + name: 'Grain Scale Shift', + value: filmGrainParams.grain_scale_shift, + }, + { + name: 'Chroma Scaling From Luma', + value: filmGrainParams.chroma_scaling_from_luma, + }, + { + name: 'Cb Mult', + value: filmGrainParams.cb_mult, + }, + { + name: 'Cb Luma Mult', + value: filmGrainParams.cb_luma_mult, + }, + { + name: 'Bit Depth', + value: filmGrainParams.bit_depth, + }, + ]; + + return ( + + + + + Parameter Name + Value + + + + {tableData.map((dp) => ( + + {dp.name} + {dp.value} + + ))} + +
+
+ ); + } + + render() { + return ( +
+ {!this.props.containsFilm ? ( +
Film Grain not used for this frame
+ ) : ( +
+
+

Film Grain Parameters

+ {this.renderTable()} +
+

Scaling LUT for each Pixel

+ +

Grain Blocks

+
+
+ this.resetFilmCanvas(self, 0)} width="100" height="100" /> +
Grain Block Y
+
+
+ this.resetFilmCanvas(self, 1)} width="100" height="100" /> +
Grain Block CB
+
+
+ this.resetFilmCanvas(self, 2)} width="100" height="100" /> +
Grain Block CR
+
+
+
+ )} +
+ ); + } +} + +export class AnalyzerView extends React.Component< + AnalyzerViewProps, + { + activeFrame: number; + activeGroup: number; + scale: number; + showDecodedImage: boolean; + showGrains: boolean; + showMotionVectors: boolean; + showReferenceFrames: boolean; + showBlockGrid: boolean; + showTileGrid: boolean; + showSuperBlockGrid: boolean; + showTransformGrid: boolean; + showSkip: boolean; + showFilters: boolean; + showCDEF: boolean; + showMotionMode: boolean; + showWedgeMode: boolean; + showMode: boolean; + showUVMode: boolean; + showCompoundTypes: boolean; + showSegment: boolean; + showBits: boolean; + showBitsScale: 'frame' | 'video' | 'videos'; + showBitsMode: 'linear' | 'heat' | 'heat-opaque'; + showGrainMode: -1 | 0 | 1 | 2; + showGrainType: 0 | 1; + showBitsFilter: ''; + showTransformType: boolean; + showTools: boolean; + showFrameComment: boolean; + activeHistogramTab: number; + layerMenuIsOpen: boolean; + + showDecodeDialog: boolean; + decodeFrameCount: number; + activeTab: number; + playInterval: any; + + graphType: string; + activeGraphTab: number; + + showLayersInZoom: boolean; + lockSelection: boolean; + layerAlpha: number; + shareUrl: string; + showShareUrlDialog: boolean; + } +> { public static defaultProps: AnalyzerViewProps = { groups: [], groupNames: null, playbackFrameRate: 30, blind: 0, onDecodeAdditionalFrames: null, - decoderVideoUrlPairs: [] + decoderVideoUrlPairs: [], }; activeGroupScore: number[][]; @@ -444,8 +793,12 @@ export class AnalyzerView extends React.Component { - this.setState({showLayersInZoom: !this.state.showLayersInZoom} as any); + this.setState({ showLayersInZoom: !this.state.showLayersInZoom } as any); e.preventDefault(); }); Mousetrap.bind(['x'], (e) => { - this.setState({lockSelection: !this.state.lockSelection} as any); + this.setState({ lockSelection: !this.state.lockSelection } as any); e.preventDefault(); }); - let self = this; + const self = this; function toggle(name, event) { self.toggleLayer(name); event.preventDefault(); } - let installedKeys = {}; - for (let name in this.options) { - let option = this.options[name]; + const installedKeys = {}; + for (const name in this.options) { + const option = this.options[name]; if (option.key) { if (installedKeys[option.key]) { - console.error("Key: " + option.key + " for " + option.description + ", is already mapped to " + installedKeys[option.key].description); + console.error( + 'Key: ' + + option.key + + ' for ' + + option.description + + ', is already mapped to ' + + installedKeys[option.key].description, + ); } installedKeys[option.key] = option; Mousetrap.bind([option.key], toggle.bind(this, name)); @@ -959,7 +1399,6 @@ export class AnalyzerView extends React.Component> miSizeLog2); r = r & ~(((1 << log2Map[1]) - 1) >> miSizeLog2); - return new Rectangle((c << miSizeLog2), (r << miSizeLog2), 1 << log2Map[0], 1 << log2Map[1]); + return new Rectangle(c << miSizeLog2, r << miSizeLog2, 1 << log2Map[0], 1 << log2Map[1]); } /** * Calculate MI coordinates. */ getMIPosition(frame: AnalyzerFrame, v: Vector): Vector { const miSizeLog2 = frame.miSizeLog2; - let c = (v.x / this.state.scale) >> miSizeLog2; - let r = (v.y / this.state.scale) >> miSizeLog2; + const c = (v.x / this.state.scale) >> miSizeLog2; + const r = (v.y / this.state.scale) >> miSizeLog2; return new Vector(c, r); } getActiveFrame(): AnalyzerFrame { @@ -1074,49 +1510,49 @@ export class AnalyzerView extends React.Component frame.accounting)); + const data = []; + const names = Accounting.getSortedSymbolNames(frames.map((frame) => frame.accounting)); frames.forEach((frame, i) => { - let row = { frame: i, total: 0 }; - let symbols = frame.accounting.createFrameSymbols(); + const row = { frame: i, total: 0 }; + const symbols = frame.accounting.createFrameSymbols(); let total = 0; - names.forEach(name => { - let symbol = symbols[name]; - let bits = symbol ? symbol.bits : 0; + names.forEach((name) => { + const symbol = symbols[name]; + const bits = symbol ? symbol.bits : 0; total += bits; }); names.forEach((name, i) => { - let symbol = symbols[name]; - let bits = symbol ? symbol.bits : 0; + const symbol = symbols[name]; + const bits = symbol ? symbol.bits : 0; row[i] = bits; }); data.push(row); }); - let nameMap = {}; + const nameMap = {}; names.forEach((name, i) => { nameMap[name] = i; }); - return data.map(data => new Histogram(data, nameMap)); + return data.map((data) => new Histogram(data, nameMap)); } onBitsScaleSelect(eventKey: any, event: Object) { - let showBitsScale = eventKey; + const showBitsScale = eventKey; this.setState({ showBitsScale } as any); } onBitsModeSelect(eventKey: any, event: Object) { - let showBitsMode = eventKey; + const showBitsMode = eventKey; this.setState({ showBitsMode } as any); } onBitsFilterSelect(eventKey: any, event: Object) { - let showBitsFilter = eventKey; + const showBitsFilter = eventKey; this.setState({ showBitsFilter } as any); } getActiveGroupScore() { let s = 0; - let j = this.state.activeGroup; + const j = this.state.activeGroup; for (let i = 0; i < this.activeGroupScore.length; i++) { s += this.activeGroupScore[i][j]; } @@ -1124,9 +1560,9 @@ export class AnalyzerView extends React.Component x.blockSizeHist); + return frames.map((x) => x.blockSizeHist); case HistogramTab.TransformSize: - return frames.map(x => x.transformSizeHist); + return frames.map((x) => x.transformSizeHist); case HistogramTab.TransformType: - return frames.map(x => x.transformTypeHist); + return frames.map((x) => x.transformTypeHist); case HistogramTab.PredictionMode: - return frames.map(x => x.predictionModeHist); + return frames.map((x) => x.predictionModeHist); case HistogramTab.UVPredictionMode: - return frames.map(x => x.uvPredictionModeHist); + return frames.map((x) => x.uvPredictionModeHist); case HistogramTab.Skip: - return frames.map(x => x.skipHist); + return frames.map((x) => x.skipHist); case HistogramTab.DualFilterType: - return frames.map(x => x.dualFilterTypeHist); + return frames.map((x) => x.dualFilterTypeHist); } return null; } @@ -1231,9 +1666,9 @@ export class AnalyzerView extends React.Component defaultOptions.indexOf(option) < 0).join(" "); + const defaultOptions = DEFAULT_CONFIG.split(' '); + const options = this.getActiveFrame().config.split(' '); + return options.filter((option) => defaultOptions.indexOf(option) < 0).join(' '); } downloadIvf() { @@ -1241,197 +1676,400 @@ export class AnalyzerView extends React.ComponentFrame Relative - Video Relative - Video Relative (all) - - - - - + const names = Accounting.getSortedSymbolNames(frames.map((frame) => frame.accounting)); + bitLayerToolbar = ( + +
+ + + +
+
+ ); + } + + let grainLayerToolbar = null; + + if (this.state.showGrains) { + grainLayerToolbar = ( + +
+ + + +
+
+ ); } let groupTabs = null; if (this.props.groups.length > 1) { - let tabs = []; + const tabs = []; for (let i = 0; i < this.props.groups.length; i++) { tabs.push(); } - groupTabs =
{ - this.setState({ - activeGroup: value, - } as any); - }}>{tabs} -
+ groupTabs = ( +
+ { + this.setState({ + activeGroup: value, + } as any); + }} + > + {tabs} + +
+ ); } - let layerMenuItems = []; - for (let name in this.options) { - let option = this.options[name]; + const layerMenuItems = []; + for (const name in this.options) { + const option = this.options[name]; layerMenuItems.push( - - {option.description}{option.key.toUpperCase()} - + + {option.description} + {option.key.toUpperCase()} + , ); } - sidePanel =
- - Share URL - - - - - - - - - {groupTabs} -
- Frame: {padLeft(this.state.activeFrame + 1, 2)}, Group: {this.getGroupName(this.state.activeGroup)} {this.getActiveFrameConfig()} -
- -
- - - - - - - {layerMenuItems} - - Reset Layers - - - - - - - - - - - - - - - - - - - {!this.state.playInterval ? : } - - - - - - - - - - - - - - - - - - - - - - - - - - - + sidePanel = ( +
+ + Share URL + + + + + + + + + {groupTabs} +
+ Frame: {padLeft(this.state.activeFrame + 1, 2)}, Group: {this.getGroupName(this.state.activeGroup)}{' '} + {this.getActiveFrameConfig()}
- - {bitLayerToolbar} - { - this.setState({ - activeTab: newValue - }); - }} variant="fullWidth"> - - - - - - - {this.state.activeTab === 0 &&
- this.resetZoomCanvas(self)} width="256" height="256"/> -
+ +
+ + + + + + + {layerMenuItems} + + Reset Layers + + + + + + + + + + + + + + + + + + + {!this.state.playInterval ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ {bitLayerToolbar} + {grainLayerToolbar} + { + this.setState({ + activeTab: newValue, + }); + }} + variant="fullWidth" + > + + + + + + + + {this.state.activeTab === 0 && ( +
+ this.resetZoomCanvas(self)} width="100" height="100" /> +
- this.setState({ showLayersInZoom: event.target.checked })} - />} label="Show Layers in Zoom: Z" /> + this.setState({ showLayersInZoom: event.target.checked })} + /> + } + label="Show Layers in Zoom: Z" + /> - this.setState({ lockSelection: event.target.checked })} - />} label="Lock Selection: X" /> + this.setState({ lockSelection: event.target.checked })} + /> + } + label="Lock Selection: X" + />
Layer Alpha
- { - this.setState({layerAlpha: value} as any); - }} + { + this.setState({ layerAlpha: value } as any); + }} /> +
-
} - {this.state.activeTab === 1 &&
- + )} + {this.state.activeTab === 1 && ( +
+
+ + this.setState({ graphType: e.target.value })} + > + } label="Histogram" /> + } label="Chart" /> + + +
+ {this.state.graphType === 'histogram' ? (
- this.setState({ activeHistogramTab: event.target.value } as any)} + > Bits Symbols Block Size @@ -1441,84 +2079,178 @@ export class AnalyzerView extends React.ComponentUV Prediction Mode Skip Dual Filter Type - + +
+ + +
+ ) : ( +
+ +
+ +
+
+
-
- -
} - {p && this.state.activeTab === 2 &&
- - -
} - {this.state.activeTab === 3 &&
- - -
} - {this.state.activeTab === 4 &&
- {' '} - -

-

-

Configuration

-

- {frame.config} -

-

Tips

-
    + )} +
+ )} + {p && this.state.activeTab === 2 && ( +
+ + +
+ )} + {this.state.activeTab === 3 && ( +
+ + +
+ )} + {this.state.activeTab === 4 && ( +
+ {' '} + +

+ +

+

+ +

+

Configuration

+

{frame.config}

+

Tips

+
  • Click anywhere on the image to lock focus and get mode info details.
  • All analyzer features have keyboard shortcuts, use them.
  • Toggle between video sequences by using the number keys: 1, 2, 3, etc.
  • -
-
} -
+ +
+ )} + {this.state.activeTab === 5 && ( + + )} +
+ ); } } - let activeGroup = this.state.activeGroup; - let groupName = this.props.groupNames ? this.props.groupNames[activeGroup] : String(activeGroup); + const activeGroup = this.state.activeGroup; + const groupName = this.props.groupNames ? this.props.groupNames[activeGroup] : String(activeGroup); - let result =
- this.downloadLink = self} /> - {this.state.showFrameComment && -
-
-
Config
-
{this.getActiveFrame().config}
-
Video
-
{groupName}
-
Group
-
{activeGroup}: {this.props.groupNames[activeGroup]}
-
Score
-
{this.getActiveGroupScore()}
-
Frame
-
{this.state.activeFrame}
+ const result = ( +
- } -
-
-
this.canvasContainer = self}> - this.displayCanvas = self} width="256" height="256" style={{ position: "absolute", left: 0, top: 0, zIndex: 0, imageRendering: "pixelated" }}> - this.overlayCanvas = self} width="256" height="256" style={{ position: "absolute", left: 0, top: 0, zIndex: 1, imageRendering: "pixelated", cursor: "crosshair", opacity: this.state.layerAlpha }}> + )} +
+
+
(this.canvasContainer = self)}> + (this.displayCanvas = self)} + width="256" + height="256" + style={{ + position: 'absolute', + left: 0, + top: 0, + zIndex: 0, + imageRendering: 'pixelated', + backgroundColor: '#333333', + }} + > + (this.grainCanvas = self)} + width="256" + height="256" + style={{ + position: 'absolute', + left: 0, + top: 0, + zIndex: 0, + imageRendering: 'pixelated', + opacity: this.state.layerAlpha, + }} + > + (this.overlayCanvas = self)} + width="256" + height="256" + style={{ + position: 'absolute', + left: 0, + top: 0, + zIndex: 1, + imageRendering: 'pixelated', + cursor: 'crosshair', + opacity: this.state.layerAlpha, + }} + > +
+ {sidePanel}
- {sidePanel}
-
+ ); return result; } drawSkip(frame: AnalyzerFrame, ctx: CanvasRenderingContext2D, src: Rectangle, dst: Rectangle) { - let skipGrid = frame.json["skip"]; - let skipMap = frame.json["skipMap"]; + const skipGrid = frame.json['skip']; + const skipMap = frame.json['skipMap']; this.fillBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr) => { - let v = skipGrid[r][c]; + const v = skipGrid[r][c]; if (v == skipMap.NO_SKIP) { return false; } @@ -1527,10 +2259,44 @@ export class AnalyzerView extends React.Component { + const v = motionMode[r][c]; + if (v in motionModeMapValue && v < 3) { + const value = motionModeMapValue[v]; + ctx.fillStyle = palette.motionMode[value]; + return true; + } else { + return false; + } + }); + } + + drawCompoundTypes(frame: AnalyzerFrame, ctx: CanvasRenderingContext2D, src: Rectangle, dst: Rectangle) { + const compoundTypes = frame.json['compound_type']; + const compoundTypeMap = frame.json['compound_typeMap']; + const compountTypeMapValue = reverseMap(compoundTypeMap); + if (!compoundTypes) return; + + this.fillBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr) => { + const v = compoundTypes[r][c]; + if (v in compountTypeMapValue && v < 4) { + const value = compountTypeMapValue[v]; + ctx.fillStyle = palette.compoundType[value]; + return true; + } + return false; + }); + } + drawFilters(frame: AnalyzerFrame, ctx: CanvasRenderingContext2D, src: Rectangle, dst: Rectangle) { - let dualFilterTypeGrid = frame.json["dualFilterType"]; + const dualFilterTypeGrid = frame.json['dualFilterType']; if (!dualFilterTypeGrid) return; - let dualFilterTypeMapByValue = reverseMap(frame.json["dualFilterTypeMap"]); + const dualFilterTypeMapByValue = reverseMap(frame.json['dualFilterTypeMap']); this.fillBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr) => { ctx.fillStyle = getColor(dualFilterTypeMapByValue[dualFilterTypeGrid[r][c]], palette.dualFilterType); return true; @@ -1538,12 +2304,12 @@ export class AnalyzerView extends React.Component= rows || c + x >= cols) { @@ -1557,41 +2323,535 @@ export class AnalyzerView extends React.Component { - if (allSkip(c, r)) { - return; - } - let v = levelGrid[r][c] + strengthGrid[r][c]; - if (!v) { - return false; - } - ctx.fillStyle = colorScale(v / (DERING_STRENGTHS + CLPF_STRENGTHS), HEAT_COLORS); - return true; - }, VisitMode.SuperBlock); + this.fillBlock( + frame, + ctx, + src, + dst, + (blockSize, c, r, sc, sr) => { + if (allSkip(c, r)) { + return; + } + const v = levelGrid[r][c] + strengthGrid[r][c]; + if (!v) { + return false; + } + ctx.fillStyle = colorScale(v / (DERING_STRENGTHS + CLPF_STRENGTHS), HEAT_COLORS); + return true; + }, + VisitMode.SuperBlock, + ); ctx.globalAlpha = 1; - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillStyle = "white"; - ctx.font = String(8 * this.ratio) + "pt Courier New"; - this.drawBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr, bounds, scale) => { - if (allSkip(c, r)) { - return; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = 'white'; + ctx.font = String(8 * this.ratio) + 'pt Courier New'; + this.drawBlock( + frame, + ctx, + src, + dst, + (blockSize, c, r, sc, sr, bounds, scale) => { + if (allSkip(c, r)) { + return; + } + const s = strengthGrid[r][c]; + const l = levelGrid[r][c]; + const o = bounds.getCenter(); + ctx.fillText(l + '/' + s, o.x, o.y); + return true; + }, + VisitMode.SuperBlock, + ); + } + + drawWedgeMode(frame: AnalyzerFrame, ctx: CanvasRenderingContext2D, src: Rectangle, dst: Rectangle) { + const blockSizeMap = frame.json['blockSizeMap']; + + const wedgeParamsLookup = frame.json['wedgeParamsLookup']; + const wedgeGrid = frame.json['wedge']; + + const reverseBlockMap = reverseMap(blockSizeMap); + + const blockTypeEnum = { + square: 0, + horizontalRect: 1, + verticalRect: 2, + }; + + const getBlockType = (block) => { + const blockName = reverseBlockMap[block]; + const blockDim = blockName.split('_')[1].split('X').map(Number); + if (blockDim[0] == blockDim[1]) { + return blockTypeEnum.square; + } else if (blockDim[0] > blockDim[1]) { + return blockTypeEnum.horizontalRect; // Horizontal Rectangle + } else { + return blockTypeEnum.verticalRect; // Vertical Rectangle } - let s = strengthGrid[r][c]; - let l = levelGrid[r][c]; - let o = bounds.getCenter(); - ctx.fillText(l + "/" + s, o.x, o.y); - return true; - }, VisitMode.SuperBlock); + }; + + const drawShape = (bounds: Rectangle, start: [number, number], points: [number, number][]) => { + ctx.moveTo(bounds.x + start[0] * bounds.w, bounds.y + start[1] * bounds.h); + points.forEach((pt) => { + ctx.lineTo(bounds.x + pt[0] * bounds.w, bounds.y + pt[1] * bounds.h); + }); + }; + + this.drawBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr, bounds) => { + ctx.save(); + const wedge = wedgeGrid[r][c]; + const wedgeParams = wedgeParamsLookup[blockSize]; + if (wedge[0] != -1 && wedgeParams['wedge_types'] > 0) { + ctx.fillStyle = wedge[1] === 0 ? palette.wedgeType.NO_SIGN : palette.wedgeType.SIGN; + ctx.beginPath(); + const wedgeInfo = wedgeParams['codebook'][wedge[0]]; + const xOffset = wedgeInfo['x_offset']; + const yOffset = wedgeInfo['y_offset']; + const wedgeDirection = wedgeInfo['wedgeDirectionType']; + const blockType = getBlockType(blockSize); + + if (wedgeDirection == 0) { + ctx.fillRect(bounds.x, bounds.y + (bounds.h * yOffset) / 8, bounds.w, bounds.h * (1 - yOffset / 8)); + } else if (wedgeDirection == 1) { + ctx.fillRect(bounds.x + (bounds.w * xOffset) / 8, bounds.y, bounds.w * (1 - xOffset / 8), bounds.h); + } else if (wedgeDirection == 2) { + if (blockType == blockTypeEnum.square) { + if (yOffset == 2) { + drawShape( + bounds, + [0, 0.5], + [ + [1, 0], + [0, 0], + [0, 0.4], + ], + ); + } else if (yOffset == 4) { + drawShape( + bounds, + [0, 0.8], + [ + [1, 0.25], + [1, 0], + [0, 0], + [0, 0.8], + ], + ); + } else if (yOffset == 6) { + drawShape( + bounds, + [0, 1], + [ + [1, 0.5], + [1, 0], + [0, 0], + [0, 1], + ], + ); + } else if (blockType == blockTypeEnum.horizontalRect) { + if (yOffset == 2) { + drawShape( + bounds, + [0, 0.5], + [ + [0.6, 0], + [0, 0], + [0, 0.5], + ], + ); + } else if (yOffset == 4) { + drawShape( + bounds, + [0, 1], + [ + [1, 0], + [0, 0], + [0, 1], + ], + ); + } else if (yOffset == 6) { + drawShape( + bounds, + [0.4, 1], + [ + [1, 0.4], + [1, 0], + [0, 0], + [0, 1], + [0.4, 1], + ], + ); + } + } else { + if (yOffset == 2) { + drawShape( + bounds, + [0, 0.3], + [ + [1, 0.2], + [1, 0], + [0, 0], + [0, 0.3], + ], + ); + } else if (yOffset == 4) { + drawShape( + bounds, + [0, 0.5], + [ + [1, 0.4], + [1, 0], + [0, 0], + [0, 0.3], + ], + ); + } else if (yOffset == 6) { + drawShape( + bounds, + [0, 0.7], + [ + [1, 0.6], + [1, 0], + [0, 0], + [0, 0.7], + ], + ); + } + } + ctx.fill(); + } + } else if (wedgeDirection == 3) { + if (blockType == blockTypeEnum.square) { + if (xOffset == 4) { + drawShape( + bounds, + [0.25, 1], + [ + [0.6, 0], + [0, 0], + [0, 1], + [0.25, 1], + ], + ); + } else if (xOffset == 2) { + drawShape( + bounds, + [0, 1], + [ + [0.4, 0], + [0, 0], + [0, 1], + ], + ); + } else if (xOffset == 6) { + drawShape( + bounds, + [0.5, 1], + [ + [1, 0], + [0, 0], + [0, 1], + [0.5, 1], + ], + ); + } + } else if (blockType == blockTypeEnum.horizontalRect) { + if (xOffset == 2) { + drawShape( + bounds, + [0.2, 1], + [ + [0.35, 0], + [0, 0], + [0, 1], + [0.2, 1], + ], + ); + } else if (xOffset == 4) { + drawShape( + bounds, + [0.4, 1], + [ + [0.55, 0], + [0, 0], + [0, 1], + [0.4, 1], + ], + ); + } else if (xOffset == 6) { + drawShape( + bounds, + [0.7, 1], + [ + [0.85, 0], + [0, 0], + [0, 1], + [0.7, 1], + ], + ); + } + } else { + if (xOffset == 2) { + drawShape( + bounds, + [0, 0.5], + [ + [0.4, 0], + [0, 0], + [0, 0.5], + ], + ); + } else if (xOffset == 4) { + drawShape( + bounds, + [0, 1], + [ + [1, 0], + [0, 0], + [0, 1], + ], + ); + } else if (xOffset == 6) { + drawShape( + bounds, + [0.3, 1], + [ + [1, 0.3], + [1, 0], + [0, 0], + [0, 1], + [0.3, 1], + ], + ); + } + } + ctx.fill(); + } else if (wedgeDirection == 4) { + if (blockType == blockTypeEnum.square) { + if (xOffset == 2) { + drawShape( + bounds, + [0.4, 1], + [ + [0, 0], + [0, 1], + [0.4, 1], + ], + ); + } else if (xOffset == 4) { + drawShape( + bounds, + [0.6, 1], + [ + [0.25, 0], + [0, 0], + [0, 1], + [0.6, 1], + ], + ); + } else if (xOffset == 6) { + drawShape( + bounds, + [1, 1], + [ + [0.5, 0], + [0, 0], + [0, 1], + [1, 1], + ], + ); + } + } else if (blockType == blockTypeEnum.horizontalRect) { + if (xOffset == 2) { + drawShape( + bounds, + [0.4, 1], + [ + [0.25, 0], + [0, 0], + [0, 1], + [0.4, 1], + ], + ); + } else if (xOffset == 4) { + drawShape( + bounds, + [0.6, 1], + [ + [0.45, 0], + [0, 0], + [0, 1], + [0.6, 1], + ], + ); + } else if (xOffset == 6) { + drawShape( + bounds, + [0.8, 1], + [ + [0.65, 0], + [0, 0], + [0, 1], + [0.8, 1], + ], + ); + } + } else { + if (xOffset == 2) { + drawShape( + bounds, + [0.5, 1], + [ + [0, 0.4], + [0, 1], + [0.5, 1], + ], + ); + } else if (xOffset == 4) { + drawShape( + bounds, + [1, 1], + [ + [0, 0], + [0, 1], + [1, 1], + ], + ); + } else if (xOffset == 6) { + drawShape( + bounds, + [1, 0.5], + [ + [0.6, 0], + [0, 0], + [0, 1], + [1, 1], + [1, 0.5], + ], + ); + } + } + ctx.fill(); + } else if (wedgeDirection == 5) { + if (blockType == blockTypeEnum.square) { + if (yOffset == 2) { + drawShape( + bounds, + [0, 0], + [ + [1, 0.5], + [1, 0], + [0, 0], + ], + ); + } else if (yOffset == 4) { + drawShape( + bounds, + [0, 0.25], + [ + [1, 0.8], + [1, 0], + [0, 0], + [0, 0.25], + ], + ); + } else if (yOffset == 6) { + drawShape( + bounds, + [0, 0.5], + [ + [1, 1], + [1, 0], + [0, 0], + [0, 0.5], + ], + ); + } + } else if (blockType == blockTypeEnum.horizontalRect) { + if (yOffset == 2) { + drawShape( + bounds, + [0.4, 0], + [ + [1, 0.6], + [1, 1], + [0.4, 0], + ], + ); + } else if (yOffset == 4) { + drawShape( + bounds, + [0, 0], + [ + [1, 1], + [1, 0], + [0, 0], + ], + ); + } else if (yOffset == 6) { + drawShape( + bounds, + [0, 0.3], + [ + [0.7, 1], + [1, 1], + [1, 0], + [0, 0], + [0, 0.3], + ], + ); + } + } else { + if (yOffset == 2) { + drawShape( + bounds, + [0, 0.2], + [ + [1, 0.4], + [1, 0], + [0, 0], + [0, 0.2], + ], + ); + } else if (yOffset == 4) { + drawShape( + bounds, + [0, 0.4], + [ + [1, 0.6], + [1, 0], + [0, 0], + [0, 0.4], + ], + ); + } else if (yOffset == 6) { + drawShape( + bounds, + [0, 0.7], + [ + [1, 0.9], + [1, 0], + [0, 0], + [0, 0.7], + ], + ); + } + } + ctx.fill(); + } + } + }); } + drawReferenceFrames(frame: AnalyzerFrame, ctx: CanvasRenderingContext2D, src: Rectangle, dst: Rectangle) { - let referenceGrid = frame.json["referenceFrame"]; - let referenceMapByValue = reverseMap(frame.json["referenceFrameMap"]); + const referenceGrid = frame.json['referenceFrame']; + const referenceMapByValue = reverseMap(frame.json['referenceFrameMap']); const triangles = true; this.drawBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr, bounds) => { ctx.save(); @@ -1625,23 +2885,23 @@ export class AnalyzerView extends React.Component { bounds.multiplyScalar(scale); - let o = bounds.getCenter(); - let m = motionVectorsGrid[r][c]; - let a = new Vector(m[0], m[1]) - let b = new Vector(m[2], m[3]) + const o = bounds.getCenter(); + const m = motionVectorsGrid[r][c]; + const a = new Vector(m[0], m[1]); + const b = new Vector(m[2], m[3]); if (a.length() > 0) { ctx.globalAlpha = Math.min(0.3, a.length() / 128); @@ -1656,9 +2916,9 @@ export class AnalyzerView extends React.Component { ctx.fillStyle = getColor(segGrid[r][c], palette.seg_id); return true; }); } drawTransformType(frame: AnalyzerFrame, ctx: CanvasRenderingContext2D, src: Rectangle, dst: Rectangle) { - let typeGrid = frame.json["transformType"]; - let transformTypeMapByValue = reverseMap(frame.json["transformTypeMap"]); + const typeGrid = frame.json['transformType']; + const transformTypeMapByValue = reverseMap(frame.json['transformTypeMap']); this.fillBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr) => { ctx.fillStyle = getColor(transformTypeMapByValue[typeGrid[r][c]], palette.transformType); return true; }); } drawBits(frame: AnalyzerFrame, ctx: CanvasRenderingContext2D, src: Rectangle, dst: Rectangle) { - let { blocks, total } = frame.accounting.countBits(this.state.showBitsFilter); + const { blocks, total } = frame.accounting.countBits(this.state.showBitsFilter); function getBits(blocks, c, r) { if (!blocks[r]) { return 0; @@ -1701,37 +2961,37 @@ export class AnalyzerView extends React.Component { - let area = blockSizeArea(frame, blockSize); - let bits = getBits(blocks, c, r); + const area = blockSizeArea(frame, blockSize); + const bits = getBits(blocks, c, r); maxBitsPerPixel = Math.max(maxBitsPerPixel, bits / area); }); } else { - let groups = this.state.showBitsScale === "video" ? [this.getActiveGroup()] : this.props.groups; - groups.forEach(frames => { - frames.forEach(frame => { - let { blocks } = frame.accounting.countBits(this.state.showBitsFilter); + const groups = this.state.showBitsScale === 'video' ? [this.getActiveGroup()] : this.props.groups; + groups.forEach((frames) => { + frames.forEach((frame) => { + const { blocks } = frame.accounting.countBits(this.state.showBitsFilter); this.visitBlocks(VisitMode.Block, frame, (blockSize, c, r, sc, sr, bounds) => { - let area = blockSizeArea(frame, blockSize); - let bits = getBits(blocks, c, r); + const area = blockSizeArea(frame, blockSize); + const bits = getBits(blocks, c, r); maxBitsPerPixel = Math.max(maxBitsPerPixel, bits / area); }); }); }); } this.fillBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr) => { - let area = blockSizeArea(frame, blockSize); - let bits = getBits(blocks, c, r); - let value = (bits / area) / maxBitsPerPixel; - let mode = this.state.showBitsMode; - if (mode == "linear") { + const area = blockSizeArea(frame, blockSize); + const bits = getBits(blocks, c, r); + const value = bits / area / maxBitsPerPixel; + const mode = this.state.showBitsMode; + if (mode == 'linear') { ctx.globalAlpha = value; - ctx.fillStyle = "#9400D3"; - } else if (mode == "heat") { + ctx.fillStyle = '#9400D3'; + } else if (mode == 'heat') { ctx.globalAlpha = value; ctx.fillStyle = colorScale(value, HEAT_COLORS); - } else if (mode == "heat-opaque") { + } else if (mode == 'heat-opaque') { ctx.globalAlpha = 1; ctx.fillStyle = colorScale(value, HEAT_COLORS); } @@ -1739,12 +2999,12 @@ export class AnalyzerView extends React.Component { bounds.multiplyScalar(scale); drawMode(modeGrid[r][c], bounds); - if (alphaIndex && type === "uv_mode" && modeGrid[r][c] === UV_CFL_PRED) { + if (alphaIndex && type === 'uv_mode' && modeGrid[r][c] === UV_CFL_PRED) { if (bounds.w < 16 * this.ratio || bounds.h < 16 * this.ratio) { return; } - let o = bounds.getCenter(); - let cfl_alpha_idx = frame.json["cfl_alpha_idx"][r][c]; - let cfl_alpha_sign = frame.json["cfl_alpha_sign"][r][c]; - let [cfl_alpha_u, cfl_alpha_v] = toCflAlphas(cfl_alpha_idx, cfl_alpha_sign); - ctx.fillStyle = "black"; + const o = bounds.getCenter(); + const cfl_alpha_idx = frame.json['cfl_alpha_idx'][r][c]; + const cfl_alpha_sign = frame.json['cfl_alpha_sign'][r][c]; + const [cfl_alpha_u, cfl_alpha_v] = toCflAlphas(cfl_alpha_idx, cfl_alpha_sign); + ctx.fillStyle = 'black'; ctx.fillText(`${cfl_alpha_u}`, o.x, o.y - 4 * this.ratio); ctx.fillText(`${cfl_alpha_v}`, o.x, o.y + 4 * this.ratio); } }); function drawMode(m: number, bounds: Rectangle) { - let x = bounds.x; - let y = bounds.y; - let w = bounds.w; - let h = bounds.h; - let hw = w / 2; - let hh = h / 2; + const x = bounds.x; + const y = bounds.y; + const w = bounds.w; + const h = bounds.h; + const hw = w / 2; + const hh = h / 2; ctx.fillStyle = getColor(modeMapByValue[m], palette.predictionMode); ctx.fillRect(x, y, w, h); switch (m) { @@ -1829,16 +3089,37 @@ export class AnalyzerView extends React.Component boolean, mode = VisitMode.Block) { - this.drawBlock(frame, ctx, src, dst, (blockSize, c, r, sc, sr, bounds, scale) => { - if (setFillStyle(blockSize, c, r, sc, sr)) { - ctx.fillRect(bounds.x, bounds.y, bounds.w, bounds.h); - } - }, mode); - } - - drawBlock(frame: AnalyzerFrame, ctx: CanvasRenderingContext2D, src: Rectangle, dst: Rectangle, visitor: BlockVisitor, mode = VisitMode.Block) { - let scale = dst.w / src.w; + fillBlock( + frame: AnalyzerFrame, + ctx: CanvasRenderingContext2D, + src: Rectangle, + dst: Rectangle, + setFillStyle: (blockSize: number, c: number, r: number, sc: number, sr: number) => boolean, + mode = VisitMode.Block, + ) { + this.drawBlock( + frame, + ctx, + src, + dst, + (blockSize, c, r, sc, sr, bounds, scale) => { + if (setFillStyle(blockSize, c, r, sc, sr)) { + ctx.fillRect(bounds.x, bounds.y, bounds.w, bounds.h); + } + }, + mode, + ); + } + + drawBlock( + frame: AnalyzerFrame, + ctx: CanvasRenderingContext2D, + src: Rectangle, + dst: Rectangle, + visitor: BlockVisitor, + mode = VisitMode.Block, + ) { + const scale = dst.w / src.w; ctx.save(); ctx.translate(-src.x * scale, -src.y * scale); this.visitBlocks(mode, frame, (blockSize, c, r, sc, sr, bounds) => { @@ -1851,7 +3132,7 @@ export class AnalyzerView extends React.Component { return new Promise((resolve, reject) => { - shortenUrl(window.location.href , (url) => { + shortenUrl(window.location.href, (url) => { resolve(url); }); }); } shareLink() { - this.createSharingLink().then(link => { - this.setState({showShareUrlDialog: true, shareUrl: link} as any); + this.createSharingLink().then((link) => { + this.setState({ showShareUrlDialog: true, shareUrl: link } as any); }); } - fileIssue(label: string = "") { - this.createSharingLink().then(link => { - window.open("https://github.com/mbebenita/aomanalyzer/issues/new?labels=" + label + "&body=" + encodeURIComponent(link)); + fileIssue(label = '') { + this.createSharingLink().then((link) => { + window.open( + 'https://github.com/mbebenita/aomanalyzer/issues/new?labels=' + label + '&body=' + encodeURIComponent(link), + ); }); } } diff --git a/src/components/Calibrate.tsx b/src/components/Calibrate.tsx index 018d709..b834e02 100644 --- a/src/components/Calibrate.tsx +++ b/src/components/Calibrate.tsx @@ -1,4 +1,4 @@ -import * as React from "react"; +import * as React from 'react'; interface CalibrateComponentProps { width: number; @@ -8,7 +8,7 @@ interface CalibrateComponentProps { export class CalibrateComponent extends React.Component { public static defaultProps: CalibrateComponentProps = { width: 512, - height: 512 + height: 512, } as any; canvas: HTMLCanvasElement; renderCanvas() { @@ -18,30 +18,30 @@ export class CalibrateComponent extends React.Component this.renderBrightnessTest(); } renderBrightnessTest() { - let ctx = this.canvas.getContext("2d"); - ctx.fillStyle = "#00000000"; + const ctx = this.canvas.getContext('2d'); + ctx.fillStyle = '#00000000'; ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); - let colors = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 25, 30]; - let p = 16; - let size = 100; - let cols = this.canvas.width / (size + p) | 0; - let rows = Math.ceil(colors.length / cols); - let h = size; - let w = size; + const colors = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 25, 30]; + const p = 16; + const size = 100; + const cols = (this.canvas.width / (size + p)) | 0; + const rows = Math.ceil(colors.length / cols); + const h = size; + const w = size; ctx.save(); ctx.translate(p, p); for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { - let i = y * cols + x; + const i = y * cols + x; if (i >= colors.length) { return; } - let c = colors[i]; + const c = colors[i]; ctx.fillStyle = `rgb(${c}, ${c}, ${c})`; ctx.fillRect(x * (w + p), y * (h + p), w, h); - ctx.fillStyle = "darkred"; - ctx.textBaseline = "top"; + ctx.fillStyle = 'darkred'; + ctx.textBaseline = 'top'; ctx.fillText(String(c), 4 + x * (w + p), 4 + y * (h + p)); } } @@ -51,9 +51,17 @@ export class CalibrateComponent extends React.Component this.renderCanvas(); } render() { - let canvasStyle: any = {}; - return
- this.canvas = self} style={canvasStyle} width={this.props.width} height={this.props.height}/> -
+ const canvasStyle: any = {}; + return ( +
+ (this.canvas = self)} + style={canvasStyle} + width={this.props.width} + height={this.props.height} + /> +
+ ); } -} \ No newline at end of file +} diff --git a/src/components/Download.tsx b/src/components/Download.tsx index 9b92b77..a9bb1cf 100644 --- a/src/components/Download.tsx +++ b/src/components/Download.tsx @@ -1,31 +1,34 @@ -import * as React from "react"; +import * as React from 'react'; import { Decoder, AnalyzerFrame, downloadFile, FrameImage } from './analyzerTools'; import { saveAs } from 'file-saver'; -import {CircularProgress} from "@material-ui/core"; +import { CircularProgress } from '@material-ui/core'; interface DownloadComponentProps { - video: { decoderUrl: string, videoUrl: string, decoderName: string }; + video: { decoderUrl: string; videoUrl: string; decoderName: string }; filename?: string; } -export class DownloadComponent extends React.Component { +export class DownloadComponent extends React.Component< + DownloadComponentProps, + { + decoder: Decoder; + status: string; + } +> { public static defaultProps: DownloadComponentProps = { - filename: "image.y4m" + filename: 'image.y4m', } as any; decoder: Decoder; y4m: Blob; - wroteHeader: boolean = false; + wroteHeader = false; constructor(props) { super(props); this.state = { decoder: null, - status: "", + status: '', }; } @@ -33,11 +36,11 @@ export class DownloadComponent extends React.Component { - this.setState({ status: "Downloading Video" } as any); - downloadFile(this.props.video.videoUrl).then(bytes => { + Decoder.loadDecoder(this.props.video.decoderUrl).then((decoder) => { + this.setState({ status: 'Downloading Video' } as any); + downloadFile(this.props.video.videoUrl).then((bytes) => { decoder.openFileBytes(bytes); decoder.setLayers(0); this.decoder = decoder; @@ -49,13 +52,13 @@ export class DownloadComponent extends React.Component { - this.setState({ status: "Decoding video" } as any); - frames.forEach(frame => { - let image = frame.frameImage; - this.dumpY4MFrame(image); - }); - this.dumpFrames(); - }, () => { - this.setState({ status: "Complete!" } as any); - saveAs(this.y4m, this.props.filename, true); - }); + this.decoder.readFrame().then( + (frames) => { + this.setState({ status: 'Decoding video' } as any); + frames.forEach((frame) => { + const image = frame.frameImage; + this.dumpY4MFrame(image); + }); + this.dumpFrames(); + }, + () => { + this.setState({ status: 'Complete!' } as any); + saveAs(this.y4m, this.props.filename, true); + }, + ); } render() { - if (this.state.status != "Complete!") { - return
-
-

- {this.state.status} + if (this.state.status != 'Complete!') { + return ( +
+
+ +
+
+ {this.state.status} +
-
+ ); } else { - return
-
- {this.state.status} + return ( +
+
{this.state.status}
-
+ ); } } } diff --git a/src/components/Histogram.tsx b/src/components/Histogram.tsx index 19c627f..ba2a740 100644 --- a/src/components/Histogram.tsx +++ b/src/components/Histogram.tsx @@ -1,24 +1,26 @@ -import * as React from "react"; -import { TRACE_RENDERING, COLORS, Rectangle, Histogram, Vector, hashString } from "./analyzerTools"; +import * as React from 'react'; +import { TRACE_RENDERING, COLORS, Rectangle, Histogram, Vector, hashString } from './analyzerTools'; -export class HistogramComponent extends React.Component<{ - histograms: Histogram[]; - highlight?: number; - width?: number; - height?: number; - scale?: string | number; - color?: (name: string) => string; - horizontal?: boolean; -}, { -}> { +export class HistogramComponent extends React.Component< + { + histograms: Histogram[]; + highlight?: number; + width?: number; + height?: number; + scale?: string | number; + color?: (name: string) => string; + horizontal?: boolean; + }, + {} +> { public static defaultProps = { width: 128, height: 128, - scale: "relative", + scale: 'relative', horizontal: true, color: function (name: string) { return COLORS[hashString(name) % COLORS.length]; - } + }, }; canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D; @@ -35,35 +37,33 @@ export class HistogramComponent extends React.Component<{ this.renderHistogram(this.ctx, this.props.histograms); } componentDidMount() { - let w = this.w = this.props.width; - let h = this.h = this.props.height; - this.canvas.style.width = w + "px"; - this.canvas.style.height = h + "px"; + const w = (this.w = this.props.width); + const h = (this.h = this.props.height); + this.canvas.style.width = w + 'px'; + this.canvas.style.height = h + 'px'; this.canvas.width = w * this.ratio; this.canvas.height = h * this.ratio; - this.ctx = this.canvas.getContext("2d"); + this.ctx = this.canvas.getContext('2d'); this.renderHistogram(this.ctx, this.props.histograms); - this.canvas.addEventListener("mousemove", this.handleMouseEvent.bind(this)); + this.canvas.addEventListener('mousemove', this.handleMouseEvent.bind(this)); } componentWillUnmount() { - + /* tslint:disable:no-empty */ } renderHistogram(ctx: CanvasRenderingContext2D, histograms: Histogram[]) { - TRACE_RENDERING && console.log("renderHistogram"); - let names: string [] = null; - let nameMap: { [id: string]: number }; + TRACE_RENDERING && console.log('renderHistogram'); if (!histograms.length || !histograms[0]) { return; } - nameMap = histograms[0].names; - names = Object.keys(nameMap); + const nameMap: { [id: string]: number } = histograms[0].names; + const names: string[] = Object.keys(nameMap); function valueOf(histogram: Histogram, name: string) { - let count = histogram.counts[histogram.names[name]]; + const count = histogram.counts[histogram.names[name]]; return count === undefined ? 0 : count; } - let rows = []; + const rows = []; let scale = 1; - if (this.props.scale == "max") { + if (this.props.scale == 'max') { let max = 0; histograms.forEach((histogram: Histogram, i) => { let total = 0; @@ -75,16 +75,16 @@ export class HistogramComponent extends React.Component<{ scale = max; } histograms.forEach((histogram: Histogram, i) => { - let row = { frame: i, total: 0 }; - if (this.props.scale == "relative") { + const row = { frame: i, total: 0 }; + if (this.props.scale == 'relative') { scale = 0; - names.forEach(name => { + names.forEach((name) => { scale += valueOf(histogram, name); }); - } else if (typeof this.props.scale == "number") { + } else if (typeof this.props.scale == 'number') { scale = this.props.scale; } - names.forEach(name => { + names.forEach((name) => { row[name] = valueOf(histogram, name) / scale; }); rows.push(row); @@ -95,44 +95,47 @@ export class HistogramComponent extends React.Component<{ handleMouseEvent(event: MouseEvent) { function getMousePosition(canvas: HTMLCanvasElement, event: MouseEvent) { - let rect = canvas.getBoundingClientRect(); - return new Vector( - event.clientX - rect.left, - event.clientY - rect.top - ); + const rect = canvas.getBoundingClientRect(); + return new Vector(event.clientX - rect.left, event.clientY - rect.top); } this.position = getMousePosition(this.canvas, event).multiplyScalar(this.ratio); this.forceUpdate(); } - renderChart(ctx: CanvasRenderingContext2D, names: string[], nameMap: { [id: string]: number }, data: any[], yDomain = [0, 1]) { + renderChart( + ctx: CanvasRenderingContext2D, + names: string[], + nameMap: { [id: string]: number }, + data: any[], + yDomain = [0, 1], + ) { ctx.save(); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - let w = this.w * this.ratio; - let h = this.h * this.ratio; - let bw = Math.min(16 * this.ratio, w / data.length | 0); - let bh = Math.min(16 * this.ratio, h / data.length | 0); + const w = this.w * this.ratio; + const h = this.h * this.ratio; + const bw = Math.min(16 * this.ratio, (w / data.length) | 0); + const bh = Math.min(16 * this.ratio, (h / data.length) | 0); let selectedName = null; let selectedValue = undefined; let selectedFrame = -1; for (let i = 0; i < data.length; i++) { let t = 0; - let r = new Rectangle(0, 0, 0, 0); - names.forEach(k => { - let v = data[i][k]; + const r = new Rectangle(0, 0, 0, 0); + names.forEach((k) => { + const v = data[i][k]; ctx.fillStyle = this.props.color(k); if (this.props.horizontal) { - let y = (h - ((t + v) * h)); + const y = h - (t + v) * h; r.set(i * bw, y | 0, bw - 1, (v * h + (y - (y | 0))) | 0); } else { - r.set((t * w | 0), bh * i, (v * w) | 0, bh - 1); + r.set((t * w) | 0, bh * i, (v * w) | 0, bh - 1); } if (r.containsPoint(this.position)) { ctx.globalAlpha = 1; selectedName = k; selectedValue = v; selectedFrame = i; - ctx.fillStyle = "white"; + ctx.fillStyle = 'white'; } else { ctx.globalAlpha = 1; } @@ -140,7 +143,7 @@ export class HistogramComponent extends React.Component<{ t += v; }); if (this.props.highlight == i) { - ctx.fillStyle = "white"; + ctx.fillStyle = 'white'; if (this.props.horizontal) { ctx.fillRect(i * bw, 0, bw - 1, this.ratio * 4); } else { @@ -149,20 +152,20 @@ export class HistogramComponent extends React.Component<{ } } if (selectedName) { - let top = this.position.distanceTo(new Vector(0, 0)) > this.position.distanceTo(new Vector(0, h)); - let text = selectedName + " " + (selectedValue * 100).toFixed(2) + "%" + " (" + String(selectedFrame) + ")"; + const top = this.position.distanceTo(new Vector(0, 0)) > this.position.distanceTo(new Vector(0, h)); + const text = selectedName + ' ' + (selectedValue * 100).toFixed(2) + '%' + ' (' + String(selectedFrame) + ')'; ctx.globalAlpha = 0.75; - ctx.font = (10 * this.ratio) + "px Arial"; - ctx.fillStyle = "black"; - let tw = ctx.measureText(text).width + 8 * this.ratio; + ctx.font = 10 * this.ratio + 'px Arial'; + ctx.fillStyle = 'black'; + const tw = ctx.measureText(text).width + 8 * this.ratio; if (top) { ctx.fillRect(0, 0, tw, 20 * this.ratio); } else { ctx.fillRect(0, h - 20 * this.ratio, tw, 20 * this.ratio); } - ctx.fillStyle = "white"; + ctx.fillStyle = 'white'; ctx.globalAlpha = 1; - ctx.textBaseline = "middle"; + ctx.textBaseline = 'middle'; if (top) { ctx.fillText(text, 4 * this.ratio, 10 * this.ratio); } else { @@ -173,8 +176,10 @@ export class HistogramComponent extends React.Component<{ } render() { - return
- this.canvas = self} width="256" height="256"> -
+ return ( +
+ (this.canvas = self)} width="256" height="256"> +
+ ); } -} \ No newline at end of file +} diff --git a/src/components/LineGraph.tsx b/src/components/LineGraph.tsx new file mode 100644 index 0000000..b80cd96 --- /dev/null +++ b/src/components/LineGraph.tsx @@ -0,0 +1,90 @@ +import * as React from 'react'; + +import { theme } from '../theme'; +import { LineCanvas, Serie } from '@nivo/line'; + +export class LineGraph extends React.Component< + { + width: number; + height: number; + data: Serie[]; + }, + {} +> { + public static defaultProps = { + width: 500, + height: 512, + }; + + constructor(props) { + super(props); + } + + render() { + return ( +
+ +
+ ); + } +} diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx index a679f8c..fd09f0d 100644 --- a/src/components/Loader.tsx +++ b/src/components/Loader.tsx @@ -1,13 +1,13 @@ -import * as React from "react"; +import * as React from 'react'; const MAX_FRAMES = 128; -import { downloadFile, Decoder, AnalyzerFrame } from "./analyzerTools"; -import { AnalyzerView } from './Analyzer' -import { SplitView } from './Split' -import {CircularProgress, Dialog, DialogContent} from "@material-ui/core"; +import { downloadFile, Decoder, AnalyzerFrame } from './analyzerTools'; +import { AnalyzerView } from './Analyzer'; +import { SplitView } from './Split'; +import { CircularProgress, Dialog, DialogContent } from '@material-ui/core'; interface LoaderComponentProps { - decoderVideoUrlPairs: { decoderUrl: string, videoUrl: string, decoderName: string }[]; + decoderVideoUrlPairs: { decoderUrl: string; videoUrl: string; decoderName: string }[]; playbackFrameRate?: number; layers?: number; maxFrames?: number; @@ -16,21 +16,25 @@ interface LoaderComponentProps { bench?: number; } -export class LoaderComponent extends React.Component { +export class LoaderComponent extends React.Component< + LoaderComponentProps, + { + frames: AnalyzerFrame[][]; + groupNames: string[]; + analyzerFailedToLoad: boolean; + decodedFrameCount: number; + loading: 'done' | 'failed' | 'loading'; + status: string; + playbackFrameRate; + number; + } +> { playbackFrameRate: number; public static defaultProps: LoaderComponentProps = { decoderVideoUrlPairs: [], playbackFrameRate: 1000, maxFrames: MAX_FRAMES, - layers: 0xFFFFFFFF + layers: 0xffffffff, }; constructor(props: LoaderComponentProps) { super(props); @@ -39,16 +43,15 @@ export class LoaderComponent extends React.Component { + + const decoderUrls = []; + const decoderNames = []; + const videoUrls = []; + this.props.decoderVideoUrlPairs.forEach((pair) => { decoderUrls.push(pair.decoderUrl); decoderNames.push(pair.decoderName); videoUrls.push(pair.videoUrl); @@ -57,49 +60,53 @@ export class LoaderComponent extends React.Component Decoder.loadDecoder(path))).then(decoders => { - this.decoders = decoders; - this.setState({ status: "Downloading Files" } as any); - Promise.all(videoPaths.map(path => downloadFile(path))).then(bytes => { - let decodedFrames = []; - for (let i = 0; i < decoders.length; i++) { - let decoder = decoders[i]; - decoder.openFileBytes(bytes[i]); - } - let groupNames = decoderNames.slice(); - for (let i = 0; i < decoderPaths.length; i++) { - if (groupNames[i]) { - continue; - } - let videoPath = videoPaths[i]; - let j = videoPath.lastIndexOf("/"); - if (j >= 0) { - videoPath = videoPath.substring(j + 1); - } - groupNames[i] = videoPath; - } - this.setState({ status: "Decoding Frames" } as any); - let s = performance.now(); - Promise.all(decoders.map(decoder => this.decodeFrames(decoder, this.props.maxFrames))).then(frames => { - let playbackFrameRate = Math.min(this.props.playbackFrameRate, decoders[0].frameRate); - if (this.props.bench) { - this.setState({ status: "Decoded Frames in " + (performance.now() - s).toFixed(2) + " ms." } as any); - } else { - this.setState({ frames: frames, groupNames: groupNames, loading: "done", playbackFrameRate } as any); - } - }); - }).catch(e => { - this.setState({ status: `Downloading Files Failed: ${e}`, loading: "error" } as any); + this.setState({ status: 'Loading Decoders' } as any); + Promise.all(decoderPaths.map((path) => Decoder.loadDecoder(path))) + .then((decoders) => { + this.decoders = decoders; + this.setState({ status: 'Downloading Files' } as any); + Promise.all(videoPaths.map((path) => downloadFile(path))) + .then((bytes) => { + const decodedFrames = []; + for (let i = 0; i < decoders.length; i++) { + const decoder = decoders[i]; + decoder.openFileBytes(bytes[i]); + } + const groupNames = decoderNames.slice(); + for (let i = 0; i < decoderPaths.length; i++) { + if (groupNames[i]) { + continue; + } + let videoPath = videoPaths[i]; + const j = videoPath.lastIndexOf('/'); + if (j >= 0) { + videoPath = videoPath.substring(j + 1); + } + groupNames[i] = videoPath; + } + this.setState({ status: 'Decoding Frames' } as any); + const s = performance.now(); + Promise.all(decoders.map((decoder) => this.decodeFrames(decoder, this.props.maxFrames))).then((frames) => { + const playbackFrameRate = Math.min(this.props.playbackFrameRate, decoders[0].frameRate); + if (this.props.bench) { + this.setState({ status: 'Decoded Frames in ' + (performance.now() - s).toFixed(2) + ' ms.' } as any); + } else { + this.setState({ frames: frames, groupNames: groupNames, loading: 'done', playbackFrameRate } as any); + } + }); + }) + .catch((e) => { + this.setState({ status: `Downloading Files Failed: ${e}`, loading: 'error' } as any); + }); + }) + .catch((e) => { + this.setState({ status: `Loading Decoders Failed: ${e}`, loading: 'error' } as any); }); - }).catch(e => { - this.setState({ status: `Loading Decoders Failed: ${e}`, loading: "error" } as any); - }); } decodeAdditionalFrames(count: number) { - Promise.all(this.decoders.map(decoder => this.decodeFrames(decoder, count))).then(frames => { - let currentFrames = this.state.frames; + Promise.all(this.decoders.map((decoder) => this.decodeFrames(decoder, count))).then((frames) => { + const currentFrames = this.state.frames; for (let i = 0; i < frames.length; i++) { currentFrames[i] = currentFrames[i].concat(frames[i]); } @@ -117,24 +124,28 @@ export class LoaderComponent extends React.Component { - let time = performance.now(); + const time = performance.now(); let decodedFrames = []; let framePromises = []; for (let i = 0; i < count; i++) { framePromises.push(decoder.readFrame()); } // Don't swallow all promises if some fail. - framePromises = framePromises.map(p => p.then((x) => { - if (x) { - this.decodedFrameCount += x.length; - if (!this.props.bench) { - this.setState({ status: `Decoded ${this.decodedFrameCount} Frames ...` } as any); - } - } - return x; - }).catch(() => undefined)); + framePromises = framePromises.map((p) => + p + .then((x) => { + if (x) { + this.decodedFrameCount += x.length; + if (!this.props.bench) { + this.setState({ status: `Decoded ${this.decodedFrameCount} Frames ...` } as any); + } + } + return x; + }) + .catch(() => undefined), + ); Promise.all(framePromises).then((frames: AnalyzerFrame[][]) => { - frames.forEach(f => { + frames.forEach((f) => { if (f) { decodedFrames = decodedFrames.concat(f); } @@ -144,25 +155,48 @@ export class LoaderComponent extends React.Component : ; - return - - { this.props.bench ? null : } -
{this.state.status}
-
-
+ const frames = this.state.frames; + if (this.state.loading != 'done') { + const icon = + this.state.loading === 'loading' ? ( + + ) : ( + + ); + return ( + + + {this.props.bench ? null : } +
{this.state.status}
+
+
+ ); } if (this.props.split) { - return
- -
; + return ( +
+ +
+ ); } else { - return
- -
+ return ( +
+ +
+ ); } } } diff --git a/src/components/LocalAnalyzer.tsx b/src/components/LocalAnalyzer.tsx index 9d3e37d..1b368b2 100644 --- a/src/components/LocalAnalyzer.tsx +++ b/src/components/LocalAnalyzer.tsx @@ -1,19 +1,22 @@ -import * as React from "react"; -import { localFiles, localFileProtocol } from "./analyzerTools"; -import { LoaderComponent } from "./Loader" +import * as React from 'react'; +import { localFiles, localFileProtocol } from './analyzerTools'; +import { LoaderComponent } from './Loader'; import Select from 'react-select'; import { Button, Checkbox, CircularProgress, - Dialog, DialogContent, + Dialog, + DialogContent, DialogTitle, - FormControlLabel, FormGroup, FormHelperText, + FormControlLabel, + FormGroup, + FormHelperText, Switch, - TextField -} from "@material-ui/core"; -declare var require; -declare var shortenUrl; + TextField, +} from '@material-ui/core'; +declare let require; +declare let shortenUrl; export interface Option { label: string; @@ -22,51 +25,51 @@ export interface Option { } export function daysSince(date: Date) { - var oneSecond = 1000; - var oneMinute = 60 * oneSecond; - var oneHour = 60 * oneMinute; - var oneDay = 24 * oneHour; - let diff = new Date().getTime() - date.getTime(); + const oneSecond = 1000; + const oneMinute = 60 * oneSecond; + const oneHour = 60 * oneMinute; + const oneDay = 24 * oneHour; + const diff = new Date().getTime() - date.getTime(); return Math.round(Math.abs(diff / oneDay)); } export function secondsSince(date: Date) { - var oneSecond = 1000; - let diff = new Date().getTime() - date.getTime(); + const oneSecond = 1000; + const diff = new Date().getTime() - date.getTime(); return Math.round(Math.abs(diff / oneSecond)); } export function minutesSince(date: Date) { - var oneSecond = 1000; - var oneMinute = 60 * oneSecond; - let diff = new Date().getTime() - date.getTime(); + const oneSecond = 1000; + const oneMinute = 60 * oneSecond; + const diff = new Date().getTime() - date.getTime(); return Math.round(Math.abs(diff / oneMinute)); } export function timeSince(date: Date) { - var oneSecond = 1000; - var oneMinute = 60 * oneSecond; - var oneHour = 60 * oneMinute; - var oneDay = 24 * oneHour; - let diff = new Date().getTime() - date.getTime(); - var days = Math.round(Math.abs(diff / oneDay)); - var hours = Math.round(Math.abs(diff % oneDay) / oneHour); - var minutes = Math.round(Math.abs(diff % oneHour) / oneMinute); - let s = []; + const oneSecond = 1000; + const oneMinute = 60 * oneSecond; + const oneHour = 60 * oneMinute; + const oneDay = 24 * oneHour; + const diff = new Date().getTime() - date.getTime(); + const days = Math.round(Math.abs(diff / oneDay)); + const hours = Math.round(Math.abs(diff % oneDay) / oneHour); + const minutes = Math.round(Math.abs(diff % oneHour) / oneMinute); + const s = []; if (days > 0) { - s.push(`${days} day${days === 1 ? "" : "s"}`); + s.push(`${days} day${days === 1 ? '' : 's'}`); } if (hours > 0) { - s.push(`${hours} hour${hours === 1 ? "" : "s"}`); + s.push(`${hours} hour${hours === 1 ? '' : 's'}`); } if (minutes > 0) { - s.push(`${minutes} minute${minutes === 1 ? "" : "s"}`); + s.push(`${minutes} minute${minutes === 1 ? '' : 's'}`); } - return s.join(", ") + " ago"; + return s.join(', ') + ' ago'; } function unique(array: Array): Array { - let result = []; + const result = []; for (let i = 0; i < array.length; i++) { if (result.indexOf(array[i]) < 0) { result.push(array[i]); @@ -75,38 +78,44 @@ function unique(array: Array): Array { return result; } -const ABC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; -declare var process; +const ABC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; +declare let process; let masterUrl = window.location.origin + '/'; if (window.location.origin.startsWith('file://') || window.location.origin.startsWith('http://localhost')) { - masterUrl = 'https://arewecompressedyet.com/'; + masterUrl = 'https://beta.arewecompressedyet.com/'; } +masterUrl = 'https://beta.arewecompressedyet.com/'; -export class RunDetails extends React.Component<{ - json: any; -}, { - - }> { +export class RunDetails extends React.Component< + { + json: any; + }, + {} +> { render() { - let json = this.props.json; - let info = json.info; - return
-
Commit: {info.commit}
-
Nick: {info.nick}
-
Task: {info.task}
-
Build Options: {info.build_options}
-
Extra Options: {info.extra_options}
-
Date: {new Date(json.date).toString()}: ({timeSince(new Date(json.date))})
-
+ const json = this.props.json; + const info = json.info; + return ( +
+
Commit: {info.commit}
+
Nick: {info.nick}
+
Task: {info.task}
+
Build Options: {info.build_options}
+
Extra Options: {info.extra_options}
+
+ Date: {new Date(json.date).toString()}: ({timeSince(new Date(json.date))}) +
+
+ ); } } -export class LocalAnalyzerComponent extends React.Component<{ - -}, { +export class LocalAnalyzerComponent extends React.Component< + {}, + { listJson: any; setsJson: any; - slots: { runId: string, video: string, quality: number }[]; + slots: { runId: string; video: string; quality: number }[]; pairs: any; vote: string; votingEnabled: boolean; @@ -117,49 +126,53 @@ export class LocalAnalyzerComponent extends React.Component<{ shortURL: string; taskFilter: string; nickFilter: string; - configFilter: Option []; - statusFilter: Option []; - commandLineFilter: Option []; - }> { + configFilter: Option[]; + statusFilter: Option[]; + commandLineFilter: Option[]; + } +> { constructor(props) { super(props); this.state = { listJson: null, setsJson: null, - slots: [{ runId: "", video: "", quality: 0 }], - vote: "", + slots: [{ runId: '', video: '', quality: 0 }], + vote: '', votingEnabled: false, showVoteResult: false, blind: true, - voteMessage: "", - shortURL: "", + voteMessage: '', + shortURL: '', taskFilter: undefined, nickFilter: undefined, configFilter: [], - statusFilter: [{ - label: "completed", value: "completed" - }], + statusFilter: [ + { + label: 'completed', + value: 'completed', + }, + ], commandLineFilter: [], - filtersEnabled: true + filtersEnabled: true, } as any; } - loadXHR(path: string, type = "json"): Promise { + loadXHR(path: string, type = 'json'): Promise { return new Promise((resolve, reject) => { - let xhr = new XMLHttpRequest(); - let self = this; - xhr.open("GET", path, true); - xhr.responseType = "text"; + const xhr = new XMLHttpRequest(); + const self = this; + xhr.open('GET', path, true); + xhr.responseType = 'text'; xhr.send(); - xhr.addEventListener("load", function () { + xhr.addEventListener('load', function () { if (xhr.status != 200) { - console.error("Failed to load XHR: " + path); + console.error('Failed to load XHR: ' + path); reject(); return; } - console.info("Loaded XHR: " + path); + console.info('Loaded XHR: ' + path); let response = this.responseText; - if (type === "json") { - response = response.replace(/NaN/g, "null"); + if (type === 'json') { + response = response.replace(/NaN/g, 'null'); try { response = response ? JSON.parse(response) : null; } catch (x) { @@ -180,7 +193,7 @@ export class LocalAnalyzerComponent extends React.Component<{ // this.setState({ listJson } as any); // return; - this.loadXHR(masterUrl + "list.json").then((listJson: any) => { + this.loadXHR(masterUrl + 'list.json').then((listJson: any) => { listJson.sort(function (a, b) { return (new Date(b.date) as any) - (new Date(a.date) as any); }); @@ -191,128 +204,125 @@ export class LocalAnalyzerComponent extends React.Component<{ listJson = listJson.slice(0, 5000); // Say no to long names. - listJson = listJson.filter(job => { + listJson = listJson.filter((job) => { return job.run_id.length < 128; }); this.setState({ listJson } as any); }); - this.loadXHR(masterUrl + "sets.json").then((setsJson: any) => { + this.loadXHR(masterUrl + 'sets.json').then((setsJson: any) => { this.setState({ setsJson } as any); }); - } - handleAction(value) { - } resetURL() { - this.setState({shortURL: ""} as any); + this.setState({ shortURL: '' } as any); } onChangeTaskFilter(option) { - let taskFilter = option ? option.value : undefined; + const taskFilter = option ? option.value : undefined; this.setState({ taskFilter } as any); } onChangeNickFilter(option) { - let nickFilter = option ? option.value : undefined; + const nickFilter = option ? option.value : undefined; this.setState({ nickFilter } as any); } onChangeConfigFilter(option) { - let configFilter = option || []; + const configFilter = option || []; this.setState({ configFilter } as any); } onChangeStatusFilter(option) { - let statusFilter = option || []; + const statusFilter = option || []; this.setState({ statusFilter } as any); } onChangeCommandLineFilter(option) { - let commandLineFilter = option || []; + const commandLineFilter = option || []; this.setState({ commandLineFilter } as any); } onChangeRun(slot, option) { - let slots = this.state.slots; + const slots = this.state.slots; slots[slot].runId = option ? option.value : undefined; this.setState({ slots } as any); this.resetURL(); } onChangeVideo(slot, option) { - let slots = this.state.slots; + const slots = this.state.slots; slots[slot].video = option ? option.value : undefined; this.setState({ slots } as any); this.resetURL(); } onChangeQuality(slot, option) { - let slots = this.state.slots; + const slots = this.state.slots; slots[slot].quality = option ? option.value : undefined; this.setState({ slots } as any); this.resetURL(); } onChangeVote(option, value: string) { - this.setState({vote: value} as any); + this.setState({ vote: value } as any); this.resetURL(); } onDeleteRun(slot) { - let slots = this.state.slots; + const slots = this.state.slots; slots.splice(slot, 1); this.setState({ slots } as any); this.resetURL(); } onDuplicateRun(slot) { - let slots = this.state.slots; - let oldSlot = slots[slot]; - let newSlot = { runId: oldSlot.runId, video: oldSlot.video, quality: oldSlot.quality }; + const slots = this.state.slots; + const oldSlot = slots[slot]; + const newSlot = { runId: oldSlot.runId, video: oldSlot.video, quality: oldSlot.quality }; slots.splice(slot, 0, newSlot); this.setState({ slots } as any); this.resetURL(); } onMoveRun(slot, offset) { if (slot + offset < 0) return; - let slots = this.state.slots; + const slots = this.state.slots; if (slot + offset >= slots.length) return; - let tmp = slots[slot + offset]; + const tmp = slots[slot + offset]; slots[slot + offset] = slots[slot]; slots[slot] = tmp; this.setState({ slots } as any); this.resetURL(); } onAddRun() { - let slots = this.state.slots; - slots.push({ runId: "", video: "", quality: 0 }); + const slots = this.state.slots; + slots.push({ runId: '', video: '', quality: 0 }); this.setState({ slots } as any); this.resetURL(); } onVoteMessageChange(event, value: string) { - this.setState({voteMessage: value} as any); + this.setState({ voteMessage: value } as any); this.resetURL(); } makePairs(): any { - return this.state.slots.map(slot => { - let run = this.getRunById(slot.runId); - let videoUrl = masterUrl + `runs/${run.run_id}/${run.info.task}/${slot.video}-${slot.quality}.ivf`; - let decoderUrl = masterUrl + `runs/${run.run_id}/js/decoder.js`; - return {decoderUrl, videoUrl}; + return this.state.slots.map((slot) => { + const run = this.getRunById(slot.runId); + const videoUrl = masterUrl + `runs/${run.run_id}/${run.info.task}/${slot.video}-${slot.quality}.ivf`; + const decoderUrl = masterUrl + `runs/${run.run_id}/js/decoder.js`; + return { decoderUrl, videoUrl }; }); } - findLongestPrefix(pairs: {decoderUrl: string, videoUrl: string} []): string { - let list = []; - pairs.forEach(pair => { + findLongestPrefix(pairs: { decoderUrl: string; videoUrl: string }[]): string { + const list = []; + pairs.forEach((pair) => { list.push(pair.decoderUrl); list.push(pair.videoUrl); }); if (list.length == 0) { - return ""; + return ''; } - let first = list[0]; - let prefix = ""; + const first = list[0]; + let prefix = ''; // Find longest prefix. for (let i = 0; i < first.length; i++) { - let tmp = first.slice(0, i); - let isCommon = list.every(s => s.indexOf(tmp) == 0); + const tmp = first.slice(0, i); + const isCommon = list.every((s) => s.indexOf(tmp) == 0); if (!isCommon) { break; } prefix = tmp; } // Remove prefix. - pairs.forEach(pair => { + pairs.forEach((pair) => { pair.decoderUrl = pair.decoderUrl.slice(prefix.length); pair.videoUrl = pair.videoUrl.slice(prefix.length); }); @@ -320,35 +330,39 @@ export class LocalAnalyzerComponent extends React.Component<{ return prefix; } onSend() { - window.open(this.createURL(), "_blank"); + window.open(this.createURL(), '_blank'); } getRunById(runId) { - return this.state.listJson.find(run => run.run_id === runId); + return this.state.listJson.find((run) => run.run_id === runId); } getOptionsForTask(task: string) { if (!this.state.setsJson || !(task in this.state.setsJson)) { return []; } - let array = this.state.setsJson[task].sources; + const array = this.state.setsJson[task].sources; if (!array) { return []; } - return array.map(video => { return { value: video, label: video }; }); + return array.map((video) => { + return { value: video, label: video }; + }); } getOptionsForQuality(quality: string) { let array = [20, 32, 43, 55, 63]; if (quality) { - array = quality.split(" ").map(q => parseInt(q)); + array = quality.split(' ').map((q) => parseInt(q)); } - return array.map(q => { return { value: q, label: q }; }) + return array.map((q) => { + return { value: q, label: q }; + }); } cannotAnalyze() { - let slots = this.state.slots; + const slots = this.state.slots; if (slots.length == 0) { return true; } for (let i = 0; i < slots.length; i++) { - let slot = slots[i]; + const slot = slots[i]; if (!slot.quality || !slot.runId || !slot.video) { return true; } @@ -357,13 +371,21 @@ export class LocalAnalyzerComponent extends React.Component<{ } createURL() { try { - let pairs = this.makePairs(); - let prefix = this.findLongestPrefix(pairs); - let url = window.location.origin + window.location.pathname + "?"; + const pairs = this.makePairs(); + const prefix = this.findLongestPrefix(pairs); + let url = window.location.origin + window.location.pathname + '?'; if (this.state.votingEnabled) { let vote = this.state.vote; if (vote) { - vote = this.state.vote.split(",").map(x => x.split(":").map((y: any) => y|0).join(":")).join(","); + vote = this.state.vote + .split(',') + .map((x) => + x + .split(':') + .map((y: any) => y | 0) + .join(':'), + ) + .join(','); url += `vote=${vote}&`; } if (this.state.voteMessage) { @@ -382,28 +404,28 @@ export class LocalAnalyzerComponent extends React.Component<{ if (prefix) { url += `p=${prefix}&`; } - return url + pairs.map(pair => `d=${pair.decoderUrl}&f=${pair.videoUrl}`).join("&"); - } catch(e) { - return ""; + return url + pairs.map((pair) => `d=${pair.decoderUrl}&f=${pair.videoUrl}`).join('&'); + } catch (e) { + return ''; } } onShortenURL() { shortenUrl(this.createURL(), (shortURL) => { - this.setState({shortURL} as any); + this.setState({ shortURL } as any); }); } getVoteErrorText() { if (!this.state.vote) { - return "Required"; + return 'Required'; } let vote = []; try { - vote = this.state.vote.split(",").map(x => { - return x.split(":").map((y: any) => { - if (y != (y|0)) { + vote = this.state.vote.split(',').map((x) => { + return x.split(':').map((y: any) => { + if (y != (y | 0)) { throw `Cannot parse ${y}.`; } - return parseInt(y) + return parseInt(y); }); }); } catch (e) { @@ -411,7 +433,7 @@ export class LocalAnalyzerComponent extends React.Component<{ } for (let i = 0; i < vote.length; i++) { for (let j = 0; j < vote[i].length; j++) { - let run = vote[i][j]; + const run = vote[i][j]; if (!this.state.slots[run]) { return `Run ${run} is missing.`; } @@ -421,291 +443,358 @@ export class LocalAnalyzerComponent extends React.Component<{ } render() { function logChange(val) { - console.log("Selected: " + val); + console.log('Selected: ' + val); } - let listJson = this.state.listJson; + const listJson = this.state.listJson; if (!listJson) { - return - Downloading AWCY Runs - - - - ; + return ( + + Downloading AWCY Runs + + + + + ); } else { - let filtersEnabled = this.state.filtersEnabled; - let runOptions = listJson.filter(run => { - if (!this.state.filtersEnabled) { - return true; - } - let pass = true; - if (pass && this.state.statusFilter.length) { - // Unlike other filters, this is an OR filter. - pass = this.state.statusFilter.some(option => { - return run.status == option.value; - }); - } - if (pass && this.state.taskFilter && run.info.task !== this.state.taskFilter) { - pass = false; - } - if (pass && this.state.nickFilter && run.info.nick !== this.state.nickFilter) { - pass = false; - } - if (pass && this.state.configFilter.length) { - let buildOptions = run.info.build_options.split(" ").filter(x => !!x); - pass = this.state.configFilter.every(option => { - return buildOptions.indexOf(option.value) >= 0; - }); - } - if (pass && this.state.commandLineFilter.length) { - let commandLineOptions = run.info.extra_options.split(" ").filter(x => !!x); - pass = this.state.commandLineFilter.every(option => { - return commandLineOptions.indexOf(option.value) >= 0; - }); - } - return pass; - }).map(run => { - return { value: run.run_id, label: run.run_id } - }).slice(0, 1000); + const filtersEnabled = this.state.filtersEnabled; + const runOptions = listJson + .filter((run) => { + if (!this.state.filtersEnabled) { + return true; + } + let pass = true; + if (pass && this.state.statusFilter.length) { + // Unlike other filters, this is an OR filter. + pass = this.state.statusFilter.some((option) => { + return run.status == option.value; + }); + } + if (pass && this.state.taskFilter && run.info.task !== this.state.taskFilter) { + pass = false; + } + if (pass && this.state.nickFilter && run.info.nick !== this.state.nickFilter) { + pass = false; + } + if (pass && this.state.configFilter.length) { + const buildOptions = run.info.build_options.split(' ').filter((x) => !!x); + pass = this.state.configFilter.every((option) => { + return buildOptions.indexOf(option.value) >= 0; + }); + } + if (pass && this.state.commandLineFilter.length) { + const commandLineOptions = run.info.extra_options.split(' ').filter((x) => !!x); + pass = this.state.commandLineFilter.every((option) => { + return commandLineOptions.indexOf(option.value) >= 0; + }); + } + return pass; + }) + .map((run) => { + return { value: run.run_id, label: run.run_id }; + }) + .slice(0, 1000); - let taskFilterOptions = !filtersEnabled ? [] : unique(listJson.map(run => run.info.task)).map(task => { - return { value: task, label: task } - }); + const taskFilterOptions = !filtersEnabled + ? [] + : unique(listJson.map((run) => run.info.task)).map((task) => { + return { value: task, label: task }; + }); - let nickFilterOptions = !filtersEnabled ? [] : unique(listJson.map(run => run.info.nick)).map(nick => { - return { value: nick, label: nick } - }); + const nickFilterOptions = !filtersEnabled + ? [] + : unique(listJson.map((run) => run.info.nick)).map((nick) => { + return { value: nick, label: nick }; + }); - let configFilterOptions = !filtersEnabled ? [] : unique(listJson.map(run => run.info.build_options.split(" ").filter(x => !!x)).reduce((a, b) => a.concat(b))).map(option => { - return {value: option, label: option} - }); + const configFilterOptions = !filtersEnabled + ? [] + : unique( + listJson.map((run) => run.info.build_options.split(' ').filter((x) => !!x)).reduce((a, b) => a.concat(b)), + ).map((option) => { + return { value: option, label: option }; + }); - let commandLineFilterOptions = !filtersEnabled ? [] : unique(listJson.map(run => run.info.extra_options.split(" ").filter(x => !!x)).reduce((a, b) => a.concat(b))).map(option => { - return {value: option, label: option} - }); + const commandLineFilterOptions = !filtersEnabled + ? [] + : unique( + listJson.map((run) => run.info.extra_options.split(' ').filter((x) => !!x)).reduce((a, b) => a.concat(b)), + ).map((option) => { + return { value: option, label: option }; + }); - let statusFilterOptions = !filtersEnabled ? [] : unique(listJson.map(run => run.status)).map(status => { - return { value: status, label: status } - }); + const statusFilterOptions = !filtersEnabled + ? [] + : unique(listJson.map((run) => run.status)).map((status) => { + return { value: status, label: status }; + }); - return
-
- - { - this.setState({ filtersEnabled: event.target.checked }); - this.resetURL(); - }} - />} - label="Filter Runs" - /> - -
- { this.state.filtersEnabled && -
-
-
- + return ( +
+
+ + { + this.setState({ filtersEnabled: event.target.checked }); + this.resetURL(); + }} + /> + } + label="Filter Runs" + /> + +
+ {this.state.filtersEnabled && ( +
+
+
+ +
+
+ +
+
+ +
-
-
- + )} +
Runs ({runOptions.length})
+ {this.state.slots.map((_, i) => { + const slot = this.state.slots[i]; + const run = this.getRunById(slot.runId); + + return ( +
+
+
+ {i} +
+
+ +
+
+ +
+ + { + this.setState({ showVoteResult: event.target.checked }); + this.resetURL(); + }} + /> + } + label="Show Vote Results" + /> + Show vote results at the end of the voting session. +
-
- +
-
- - - - -
-
- {run && } + )} +
+
+ + +
- }) - } -
- - { - this.setState({ votingEnabled: event.target.checked }); - this.resetURL(); - }} - />} label="Enable Voting" /> - -
- {this.state.votingEnabled && -
-
-
- -
-
-
- - { - this.setState({ showVoteResult: event.target.checked }); - this.resetURL(); - }} - />} label="Show Vote Results" /> - - Show vote results at the end of the voting session. - - -
-
- - { - this.setState({ blind: event.target.checked }); - this.resetURL(); - }} - />} label="Blind" /> - - Randomize runs when comparing them. - - -
-
- -
-
- } -
-
- - - +
Analyzer Link
+
+
{this.state.shortURL || this.createURL()}
-
- Analyzer Link -
-
-
- {this.state.shortURL || this.createURL()} -
-
-
+ ); } } } diff --git a/src/components/PieGraph.tsx b/src/components/PieGraph.tsx new file mode 100644 index 0000000..76ccbcc --- /dev/null +++ b/src/components/PieGraph.tsx @@ -0,0 +1,86 @@ +import * as React from 'react'; + +import { theme } from '../theme'; +import { Pie } from '@nivo/pie'; + +export class PieGraph extends React.Component<{ + width: number; + height: number; + data: any[]; + paletteColor?: { + [id: string]: string; + }; +}> { + public static defaultProps = { + width: 500, + height: 512, + }; + + constructor(props) { + super(props); + } + + render() { + const getColor = !!this.props.paletteColor + ? (dataValue) => { + return dataValue.id in this.props.paletteColor ? this.props.paletteColor[dataValue.id] : `blue`; + } + : undefined; + return ( +
+ +
+ ); + } +} diff --git a/src/components/Player.tsx b/src/components/Player.tsx index c4b89a2..c1f945b 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -1,11 +1,11 @@ -import * as React from "react"; +import * as React from 'react'; -import { assert, clamp, downloadFile, Decoder, AnalyzerFrame, FrameImage } from "./analyzerTools"; +import { assert, clamp, downloadFile, Decoder, AnalyzerFrame, FrameImage } from './analyzerTools'; import { YUVCanvas } from '../YUVCanvas'; -import {CircularProgress, LinearProgress, Table, TableBody, TableCell, TableRow} from "@material-ui/core"; -import { red, deepOrange } from "@material-ui/core/colors"; +import { CircularProgress, LinearProgress, Table, TableBody, TableCell, TableRow } from '@material-ui/core'; +import { red, deepOrange } from '@material-ui/core/colors'; -declare var dragscroll; +declare let dragscroll; const MAX_FRAME_BUFFER_SIZE = 300; function fixedRatio(n: number) { @@ -32,12 +32,12 @@ function prepareBuffer(image: FrameImage) { height: image.Y.height, vdec: 1, - hdec: 1 + hdec: 1, }; } interface PlayerComponentProps { - video: { decoderUrl: string, videoUrl: string, decoderName: string }; + video: { decoderUrl: string; videoUrl: string; decoderName: string }; bench: number; areDetailsVisible: boolean; onScroll?: (top: number, left: number) => void; @@ -50,22 +50,25 @@ interface PlayerComponentProps { onInitialized?: () => void; } -export class PlayerComponent extends React.Component { +export class PlayerComponent extends React.Component< + PlayerComponentProps, + { + decoder: Decoder; + status: string; + playInterval: number; + playbackFrameRate: number; + maxFrameBufferSize: number; + baseFrameOffset: number; + frameOffset: number; + } +> { public static defaultProps: PlayerComponentProps = { scale: 1 / window.devicePixelRatio, scrollTop: 0, scrollLeft: 0, - label: "", + label: '', loop: false, - shouldFitWidth: true + shouldFitWidth: true, } as any; canvasContainer: HTMLDivElement; @@ -80,12 +83,12 @@ export class PlayerComponent extends React.Component { self.advanceOffset(true, false); self.forceUpdateIfMounted(); @@ -118,7 +121,7 @@ export class PlayerComponent extends React.Component { - this.setState({ status: "Downloading Video" } as any); - downloadFile(this.props.video.videoUrl).then(bytes => { + Decoder.loadDecoder(this.props.video.decoderUrl).then((decoder) => { + this.setState({ status: 'Downloading Video' } as any); + downloadFile(this.props.video.videoUrl).then((bytes) => { decoder.openFileBytes(bytes); decoder.setLayers(0); this.setState({ decoder } as any); - this.setState({ status: "Ready" } as any); + this.setState({ status: 'Ready' } as any); this.initialize(decoder); }); }); @@ -173,15 +176,18 @@ export class PlayerComponent extends React.Component= fetchBufferMaxSize) { return; } - this.state.decoder.readFrame().then(frames => { - assert(frames.length === 1); - this.fetchRequestsInFlight--; - this.fetchBuffer.push(frames[0]); - this.forceUpdateIfMounted(); - }).catch(() => { - this.fetchRequestsInFlight--; - this.stopFetchPump(); - }); + this.state.decoder + .readFrame() + .then((frames) => { + assert(frames.length === 1); + this.fetchRequestsInFlight--; + this.fetchBuffer.push(frames[0]); + this.forceUpdateIfMounted(); + }) + .catch(() => { + this.fetchRequestsInFlight--; + this.stopFetchPump(); + }); this.fetchRequestsInFlight++; } @@ -210,7 +216,7 @@ export class PlayerComponent extends React.Component { - frames.forEach(frame => { + decoder.readFrame().then((frames) => { + frames.forEach((frame) => { this.frames.push(frame); this.frameBuffer.push(frame); }); - let image = frames[0].frameImage; + const image = frames[0].frameImage; this.canvas.width = image.Y.width; this.canvas.height = image.Y.height; this.sink = new YUVCanvas(this.canvas); @@ -246,11 +252,11 @@ export class PlayerComponent extends React.Component= this.frameBuffer.length) { return; } - let image = this.frameBuffer[index].frameImage; + const image = this.frameBuffer[index].frameImage; if (this.lastFrameImage === image) { return; } else { - let elapsed = performance.now() - this.lastFrameImageDrawTime; + const elapsed = performance.now() - this.lastFrameImageDrawTime; // console.log("Time Since Last Draw Frame: " + elapsed); } this.sink.drawFrame(prepareBuffer(image)); @@ -317,28 +323,28 @@ export class PlayerComponent extends React.Component { + el.addEventListener('mousedown', (e: MouseEvent) => { lastClientX = e.clientX - el.offsetLeft; lastClientY = e.clientY - el.offsetTop; mouseDown = true; // TODO: Chrome needs a prefix, but it's also behaving strangely when updating the cursor. // I didn't investigate this too much. - el.style.cursor = "grabbing"; + el.style.cursor = 'grabbing'; }); - document.documentElement.addEventListener("mouseup", (e: MouseEvent) => { + document.documentElement.addEventListener('mouseup', (e: MouseEvent) => { mouseDown = false; - el.style.cursor = "grab"; + el.style.cursor = 'grab'; }); - document.documentElement.addEventListener("mousemove", (e: MouseEvent) => { + document.documentElement.addEventListener('mousemove', (e: MouseEvent) => { if (mouseDown) { - var X = e.pageX - el.offsetLeft; - var Y = e.pageY - el.offsetTop; - let dx = -lastClientX + (lastClientX = X); - let dy = -lastClientY + (lastClientY = Y); + const X = e.pageX - el.offsetLeft; + const Y = e.pageY - el.offsetTop; + const dx = -lastClientX + (lastClientX = X); + const dy = -lastClientY + (lastClientY = Y); this.props.onScroll && this.props.onScroll(el.scrollTop - dy, el.scrollLeft - dx); } }); @@ -347,14 +353,14 @@ export class PlayerComponent extends React.Component= this.props.bench) { @@ -363,88 +369,117 @@ export class PlayerComponent extends React.Component -
-

- {this.state.status} + return ( +
+
+ +
+
+ {this.state.status} +
-
+ ); } - let canvasStyle: any = {}; - let scaleLabel = ""; + const canvasStyle: any = {}; + let scaleLabel = ''; if (this.props.shouldFitWidth) { - canvasStyle.width = "100%"; - scaleLabel = " Fit Width"; + canvasStyle.width = '100%'; + scaleLabel = ' Fit Width'; } else if (this.canvas) { - canvasStyle.width = (this.canvas.width * this.props.scale) + "px"; + canvasStyle.width = this.canvas.width * this.props.scale + 'px'; // scaleLabel = " " + this.props.scale + "X" + (window.devicePixelRatio * this.props.scale) + " : 1"; - scaleLabel = ` ${fixedRatio(window.devicePixelRatio * this.props.scale) + ":1"}`; + scaleLabel = ` ${fixedRatio(window.devicePixelRatio * this.props.scale) + ':1'}`; } - return
- { this.props.labelPrefix && -
{this.props.labelPrefix} {this.state.baseFrameOffset + 1 + this.state.frameOffset} {scaleLabel}
- } -
this.mountCanvasContainer(self)}> - this.canvas = self} style={canvasStyle} /> -
- - {this.props.areDetailsVisible && this.state.decoder && -
- - - - Frame # (Base + Offset) - {this.state.baseFrameOffset + 1} + {this.state.frameOffset} = {this.state.baseFrameOffset + 1 + this.state.frameOffset} - - - Frame Buffer - {this.frameBuffer.length} - - - Fetch Buffer - {this.fetchBuffer.length} - - - Decoded Frames - {this.frames.length} - - {this.frameBuffer.length && + return ( +
+ {this.props.labelPrefix && ( +
+ {this.props.labelPrefix} {this.state.baseFrameOffset + 1 + this.state.frameOffset} {scaleLabel} +
+ )} +
this.mountCanvasContainer(self)}> + (this.canvas = self)} style={canvasStyle} /> +
+ + {this.props.areDetailsVisible && this.state.decoder && ( +
+
+ + + Frame # (Base + Offset) + + {this.state.baseFrameOffset + 1} + {this.state.frameOffset} ={' '} + {this.state.baseFrameOffset + 1 + this.state.frameOffset} + + + + Frame Buffer + {this.frameBuffer.length} + + + Fetch Buffer + {this.fetchBuffer.length} + - Frame Decode Time (ms) - {this.frameBuffer[this.state.frameOffset].decodeTime.toFixed(2)} + Decoded Frames + {this.frames.length} - } - - All Frame Decode Time - {allStats.avg.toFixed(2)} avg, {allStats.std.toFixed(2)} std, {allStats.min.toFixed(2)} min, {allStats.max.toFixed(2)} max - - - Last {this.state.decoder.frameRate} Frame Decode Time - {lastStats.avg.toFixed(2)} avg, {lastStats.std.toFixed(2)} std, {lastStats.min.toFixed(2)} min, {lastStats.max.toFixed(2)} max - - {this.canvas && + {this.frameBuffer.length && ( + + Frame Decode Time (ms) + + {this.frameBuffer[this.state.frameOffset].decodeTime.toFixed(2)} + + + )} - Frame Info - - {this.canvas.width} x {this.canvas.height}{' '} - {this.state.decoder.frameRate} fps + All Frame Decode Time + + {allStats.avg.toFixed(2)} avg, {allStats.std.toFixed(2)} std, {allStats.min.toFixed(2)} min,{' '} + {allStats.max.toFixed(2)} max - } - {this.props.bench && - Benchmark (Worker Frame Decode Time Only) - {benchStats ? - {benchStats.avg.toFixed(2)} avg, {benchStats.std.toFixed(2)} std, {benchStats.min.toFixed(2)} min, {benchStats.max.toFixed(2)} max : - Benchmarking {this.frames.length} of {this.props.bench} Frames - } + Last {this.state.decoder.frameRate} Frame Decode Time + + {lastStats.avg.toFixed(2)} avg, {lastStats.std.toFixed(2)} std, {lastStats.min.toFixed(2)} min,{' '} + {lastStats.max.toFixed(2)} max + - } - -
-
- } -
; + {this.canvas && ( + + Frame Info + + {this.canvas.width} x {this.canvas.height} {this.state.decoder.frameRate} fps + + + )} + {this.props.bench && ( + + Benchmark (Worker Frame Decode Time Only) + {benchStats ? ( + + {benchStats.avg.toFixed(2)} avg, {benchStats.std.toFixed(2)} std, {benchStats.min.toFixed(2)}{' '} + min, {benchStats.max.toFixed(2)} max + + ) : ( + + Benchmarking {this.frames.length} of {this.props.bench} Frames{' '} + + + )} + + )} + + +
+ )} +
+ ); } getAllFrameDecodeStats() { @@ -455,16 +490,16 @@ export class PlayerComponent extends React.Component { + const frames = this.frames.slice(start, end); + frames.forEach((frame) => { sum += frame.decodeTime; max = Math.max(max, frame.decodeTime); min = Math.min(min, frame.decodeTime); }); - let avg = sum / frames.length; + const avg = sum / frames.length; let std = 0; - frames.forEach(frame => { - let diff = frame.decodeTime - avg; + frames.forEach((frame) => { + const diff = frame.decodeTime - avg; std += diff * diff; }); std = Math.sqrt(std / frames.length); @@ -473,7 +508,7 @@ export class PlayerComponent extends React.Component void; } @@ -52,16 +51,19 @@ declare global { } } -function generateUUID() { // Public Domain/MIT - var d = new Date().getTime(); - if (typeof performance !== 'undefined' && typeof performance.now === 'function'){ +function generateUUID() { + // Public Domain/MIT + let d = new Date().getTime(); + if (typeof performance !== 'undefined' && typeof performance.now === 'function') { d += performance.now(); //use high-precision timer if available } - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = (d + Math.random() * 16) % 16 | 0; - d = Math.floor(d / 16); - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); - }).toUpperCase(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + .replace(/[xy]/g, function (c) { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); + }) + .toUpperCase(); } function isUUID(uuid: string) { @@ -71,22 +73,25 @@ function isUUID(uuid: string) { return true; } -export class PlayerSplitComponent extends React.Component { +export class PlayerSplitComponent extends React.Component< + PlayerSplitComponentProps, + { + scale: number; + shouldFitWidth: boolean; + isFullScreen: boolean; + playing: boolean; + focus: number; + scrollTop: number; + scrollLeft: number; + voteIndex: number; + showVoterIDDialog: boolean; + voterID: string; + voterEmail: String; + isLooping: boolean; + playersInitialized: boolean[]; + directionsStepIndex: number; + } +> { startTime = performance.now(); // Metrics are submitted along with the vote. metrics = { @@ -101,12 +106,12 @@ export class PlayerSplitComponent extends React.Component { - if (!this.players.every(player => player.canAdvanceOffsetWithoutLooping(true))) { + if (!this.players.every((player) => player.canAdvanceOffsetWithoutLooping(true))) { if (this.state.isLooping) { - this.players.forEach(player => player.resetFrameOffset()); + this.players.forEach((player) => player.resetFrameOffset()); } return; } - this.players.forEach(player => player.advanceOffset(true, false)); + this.players.forEach((player) => player.advanceOffset(true, false)); }, 1000 / frameRate); - this.metrics.playCount ++; + this.metrics.playCount++; } advanceOffset(forward: boolean, userTriggered = true) { - if (!this.players.every(player => player.canAdvanceOffsetWithoutLooping(forward))) { + if (!this.players.every((player) => player.canAdvanceOffsetWithoutLooping(forward))) { if (this.state.isLooping) { - this.players.forEach(player => player.resetFrameOffset()); + this.players.forEach((player) => player.resetFrameOffset()); } return; } this.pauseIfPlaying(); - this.players.forEach(player => { - player.advanceOffset(forward, userTriggered) + this.players.forEach((player) => { + player.advanceOffset(forward, userTriggered); this.setState({ playing: false } as any); }); if (forward) { - this.metrics.stepForwardCount ++; + this.metrics.stepForwardCount++; } else { - this.metrics.stepBackwardCount ++; + this.metrics.stepBackwardCount++; } } resetFrameOffset() { - this.players.forEach(player => player.resetFrameOffset()); - this.metrics.resetCount ++; + this.players.forEach((player) => player.resetFrameOffset()); + this.metrics.resetCount++; } toggleShouldFitWidth() { this.setState({ shouldFitWidth: !this.state.shouldFitWidth } as any); @@ -188,22 +193,22 @@ export class PlayerSplitComponent extends React.Component { - this.zoom(0.5) + this.zoom(0.5); }); Mousetrap.bind(['`'], () => { setFocus(-1); @@ -246,57 +251,57 @@ export class PlayerSplitComponent extends React.Component { localStorage.clear(); - console.log("Cleared Local Storage"); + console.log('Cleared Local Storage'); }); const keyboardScrollSpeed = 64; Mousetrap.bind(['right'], (e) => { - let { scale, scrollLeft } = this.state; + let { scrollLeft } = this.state; scrollLeft += keyboardScrollSpeed; // TODO: Clamp right. - this.setState({scrollLeft} as any); + this.setState({ scrollLeft } as any); e.preventDefault(); }); Mousetrap.bind(['left'], (e) => { - let { scale, scrollLeft } = this.state; + let { scrollLeft } = this.state; scrollLeft -= keyboardScrollSpeed; if (scrollLeft < 0) scrollLeft = 0; - this.setState({scrollLeft} as any); + this.setState({ scrollLeft } as any); e.preventDefault(); }); Mousetrap.bind(['up'], (e) => { - let { scale, scrollTop } = this.state; + let { scrollTop } = this.state; scrollTop -= keyboardScrollSpeed; if (scrollTop < 0) scrollTop = 0; - this.setState({scrollTop} as any); + this.setState({ scrollTop } as any); e.preventDefault(); }); Mousetrap.bind(['down'], (e) => { - let { scale, scrollTop } = this.state; + let { scrollTop } = this.state; scrollTop += keyboardScrollSpeed; // TODO: Clamp down. - this.setState({scrollTop} as any); + this.setState({ scrollTop } as any); e.preventDefault(); }); - let self = this; + const self = this; function setFocus(focus: number) { self.setState({ focus } as any); - self.metrics.focusCount ++; + self.metrics.focusCount++; } for (let i = 1; i <= this.props.videos.length; i++) { Mousetrap.bind([String(i), ABC[i - 1].toLowerCase()], setFocus.bind(this, i - 1)); } } zoom(multiplier: number) { - let scale = this.state.scale; - let newScale = Math.max(0.25, Math.min(32, scale * multiplier)); - let ratio = newScale / scale; + const scale = this.state.scale; + const newScale = Math.max(0.25, Math.min(32, scale * multiplier)); + const ratio = newScale / scale; this.setState({ scale: newScale, shouldFitWidth: false, scrollTop: this.state.scrollTop * ratio, - scrollLeft: this.state.scrollLeft * ratio + scrollLeft: this.state.scrollLeft * ratio, } as any); - this.metrics.zoomCount ++; + this.metrics.zoomCount++; } mountPlayer(index: number, player: PlayerComponent) { this.players[index] = player; @@ -305,36 +310,38 @@ export class PlayerSplitComponent extends React.Component 0) { - this.setState({directionsStepIndex: directionsStepIndex - 1} as any); + this.setState({ directionsStepIndex: directionsStepIndex - 1 } as any); } - localStorage["directionsStepIndex"] = directionsStepIndex - 1; - }; + localStorage['directionsStepIndex'] = directionsStepIndex - 1; + } renderStepActions(step) { - const {directionsStepIndex} = this.state; + const { directionsStepIndex } = this.state; return ( -
+
{step > 0 && ( + style={{ marginRight: 12 }} + > + Back + )} + > + {directionsStepIndex >= 3 ? 'Finish' : 'Next'} +
); } showDirections() { - this.setState({directionsStepIndex: 0} as any); - localStorage["directionsStepIndex"] = 0; + this.setState({ directionsStepIndex: 0 } as any); + localStorage['directionsStepIndex'] = 0; } onVoterIDChange(event, value: string) { - this.setState({voterID: value} as any); - localStorage["voterID"] = value; + this.setState({ voterID: value } as any); + localStorage['voterID'] = value; } onVoterEmailChange(event, value: string) { - this.setState({voterEmail: value} as any); - localStorage["voterEmail"] = value; + this.setState({ voterEmail: value } as any); + localStorage['voterEmail'] = value; } onSubmitVote() { - this.setState({showVoterIDDialog: false} as any); - let vote = { + this.setState({ showVoterIDDialog: false } as any); + const vote = { id: generateUUID(), voter: this.state.voterID || generateUUID(), - videos: [], metrics: this.metrics + videos: [], + metrics: this.metrics, }; - this.props.videos.forEach(video => { - vote.videos.push({decoder: video.decoderUrl, video: video.videoUrl}); - }) + this.props.videos.forEach((video) => { + vote.videos.push({ decoder: video.decoderUrl, video: video.videoUrl }); + }); if (this.state.voteIndex >= 0) { vote.videos[this.state.voteIndex].selected = true; } vote.metrics.time = performance.now() - this.startTime; vote.metrics.devicePixelRatio = window.devicePixelRatio; - vote.metrics.playerDecodeStats = this.players.map(player => player.getAllFrameDecodeStats()); - function sendRequest(object: any, ok: (any), error: (any)) { - var self = this; - var xhr = new XMLHttpRequest(); - xhr.addEventListener("load", function () { + vote.metrics.playerDecodeStats = this.players.map((player) => player.getAllFrameDecodeStats()); + function sendRequest(object: any, ok: any, error: any) { + const self = this; + const xhr = new XMLHttpRequest(); + xhr.addEventListener('load', function () { ok.call(this); }); - xhr.addEventListener("error", function (e) { + xhr.addEventListener('error', function (e) { error.call(this); }); - xhr.open("POST", "/subjective/vote", true); - xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + xhr.open('POST', '/subjective/vote', true); + xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); xhr.send(JSON.stringify(object)); } - sendRequest(vote, () => { - console.log("Sent"); - }, (e) => { - console.error("Something went wrong while submitting your vote."); - }); + sendRequest( + vote, + () => { + console.log('Sent'); + }, + (e) => { + console.error('Something went wrong while submitting your vote.'); + }, + ); if (this.props.onVoted) { this.props.onVoted(vote); } } onInitialized(i: number) { - let { playersInitialized } = this.state; + const { playersInitialized } = this.state; playersInitialized[i] = true; - this.setState({playersInitialized} as any); + this.setState({ playersInitialized } as any); } render() { - let panes = this.props.videos.map((video, i) => { - return
= 0 && this.state.focus != i) ? "none" : "" }}> - this.mountPlayer(i, self)} - onScroll={this.onScroll.bind(this, i)} - video={video} - bench={0} - areDetailsVisible={false} - shouldFitWidth={this.state.shouldFitWidth} - scale={this.state.scale} - scrollTop={this.state.scrollTop} - scrollLeft={this.state.scrollLeft} - labelPrefix={ABC[i]} - isLooping={this.state.isLooping} - onInitialized={this.onInitialized.bind(this, i)} - /> -
- }) + const panes = this.props.videos.map((video, i) => { + return ( +
= 0 && this.state.focus != i ? 'none' : '' }} + > + this.mountPlayer(i, self)} + onScroll={this.onScroll.bind(this, i)} + video={video} + bench={0} + areDetailsVisible={false} + shouldFitWidth={this.state.shouldFitWidth} + scale={this.state.scale} + scrollTop={this.state.scrollTop} + scrollLeft={this.state.scrollLeft} + labelPrefix={ABC[i]} + isLooping={this.state.isLooping} + onInitialized={this.onInitialized.bind(this, i)} + /> +
+ ); + }); let voteButtons = null; - let buttonStyle = { + const buttonStyle = { marginLeft: 4, - marginRight: 4 - } + marginRight: 4, + }; if (this.props.isVotingEnabled) { voteButtons = this.props.videos.map((video, i) => { - return + return ( + + ); }); let allInitialized = true; this.props.videos.forEach((video, i) => { @@ -434,179 +465,216 @@ export class PlayerSplitComponent extends React.ComponentTie) + voteButtons.push( + , + ); } - let toggleButtons = this.props.videos.map((video, i) => { - return + const toggleButtons = this.props.videos.map((video, i) => { + return ( + + ); }); - toggleButtons.push(); - - return
- this.setState({ focus: -1 } as any)} > - Directions - - - - Introduction - - - Calibrate - - - Comparing Videos - - - Submit your vote - - - { this.state.directionsStepIndex === 0 && -
-

- This tool helps engineers understand how various compression techniques affect perceived image quality. -

- {this.renderStepActions(0)} -
- } - { this.state.directionsStepIndex === 1 && -
-

- All squares should be distinguishable from the background. Increase your screen's brightness level or use a different monitor. -

- - {this.renderStepActions(1)} -
- } - { this.state.directionsStepIndex === 2 && -
-

- Two or more videos will be loaded side by side. Please note that the videos may take a while to fully download and decompress. - You can pan / zoom and step through frames backwards and forwards. - We recommend that you get familiar with the keyboard shortcuts to navigate. -

-
- {'<'}, {'>'} Step Backwards and Forwards -
-
- R Rewind -
-
- SPACE Play/Pause -
-
- 1, 2 or A, B Toggle Between Videos -
-
- ~ or S Split Screen + Split + , + ); + + return ( +
+ + Directions + + + + Introduction + + + Calibrate + + + Comparing Videos + + + Submit your vote + + + {this.state.directionsStepIndex === 0 && ( +
+

+ This tool helps engineers understand how various compression techniques affect perceived image + quality. +

+ {this.renderStepActions(0)}
-
- F Fit Width + )} + {this.state.directionsStepIndex === 1 && ( +
+

+ All squares should be distinguishable from the background. Increase your screen's brightness + level or use a different monitor. +

+ + {this.renderStepActions(1)}
-
- [ , ] Zoom Out / In + )} + {this.state.directionsStepIndex === 2 && ( +
+

+ Two or more videos will be loaded side by side. Please note that the videos may take a while to fully + download and decompress. You can pan / zoom and step through frames backwards and forwards. We + recommend that you get familiar with the keyboard shortcuts to navigate. +

+
+ {'<'}, {'>'} Step + Backwards and Forwards +
+
+ R Rewind +
+
+ SPACE Play/Pause +
+
+ 1, 2 or{' '} + A, B Toggle Between + Videos +
+
+ ~ or S Split Screen +
+
+ F Fit Width +
+
+ [ , ] Zoom Out / In +
+
+ Arrow Keys Pan +
+ {this.renderStepActions(2)}
-
- Arrow Keys Pan + )} + {this.state.directionsStepIndex === 3 && ( +
+

+ After carefully inspecting the videos, please vote on which you prefer more. If you have no + preference, select TIE. Click the Information button on the + bottom left to return to this panel. +

+ {this.renderStepActions(3)}
- {this.renderStepActions(2)} -
- } - { this.state.directionsStepIndex === 3 && -
-

- After carefully inspecting the videos, please vote on which you prefer more. - If you have no preference, select TIE. - Click the Information button on the bottom left to return to this panel. -

- {this.renderStepActions(3)} -
- } - -
- - Voter ID - - - - - - - - -
- {panes} + )} + +
+ + Voter ID + + + + + + + + +
{panes}
+ +
+ + + + + + + + + + + + + + + + + + {this.state.playing ? : } + + + + + + + + + + + + + + + + + + + this.toggleIsLooping()}> + + + + + this.toggleShouldFitWidth()}> + + + + + this.toggleFullScreen()}> + {this.state.isFullScreen ? ( + + ) : ( + + )} + + +
+
+ View + {toggleButtons} +
+
+ Vote + {voteButtons} +
+
- -
- - - - - - - - - - - - - - - - - - {this.state.playing ? : } - - - - - - - - - - - - - - - - - - - this.toggleIsLooping()}> - - - - - this.toggleShouldFitWidth()}> - - - - - this.toggleFullScreen()}> - {this.state.isFullScreen ? : } - - -
-
- View - {toggleButtons} -
-
- Vote - {voteButtons} -
-
-
+ ); } } diff --git a/src/components/Split.tsx b/src/components/Split.tsx index 90e43b8..eafe2b4 100644 --- a/src/components/Split.tsx +++ b/src/components/Split.tsx @@ -1,26 +1,27 @@ -import * as React from "react"; +import * as React from 'react'; -import { AnalyzerFrame } from "./analyzerTools"; -import { unreachable } from "./analyzerTools"; +import { AnalyzerFrame } from './analyzerTools'; +import { unreachable } from './analyzerTools'; import { Checkbox, Dialog, DialogContent, Drawer, - FormControlLabel, FormGroup, + FormControlLabel, + FormGroup, IconButton, Paper, Toolbar, - Tooltip -} from "@material-ui/core"; -import { grey } from "@material-ui/core/colors"; -import DashboardIcon from "@material-ui/icons/Dashboard"; -import ListIcon from "@material-ui/icons/List"; -import SkipPreviousIcon from "@material-ui/icons/SkipPrevious"; -import StopIcon from "@material-ui/icons/Stop"; -import PlayArrowIcon from "@material-ui/icons/PlayArrow"; -import SkipNextIcon from "@material-ui/icons/SkipNext"; -import Replay30Icon from "@material-ui/icons/Replay30"; + Tooltip, +} from '@material-ui/core'; +import { grey } from '@material-ui/core/colors'; +import DashboardIcon from '@material-ui/icons/Dashboard'; +import ListIcon from '@material-ui/icons/List'; +import SkipPreviousIcon from '@material-ui/icons/SkipPrevious'; +import StopIcon from '@material-ui/icons/Stop'; +import PlayArrowIcon from '@material-ui/icons/PlayArrow'; +import SkipNextIcon from '@material-ui/icons/SkipNext'; +import Replay30Icon from '@material-ui/icons/Replay30'; declare const Mousetrap; @@ -29,24 +30,27 @@ enum SplitMode { // Right, Vertical, Horizontal, - Last + Last, } interface SplitViewProps { - groups: AnalyzerFrame[][], - groupNames?: string[], + groups: AnalyzerFrame[][]; + groupNames?: string[]; onDecodeAdditionalFrames: (count: number) => void; playbackFrameRate?: number; - mode?: SplitMode + mode?: SplitMode; } -export class SplitView extends React.Component { +export class SplitView extends React.Component< + SplitViewProps, + { + activeFrame: number; + mode: SplitMode; + playInterval: any; + lockScroll: boolean; + showDetails: boolean; + } +> { center: HTMLDivElement; left: HTMLDivElement; right: HTMLDivElement; @@ -58,7 +62,7 @@ export class SplitView extends React.ComponentProvide at least two videos to compare. + if (this.props.groups.length < 2) { + return ( + + Provide at least two videos to compare. + + ); } let content = null; switch (this.state.mode) { @@ -174,16 +182,20 @@ export class SplitView extends React.Component break;*/ case SplitMode.Vertical: - content =
-
this.mountView("left", self, 0)} /> -
this.mountView("right", self, 1)} /> -
+ content = ( +
+
this.mountView('left', self, 0)} /> +
this.mountView('right', self, 1)} /> +
+ ); break; case SplitMode.Horizontal: - content =
-
this.mountView("top", self, 0)} /> -
this.mountView("bottom", self, 1)} /> -
+ content = ( +
+
this.mountView('top', self, 0)} /> +
this.mountView('bottom', self, 1)} /> +
+ ); break; } @@ -193,92 +205,101 @@ export class SplitView extends React.Component - { - this.props.groups.map((name, i) => + if (this.props.groups) { + details = ( +
+ {this.props.groups.map((name, i) => ( -
{getLabel(i)} {getGroupName(i)}
+
+ {getLabel(i)} {getGroupName(i)} +
- ) - } -
- } - return
- this.setState(state => ({showDetails: !state.showDetails}))} - > - {details} - - {content} - -
- - - - - - - - - - - - - - - - - - {!this.state.playInterval ? : } - - - - - - - - - - - - - - Frame: {this.state.activeFrame + 1} of {this.props.groups[0].length} - - - this.setState({ lockScroll: event.target.checked })} - />} - label="Lock Scroll" - /> - + ))}
-
-
+ ); + } + return ( +
+ this.setState((state) => ({ showDetails: !state.showDetails }))} + > + {details} + + {content} + +
+ + + + + + + + + + + + + + + + + + {!this.state.playInterval ? : } + + + + + + + + + + + + + + Frame: {this.state.activeFrame + 1} of {this.props.groups[0].length} + + + this.setState({ lockScroll: event.target.checked })} + /> + } + label="Lock Scroll" + /> + +
+
+
+ ); } -} \ No newline at end of file +} diff --git a/src/components/VotingSession.tsx b/src/components/VotingSession.tsx index 7fad1c5..4ce43bb 100644 --- a/src/components/VotingSession.tsx +++ b/src/components/VotingSession.tsx @@ -1,16 +1,16 @@ -import * as React from "react"; +import * as React from 'react'; -import {PlayerSplitComponent} from "./PlayerSplit"; -import {Dialog, DialogActions, DialogContent, DialogTitle, LinearProgress} from "@material-ui/core"; -import Button from "@material-ui/core/Button"; -import {deepOrange} from "@material-ui/core/colors"; +import { PlayerSplitComponent } from './PlayerSplit'; +import { Dialog, DialogActions, DialogContent, DialogTitle, LinearProgress } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import { deepOrange } from '@material-ui/core/colors'; function shuffle(array: any[], count: number) { // Shuffle Indices for (let j = 0; j < count; j++) { - let a = Math.random() * array.length | 0; - let b = Math.random() * array.length | 0; - let t = array[a]; + const a = (Math.random() * array.length) | 0; + const b = (Math.random() * array.length) | 0; + const t = array[a]; array[a] = array[b]; array[b] = t; } @@ -21,10 +21,10 @@ interface VotingSessionComponentProps { /** * Sets of videos to vote on. */ - videos : { - decoderUrl: string, - videoUrl: string, - decoderName: string + videos: { + decoderUrl: string; + videoUrl: string; + decoderName: string; }[][]; /** @@ -37,7 +37,6 @@ interface VotingSessionComponentProps { */ isBlind?: boolean; - /** * Whether to show voting results. */ @@ -47,99 +46,111 @@ interface VotingSessionComponentProps { /** * Walks through a sequence of votes. */ -export class VotingSessionComponent extends React.Component < VotingSessionComponentProps, { - index : number -} > { +export class VotingSessionComponent extends React.Component< + VotingSessionComponentProps, + { + index: number; + } +> { public static defaultProps: VotingSessionComponentProps = { - description: "", + description: '', isBlind: true, - showResult: false + showResult: false, } as any; - votes: any [] = []; + votes: any[] = []; constructor(props) { super(props); this.state = { - index: -1 + index: -1, }; } next() { this.setState({ - index: this.state.index + 1 + index: this.state.index + 1, }); } renderVoteResults() { - let decoders = {}; - this.votes.forEach(vote => { - vote.videos.forEach(video => { + const decoders = {}; + this.votes.forEach((vote) => { + vote.videos.forEach((video) => { if (!(video.decoder in decoders)) { decoders[video.decoder] = 0; } if (video.selected) { - decoders[video.decoder] ++; + decoders[video.decoder]++; } - }) + }); }); - let decoderList = []; - for (var k in decoders) { + const decoderList = []; + for (const k in decoders) { decoderList.push([k, decoders[k]]); } - return
- { decoderList.map((pair, i) => -
- {pair[1]}{': '}{pair[0]} -
) - } -
+ return ( +
+ {decoderList.map((pair, i) => ( +
+ {pair[1]} + {': '} + {pair[0]} +
+ ))} +
+ ); } render() { - let index = this.state.index; - let videos = this.props.videos; + const index = this.state.index; + const videos = this.props.videos; let body; if (index < 0) { - body = - Vote - -

- You will be asked to vote on {videos.length} sets of videos. {' '} - {this.props.description} -

-
- - - -
+ body = ( + + Vote + +

+ You will be asked to vote on {videos.length} sets of videos. {this.props.description} +

+
+ + + +
+ ); } else if (index < videos.length) { let videosToVoteOn = videos[index]; if (this.props.isBlind) { videosToVoteOn = shuffle(videosToVoteOn.slice(), 16); } - body = { - this.votes.push(vote); - this.next(); - }}/> + body = ( + { + this.votes.push(vote); + this.next(); + }} + /> + ); } else { - body = - Thank You - -

- Your vote counts! -

- { this.props.showResult && - this.renderVoteResults() - } -
-
+ body = ( + + Thank You + +

Your vote counts!

+ {this.props.showResult && this.renderVoteResults()} +
+
+ ); } - return
- {body} -
+ return ( +
+ {' '} + {body} +
+ ); } -} \ No newline at end of file +} diff --git a/src/components/analyzerTools.ts b/src/components/analyzerTools.ts index 28adc16..8d14f68 100644 --- a/src/components/analyzerTools.ts +++ b/src/components/analyzerTools.ts @@ -3,7 +3,7 @@ declare let TextDecoder: any; export const TRACE_RENDERING = 0; -let YUV2RGB_TABLE = new Uint32Array(256 * 256 * 256); +const YUV2RGB_TABLE = new Uint32Array(256 * 256 * 256); function YUV2RGB(y, u, v) { return YUV2RGB_TABLE[(y << 16) | (u << 8) | v]; } @@ -13,12 +13,12 @@ export function clamp(v, a, b) { return v; } function computeYUV2RGB(y, u, v) { - let rTmp = y + (1.370705 * (v - 128)); - let gTmp = y - (0.698001 * (v - 128)) - (0.337633 * (u - 128)); - let bTmp = y + (1.732446 * (u - 128)); - let r = clamp(rTmp | 0, 0, 255) | 0; - let g = clamp(gTmp | 0, 0, 255) | 0; - let b = clamp(bTmp | 0, 0, 255) | 0; + const rTmp = y + 1.370705 * (v - 128); + const gTmp = y - 0.698001 * (v - 128) - 0.337633 * (u - 128); + const bTmp = y + 1.732446 * (u - 128); + const r = clamp(rTmp | 0, 0, 255) | 0; + const g = clamp(gTmp | 0, 0, 255) | 0; + const b = clamp(bTmp | 0, 0, 255) | 0; return (b << 16) | (g << 8) | (r << 0); } function buildYUVTable() { @@ -33,7 +33,7 @@ function buildYUVTable() { buildYUVTable(); export interface FrameImagePlane { - buffer: ArrayBuffer, + buffer: ArrayBuffer; depth: number; width: number; height: number; @@ -43,48 +43,63 @@ export interface FrameImagePlane { } export interface FrameImage { - hashCode: number, - Y: FrameImagePlane, - U: FrameImagePlane, - V: FrameImagePlane + hashCode: number; + Y: FrameImagePlane; + U: FrameImagePlane; + V: FrameImagePlane; } -function createImageData(image: FrameImage) { - let w = image.Y.width; - let h = image.Y.height; - let depth = image.Y.depth; +function createImageData(image: FrameImage, plane = -1) { + const w = image.Y.width; + const h = image.Y.height; + const depth = image.Y.depth; assert(depth == 8); - let YH = new Uint8Array(image.Y.buffer); - let UH = new Uint8Array(image.U.buffer); - let VH = new Uint8Array(image.V.buffer); + const YH = new Uint8Array(image.Y.buffer); + const UH = new Uint8Array(image.U.buffer); + const VH = new Uint8Array(image.V.buffer); - let Ys = image.Y.stride; - let Us = image.U.stride; - let Vs = image.V.stride; + const Ys = image.Y.stride; + const Us = image.U.stride; + const Vs = image.V.stride; - let imageData = new ImageData(w, h); - let I = imageData.data; + const imageData = new ImageData(w, h); + const I = imageData.data; - let p = 0; + const p = 0; let bgr = 0; - let uxdec = image.U.xdec; - let vxdec = image.V.xdec; - let uydec = image.U.ydec; - let vydec = image.V.ydec; + const uxdec = image.U.xdec; + const vxdec = image.V.xdec; + const uydec = image.U.ydec; + const vydec = image.V.ydec; for (let y = 0; y < h; y++) { - let yYs = y * Ys; - let yUs = (y >> uydec) * Us; - let yVs = (y >> vydec) * Vs; + const yYs = y * Ys; + const yUs = (y >> uydec) * Us; + const yVs = (y >> vydec) * Vs; for (let x = 0; x < w; x++) { - let Y = YH[yYs + x]; - let U = UH[yUs + (x >> uxdec)]; - let V = VH[yVs + (x >> vxdec)]; + const Y = YH[yYs + x]; + const U = UH[yUs + (x >> uxdec)]; + const V = VH[yVs + (x >> vxdec)]; bgr = YUV2RGB(Y, U, V); - let r = (bgr >> 0) & 0xFF; - let g = (bgr >> 8) & 0xFF; - let b = (bgr >> 16) & 0xFF; - let index = (Math.imul(y, w) + x) << 2; + let r, g, b; + if (plane === 0) { + r = Y; + g = Y; + b = Y; + } else if (plane == 1) { + r = U; + g = U; + b = U; + } else if (plane == 2) { + r = V; + g = V; + b = V; + } else { + r = (bgr >> 0) & 0xff; + g = (bgr >> 8) & 0xff; + b = (bgr >> 16) & 0xff; + } + const index = (Math.imul(y, w) + x) << 2; I[index + 0] = r; I[index + 1] = g; I[index + 2] = b; @@ -94,37 +109,37 @@ function createImageData(image: FrameImage) { return imageData; } -function makeCanvas(image: FrameImage): HTMLCanvasElement { - var canvas = document.createElement("canvas"); - var imageData = createImageData(image); +function makeCanvas(image: FrameImage, plane = -1): HTMLCanvasElement { + const canvas = document.createElement('canvas'); + const imageData = createImageData(image, plane); canvas.width = imageData.width; canvas.height = imageData.height; - var ctx = canvas.getContext("2d"); + const ctx = canvas.getContext('2d'); ctx.putImageData(imageData, 0, 0); return canvas; } export function makePattern(uri: string, scale: number, ready: (canvas: HTMLCanvasElement) => void) { - let image = new Image(); + const image = new Image(); image.onload = function () { - var canvas = document.createElement("canvas"); + const canvas = document.createElement('canvas'); canvas.width = image.width * scale; canvas.height = image.height * scale; - let ctx = canvas.getContext("2d"); + const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); ready(canvas); - } + }; image.src = uri; } -export function assert(c: any, message: string = "") { +export function assert(c: any, message = '') { if (!c) { throw new Error(message); } } export function unreachable() { - throw new Error("Unreachable"); + throw new Error('Unreachable'); } export function hashString(s: string) { @@ -133,7 +148,7 @@ export function hashString(s: string) { return hashValue; } for (let i = 0; i < s.length; i++) { - hashValue = ((hashValue << 5) - hashValue) + s.charCodeAt(i); + hashValue = (hashValue << 5) - hashValue + s.charCodeAt(i); hashValue |= 0; } return hashValue >>> 0; @@ -142,44 +157,44 @@ export function hashString(s: string) { // Use 31 colors, don't use 32 colors since hash(string) % 32 can cause colors // collisions. export const COLORS = [ - "#126800", - "#3e2dd5", - "#87ba00", - "#305eff", - "#8eda53", - "#37007f", - "#e1c633", - "#0055d0", - "#ffab28", - "#00267a", - "#fc6800", - "#016fc7", - "#6e9000", - "#b2007c", - "#00ae63", - "#d80048", - "#00caed", - "#a31500", - "#02a4e3", - "#ff4553", - "#003d5b", - "#ff6c7e", - "#2a3700", - "#ff95c5", - "#a9d19d", - "#5e0060", - "#8f5600", - "#dcbaed", - "#511500", - "#f3b9a2", - "#5b0022" + '#126800', + '#3e2dd5', + '#87ba00', + '#305eff', + '#8eda53', + '#37007f', + '#e1c633', + '#0055d0', + '#ffab28', + '#00267a', + '#fc6800', + '#016fc7', + '#6e9000', + '#b2007c', + '#00ae63', + '#d80048', + '#00caed', + '#a31500', + '#02a4e3', + '#ff4553', + '#003d5b', + '#ff6c7e', + '#2a3700', + '#ff95c5', + '#a9d19d', + '#5e0060', + '#8f5600', + '#dcbaed', + '#511500', + '#f3b9a2', + '#5b0022', ]; export const HEAT_COLORS = []; function generateHeatColors() { function color(value) { - var h = (1.0 - value) * 240; - return "hsl(" + h + ", 100%, 50%)"; + const h = (1.0 - value) * 240; + return 'hsl(' + h + ', 100%, 50%)'; } for (let i = 0; i < 256; i++) { HEAT_COLORS.push(color(i / 256)); @@ -209,22 +224,22 @@ export class Accounting { this.frameSymbols = Accounting.flatten(this.symbols); return this.frameSymbols; } - countCache: { [filter: string]: { blocks: number[][], total: number, leftover: number } } = {}; - countBits(filter: string): { blocks: number[][], total: number } { + countCache: { [filter: string]: { blocks: number[][]; total: number; leftover: number } } = {}; + countBits(filter: string): { blocks: number[][]; total: number } { if (!filter) { - filter = "__none__"; + filter = '__none__'; } if (this.countCache[filter]) { return this.countCache[filter]; } - let blocks = []; + const blocks = []; let total = 0; let leftover = 0; - this.symbols.forEach(symbol => { - if (filter !== "__none__" && symbol.name != filter) { + this.symbols.forEach((symbol) => { + if (filter !== '__none__' && symbol.name != filter) { return; } - let { x, y } = symbol; + const { x, y } = symbol; if (x < 0 || y < 0) { leftover += symbol.bits; return; @@ -238,16 +253,18 @@ export class Accounting { blocks[y][x] += symbol.bits; total += symbol.bits; }); - return this.countCache[filter] = { blocks: blocks, total, leftover }; + return (this.countCache[filter] = { blocks: blocks, total, leftover }); } createBlockSymbols(c: number, r: number) { - return Accounting.flatten(this.symbols.filter(symbol => { - return symbol.x === c && symbol.y === r; - })); + return Accounting.flatten( + this.symbols.filter((symbol) => { + return symbol.x === c && symbol.y === r; + }), + ); } static flatten(sybmols: AccountingSymbol[]): AccountingSymbolMap { - let map = Object.create(null); - sybmols.forEach(symbol => { + const map = Object.create(null); + sybmols.forEach((symbol) => { let s = map[symbol.name]; if (!s) { s = map[symbol.name] = new AccountingSymbol(symbol.name, 0, 0, symbol.x, symbol.y); @@ -255,39 +272,59 @@ export class Accounting { s.bits += symbol.bits; s.samples += symbol.samples; }); - let ret = Object.create(null); - let names = []; - for (let name in map) names.push(name); + const ret = Object.create(null); + const names = []; + for (const name in map) names.push(name); // Sort by bits. names.sort((a, b) => map[b].bits - map[a].bits); - names.forEach(name => { + names.forEach((name) => { ret[name] = map[name]; }); return ret; } static getSortedSymbolNames(accountings: Accounting[]): string[] { - let set = {}; - accountings.forEach(accounting => { - let frameSymbols = accounting.createFrameSymbols(); - for (let name in frameSymbols) { + const set = {}; + accountings.forEach((accounting) => { + const frameSymbols = accounting.createFrameSymbols(); + for (const name in frameSymbols) { set[name] = undefined; } }); - let names = Object.keys(set); + const names = Object.keys(set); names.sort(); return names; } } export class Histogram { - constructor( - public counts: { [id: string]: number }, - public names: { [id: string]: number }) { + constructor(public counts: { [id: string]: number }, public names: { [id: string]: number }) { // ... } } +export interface FilmGrainParams { + scaling_lut_y: number[]; + scaling_lut_cr: number[]; + scaling_lut_cb: number[]; + grain_sample_y: number[][]; + grain_sample_cb: number[][]; + grain_sample_cr: number[][]; + normalizedGrain_y: number[][]; + normalizedGrain_cb: number[][]; + normalizedGrain_cr: number[][]; + ar_coeff_lag: number; + ar_coeff_shift: number; + overlap_flag: number; + random_seed: number; + chroma_scaling_from_luma: number; + grain_scale_shift: number; + scaling_shift: number; + cb_mult: number; + cb_luma_mult: number; + bit_depth: number; +} + export class AnalyzerFrame { json: { frameType: number; @@ -305,8 +342,10 @@ export class AnalyzerFrame { deltaQRes: number; deltaQPresentFlag: number; config: { - MI_SIZE: number + MI_SIZE: number; }; + filmGrainParamsPresent: boolean; + filmGrainParams?: FilmGrainParams; }; accounting: Accounting; blockSizeHist: Histogram; @@ -316,18 +355,124 @@ export class AnalyzerFrame { uvPredictionModeHist: Histogram; skipHist: Histogram; dualFilterTypeHist: Histogram; + compoundTypeHist: Histogram; + motionModeHist: Histogram; frameImage: FrameImage; + grainFrameImage: FrameImage; + scaledGrainImage: FrameImage; + oldImage: FrameImage; decodeTime: number; canvasImage: HTMLCanvasElement; - get image() : HTMLCanvasElement { - if (this.canvasImage) { - return this.canvasImage; + canvasGrainImage: HTMLCanvasElement[]; + scaledGrainCanvas: HTMLCanvasElement[]; + oldImageCanvas: HTMLCanvasElement[]; + imageCanvas: HTMLCanvasElement[]; + + constructor() { + this.canvasGrainImage = [null, null, null, null]; + this.scaledGrainCanvas = [null, null, null, null]; + this.oldImageCanvas = [null, null, null, null]; + this.imageCanvas = [null, null, null, null]; + } + + get image(): HTMLCanvasElement { + return this.getImage(-1); + } + + getImage(plane: number): HTMLCanvasElement { + const index = plane < 3 && plane >= 0 ? plane : 4; + + if (this.imageCanvas[index]) { + return this.imageCanvas[index]; + } + + this.imageCanvas[index] = makeCanvas(this.frameImage, plane); + if (!this.imageCanvas.includes(null)) { + this.frameImage = null; + } + + return this.imageCanvas[index]; + } + + getOldImage(plane: number): HTMLCanvasElement { + const index = plane < 3 && plane >= 0 ? plane : 4; + + if (this.oldImageCanvas[index]) { + return this.oldImageCanvas[index]; + } + + this.oldImageCanvas[index] = makeCanvas(this.oldImage, plane); + if (!this.oldImageCanvas.includes(null)) { + this.oldImage = null; + } + + return this.oldImageCanvas[index]; + } + + getGrainImage(plane: number): HTMLCanvasElement { + const index = plane < 3 && plane >= 0 ? plane : 4; + + if (this.canvasGrainImage[index]) { + return this.canvasGrainImage[index]; + } + + this.canvasGrainImage[index] = makeCanvas(this.grainFrameImage, plane); + if (!this.canvasGrainImage.includes(null)) { + this.grainFrameImage = null; + } + + return this.canvasGrainImage[index]; + } + + getScaledGrainImage(plane: number): HTMLCanvasElement { + const index = plane < 3 && plane >= 0 ? plane : 4; + + if (this.scaledGrainCanvas[index]) { + return this.scaledGrainCanvas[index]; + } + + this.scaledGrainCanvas[index] = makeCanvas(this.scaledGrainImage, plane); + if (!this.scaledGrainCanvas.includes(null)) { + this.scaledGrainImage = null; + } + + return this.scaledGrainCanvas[index]; + } + + normalizeGrainSamples(index: number) { + let grain; + + switch (index) { + case 0: + grain = this.json.filmGrainParams.grain_sample_y; + break; + case 1: + grain = this.json.filmGrainParams.grain_sample_cb; + break; + default: + grain = this.json.filmGrainParams.grain_sample_cr; + } + + const minY = Math.min(...[].concat(...grain)); + const maxY = Math.max(...[].concat(...grain)); + + let normalizedGrain = grain.map((val) => + val.map((subVal) => { + const z = ((subVal - minY) / (maxY - minY)) * 255; + return Math.round(z); + }), + ); + + if (index == 0) { + normalizedGrain = normalizedGrain.slice(9).map((k) => k.slice(9, 82 - 9)); + this.json.filmGrainParams.normalizedGrain_y = normalizedGrain; + } else if (index == 1) { + normalizedGrain = normalizedGrain.slice(6).map((k) => k.slice(6, 44 - 6)); + this.json.filmGrainParams.normalizedGrain_cb = normalizedGrain; + } else { + normalizedGrain = normalizedGrain.slice(6).map((k) => k.slice(6, 44 - 6)); + this.json.filmGrainParams.normalizedGrain_cr = normalizedGrain; } - // Make canvas elements lazily, this speeds up loading. - this.canvasImage = makeCanvas(this.frameImage); - // Free frame image data, we don't need it anymore. - this.frameImage = null; - return this.canvasImage; } config: string; blockSizeLog2Map: [number, number][]; @@ -337,20 +482,21 @@ export class AnalyzerFrame { } function getAccountingFromJson(json: any, name: string): Accounting { - var accounting = new Accounting(); + const accounting = new Accounting(); if (json[name]) { - let names = json[name + "Map"]; - let symbols = []; - let x = -1, y = -1; + const names = json[name + 'Map']; + const symbols = []; + let x = -1, + y = -1; for (let i = 0; i < json.symbols.length; i++) { - let symbol = json.symbols[i]; + const symbol = json.symbols[i]; if (symbol.length == 2) { x = symbol[0]; y = symbol[1]; } else { - let name = symbol[0]; - let bits = symbol[1]; - let samples = symbol[2]; + const name = symbol[0]; + const bits = symbol[1]; + const samples = symbol[2]; symbols.push(new AccountingSymbol(names[name], bits, samples, x, y)); } } @@ -363,30 +509,29 @@ function getHistogramFromJson(json: any, name: string): Histogram { if (!json[name]) { return null; } - let counts = {}; - json[name].forEach(row => { - row.forEach(v => { + const counts = {}; + json[name].forEach((row) => { + row.forEach((v) => { if (counts[v] === undefined) { counts[v] = 0; } counts[v]++; }); }); - return new Histogram(counts, json[name + "Map"]); + return new Histogram(counts, json[name + 'Map']); } - /** * JSON arrays are RLE encoded. ..., x, [12], ... means that x repeats itself * an additional 12 times. The RLE marker is a single element array. */ -function uncompressArray(src: any []) { +function uncompressArray(src: any[]) { let pre; - let dst = []; + const dst = []; let allUint8 = true; for (let i = 0; i < src.length; i++) { if (Array.isArray(src[i]) && src[i].length == 1) { - let count = src[i][0]; + const count = src[i][0]; for (let j = 0; j < count; j++) { dst.push(pre); } @@ -394,7 +539,7 @@ function uncompressArray(src: any []) { } else { pre = src[i]; dst.push(pre); - if (pre !== (pre & 0xFF)) { + if (pre !== (pre & 0xff)) { allUint8 = false; } } @@ -415,45 +560,56 @@ function uncompress(arrays) { } function readFrameFromJson(json): AnalyzerFrame { - uncompress(json["blockSize"]); - uncompress(json["transformSize"]); - uncompress(json["transformType"]); - uncompress(json["mode"]); - uncompress(json["uv_mode"]); - uncompress(json["skip"]); - uncompress(json["filter"]); - uncompress(json["cdef_level"]); - uncompress(json["cdef_strength"]); - uncompress(json["motionVectors"]); - uncompress(json["referenceFrame"]); - uncompress(json["cfl_alpha_idx"]); - uncompress(json["cfl_alpha_sign"]); - uncompress(json["dualFilterType"]); - uncompress(json["delta_q"]); - uncompress(json["seg_id"]); - - let frame = new AnalyzerFrame(); + uncompress(json['blockSize']); + uncompress(json['transformSize']); + uncompress(json['transformType']); + uncompress(json['mode']); + uncompress(json['uv_mode']); + uncompress(json['skip']); + uncompress(json['filter']); + uncompress(json['cdef_level']); + uncompress(json['cdef_strength']); + uncompress(json['motionVectors']); + uncompress(json['referenceFrame']); + uncompress(json['cfl_alpha_idx']); + uncompress(json['cfl_alpha_sign']); + uncompress(json['dualFilterType']); + uncompress(json['delta_q']); + uncompress(json['seg_id']); + uncompress(json['motion_mode']); + uncompress(json['compound_type']); + uncompress(json['wedge']); + + const frame = new AnalyzerFrame(); frame.json = json; - frame.accounting = getAccountingFromJson(json, "symbols"); - frame.blockSizeHist = getHistogramFromJson(json, "blockSize"); - frame.skipHist = getHistogramFromJson(json, "skip"); - frame.transformSizeHist = getHistogramFromJson(json, "transformSize"); - frame.transformTypeHist = getHistogramFromJson(json, "transformType"); - frame.predictionModeHist = getHistogramFromJson(json, "mode"); - frame.uvPredictionModeHist = getHistogramFromJson(json, "uv_mode"); - frame.dualFilterTypeHist = getHistogramFromJson(json, "dualFilterType"); + frame.accounting = getAccountingFromJson(json, 'symbols'); + frame.blockSizeHist = getHistogramFromJson(json, 'blockSize'); + frame.skipHist = getHistogramFromJson(json, 'skip'); + frame.transformSizeHist = getHistogramFromJson(json, 'transformSize'); + frame.transformTypeHist = getHistogramFromJson(json, 'transformType'); + frame.predictionModeHist = getHistogramFromJson(json, 'mode'); + frame.uvPredictionModeHist = getHistogramFromJson(json, 'uv_mode'); + frame.dualFilterTypeHist = getHistogramFromJson(json, 'dualFilterType'); + frame.motionModeHist = getHistogramFromJson(json, 'motion_mode'); + frame.compoundTypeHist = getHistogramFromJson(json, 'compound_type'); frame.miSizeLog2 = log2(json.config.MI_SIZE); frame.miSuperSizeLog2 = log2(64); // TODO: Does this ever change? - frame.blockSizeLog2Map = makeBlockSizeLog2MapByValue(json["blockSizeMap"]); - frame.transformSizeLog2Map = makeTransformSizeLog2MapByValue(json["transformSizeMap"]); + frame.blockSizeLog2Map = makeBlockSizeLog2MapByValue(json['blockSizeMap']); + frame.transformSizeLog2Map = makeTransformSizeLog2MapByValue(json['transformSizeMap']); + + if (frame.json.filmGrainParamsPresent) { + frame.normalizeGrainSamples(0); + frame.normalizeGrainSamples(1); + frame.normalizeGrainSamples(2); + } return frame; } export function downloadFile(url: string): Promise { return new Promise((resolve, reject) => { if (url.startsWith(localFileProtocol)) { - let localFile = url.substring(localFileProtocol.length); - let file = localFiles[localFile]; + const localFile = url.substring(localFileProtocol.length); + const file = localFiles[localFile]; if (file) { resolve(new Uint8Array(file)); return; @@ -462,22 +618,22 @@ export function downloadFile(url: string): Promise { return; } } - let xhr = new XMLHttpRequest(); - let self = this; - xhr.open("GET", url, true); - xhr.responseType = "arraybuffer"; + const xhr = new XMLHttpRequest(); + const self = this; + xhr.open('GET', url, true); + xhr.responseType = 'arraybuffer'; xhr.send(); - xhr.addEventListener("progress", (e) => { - let progress = (e.loaded / e.total) * 100; + xhr.addEventListener('progress', (e) => { + const progress = (e.loaded / e.total) * 100; }); - xhr.addEventListener("load", function () { + xhr.addEventListener('load', function () { if (xhr.status != 200) { reject(); return; } resolve(new Uint8Array(this.response)); }); - xhr.addEventListener("error", function () { + xhr.addEventListener('error', function () { reject(`Cannot download ${url}`); }); }); @@ -485,15 +641,15 @@ export function downloadFile(url: string): Promise { export function downloadJson(url: string): Promise { return new Promise((resolve, reject) => { - let xhr = new XMLHttpRequest(); - let self = this; - xhr.open("GET", url, true); - xhr.responseType = "json"; + const xhr = new XMLHttpRequest(); + const self = this; + xhr.open('GET', url, true); + xhr.responseType = 'json'; xhr.send(); - xhr.addEventListener("progress", (e) => { - let progress = (e.loaded / e.total) * 100; + xhr.addEventListener('progress', (e) => { + const progress = (e.loaded / e.total) * 100; }); - xhr.addEventListener("load", function () { + xhr.addEventListener('load', function () { if (xhr.status != 200) { reject(); return; @@ -506,9 +662,13 @@ export function downloadJson(url: string): Promise { export function loadFramesFromJson(url: string): Promise { return new Promise((resolve, reject) => { downloadJson(url).then((json: any) => { - resolve(json.filter(frame => !!frame).map(frame => { - return readFrameFromJson(frame); - })); + resolve( + json + .filter((frame) => !!frame) + .map((frame) => { + return readFrameFromJson(frame); + }), + ); }); }); } @@ -537,7 +697,7 @@ export class Size { return this; } roundUpToMultipleOfLog2(roundToLog2) { - let roundTo = 1 << roundToLog2; + const roundTo = 1 << roundToLog2; this.w = (this.w + roundTo - 1) & ~(roundTo - 1); this.h = (this.h + roundTo - 1) & ~(roundTo - 1); return this; @@ -562,10 +722,7 @@ export class Rectangle { return this; } containsPoint(point: Vector): boolean { - return (point.x >= this.x) && - (point.x < this.x + this.w) && - (point.y >= this.y) && - (point.y < this.y + this.h); + return point.x >= this.x && point.x < this.x + this.w && point.y >= this.y && point.y < this.y + this.h; } getCenter(): Vector { return new Vector(this.x + this.w / 2, this.y + this.h / 2); @@ -609,8 +766,8 @@ export class Vector { return Math.sqrt(this.x * this.x + this.y * this.y); } distanceTo(v: Vector) { - let x = this.x - v.x; - let y = this.y - v.y; + const x = this.x - v.x; + const y = this.y - v.y; return Math.sqrt(x * x + y * y); } normalize() { @@ -651,12 +808,12 @@ export class Vector { return this; } clampLength(min: number, max: number) { - let length = this.length(); + const length = this.length(); this.multiplyScalar(Math.max(min, Math.min(max, length)) / length); return this; } toString(): string { - return this.x + "," + this.y; + return this.x + ',' + this.y; } } @@ -671,7 +828,7 @@ function getFramesIvf(ivf: Uint8Array): number { let i = 32; let frames = 0; while (i < length) { - let frame_length = ivf[i] + (ivf[i+1]<<8) + (ivf[i+2]<<16) + (ivf[i+3]<<24); + const frame_length = ivf[i] + (ivf[i + 1] << 8) + (ivf[i + 2] << 16) + (ivf[i + 3] << 24); i += 12 + frame_length; frames++; } @@ -685,10 +842,10 @@ export class Decoder { decoder: string; buffer: Uint8Array; frames: AnalyzerFrame[] = []; - frameRate: number = 30; + frameRate = 30; totalFrames: number; /** Whether to read image data after decoding a frame. */ - shouldReadImageData: boolean = true; + shouldReadImageData = true; constructor(nativeModule, worker) { this.buffer = new Uint8Array(0); @@ -703,49 +860,49 @@ export class Decoder { } load(url): Promise { - if (url.indexOf("://") < 0) { + if (url.indexOf('://') < 0) { url = window.location.origin + '/' + url; } return new Promise((resolve, reject) => { - var id = String(Math.random()); + const id = String(Math.random()); this.addWorkerCallback(id, (e) => { - 3 + 3; if (e.data.payload) { this.workerInfo = { - buildConfig: e.data.payload.buildConfig - } + buildConfig: e.data.payload.buildConfig, + }; resolve(null); } else { reject(`Cannot load decoder, check url: ${url}`); } }); this.worker.postMessage({ - command: "load", + command: 'load', payload: [url], - id + id, }); }); } openFileBytes(buffer: Uint8Array) { - this.frameRate = buffer[16] | buffer[17] << 24 | buffer[18] << 16 | buffer[19] << 24; + this.frameRate = buffer[16] | (buffer[17] << 24) | (buffer[18] << 16) | (buffer[19] << 24); this.totalFrames = getFramesIvf(buffer); this.buffer = buffer; this.worker.postMessage({ - command: "openFileBytes", - payload: buffer + command: 'openFileBytes', + payload: buffer, }); } setLayers(layers: number) { this.worker.postMessage({ - command: "setLayers", - payload: layers + command: 'setLayers', + payload: layers, }); } initWorker() { - this.worker.addEventListener("message", (e) => { + this.worker.addEventListener('message', (e) => { if (!e.data.id) { return; } @@ -763,68 +920,80 @@ export class Decoder { * memory pressure. */ releaseFrameImageBuffers(frameImage: FrameImage) { - this.worker.postMessage({ - command: "releaseFrameBuffers", - payload: { - Y: frameImage.Y.buffer, - U: frameImage.U.buffer, - V: frameImage.V.buffer - } - }, [frameImage.Y.buffer, frameImage.U.buffer, frameImage.V.buffer]); - assert(frameImage.Y.buffer.byteLength === 0 && - frameImage.U.buffer.byteLength === 0 && - frameImage.V.buffer.byteLength === 0, "Buffers must be transferred."); + this.worker.postMessage( + { + command: 'releaseFrameBuffers', + payload: { + Y: frameImage.Y.buffer, + U: frameImage.U.buffer, + V: frameImage.V.buffer, + }, + }, + [frameImage.Y.buffer, frameImage.U.buffer, frameImage.V.buffer], + ); + assert( + frameImage.Y.buffer.byteLength === 0 && + frameImage.U.buffer.byteLength === 0 && + frameImage.V.buffer.byteLength === 0, + 'Buffers must be transferred.', + ); } readFrame(): Promise { - let worker = this.worker; - let self = this; - let id = String(Math.random()); + const worker = this.worker; + const self = this; + const id = String(Math.random()); return new Promise((resolve, reject) => { this.addWorkerCallback(id, function (e) { - let o = e.data.payload.json as Object[]; + const o = e.data.payload.json as Object[]; if (!o) { reject(); return; } - let frames: AnalyzerFrame[] = []; + const frames: AnalyzerFrame[] = []; for (let i = 0; i < o.length - 1; i++) { - let json = o[i]; - let frame = readFrameFromJson(json); + const json = o[i]; + const frame = readFrameFromJson(json); frame.config = self.workerInfo.buildConfig; frames.push(frame); self.frames && self.frames.push(frame); } if (self.shouldReadImageData) { frames[frames.length - 1].frameImage = e.data.payload.image; + frames[frames.length - 1].grainFrameImage = e.data.payload.grainImage; + frames[frames.length - 1].scaledGrainImage = e.data.payload.scaledGrainImage; + frames[frames.length - 1].oldImage = e.data.payload.oldImage; } frames[frames.length - 1].decodeTime = e.data.payload.decodeTime; resolve(frames); }); - let shouldReadImageData = self.shouldReadImageData; + const shouldReadImageData = self.shouldReadImageData; worker.postMessage({ - command: "readFrame", + command: 'readFrame', id, - shouldReadImageData + shouldReadImageData, }); }); } static loadDecoder(url: string): Promise { return new Promise((resolve, reject) => { - let worker = new Worker("dist/analyzer_worker.bundle.js"); - let decoder = new Decoder(null, worker); - decoder.load(url).then(() => { - resolve(decoder); - }).catch((x) => { - reject(x); - }); + const worker = new Worker('dist/analyzer_worker.bundle.js'); + const decoder = new Decoder(null, worker); + decoder + .load(url) + .then(() => { + resolve(decoder); + }) + .catch((x) => { + reject(x); + }); }); } } -export let localFileProtocol = "local://"; -export let localFiles = {}; +export const localFileProtocol = 'local://'; +export const localFiles = {}; const blockSizeLog2MapByName = { BLOCK_2X2: [1, 1], @@ -853,7 +1022,7 @@ const blockSizeLog2MapByName = { BLOCK_16X64: [4, 6], BLOCK_64X16: [6, 4], BLOCK_32X128: [5, 7], - BLOCK_128X32: [7, 5] + BLOCK_128X32: [7, 5], }; const transformSizeLog2MapByName = { @@ -876,31 +1045,38 @@ const transformSizeLog2MapByName = { TX_64X32: [6, 5], TX_64X64: [6, 6], TX_16X64: [4, 6], - TX_64X16: [6, 4] -} + TX_64X16: [6, 4], +}; export function padLeft(v, n) { let str = String(v); - while (str.length < n) str = " " + str; + while (str.length < n) str = ' ' + str; return str; } export function log2(n: number): number { switch (n) { - case 1: return 0; - case 2: return 1; - case 4: return 2; - case 8: return 3; - case 16: return 4; - case 32: return 5; - case 64: return 6; + case 1: + return 0; + case 2: + return 1; + case 4: + return 2; + case 8: + return 3; + case 16: + return 4; + case 32: + return 5; + case 64: + return 6; default: unreachable(); } } export function makeBlockSizeLog2MapByValue(blockSizeMap): [number, number][] { - let byValue = []; - for (let key in blockSizeMap) { + const byValue = []; + for (const key in blockSizeMap) { assert(key in blockSizeLog2MapByName, `Key ${key} not found in blockSizeLog2MapByName.`); byValue[blockSizeMap[key]] = blockSizeLog2MapByName[key]; } @@ -908,8 +1084,8 @@ export function makeBlockSizeLog2MapByValue(blockSizeMap): [number, number][] { } export function makeTransformSizeLog2MapByValue(transformSizeMap): [number, number][] { - let byValue = []; - for (let key in transformSizeMap) { + const byValue = []; + for (const key in transformSizeMap) { assert(key in transformSizeLog2MapByName, `Key ${key} not found in transformSizeLog2MapByName.`); byValue[transformSizeMap[key]] = transformSizeLog2MapByName[key]; } @@ -917,9 +1093,9 @@ export function makeTransformSizeLog2MapByValue(transformSizeMap): [number, numb } export function reverseMap(map: { [name: string]: number }): { [id: number]: string } { - let o = []; - for (let k in map) { - o[map[k]] = k + const o = []; + for (const k in map) { + o[map[k]] = k; } return o; } @@ -929,129 +1105,144 @@ export function reverseMap(map: { [name: string]: number }): { [id: number]: str */ export const palette = { blockSize: { - BLOCK_2X2: "#f4ffc3", - BLOCK_2X4: "#622cd8", - BLOCK_4X2: "#deff76", - BLOCK_4X4: "#ff50ed", - BLOCK_4X8: "#808900", - BLOCK_8X4: "#014bb5", - BLOCK_8X8: "#ffbd35", - BLOCK_8X16: "#6895ff", - BLOCK_16X8: "#e62b00", - BLOCK_16X16: "#02b4e1", - BLOCK_16X32: "#a45a00", - BLOCK_32X16: "#00a781", - BLOCK_32X32: "#ff70a6", - BLOCK_32X64: "#00372a", - BLOCK_64X32: "#ff9556", - BLOCK_64X64: "#7a0032" + BLOCK_2X2: '#f4ffc3', + BLOCK_2X4: '#622cd8', + BLOCK_4X2: '#deff76', + BLOCK_4X4: '#ff50ed', + BLOCK_4X8: '#808900', + BLOCK_8X4: '#014bb5', + BLOCK_8X8: '#ffbd35', + BLOCK_8X16: '#6895ff', + BLOCK_16X8: '#e62b00', + BLOCK_16X16: '#02b4e1', + BLOCK_16X32: '#a45a00', + BLOCK_32X16: '#00a781', + BLOCK_32X32: '#ff70a6', + BLOCK_32X64: '#00372a', + BLOCK_64X32: '#ff9556', + BLOCK_64X64: '#7a0032', }, transformSize: { - TX_2X2: "#f4ffc3", - TX_4X4: "#622cd8", - TX_4X8: "#deff76", - TX_4X16: "#ff50ed", - TX_8X4: "#808900", - TX_8X8: "#014bb5", - TX_8X16: "#ffbd35", - TX_8X32: "#6895ff", - TX_16X4: "#e62b00", - TX_16X8: "#02b4e1", - TX_16X16: "#a45a00", - TX_16X32: "#00a781", - TX_32X8: "#ff70a6", - TX_32X16: "#00372a", - TX_32X32: "#ff9556" + TX_2X2: '#f4ffc3', + TX_4X4: '#622cd8', + TX_4X8: '#deff76', + TX_4X16: '#ff50ed', + TX_8X4: '#808900', + TX_8X8: '#014bb5', + TX_8X16: '#ffbd35', + TX_8X32: '#6895ff', + TX_16X4: '#e62b00', + TX_16X8: '#02b4e1', + TX_16X16: '#a45a00', + TX_16X32: '#00a781', + TX_32X8: '#ff70a6', + TX_32X16: '#00372a', + TX_32X32: '#ff9556', }, seg_id: { - 0: "#f4ffc3", - 1: "#622cd8", - 2: "#deff76", - 3: "#ff50ed", - 4: "#6895ff", - 5: "#014bb5", - 6: "#ffbd35", - 7: "#682bff", - 8: "#e62b00", + 0: '#f4ffc3', + 1: '#622cd8', + 2: '#deff76', + 3: '#ff50ed', + 4: '#6895ff', + 5: '#014bb5', + 6: '#ffbd35', + 7: '#682bff', + 8: '#e62b00', }, transformType: { - DCT_DCT: "#f4ffc3", - ADST_DCT: "#622cd8", - DCT_ADST: "#deff76", - ADST_ADST: "#ff50ed", - FLIPADST_DCT: "#808900", - DCT_FLIPADST: "#014bb5", - FLIPADST_FLIPADST: "#ffbd35", - ADST_FLIPADST: "#6895ff", - FLIPADST_ADST: "#e62b00", - IDTX: "#02b4e1", - V_DCT: "#a45a00", - H_DCT: "#00a781", - V_ADST: "#ff70a6", - H_ADST: "#00372a", - V_FLIPADST: "#ff9556", - H_FLIPADST: "#7a0032" + DCT_DCT: '#f4ffc3', + ADST_DCT: '#622cd8', + DCT_ADST: '#deff76', + ADST_ADST: '#ff50ed', + FLIPADST_DCT: '#808900', + DCT_FLIPADST: '#014bb5', + FLIPADST_FLIPADST: '#ffbd35', + ADST_FLIPADST: '#6895ff', + FLIPADST_ADST: '#e62b00', + IDTX: '#02b4e1', + V_DCT: '#a45a00', + H_DCT: '#00a781', + V_ADST: '#ff70a6', + H_ADST: '#00372a', + V_FLIPADST: '#ff9556', + H_FLIPADST: '#7a0032', }, skip: { - SKIP: "#6c0039", - NO_SKIP: "#00d041" + SKIP: '#6c0039', + NO_SKIP: '#00d041', + }, + motionMode: { + SIMPLE_TRANSLATION: '#00d041', + OBMC_CAUSAL: '#6f7dcb', + WARPED_CAUSAL: '#b459c0', + }, + compoundType: { + COMPOUND_AVERAGE: '#468400', + COMPOUND_DISTWTD: '#0164d9', + COMPOUND_DIFFWTD: '#fdb78c', + COMPOUND_WEDGE: '#c9004c', + }, + wedgeType: { + SIGN: '#a4558f', + NO_SIGN: '#9c9c4d', }, predictionMode: { - DC_PRED: "#6c0039", - V_PRED: "#00d041", - H_PRED: "#801cd1", - D45_PRED: "#a0ff78", - D135_PRED: "#ff4ff7", - D113_PRED: "#02c45a", - D157_PRED: "#2d64ff", - D203_PRED: "#91b900", - D67_PRED: "#001d80", - SMOOTH_PRED: "#78ff9f", - SMOOTH_V_PRED: "#08ff9f", - SMOOTH_H_PRED: "#f8ff9f", - PAETH_PRED: "#410065", - NEARESTMV: "#8affe8", - NEARMV: "#ee007d", - ZEROMV: "#01ad84", - NEWMV: "#c00045", - NEWFROMNEARMV: "#6beeff", - NEAREST_NEARESTMV: "#af1b00", - NEAREST_NEARMV: "#00468f", - NEAR_NEARESTMV: "#ff5a3b", - NEAR_NEARMV: "#007e7c", - NEAREST_NEWMV: "#ff696f", - NEW_NEARESTMV: "#006a43", - NEAR_NEWMV: "#b79dff", - NEW_NEARMV: "#b17d00", - ZERO_ZEROMV: "#00041a", - NEW_NEWMV: "#ffa574" + DC_PRED: '#6c0039', + V_PRED: '#00d041', + H_PRED: '#801cd1', + D45_PRED: '#a0ff78', + D135_PRED: '#ff4ff7', + D113_PRED: '#02c45a', + D157_PRED: '#2d64ff', + D203_PRED: '#91b900', + D67_PRED: '#001d80', + SMOOTH_PRED: '#78ff9f', + SMOOTH_V_PRED: '#08ff9f', + SMOOTH_H_PRED: '#f8ff9f', + PAETH_PRED: '#410065', + NEARESTMV: '#8affe8', + NEARMV: '#ee007d', + ZEROMV: '#01ad84', + NEWMV: '#c00045', + NEWFROMNEARMV: '#6beeff', + NEAREST_NEARESTMV: '#af1b00', + NEAREST_NEARMV: '#00468f', + NEAR_NEARESTMV: '#ff5a3b', + NEAR_NEARMV: '#007e7c', + NEAREST_NEWMV: '#ff696f', + NEW_NEARESTMV: '#006a43', + NEAR_NEWMV: '#b79dff', + NEW_NEARMV: '#b17d00', + ZERO_ZEROMV: '#00041a', + NEW_NEWMV: '#ffa574', }, referenceFrame: { - INTRA_FRAME: "#f4ffc3", - LAST_FRAME: "#622cd8", - LAST2_FRAME: "#deff76", - LAST3_FRAME: "#ff50ed", - GOLDEN_FRAME: "#ff50ed", - BWDREF_FRAME: "#808900", - ALTREF_FRAME: "#014bb5" + INTRA_FRAME: '#f4ffc3', + LAST_FRAME: '#622cd8', + LAST2_FRAME: '#deff76', + LAST3_FRAME: '#ff50ed', + GOLDEN_FRAME: '#ff50ed', + BWDREF_FRAME: '#808900', + ALTREF_FRAME: '#014bb5', }, dualFilterType: { - REG_REG: "#c95f3f", - REG_SMOOTH: "#4eb7a0", - REG_SHARP: "#b459c0", - SMOOTH_REG: "#77b84b", - SMOOTH_SMOOTH: "#d0406d", - SMOOTH_SHARP: "#627e3b", - SHARP_REG: "#6f7dcb", - SHARP_SMOOTH: "#c29743", - SHARP_SHARP: "#c06d93" - } -} + REG_REG: '#c95f3f', + REG_SMOOTH: '#4eb7a0', + REG_SHARP: '#b459c0', + SMOOTH_REG: '#77b84b', + SMOOTH_SMOOTH: '#d0406d', + SMOOTH_SHARP: '#627e3b', + SHARP_REG: '#6f7dcb', + SHARP_SMOOTH: '#c29743', + SHARP_SHARP: '#c06d93', + }, +}; export function getColor(name: string, palette = undefined): string { if (name === undefined) { - console.warn("Undefined name in getColor(), make sure ENUMs are exported correctly."); - return "#FF0000"; + console.warn('Undefined name in getColor(), make sure ENUMs are exported correctly.'); + return '#FF0000'; } return (palette && palette[name]) || COLORS[hashString(name) % COLORS.length]; } diff --git a/src/main.tsx b/src/main.tsx index 78c06aa..d65ecdb 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,135 +1,111 @@ -import * as React from "react"; -import * as ReactDOM from "react-dom"; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; -import { LoaderComponent } from "./components/Loader"; -import { PlayerSplitComponent } from "./components/PlayerSplit"; -import { VotingSessionComponent } from "./components/VotingSession"; -import { DownloadComponent } from "./components/Download"; -import { LocalAnalyzerComponent } from "./components/LocalAnalyzer"; +import { LoaderComponent } from './components/Loader'; +import { PlayerSplitComponent } from './components/PlayerSplit'; +import { VotingSessionComponent } from './components/VotingSession'; +import { DownloadComponent } from './components/Download'; +import { LocalAnalyzerComponent } from './components/LocalAnalyzer'; -import {grey} from '@material-ui/core/colors'; -import {createMuiTheme, CssBaseline, PaletteType, ThemeProvider} from "@material-ui/core"; +import { theme } from './theme'; + +import { grey } from '@material-ui/core/colors'; +import { createMuiTheme, CssBaseline, PaletteType, ThemeProvider } from '@material-ui/core'; export function forEachUrlParameter(callback: (key: string, value: string) => void) { let url = window.location.search.substring(1); - url = url.replace(/\/$/, ""); // Replace / at the end that gets inserted by browsers. - let params = {}; + url = url.replace(/\/$/, ''); // Replace / at the end that gets inserted by browsers. + const params = {}; url.split('&').forEach(function (s) { - let t = s.split('='); + const t = s.split('='); callback(t[0], decodeURIComponent(t[1])); }); } export function getUrlParameters(): any { - let params = {}; + const params = {}; forEachUrlParameter((key, value) => { params[key] = value; }); return params; -}; +} -let parameters = getUrlParameters(); -let decoder = parameters.decoder; -let file = parameters.file; -let playbackFrameRate = parameters.playbackFrameRate; -let layers = parameters.layers; -let maxFrames = parameters.maxFrames; -let local = parameters.local | 0; -let blind = parameters.blind | 0; -let split = parameters.split | 0; -let bench = parameters.bench | 0; -let download = parameters.download | 0; -let showVoteResult = parameters.showVoteResult | 0; -let player = parameters.player | 0; -let vote = parameters.vote; -let voteDescription = parameters.voteDescription || ""; -let benchmark = parameters.benchmark | 0; +const parameters = getUrlParameters(); +const decoder = parameters.decoder; +const file = parameters.file; +const playbackFrameRate = parameters.playbackFrameRate; +const layers = parameters.layers; +const maxFrames = parameters.maxFrames; +const local = parameters.local | 0; +const blind = parameters.blind | 0; +const split = parameters.split | 0; +const bench = parameters.bench | 0; +const download = parameters.download | 0; +const showVoteResult = parameters.showVoteResult | 0; +const player = parameters.player | 0; +const vote = parameters.vote; +const voteDescription = parameters.voteDescription || ''; +const benchmark = parameters.benchmark | 0; /** * Extracts decoder / file pairs from the url parameter string. */ -function getDecoderVideoUrls(): {decoderUrl: string, videoUrl: string, decoderName: string} [] { +function getDecoderVideoUrls(): { decoderUrl: string; videoUrl: string; decoderName: string }[] { let currentDecoderUrl = null; let currentDecoderName = null; - let currentUrlPrefix = ""; - let pairs = []; + let currentUrlPrefix = ''; + const pairs = []; forEachUrlParameter((key, value) => { - if (key == "decoder" || key == "d") { + if (key == 'decoder' || key == 'd') { currentDecoderUrl = value; - } else if (key == "decoderName") { + } else if (key == 'decoderName') { currentDecoderName = value; - } else if (key == "prefix" || key == "p") { + } else if (key == 'prefix' || key == 'p') { currentUrlPrefix = value; - } else if (key == "file" || key == "f") { + } else if (key == 'file' || key == 'f') { pairs.push({ decoderUrl: currentUrlPrefix + currentDecoderUrl, videoUrl: currentUrlPrefix + value, - decoderName: currentDecoderName + decoderName: currentDecoderName, }); } }); return pairs; } -let pairs = getDecoderVideoUrls(); - -let overrideTheme = { - palette: { - type: 'dark' as PaletteType, - accent1Color: "red" - }, - tableRow: { - height: 24 - }, - tableRowColumn: { - height: 24, - spacing: 4 - }, - tableHeaderColumn: { - height: 32, - spacing: 4 - }, - toolbar: { - backgroundColor: grey[900] - }, - tabs: { - backgroundColor: grey[800], - textColor: grey[100], - selectedTextColor: grey[200] - }, - table: { - backgroundColor: grey[900] - } -}; - -let theme = createMuiTheme(overrideTheme); +const pairs = getDecoderVideoUrls(); if (player || vote) { - let videos = (vote || '').split(",").map(x => { - return x.split(":").map(y => pairs[y|0]); + const videos = (vote || '').split(',').map((x) => { + return x.split(':').map((y) => pairs[y | 0]); }); ReactDOM.render( - + , - document.getElementById("analyzer-app") + document.getElementById('analyzer-app'), ); } else if (local || pairs.length === 0) { ReactDOM.render( - + , - document.getElementById("analyzer-app") + document.getElementById('analyzer-app'), ); } else if (download) { ReactDOM.render( - + , - document.getElementById("analyzer-app") + document.getElementById('analyzer-app'), ); } else { ReactDOM.render( @@ -142,8 +118,9 @@ if (player || vote) { maxFrames={maxFrames} blind={blind} split={split} - bench={bench}/> + bench={bench} + /> , - document.getElementById("analyzer-app") + document.getElementById('analyzer-app'), ); } diff --git a/src/theme.ts b/src/theme.ts new file mode 100644 index 0000000..034d9b5 --- /dev/null +++ b/src/theme.ts @@ -0,0 +1,33 @@ +import { createMuiTheme, PaletteType } from '@material-ui/core'; +import { grey } from '@material-ui/core/colors'; + +const overrideTheme = { + palette: { + type: 'dark' as PaletteType, + accent1Color: 'red', + }, + tableRow: { + height: 24, + }, + tableRowColumn: { + height: 24, + spacing: 4, + }, + tableHeaderColumn: { + height: 32, + spacing: 4, + }, + toolbar: { + backgroundColor: grey[900], + }, + tabs: { + backgroundColor: grey[800], + textColor: grey[100], + selectedTextColor: grey[200], + }, + table: { + backgroundColor: grey[900], + }, +}; + +export const theme = createMuiTheme(overrideTheme); diff --git a/src/worker.ts b/src/worker.ts index aecd9ba..3cdc0b0 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,22 +1,29 @@ -declare var importScripts; -declare var DecoderModule; +declare let importScripts; +declare let DecoderModule; -function assert(c: boolean, message: string = "") { +function assert(c: boolean, message = '') { if (!c) { throw new Error(message); } } -var decoderPathPrefix = ""; +enum ImageType { + Image, + ScaleGrain, + Grain, + OldImage, +} + +let decoderPathPrefix = ''; onmessage = function (e) { // console.log("Worker: " + e.data.command); switch (e.data.command) { - case "load": + case 'load': try { - let payload = e.data.payload; - let path = payload[0]; - decoderPathPrefix = path.substring(0, path.lastIndexOf("/") + 1); + const payload = e.data.payload; + const path = payload[0]; + decoderPathPrefix = path.substring(0, path.lastIndexOf('/') + 1); importScripts.apply(self, payload); load(payload[0], (nativeModule) => { native = nativeModule; @@ -30,40 +37,46 @@ onmessage = function (e) { } else if (native._get_codec_build_config) { buildConfig = native.UTF8ToString(native._get_codec_build_config()); } else { - buildConfig = "N/A"; + buildConfig = 'N/A'; } - postMessage({ - command: "loadResult", - payload: { - buildConfig + postMessage( + { + command: 'loadResult', + payload: { + buildConfig, + }, + id: e.data.id, }, - id: e.data.id - }, undefined); + undefined, + ); }); } catch (x) { - postMessage({ - command: "loadResult", - payload: false, - id: e.data.id - }, undefined); + postMessage( + { + command: 'loadResult', + payload: false, + id: e.data.id, + }, + undefined, + ); } break; - case "readFrame": + case 'readFrame': readFrame(e); break; - case "setLayers": + case 'setLayers': setLayers(e); break; - case "openFileBytes": + case 'openFileBytes': openFileBytes(e.data.payload); break; - case "releaseFrameBuffers": + case 'releaseFrameBuffers': releaseFrameBuffer(e.data.payload.Y); releaseFrameBuffer(e.data.payload.U); releaseFrameBuffer(e.data.payload.V); break; } -} +}; interface Native { _read_frame(): number; @@ -84,6 +97,8 @@ interface Native { _set_compress(compress: number): number; _get_codec_build_config(): number; _get_aom_codec_build_config(): number; // Legacy for AV1 + _get_grain_values(pli: number); + _get_old_plane(pli: number); FS: any; HEAPU8: Uint8Array; UTF8ToString(p: number): string; @@ -91,52 +106,54 @@ interface Native { let native: Native = null; let frameRate = 0; -let buffer: Uint8Array = null; +const buffer: Uint8Array = null; let json = null; function load(path: string, ready: (native: any) => void) { - var Module = { + const Module = { locateFile: function (path) { return decoderPathPrefix + path; }, noExitRuntime: true, noInitialRun: true, preRun: [], - postRun: [function () { - // console.info(`Loaded Decoder in Worker`); - }], - memoryInitializerPrefixURL: "bin/", + postRun: [ + function () { + // console.info(`Loaded Decoder in Worker`); + }, + ], + memoryInitializerPrefixURL: 'bin/', arguments: ['input.ivf', 'output.raw'], on_frame_decoded_json: function (p) { - let s = ""; - if (typeof TextDecoder != "undefined") { - let m = (Module as any).HEAP8; + let s = ''; + if (typeof TextDecoder != 'undefined') { + const m = (Module as any).HEAP8; let e = p; while (m[e] != 0) { e++; } - let textDecoder = new TextDecoder("utf-8"); + const textDecoder = new TextDecoder('utf-8'); s = textDecoder.decode(m.subarray(p, e)); } else { s = (Module as any).UTF8ToString(p); } - json = JSON.parse("[" + s + "null]"); + json = JSON.parse('[' + s + 'null]'); }, onRuntimeInitialized: function () { ready(Module); - } + }, }; - DecoderModule(Module) + DecoderModule(Module); } function openFileBytes(buffer: Uint8Array) { - frameRate = buffer[16] | buffer[17] << 24 | buffer[18] << 16 | buffer[19] << 24; + frameRate = buffer[16] | (buffer[17] << 24) | (buffer[18] << 16) | (buffer[19] << 24); buffer = buffer; - native.FS.writeFile("/tmp/input.ivf", buffer, { encoding: "binary" }); + native.FS.writeFile('/tmp/input.ivf', buffer, { encoding: 'binary' }); native._open_file(); } -var bufferPool: ArrayBuffer [] = []; +const bufferPool: ArrayBuffer[] = []; function releaseFrameBuffer(buffer: ArrayBuffer) { if (bufferPool.length < 64) { @@ -157,9 +174,9 @@ function getReleasedBuffer(byteLength: number) { const AOM_IMG_FMT_PLANAR = 0x100; const AOM_IMG_FMT_HIGHBITDEPTH = 0x800; const AOM_IMG_FMT_I422 = AOM_IMG_FMT_PLANAR | 5; -const AOM_IMG_FMT_I42216 = AOM_IMG_FMT_I422 | AOM_IMG_FMT_HIGHBITDEPTH +const AOM_IMG_FMT_I42216 = AOM_IMG_FMT_I422 | AOM_IMG_FMT_HIGHBITDEPTH; const AOM_IMG_FMT_I444 = AOM_IMG_FMT_PLANAR | 6; -const AOM_IMG_FMT_I44416 = AOM_IMG_FMT_I444 | AOM_IMG_FMT_HIGHBITDEPTH +const AOM_IMG_FMT_I44416 = AOM_IMG_FMT_I444 | AOM_IMG_FMT_HIGHBITDEPTH; function getImageFormat() { // TODO: Just call |native._get_image_format| directly. Older analyzer builds may not have @@ -167,15 +184,30 @@ function getImageFormat() { return native._get_image_format ? native._get_image_format() : AOM_IMG_FMT_PLANAR; } -function readPlane(plane) { - let p = native._get_plane(plane); - let HEAPU8 = native.HEAPU8; +function readPlane(plane, type: ImageType = ImageType.Image) { + let p = 0; + + switch (type) { + case ImageType.Image: + p = native._get_plane(plane); + break; + case ImageType.Grain: + p = native._get_grain_values(plane); + break; + case ImageType.ScaleGrain: + p = native._get_grain_values(plane + 3); + break; + case ImageType.OldImage: + p = native._get_old_plane(plane); + break; + } + const HEAPU8 = native.HEAPU8; let stride = native._get_plane_stride(plane); let depth = native._get_bit_depth(); let width = native._get_frame_width(); let height = native._get_frame_height(); - let fmt = getImageFormat(); - let hbd = fmt & AOM_IMG_FMT_HIGHBITDEPTH; + const fmt = getImageFormat(); + const hbd = fmt & AOM_IMG_FMT_HIGHBITDEPTH; if (hbd) { stride >>= 1; } @@ -193,12 +225,12 @@ function readPlane(plane) { } width >>= xdec; height >>= ydec; - let byteLength = height * width; - var buffer = getReleasedBuffer(byteLength); + const byteLength = height * width; + let buffer = getReleasedBuffer(byteLength); if (buffer && !hbd) { // Copy into released buffer. - let tmp = new Uint8Array(buffer); + const tmp = new Uint8Array(buffer); if (stride === width) { tmp.set(HEAPU8.subarray(p, p + byteLength)); } else { @@ -208,12 +240,12 @@ function readPlane(plane) { } } } else if (hbd) { - let tmpBuffer = buffer ? new Uint8Array(buffer) : new Uint8Array(byteLength); + const tmpBuffer = buffer ? new Uint8Array(buffer) : new Uint8Array(byteLength); if (depth == 10) { // Convert to 8 bit depth. for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { - let offset = y * (stride << 1) + (x << 1); + const offset = y * (stride << 1) + (x << 1); tmpBuffer[y * width + x] = (HEAPU8[p + offset] + (HEAPU8[p + offset + 1] << 8)) >> 2; } } @@ -221,7 +253,7 @@ function readPlane(plane) { // Unpack to 8 bit depth. for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { - let offset = y * (stride << 1) + (x << 1); + const offset = y * (stride << 1) + (x << 1); tmpBuffer[y * width + x] = HEAPU8[p + offset]; } } @@ -232,7 +264,7 @@ function readPlane(plane) { if (stride === width) { buffer = HEAPU8.slice(p, p + byteLength).buffer; } else { - let tmp = new Uint8Array(byteLength); + const tmp = new Uint8Array(byteLength); for (let i = 0; i < height; i++) { tmp.set(HEAPU8.subarray(p, p + width), i * width); p += stride; @@ -247,45 +279,81 @@ function readPlane(plane) { width, height, xdec, - ydec + ydec, + }; +} + +function readGrainImage() { + return { + hashCode: (Math.random() * 10000000) | 0, + Y: readPlane(0, ImageType.Grain), + U: readPlane(1, ImageType.Grain), + V: readPlane(2, ImageType.Grain), + }; +} + +function readOldPlaneImage() { + return { + hashCode: (Math.random() * 10000000) | 0, + Y: readPlane(0, ImageType.OldImage), + U: readPlane(1, ImageType.OldImage), + V: readPlane(2, ImageType.OldImage), + }; +} + +function readScaledGrainImage() { + return { + hashCode: (Math.random() * 10000000) | 0, + Y: readPlane(0, ImageType.ScaleGrain), + U: readPlane(1, ImageType.ScaleGrain), + V: readPlane(2, ImageType.ScaleGrain), }; } function readImage() { return { - hashCode: Math.random() * 1000000 | 0, - Y: readPlane(0), - U: readPlane(1), - V: readPlane(2) - } + hashCode: (Math.random() * 1000000) | 0, + Y: readPlane(0, ImageType.Image), + U: readPlane(1, ImageType.Image), + V: readPlane(2, ImageType.Image), + }; } function readFrame(e) { - let s = performance.now(); + const s = performance.now(); if (native._read_frame() != 0) { - postMessage({ - command: "readFrameResult", - payload: { json: null, decodeTime: performance.now() - s }, - id: e.data.id - }, undefined); + postMessage( + { + command: 'readFrameResult', + payload: { json: null, decodeTime: performance.now() - s }, + id: e.data.id, + }, + undefined, + ); return null; } let image = null; + let grainImage = null; + let scaledGrainImage = null; + let oldImage = null; if (e.data.shouldReadImageData) { image = readImage(); + grainImage = readGrainImage(); + oldImage = readOldPlaneImage(); + scaledGrainImage = readScaledGrainImage(); } - self.postMessage({ - command: "readFrameResult", - payload: { json, image, decodeTime: performance.now() - s }, - id: e.data.id - }, image ? [ - image.Y.buffer, - image.U.buffer, - image.V.buffer - ] : undefined as any); - assert(image.Y.buffer.byteLength === 0 && - image.U.buffer.byteLength === 0 && - image.V.buffer.byteLength === 0, "Buffers must be transferred."); + self.postMessage( + { + command: 'readFrameResult', + payload: { json, image, decodeTime: performance.now() - s, grainImage, scaledGrainImage, oldImage }, + id: e.data.id, + }, + image ? [image.Y.buffer, image.U.buffer, image.V.buffer] : (undefined as any), + ); + assert( + image.Y.buffer.byteLength === 0 && image.U.buffer.byteLength === 0 && image.V.buffer.byteLength === 0, + 'Buffers must be transferred.', + ); } function setLayers(e) { diff --git a/tsconfig.json b/tsconfig.json index 2c55d30..1db7255 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,14 @@ "allowSyntheticDefaultImports": true, "lib": ["dom", "es6"] }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2020, // Allows for the parsing of modern ECMAScript features + "sourceType": "module", // Allows for the use of imports + "ecmaFeatures": { + "jsx": true // Allows for the parsing of JSX + } + }, "exclude": [ "node_modules", "release_builds"