1
Fork 0

Merge pull request #715 from photoview/ui-gql-fragments

Refactor UI: Add graphql fragments + eslint
This commit is contained in:
Viktor Strate Kløvedal 2022-07-13 18:08:31 +02:00 committed by GitHub
commit a17a680457
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 1104 additions and 469 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'
@ -12,6 +14,8 @@ import { useParams } from 'react-router-dom'
import { isNil } from '../../helpers/utils'
const ALBUM_QUERY = gql`
${ALBUM_GALLERY_FRAGMENT}
query albumQuery(
$id: ID!
$onlyFavorites: Boolean
@ -21,41 +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
) {
id
type
blurhash
thumbnail {
url
width
height
}
highRes {
url
}
videoWeb {
url
}
favorite
}
...AlbumGalleryFields
}
}
`

View File

@ -8,35 +8,35 @@
// ====================================================
export interface getMyAlbums_myAlbums_thumbnail_thumbnail {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
}
export interface getMyAlbums_myAlbums_thumbnail {
__typename: "Media";
id: string;
__typename: 'Media'
id: string
/**
* URL to display the media in a smaller resolution
*/
thumbnail: getMyAlbums_myAlbums_thumbnail_thumbnail | null;
thumbnail: getMyAlbums_myAlbums_thumbnail_thumbnail | null
}
export interface getMyAlbums_myAlbums {
__typename: "Album";
id: string;
title: string;
__typename: 'Album'
id: string
title: string
/**
* An image in this album used for previewing this album
*/
thumbnail: getMyAlbums_myAlbums_thumbnail | null;
thumbnail: getMyAlbums_myAlbums_thumbnail | null
}
export interface getMyAlbums {
/**
* List of albums owned by the logged in user.
*/
myAlbums: getMyAlbums_myAlbums[];
myAlbums: getMyAlbums_myAlbums[]
}

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

@ -8,13 +8,13 @@
// ====================================================
export interface CheckInitialSetup_siteInfo {
__typename: "SiteInfo";
__typename: 'SiteInfo'
/**
* Whether or not the initial setup wizard should be shown
*/
initialSetup: boolean;
initialSetup: boolean
}
export interface CheckInitialSetup {
siteInfo: CheckInitialSetup_siteInfo;
siteInfo: CheckInitialSetup_siteInfo
}

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 }) => (
<ProtectedImage {...rest} />
))<{ origin: { x: number; y: number }; selectable: boolean; scale: number }>`
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} />
)
)`
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

