diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index e5314a2..e14b0ea 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -1,10 +1,18 @@ module.exports = { + root: true, + parser: '@typescript-eslint/parser', env: { browser: true, es6: true, }, - ignorePatterns: ['**/*.ts', '**/*.tsx'], - extends: ['eslint:recommended', 'plugin:react/recommended'], + ignorePatterns: ['node_modules', 'dist'], + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + ], globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly', @@ -19,17 +27,20 @@ module.exports = { ecmaVersion: 2018, sourceType: 'module', }, - plugins: ['react', 'react-hooks'], + plugins: ['react', 'react-hooks', '@typescript-eslint'], rules: { - 'no-unused-vars': 'warn', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'warn', 'react/display-name': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', }, settings: { react: { version: 'detect', }, }, - parser: 'babel-eslint', + // parser: 'babel-eslint', overrides: [ Object.assign(require('eslint-plugin-jest').configs.recommended, { files: ['**/*.test.js'], @@ -43,5 +54,11 @@ module.exports = { } ), }), + { + files: ['**/*.js'], + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, + }, ], } diff --git a/ui/babel.config.js b/ui/babel.config.js index 43e0db3..5cdd249 100644 --- a/ui/babel.config.js +++ b/ui/babel.config.js @@ -2,7 +2,7 @@ module.exports = function (api) { const isTest = api.env('test') const isProduction = api.env('NODE_ENV') == 'production' - let presets = ['@babel/preset-react'] + let presets = ['@babel/preset-typescript', '@babel/preset-react'] let plugins = [] if (isTest) { diff --git a/ui/build.mjs b/ui/build.mjs index 3cab176..42f4006 100644 --- a/ui/build.mjs +++ b/ui/build.mjs @@ -23,7 +23,7 @@ const esbuildOptions = { entryPoints: ['src/index.tsx'], plugins: [ babel({ - filter: /photoview\/ui\/src\/.*\.js$/, + filter: /photoview\/ui\/src\/.*\.(js|tsx?)$/, }), ], publicPath: process.env.UI_PUBLIC_URL || '/', @@ -66,25 +66,25 @@ if (watchMode) { open: false, }) - bs.watch('src/**/*.js').on('change', async args => { + bs.watch('src/**/*.@(js|tsx|ts)').on('change', async args => { console.log('reloading', args) builderPromise = (await builderPromise).rebuild() bs.reload(args) }) } else { - const esbuildPromise = esbuild - .build(esbuildOptions) - .then(() => console.log('esbuild done')) + const build = async () => { + await esbuild.build(esbuildOptions) - const workboxPromise = workboxBuild - .generateSW({ + console.log('esbuild done') + + await workboxBuild.generateSW({ globDirectory: 'dist/', globPatterns: ['**/*.{png,svg,woff2,ttf,eot,woff,js,ico,html,json,css}'], swDest: 'dist/service-worker.js', }) - .then(() => console.log('workbox done')) - Promise.all([esbuildPromise, workboxPromise]).then(() => + console.log('workbox done') console.log('build complete') - ) + } + build() } diff --git a/ui/package-lock.json b/ui/package-lock.json index ef8a704..2298621 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -15,6 +15,7 @@ "@babel/plugin-transform-runtime": "^7.13.15", "@babel/preset-env": "^7.13.15", "@babel/preset-react": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", "babel-eslint": "^10.1.0", "babel-jest": "^26.6.3", "babel-plugin-graphql-tag": "^3.2.0", @@ -60,6 +61,10 @@ "@types/react-helmet": "^6.1.1", "@types/react-router-dom": "^5.1.7", "@types/styled-components": "^5.1.9", + "@types/url-join": "^4.0.0", + "@typescript-eslint/eslint-plugin": "^4.21.0", + "@typescript-eslint/parser": "^4.21.0", + "eslint-config-prettier": "^8.1.0", "husky": "^6.0.0", "jest": "^26.6.3", "lint-staged": "^10.5.4", @@ -769,6 +774,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz", + "integrity": "sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", @@ -1192,6 +1208,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz", + "integrity": "sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-typescript": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", @@ -1330,6 +1359,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz", + "integrity": "sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", + "@babel/plugin-transform-typescript": "^7.13.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.13.10", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", @@ -2344,31 +2386,31 @@ } }, "node_modules/@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "dependencies": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" }, "engines": { "node": ">= 8" } }, - "node_modules/@nodelib/fs.scandir/node_modules/@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "dependencies": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" }, "engines": { @@ -2882,6 +2924,12 @@ "@types/jest": "*" } }, + "node_modules/@types/url-join": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/url-join/-/url-join-4.0.0.tgz", + "integrity": "sha512-awrJu8yML4E/xTwr2EMatC+HBnHGoDxc2+ImA9QyeUELI1S7dOCIZcyjki1rkwoA8P2D2NVgLAJLjnclkdLtAw==", + "dev": true + }, "node_modules/@types/yargs": { "version": "15.0.9", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", @@ -2900,64 +2948,164 @@ "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.1.tgz", "integrity": "sha512-wmk0xQI6Yy7Fs/il4EpOcflG4uonUpYGqvZARESLc2oy4u69fkatFLbJOeW4Q6awO15P4rduAe6xkwHevpXcUQ==" }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.6.0.tgz", - "integrity": "sha512-pnh6Beh2/4xjJVNL+keP49DFHk3orDHHFylSp3WEjtgW3y1U+6l+jNnJrGlbs6qhAz5z96aFmmbUyKhunXKvKw==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz", + "integrity": "sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ==", + "devOptional": true, "dependencies": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.6.0", - "@typescript-eslint/types": "4.6.0", - "@typescript-eslint/typescript-estree": "4.6.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.6.0.tgz", - "integrity": "sha512-uZx5KvStXP/lwrMrfQQwDNvh2ppiXzz5TmyTVHb+5TfZ3sUP7U1onlz3pjoWrK9konRyFe1czyxObWTly27Ang==", - "dependencies": { - "@typescript-eslint/types": "4.6.0", - "@typescript-eslint/visitor-keys": "4.6.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.6.0.tgz", - "integrity": "sha512-5FAgjqH68SfFG4UTtIFv+rqYJg0nLjfkjD0iv+5O27a0xEeNZ5rZNDvFGZDizlCD1Ifj7MAbSW2DPMrf0E9zjA==", - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.0.tgz", - "integrity": "sha512-s4Z9qubMrAo/tw0CbN0IN4AtfwuehGXVZM0CHNMdfYMGBDhPdwTEpBrecwhP7dRJu6d9tT9ECYNaWDHvlFSngA==", - "dependencies": { - "@typescript-eslint/types": "4.6.0", - "@typescript-eslint/visitor-keys": "4.6.0", + "@typescript-eslint/experimental-utils": "4.21.0", + "@typescript-eslint/scope-manager": "4.21.0", "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", + "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", + "regexpp": "^3.0.0", "semver": "^7.3.2", "tsutils": "^3.17.1" }, "engines": { "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "devOptional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.21.0.tgz", + "integrity": "sha512-cEbgosW/tUFvKmkg3cU7LBoZhvUs+ZPVM9alb25XvR0dal4qHL3SiUqHNrzoWSxaXA9gsifrYrS1xdDV6w/gIA==", + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.21.0", + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/typescript-estree": "4.21.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.21.0.tgz", + "integrity": "sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA==", + "devOptional": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.21.0", + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/typescript-estree": "4.21.0", + "debug": "^4.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz", + "integrity": "sha512-kfOjF0w1Ix7+a5T1knOw00f7uAP9Gx44+OEsNQi0PvvTPLYeXJlsCJ4tYnDj5PQEYfpcgOH5yBlw7K+UEI9Agw==", + "dependencies": { + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/visitor-keys": "4.21.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.21.0.tgz", + "integrity": "sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w==", + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz", + "integrity": "sha512-ZD3M7yLaVGVYLw4nkkoGKumb7Rog7QID9YOWobFDMQKNl+vPxqVIW/uDk+MDeGc+OHcoG2nJ2HphwiPNajKw3w==", + "dependencies": { + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/visitor-keys": "4.21.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" }, @@ -2966,15 +3114,19 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.0.tgz", - "integrity": "sha512-38Aa9Ztl0XyFPVzmutHXqDMCu15Xx8yKvUo38Gu3GhsuckCh3StPI5t2WIO9LHEsOH7MLmlGfKUisU8eW1Sjhg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz", + "integrity": "sha512-dH22dROWGi5Z6p+Igc8bLVLmwy7vEe8r+8c+raPQU0LxgogPUrRAtRGtvBWmlr9waTu3n+QLt/qrS/hWzk1x5w==", "dependencies": { - "@typescript-eslint/types": "4.6.0", + "@typescript-eslint/types": "4.21.0", "eslint-visitor-keys": "^2.0.0" }, "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { @@ -5173,6 +5325,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", + "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-plugin-jest": { "version": "24.3.5", "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.3.5.tgz", @@ -5934,6 +6098,75 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-glob/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/fast-glob/node_modules/micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/fast-glob/node_modules/to-regex-range": { + "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==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/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", @@ -5945,9 +6178,9 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "node_modules/fastq": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", - "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", "dependencies": { "reusify": "^1.0.4" } @@ -6291,9 +6524,9 @@ } }, "node_modules/globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -6304,52 +6537,9 @@ }, "engines": { "node": ">=10" - } - }, - "node_modules/globby/node_modules/@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/globby/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/globby/node_modules/fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/globby/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globby/node_modules/ignore": { @@ -6360,37 +6550,6 @@ "node": ">= 4" } }, - "node_modules/globby/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/globby/node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/globby/node_modules/to-regex-range": { - "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==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -9981,6 +10140,17 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/lz-string": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", @@ -10773,11 +10943,14 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", "engines": { "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pirates": { @@ -10981,6 +11154,25 @@ "node": ">=0.6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/quickselect": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", @@ -11639,9 +11831,26 @@ } }, "node_modules/run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } }, "node_modules/rw": { "version": "1.3.3", @@ -13894,6 +14103,11 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/yaml": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", @@ -14543,6 +14757,14 @@ "@babel/helper-plugin-utils": "^7.12.13" } }, + "@babel/plugin-syntax-typescript": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz", + "integrity": "sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, "@babel/plugin-transform-arrow-functions": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", @@ -14863,6 +15085,16 @@ "@babel/helper-plugin-utils": "^7.12.13" } }, + "@babel/plugin-transform-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz", + "integrity": "sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-typescript": "^7.12.13" + } + }, "@babel/plugin-transform-unicode-escapes": { "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", @@ -14988,6 +15220,16 @@ "@babel/plugin-transform-react-pure-annotations": "^7.12.1" } }, + "@babel/preset-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz", + "integrity": "sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw==", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", + "@babel/plugin-transform-typescript": "^7.13.0" + } + }, "@babel/runtime": { "version": "7.13.10", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", @@ -15825,27 +16067,25 @@ "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==" }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" - }, - "dependencies": { - "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" - } } }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" + }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, @@ -16298,6 +16538,12 @@ "@types/jest": "*" } }, + "@types/url-join": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/url-join/-/url-join-4.0.0.tgz", + "integrity": "sha512-awrJu8yML4E/xTwr2EMatC+HBnHGoDxc2+ImA9QyeUELI1S7dOCIZcyjki1rkwoA8P2D2NVgLAJLjnclkdLtAw==", + "dev": true + }, "@types/yargs": { "version": "15.0.9", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.9.tgz", @@ -16316,61 +16562,102 @@ "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.1.tgz", "integrity": "sha512-wmk0xQI6Yy7Fs/il4EpOcflG4uonUpYGqvZARESLc2oy4u69fkatFLbJOeW4Q6awO15P4rduAe6xkwHevpXcUQ==" }, - "@typescript-eslint/experimental-utils": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.6.0.tgz", - "integrity": "sha512-pnh6Beh2/4xjJVNL+keP49DFHk3orDHHFylSp3WEjtgW3y1U+6l+jNnJrGlbs6qhAz5z96aFmmbUyKhunXKvKw==", + "@typescript-eslint/eslint-plugin": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.21.0.tgz", + "integrity": "sha512-FPUyCPKZbVGexmbCFI3EQHzCZdy2/5f+jv6k2EDljGdXSRc0cKvbndd2nHZkSLqCNOPk0jB6lGzwIkglXcYVsQ==", + "devOptional": true, "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.6.0", - "@typescript-eslint/types": "4.6.0", - "@typescript-eslint/typescript-estree": "4.6.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } - }, - "@typescript-eslint/scope-manager": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.6.0.tgz", - "integrity": "sha512-uZx5KvStXP/lwrMrfQQwDNvh2ppiXzz5TmyTVHb+5TfZ3sUP7U1onlz3pjoWrK9konRyFe1czyxObWTly27Ang==", - "requires": { - "@typescript-eslint/types": "4.6.0", - "@typescript-eslint/visitor-keys": "4.6.0" - } - }, - "@typescript-eslint/types": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.6.0.tgz", - "integrity": "sha512-5FAgjqH68SfFG4UTtIFv+rqYJg0nLjfkjD0iv+5O27a0xEeNZ5rZNDvFGZDizlCD1Ifj7MAbSW2DPMrf0E9zjA==" - }, - "@typescript-eslint/typescript-estree": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.0.tgz", - "integrity": "sha512-s4Z9qubMrAo/tw0CbN0IN4AtfwuehGXVZM0CHNMdfYMGBDhPdwTEpBrecwhP7dRJu6d9tT9ECYNaWDHvlFSngA==", - "requires": { - "@typescript-eslint/types": "4.6.0", - "@typescript-eslint/visitor-keys": "4.6.0", + "@typescript-eslint/experimental-utils": "4.21.0", + "@typescript-eslint/scope-manager": "4.21.0", "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", + "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", + "regexpp": "^3.0.0", "semver": "^7.3.2", "tsutils": "^3.17.1" }, "dependencies": { "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "devOptional": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.21.0.tgz", + "integrity": "sha512-cEbgosW/tUFvKmkg3cU7LBoZhvUs+ZPVM9alb25XvR0dal4qHL3SiUqHNrzoWSxaXA9gsifrYrS1xdDV6w/gIA==", + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.21.0", + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/typescript-estree": "4.21.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.21.0.tgz", + "integrity": "sha512-eyNf7QmE5O/l1smaQgN0Lj2M/1jOuNg2NrBm1dqqQN0sVngTLyw8tdCbih96ixlhbF1oINoN8fDCyEH9SjLeIA==", + "devOptional": true, + "requires": { + "@typescript-eslint/scope-manager": "4.21.0", + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/typescript-estree": "4.21.0", + "debug": "^4.1.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.21.0.tgz", + "integrity": "sha512-kfOjF0w1Ix7+a5T1knOw00f7uAP9Gx44+OEsNQi0PvvTPLYeXJlsCJ4tYnDj5PQEYfpcgOH5yBlw7K+UEI9Agw==", + "requires": { + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/visitor-keys": "4.21.0" + } + }, + "@typescript-eslint/types": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.21.0.tgz", + "integrity": "sha512-+OQaupjGVVc8iXbt6M1oZMwyKQNehAfLYJJ3SdvnofK2qcjfor9pEM62rVjBknhowTkh+2HF+/KdRAc/wGBN2w==" + }, + "@typescript-eslint/typescript-estree": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.21.0.tgz", + "integrity": "sha512-ZD3M7yLaVGVYLw4nkkoGKumb7Rog7QID9YOWobFDMQKNl+vPxqVIW/uDk+MDeGc+OHcoG2nJ2HphwiPNajKw3w==", + "requires": { + "@typescript-eslint/types": "4.21.0", + "@typescript-eslint/visitor-keys": "4.21.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "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.6.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.0.tgz", - "integrity": "sha512-38Aa9Ztl0XyFPVzmutHXqDMCu15Xx8yKvUo38Gu3GhsuckCh3StPI5t2WIO9LHEsOH7MLmlGfKUisU8eW1Sjhg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.21.0.tgz", + "integrity": "sha512-dH22dROWGi5Z6p+Igc8bLVLmwy7vEe8r+8c+raPQU0LxgogPUrRAtRGtvBWmlr9waTu3n+QLt/qrS/hWzk1x5w==", "requires": { - "@typescript-eslint/types": "4.6.0", + "@typescript-eslint/types": "4.21.0", "eslint-visitor-keys": "^2.0.0" }, "dependencies": { @@ -18296,6 +18583,13 @@ } } }, + "eslint-config-prettier": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz", + "integrity": "sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==", + "dev": true, + "requires": {} + }, "eslint-plugin-jest": { "version": "24.3.5", "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.3.5.tgz", @@ -18720,6 +19014,59 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "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" + } + }, + "to-regex-range": { + "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==", + "requires": { + "is-number": "^7.0.0" + } + } + } + }, "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", @@ -18731,9 +19078,9 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fastq": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", - "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "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" } @@ -19005,9 +19352,9 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globby": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", - "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -19017,66 +19364,10 @@ "slash": "^3.0.0" }, "dependencies": { - "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==" - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "to-regex-range": { - "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==", - "requires": { - "is-number": "^7.0.0" - } } } }, @@ -21898,6 +22189,14 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "lz-string": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", @@ -22521,9 +22820,9 @@ "dev": true }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==" }, "pirates": { "version": "4.0.1", @@ -22682,6 +22981,11 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "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==" + }, "quickselect": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", @@ -23227,9 +23531,12 @@ "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==" }, "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + "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" + } }, "rw": { "version": "1.3.3", @@ -25118,6 +25425,11 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "yaml": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", diff --git a/ui/package.json b/ui/package.json index 24d6612..04be30a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,6 +15,7 @@ "@babel/plugin-transform-runtime": "^7.13.15", "@babel/preset-env": "^7.13.15", "@babel/preset-react": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", "babel-eslint": "^10.1.0", "babel-jest": "^26.6.3", "babel-plugin-graphql-tag": "^3.2.0", @@ -56,7 +57,7 @@ "start": "node --experimental-modules build.mjs watch", "build": "NODE_ENV=production node --experimental-modules build.mjs", "test": "npm run lint && npm run jest", - "lint": "eslint ./src --max-warnings 0 --cache", + "lint": "eslint ./src --max-warnings 0 --cache --config .eslintrc.js", "jest": "jest", "genSchemaTypes": "npx apollo client:codegen --target=typescript", "prepare": "(cd .. && npx husky install)" @@ -69,6 +70,10 @@ "@types/react-helmet": "^6.1.1", "@types/react-router-dom": "^5.1.7", "@types/styled-components": "^5.1.9", + "@types/url-join": "^4.0.0", + "@typescript-eslint/eslint-plugin": "^4.21.0", + "@typescript-eslint/parser": "^4.21.0", + "eslint-config-prettier": "^8.1.0", "husky": "^6.0.0", "jest": "^26.6.3", "lint-staged": "^10.5.4", diff --git a/ui/src/@types/index.d.ts b/ui/src/@types/index.d.ts new file mode 100644 index 0000000..d41bc37 --- /dev/null +++ b/ui/src/@types/index.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const src: string + export default src +} diff --git a/ui/src/Layout.tsx b/ui/src/Layout.tsx index fe11940..bcb2241 100644 --- a/ui/src/Layout.tsx +++ b/ui/src/Layout.tsx @@ -80,7 +80,7 @@ const SideButtonLink = styled(NavLink)` ` type SideButtonProps = { - children: any + children: ReactChild | ReactChild[] to: string exact: boolean } diff --git a/ui/src/apolloClient.js b/ui/src/apolloClient.ts similarity index 76% rename from ui/src/apolloClient.js rename to ui/src/apolloClient.ts index c353ad9..c239aa8 100644 --- a/ui/src/apolloClient.js +++ b/ui/src/apolloClient.ts @@ -4,6 +4,8 @@ import { split, ApolloLink, HttpLink, + ServerError, + FieldMergeFunction, } from '@apollo/client' import { getMainDefinition } from '@apollo/client/utilities' import { onError } from '@apollo/client/link/error' @@ -12,6 +14,7 @@ import { WebSocketLink } from '@apollo/client/link/ws' import urlJoin from 'url-join' import { clearTokenCookie } from './helpers/authentication' import { MessageState } from './components/messages/Messages' +import { Message } from './components/messages/SubscriptionsHook' export const GRAPHQL_ENDPOINT = process.env.PHOTOVIEW_API_ENDPOINT ? urlJoin(process.env.PHOTOVIEW_API_ENDPOINT, '/graphql') @@ -26,12 +29,12 @@ console.log('GRAPHQL ENDPOINT', GRAPHQL_ENDPOINT) const apiProtocol = new URL(GRAPHQL_ENDPOINT).protocol -let websocketUri = new URL(GRAPHQL_ENDPOINT) +const websocketUri = new URL(GRAPHQL_ENDPOINT) websocketUri.protocol = apiProtocol === 'https:' ? 'wss:' : 'ws:' const wsLink = new WebSocketLink({ - uri: websocketUri, - credentials: 'include', + uri: websocketUri.toString(), + // credentials: 'include', }) const link = split( @@ -48,7 +51,7 @@ const link = split( ) const linkError = onError(({ graphQLErrors, networkError }) => { - let errorMessages = [] + const errorMessages = [] if (graphQLErrors) { graphQLErrors.map(({ message, locations, path }) => @@ -82,7 +85,7 @@ const linkError = onError(({ graphQLErrors, networkError }) => { console.log(`[Network error]: ${JSON.stringify(networkError)}`) clearTokenCookie() - const errors = networkError.result.errors + const errors = (networkError as ServerError).result.errors if (errors.length == 1) { errorMessages.push({ @@ -92,7 +95,9 @@ const linkError = onError(({ graphQLErrors, networkError }) => { } else if (errors.length > 1) { errorMessages.push({ header: 'Multiple server errors', - content: `Received ${graphQLErrors.length} errors from the server. You are being logged out in an attempt to recover.`, + content: `Received ${ + graphQLErrors?.length || 0 + } errors from the server. You are being logged out in an attempt to recover.`, }) } } @@ -106,26 +111,32 @@ const linkError = onError(({ graphQLErrors, networkError }) => { ...msg, }, })) - MessageState.set(messages => [...messages, ...newMessages]) + MessageState.set((messages: Message[]) => [...messages, ...newMessages]) } }) +type PaginateCacheType = { + keyArgs: string[] + merge: FieldMergeFunction +} + // Modified version of Apollo's offsetLimitPagination() -const paginateCache = keyArgs => ({ - keyArgs, - merge(existing, incoming, { args, fieldName }) { - const merged = existing ? existing.slice(0) : [] - if (args?.paginate) { - const { offset = 0 } = args.paginate - for (let i = 0; i < incoming.length; ++i) { - merged[offset + i] = incoming[i] +const paginateCache = (keyArgs: string[]) => + ({ + keyArgs, + merge(existing, incoming, { args, fieldName }) { + const merged = existing ? existing.slice(0) : [] + if (args?.paginate) { + const { offset = 0 } = args.paginate + for (let i = 0; i < incoming.length; ++i) { + merged[offset + i] = incoming[i] + } + } else { + throw new Error(`Paginate argument is missing for query: ${fieldName}`) } - } else { - throw new Error(`Paginate argument is missing for query: ${fieldName}`) - } - return merged - }, -}) + return merged + }, + } as PaginateCacheType) const memoryCache = new InMemoryCache({ typePolicies: { diff --git a/ui/src/components/AlbumTitle.js b/ui/src/components/AlbumTitle.tsx similarity index 81% rename from ui/src/components/AlbumTitle.js rename to ui/src/components/AlbumTitle.tsx index f4a6872..b7fd32b 100644 --- a/ui/src/components/AlbumTitle.js +++ b/ui/src/components/AlbumTitle.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useContext } from 'react' import PropTypes from 'prop-types' -import { Breadcrumb } from 'semantic-ui-react' +import { Breadcrumb, IconProps } from 'semantic-ui-react' import { Link } from 'react-router-dom' import styled from 'styled-components' import { Icon } from 'semantic-ui-react' @@ -8,6 +8,7 @@ import { SidebarContext } from './sidebar/Sidebar' import AlbumSidebar from './sidebar/AlbumSidebar' import { useLazyQuery, gql } from '@apollo/client' import { authToken } from '../helpers/authentication' +import { albumPathQuery } from './__generated__/albumPathQuery' const Header = styled.h1` margin: 24px 0 8px 0 !important; @@ -32,7 +33,7 @@ const StyledIcon = styled(Icon)` } ` -const SettingsIcon = props => { +const SettingsIcon = (props: IconProps) => { return } @@ -48,8 +49,18 @@ const ALBUM_PATH_QUERY = gql` } ` -const AlbumTitle = ({ album, disableLink = false }) => { - const [fetchPath, { data: pathData }] = useLazyQuery(ALBUM_PATH_QUERY) +type AlbumTitleProps = { + album: { + id: string + title: string + } + disableLink: boolean +} + +const AlbumTitle = ({ album, disableLink = false }: AlbumTitleProps) => { + const [fetchPath, { data: pathData }] = useLazyQuery( + ALBUM_PATH_QUERY + ) const { updateSidebar } = useContext(SidebarContext) useEffect(() => { @@ -68,10 +79,7 @@ const AlbumTitle = ({ album, disableLink = false }) => { let title = {album.title} - let path = [] - if (pathData) { - path = pathData.album.path - } + const path = pathData?.album.path || [] const breadcrumbSections = path .slice() diff --git a/ui/src/components/PaginateLoader.js b/ui/src/components/PaginateLoader.tsx similarity index 60% rename from ui/src/components/PaginateLoader.js rename to ui/src/components/PaginateLoader.tsx index 19e6615..4c9ad2b 100644 --- a/ui/src/components/PaginateLoader.js +++ b/ui/src/components/PaginateLoader.tsx @@ -1,8 +1,12 @@ import React from 'react' -import PropTypes from 'prop-types' import { Loader } from 'semantic-ui-react' -const PaginateLoader = ({ active, text }) => ( +type PaginateLoaderProps = { + active: boolean + text: string +} + +const PaginateLoader = ({ active, text }: PaginateLoaderProps) => ( ( ) -PaginateLoader.propTypes = { - active: PropTypes.bool, - text: PropTypes.string, -} - export default PaginateLoader diff --git a/ui/src/components/header/Header.js b/ui/src/components/header/Header.tsx similarity index 100% rename from ui/src/components/header/Header.js rename to ui/src/components/header/Header.tsx diff --git a/ui/src/components/header/Searchbar.js b/ui/src/components/header/Searchbar.tsx similarity index 75% rename from ui/src/components/header/Searchbar.js rename to ui/src/components/header/Searchbar.tsx index 2f7886b..4d3d227 100644 --- a/ui/src/components/header/Searchbar.js +++ b/ui/src/components/header/Searchbar.tsx @@ -1,11 +1,15 @@ import React, { useState, useRef, useEffect } from 'react' -import PropTypes from 'prop-types' import styled from 'styled-components' import { useLazyQuery, gql } from '@apollo/client' -import { debounce } from '../../helpers/utils' +import { debounce, DebouncedFn } from '../../helpers/utils' import { ProtectedImage } from '../photoGallery/ProtectedMedia' import { NavLink } from 'react-router-dom' import { useTranslation } from 'react-i18next' +import { + searchQuery, + searchQuery_search_albums, + searchQuery_search_media, +} from './__generated__/searchQuery' const Container = styled.div` height: 60px; @@ -30,7 +34,7 @@ const SearchField = styled.input` } ` -const Results = styled.div` +const Results = styled.div<{ show: boolean }>` display: ${({ show }) => (show ? 'block' : 'none')}; position: absolute; width: 100%; @@ -79,27 +83,29 @@ const SEARCH_QUERY = gql` const SearchBar = () => { const { t } = useTranslation() - const [fetchSearches, fetchResult] = useLazyQuery(SEARCH_QUERY) + const [fetchSearches, fetchResult] = useLazyQuery(SEARCH_QUERY) const [query, setQuery] = useState('') const [fetched, setFetched] = useState(false) - let debouncedFetch = useRef(null) + type QueryFn = (query: string) => void + + const debouncedFetch = useRef>(null) useEffect(() => { - debouncedFetch.current = debounce(query => { + debouncedFetch.current = debounce(query => { fetchSearches({ variables: { query } }) setFetched(true) }, 250) return () => { - debouncedFetch.current.cancel() + debouncedFetch.current?.cancel() } }, []) - const fetchEvent = e => { + const fetchEvent = (e: React.ChangeEvent) => { e.persist() setQuery(e.target.value) - if (e.target.value.trim() != '') { + if (e.target.value.trim() != '' && debouncedFetch.current) { debouncedFetch.current(e.target.value.trim()) } else { setFetched(false) @@ -108,7 +114,12 @@ const SearchBar = () => { let results = null if (query.trim().length > 0 && fetched) { - results = + results = ( + + ) } return ( @@ -128,17 +139,21 @@ const ResultTitle = styled.h1` margin: 12px 0 0.25rem; ` -const SearchResults = ({ result }) => { - const { t } = useTranslation() - const { data, loading } = result - const query = data && data.search.query +type SearchResultsProps = { + searchData?: searchQuery + loading: boolean +} - const media = (data && data.search.media) || [] - const albums = (data && data.search.albums) || [] +const SearchResults = ({ searchData, loading }: SearchResultsProps) => { + const { t } = useTranslation() + const query = searchData?.search.query || '' + + const media = searchData?.search.media || [] + const albums = searchData?.search.albums || [] let message = null if (loading) message = t('header.search.loading', 'Loading results...') - else if (data && media.length == 0 && albums.length == 0) + else if (searchData && media.length == 0 && albums.length == 0) message = t('header.search.no_results', 'No results found') const albumElements = albums.map(album => ( @@ -155,7 +170,7 @@ const SearchResults = ({ result }) => { // Prevent input blur event e.preventDefault() }} - show={data} + show={!!searchData} > {message} {albumElements.length > 0 && ( @@ -174,10 +189,6 @@ const SearchResults = ({ result }) => { ) } -SearchResults.propTypes = { - result: PropTypes.object, -} - const RowLink = styled(NavLink)` display: flex; align-items: center; @@ -205,31 +216,31 @@ const RowTitle = styled.span` padding-left: 8px; ` -const PhotoRow = ({ query, media }) => ( +type PhotoRowArgs = { + query: string + media: searchQuery_search_media +} + +const PhotoRow = ({ query, media }: PhotoRowArgs) => ( {searchHighlighted(query, media.title)} ) -PhotoRow.propTypes = { - query: PropTypes.string.isRequired, - media: PropTypes.object.isRequired, +type AlbumRowArgs = { + query: string + album: searchQuery_search_albums } -const AlbumRow = ({ query, album }) => ( +const AlbumRow = ({ query, album }: AlbumRowArgs) => ( {searchHighlighted(query, album.title)} ) -AlbumRow.propTypes = { - query: PropTypes.string.isRequired, - album: PropTypes.object.isRequired, -} - -const searchHighlighted = (query, text) => { +const searchHighlighted = (query: string, text: string) => { const i = text.toLowerCase().indexOf(query.toLowerCase()) if (i == -1) { diff --git a/ui/src/components/messages/Messages.js b/ui/src/components/messages/Messages.js index d16ae24..894c3f5 100644 --- a/ui/src/components/messages/Messages.js +++ b/ui/src/components/messages/Messages.js @@ -17,9 +17,11 @@ const Container = styled.div` } ` -export let MessageState = { - set: null, - get: null, +export const MessageState = { + set: fn => { + console.warn('set function is not defined yet, called with', fn) + }, + get: [], add: message => { MessageState.set(messages => { const newMessages = messages.filter(msg => msg.key != message.key) diff --git a/ui/src/components/messages/SubscriptionsHook.js b/ui/src/components/messages/SubscriptionsHook.ts similarity index 69% rename from ui/src/components/messages/SubscriptionsHook.js rename to ui/src/components/messages/SubscriptionsHook.ts index 5b52737..de1b287 100644 --- a/ui/src/components/messages/SubscriptionsHook.js +++ b/ui/src/components/messages/SubscriptionsHook.ts @@ -1,7 +1,9 @@ +import { notificationSubscription } from './__generated__/notificationSubscription' import PropTypes from 'prop-types' import { useEffect } from 'react' import { useSubscription, gql } from '@apollo/client' import { authToken } from '../../helpers/authentication' +import { NotificationType } from '../../../__generated__/globalTypes' const notificationSubscription = gql` subscription notificationSubscription { @@ -18,14 +20,37 @@ const notificationSubscription = gql` } ` -let messageTimeoutHandles = new Map() +const messageTimeoutHandles = new Map() -const SubscriptionsHook = ({ messages, setMessages }) => { +export interface Message { + key: string + type: NotificationType + timeout?: number + props: { + header: string + content: string + negative?: boolean + positive?: boolean + percent?: number + } +} + +type SubscriptionHookProps = { + messages: Message[] + setMessages: React.Dispatch> +} + +const SubscriptionsHook = ({ + messages, + setMessages, +}: SubscriptionHookProps) => { if (!authToken()) { return null } - const { data, error } = useSubscription(notificationSubscription) + const { data, error } = useSubscription( + notificationSubscription + ) useEffect(() => { if (error) { @@ -33,7 +58,7 @@ const SubscriptionsHook = ({ messages, setMessages }) => { ...state, { key: Math.random().toString(26), - type: 'message', + type: NotificationType.Message, props: { header: 'Network error', content: error.message, @@ -54,16 +79,16 @@ const SubscriptionsHook = ({ messages, setMessages }) => { return } - const newNotification = { + const newNotification: Message = { key: msg.key, - type: msg.type.toLowerCase(), - timeout: msg.timeout, + type: msg.type, + timeout: msg.timeout || undefined, props: { header: msg.header, content: msg.content, negative: msg.negative, positive: msg.positive, - percent: msg.progress, + percent: msg.progress || undefined, }, } diff --git a/ui/src/components/routes/AuthorizedRoute.js b/ui/src/components/routes/AuthorizedRoute.tsx similarity index 71% rename from ui/src/components/routes/AuthorizedRoute.js rename to ui/src/components/routes/AuthorizedRoute.tsx index b0ccb2c..2ba606e 100644 --- a/ui/src/components/routes/AuthorizedRoute.js +++ b/ui/src/components/routes/AuthorizedRoute.tsx @@ -1,5 +1,5 @@ -import React, { useEffect } from 'react' -import PropTypes from 'prop-types' +import React, { ReactChild, useEffect } from 'react' +import PropTypes, { ReactComponentLike } from 'prop-types' import { Route, Redirect } from 'react-router-dom' import { useLazyQuery } from '@apollo/client' import { authToken } from '../../helpers/authentication' @@ -21,22 +21,31 @@ export const useIsAdmin = (enabled = true) => { return data?.myUser?.admin } -export const Authorized = ({ children }) => { +export const Authorized = ({ children }: { children: JSX.Element }) => { const token = authToken() return token ? children : null } -const AuthorizedRoute = ({ component: Component, admin = false, ...props }) => { +type AuthorizedRouteProps = { + component: ReactComponentLike + admin: boolean +} + +const AuthorizedRoute = ({ + component: Component, + admin = false, + ...props +}: AuthorizedRouteProps) => { const token = authToken() const isAdmin = useIsAdmin(admin) - let unauthorizedRedirect = null + let unauthorizedRedirect: null | ReactChild = null if (!token) { unauthorizedRedirect = } - let adminRedirect = null + let adminRedirect: null | ReactChild = null if (token && admin) { if (isAdmin === false) { adminRedirect = diff --git a/ui/src/helpers/LazyLoad.js b/ui/src/helpers/LazyLoad.ts similarity index 65% rename from ui/src/helpers/LazyLoad.js rename to ui/src/helpers/LazyLoad.ts index 6892bf2..f70410d 100644 --- a/ui/src/helpers/LazyLoad.js +++ b/ui/src/helpers/LazyLoad.ts @@ -1,4 +1,6 @@ class LazyLoad { + observer: null | IntersectionObserver + constructor() { this.observe = this.observe.bind(this) this.loadImages = this.loadImages.bind(this) @@ -6,22 +8,22 @@ class LazyLoad { this.observer = null } - observe(images) { + observe(images: Element[]) { if (!this.observer) { this.observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting || entry.intersectionRatio > 0) { const element = entry.target this.setSrcAttribute(element) - this.observer.unobserve(element) + this.observer?.unobserve(element) } }) }) } - Array.from(images).forEach(image => this.observer.observe(image)) + Array.from(images).forEach(image => this.observer?.observe(image)) } - loadImages(elements) { + loadImages(elements: Element[]) { const images = Array.from(elements) if (images.length) { if ('IntersectionObserver' in window) { @@ -36,11 +38,18 @@ class LazyLoad { this.observer && this.observer.disconnect() } - setSrcAttribute(element) { + setSrcAttribute(element: Element) { if (element.hasAttribute('data-src')) { const src = element.getAttribute('data-src') - element.removeAttribute('data-src') - element.setAttribute('src', src) + if (src) { + element.removeAttribute('data-src') + element.setAttribute('src', src) + } else { + console.warn( + 'WARN: expected element to have `data-src` property', + element + ) + } } } } diff --git a/ui/src/helpers/authentication.js b/ui/src/helpers/authentication.ts similarity index 75% rename from ui/src/helpers/authentication.js rename to ui/src/helpers/authentication.ts index 3a60643..abe3655 100644 --- a/ui/src/helpers/authentication.js +++ b/ui/src/helpers/authentication.ts @@ -1,4 +1,4 @@ -export function saveTokenCookie(token) { +export function saveTokenCookie(token: string) { const maxAge = 14 * 24 * 60 * 60 document.cookie = `auth-token=${token} ;max-age=${maxAge} ;path=/ ;sameSite=Lax` @@ -13,11 +13,11 @@ export function authToken() { return match && match[1] } -export function saveSharePassword(shareToken, password) { +export function saveSharePassword(shareToken: string, password: string) { document.cookie = `share-token-pw-${shareToken}=${password} ;path=/ ;sameSite=Lax` } -export function getSharePassword(shareToken) { +export function getSharePassword(shareToken: string) { const match = document.cookie.match( `share-token-pw-${shareToken}=([\\d\\w]+)` ) diff --git a/ui/src/helpers/utils.js b/ui/src/helpers/utils.js deleted file mode 100644 index b36de48..0000000 --- a/ui/src/helpers/utils.js +++ /dev/null @@ -1,28 +0,0 @@ -export function debounce(func, wait, triggerRising) { - let timeout = null - - const debounced = (...args) => { - if (timeout) { - clearTimeout(timeout) - timeout = null - } else if (triggerRising) { - func(...args) - } - - timeout = setTimeout(() => { - timeout = null - func(...args) - }, wait) - } - - debounced.cancel = () => { - clearTimeout(timeout) - timeout = null - } - - return debounced -} - -export function isNil(value) { - return value === undefined || value === null -} diff --git a/ui/src/helpers/utils.ts b/ui/src/helpers/utils.ts new file mode 100644 index 0000000..e543c1b --- /dev/null +++ b/ui/src/helpers/utils.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export interface DebouncedFn any> { + (...args: Parameters): void + cancel(): void +} + +export function debounce any>( + func: T, + wait: number, + triggerRising?: boolean +): DebouncedFn { + let timeout: number | undefined = undefined + + const debounced = (...args: Parameters) => { + if (timeout) { + clearTimeout(timeout) + timeout = undefined + } else if (triggerRising) { + func(...args) + } + + timeout = window.setTimeout(() => { + timeout = undefined + func(...args) + }, wait) + } + + debounced.cancel = () => { + clearTimeout(timeout) + timeout = undefined + } + + return debounced +} + +export function isNil(value: any) { + return value === undefined || value === null +} diff --git a/ui/src/hooks/useScrollPagination.js b/ui/src/hooks/useScrollPagination.ts similarity index 65% rename from ui/src/hooks/useScrollPagination.js rename to ui/src/hooks/useScrollPagination.ts index 165bc87..2e7f716 100644 --- a/ui/src/hooks/useScrollPagination.js +++ b/ui/src/hooks/useScrollPagination.ts @@ -1,26 +1,44 @@ import { useCallback, useEffect, useRef, useState } from 'react' -const useScrollPagination = ({ loading, fetchMore, data, getItems }) => { - const observer = useRef(null) - const observerElem = useRef(null) +interface ScrollPaginationArgs { + loading: boolean + data: D + fetchMore(args: { variables: { offset: number } }): Promise<{ data: D }> + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getItems(data: D): any[] +} + +type ScrollPaginationResult = { + finished: boolean + containerElem(node: null | Element): void +} + +function useScrollPagination({ + loading, + fetchMore, + data, + getItems, +}: ScrollPaginationArgs): ScrollPaginationResult { + const observer = useRef(null) + const observerElem = useRef(null) const [finished, setFinished] = useState(false) const reconfigureIntersectionObserver = () => { - var options = { + const options = { root: null, rootMargin: '-100% 0px 0px 0px', threshold: 0, } // delete old observer - if (observer.current) observer.current.disconnect() + observer.current?.disconnect() if (finished) return // configure new observer observer.current = new IntersectionObserver(entities => { if (entities.find(x => x.isIntersecting == false)) { - let itemCount = getItems(data).length + const itemCount = getItems(data).length fetchMore({ variables: { offset: itemCount, @@ -40,7 +58,7 @@ const useScrollPagination = ({ loading, fetchMore, data, getItems }) => { } } - const containerElem = useCallback(node => { + const containerElem = useCallback((node: null | Element): void => { observerElem.current = node // cleanup @@ -55,7 +73,7 @@ const useScrollPagination = ({ loading, fetchMore, data, getItems }) => { // only observe when not loading useEffect(() => { - if (observer.current != null) { + if (observer.current && observerElem.current) { if (loading) { observer.current.unobserve(observerElem.current) } else { diff --git a/ui/src/hooks/useURLParameters.js b/ui/src/hooks/useURLParameters.ts similarity index 79% rename from ui/src/hooks/useURLParameters.js rename to ui/src/hooks/useURLParameters.ts index 6ad3829..79c0051 100644 --- a/ui/src/hooks/useURLParameters.js +++ b/ui/src/hooks/useURLParameters.ts @@ -6,7 +6,7 @@ function useURLParameters() { const url = new URL(urlString) const params = new URLSearchParams(url.search) - const getParam = (key, defaultValue = null) => { + const getParam = (key: string, defaultValue = null) => { return params.has(key) ? params.get(key) : defaultValue } @@ -15,12 +15,12 @@ function useURLParameters() { setUrlString(document.location.href) } - const setParam = (key, value) => { + const setParam = (key: string, value: string) => { params.set(key, value) updateParams() } - const setParams = pairs => { + const setParams = (pairs: { key: string; value: string }[]) => { for (const pair of pairs) { params.set(pair.key, pair.value) } diff --git a/ui/src/localization.js b/ui/src/localization.ts similarity index 89% rename from ui/src/localization.js rename to ui/src/localization.ts index 3890d3a..882ad83 100644 --- a/ui/src/localization.js +++ b/ui/src/localization.ts @@ -1,7 +1,7 @@ import i18n from 'i18next' import { initReactI18next } from 'react-i18next' -export default function setupLocalization() { +export default function setupLocalization(): void { i18n.use(initReactI18next).init({ resources: { en: { diff --git a/ui/tsconfig.json b/ui/tsconfig.json index b3854d6..fd4fb47 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -4,9 +4,12 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - // "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ - // "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ + "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + "lib": [ + "es2015", + "dom" + ] /* Specify library files to be included in the compilation. */, "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ "jsx": "preserve" /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */, @@ -48,7 +51,9 @@ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ + "typeRoots": [ + "./src/@types/" + ] /* List of folders to include type definitions from. */, // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, @@ -68,5 +73,6 @@ /* Advanced Options */ "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } + }, + "exclude": ["node_modules/", "dist/"] }