diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 77015096..4f994667 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,7 +28,33 @@ "prettier": "3.4.2", "typescript": "^5.7.3", "typescript-eslint": "^8.21.0", - "vite": "^6.0.11" + "vite": "^6.0.11", + "vite-plugin-checker": "^0.8.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -1516,6 +1542,32 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1531,6 +1583,20 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1544,6 +1610,19 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -1593,6 +1672,44 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1611,6 +1728,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2090,6 +2217,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2130,6 +2272,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2180,6 +2329,19 @@ "node": ">=0.8.19" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2218,6 +2380,13 @@ "dev": true, "license": "ISC" }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2251,6 +2420,19 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2366,6 +2548,29 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -2611,6 +2816,19 @@ "react-dom": ">=18" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2753,6 +2971,19 @@ "node": ">=0.10.0" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2778,6 +3009,13 @@ "node": ">=8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2822,6 +3060,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -2859,6 +3110,16 @@ "typescript": ">=4.8.4 <5.8.0" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2941,6 +3202,167 @@ } } }, + "node_modules/vite-plugin-checker": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.8.0.tgz", + "integrity": "sha512-UA5uzOGm97UvZRTdZHiQVYFnd86AVn8EVaD4L3PoVzxH+IZSfaAw14WGFwX9QS23UW3lV/5bVKZn6l0w+q9P0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "ansi-escapes": "^4.3.0", + "chalk": "^4.1.1", + "chokidar": "^3.5.1", + "commander": "^8.0.0", + "fast-glob": "^3.2.7", + "fs-extra": "^11.1.0", + "npm-run-path": "^4.0.1", + "strip-ansi": "^6.0.0", + "tiny-invariant": "^1.1.0", + "vscode-languageclient": "^7.0.0", + "vscode-languageserver": "^7.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-uri": "^3.0.2" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "@biomejs/biome": ">=1.7", + "eslint": ">=7", + "meow": "^9.0.0", + "optionator": "^0.9.1", + "stylelint": ">=13", + "typescript": "*", + "vite": ">=2.0.0", + "vls": "*", + "vti": "*", + "vue-tsc": "~2.1.6" + }, + "peerDependenciesMeta": { + "@biomejs/biome": { + "optional": true + }, + "eslint": { + "optional": true + }, + "meow": { + "optional": true + }, + "optionator": { + "optional": true + }, + "stylelint": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vls": { + "optional": true + }, + "vti": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/vscode-jsonrpc": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", + "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0 || >=10.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz", + "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.4", + "semver": "^7.3.4", + "vscode-languageserver-protocol": "3.16.0" + }, + "engines": { + "vscode": "^1.52.0" + } + }, + "node_modules/vscode-languageclient/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/vscode-languageclient/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/vscode-languageserver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", + "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.16.0" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", + "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "dev": true, + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "6.0.0", + "vscode-languageserver-types": "3.16.0" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index caa2d6e4..97b96762 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,7 @@ "prettier": "3.4.2", "typescript": "^5.7.3", "typescript-eslint": "^8.21.0", - "vite": "^6.0.11" + "vite": "^6.0.11", + "vite-plugin-checker": "^0.8.0" } } diff --git a/frontend/src/api/actions/updateUserConfig.ts b/frontend/src/api/actions/updateUserConfig.ts index c091741e..675d80d9 100644 --- a/frontend/src/api/actions/updateUserConfig.ts +++ b/frontend/src/api/actions/updateUserConfig.ts @@ -1,17 +1,6 @@ import { SortByType, SortOrderType, ViewLayoutType } from '../../pages/Home'; import APIClient from '../../functions/APIClient'; -export type UserMeType = { - id: number; - name: string; - is_superuser: boolean; - is_staff: boolean; - groups: []; - user_permissions: []; - last_login: string; - config: UserConfigType; -}; - export type ColourVariants = 'dark.css' | 'light.css' | 'matrix.css' | 'midnight.css'; export type UserConfigType = { @@ -33,7 +22,7 @@ export type UserConfigType = { const updateUserConfig = async (config: Partial): Promise => { return APIClient('/api/user/me/', { method: 'POST', - body: { config: config }, + body: config, }); }; diff --git a/frontend/src/api/loader/loadUserAccount.ts b/frontend/src/api/loader/loadUserAccount.ts new file mode 100644 index 00000000..e04fb330 --- /dev/null +++ b/frontend/src/api/loader/loadUserAccount.ts @@ -0,0 +1,8 @@ +import APIClient from '../../functions/APIClient'; +import { UserAccountType } from '../../pages/Base'; + +const loadUserAccount = async (): Promise => { + return APIClient('/api/user/account/'); +}; + +export default loadUserAccount; diff --git a/frontend/src/api/loader/loadUserConfig.ts b/frontend/src/api/loader/loadUserConfig.ts index 3cd89736..fbf96dd3 100644 --- a/frontend/src/api/loader/loadUserConfig.ts +++ b/frontend/src/api/loader/loadUserConfig.ts @@ -1,7 +1,7 @@ -import { UserMeType } from '../actions/updateUserConfig'; +import { UserConfigType } from '../actions/updateUserConfig'; import APIClient from '../../functions/APIClient'; -const loadUserMeConfig = async (): Promise => { +const loadUserMeConfig = async (): Promise => { return APIClient('/api/user/me/'); }; diff --git a/frontend/src/components/ChannelList.tsx b/frontend/src/components/ChannelList.tsx index 538f3e8c..90264a78 100644 --- a/frontend/src/components/ChannelList.tsx +++ b/frontend/src/components/ChannelList.tsx @@ -16,7 +16,7 @@ type ChannelListProps = { const ChannelList = ({ channelList, refreshChannelList }: ChannelListProps) => { const { userConfig } = useUserConfigStore(); - const viewLayout = userConfig.config.view_style_channel; + const viewLayout = userConfig.view_style_channel; if (!channelList || channelList.length === 0) { return

No channels found.

; diff --git a/frontend/src/components/DownloadListItem.tsx b/frontend/src/components/DownloadListItem.tsx index a16b7507..ef916ffc 100644 --- a/frontend/src/components/DownloadListItem.tsx +++ b/frontend/src/components/DownloadListItem.tsx @@ -16,8 +16,8 @@ type DownloadListItemProps = { const DownloadListItem = ({ download, setRefresh }: DownloadListItemProps) => { const { userConfig } = useUserConfigStore(); - const view = userConfig.config.view_style_downloads; - const showIgnored = userConfig.config.show_ignored_only; + const view = userConfig.view_style_downloads; + const showIgnored = userConfig.show_ignored_only; const [hideDownload, setHideDownload] = useState(false); diff --git a/frontend/src/components/EmbeddableVideoPlayer.tsx b/frontend/src/components/EmbeddableVideoPlayer.tsx index 039855de..726607c2 100644 --- a/frontend/src/components/EmbeddableVideoPlayer.tsx +++ b/frontend/src/components/EmbeddableVideoPlayer.tsx @@ -69,6 +69,7 @@ const EmbeddableVideoPlayer = ({ videoId }: EmbeddableVideoPlayerProps) => { setRefresh(false); } })(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [videoId, refresh]); useEffect(() => { diff --git a/frontend/src/components/Filterbar.tsx b/frontend/src/components/Filterbar.tsx index 5017127e..9255749d 100644 --- a/frontend/src/components/Filterbar.tsx +++ b/frontend/src/components/Filterbar.tsx @@ -7,6 +7,7 @@ import iconListView from '/img/icon-listview.svg'; import { SortByType, SortOrderType } from '../pages/Home'; import { useUserConfigStore } from '../stores/UserConfigStore'; import { ViewStyles } from '../configuration/constants/ViewStyle'; +import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; type FilterbarProps = { hideToggleText: string; @@ -15,9 +16,14 @@ type FilterbarProps = { }; const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: FilterbarProps) => { - const { userConfig, setPartialConfig } = useUserConfigStore(); + const { userConfig, setUserConfig } = useUserConfigStore(); const [showHidden, setShowHidden] = useState(false); - const isGridView = userConfig.config.view_style_home === ViewStyles.grid; + const isGridView = userConfig.view_style_home === ViewStyles.grid; + + const handleUserConfigUpdate = async (config: Partial) => { + const updatedUserConfig = await updateUserConfig(config); + setUserConfig(updatedUserConfig); + }; return (
@@ -27,13 +33,13 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar { - setPartialConfig({ hide_watched: !userConfig.config.hide_watched }); + handleUserConfigUpdate({ hide_watched: !userConfig.hide_watched }); }} /> - {userConfig.config.hide_watched ? ( + {userConfig.hide_watched ? ( @@ -52,9 +58,9 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar { - setPartialConfig({ sort_order: event.target.value as SortOrderType }); + handleUserConfigUpdate({ sort_order: event.target.value as SortOrderType }); }} > @@ -90,22 +96,22 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar /> )} - {userConfig.config.grid_items !== undefined && isGridView && ( + {userConfig.grid_items !== undefined && isGridView && (
- {userConfig.config.grid_items < 7 && ( + {userConfig.grid_items < 7 && ( { - setPartialConfig({ grid_items: userConfig.config.grid_items + 1 }); + handleUserConfigUpdate({ grid_items: userConfig.grid_items + 1 }); }} alt="grid plus row" /> )} - {userConfig.config.grid_items > 3 && ( + {userConfig.grid_items > 3 && ( { - setPartialConfig({ grid_items: userConfig.config.grid_items - 1 }); + handleUserConfigUpdate({ grid_items: userConfig.grid_items - 1 }); }} alt="grid minus row" /> @@ -115,14 +121,14 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar { - setPartialConfig({ [viewStyleName]: 'grid' }); + handleUserConfigUpdate({ [viewStyleName]: 'grid' }); }} alt="grid view" /> { - setPartialConfig({ [viewStyleName]: 'list' }); + handleUserConfigUpdate({ [viewStyleName]: 'list' }); }} alt="list view" /> diff --git a/frontend/src/components/GoogleCast.tsx b/frontend/src/components/GoogleCast.tsx index 4024c561..210c7203 100644 --- a/frontend/src/components/GoogleCast.tsx +++ b/frontend/src/components/GoogleCast.tsx @@ -93,8 +93,10 @@ const GoogleCast = ({ video, setRefresh, onWatchStateChanged }: GoogleCastProps) const [isConnected, setIsConnected] = useState(false); const setup = useCallback(() => { - const cast = globalThis.cast; - const chrome = globalThis.chrome; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cast = (globalThis as any).cast; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const chrome = (globalThis as any).chrome; cast.framework.CastContext.getInstance().setOptions({ receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, // Use built in receiver app on cast device, see https://developers.google.com/cast/docs/styled_receiver if you want to be able to add a theme, splash screen or watermark. Has a $5 one time fee. @@ -132,8 +134,10 @@ const GoogleCast = ({ video, setRefresh, onWatchStateChanged }: GoogleCastProps) }, [setRefresh, video]); const startPlayback = useCallback(() => { - const chrome = globalThis.chrome; - const cast = globalThis.cast; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const chrome = (globalThis as any).chrome; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cast = (globalThis as any).cast; const castSession = cast.framework.CastContext.getInstance().getCurrentSession(); const mediaUrl = video?.media_url; diff --git a/frontend/src/components/PlaylistList.tsx b/frontend/src/components/PlaylistList.tsx index 84c01525..03317097 100644 --- a/frontend/src/components/PlaylistList.tsx +++ b/frontend/src/components/PlaylistList.tsx @@ -14,7 +14,7 @@ type PlaylistListProps = { const PlaylistList = ({ playlistList, setRefresh }: PlaylistListProps) => { const { userConfig } = useUserConfigStore(); - const viewLayout = userConfig.config.view_style_playlist; + const viewLayout = userConfig.view_style_playlist; if (!playlistList || playlistList.length === 0) { return

No playlists found.

; diff --git a/frontend/src/components/VideoPlayer.tsx b/frontend/src/components/VideoPlayer.tsx index e720a66d..a613e57c 100644 --- a/frontend/src/components/VideoPlayer.tsx +++ b/frontend/src/components/VideoPlayer.tsx @@ -59,7 +59,6 @@ const Subtitles = ({ subtitles }: SubtitlesProp) => { const handleTimeUpdate = ( youtubeId: string, - duration: number, watched: boolean, sponsorBlock?: SponsorBlockType, setSponsorSegmentSkipped?: Dispatch>, @@ -337,7 +336,6 @@ const VideoPlayer = ({ }} onTimeUpdate={handleTimeUpdate( videoId, - duration, watched, sponsorBlock, setSkippedSegments, diff --git a/frontend/src/configuration/colours/useColours.ts b/frontend/src/configuration/colours/useColours.ts index 27b2acba..0c2a131d 100644 --- a/frontend/src/configuration/colours/useColours.ts +++ b/frontend/src/configuration/colours/useColours.ts @@ -9,7 +9,7 @@ export const ColourConstant = { const useColours = () => { const { userConfig } = useUserConfigStore(); - const stylesheet = userConfig?.config.stylesheet; + const stylesheet = userConfig?.stylesheet; switch (stylesheet) { case ColourConstant.Dark: diff --git a/frontend/src/functions/useIsAdmin.ts b/frontend/src/functions/useIsAdmin.ts index 7fb5661b..75ae3b39 100644 --- a/frontend/src/functions/useIsAdmin.ts +++ b/frontend/src/functions/useIsAdmin.ts @@ -1,8 +1,8 @@ -import { useUserConfigStore } from '../stores/UserConfigStore'; +import { useUserAccountStore } from '../stores/UserAccountStore'; const useIsAdmin = () => { - const { userConfig } = useUserConfigStore(); - const isAdmin = userConfig?.is_staff || userConfig?.is_superuser; + const { userAccount } = useUserAccountStore(); + const isAdmin = userAccount?.is_staff || userAccount?.is_superuser; return isAdmin; }; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f3f37e89..04fede50 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -25,6 +25,7 @@ import ChannelVideo from './pages/ChannelVideo'; import ChannelPlaylist from './pages/ChannelPlaylist'; import ChannelAbout from './pages/ChannelAbout'; import Download from './pages/Download'; +import loadUserAccount from './api/loader/loadUserAccount'; const router = createBrowserRouter( [ @@ -41,8 +42,9 @@ const router = createBrowserRouter( const authData = await auth.json(); const userConfig = await loadUserMeConfig(); + const userAccount = await loadUserAccount(); - return { userConfig, auth: authData }; + return { userConfig, userAccount, auth: authData }; }, element: , errorElement: , diff --git a/frontend/src/pages/Base.tsx b/frontend/src/pages/Base.tsx index 74cae1ad..aab8204c 100644 --- a/frontend/src/pages/Base.tsx +++ b/frontend/src/pages/Base.tsx @@ -1,11 +1,12 @@ import { Outlet, useLoaderData, useLocation, useSearchParams } from 'react-router-dom'; import Footer from '../components/Footer'; import useColours from '../configuration/colours/useColours'; -import { UserMeType } from '../api/actions/updateUserConfig'; +import { UserConfigType } from '../api/actions/updateUserConfig'; import { useEffect, useState } from 'react'; import Navigation from '../components/Navigation'; import { useAuthStore } from '../stores/AuthDataStore'; import { useUserConfigStore } from '../stores/UserConfigStore'; +import { useUserAccountStore } from '../stores/UserAccountStore'; export type TaUpdateType = { version?: string; @@ -19,8 +20,19 @@ export type AuthenticationType = { ta_update: TaUpdateType; }; +export type UserAccountType = { + id: number; + name: string; + is_superuser: boolean; + is_staff: boolean; + groups: []; + user_permissions: []; + last_login: string; +}; + type BaseLoaderData = { - userConfig: UserMeType; + userConfig: UserConfigType; + userAccount: UserAccountType; auth: AuthenticationType; }; @@ -32,7 +44,8 @@ export type OutletContextType = { const Base = () => { const { setAuth } = useAuthStore(); const { setUserConfig } = useUserConfigStore(); - const { userConfig, auth } = useLoaderData() as BaseLoaderData; + const { setUserAccount } = useUserAccountStore(); + const { userConfig, userAccount, auth } = useLoaderData() as BaseLoaderData; const location = useLocation(); @@ -46,6 +59,7 @@ const Base = () => { useEffect(() => { setAuth(auth); setUserConfig(userConfig); + setUserAccount(userAccount); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/frontend/src/pages/ChannelAbout.tsx b/frontend/src/pages/ChannelAbout.tsx index 679a7800..c87fca3c 100644 --- a/frontend/src/pages/ChannelAbout.tsx +++ b/frontend/src/pages/ChannelAbout.tsx @@ -244,7 +244,7 @@ const ChannelAbout = () => {

Channel Customization

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (
  • Overwrite the download format over the format set globally.
  • @@ -334,7 +334,7 @@ const ChannelAbout = () => {

Page Size Overrides

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (

Disable standard videos, shorts, or streams for this channel by setting their diff --git a/frontend/src/pages/ChannelPlaylist.tsx b/frontend/src/pages/ChannelPlaylist.tsx index 3f4846a1..52dbdcda 100644 --- a/frontend/src/pages/ChannelPlaylist.tsx +++ b/frontend/src/pages/ChannelPlaylist.tsx @@ -10,10 +10,11 @@ import { PlaylistsResponseType } from './Playlists'; import iconGridView from '/img/icon-gridview.svg'; import iconListView from '/img/icon-listview.svg'; import { useUserConfigStore } from '../stores/UserConfigStore'; +import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; const ChannelPlaylist = () => { const { channelId } = useParams(); - const { userConfig, setPartialConfig } = useUserConfigStore(); + const { userConfig, setUserConfig } = useUserConfigStore(); const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType; const [refreshPlaylists, setRefreshPlaylists] = useState(false); @@ -23,8 +24,13 @@ const ChannelPlaylist = () => { const playlistList = playlistsResponse?.data; const pagination = playlistsResponse?.paginate; - const view = userConfig.config.view_style_playlist; - const showSubedOnly = userConfig.config.show_subed_only; + const view = userConfig.view_style_playlist; + const showSubedOnly = userConfig.show_subed_only; + + const handleUserConfigUpdate = async (config: Partial) => { + const updatedUserConfig = await updateUserConfig(config); + setUserConfig(updatedUserConfig); + }; useEffect(() => { (async () => { @@ -52,7 +58,7 @@ const ChannelPlaylist = () => { { - setPartialConfig({ show_subed_only: !showSubedOnly }); + handleUserConfigUpdate({ show_subed_only: !showSubedOnly }); setRefreshPlaylists(true); }} type="checkbox" @@ -73,14 +79,14 @@ const ChannelPlaylist = () => { { - setPartialConfig({ view_style_playlist: 'grid' }); + handleUserConfigUpdate({ view_style_playlist: 'grid' }); }} alt="grid view" /> { - setPartialConfig({ view_style_playlist: 'list' }); + handleUserConfigUpdate({ view_style_playlist: 'list' }); }} alt="list view" /> diff --git a/frontend/src/pages/ChannelVideo.tsx b/frontend/src/pages/ChannelVideo.tsx index ab43184a..f735f64d 100644 --- a/frontend/src/pages/ChannelVideo.tsx +++ b/frontend/src/pages/ChannelVideo.tsx @@ -49,10 +49,10 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => { const hasVideos = videoResponse?.data?.length !== 0; const showEmbeddedVideo = videoId !== null; - const view = userConfig.config.view_style_home; + const view = userConfig.view_style_home; const isGridView = view === ViewStyles.grid; - const gridView = isGridView ? `boxed-${userConfig.config.grid_items}` : ''; - const gridViewGrid = isGridView ? `grid-${userConfig.config.grid_items}` : ''; + const gridView = isGridView ? `boxed-${userConfig.grid_items}` : ''; + const gridViewGrid = isGridView ? `grid-${userConfig.grid_items}` : ''; useEffect(() => { (async () => { @@ -60,9 +60,9 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => { const videos = await loadVideoListByFilter({ channel: channelId, page: currentPage, - watch: userConfig.config.hide_watched ? 'unwatched' : undefined, - sort: userConfig.config.sort_by, - order: userConfig.config.sort_order, + watch: userConfig.hide_watched ? 'unwatched' : undefined, + sort: userConfig.sort_by, + order: userConfig.sort_order, type: videoType, }); const channelAggs = await loadChannelAggs(channelId); @@ -74,9 +74,9 @@ const ChannelVideo = ({ videoType }: ChannelVideoProps) => { })(); }, [ refresh, - userConfig.config.sort_by, - userConfig.config.sort_order, - userConfig.config.hide_watched, + userConfig.sort_by, + userConfig.sort_order, + userConfig.hide_watched, currentPage, channelId, pagination?.current_page, diff --git a/frontend/src/pages/Channels.tsx b/frontend/src/pages/Channels.tsx index b113708d..57c8d998 100644 --- a/frontend/src/pages/Channels.tsx +++ b/frontend/src/pages/Channels.tsx @@ -14,6 +14,7 @@ import Button from '../components/Button'; import updateBulkChannelSubscriptions from '../api/actions/updateBulkChannelSubscriptions'; import useIsAdmin from '../functions/useIsAdmin'; import { useUserConfigStore } from '../stores/UserConfigStore'; +import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; type ChannelOverwritesType = { download_format?: string; @@ -48,7 +49,7 @@ type ChannelsListResponse = { }; const Channels = () => { - const { userConfig, setPartialConfig } = useUserConfigStore(); + const { userConfig, setUserConfig } = useUserConfigStore(); const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType; const isAdmin = useIsAdmin(); @@ -63,18 +64,20 @@ const Channels = () => { // const channelCount = pagination?.total_hits; const hasChannels = channels?.length !== 0; + const handleUserConfigUpdate = async (config: Partial) => { + const updatedUserConfig = await updateUserConfig(config); + setUserConfig(updatedUserConfig); + }; + useEffect(() => { (async () => { - const channelListResponse = await loadChannelList( - currentPage, - userConfig.config.show_subed_only, - ); + const channelListResponse = await loadChannelList(currentPage, userConfig.show_subed_only); setChannelListResponse(channelListResponse); setShowNotification(false); setRefresh(false); })(); - }, [refresh, userConfig.config.show_subed_only, currentPage, pagination?.current_page]); + }, [refresh, userConfig.show_subed_only, currentPage, pagination?.current_page]); return ( <> @@ -145,18 +148,18 @@ const Channels = () => { { - setPartialConfig({ show_subed_only: !userConfig.config.show_subed_only }); + handleUserConfigUpdate({ show_subed_only: !userConfig.show_subed_only }); setRefresh(true); }} type="checkbox" - checked={userConfig.config.show_subed_only} + checked={userConfig.show_subed_only} /> - {!userConfig.config.show_subed_only && ( + {!userConfig.show_subed_only && ( )} - {userConfig.config.show_subed_only && ( + {userConfig.show_subed_only && ( @@ -167,7 +170,7 @@ const Channels = () => { { - setPartialConfig({ view_style_channel: 'grid' }); + handleUserConfigUpdate({ view_style_channel: 'grid' }); }} data-origin="channel" data-value="grid" @@ -176,7 +179,7 @@ const Channels = () => { { - setPartialConfig({ view_style_channel: 'list' }); + handleUserConfigUpdate({ view_style_channel: 'list' }); }} data-origin="channel" data-value="list" @@ -186,7 +189,7 @@ const Channels = () => {

{/* {hasChannels &&

Total channels: {channelCount}

} */} -
+
{!hasChannels &&

No channels found...

} {hasChannels && } diff --git a/frontend/src/pages/Download.tsx b/frontend/src/pages/Download.tsx index d46adae6..f9db07fd 100644 --- a/frontend/src/pages/Download.tsx +++ b/frontend/src/pages/Download.tsx @@ -19,6 +19,7 @@ import Button from '../components/Button'; import DownloadListItem from '../components/DownloadListItem'; import loadDownloadAggs, { DownloadAggsType } from '../api/loader/loadDownloadAggs'; import { useUserConfigStore } from '../stores/UserConfigStore'; +import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; type Download = { auto_start: boolean; @@ -46,7 +47,7 @@ export type DownloadResponseType = { const Download = () => { const [searchParams, setSearchParams] = useSearchParams(); - const { userConfig, setPartialConfig } = useUserConfigStore(); + const { userConfig, setUserConfig } = useUserConfigStore(); const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType; const channelFilterFromUrl = searchParams.get('channel'); @@ -75,13 +76,18 @@ const Download = () => { ? downloadResponse?.data[0].channel_name : ''; - const view = userConfig.config.view_style_downloads; - const gridItems = userConfig.config.grid_items; - const showIgnored = userConfig.config.show_ignored_only; + const view = userConfig.view_style_downloads; + const gridItems = userConfig.grid_items; + const showIgnored = userConfig.show_ignored_only; const isGridView = view === ViewStyles.grid; const gridView = isGridView ? `boxed-${gridItems}` : ''; const gridViewGrid = isGridView ? `grid-${gridItems}` : ''; + const handleUserConfigUpdate = async (config: Partial) => { + const updatedUserConfig = await updateUserConfig(config); + setUserConfig(updatedUserConfig); + }; + useEffect(() => { (async () => { const videos = await loadDownloadQueue(currentPage, channelFilterFromUrl, showIgnored); @@ -208,7 +214,7 @@ const Download = () => { { - setPartialConfig({ show_ignored_only: !showIgnored }); + handleUserConfigUpdate({ show_ignored_only: !showIgnored }); setRefresh(true); }} type="checkbox" @@ -263,7 +269,7 @@ const Download = () => { { - setPartialConfig({ grid_items: gridItems + 1 }); + handleUserConfigUpdate({ grid_items: gridItems + 1 }); }} alt="grid plus row" /> @@ -272,7 +278,7 @@ const Download = () => { { - setPartialConfig({ grid_items: gridItems - 1 }); + handleUserConfigUpdate({ grid_items: gridItems - 1 }); }} alt="grid minus row" /> @@ -283,14 +289,14 @@ const Download = () => { { - setPartialConfig({ view_style_downloads: 'grid' }); + handleUserConfigUpdate({ view_style_downloads: 'grid' }); }} alt="grid view" /> { - setPartialConfig({ view_style_downloads: 'list' }); + handleUserConfigUpdate({ view_style_downloads: 'list' }); }} alt="list view" /> diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 11cd89cb..b8e4b58e 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -108,8 +108,6 @@ const Home = () => { const [searchParams] = useSearchParams(); const videoId = searchParams.get('videoId'); - const userMeConfig = userConfig.config; - const [refreshVideoList, setRefreshVideoList] = useState(false); const [videoResponse, setVideoReponse] = useState(); @@ -123,17 +121,17 @@ const Home = () => { const hasVideos = videoResponse?.data?.length !== 0; const showEmbeddedVideo = videoId !== null; - const isGridView = userMeConfig.view_style_home === ViewStyles.grid; - const gridView = isGridView ? `boxed-${userMeConfig.grid_items}` : ''; - const gridViewGrid = isGridView ? `grid-${userMeConfig.grid_items}` : ''; + const isGridView = userConfig.view_style_home === ViewStyles.grid; + const gridView = isGridView ? `boxed-${userConfig.grid_items}` : ''; + const gridViewGrid = isGridView ? `grid-${userConfig.grid_items}` : ''; useEffect(() => { (async () => { const videos = await loadVideoListByFilter({ page: currentPage, - watch: userMeConfig.hide_watched ? 'unwatched' : undefined, - sort: userMeConfig.sort_by, - order: userMeConfig.sort_order, + watch: userConfig.hide_watched ? 'unwatched' : undefined, + sort: userConfig.sort_by, + order: userConfig.sort_order, }); try { @@ -150,9 +148,9 @@ const Home = () => { })(); }, [ refreshVideoList, - userMeConfig.sort_by, - userMeConfig.sort_order, - userMeConfig.hide_watched, + userConfig.sort_by, + userConfig.sort_order, + userConfig.hide_watched, currentPage, pagination?.current_page, showEmbeddedVideo, @@ -171,10 +169,10 @@ const Home = () => {

Continue Watching

-
+
@@ -189,7 +187,7 @@ const Home = () => {
-
+
{!hasVideos && ( <>

No videos found...

@@ -204,7 +202,7 @@ const Home = () => { {hasVideos && ( )} diff --git a/frontend/src/pages/Playlist.tsx b/frontend/src/pages/Playlist.tsx index 3ea08180..497d8acb 100644 --- a/frontend/src/pages/Playlist.tsx +++ b/frontend/src/pages/Playlist.tsx @@ -81,9 +81,9 @@ const Playlist = () => { const videoInPlaylistCount = pagination?.total_hits; const showEmbeddedVideo = videoId !== null; - const view = userConfig.config.view_style_home; - const gridItems = userConfig.config.grid_items; - const hideWatched = userConfig.config.hide_watched; + const view = userConfig.view_style_home; + const gridItems = userConfig.grid_items; + const hideWatched = userConfig.hide_watched; const isGridView = view === ViewStyles.grid; const gridView = isGridView ? `boxed-${gridItems}` : ''; const gridViewGrid = isGridView ? `grid-${gridItems}` : ''; @@ -112,7 +112,7 @@ const Playlist = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [ playlistId, - userConfig.config.hide_watched, + userConfig.hide_watched, refresh, currentPage, pagination?.current_page, diff --git a/frontend/src/pages/Playlists.tsx b/frontend/src/pages/Playlists.tsx index 55b08ecf..db14e5da 100644 --- a/frontend/src/pages/Playlists.tsx +++ b/frontend/src/pages/Playlists.tsx @@ -18,6 +18,7 @@ import Button from '../components/Button'; import useIsAdmin from '../functions/useIsAdmin'; import { useUserConfigStore } from '../stores/UserConfigStore'; import Notifications from '../components/Notifications'; +import updateUserConfig, { UserConfigType } from '../api/actions/updateUserConfig'; export type PlaylistEntryType = { youtube_id: string; @@ -34,7 +35,7 @@ export type PlaylistsResponseType = { }; const Playlists = () => { - const { userConfig, setPartialConfig } = useUserConfigStore(); + const { userConfig, setUserConfig } = useUserConfigStore(); const { currentPage, setCurrentPage } = useOutletContext() as OutletContextType; const isAdmin = useIsAdmin(); @@ -51,8 +52,8 @@ const Playlists = () => { const hasPlaylists = playlistResponse?.data?.length !== 0; - const view = userConfig.config.view_style_playlist; - const showSubedOnly = userConfig.config.show_subed_only; + const view = userConfig.view_style_playlist; + const showSubedOnly = userConfig.show_subed_only; useEffect(() => { (async () => { @@ -66,7 +67,12 @@ const Playlists = () => { setShowNotification(false); })(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [refresh, userConfig.config.show_subed_only, currentPage, pagination?.current_page]); + }, [refresh, userConfig.show_subed_only, currentPage, pagination?.current_page]); + + const handleUserConfigUpdate = async (config: Partial) => { + const updatedUserConfig = await updateUserConfig(config); + setUserConfig(updatedUserConfig); + }; return ( <> @@ -156,7 +162,7 @@ const Playlists = () => { { - setPartialConfig({ show_subed_only: !showSubedOnly }); + handleUserConfigUpdate({ show_subed_only: !showSubedOnly }); }} type="checkbox" /> @@ -176,14 +182,14 @@ const Playlists = () => { { - setPartialConfig({ view_style_playlist: 'grid' }); + handleUserConfigUpdate({ view_style_playlist: 'grid' }); }} alt="grid view" /> { - setPartialConfig({ view_style_playlist: 'list' }); + handleUserConfigUpdate({ view_style_playlist: 'list' }); }} alt="list view" /> diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx index acf39d71..3d9bcdf6 100644 --- a/frontend/src/pages/Search.tsx +++ b/frontend/src/pages/Search.tsx @@ -39,12 +39,11 @@ const Search = () => { const { userConfig } = useUserConfigStore(); const [searchParams] = useSearchParams(); const videoId = searchParams.get('videoId'); - const userMeConfig = userConfig.config; - const viewVideos = userMeConfig.view_style_home; - const viewChannels = userMeConfig.view_style_channel; - const viewPlaylists = userMeConfig.view_style_playlist; - const gridItems = userMeConfig.grid_items || 3; + const viewVideos = userConfig.view_style_home; + const viewChannels = userConfig.view_style_channel; + const viewPlaylists = userConfig.view_style_playlist; + const gridItems = userConfig.grid_items || 3; const [searchTerm, setSearchTerm] = useState(''); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(''); diff --git a/frontend/src/pages/SettingsApplication.tsx b/frontend/src/pages/SettingsApplication.tsx index 06dc214d..7ed9f9d8 100644 --- a/frontend/src/pages/SettingsApplication.tsx +++ b/frontend/src/pages/SettingsApplication.tsx @@ -209,7 +209,7 @@ const SettingsApplication = () => {

Subscription Scan

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (

Configure how a subscription Scan tracks videos.

    @@ -275,7 +275,7 @@ const SettingsApplication = () => {

Downloads

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (
  • @@ -362,7 +362,7 @@ const SettingsApplication = () => {

Download Format

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (
  • @@ -507,7 +507,7 @@ const SettingsApplication = () => {

Subtitles

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (

Additional subtitle options show once you choose a language.

    @@ -586,7 +586,7 @@ const SettingsApplication = () => {

Comments

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (

Additional options show once you set a comment index option.

    @@ -650,7 +650,7 @@ const SettingsApplication = () => {
- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (

Importing your cookie will authenticate requests to YT with your user account. @@ -777,7 +777,7 @@ const SettingsApplication = () => {

Integrations

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (
  • The API token is used to make automater requests to TA through the API.
  • @@ -868,7 +868,7 @@ const SettingsApplication = () => {

Snapshots

- {userConfig.config.show_help_text && ( + {userConfig.show_help_text && (

Automatically create daily deduplicated snapshots of the index, stored in diff --git a/frontend/src/pages/SettingsUser.tsx b/frontend/src/pages/SettingsUser.tsx index c8f2087a..84be8e13 100644 --- a/frontend/src/pages/SettingsUser.tsx +++ b/frontend/src/pages/SettingsUser.tsx @@ -1,5 +1,5 @@ import { useNavigate } from 'react-router-dom'; -import { ColourVariants } from '../api/actions/updateUserConfig'; +import updateUserConfig, { ColourVariants, UserConfigType } from '../api/actions/updateUserConfig'; import { ColourConstant } from '../configuration/colours/useColours'; import SettingsNavigation from '../components/SettingsNavigation'; import Notifications from '../components/Notifications'; @@ -10,35 +10,40 @@ import { useEffect, useState } from 'react'; import ToggleConfig from '../components/ToggleConfig'; const SettingsUser = () => { - const { userConfig, setPartialConfig } = useUserConfigStore(); + const { userConfig, setUserConfig } = useUserConfigStore(); const isAdmin = useIsAdmin(); const navigate = useNavigate(); - const [styleSheet, setStyleSheet] = useState(userConfig.config.stylesheet); + const [styleSheet, setStyleSheet] = useState(userConfig.stylesheet); const [styleSheetRefresh, setStyleSheetRefresh] = useState(false); - const [pageSize, setPageSize] = useState(userConfig.config.page_size); - const [showHelpText, setShowHelpText] = useState(userConfig.config.show_help_text); + const [pageSize, setPageSize] = useState(userConfig.page_size); + const [showHelpText, setShowHelpText] = useState(userConfig.show_help_text); useEffect(() => { (async () => { - setStyleSheet(userConfig.config.stylesheet); - setPageSize(userConfig.config.page_size); - setShowHelpText(userConfig.config.show_help_text); + setStyleSheet(userConfig.stylesheet); + setPageSize(userConfig.page_size); + setShowHelpText(userConfig.show_help_text); })(); - }, [userConfig.config.page_size, userConfig.config.stylesheet, userConfig.config.show_help_text]); + }, [userConfig.page_size, userConfig.stylesheet, userConfig.show_help_text]); const handleStyleSheetChange = async (selectedStyleSheet: ColourVariants) => { - setPartialConfig({ stylesheet: selectedStyleSheet }); + handleUserConfigUpdate({ stylesheet: selectedStyleSheet }); setStyleSheet(selectedStyleSheet); setStyleSheetRefresh(true); }; const handlePageSizeChange = async () => { - setPartialConfig({ page_size: pageSize }); + handleUserConfigUpdate({ page_size: pageSize }); }; const handleShowHelpTextChange = async (configKey: string, configValue: boolean) => { - setPartialConfig({ [configKey]: configValue }); + handleUserConfigUpdate({ [configKey]: configValue }); + }; + + const handleUserConfigUpdate = async (config: Partial) => { + const updatedUserConfig = await updateUserConfig(config); + setUserConfig(updatedUserConfig); }; const handlePageRefresh = () => { @@ -99,12 +104,10 @@ const SettingsUser = () => { />

- {userConfig.config.page_size !== pageSize && ( + {userConfig.page_size !== pageSize && ( <> - + )}
diff --git a/frontend/src/stores/UserAccountStore.ts b/frontend/src/stores/UserAccountStore.ts new file mode 100644 index 00000000..4ed312ee --- /dev/null +++ b/frontend/src/stores/UserAccountStore.ts @@ -0,0 +1,12 @@ +import { create } from 'zustand'; +import { UserAccountType } from '../pages/Base'; + +interface AccountState { + userAccount: UserAccountType | null; + setUserAccount: (userAccount: UserAccountType) => void; +} + +export const useUserAccountStore = create(set => ({ + userAccount: null, + setUserAccount: userAccount => set({ userAccount }), +})); diff --git a/frontend/src/stores/UserConfigStore.ts b/frontend/src/stores/UserConfigStore.ts index d3cec6b7..609f4b7b 100644 --- a/frontend/src/stores/UserConfigStore.ts +++ b/frontend/src/stores/UserConfigStore.ts @@ -1,45 +1,26 @@ import { create } from 'zustand'; -import updateUserConfig, { UserMeType, UserConfigType } from '../api/actions/updateUserConfig'; +import { UserConfigType } from '../api/actions/updateUserConfig'; interface UserConfigState { - userConfig: UserMeType; - setUserConfig: (userConfig: UserMeType) => void; - setPartialConfig: (userConfig: Partial) => void; + userConfig: UserConfigType; + setUserConfig: (userConfig: UserConfigType) => void; } export const useUserConfigStore = create(set => ({ userConfig: { - id: 0, - name: '', - is_superuser: false, - is_staff: false, - groups: [], - user_permissions: [], - last_login: '', - config: { - stylesheet: 'dark.css', - page_size: 12, - sort_by: 'published', - sort_order: 'desc', - view_style_home: 'grid', - view_style_channel: 'list', - view_style_downloads: 'list', - view_style_playlist: 'grid', - grid_items: 3, - hide_watched: false, - show_ignored_only: false, - show_subed_only: false, - show_help_text: true, - }, + stylesheet: 'dark.css', + page_size: 12, + sort_by: 'published', + sort_order: 'desc', + view_style_home: 'grid', + view_style_channel: 'list', + view_style_downloads: 'list', + view_style_playlist: 'grid', + grid_items: 3, + hide_watched: false, + show_ignored_only: false, + show_subed_only: false, + show_help_text: true, }, setUserConfig: userConfig => set({ userConfig }), - - setPartialConfig: async (userConfig: Partial) => { - const userConfigResponse = await updateUserConfig(userConfig); - set(state => ({ - userConfig: state.userConfig - ? { ...state.userConfig, config: userConfigResponse } - : state.userConfig, - })); - }, })); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 41261f94..be6d249e 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,9 +1,10 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react-swc'; +import checker from 'vite-plugin-checker'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), checker({ typescript: true })], base: '/', server: { host: true,