1
Fork 0

Make eslint check for typescript errors

Fix errors after making eslint more strict
This commit is contained in:
viktorstrate 2022-07-13 18:02:06 +02:00
parent b0512d7128
commit 4c9e9a2b9a
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
61 changed files with 877 additions and 300 deletions

3
ui/.eslintignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
coverage

View File

@ -1,3 +1,5 @@
/* global __dirname */
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
@ -11,6 +13,7 @@ module.exports = {
'plugin:react/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'prettier',
],
globals: {
@ -21,6 +24,8 @@ module.exports = {
require: 'readonly',
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
ecmaFeatures: {
jsx: true,
},
@ -41,30 +46,13 @@ module.exports = {
version: 'detect',
},
},
// parser: 'babel-eslint',
overrides: [
// Object.assign(require('eslint-plugin-jest').configs.recommended, {
// files: ['**/*.test.js', '**/*.test.ts', '**/*.test.tsx'],
// env: { jest: true },
// plugins: ['jest', 'jest-dom'],
// rules: Object.assign(
// require('eslint-plugin-jest').configs.recommended.rules,
// {
// 'no-import-assign': 'off',
// 'react/prop-types': 'off',
// 'jest/valid-title': 'off',
// }
// ),
// settings: {
// jest: {
// version: 26,
// },
// },
// }),
{
files: ['**/*.js'],
files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
rules: {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',
},
},
],

507
ui/package-lock.json generated
View File

@ -53,6 +53,8 @@
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"@vitest/ui": "^0.17.1",
"apollo": "2.34.0",
"apollo-language-server": "1.26.9",
@ -5326,13 +5328,13 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz",
"integrity": "sha512-lftkqRoBvc28VFXEoRgyZuztyVUQ04JvUnATSPtIRFAccbXTWL6DEtXGYMcbg998kXw1NLUJm7rTQ9eUt+q6Ig==",
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz",
"integrity": "sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==",
"dependencies": {
"@typescript-eslint/scope-manager": "5.30.5",
"@typescript-eslint/type-utils": "5.30.5",
"@typescript-eslint/utils": "5.30.5",
"@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/type-utils": "5.30.6",
"@typescript-eslint/utils": "5.30.6",
"debug": "^4.3.4",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.2.0",
@ -5357,6 +5359,99 @@
}
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz",
"integrity": "sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.6.tgz",
"integrity": "sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz",
"integrity": "sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.30.6.tgz",
"integrity": "sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==",
"dependencies": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/typescript-estree": "5.30.6",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz",
"integrity": "sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -5390,13 +5485,13 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.5.tgz",
"integrity": "sha512-zj251pcPXI8GO9NDKWWmygP6+UjwWmrdf9qMW/L/uQJBM/0XbU2inxe5io/234y/RCvwpKEYjZ6c1YrXERkK4Q==",
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.6.tgz",
"integrity": "sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==",
"dependencies": {
"@typescript-eslint/scope-manager": "5.30.5",
"@typescript-eslint/types": "5.30.5",
"@typescript-eslint/typescript-estree": "5.30.5",
"@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/typescript-estree": "5.30.6",
"debug": "^4.3.4"
},
"engines": {
@ -5415,6 +5510,90 @@
}
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz",
"integrity": "sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.6.tgz",
"integrity": "sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz",
"integrity": "sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz",
"integrity": "sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.5.tgz",
@ -5432,11 +5611,11 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.5.tgz",
"integrity": "sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw==",
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz",
"integrity": "sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==",
"dependencies": {
"@typescript-eslint/utils": "5.30.5",
"@typescript-eslint/utils": "5.30.6",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@ -5456,6 +5635,113 @@
}
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz",
"integrity": "sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.6.tgz",
"integrity": "sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz",
"integrity": "sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.30.6.tgz",
"integrity": "sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==",
"dependencies": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/typescript-estree": "5.30.6",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz",
"integrity": "sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==",
"dependencies": {
"@typescript-eslint/types": "5.30.6",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.5.tgz",
@ -28832,13 +29118,13 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA=="
},
"@typescript-eslint/eslint-plugin": {
"version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz",
"integrity": "sha512-lftkqRoBvc28VFXEoRgyZuztyVUQ04JvUnATSPtIRFAccbXTWL6DEtXGYMcbg998kXw1NLUJm7rTQ9eUt+q6Ig==",
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz",
"integrity": "sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==",
"requires": {
"@typescript-eslint/scope-manager": "5.30.5",
"@typescript-eslint/type-utils": "5.30.5",
"@typescript-eslint/utils": "5.30.5",
"@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/type-utils": "5.30.6",
"@typescript-eslint/utils": "5.30.6",
"debug": "^4.3.4",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.2.0",
@ -28847,6 +29133,56 @@
"tsutils": "^3.21.0"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz",
"integrity": "sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6"
}
},
"@typescript-eslint/types": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.6.tgz",
"integrity": "sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg=="
},
"@typescript-eslint/typescript-estree": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz",
"integrity": "sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/utils": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.30.6.tgz",
"integrity": "sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==",
"requires": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/typescript-estree": "5.30.6",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz",
"integrity": "sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"eslint-visitor-keys": "^3.3.0"
}
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -28866,14 +29202,61 @@
}
},
"@typescript-eslint/parser": {
"version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.5.tgz",
"integrity": "sha512-zj251pcPXI8GO9NDKWWmygP6+UjwWmrdf9qMW/L/uQJBM/0XbU2inxe5io/234y/RCvwpKEYjZ6c1YrXERkK4Q==",
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.6.tgz",
"integrity": "sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==",
"requires": {
"@typescript-eslint/scope-manager": "5.30.5",
"@typescript-eslint/types": "5.30.5",
"@typescript-eslint/typescript-estree": "5.30.5",
"@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/typescript-estree": "5.30.6",
"debug": "^4.3.4"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz",
"integrity": "sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6"
}
},
"@typescript-eslint/types": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.6.tgz",
"integrity": "sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg=="
},
"@typescript-eslint/typescript-estree": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz",
"integrity": "sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz",
"integrity": "sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"eslint-visitor-keys": "^3.3.0"
}
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"@typescript-eslint/scope-manager": {
@ -28886,13 +29269,73 @@
}
},
"@typescript-eslint/type-utils": {
"version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.5.tgz",
"integrity": "sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw==",
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz",
"integrity": "sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==",
"requires": {
"@typescript-eslint/utils": "5.30.5",
"@typescript-eslint/utils": "5.30.6",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz",
"integrity": "sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6"
}
},
"@typescript-eslint/types": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.6.tgz",
"integrity": "sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg=="
},
"@typescript-eslint/typescript-estree": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz",
"integrity": "sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/visitor-keys": "5.30.6",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"semver": "^7.3.7",
"tsutils": "^3.21.0"
}
},
"@typescript-eslint/utils": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.30.6.tgz",
"integrity": "sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==",
"requires": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/types": "5.30.6",
"@typescript-eslint/typescript-estree": "5.30.6",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz",
"integrity": "sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==",
"requires": {
"@typescript-eslint/types": "5.30.6",
"eslint-visitor-keys": "^3.3.0"
}
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"@typescript-eslint/types": {

View File

@ -11,10 +11,10 @@
"scripts": {
"start": "vite",
"build": "vite build",
"lint": "eslint ./src --max-warnings 0 --cache --config .eslintrc.js",
"lint": "eslint ./src --max-warnings 0 --config .eslintrc.js",
"test": "vitest",
"test:ci": "CI=true vitest --reporter verbose --run --coverage",
"genSchemaTypes": "apollo client:codegen --target=typescript --globalTypesFile=src/__generated__/globalTypes.ts && prettier --write */**/__generated__/*.ts",
"genSchemaTypes": "apollo client:codegen --target=typescript --globalTypesFile=src/__generated__/globalTypes.ts --passthroughCustomScalars && prettier --write */**/__generated__/*.ts",
"extractTranslations": "i18next -c i18next-parser.config.js",
"prepare": "(cd .. && ./ui/node_modules/.bin/husky install)"
},
@ -63,6 +63,8 @@
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"@vitest/ui": "^0.17.1",
"apollo": "2.34.0",
"apollo-language-server": "1.26.9",

View File

@ -5,3 +5,16 @@ declare module '*.svg' {
export { ReactComponent }
// export default content
}
interface ImportMetaEnv {
readonly REACT_APP_BUILD_VERSION: string | undefined
readonly REACT_APP_BUILD_DATE: string | undefined
readonly REACT_APP_BUILD_COMMIT_SHA: string | undefined
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
type Time = string
type Any = object

View File

@ -1,6 +1,8 @@
import React, { useCallback } from 'react'
import { useQuery, gql } from '@apollo/client'
import AlbumGallery from '../../components/albumGallery/AlbumGallery'
import AlbumGallery, {
ALBUM_GALLERY_FRAGMENT,
} from '../../components/albumGallery/AlbumGallery'
import Layout from '../../components/layout/Layout'
import useURLParameters from '../../hooks/useURLParameters'
import useScrollPagination from '../../hooks/useScrollPagination'
@ -10,10 +12,9 @@ import { albumQuery, albumQueryVariables } from './__generated__/albumQuery'
import useOrderingParams from '../../hooks/useOrderingParams'
import { useParams } from 'react-router-dom'
import { isNil } from '../../helpers/utils'
import { MEDIA_GALLERY_FRAGMENT } from '../../components/photoGallery/MediaGallery'
const ALBUM_QUERY = gql`
${MEDIA_GALLERY_FRAGMENT}
${ALBUM_GALLERY_FRAGMENT}
query albumQuery(
$id: ID!
@ -24,27 +25,7 @@ const ALBUM_QUERY = gql`
$offset: Int
) {
album(id: $id) {
id
title
subAlbums(
order: { order_by: "title", order_direction: $orderDirection }
) {
id
title
thumbnail {
id
thumbnail {
url
}
}
}
media(
paginate: { limit: $limit, offset: $offset }
order: { order_by: $mediaOrderBy, order_direction: $orderDirection }
onlyFavorites: $onlyFavorites
) {
...MediaGalleryFields
}
...AlbumGalleryFields
}
}
`

View File

@ -9,10 +9,10 @@ import { mockInitialSetupGraphql } from './loginTestHelpers'
vi.mock('../../helpers/authentication.ts')
const authToken = authentication.authToken // as vi.Mock<ReturnType<typeof authentication.authToken>>
const authToken = vi.mocked(authentication.authToken)
describe('Initial setup page', () => {
test('Render initial setup form', async () => {
test('Render initial setup form', () => {
authToken.mockImplementation(() => null)
const history = createMemoryHistory({

View File

@ -10,6 +10,10 @@ import { CheckInitialSetup } from './__generated__/CheckInitialSetup'
import { useForm } from 'react-hook-form'
import { Submit, TextField } from '../../primitives/form/Input'
import MessageBox from '../../primitives/form/MessageBox'
import {
InitialSetup,
InitialSetupVariables,
} from './__generated__/InitialSetup'
const initialSetupMutation = gql`
mutation InitialSetup(
@ -59,13 +63,12 @@ const InitialSetupPage = () => {
}, [notInitialSetup])
const [authorize, { loading: authorizeLoading, data: authorizationData }] =
useMutation(initialSetupMutation, {
useMutation<InitialSetup, InitialSetupVariables>(initialSetupMutation, {
onCompleted: data => {
const { success, token } = data.initialSetupWizard
if (!data.initialSetupWizard) return
if (success) {
login(token)
}
const { success, token } = data.initialSetupWizard
if (success && token) login(token)
},
})
@ -84,8 +87,8 @@ const InitialSetupPage = () => {
}
let errorMessage = null
if (authorizationData && !authorizationData.initialSetupWizard.success) {
errorMessage = authorizationData.initialSetupWizard.status
if (authorizationData && !authorizationData?.initialSetupWizard?.success) {
errorMessage = authorizationData?.initialSetupWizard?.status
}
return (
@ -138,7 +141,7 @@ const InitialSetupPage = () => {
<MessageBox
type="negative"
message={errorMessage}
show={errorMessage}
show={!!errorMessage}
/>
<Submit className="mt-2" disabled={authorizeLoading}>
{t('login_page.initial_setup.field.submit', 'Setup Photoview')}

View File

@ -9,7 +9,7 @@ import { mockInitialSetupGraphql } from './loginTestHelpers'
vi.mock('../../helpers/authentication.ts')
const authToken = authentication.authToken // as vi.Mock<ReturnType<typeof authentication.authToken>>
const authToken = vi.mocked(authentication.authToken)
describe('Login page redirects', () => {
test('Auth token redirect', async () => {
@ -54,7 +54,7 @@ describe('Login page redirects', () => {
})
describe('Login page', () => {
test('Render login form', async () => {
test('Render login form', () => {
authToken.mockImplementation(() => null)
const history = createMemoryHistory({

View File

@ -106,7 +106,7 @@ const LoginForm = () => {
<input
type="submit"
disabled={loading}
value={t('login_page.field.submit', 'Sign in') as string}
value={t('login_page.field.submit', 'Sign in')}
className="rounded-md px-8 py-2 mt-2 focus:outline-none cursor-pointer bg-gradient-to-bl from-[#FF8246] to-[#D6264D] text-white font-semibold focus:ring-2 focus:ring-red-200 disabled:cursor-default disabled:opacity-80"
/>
<MessageBox

View File

@ -1,15 +1,26 @@
import React from 'react'
import styled from 'styled-components'
import { ProtectedImage } from '../../components/photoGallery/ProtectedMedia'
import {
ProtectedImage,
ProtectedImageProps,
} from '../../components/photoGallery/ProtectedMedia'
import {
myFaces_myFaceGroups_imageFaces_media,
myFaces_myFaceGroups_imageFaces_rectangle,
} from './__generated__/myFaces'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const FaceImage = styled(({ origin, selectable, scale, ...rest }) => (
type FaceImageProps = ProtectedImageProps & {
origin: { x: number; y: number }
selectable: boolean
scale: number
}
const FaceImage = styled(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
({ origin, selectable, scale, ...rest }: FaceImageProps) => (
<ProtectedImage {...rest} />
))<{ origin: { x: number; y: number }; selectable: boolean; scale: number }>`
)
)`
position: absolute;
transform-origin: ${({ origin }) => `${origin.x * 100}% ${origin.y * 100}%`};
object-fit: cover;

View File

@ -218,7 +218,7 @@ describe('FaceDetails component', () => {
})
})
test('cancel add label to face group', async () => {
test('cancel add label to face group', () => {
render(
<MockedProvider mocks={[]} addTypename={false}>
<MemoryRouter>

View File

@ -3,7 +3,7 @@ import React, { useEffect, useReducer } from 'react'
import { useTranslation } from 'react-i18next'
import PaginateLoader from '../../../components/PaginateLoader'
import MediaGallery from '../../../components/photoGallery/MediaGallery'
import { photoGalleryReducer } from '../../../components/photoGallery/photoGalleryReducer'
import { mediaGalleryReducer } from '../../../components/photoGallery/mediaGalleryReducer'
import useScrollPagination from '../../../hooks/useScrollPagination'
import FaceGroupTitle from './FaceGroupTitle'
import {
@ -62,7 +62,7 @@ const SingleFaceGroup = ({ faceGroupID }: SingleFaceGroupProps) => {
},
})
const [mediaState, dispatchMedia] = useReducer(photoGalleryReducer, {
const [mediaState, dispatchMedia] = useReducer(mediaGalleryReducer, {
presenting: false,
activeIndex: -1,
media: [],

View File

@ -49,7 +49,7 @@ const MapClusterMarker = ({
marker,
dispatchMarkerMedia,
}: MapClusterMarkerProps) => {
const thumbnail = JSON.parse(marker.thumbnail)
const thumbnail = JSON.parse(marker.thumbnail) as { url: string }
const presentMedia = () => {
dispatchMarkerMedia({

View File

@ -7,7 +7,7 @@ import styled from 'styled-components'
import Layout from '../../components/layout/Layout'
import { registerMediaMarkers } from '../../components/mapbox/mapboxHelperFunctions'
import useMapboxMap from '../../components/mapbox/MapboxMap'
import { urlPresentModeSetupHook } from '../../components/photoGallery/photoGalleryReducer'
import { urlPresentModeSetupHook } from '../../components/photoGallery/mediaGalleryReducer'
import MapPresentMarker from './MapPresentMarker'
import { PlacesAction, placesReducer } from './placesReducer'
import { mediaGeoJson } from './__generated__/mediaGeoJson'
@ -108,7 +108,7 @@ const configureMapbox =
map.addSource('media', {
type: 'geojson',
data: mapboxData?.myMediaGeoJson,
data: mapboxData?.myMediaGeoJson as never,
cluster: true,
clusterRadius: 50,
clusterProperties: {

View File

@ -11,5 +11,5 @@ export interface mediaGeoJson {
/**
* Get media owned by the logged in user, returned in GeoJson format
*/
myMediaGeoJson: any
myMediaGeoJson: Any
}

View File

@ -1,11 +1,11 @@
import { PresentMarker } from './PlacesPage'
import {
PhotoGalleryState,
MediaGalleryState,
PhotoGalleryAction,
photoGalleryReducer,
} from './../../components/photoGallery/photoGalleryReducer'
mediaGalleryReducer,
} from '../../components/photoGallery/mediaGalleryReducer'
export interface PlacesState extends PhotoGalleryState {
export interface PlacesState extends MediaGalleryState {
presentMarker?: PresentMarker
}
@ -36,6 +36,6 @@ export function placesReducer(
}
}
default:
return photoGalleryReducer(state, action)
return mediaGalleryReducer(state, action)
}
}

View File

@ -9,7 +9,7 @@ import {
ScannerConcurrentWorkers,
} from './ScannerConcurrentWorkers'
test('load ScannerConcurrentWorkers', async () => {
test('load ScannerConcurrentWorkers', () => {
const graphqlMocks = [
{
request: {

View File

@ -4,6 +4,11 @@ import { useTranslation } from 'react-i18next'
import Checkbox from '../../../primitives/form/Checkbox'
import { TextField, Button, ButtonGroup } from '../../../primitives/form/Input'
import { TableRow, TableCell } from '../../../primitives/Table'
import { createUser, createUserVariables } from './__generated__/createUser'
import {
userAddRootPath,
userAddRootPathVariables,
} from './__generated__/userAddRootPath'
export const CREATE_USER_MUTATION = gql`
mutation createUser($username: String!, $admin: Boolean!) {
@ -46,21 +51,22 @@ const AddUserRow = ({ setShow, show, onUserAdded }: AddUserRowProps) => {
onUserAdded()
}
const [addRootPath, { loading: addRootPathLoading }] = useMutation(
USER_ADD_ROOT_PATH_MUTATION,
{
const [addRootPath, { loading: addRootPathLoading }] = useMutation<
userAddRootPath,
userAddRootPathVariables
>(USER_ADD_ROOT_PATH_MUTATION, {
onCompleted: () => {
finished()
},
onError: () => {
finished()
},
}
)
})
const [createUser, { loading: createUserLoading }] = useMutation(
CREATE_USER_MUTATION,
{
const [createUser, { loading: createUserLoading }] = useMutation<
createUser,
createUserVariables
>(CREATE_USER_MUTATION, {
onCompleted: ({ createUser: { id } }) => {
if (state.rootPath) {
addRootPath({
@ -73,8 +79,7 @@ const AddUserRow = ({ setShow, show, onUserAdded }: AddUserRowProps) => {
finished()
}
},
}
)
})
const loading = addRootPathLoading || createUserLoading

View File

@ -67,7 +67,7 @@ export type UserRowChildProps = {
export type UserRowProps = {
user: settingsUsersQuery_user
refetchUsers(): void
refetchUsers: () => void
}
const UserRow = ({ user, refetchUsers }: UserRowProps) => {

View File

@ -10,9 +10,7 @@ import {
const VERSION = import.meta.env.REACT_APP_BUILD_VERSION ?? 'undefined'
const BUILD_DATE = import.meta.env.REACT_APP_BUILD_DATE ?? 'undefined'
const COMMIT_SHA = import.meta.env.REACT_APP_BUILD_COMMIT_SHA as
| string
| undefined
const COMMIT_SHA = import.meta.env.REACT_APP_BUILD_COMMIT_SHA
let commitLink: ReactElement
if (COMMIT_SHA) {

View File

@ -25,7 +25,7 @@ const PasswordProtectedShare = ({
const [invalidPassword, setInvalidPassword] = useState(false)
const onSubmit = () => {
refetchWithPassword(watch('password'))
refetchWithPassword(watch('password') as string)
setInvalidPassword(true)
}

View File

@ -11,6 +11,14 @@ import MediaSharePage from './MediaSharePage'
import { useTranslation } from 'react-i18next'
import PasswordProtectedShare from './PasswordProtectedShare'
import { isNil } from '../../helpers/utils'
import {
SharePageToken,
SharePageTokenVariables,
} from './__generated__/SharePageToken'
import {
ShareTokenValidatePassword,
ShareTokenValidatePasswordVariables,
} from './__generated__/ShareTokenValidatePassword'
export const SHARE_TOKEN_QUERY = gql`
query SharePageToken($token: String!, $password: String) {
@ -90,17 +98,20 @@ const AuthorizedTokenRoute = () => {
const token = tokenFromParams()
const password = getSharePassword(token)
const { loading, error, data } = useQuery(SHARE_TOKEN_QUERY, {
const { loading, error, data } = useQuery<
SharePageToken,
SharePageTokenVariables
>(SHARE_TOKEN_QUERY, {
variables: {
token,
password,
},
})
if (error) return <div>{error.message}</div>
if (!isNil(error)) return <div>{error.message}</div>
if (loading) return <div>{t('general.loading.default', 'Loading...')}</div>
if (data.shareToken.album) {
if (data?.shareToken.album) {
const SharedSubAlbumPage = () => {
const { subAlbum } = useParams()
if (isNil(subAlbum))
@ -128,7 +139,7 @@ const AuthorizedTokenRoute = () => {
)
}
if (data.shareToken.media) {
if (data?.shareToken.media) {
return <MediaSharePage media={data.shareToken.media} />
}
@ -144,16 +155,16 @@ export const TokenRoute = () => {
const { t } = useTranslation()
const token = tokenFromParams()
const { loading, error, data, refetch } = useQuery(
VALIDATE_TOKEN_PASSWORD_QUERY,
{
const { loading, error, data, refetch } = useQuery<
ShareTokenValidatePassword,
ShareTokenValidatePasswordVariables
>(VALIDATE_TOKEN_PASSWORD_QUERY, {
notifyOnNetworkStatusChange: true,
variables: {
token: token,
password: getSharePassword(token),
},
}
)
})
if (error) {
if (error.message == 'GraphQL error: share not found') {

View File

@ -122,7 +122,7 @@ export interface SharePageToken_shareToken_media_exif {
* The name of the lens
*/
lens: string | null
dateShot: any | null
dateShot: Time | null
/**
* The exposure time of the image
*/

View File

@ -136,7 +136,7 @@ export interface shareAlbumQuery_album_media_exif {
* The name of the lens
*/
lens: string | null
dateShot: any | null
dateShot: Time | null
/**
* The exposure time of the image
*/

View File

@ -56,19 +56,24 @@ const link = split(
const linkError = onError(({ graphQLErrors, networkError }) => {
const errorMessages = []
const formatPath = (path: readonly (string | number)[] | undefined) =>
path?.join('::') ?? 'undefined'
if (graphQLErrors) {
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${JSON.stringify(
locations
)} Path: ${path}`
)} Path: ${formatPath(path)}`
)
)
if (graphQLErrors.length == 1) {
errorMessages.push({
header: 'Something went wrong',
content: `Server error: ${graphQLErrors[0].message} at (${graphQLErrors[0].path})`,
content: `Server error: ${graphQLErrors[0].message} at (${formatPath(
graphQLErrors[0].path
)})`,
})
} else if (graphQLErrors.length > 1) {
errorMessages.push({
@ -88,7 +93,8 @@ const linkError = onError(({ graphQLErrors, networkError }) => {
console.log(`[Network error]: ${JSON.stringify(networkError)}`)
clearTokenCookie()
const errors = (networkError as ServerError)?.result.errors || []
const errors =
((networkError as ServerError)?.result.errors as Error[]) || []
if (errors.length == 1) {
errorMessages.push({
@ -120,7 +126,7 @@ const linkError = onError(({ graphQLErrors, networkError }) => {
type PaginateCacheType = {
keyArgs: string[]
merge: FieldMergeFunction
merge: FieldMergeFunction<unknown[], unknown[]>
}
// Modified version of Apollo's offsetLimitPagination()
@ -130,7 +136,7 @@ const paginateCache = (keyArgs: string[]) =>
merge(existing, incoming, { args, fieldName }) {
const merged = existing ? existing.slice(0) : []
if (args?.paginate) {
const { offset = 0 } = args.paginate
const { offset = 0 } = args.paginate as { offset: number }
for (let i = 0; i < incoming.length; ++i) {
merged[offset + i] = incoming[i]
}

View File

@ -1,49 +1,46 @@
import React, { useEffect, useReducer } from 'react'
import AlbumTitle from '../album/AlbumTitle'
import MediaGallery from '../photoGallery/MediaGallery'
import MediaGallery, {
MEDIA_GALLERY_FRAGMENT,
} from '../photoGallery/MediaGallery'
import AlbumBoxes from './AlbumBoxes'
import AlbumFilter from '../album/AlbumFilter'
import {
albumQuery_album_media_highRes,
albumQuery_album_media_thumbnail,
albumQuery_album_media_videoWeb,
albumQuery_album_subAlbums,
} from '../../Pages/AlbumPage/__generated__/albumQuery'
import {
photoGalleryReducer,
mediaGalleryReducer,
urlPresentModeSetupHook,
} from '../photoGallery/photoGalleryReducer'
} from '../photoGallery/mediaGalleryReducer'
import { MediaOrdering, SetOrderingFn } from '../../hooks/useOrderingParams'
import { MediaType } from '../../__generated__/globalTypes'
import { gql } from '@apollo/client'
import { AlbumGalleryFields } from './__generated__/AlbumGalleryFields'
type AlbumGalleryAlbum = {
__typename: 'Album'
id: string
title: string
subAlbums: albumQuery_album_subAlbums[]
media: {
__typename: 'Media'
id: string
type: MediaType
/**
* URL to display the media in a smaller resolution
*/
thumbnail: albumQuery_album_media_thumbnail | null
/**
* URL to display the photo in full resolution, will be null for videos
*/
highRes: albumQuery_album_media_highRes | null
/**
* URL to get the video in a web format that can be played in the browser, will be null for photos
*/
videoWeb: albumQuery_album_media_videoWeb | null
favorite?: boolean
blurhash: string | null
}[]
}
export const ALBUM_GALLERY_FRAGMENT = gql`
${MEDIA_GALLERY_FRAGMENT}
fragment AlbumGalleryFields on Album {
id
title
subAlbums(order: { order_by: "title", order_direction: $orderDirection }) {
id
title
thumbnail {
id
thumbnail {
url
}
}
}
media(
paginate: { limit: $limit, offset: $offset }
order: { order_by: $mediaOrderBy, order_direction: $orderDirection }
onlyFavorites: $onlyFavorites
) {
...MediaGalleryFields
}
}
`
type AlbumGalleryProps = {
album?: AlbumGalleryAlbum
album?: AlbumGalleryFields
loading?: boolean
customAlbumLink?(albumID: string): string
showFilter?: boolean
@ -68,7 +65,7 @@ const AlbumGallery = React.forwardRef(
}: AlbumGalleryProps,
ref: React.ForwardedRef<HTMLDivElement>
) => {
const [mediaState, dispatchMedia] = useReducer(photoGalleryReducer, {
const [mediaState, dispatchMedia] = useReducer(mediaGalleryReducer, {
presenting: false,
activeIndex: -1,
media: album?.media || [],

View File

@ -0,0 +1,106 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { MediaType } from './../../../__generated__/globalTypes'
// ====================================================
// GraphQL fragment: AlbumGalleryFields
// ====================================================
export interface AlbumGalleryFields_subAlbums_thumbnail_thumbnail {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
}
export interface AlbumGalleryFields_subAlbums_thumbnail {
__typename: 'Media'
id: string
/**
* URL to display the media in a smaller resolution
*/
thumbnail: AlbumGalleryFields_subAlbums_thumbnail_thumbnail | null
}
export interface AlbumGalleryFields_subAlbums {
__typename: 'Album'
id: string
title: string
/**
* An image in this album used for previewing this album
*/
thumbnail: AlbumGalleryFields_subAlbums_thumbnail | null
}
export interface AlbumGalleryFields_media_thumbnail {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
/**
* Width of the image in pixels
*/
width: number
/**
* Height of the image in pixels
*/
height: number
}
export interface AlbumGalleryFields_media_highRes {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
}
export interface AlbumGalleryFields_media_videoWeb {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
}
export interface AlbumGalleryFields_media {
__typename: 'Media'
id: string
type: MediaType
/**
* A short string that can be used to generate a blured version of the media, to show while the original is loading
*/
blurhash: string | null
/**
* URL to display the media in a smaller resolution
*/
thumbnail: AlbumGalleryFields_media_thumbnail | null
/**
* URL to display the photo in full resolution, will be null for videos
*/
highRes: AlbumGalleryFields_media_highRes | null
/**
* URL to get the video in a web format that can be played in the browser, will be null for photos
*/
videoWeb: AlbumGalleryFields_media_videoWeb | null
favorite: boolean
}
export interface AlbumGalleryFields {
__typename: 'Album'
id: string
title: string
/**
* The albums contained in this album
*/
subAlbums: AlbumGalleryFields_subAlbums[]
/**
* The media inside this album
*/
media: AlbumGalleryFields_media[]
}

View File

@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'
import React from 'react'
import Layout from './Layout'
test('Layout component', async () => {
test('Layout component', () => {
render(
<Layout title="Test title">
<p>layout_content</p>

View File

@ -9,7 +9,7 @@ import MainMenu, { MAPBOX_QUERY } from './MainMenu'
vi.mock('../../helpers/authentication.ts')
const authTokenMock = authentication.authToken // as vi.MockedFunction<typeof authentication.authToken>
const authTokenMock = vi.mocked(authentication.authToken)
afterEach(() => {
authTokenMock.mockClear()

View File

@ -5,6 +5,7 @@ import { authToken } from '../../helpers/authentication'
import { useTranslation } from 'react-i18next'
import { mapboxEnabledQuery } from '../../__generated__/mapboxEnabledQuery'
import { tailwindClassNames } from '../../helpers/utils'
import { faceDetectionEnabled } from './__generated__/faceDetectionEnabled'
export const MAPBOX_QUERY = gql`
query mapboxEnabledQuery {
@ -27,7 +28,7 @@ type MenuButtonProps = {
background: string
activeClasses?: string
className?: string
icon?: React.ReactChild
icon?: React.ReactNode
}
const MenuButton = ({
@ -48,7 +49,7 @@ const MenuButton = ({
`rounded-lg my-2 outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-dark-bg`,
className,
{
[`ring-4 lg:ring-4 ${activeClasses}`]: isActive,
[`ring-4 lg:ring-4 ${activeClasses ?? ''}`]: isActive,
}
)
}
@ -77,7 +78,7 @@ export const MainMenu = () => {
? useQuery<mapboxEnabledQuery>(MAPBOX_QUERY)
: null
const faceDetectionEnabledQuery = authToken()
? useQuery(FACE_DETECTION_ENABLED_QUERY)
? useQuery<faceDetectionEnabled>(FACE_DETECTION_ENABLED_QUERY)
: null
const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken

View File

@ -15,5 +15,5 @@ export interface mapboxToken {
/**
* Get media owned by the logged in user, returned in GeoJson format
*/
myMediaGeoJson: any
myMediaGeoJson: Any
}

View File

@ -19,7 +19,7 @@ const NOTIFICATION_SUBSCRIPTION = gql`
}
`
const messageTimeoutHandles = new Map()
const messageTimeoutHandles = new Map<string, number>()
export interface Message {
key: string
@ -101,7 +101,7 @@ const SubscriptionsHook = ({
const timeoutHandle = setTimeout(() => {
setMessages(messages => messages.filter(m => m.key != msg.key))
}, msg.timeout)
}, msg.timeout) as unknown as number
messageTimeoutHandles.set(msg.key, timeoutHandle)
}

View File

@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react'
import React from 'react'
import { MediaType } from '../../__generated__/globalTypes'
import MediaGallery from './MediaGallery'
import { PhotoGalleryState } from './photoGalleryReducer'
import { MediaGalleryState } from './mediaGalleryReducer'
vi.mock('./photoGalleryMutations', () => ({
useMarkFavoriteMutation: () => [vi.fn()],
@ -12,7 +12,7 @@ vi.mock('./photoGalleryMutations', () => ({
test('photo gallery with media', () => {
const dispatchMedia = vi.fn()
const mediaState: PhotoGalleryState = {
const mediaState: MediaGalleryState = {
activeIndex: 0,
media: [
{
@ -71,7 +71,7 @@ describe('photo gallery presenting', () => {
const dispatchMedia = vi.fn()
test('not presenting', () => {
const mediaStateNoPresent: PhotoGalleryState = {
const mediaStateNoPresent: MediaGalleryState = {
activeIndex: -1,
media: [],
presenting: false,
@ -89,7 +89,7 @@ describe('photo gallery presenting', () => {
})
test('presenting', () => {
const mediaStatePresent: PhotoGalleryState = {
const mediaStatePresent: MediaGalleryState = {
activeIndex: 0,
media: [
{

View File

@ -5,8 +5,8 @@ import PresentView from './presentView/PresentView'
import {
openPresentModeAction,
PhotoGalleryAction,
PhotoGalleryState,
} from './photoGalleryReducer'
MediaGalleryState,
} from './mediaGalleryReducer'
import {
toggleFavoriteAction,
useMarkFavoriteMutation,
@ -56,7 +56,7 @@ export const MEDIA_GALLERY_FRAGMENT = gql`
type MediaGalleryProps = {
loading: boolean
mediaState: PhotoGalleryState
mediaState: MediaGalleryState
dispatchMedia: React.Dispatch<PhotoGalleryAction>
}

View File

@ -1,7 +1,7 @@
import React, { useEffect } from 'react'
import { MediaGalleryFields } from './__generated__/MediaGalleryFields'
export interface PhotoGalleryState {
export interface MediaGalleryState {
presenting: boolean
activeIndex: number
media: MediaGalleryFields[]
@ -18,10 +18,10 @@ export type PhotoGalleryAction =
| { type: 'selectImage'; index: number }
| { type: 'replaceMedia'; media: MediaGalleryFields[] }
export function photoGalleryReducer(
state: PhotoGalleryState,
export function mediaGalleryReducer(
state: MediaGalleryState,
action: PhotoGalleryAction
): PhotoGalleryState {
): MediaGalleryState {
switch (action.type) {
case 'nextImage':
return {
@ -69,15 +69,19 @@ export function photoGalleryReducer(
}
}
export interface MediaGalleryPopStateEvent extends PopStateEvent {
state: MediaGalleryState
}
export const urlPresentModeSetupHook = ({
dispatchMedia,
openPresentMode,
}: {
dispatchMedia: React.Dispatch<GalleryAction>
openPresentMode: (event: PopStateEvent) => void
openPresentMode: (event: MediaGalleryPopStateEvent) => void
}) => {
useEffect(() => {
const urlChangeListener = (event: PopStateEvent) => {
const urlChangeListener = (event: MediaGalleryPopStateEvent) => {
if (event.state.presenting === true) {
openPresentMode(event)
} else {

View File

@ -1,5 +1,5 @@
import { MediaGalleryFields } from './__generated__/MediaGalleryFields'
import { gql, MutationFunction, useMutation } from '@apollo/client'
import { PhotoGalleryProps_Media } from './MediaGallery'
import {
markMediaFavorite,
markMediaFavoriteVariables,
@ -24,7 +24,7 @@ export const toggleFavoriteAction = ({
media,
markFavorite,
}: {
media: PhotoGalleryProps_Media
media: MediaGalleryFields
markFavorite: MutationFunction<markMediaFavorite, markMediaFavoriteVariables>
}) => {
return markFavorite({

View File

@ -1,8 +1,8 @@
import { photoGalleryReducer, PhotoGalleryState } from './photoGalleryReducer'
import { mediaGalleryReducer, MediaGalleryState } from './mediaGalleryReducer'
import { MediaType } from '../../__generated__/globalTypes'
describe('photo gallery reducer', () => {
const defaultState: PhotoGalleryState = {
const defaultState: MediaGalleryState = {
presenting: false,
activeIndex: 0,
media: [
@ -34,13 +34,13 @@ describe('photo gallery reducer', () => {
}
test('next image', () => {
expect(photoGalleryReducer(defaultState, { type: 'nextImage' })).toEqual({
expect(mediaGalleryReducer(defaultState, { type: 'nextImage' })).toEqual({
...defaultState,
activeIndex: 1,
})
expect(
photoGalleryReducer(
mediaGalleryReducer(
{ ...defaultState, activeIndex: 2 },
{ type: 'nextImage' }
)
@ -52,14 +52,14 @@ describe('photo gallery reducer', () => {
test('previous image', () => {
expect(
photoGalleryReducer(defaultState, { type: 'previousImage' })
mediaGalleryReducer(defaultState, { type: 'previousImage' })
).toEqual({
...defaultState,
activeIndex: 2,
})
expect(
photoGalleryReducer(
mediaGalleryReducer(
{ ...defaultState, activeIndex: 2 },
{ type: 'previousImage' }
)
@ -71,21 +71,21 @@ describe('photo gallery reducer', () => {
test('select image', () => {
expect(
photoGalleryReducer(defaultState, { type: 'selectImage', index: 1 })
mediaGalleryReducer(defaultState, { type: 'selectImage', index: 1 })
).toEqual({
...defaultState,
activeIndex: 1,
})
expect(
photoGalleryReducer(defaultState, { type: 'selectImage', index: 100 })
mediaGalleryReducer(defaultState, { type: 'selectImage', index: 100 })
).toEqual({
...defaultState,
activeIndex: 2,
})
expect(
photoGalleryReducer(defaultState, { type: 'selectImage', index: -5 })
mediaGalleryReducer(defaultState, { type: 'selectImage', index: -5 })
).toEqual({
...defaultState,
activeIndex: 0,
@ -93,7 +93,7 @@ describe('photo gallery reducer', () => {
})
test('present mode', () => {
const openState = photoGalleryReducer(defaultState, {
const openState = mediaGalleryReducer(defaultState, {
type: 'openPresentMode',
activeIndex: 10,
})
@ -104,7 +104,7 @@ describe('photo gallery reducer', () => {
})
expect(
photoGalleryReducer(openState, { type: 'closePresentMode' })
mediaGalleryReducer(openState, { type: 'closePresentMode' })
).toEqual({
...openState,
presenting: false,

View File

@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from 'react'
import styled from 'styled-components'
import { debounce, DebouncedFn } from '../../../helpers/utils'
import { closePresentModeAction, GalleryAction } from '../photoGalleryReducer'
import { closePresentModeAction, GalleryAction } from '../mediaGalleryReducer'
import ExitIcon from './icons/Exit'
import NextIcon from './icons/Next'

View File

@ -2,7 +2,7 @@ import React, { useEffect } from 'react'
import styled, { createGlobalStyle } from 'styled-components'
import PresentNavigationOverlay from './PresentNavigationOverlay'
import PresentMedia from './PresentMedia'
import { closePresentModeAction, GalleryAction } from '../photoGalleryReducer'
import { closePresentModeAction, GalleryAction } from '../mediaGalleryReducer'
import { MediaGalleryFields } from '../__generated__/MediaGalleryFields'
const StyledContainer = styled.div`

View File

@ -14,7 +14,7 @@ const authToken = vi.mocked(authentication.authToken)
describe('AuthorizedRoute component', () => {
const AuthorizedComponent = () => <div>authorized content</div>
test('not logged in', async () => {
test('not logged in', () => {
authToken.mockImplementation(() => null)
render(
@ -37,7 +37,7 @@ describe('AuthorizedRoute component', () => {
expect(screen.queryByText('authorized content')).toBeNull()
})
test('logged in', async () => {
test('logged in', () => {
authToken.mockImplementation(() => 'token-here')
render(

View File

@ -2,10 +2,12 @@ import { useLazyQuery } from '@apollo/client'
import React, { useEffect } from 'react'
import { Navigate } from 'react-router-dom'
import { authToken } from '../../helpers/authentication'
import { adminQuery } from '../../__generated__/adminQuery'
import { ADMIN_QUERY } from '../layout/Layout'
export const useIsAdmin = () => {
const [fetchAdminQuery, { data, called }] = useLazyQuery(ADMIN_QUERY)
const [fetchAdminQuery, { data, called }] =
useLazyQuery<adminQuery>(ADMIN_QUERY)
useEffect(() => {
if (authToken() && !called) {

View File

@ -29,7 +29,7 @@ describe('routes', () => {
expect(screen.getByText('mocked login page')).toBeInTheDocument()
})
test('invalid page should print a "not found" message', async () => {
test('invalid page should print a "not found" message', () => {
render(
<MemoryRouter initialEntries={['/random_non_existent_page']}>
<Routes />

View File

@ -5,7 +5,7 @@ import { MediaSidebarMedia } from './MediaSidebar'
import { MediaType } from '../../../__generated__/globalTypes'
describe('ExifDetails', () => {
test('without EXIF information', async () => {
test('without EXIF information', () => {
const media: MediaSidebarMedia = {
id: '1730',
title: 'media_name.jpg',
@ -45,14 +45,14 @@ describe('ExifDetails', () => {
expect(screen.queryByText('Coordinates')).not.toBeInTheDocument()
})
test('with EXIF information', async () => {
test('with EXIF information', () => {
const media: MediaSidebarMedia = {
id: '1730',
title: 'media_name.jpg',
type: MediaType.Photo,
exif: {
id: '1666',
description: "Media description",
description: 'Media description',
camera: 'Canon EOS R',
maker: 'Canon',
lens: 'TAMRON SP 24-70mm F/2.8',

View File

@ -92,7 +92,7 @@ const ExifDetails = ({ media }: ExifDetailsProps) => {
let metadata = Object.keys(videoMetadata)
.filter(x => !['id', '__typename', 'width', 'height'].includes(x))
.reduce((prev, curr) => {
const value = videoMetadata[curr as string]
const value = videoMetadata[curr]
if (isNil(value)) return prev
return {

View File

@ -101,7 +101,7 @@ export interface sidebarMediaQuery_media_exif {
* The name of the lens
*/
lens: string | null
dateShot: any | null
dateShot: Time | null
/**
* The exposure time of the image
*/

View File

@ -379,7 +379,7 @@ type SidebarShareProps = {
isPhoto: boolean
loading: boolean
shares?: sidebarGetPhotoShares_media_shares[]
shareItem(item: { variables: { id: string } }): void
shareItem: (item: { variables: { id: string } }) => Promise<unknown>
}
const SidebarShare = ({

View File

@ -51,7 +51,7 @@ const formatBytes = (t: TranslationFn) => (bytes: number) => {
case 4:
return t('sidebar.download.filesize.tera_byte', '{{count}} TB', { count })
default:
return new Error(`invalid byte value: ${bytes}`)
throw new Error(`invalid byte value: ${bytes}`)
}
}
@ -172,7 +172,7 @@ const downloadMediaShowProgress =
return content
}
const downloadBlob = async (blob: Blob, filename: string) => {
const downloadBlob = (blob: Blob, filename: string) => {
const objectUrl = window.URL.createObjectURL(blob)
const anchor = document.createElement('a')

View File

@ -22,5 +22,5 @@ export interface sidebarAlbumAddShare {
export interface sidebarAlbumAddShareVariables {
id: string
password?: string | null
expire?: any | null
expire?: Time | null
}

View File

@ -22,5 +22,5 @@ export interface sidebarPhotoAddShare {
export interface sidebarPhotoAddShareVariables {
id: string
password?: string | null
expire?: any | null
expire?: Time | null
}

View File

@ -15,7 +15,7 @@ import {
getActiveTimelineImage as getActiveTimelineMedia,
timelineGalleryReducer,
} from './timelineGalleryReducer'
import { urlPresentModeSetupHook } from '../photoGallery/photoGalleryReducer'
import { urlPresentModeSetupHook } from '../photoGallery/mediaGalleryReducer'
import TimelineFilters from './TimelineFilters'
import client from '../../apolloClient'

View File

@ -13,7 +13,7 @@ export interface earliestMedia_myMedia {
/**
* The date the image was shot or the date it was imported as a fallback
*/
date: any
date: Time
}
export interface earliestMedia {

View File

@ -84,7 +84,7 @@ export interface myTimeline_myTimeline {
/**
* The date the image was shot or the date it was imported as a fallback
*/
date: any
date: Time
}
export interface myTimeline {
@ -98,5 +98,5 @@ export interface myTimelineVariables {
onlyFavorites?: boolean | null
limit?: number | null
offset?: number | null
fromDate?: any | null
fromDate?: Time | null
}

View File

@ -1,7 +1,7 @@
import React from 'react'
import { myTimeline_myTimeline } from './__generated__/myTimeline'
import { TimelineGroup, TimelineGroupAlbum } from './TimelineGallery'
import { GalleryAction } from '../photoGallery/photoGalleryReducer'
import { GalleryAction } from '../photoGallery/mediaGalleryReducer'
import { isNil } from '../../helpers/utils'
export interface TimelineMediaIndex {
@ -179,8 +179,8 @@ export const getActiveTimelineImage = ({
mediaState: TimelineGalleryState
}) => {
if (
Object.values(mediaState.activeIndex).reduce(
(acc, next) => next == -1 || acc,
Object.values(mediaState.activeIndex).reduce<boolean>(
(acc, next) => next === -1 || acc,
false
)
) {

View File

@ -1,21 +1,19 @@
import classNames, { Argument as ClassNamesArg } from 'classnames'
import { overrideTailwindClasses } from 'tailwind-override'
// import { overrideTailwindClasses } from 'tailwind-override'
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface DebouncedFn<F extends (...args: any[]) => any> {
export interface DebouncedFn<F extends (...args: unknown[]) => unknown> {
(...args: Parameters<F>): void
cancel(): void
}
export function debounce<T extends (...args: any[]) => any>(
func: T,
export function debounce<F extends (...args: unknown[]) => unknown>(
func: F,
wait: number,
triggerRising?: boolean
): DebouncedFn<T> {
): DebouncedFn<F> {
let timeout: number | undefined = undefined
const debounced = (...args: Parameters<T>) => {
const debounced = (...args: Parameters<F>) => {
if (timeout) {
clearTimeout(timeout)
timeout = undefined
@ -37,11 +35,12 @@ export function debounce<T extends (...args: any[]) => any>(
return debounced
}
export function isNil(value: any): value is undefined | null {
export function isNil(value: unknown): value is undefined | null {
return value === undefined || value === null
}
export function exhaustiveCheck(value: never) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Exhaustive check failed with value: ${value}`)
}

View File

@ -4,24 +4,20 @@ import { useCallback, useEffect, useRef, useState } from 'react'
interface ScrollPaginationArgs<D> {
loading: boolean
data: D | undefined
fetchMore(args: {
fetchMore: (args: {
variables: { offset: number }
}): Promise<ApolloQueryResult<D>>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getItems(data: D): any[]
}) => Promise<ApolloQueryResult<D>>
getItems: (data: D) => unknown[]
}
type ScrollPaginationResult = {
finished: boolean
containerElem(node: null | Element): void
containerElem: (node: null | Element) => void
}
function useScrollPagination<D>({
loading,
fetchMore,
data,
getItems,
}: ScrollPaginationArgs<D>): ScrollPaginationResult {
const useScrollPagination: <D>(
args: ScrollPaginationArgs<D>
) => ScrollPaginationResult = ({ loading, fetchMore, data, getItems }) => {
const observer = useRef<IntersectionObserver | null>(null)
const observerElem = useRef<Element | null>(null)
const [finished, setFinished] = useState(false)

View File

@ -3,12 +3,12 @@ import { useState } from 'react'
export type UrlKeyValuePair = { key: string; value: string | null }
export type UrlParams = {
getParam(key: string, defaultValue?: string | null): string | null
setParam(key: string, value: string | null): void
setParams(pairs: UrlKeyValuePair[]): void
getParam: (key: string, defaultValue?: string | null) => string | null
setParam: (key: string, value: string | null) => void
setParams: (pairs: UrlKeyValuePair[]) => void
}
function useURLParameters(): UrlParams {
const useURLParameters: () => UrlParams = () => {
const [urlString, setUrlString] = useState(document.location.href)
const url = new URL(urlString)

View File

@ -10,7 +10,9 @@ import { exhaustiveCheck, isNil } from './helpers/utils'
export type TranslationFn = TFunction<'translation'>
export function setupLocalization(): void {
i18n.use(initReactI18next).init({
i18n
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
returnNull: false,
@ -24,6 +26,7 @@ export function setupLocalization(): void {
useSuspense: import.meta.env.PROD,
},
})
.catch(err => console.error('Failed to setup localization', err))
}
const SITE_TRANSLATION = gql`

View File

@ -78,6 +78,7 @@ registerRoute(
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', event => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}

View File

@ -45,12 +45,16 @@ export function register(config?: Config) {
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
navigator.serviceWorker.ready
.then(() => {
console.log(
'This web app is being served cache-first by a service worker.' +
'worker. To learn more, visit https://cra.link/PWA'
)
})
.catch(() => {
console.log('Failed to load service worker')
})
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config)
@ -139,7 +143,7 @@ export function unregister() {
.then(registration => {
registration.unregister()
})
.catch(error => {
.catch((error: Error) => {
console.error(error.message)
})
}

View File

@ -17,5 +17,5 @@
"jsx": "react-jsx",
"types": ["vitest/globals", "@testing-library/jest-dom"]
},
"include": ["src", "vite.config.ts"]
"include": ["src", "vite.config.ts", ".eslintrc.js", "apollo.config.js"]
}