From 4c9e9a2b9adf9d7133ef49e0dcbe5ef6d89fb7b8 Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Wed, 13 Jul 2022 18:02:06 +0200 Subject: [PATCH] Make eslint check for typescript errors Fix errors after making eslint more strict --- ui/.eslintignore | 3 + ui/.eslintrc.js | 28 +- ui/package-lock.json | 507 ++++++++++++++++-- ui/package.json | 6 +- ui/src/@types/index.d.ts | 13 + ui/src/Pages/AlbumPage/AlbumPage.tsx | 29 +- .../Pages/LoginPage/InitialSetupPage.test.tsx | 4 +- ui/src/Pages/LoginPage/InitialSetupPage.tsx | 19 +- ui/src/Pages/LoginPage/LoginPage.test.tsx | 4 +- ui/src/Pages/LoginPage/LoginPage.tsx | 2 +- ui/src/Pages/PeoplePage/FaceCircleImage.tsx | 21 +- ui/src/Pages/PeoplePage/PeoplePage.test.tsx | 2 +- .../SingleFaceGroup/SingleFaceGroup.tsx | 4 +- ui/src/Pages/PlacesPage/MapClusterMarker.tsx | 2 +- ui/src/Pages/PlacesPage/PlacesPage.tsx | 4 +- .../PlacesPage/__generated__/mediaGeoJson.ts | 2 +- ui/src/Pages/PlacesPage/placesReducer.ts | 10 +- .../ScannerConcurrentWorkers.test.tsx | 2 +- .../Pages/SettingsPage/Users/AddUserRow.tsx | 61 ++- ui/src/Pages/SettingsPage/Users/UserRow.tsx | 2 +- ui/src/Pages/SettingsPage/VersionInfo.tsx | 4 +- .../SharePage/PasswordProtectedShare.tsx | 2 +- ui/src/Pages/SharePage/SharePage.tsx | 39 +- .../SharePage/__generated__/SharePageToken.ts | 2 +- .../__generated__/shareAlbumQuery.ts | 2 +- ui/src/apolloClient.ts | 16 +- .../components/albumGallery/AlbumGallery.tsx | 71 ++- .../__generated__/AlbumGalleryFields.ts | 106 ++++ ui/src/components/layout/Layout.test.tsx | 2 +- ui/src/components/layout/MainMenu.test.tsx | 2 +- ui/src/components/layout/MainMenu.tsx | 7 +- .../mapbox/__generated__/mapboxToken.ts | 2 +- .../components/messages/SubscriptionsHook.ts | 4 +- .../photoGallery/MediaGallery.test.tsx | 8 +- .../components/photoGallery/MediaGallery.tsx | 6 +- ...eryReducer.tsx => mediaGalleryReducer.tsx} | 16 +- .../photoGallery/photoGalleryMutations.ts | 4 +- .../photoGallery/photoGalleryReducer.test.ts | 22 +- .../presentView/PresentNavigationOverlay.tsx | 2 +- .../photoGallery/presentView/PresentView.tsx | 2 +- .../routes/AuthorizedRoute.test.tsx | 4 +- ui/src/components/routes/AuthorizedRoute.tsx | 4 +- ui/src/components/routes/Routes.test.tsx | 2 +- .../MediaSidebar/MediaSidebarExif.test.tsx | 6 +- .../sidebar/MediaSidebar/MediaSidebarExif.tsx | 2 +- .../__generated__/sidebarMediaQuery.ts | 2 +- ui/src/components/sidebar/Sharing.tsx | 2 +- .../sidebar/SidebarDownloadMedia.tsx | 4 +- .../__generated__/sidebarAlbumAddShare.ts | 2 +- .../__generated__/sidebarPhotoAddShare.ts | 2 +- .../timelineGallery/TimelineGallery.tsx | 2 +- .../__generated__/earliestMedia.ts | 2 +- .../__generated__/myTimeline.ts | 4 +- .../timelineGalleryReducer.tsx | 6 +- ui/src/helpers/utils.ts | 15 +- ui/src/hooks/useScrollPagination.ts | 18 +- ui/src/hooks/useURLParameters.ts | 8 +- ui/src/localization.ts | 27 +- ui/src/service-worker.ts | 1 + ui/src/serviceWorkerRegistration.ts | 18 +- ui/tsconfig.json | 2 +- 61 files changed, 877 insertions(+), 300 deletions(-) create mode 100644 ui/.eslintignore create mode 100644 ui/src/components/albumGallery/__generated__/AlbumGalleryFields.ts rename ui/src/components/photoGallery/{photoGalleryReducer.tsx => mediaGalleryReducer.tsx} (87%) diff --git a/ui/.eslintignore b/ui/.eslintignore new file mode 100644 index 0000000..0e75fe5 --- /dev/null +++ b/ui/.eslintignore @@ -0,0 +1,3 @@ +node_modules +dist +coverage diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index 8e89907..f4d0cca 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -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', }, }, ], diff --git a/ui/package-lock.json b/ui/package-lock.json index e47ca4e..e6aaa65 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -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": { diff --git a/ui/package.json b/ui/package.json index f707418..dde2be9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -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", diff --git a/ui/src/@types/index.d.ts b/ui/src/@types/index.d.ts index b60ac77..8cea417 100644 --- a/ui/src/@types/index.d.ts +++ b/ui/src/@types/index.d.ts @@ -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 diff --git a/ui/src/Pages/AlbumPage/AlbumPage.tsx b/ui/src/Pages/AlbumPage/AlbumPage.tsx index d63e8fc..ec09578 100644 --- a/ui/src/Pages/AlbumPage/AlbumPage.tsx +++ b/ui/src/Pages/AlbumPage/AlbumPage.tsx @@ -1,6 +1,8 @@ import React, { useCallback } from 'react' import { useQuery, gql } from '@apollo/client' -import AlbumGallery from '../../components/albumGallery/AlbumGallery' +import AlbumGallery, { + ALBUM_GALLERY_FRAGMENT, +} from '../../components/albumGallery/AlbumGallery' import Layout from '../../components/layout/Layout' import useURLParameters from '../../hooks/useURLParameters' import useScrollPagination from '../../hooks/useScrollPagination' @@ -10,10 +12,9 @@ import { albumQuery, albumQueryVariables } from './__generated__/albumQuery' import useOrderingParams from '../../hooks/useOrderingParams' import { useParams } from 'react-router-dom' import { isNil } from '../../helpers/utils' -import { MEDIA_GALLERY_FRAGMENT } from '../../components/photoGallery/MediaGallery' const ALBUM_QUERY = gql` - ${MEDIA_GALLERY_FRAGMENT} + ${ALBUM_GALLERY_FRAGMENT} query albumQuery( $id: ID! @@ -24,27 +25,7 @@ const ALBUM_QUERY = gql` $offset: Int ) { album(id: $id) { - id - title - subAlbums( - order: { order_by: "title", order_direction: $orderDirection } - ) { - id - title - thumbnail { - id - thumbnail { - url - } - } - } - media( - paginate: { limit: $limit, offset: $offset } - order: { order_by: $mediaOrderBy, order_direction: $orderDirection } - onlyFavorites: $onlyFavorites - ) { - ...MediaGalleryFields - } + ...AlbumGalleryFields } } ` diff --git a/ui/src/Pages/LoginPage/InitialSetupPage.test.tsx b/ui/src/Pages/LoginPage/InitialSetupPage.test.tsx index 83a8519..6426ad6 100644 --- a/ui/src/Pages/LoginPage/InitialSetupPage.test.tsx +++ b/ui/src/Pages/LoginPage/InitialSetupPage.test.tsx @@ -9,10 +9,10 @@ import { mockInitialSetupGraphql } from './loginTestHelpers' vi.mock('../../helpers/authentication.ts') -const authToken = authentication.authToken // as vi.Mock> +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({ diff --git a/ui/src/Pages/LoginPage/InitialSetupPage.tsx b/ui/src/Pages/LoginPage/InitialSetupPage.tsx index b0b1722..9c51446 100644 --- a/ui/src/Pages/LoginPage/InitialSetupPage.tsx +++ b/ui/src/Pages/LoginPage/InitialSetupPage.tsx @@ -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(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 = () => { {t('login_page.initial_setup.field.submit', 'Setup Photoview')} diff --git a/ui/src/Pages/LoginPage/LoginPage.test.tsx b/ui/src/Pages/LoginPage/LoginPage.test.tsx index 4689bee..8e8a0ef 100644 --- a/ui/src/Pages/LoginPage/LoginPage.test.tsx +++ b/ui/src/Pages/LoginPage/LoginPage.test.tsx @@ -9,7 +9,7 @@ import { mockInitialSetupGraphql } from './loginTestHelpers' vi.mock('../../helpers/authentication.ts') -const authToken = authentication.authToken // as vi.Mock> +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({ diff --git a/ui/src/Pages/LoginPage/LoginPage.tsx b/ui/src/Pages/LoginPage/LoginPage.tsx index 76c477e..d520f44 100644 --- a/ui/src/Pages/LoginPage/LoginPage.tsx +++ b/ui/src/Pages/LoginPage/LoginPage.tsx @@ -106,7 +106,7 @@ const LoginForm = () => { ( - -))<{ 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) => ( + + ) +)` position: absolute; transform-origin: ${({ origin }) => `${origin.x * 100}% ${origin.y * 100}%`}; object-fit: cover; diff --git a/ui/src/Pages/PeoplePage/PeoplePage.test.tsx b/ui/src/Pages/PeoplePage/PeoplePage.test.tsx index 2cb45c6..00c6835 100644 --- a/ui/src/Pages/PeoplePage/PeoplePage.test.tsx +++ b/ui/src/Pages/PeoplePage/PeoplePage.test.tsx @@ -218,7 +218,7 @@ describe('FaceDetails component', () => { }) }) - test('cancel add label to face group', async () => { + test('cancel add label to face group', () => { render( diff --git a/ui/src/Pages/PeoplePage/SingleFaceGroup/SingleFaceGroup.tsx b/ui/src/Pages/PeoplePage/SingleFaceGroup/SingleFaceGroup.tsx index 8d352fa..6f3aa20 100644 --- a/ui/src/Pages/PeoplePage/SingleFaceGroup/SingleFaceGroup.tsx +++ b/ui/src/Pages/PeoplePage/SingleFaceGroup/SingleFaceGroup.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useReducer } from 'react' import { useTranslation } from 'react-i18next' import PaginateLoader from '../../../components/PaginateLoader' import MediaGallery from '../../../components/photoGallery/MediaGallery' -import { photoGalleryReducer } from '../../../components/photoGallery/photoGalleryReducer' +import { mediaGalleryReducer } from '../../../components/photoGallery/mediaGalleryReducer' import useScrollPagination from '../../../hooks/useScrollPagination' import FaceGroupTitle from './FaceGroupTitle' import { @@ -62,7 +62,7 @@ const SingleFaceGroup = ({ faceGroupID }: SingleFaceGroupProps) => { }, }) - const [mediaState, dispatchMedia] = useReducer(photoGalleryReducer, { + const [mediaState, dispatchMedia] = useReducer(mediaGalleryReducer, { presenting: false, activeIndex: -1, media: [], diff --git a/ui/src/Pages/PlacesPage/MapClusterMarker.tsx b/ui/src/Pages/PlacesPage/MapClusterMarker.tsx index 4ec4423..5911662 100644 --- a/ui/src/Pages/PlacesPage/MapClusterMarker.tsx +++ b/ui/src/Pages/PlacesPage/MapClusterMarker.tsx @@ -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({ diff --git a/ui/src/Pages/PlacesPage/PlacesPage.tsx b/ui/src/Pages/PlacesPage/PlacesPage.tsx index b41f432..e34132b 100644 --- a/ui/src/Pages/PlacesPage/PlacesPage.tsx +++ b/ui/src/Pages/PlacesPage/PlacesPage.tsx @@ -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: { diff --git a/ui/src/Pages/PlacesPage/__generated__/mediaGeoJson.ts b/ui/src/Pages/PlacesPage/__generated__/mediaGeoJson.ts index 5f6d5da..f1306ef 100644 --- a/ui/src/Pages/PlacesPage/__generated__/mediaGeoJson.ts +++ b/ui/src/Pages/PlacesPage/__generated__/mediaGeoJson.ts @@ -11,5 +11,5 @@ export interface mediaGeoJson { /** * Get media owned by the logged in user, returned in GeoJson format */ - myMediaGeoJson: any + myMediaGeoJson: Any } diff --git a/ui/src/Pages/PlacesPage/placesReducer.ts b/ui/src/Pages/PlacesPage/placesReducer.ts index a92de08..08be8a2 100644 --- a/ui/src/Pages/PlacesPage/placesReducer.ts +++ b/ui/src/Pages/PlacesPage/placesReducer.ts @@ -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) } } diff --git a/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.test.tsx b/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.test.tsx index 1207e1e..42b42da 100644 --- a/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.test.tsx +++ b/ui/src/Pages/SettingsPage/ScannerConcurrentWorkers.test.tsx @@ -9,7 +9,7 @@ import { ScannerConcurrentWorkers, } from './ScannerConcurrentWorkers' -test('load ScannerConcurrentWorkers', async () => { +test('load ScannerConcurrentWorkers', () => { const graphqlMocks = [ { request: { diff --git a/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx b/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx index 4ff9036..ea35f1e 100644 --- a/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx +++ b/ui/src/Pages/SettingsPage/Users/AddUserRow.tsx @@ -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 diff --git a/ui/src/Pages/SettingsPage/Users/UserRow.tsx b/ui/src/Pages/SettingsPage/Users/UserRow.tsx index 8ab8a9d..9f0573a 100644 --- a/ui/src/Pages/SettingsPage/Users/UserRow.tsx +++ b/ui/src/Pages/SettingsPage/Users/UserRow.tsx @@ -67,7 +67,7 @@ export type UserRowChildProps = { export type UserRowProps = { user: settingsUsersQuery_user - refetchUsers(): void + refetchUsers: () => void } const UserRow = ({ user, refetchUsers }: UserRowProps) => { diff --git a/ui/src/Pages/SettingsPage/VersionInfo.tsx b/ui/src/Pages/SettingsPage/VersionInfo.tsx index ee76a70..b673ca1 100644 --- a/ui/src/Pages/SettingsPage/VersionInfo.tsx +++ b/ui/src/Pages/SettingsPage/VersionInfo.tsx @@ -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) { diff --git a/ui/src/Pages/SharePage/PasswordProtectedShare.tsx b/ui/src/Pages/SharePage/PasswordProtectedShare.tsx index 36bc6ea..f126252 100644 --- a/ui/src/Pages/SharePage/PasswordProtectedShare.tsx +++ b/ui/src/Pages/SharePage/PasswordProtectedShare.tsx @@ -25,7 +25,7 @@ const PasswordProtectedShare = ({ const [invalidPassword, setInvalidPassword] = useState(false) const onSubmit = () => { - refetchWithPassword(watch('password')) + refetchWithPassword(watch('password') as string) setInvalidPassword(true) } diff --git a/ui/src/Pages/SharePage/SharePage.tsx b/ui/src/Pages/SharePage/SharePage.tsx index d49967c..d93fb0a 100644 --- a/ui/src/Pages/SharePage/SharePage.tsx +++ b/ui/src/Pages/SharePage/SharePage.tsx @@ -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
{error.message}
+ if (!isNil(error)) return
{error.message}
if (loading) return
{t('general.loading.default', 'Loading...')}
- 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 } @@ -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') { diff --git a/ui/src/Pages/SharePage/__generated__/SharePageToken.ts b/ui/src/Pages/SharePage/__generated__/SharePageToken.ts index aff5a89..a974afa 100644 --- a/ui/src/Pages/SharePage/__generated__/SharePageToken.ts +++ b/ui/src/Pages/SharePage/__generated__/SharePageToken.ts @@ -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 */ diff --git a/ui/src/Pages/SharePage/__generated__/shareAlbumQuery.ts b/ui/src/Pages/SharePage/__generated__/shareAlbumQuery.ts index 344e051..5448f60 100644 --- a/ui/src/Pages/SharePage/__generated__/shareAlbumQuery.ts +++ b/ui/src/Pages/SharePage/__generated__/shareAlbumQuery.ts @@ -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 */ diff --git a/ui/src/apolloClient.ts b/ui/src/apolloClient.ts index 138243c..71ee1ff 100644 --- a/ui/src/apolloClient.ts +++ b/ui/src/apolloClient.ts @@ -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 } // 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] } diff --git a/ui/src/components/albumGallery/AlbumGallery.tsx b/ui/src/components/albumGallery/AlbumGallery.tsx index 72b0e1a..2081c5d 100644 --- a/ui/src/components/albumGallery/AlbumGallery.tsx +++ b/ui/src/components/albumGallery/AlbumGallery.tsx @@ -1,49 +1,46 @@ import React, { useEffect, useReducer } from 'react' import AlbumTitle from '../album/AlbumTitle' -import MediaGallery from '../photoGallery/MediaGallery' +import MediaGallery, { + MEDIA_GALLERY_FRAGMENT, +} from '../photoGallery/MediaGallery' import AlbumBoxes from './AlbumBoxes' import AlbumFilter from '../album/AlbumFilter' import { - albumQuery_album_media_highRes, - albumQuery_album_media_thumbnail, - albumQuery_album_media_videoWeb, - albumQuery_album_subAlbums, -} from '../../Pages/AlbumPage/__generated__/albumQuery' -import { - photoGalleryReducer, + mediaGalleryReducer, urlPresentModeSetupHook, -} from '../photoGallery/photoGalleryReducer' +} from '../photoGallery/mediaGalleryReducer' import { MediaOrdering, SetOrderingFn } from '../../hooks/useOrderingParams' -import { MediaType } from '../../__generated__/globalTypes' +import { gql } from '@apollo/client' +import { AlbumGalleryFields } from './__generated__/AlbumGalleryFields' -type AlbumGalleryAlbum = { - __typename: 'Album' - id: string - title: string - subAlbums: albumQuery_album_subAlbums[] - media: { - __typename: 'Media' - id: string - type: MediaType - /** - * URL to display the media in a smaller resolution - */ - thumbnail: albumQuery_album_media_thumbnail | null - /** - * URL to display the photo in full resolution, will be null for videos - */ - highRes: albumQuery_album_media_highRes | null - /** - * URL to get the video in a web format that can be played in the browser, will be null for photos - */ - videoWeb: albumQuery_album_media_videoWeb | null - favorite?: boolean - blurhash: string | null - }[] -} +export const ALBUM_GALLERY_FRAGMENT = gql` + ${MEDIA_GALLERY_FRAGMENT} + + fragment AlbumGalleryFields on Album { + id + title + subAlbums(order: { order_by: "title", order_direction: $orderDirection }) { + id + title + thumbnail { + id + thumbnail { + url + } + } + } + media( + paginate: { limit: $limit, offset: $offset } + order: { order_by: $mediaOrderBy, order_direction: $orderDirection } + onlyFavorites: $onlyFavorites + ) { + ...MediaGalleryFields + } + } +` type AlbumGalleryProps = { - album?: AlbumGalleryAlbum + album?: AlbumGalleryFields loading?: boolean customAlbumLink?(albumID: string): string showFilter?: boolean @@ -68,7 +65,7 @@ const AlbumGallery = React.forwardRef( }: AlbumGalleryProps, ref: React.ForwardedRef ) => { - const [mediaState, dispatchMedia] = useReducer(photoGalleryReducer, { + const [mediaState, dispatchMedia] = useReducer(mediaGalleryReducer, { presenting: false, activeIndex: -1, media: album?.media || [], diff --git a/ui/src/components/albumGallery/__generated__/AlbumGalleryFields.ts b/ui/src/components/albumGallery/__generated__/AlbumGalleryFields.ts new file mode 100644 index 0000000..3e99b1d --- /dev/null +++ b/ui/src/components/albumGallery/__generated__/AlbumGalleryFields.ts @@ -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[] +} diff --git a/ui/src/components/layout/Layout.test.tsx b/ui/src/components/layout/Layout.test.tsx index 7340eef..ddbe318 100644 --- a/ui/src/components/layout/Layout.test.tsx +++ b/ui/src/components/layout/Layout.test.tsx @@ -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_content

