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 = { module.exports = {
root: true, root: true,
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
@ -11,6 +13,7 @@ module.exports = {
'plugin:react/recommended', 'plugin:react/recommended',
'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'prettier', 'prettier',
], ],
globals: { globals: {
@ -21,6 +24,8 @@ module.exports = {
require: 'readonly', require: 'readonly',
}, },
parserOptions: { parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true,
}, },
@ -41,30 +46,13 @@ module.exports = {
version: 'detect', version: 'detect',
}, },
}, },
// parser: 'babel-eslint',
overrides: [ 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: { rules: {
'@typescript-eslint/explicit-module-boundary-types': 'off', '@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/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0", "@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1", "@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", "@vitest/ui": "^0.17.1",
"apollo": "2.34.0", "apollo": "2.34.0",
"apollo-language-server": "1.26.9", "apollo-language-server": "1.26.9",
@ -5326,13 +5328,13 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA=="
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.30.5", "version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz",
"integrity": "sha512-lftkqRoBvc28VFXEoRgyZuztyVUQ04JvUnATSPtIRFAccbXTWL6DEtXGYMcbg998kXw1NLUJm7rTQ9eUt+q6Ig==", "integrity": "sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.30.5", "@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/type-utils": "5.30.5", "@typescript-eslint/type-utils": "5.30.6",
"@typescript-eslint/utils": "5.30.5", "@typescript-eslint/utils": "5.30.6",
"debug": "^4.3.4", "debug": "^4.3.4",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"ignore": "^5.2.0", "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": { "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
"version": "7.3.7", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -5390,13 +5485,13 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "5.30.5", "version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.5.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.6.tgz",
"integrity": "sha512-zj251pcPXI8GO9NDKWWmygP6+UjwWmrdf9qMW/L/uQJBM/0XbU2inxe5io/234y/RCvwpKEYjZ6c1YrXERkK4Q==", "integrity": "sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "5.30.5", "@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/types": "5.30.5", "@typescript-eslint/types": "5.30.6",
"@typescript-eslint/typescript-estree": "5.30.5", "@typescript-eslint/typescript-estree": "5.30.6",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "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": { "node_modules/@typescript-eslint/scope-manager": {
"version": "5.30.5", "version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.5.tgz", "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": { "node_modules/@typescript-eslint/type-utils": {
"version": "5.30.5", "version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.5.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz",
"integrity": "sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw==", "integrity": "sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==",
"dependencies": { "dependencies": {
"@typescript-eslint/utils": "5.30.5", "@typescript-eslint/utils": "5.30.6",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "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": { "node_modules/@typescript-eslint/types": {
"version": "5.30.5", "version": "5.30.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.5.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.5.tgz",
@ -28832,13 +29118,13 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA=="
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "5.30.5", "version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz",
"integrity": "sha512-lftkqRoBvc28VFXEoRgyZuztyVUQ04JvUnATSPtIRFAccbXTWL6DEtXGYMcbg998kXw1NLUJm7rTQ9eUt+q6Ig==", "integrity": "sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.30.5", "@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/type-utils": "5.30.5", "@typescript-eslint/type-utils": "5.30.6",
"@typescript-eslint/utils": "5.30.5", "@typescript-eslint/utils": "5.30.6",
"debug": "^4.3.4", "debug": "^4.3.4",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"ignore": "^5.2.0", "ignore": "^5.2.0",
@ -28847,6 +29133,56 @@
"tsutils": "^3.21.0" "tsutils": "^3.21.0"
}, },
"dependencies": { "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": { "semver": {
"version": "7.3.7", "version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
@ -28866,14 +29202,61 @@
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "5.30.5", "version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.5.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.6.tgz",
"integrity": "sha512-zj251pcPXI8GO9NDKWWmygP6+UjwWmrdf9qMW/L/uQJBM/0XbU2inxe5io/234y/RCvwpKEYjZ6c1YrXERkK4Q==", "integrity": "sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==",
"requires": { "requires": {
"@typescript-eslint/scope-manager": "5.30.5", "@typescript-eslint/scope-manager": "5.30.6",
"@typescript-eslint/types": "5.30.5", "@typescript-eslint/types": "5.30.6",
"@typescript-eslint/typescript-estree": "5.30.5", "@typescript-eslint/typescript-estree": "5.30.6",
"debug": "^4.3.4" "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": { "@typescript-eslint/scope-manager": {
@ -28886,13 +29269,73 @@
} }
}, },
"@typescript-eslint/type-utils": { "@typescript-eslint/type-utils": {
"version": "5.30.5", "version": "5.30.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.5.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz",
"integrity": "sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw==", "integrity": "sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==",
"requires": { "requires": {
"@typescript-eslint/utils": "5.30.5", "@typescript-eslint/utils": "5.30.6",
"debug": "^4.3.4", "debug": "^4.3.4",
"tsutils": "^3.21.0" "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": { "@typescript-eslint/types": {

View File

@ -11,10 +11,10 @@
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"build": "vite build", "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": "vitest",
"test:ci": "CI=true vitest --reporter verbose --run --coverage", "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", "extractTranslations": "i18next -c i18next-parser.config.js",
"prepare": "(cd .. && ./ui/node_modules/.bin/husky install)" "prepare": "(cd .. && ./ui/node_modules/.bin/husky install)"
}, },
@ -63,6 +63,8 @@
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0", "@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1", "@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", "@vitest/ui": "^0.17.1",
"apollo": "2.34.0", "apollo": "2.34.0",
"apollo-language-server": "1.26.9", "apollo-language-server": "1.26.9",

View File

@ -5,3 +5,16 @@ declare module '*.svg' {
export { ReactComponent } export { ReactComponent }
// export default content // 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 React, { useCallback } from 'react'
import { useQuery, gql } from '@apollo/client' 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 Layout from '../../components/layout/Layout'
import useURLParameters from '../../hooks/useURLParameters' import useURLParameters from '../../hooks/useURLParameters'
import useScrollPagination from '../../hooks/useScrollPagination' import useScrollPagination from '../../hooks/useScrollPagination'
@ -10,10 +12,9 @@ import { albumQuery, albumQueryVariables } from './__generated__/albumQuery'
import useOrderingParams from '../../hooks/useOrderingParams' import useOrderingParams from '../../hooks/useOrderingParams'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { isNil } from '../../helpers/utils' import { isNil } from '../../helpers/utils'
import { MEDIA_GALLERY_FRAGMENT } from '../../components/photoGallery/MediaGallery'
const ALBUM_QUERY = gql` const ALBUM_QUERY = gql`
${MEDIA_GALLERY_FRAGMENT} ${ALBUM_GALLERY_FRAGMENT}
query albumQuery( query albumQuery(
$id: ID! $id: ID!
@ -24,27 +25,7 @@ const ALBUM_QUERY = gql`
$offset: Int $offset: Int
) { ) {
album(id: $id) { album(id: $id) {
id ...AlbumGalleryFields
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
}
} }
} }
` `

View File

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

View File

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

View File

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

View File

@ -106,7 +106,7 @@ const LoginForm = () => {
<input <input
type="submit" type="submit"
disabled={loading} 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" 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 <MessageBox

View File

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

View File

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

View File

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

View File

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

View File

@ -11,5 +11,5 @@ export interface mediaGeoJson {
/** /**
* Get media owned by the logged in user, returned in GeoJson format * 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 { PresentMarker } from './PlacesPage'
import { import {
PhotoGalleryState, MediaGalleryState,
PhotoGalleryAction, PhotoGalleryAction,
photoGalleryReducer, mediaGalleryReducer,
} from './../../components/photoGallery/photoGalleryReducer' } from '../../components/photoGallery/mediaGalleryReducer'
export interface PlacesState extends PhotoGalleryState { export interface PlacesState extends MediaGalleryState {
presentMarker?: PresentMarker presentMarker?: PresentMarker
} }
@ -36,6 +36,6 @@ export function placesReducer(
} }
} }
default: default:
return photoGalleryReducer(state, action) return mediaGalleryReducer(state, action)
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import { authToken } from '../../helpers/authentication'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { mapboxEnabledQuery } from '../../__generated__/mapboxEnabledQuery' import { mapboxEnabledQuery } from '../../__generated__/mapboxEnabledQuery'
import { tailwindClassNames } from '../../helpers/utils' import { tailwindClassNames } from '../../helpers/utils'
import { faceDetectionEnabled } from './__generated__/faceDetectionEnabled'
export const MAPBOX_QUERY = gql` export const MAPBOX_QUERY = gql`
query mapboxEnabledQuery { query mapboxEnabledQuery {
@ -27,7 +28,7 @@ type MenuButtonProps = {
background: string background: string
activeClasses?: string activeClasses?: string
className?: string className?: string
icon?: React.ReactChild icon?: React.ReactNode
} }
const MenuButton = ({ 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`, `rounded-lg my-2 outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-dark-bg`,
className, 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) ? useQuery<mapboxEnabledQuery>(MAPBOX_QUERY)
: null : null
const faceDetectionEnabledQuery = authToken() const faceDetectionEnabledQuery = authToken()
? useQuery(FACE_DETECTION_ENABLED_QUERY) ? useQuery<faceDetectionEnabled>(FACE_DETECTION_ENABLED_QUERY)
: null : null
const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken 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 * 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 { export interface Message {
key: string key: string
@ -101,7 +101,7 @@ const SubscriptionsHook = ({
const timeoutHandle = setTimeout(() => { const timeoutHandle = setTimeout(() => {
setMessages(messages => messages.filter(m => m.key != msg.key)) setMessages(messages => messages.filter(m => m.key != msg.key))
}, msg.timeout) }, msg.timeout) as unknown as number
messageTimeoutHandles.set(msg.key, timeoutHandle) messageTimeoutHandles.set(msg.key, timeoutHandle)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ describe('routes', () => {
expect(screen.getByText('mocked login page')).toBeInTheDocument() 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( render(
<MemoryRouter initialEntries={['/random_non_existent_page']}> <MemoryRouter initialEntries={['/random_non_existent_page']}>
<Routes /> <Routes />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ import {
getActiveTimelineImage as getActiveTimelineMedia, getActiveTimelineImage as getActiveTimelineMedia,
timelineGalleryReducer, timelineGalleryReducer,
} from './timelineGalleryReducer' } from './timelineGalleryReducer'
import { urlPresentModeSetupHook } from '../photoGallery/photoGalleryReducer' import { urlPresentModeSetupHook } from '../photoGallery/mediaGalleryReducer'
import TimelineFilters from './TimelineFilters' import TimelineFilters from './TimelineFilters'
import client from '../../apolloClient' 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 * The date the image was shot or the date it was imported as a fallback
*/ */
date: any date: Time
} }
export interface earliestMedia { 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 * The date the image was shot or the date it was imported as a fallback
*/ */
date: any date: Time
} }
export interface myTimeline { export interface myTimeline {
@ -98,5 +98,5 @@ export interface myTimelineVariables {
onlyFavorites?: boolean | null onlyFavorites?: boolean | null
limit?: number | null limit?: number | null
offset?: number | null offset?: number | null
fromDate?: any | null fromDate?: Time | null
} }

View File

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

View File

@ -1,21 +1,19 @@
import classNames, { Argument as ClassNamesArg } from 'classnames' import classNames, { Argument as ClassNamesArg } from 'classnames'
import { overrideTailwindClasses } from 'tailwind-override' 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 (...args: Parameters<F>): void
cancel(): void cancel(): void
} }
export function debounce<T extends (...args: any[]) => any>( export function debounce<F extends (...args: unknown[]) => unknown>(
func: T, func: F,
wait: number, wait: number,
triggerRising?: boolean triggerRising?: boolean
): DebouncedFn<T> { ): DebouncedFn<F> {
let timeout: number | undefined = undefined let timeout: number | undefined = undefined
const debounced = (...args: Parameters<T>) => { const debounced = (...args: Parameters<F>) => {
if (timeout) { if (timeout) {
clearTimeout(timeout) clearTimeout(timeout)
timeout = undefined timeout = undefined
@ -37,11 +35,12 @@ export function debounce<T extends (...args: any[]) => any>(
return debounced 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 return value === undefined || value === null
} }
export function exhaustiveCheck(value: never) { export function exhaustiveCheck(value: never) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Exhaustive check failed with value: ${value}`) 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> { interface ScrollPaginationArgs<D> {
loading: boolean loading: boolean
data: D | undefined data: D | undefined
fetchMore(args: { fetchMore: (args: {
variables: { offset: number } variables: { offset: number }
}): Promise<ApolloQueryResult<D>> }) => Promise<ApolloQueryResult<D>>
// eslint-disable-next-line @typescript-eslint/no-explicit-any getItems: (data: D) => unknown[]
getItems(data: D): any[]
} }
type ScrollPaginationResult = { type ScrollPaginationResult = {
finished: boolean finished: boolean
containerElem(node: null | Element): void containerElem: (node: null | Element) => void
} }
function useScrollPagination<D>({ const useScrollPagination: <D>(
loading, args: ScrollPaginationArgs<D>
fetchMore, ) => ScrollPaginationResult = ({ loading, fetchMore, data, getItems }) => {
data,
getItems,
}: ScrollPaginationArgs<D>): ScrollPaginationResult {
const observer = useRef<IntersectionObserver | null>(null) const observer = useRef<IntersectionObserver | null>(null)
const observerElem = useRef<Element | null>(null) const observerElem = useRef<Element | null>(null)
const [finished, setFinished] = useState(false) 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 UrlKeyValuePair = { key: string; value: string | null }
export type UrlParams = { export type UrlParams = {
getParam(key: string, defaultValue?: string | null): string | null getParam: (key: string, defaultValue?: string | null) => string | null
setParam(key: string, value: string | null): void setParam: (key: string, value: string | null) => void
setParams(pairs: UrlKeyValuePair[]): void setParams: (pairs: UrlKeyValuePair[]) => void
} }
function useURLParameters(): UrlParams { const useURLParameters: () => UrlParams = () => {
const [urlString, setUrlString] = useState(document.location.href) const [urlString, setUrlString] = useState(document.location.href)
const url = new URL(urlString) const url = new URL(urlString)

View File

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

View File

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

View File

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

View File

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