@ -2,8 +2,8 @@ import { gql, useQuery } from '@apollo/client'
import React, { useEffect, useReducer } from 'react'
import { useTranslation } from 'react-i18next'
import PaginateLoader from '../../../components/PaginateLoader'
import PhotoGallery from '../../../components/photoGallery/PhotoGallery'
import { photoGalleryReducer } from '../../../components/photoGallery/photoGalleryReducer'
import MediaGallery from '../../../components/photoGallery/MediaGallery'
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: [],
@ -91,7 +91,7 @@ const SingleFaceGroup = ({ faceGroupID }: SingleFaceGroupProps) => {
<div ref={containerElem}>
<FaceGroupTitle faceGroup={faceGroup} />
<div>
<PhotoGallery
<MediaGallery
loading={loading}
dispatchMedia={dispatchMedia}
mediaState={mediaState}

View File

@ -8,18 +8,18 @@
// ====================================================
export interface combineFaces_combineFaceGroups {
__typename: "FaceGroup";
id: string;
__typename: 'FaceGroup'
id: string
}
export interface combineFaces {
/**
* Merge two face groups into a single one, all ImageFaces from source will be moved to destination
*/
combineFaceGroups: combineFaces_combineFaceGroups;
combineFaceGroups: combineFaces_combineFaceGroups
}
export interface combineFacesVariables {
destID: string;
srcID: string;
destID: string
srcID: string
}

View File

@ -8,24 +8,24 @@
// ====================================================
export interface moveImageFaces_moveImageFaces_imageFaces {
__typename: "ImageFace";
id: string;
__typename: 'ImageFace'
id: string
}
export interface moveImageFaces_moveImageFaces {
__typename: "FaceGroup";
id: string;
imageFaces: moveImageFaces_moveImageFaces_imageFaces[];
__typename: 'FaceGroup'
id: string
imageFaces: moveImageFaces_moveImageFaces_imageFaces[]
}
export interface moveImageFaces {
/**
* Move a list of ImageFaces to another face group
*/
moveImageFaces: moveImageFaces_moveImageFaces;
moveImageFaces: moveImageFaces_moveImageFaces
}
export interface moveImageFacesVariables {
faceIDs: string[];
destFaceGroupID: string;
faceIDs: string[]
destFaceGroupID: string
}

View File

@ -8,13 +8,13 @@
// ====================================================
export interface recognizeUnlabeledFaces_recognizeUnlabeledFaces {
__typename: "ImageFace";
id: string;
__typename: 'ImageFace'
id: string
}
export interface recognizeUnlabeledFaces {
/**
* Check all unlabeled faces to see if they match a labeled FaceGroup, and move them if they match
*/
recognizeUnlabeledFaces: recognizeUnlabeledFaces_recognizeUnlabeledFaces[];
recognizeUnlabeledFaces: recognizeUnlabeledFaces_recognizeUnlabeledFaces[]
}

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,35 +51,35 @@ const AddUserRow = ({ setShow, show, onUserAdded }: AddUserRowProps) => {
onUserAdded()
}
const [addRootPath, { loading: addRootPathLoading }] = useMutation(
USER_ADD_ROOT_PATH_MUTATION,
{
onCompleted: () => {
finished()
},
onError: () => {
finished()
},
}
)
const [addRootPath, { loading: addRootPathLoading }] = useMutation<
userAddRootPath,
userAddRootPathVariables
>(USER_ADD_ROOT_PATH_MUTATION, {
onCompleted: () => {
finished()
},
onError: () => {
finished()
},
})
const [createUser, { loading: createUserLoading }] = useMutation(
CREATE_USER_MUTATION,
{
onCompleted: ({ createUser: { id } }) => {
if (state.rootPath) {
addRootPath({
variables: {
id: id,
rootPath: state.rootPath,
},
})
} else {
finished()
}
},
}
)
const [createUser, { loading: createUserLoading }] = useMutation<
createUser,
createUserVariables
>(CREATE_USER_MUTATION, {
onCompleted: ({ createUser: { id } }) => {
if (state.rootPath) {
addRootPath({
variables: {
id: id,
rootPath: state.rootPath,
},
})
} else {
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

@ -8,17 +8,17 @@
// ====================================================
export interface scanUser_scanUser {
__typename: "ScannerResult";
success: boolean;
__typename: 'ScannerResult'
success: boolean
}
export interface scanUser {
/**
* Scan a single user for new media
*/
scanUser: scanUser_scanUser;
scanUser: scanUser_scanUser
}
export interface scanUserVariables {
userId: string;
userId: string
}

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

@ -12,9 +12,9 @@ export interface changeScanIntervalMutation {
* Set how often, in seconds, the server should automatically scan for new media,
* a value of 0 will disable periodic scans
*/
setPeriodicScanInterval: number;
setPeriodicScanInterval: number
}
export interface changeScanIntervalMutationVariables {
interval: number;
interval: number
}

View File

@ -8,13 +8,13 @@
// ====================================================
export interface concurrentWorkersQuery_siteInfo {
__typename: "SiteInfo";
__typename: 'SiteInfo'
/**
* How many max concurrent scanner jobs that should run at once
*/
concurrentWorkers: number;
concurrentWorkers: number
}
export interface concurrentWorkersQuery {
siteInfo: concurrentWorkersQuery_siteInfo;
siteInfo: concurrentWorkersQuery_siteInfo
}

View File

@ -8,14 +8,14 @@
// ====================================================
export interface scanAllMutation_scanAll {
__typename: "ScannerResult";
success: boolean;
message: string | null;
__typename: 'ScannerResult'
success: boolean
message: string | null
}
export interface scanAllMutation {
/**
* Scan all users for new media
*/
scanAll: scanAllMutation_scanAll;
scanAll: scanAllMutation_scanAll
}

View File

@ -8,13 +8,13 @@
// ====================================================
export interface scanIntervalQuery_siteInfo {
__typename: "SiteInfo";
__typename: 'SiteInfo'
/**
* How often automatic scans should be initiated in seconds
*/
periodicScanInterval: number;
periodicScanInterval: number
}
export interface scanIntervalQuery {
siteInfo: scanIntervalQuery_siteInfo;
siteInfo: scanIntervalQuery_siteInfo
}

View File

@ -11,9 +11,9 @@ export interface setConcurrentWorkers {
/**
* Set max number of concurrent scanner jobs running at once
*/
setScannerConcurrentWorkers: number;
setScannerConcurrentWorkers: number
}
export interface setConcurrentWorkersVariables {
workers: number;
workers: number
}

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,
{
notifyOnNetworkStatusChange: true,
variables: {
token: token,
password: getSharePassword(token),
},
}
)
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 PhotoGallery from '../photoGallery/PhotoGallery'
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 || [],
@ -114,7 +111,7 @@ const AlbumGallery = React.forwardRef(
)}
<AlbumTitle album={album} disableLink />
{subAlbumElement}
<PhotoGallery
<MediaGallery
loading={loading}
mediaState={mediaState}
dispatchMedia={dispatchMedia}

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

@ -8,13 +8,13 @@
// ====================================================
export interface faceDetectionEnabled_siteInfo {
__typename: "SiteInfo";
__typename: 'SiteInfo'
/**
* Whether or not face detection is enabled and working
*/
faceDetectionEnabled: boolean;
faceDetectionEnabled: boolean
}
export interface faceDetectionEnabled {
siteInfo: faceDetectionEnabled_siteInfo;
siteInfo: faceDetectionEnabled_siteInfo
}

View File

@ -11,5 +11,5 @@ export interface mapboxEnabledQuery {
/**
* Get the mapbox api token, returns null if mapbox is not enabled
*/
mapboxToken: string | null;
mapboxToken: string | null
}

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

@ -2,8 +2,8 @@ import { render, screen } from '@testing-library/react'
import React from 'react'
import { MediaType } from '../../__generated__/globalTypes'
import PhotoGallery from './PhotoGallery'
import { PhotoGalleryState } from './photoGalleryReducer'
import MediaGallery from './MediaGallery'
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: [
{
@ -55,7 +55,7 @@ test('photo gallery with media', () => {
}
render(
<PhotoGallery
<MediaGallery
dispatchMedia={dispatchMedia}
mediaState={mediaState}
loading={false}
@ -71,14 +71,14 @@ describe('photo gallery presenting', () => {
const dispatchMedia = vi.fn()
test('not presenting', () => {
const mediaStateNoPresent: PhotoGalleryState = {
const mediaStateNoPresent: MediaGalleryState = {
activeIndex: -1,
media: [],
presenting: false,
}
render(
<PhotoGallery
<MediaGallery
dispatchMedia={dispatchMedia}
loading={false}
mediaState={mediaStateNoPresent}
@ -89,7 +89,7 @@ describe('photo gallery presenting', () => {
})
test('presenting', () => {
const mediaStatePresent: PhotoGalleryState = {
const mediaStatePresent: MediaGalleryState = {
activeIndex: 0,
media: [
{
@ -112,7 +112,7 @@ describe('photo gallery presenting', () => {
}
render(
<PhotoGallery
<MediaGallery
dispatchMedia={dispatchMedia}
loading={false}
mediaState={mediaStatePresent}

View File

@ -2,19 +2,18 @@ import React, { useContext } from 'react'
import styled from 'styled-components'
import { MediaThumbnail, MediaPlaceholder } from './MediaThumbnail'
import PresentView from './presentView/PresentView'
import { PresentMediaProps_Media } from './presentView/PresentMedia'
import {
openPresentModeAction,
PhotoGalleryAction,
PhotoGalleryState,
} from './photoGalleryReducer'
MediaGalleryState,
} from './mediaGalleryReducer'
import {
toggleFavoriteAction,
useMarkFavoriteMutation,
} from './photoGalleryMutations'
import MediaSidebar from '../sidebar/MediaSidebar/MediaSidebar'
import { SidebarContext } from '../sidebar/Sidebar'
import { sidebarMediaQuery_media_thumbnail } from '../sidebar/MediaSidebar/__generated__/sidebarMediaQuery'
import { gql } from '@apollo/client'
const Gallery = styled.div`
display: flex;
@ -35,28 +34,42 @@ export const PhotoFiller = styled.div`
flex-grow: 999999;
`
export interface PhotoGalleryProps_Media extends PresentMediaProps_Media {
thumbnail: sidebarMediaQuery_media_thumbnail | null
blurhash: string | null
favorite?: boolean
}
export const MEDIA_GALLERY_FRAGMENT = gql`
fragment MediaGalleryFields on Media {
id
type
blurhash
thumbnail {
url
width
height
}
highRes {
url
}
videoWeb {
url
}
favorite
}
`
type PhotoGalleryProps = {
type MediaGalleryProps = {
loading: boolean
mediaState: PhotoGalleryState
mediaState: MediaGalleryState
dispatchMedia: React.Dispatch<PhotoGalleryAction>
}
const PhotoGallery = ({ mediaState, dispatchMedia }: PhotoGalleryProps) => {
const MediaGallery = ({ mediaState, dispatchMedia }: MediaGalleryProps) => {
const [markFavorite] = useMarkFavoriteMutation()
const { media, activeIndex, presenting } = mediaState
const { updateSidebar } = useContext(SidebarContext)
let photoElements = []
let mediaElements = []
if (media) {
photoElements = media.map((media, index) => {
mediaElements = media.map((media, index) => {
const active = activeIndex == index
return (
@ -85,14 +98,14 @@ const PhotoGallery = ({ mediaState, dispatchMedia }: PhotoGalleryProps) => {
})
} else {
for (let i = 0; i < 6; i++) {
photoElements.push(<MediaPlaceholder key={i} />)
mediaElements.push(<MediaPlaceholder key={i} />)
}
}
return (
<>
<Gallery data-testid="photo-gallery-wrapper">
{photoElements}
{mediaElements}
<PhotoFiller />
</Gallery>
{presenting && (
@ -105,4 +118,4 @@ const PhotoGallery = ({ mediaState, dispatchMedia }: PhotoGalleryProps) => {
)
}
export default PhotoGallery
export default MediaGallery

View File

@ -3,6 +3,7 @@ import styled from 'styled-components'
import { ProtectedImage } from './ProtectedMedia'
import { MediaType } from '../../__generated__/globalTypes'
import { ReactComponent as VideoThumbnailIconSVG } from './icons/videoThumbnailIcon.svg'
import { MediaGalleryFields } from './__generated__/MediaGalleryFields'
const MediaContainer = styled.div`
flex-grow: 1;
@ -136,17 +137,7 @@ const VideoThumbnailIcon = styled(VideoThumbnailIconSVG)`
`
type MediaThumbnailProps = {
media: {
id: string
type: MediaType
blurhash: string | null
favorite?: boolean
thumbnail: null | {
url: string
width: number
height: number
}
}
media: MediaGalleryFields
active: boolean
selectImage(): void
clickPresent(): void

View File

@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { MediaType } from './../../../__generated__/globalTypes'
// ====================================================
// GraphQL fragment: MediaGalleryFields
// ====================================================
export interface MediaGalleryFields_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 MediaGalleryFields_highRes {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
}
export interface MediaGalleryFields_videoWeb {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
}
export interface MediaGalleryFields {
__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: MediaGalleryFields_thumbnail | null
/**
* URL to display the photo in full resolution, will be null for videos
*/
highRes: MediaGalleryFields_highRes | null
/**
* URL to get the video in a web format that can be played in the browser, will be null for photos
*/
videoWeb: MediaGalleryFields_videoWeb | null
favorite: boolean
}

View File

@ -8,19 +8,19 @@
// ====================================================
export interface markMediaFavorite_favoriteMedia {
__typename: "Media";
id: string;
favorite: boolean;
__typename: 'Media'
id: string
favorite: boolean
}
export interface markMediaFavorite {
/**
* Mark or unmark a media as being a favorite
*/
favoriteMedia: markMediaFavorite_favoriteMedia;
favoriteMedia: markMediaFavorite_favoriteMedia
}
export interface markMediaFavoriteVariables {
mediaId: string;
favorite: boolean;
mediaId: string
favorite: boolean
}

View File

@ -1,10 +1,10 @@
import React, { useEffect } from 'react'
import { PhotoGalleryProps_Media } from './PhotoGallery'
import { MediaGalleryFields } from './__generated__/MediaGalleryFields'
export interface PhotoGalleryState {
export interface MediaGalleryState {
presenting: boolean
activeIndex: number
media: PhotoGalleryProps_Media[]
media: MediaGalleryFields[]
}
export type GalleryAction =
@ -16,12 +16,12 @@ export type PhotoGalleryAction =
| GalleryAction
| { type: 'openPresentMode'; activeIndex: number }
| { type: 'selectImage'; index: number }
| { type: 'replaceMedia'; media: PhotoGalleryProps_Media[] }
| { 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 './PhotoGallery'
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

@ -2,17 +2,23 @@ import { render, screen } from '@testing-library/react'
import React from 'react'
import { MediaType } from '../../../__generated__/globalTypes'
import PresentMedia, { PresentMediaProps_Media } from './PresentMedia'
import { MediaGalleryFields } from '../__generated__/MediaGalleryFields'
import PresentMedia from './PresentMedia'
test('render present image', () => {
const media: PresentMediaProps_Media = {
const media: MediaGalleryFields = {
__typename: 'Media',
id: '123',
type: MediaType.Photo,
highRes: null,
blurhash: null,
videoWeb: null,
favorite: false,
thumbnail: {
__typename: 'MediaURL',
url: '/sample_image.jpg',
width: 300,
height: 200,
},
}
@ -28,11 +34,13 @@ test('render present image', () => {
})
test('render present video', () => {
const media: PresentMediaProps_Media = {
const media: MediaGalleryFields = {
__typename: 'Media',
id: '123',
type: MediaType.Video,
highRes: null,
blurhash: null,
favorite: false,
videoWeb: {
__typename: 'MediaURL',
url: '/sample_video.mp4',
@ -40,6 +48,8 @@ test('render present video', () => {
thumbnail: {
__typename: 'MediaURL',
url: '/sample_video_thumb.jpg',
width: 300,
height: 200,
},
}

View File

@ -2,11 +2,8 @@ import React from 'react'
import styled from 'styled-components'
import { MediaType } from '../../../__generated__/globalTypes'
import { exhaustiveCheck } from '../../../helpers/utils'
import {
ProtectedImage,
ProtectedVideo,
ProtectedVideoProps_Media,
} from '../ProtectedMedia'
import { ProtectedImage, ProtectedVideo } from '../ProtectedMedia'
import { MediaGalleryFields } from '../__generated__/MediaGalleryFields'
const StyledPhoto = styled(ProtectedImage)`
position: absolute;
@ -26,16 +23,8 @@ const StyledVideo = styled(ProtectedVideo)`
height: 100vh;
`
export interface PresentMediaProps_Media extends ProtectedVideoProps_Media {
type: MediaType
highRes: null | {
__typename: 'MediaURL'
url: string
}
}
type PresentMediaProps = {
media: PresentMediaProps_Media
media: MediaGalleryFields
imageLoaded?(): void
}

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

@ -1,8 +1,9 @@
import React, { useEffect } from 'react'
import styled, { createGlobalStyle } from 'styled-components'
import PresentNavigationOverlay from './PresentNavigationOverlay'
import PresentMedia, { PresentMediaProps_Media } from './PresentMedia'
import { closePresentModeAction, GalleryAction } from '../photoGalleryReducer'
import PresentMedia from './PresentMedia'
import { closePresentModeAction, GalleryAction } from '../mediaGalleryReducer'
import { MediaGalleryFields } from '../__generated__/MediaGalleryFields'
const StyledContainer = styled.div`
position: fixed;
@ -24,7 +25,7 @@ const PreventScroll = createGlobalStyle`
type PresentViewProps = {
className?: string
imageLoaded?(): void
activeMedia: PresentMediaProps_Media
activeMedia: MediaGalleryFields
dispatchMedia: React.Dispatch<GalleryAction>
disableSaveCloseInHistory?: boolean
}

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

@ -8,9 +8,9 @@
// ====================================================
export interface getAlbumSidebar_album {
__typename: "Album";
id: string;
title: string;
__typename: 'Album'
id: string
title: string
}
export interface getAlbumSidebar {
@ -18,9 +18,9 @@ export interface getAlbumSidebar {
* Get album by id, user must own the album or be admin
* If valid tokenCredentials are provided, the album may be retrived without further authentication
*/
album: getAlbumSidebar_album;
album: getAlbumSidebar_album
}
export interface getAlbumSidebarVariables {
id: string;
id: string
}

View File

@ -8,38 +8,38 @@
// ====================================================
export interface resetAlbumCover_resetAlbumCover_thumbnail_thumbnail {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
}
export interface resetAlbumCover_resetAlbumCover_thumbnail {
__typename: "Media";
id: string;
__typename: 'Media'
id: string
/**
* URL to display the media in a smaller resolution
*/
thumbnail: resetAlbumCover_resetAlbumCover_thumbnail_thumbnail | null;
thumbnail: resetAlbumCover_resetAlbumCover_thumbnail_thumbnail | null
}
export interface resetAlbumCover_resetAlbumCover {
__typename: "Album";
id: string;
__typename: 'Album'
id: string
/**
* An image in this album used for previewing this album
*/
thumbnail: resetAlbumCover_resetAlbumCover_thumbnail | null;
thumbnail: resetAlbumCover_resetAlbumCover_thumbnail | null
}
export interface resetAlbumCover {
/**
* Reset the assigned cover photo for an album
*/
resetAlbumCover: resetAlbumCover_resetAlbumCover;
resetAlbumCover: resetAlbumCover_resetAlbumCover
}
export interface resetAlbumCoverVariables {
albumID: string;
albumID: string
}

View File

@ -8,38 +8,38 @@
// ====================================================
export interface setAlbumCover_setAlbumCover_thumbnail_thumbnail {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
}
export interface setAlbumCover_setAlbumCover_thumbnail {
__typename: "Media";
id: string;
__typename: 'Media'
id: string
/**
* URL to display the media in a smaller resolution
*/
thumbnail: setAlbumCover_setAlbumCover_thumbnail_thumbnail | null;
thumbnail: setAlbumCover_setAlbumCover_thumbnail_thumbnail | null
}
export interface setAlbumCover_setAlbumCover {
__typename: "Album";
id: string;
__typename: 'Album'
id: string
/**
* An image in this album used for previewing this album
*/
thumbnail: setAlbumCover_setAlbumCover_thumbnail | null;
thumbnail: setAlbumCover_setAlbumCover_thumbnail | null
}
export interface setAlbumCover {
/**
* Assign a cover photo to an album
*/
setAlbumCover: setAlbumCover_setAlbumCover;
setAlbumCover: setAlbumCover_setAlbumCover
}
export interface setAlbumCoverVariables {
coverID: string;
coverID: string
}

View File

@ -8,19 +8,19 @@
// ====================================================
export interface sidebarAlbumAddShare_shareAlbum {
__typename: "ShareToken";
token: string;
__typename: 'ShareToken'
token: string
}
export interface sidebarAlbumAddShare {
/**
* Generate share token for album
*/
shareAlbum: sidebarAlbumAddShare_shareAlbum;
shareAlbum: sidebarAlbumAddShare_shareAlbum
}
export interface sidebarAlbumAddShareVariables {
id: string;
password?: string | null;
expire?: any | null;
id: string
password?: string | null
expire?: Time | null
}

View File

@ -8,19 +8,19 @@
// ====================================================
export interface sidebarPhotoAddShare_shareMedia {
__typename: "ShareToken";
token: string;
__typename: 'ShareToken'
token: string
}
export interface sidebarPhotoAddShare {
/**
* Generate share token for media
*/
shareMedia: sidebarPhotoAddShare_shareMedia;
shareMedia: sidebarPhotoAddShare_shareMedia
}
export interface sidebarPhotoAddShareVariables {
id: string;
password?: string | null;
expire?: any | null;
id: string
password?: string | null
expire?: Time | null
}

View File

@ -8,22 +8,22 @@
// ====================================================
export interface sidebarProtectShare_protectShareToken {
__typename: "ShareToken";
token: string;
__typename: 'ShareToken'
token: string
/**
* Whether or not a password is needed to access the share
*/
hasPassword: boolean;
hasPassword: boolean
}
export interface sidebarProtectShare {
/**
* Set a password for a token, if null is passed for the password argument, the password will be cleared
*/
protectShareToken: sidebarProtectShare_protectShareToken;
protectShareToken: sidebarProtectShare_protectShareToken
}
export interface sidebarProtectShareVariables {
token: string;
password?: string | null;
token: string
password?: string | null
}

View File

@ -8,17 +8,17 @@
// ====================================================
export interface sidebareDeleteShare_deleteShareToken {
__typename: "ShareToken";
token: string;
__typename: 'ShareToken'
token: string
}
export interface sidebareDeleteShare {
/**
* Delete a share token by it's token value
*/
deleteShareToken: sidebareDeleteShare_deleteShareToken;
deleteShareToken: sidebareDeleteShare_deleteShareToken
}
export interface sidebareDeleteShareVariables {
token: string;
token: string
}

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

@ -1,7 +1,7 @@
import React, { useContext } from 'react'
import { Link } from 'react-router-dom'
import { MediaThumbnail } from '../photoGallery/MediaThumbnail'
import { PhotoFiller } from '../photoGallery/PhotoGallery'
import { PhotoFiller } from '../photoGallery/MediaGallery'
import {
toggleFavoriteAction,
useMarkFavoriteMutation,

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,20 +10,23 @@ import { exhaustiveCheck, isNil } from './helpers/utils'
export type TranslationFn = TFunction<'translation'>
export function setupLocalization(): void {
i18n.use(initReactI18next).init({
lng: 'en',
fallbackLng: 'en',
returnNull: false,
returnEmptyString: false,
i18n
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
returnNull: false,
returnEmptyString: false,
interpolation: {
escapeValue: false,
},
interpolation: {
escapeValue: false,
},
react: {
useSuspense: import.meta.env.PROD,
},
})
react: {
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(() => {
console.log(
'This web app is being served cache-first by a service worker.' +
'worker. To learn more, visit https://cra.link/PWA'
)
})
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"]
}