diff --git a/ui/src/components/layout/MainMenu.test.tsx b/ui/src/components/layout/MainMenu.test.tsx index 03b6d76..94cece3 100644 --- a/ui/src/components/layout/MainMenu.test.tsx +++ b/ui/src/components/layout/MainMenu.test.tsx @@ -9,7 +9,7 @@ import MainMenu, { MAPBOX_QUERY } from './MainMenu' vi.mock('../../helpers/authentication.ts') -const authTokenMock = authentication.authToken // as vi.MockedFunction +const authTokenMock = vi.mocked(authentication.authToken) afterEach(() => { authTokenMock.mockClear() diff --git a/ui/src/components/layout/MainMenu.tsx b/ui/src/components/layout/MainMenu.tsx index 9ef9ab9..bbe2cf8 100644 --- a/ui/src/components/layout/MainMenu.tsx +++ b/ui/src/components/layout/MainMenu.tsx @@ -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(MAPBOX_QUERY) : null const faceDetectionEnabledQuery = authToken() - ? useQuery(FACE_DETECTION_ENABLED_QUERY) + ? useQuery(FACE_DETECTION_ENABLED_QUERY) : null const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken diff --git a/ui/src/components/mapbox/__generated__/mapboxToken.ts b/ui/src/components/mapbox/__generated__/mapboxToken.ts index 644bbae..2ca4e6b 100644 --- a/ui/src/components/mapbox/__generated__/mapboxToken.ts +++ b/ui/src/components/mapbox/__generated__/mapboxToken.ts @@ -15,5 +15,5 @@ export interface mapboxToken { /** * Get media owned by the logged in user, returned in GeoJson format */ - myMediaGeoJson: any + myMediaGeoJson: Any } diff --git a/ui/src/components/messages/SubscriptionsHook.ts b/ui/src/components/messages/SubscriptionsHook.ts index 94a5186..49c720d 100644 --- a/ui/src/components/messages/SubscriptionsHook.ts +++ b/ui/src/components/messages/SubscriptionsHook.ts @@ -19,7 +19,7 @@ const NOTIFICATION_SUBSCRIPTION = gql` } ` -const messageTimeoutHandles = new Map() +const messageTimeoutHandles = new Map() 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) } diff --git a/ui/src/components/photoGallery/MediaGallery.test.tsx b/ui/src/components/photoGallery/MediaGallery.test.tsx index dc9dadb..c735597 100644 --- a/ui/src/components/photoGallery/MediaGallery.test.tsx +++ b/ui/src/components/photoGallery/MediaGallery.test.tsx @@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react' import React from 'react' import { MediaType } from '../../__generated__/globalTypes' import MediaGallery from './MediaGallery' -import { PhotoGalleryState } from './photoGalleryReducer' +import { MediaGalleryState } from './mediaGalleryReducer' vi.mock('./photoGalleryMutations', () => ({ useMarkFavoriteMutation: () => [vi.fn()], @@ -12,7 +12,7 @@ vi.mock('./photoGalleryMutations', () => ({ test('photo gallery with media', () => { const dispatchMedia = vi.fn() - const mediaState: PhotoGalleryState = { + const mediaState: MediaGalleryState = { activeIndex: 0, media: [ { @@ -71,7 +71,7 @@ describe('photo gallery presenting', () => { const dispatchMedia = vi.fn() test('not presenting', () => { - const mediaStateNoPresent: PhotoGalleryState = { + const mediaStateNoPresent: MediaGalleryState = { activeIndex: -1, media: [], presenting: false, @@ -89,7 +89,7 @@ describe('photo gallery presenting', () => { }) test('presenting', () => { - const mediaStatePresent: PhotoGalleryState = { + const mediaStatePresent: MediaGalleryState = { activeIndex: 0, media: [ { diff --git a/ui/src/components/photoGallery/MediaGallery.tsx b/ui/src/components/photoGallery/MediaGallery.tsx index 311d0d4..1eca939 100644 --- a/ui/src/components/photoGallery/MediaGallery.tsx +++ b/ui/src/components/photoGallery/MediaGallery.tsx @@ -5,8 +5,8 @@ import PresentView from './presentView/PresentView' import { openPresentModeAction, PhotoGalleryAction, - PhotoGalleryState, -} from './photoGalleryReducer' + MediaGalleryState, +} from './mediaGalleryReducer' import { toggleFavoriteAction, useMarkFavoriteMutation, @@ -56,7 +56,7 @@ export const MEDIA_GALLERY_FRAGMENT = gql` type MediaGalleryProps = { loading: boolean - mediaState: PhotoGalleryState + mediaState: MediaGalleryState dispatchMedia: React.Dispatch } diff --git a/ui/src/components/photoGallery/photoGalleryReducer.tsx b/ui/src/components/photoGallery/mediaGalleryReducer.tsx similarity index 87% rename from ui/src/components/photoGallery/photoGalleryReducer.tsx rename to ui/src/components/photoGallery/mediaGalleryReducer.tsx index e3066cf..f506b71 100644 --- a/ui/src/components/photoGallery/photoGalleryReducer.tsx +++ b/ui/src/components/photoGallery/mediaGalleryReducer.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react' import { MediaGalleryFields } from './__generated__/MediaGalleryFields' -export interface PhotoGalleryState { +export interface MediaGalleryState { presenting: boolean activeIndex: number media: MediaGalleryFields[] @@ -18,10 +18,10 @@ export type PhotoGalleryAction = | { type: 'selectImage'; index: number } | { type: 'replaceMedia'; media: MediaGalleryFields[] } -export function photoGalleryReducer( - state: PhotoGalleryState, +export function mediaGalleryReducer( + state: MediaGalleryState, action: PhotoGalleryAction -): PhotoGalleryState { +): MediaGalleryState { switch (action.type) { case 'nextImage': return { @@ -69,15 +69,19 @@ export function photoGalleryReducer( } } +export interface MediaGalleryPopStateEvent extends PopStateEvent { + state: MediaGalleryState +} + export const urlPresentModeSetupHook = ({ dispatchMedia, openPresentMode, }: { dispatchMedia: React.Dispatch - 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 { diff --git a/ui/src/components/photoGallery/photoGalleryMutations.ts b/ui/src/components/photoGallery/photoGalleryMutations.ts index 44cedc2..25b1ec8 100644 --- a/ui/src/components/photoGallery/photoGalleryMutations.ts +++ b/ui/src/components/photoGallery/photoGalleryMutations.ts @@ -1,5 +1,5 @@ +import { MediaGalleryFields } from './__generated__/MediaGalleryFields' import { gql, MutationFunction, useMutation } from '@apollo/client' -import { PhotoGalleryProps_Media } from './MediaGallery' import { markMediaFavorite, markMediaFavoriteVariables, @@ -24,7 +24,7 @@ export const toggleFavoriteAction = ({ media, markFavorite, }: { - media: PhotoGalleryProps_Media + media: MediaGalleryFields markFavorite: MutationFunction }) => { return markFavorite({ diff --git a/ui/src/components/photoGallery/photoGalleryReducer.test.ts b/ui/src/components/photoGallery/photoGalleryReducer.test.ts index 73a0c1f..d7f857d 100644 --- a/ui/src/components/photoGallery/photoGalleryReducer.test.ts +++ b/ui/src/components/photoGallery/photoGalleryReducer.test.ts @@ -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, diff --git a/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.tsx b/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.tsx index d88ad5d..52d0c93 100644 --- a/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.tsx +++ b/ui/src/components/photoGallery/presentView/PresentNavigationOverlay.tsx @@ -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' diff --git a/ui/src/components/photoGallery/presentView/PresentView.tsx b/ui/src/components/photoGallery/presentView/PresentView.tsx index e5dceee..d022d2c 100644 --- a/ui/src/components/photoGallery/presentView/PresentView.tsx +++ b/ui/src/components/photoGallery/presentView/PresentView.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react' import styled, { createGlobalStyle } from 'styled-components' import PresentNavigationOverlay from './PresentNavigationOverlay' import PresentMedia from './PresentMedia' -import { closePresentModeAction, GalleryAction } from '../photoGalleryReducer' +import { closePresentModeAction, GalleryAction } from '../mediaGalleryReducer' import { MediaGalleryFields } from '../__generated__/MediaGalleryFields' const StyledContainer = styled.div` diff --git a/ui/src/components/routes/AuthorizedRoute.test.tsx b/ui/src/components/routes/AuthorizedRoute.test.tsx index 7313916..8b3503d 100644 --- a/ui/src/components/routes/AuthorizedRoute.test.tsx +++ b/ui/src/components/routes/AuthorizedRoute.test.tsx @@ -14,7 +14,7 @@ const authToken = vi.mocked(authentication.authToken) describe('AuthorizedRoute component', () => { const AuthorizedComponent = () =>
authorized content
- 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( diff --git a/ui/src/components/routes/AuthorizedRoute.tsx b/ui/src/components/routes/AuthorizedRoute.tsx index a563284..825ca49 100644 --- a/ui/src/components/routes/AuthorizedRoute.tsx +++ b/ui/src/components/routes/AuthorizedRoute.tsx @@ -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(ADMIN_QUERY) useEffect(() => { if (authToken() && !called) { diff --git a/ui/src/components/routes/Routes.test.tsx b/ui/src/components/routes/Routes.test.tsx index e0e9eff..9b8e0e5 100644 --- a/ui/src/components/routes/Routes.test.tsx +++ b/ui/src/components/routes/Routes.test.tsx @@ -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( diff --git a/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.test.tsx b/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.test.tsx index d0e1b83..46a98f7 100644 --- a/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.test.tsx +++ b/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.test.tsx @@ -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', diff --git a/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.tsx b/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.tsx index 0c243f6..9c2636a 100644 --- a/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.tsx +++ b/ui/src/components/sidebar/MediaSidebar/MediaSidebarExif.tsx @@ -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 { diff --git a/ui/src/components/sidebar/MediaSidebar/__generated__/sidebarMediaQuery.ts b/ui/src/components/sidebar/MediaSidebar/__generated__/sidebarMediaQuery.ts index 8cadd6e..ab56dd8 100644 --- a/ui/src/components/sidebar/MediaSidebar/__generated__/sidebarMediaQuery.ts +++ b/ui/src/components/sidebar/MediaSidebar/__generated__/sidebarMediaQuery.ts @@ -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 */ diff --git a/ui/src/components/sidebar/Sharing.tsx b/ui/src/components/sidebar/Sharing.tsx index 4541a7a..258aa17 100644 --- a/ui/src/components/sidebar/Sharing.tsx +++ b/ui/src/components/sidebar/Sharing.tsx @@ -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 } const SidebarShare = ({ diff --git a/ui/src/components/sidebar/SidebarDownloadMedia.tsx b/ui/src/components/sidebar/SidebarDownloadMedia.tsx index 4557166..19bba3a 100644 --- a/ui/src/components/sidebar/SidebarDownloadMedia.tsx +++ b/ui/src/components/sidebar/SidebarDownloadMedia.tsx @@ -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') diff --git a/ui/src/components/sidebar/__generated__/sidebarAlbumAddShare.ts b/ui/src/components/sidebar/__generated__/sidebarAlbumAddShare.ts index e0f7aa1..a85d270 100644 --- a/ui/src/components/sidebar/__generated__/sidebarAlbumAddShare.ts +++ b/ui/src/components/sidebar/__generated__/sidebarAlbumAddShare.ts @@ -22,5 +22,5 @@ export interface sidebarAlbumAddShare { export interface sidebarAlbumAddShareVariables { id: string password?: string | null - expire?: any | null + expire?: Time | null } diff --git a/ui/src/components/sidebar/__generated__/sidebarPhotoAddShare.ts b/ui/src/components/sidebar/__generated__/sidebarPhotoAddShare.ts index 0cd81ef..5364386 100644 --- a/ui/src/components/sidebar/__generated__/sidebarPhotoAddShare.ts +++ b/ui/src/components/sidebar/__generated__/sidebarPhotoAddShare.ts @@ -22,5 +22,5 @@ export interface sidebarPhotoAddShare { export interface sidebarPhotoAddShareVariables { id: string password?: string | null - expire?: any | null + expire?: Time | null } diff --git a/ui/src/components/timelineGallery/TimelineGallery.tsx b/ui/src/components/timelineGallery/TimelineGallery.tsx index d574f94..c180cf8 100644 --- a/ui/src/components/timelineGallery/TimelineGallery.tsx +++ b/ui/src/components/timelineGallery/TimelineGallery.tsx @@ -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' diff --git a/ui/src/components/timelineGallery/__generated__/earliestMedia.ts b/ui/src/components/timelineGallery/__generated__/earliestMedia.ts index 02411a5..cae97b5 100644 --- a/ui/src/components/timelineGallery/__generated__/earliestMedia.ts +++ b/ui/src/components/timelineGallery/__generated__/earliestMedia.ts @@ -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 { diff --git a/ui/src/components/timelineGallery/__generated__/myTimeline.ts b/ui/src/components/timelineGallery/__generated__/myTimeline.ts index 118b42e..e1d9ed1 100644 --- a/ui/src/components/timelineGallery/__generated__/myTimeline.ts +++ b/ui/src/components/timelineGallery/__generated__/myTimeline.ts @@ -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 } diff --git a/ui/src/components/timelineGallery/timelineGalleryReducer.tsx b/ui/src/components/timelineGallery/timelineGalleryReducer.tsx index 791f5f2..9700703 100644 --- a/ui/src/components/timelineGallery/timelineGalleryReducer.tsx +++ b/ui/src/components/timelineGallery/timelineGalleryReducer.tsx @@ -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( + (acc, next) => next === -1 || acc, false ) ) { diff --git a/ui/src/helpers/utils.ts b/ui/src/helpers/utils.ts index 77b7f58..0aa1e34 100644 --- a/ui/src/helpers/utils.ts +++ b/ui/src/helpers/utils.ts @@ -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 any> { +export interface DebouncedFn unknown> { (...args: Parameters): void cancel(): void } -export function debounce any>( - func: T, +export function debounce unknown>( + func: F, wait: number, triggerRising?: boolean -): DebouncedFn { +): DebouncedFn { let timeout: number | undefined = undefined - const debounced = (...args: Parameters) => { + const debounced = (...args: Parameters) => { if (timeout) { clearTimeout(timeout) timeout = undefined @@ -37,11 +35,12 @@ export function debounce 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}`) } diff --git a/ui/src/hooks/useScrollPagination.ts b/ui/src/hooks/useScrollPagination.ts index f7fcd8c..555383f 100644 --- a/ui/src/hooks/useScrollPagination.ts +++ b/ui/src/hooks/useScrollPagination.ts @@ -4,24 +4,20 @@ import { useCallback, useEffect, useRef, useState } from 'react' interface ScrollPaginationArgs { loading: boolean data: D | undefined - fetchMore(args: { + fetchMore: (args: { variables: { offset: number } - }): Promise> - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getItems(data: D): any[] + }) => Promise> + getItems: (data: D) => unknown[] } type ScrollPaginationResult = { finished: boolean - containerElem(node: null | Element): void + containerElem: (node: null | Element) => void } -function useScrollPagination({ - loading, - fetchMore, - data, - getItems, -}: ScrollPaginationArgs): ScrollPaginationResult { +const useScrollPagination: ( + args: ScrollPaginationArgs +) => ScrollPaginationResult = ({ loading, fetchMore, data, getItems }) => { const observer = useRef(null) const observerElem = useRef(null) const [finished, setFinished] = useState(false) diff --git a/ui/src/hooks/useURLParameters.ts b/ui/src/hooks/useURLParameters.ts index 40f661f..328685d 100644 --- a/ui/src/hooks/useURLParameters.ts +++ b/ui/src/hooks/useURLParameters.ts @@ -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) diff --git a/ui/src/localization.ts b/ui/src/localization.ts index d0bc9f8..35480e0 100644 --- a/ui/src/localization.ts +++ b/ui/src/localization.ts @@ -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` diff --git a/ui/src/service-worker.ts b/ui/src/service-worker.ts index ed9e09d..7ea8ebc 100644 --- a/ui/src/service-worker.ts +++ b/ui/src/service-worker.ts @@ -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() } diff --git a/ui/src/serviceWorkerRegistration.ts b/ui/src/serviceWorkerRegistration.ts index 26f8819..f96bd4e 100644 --- a/ui/src/serviceWorkerRegistration.ts +++ b/ui/src/serviceWorkerRegistration.ts @@ -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) }) } diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 6493c49..19d0249 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -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"] }