split between userConfig and userAccount

This commit is contained in:
Simon 2025-02-13 21:43:01 +07:00
parent ad7af09159
commit d96ac486e4
No known key found for this signature in database
GPG Key ID: 2C15AA5E89985DD4
30 changed files with 640 additions and 180 deletions

View File

@ -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",

View File

@ -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"
}
}

View File

@ -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<UserConfigType>): Promise<UserConfigType> => {
return APIClient('/api/user/me/', {
method: 'POST',
body: { config: config },
body: config,
});
};

View File

@ -0,0 +1,8 @@
import APIClient from '../../functions/APIClient';
import { UserAccountType } from '../../pages/Base';
const loadUserAccount = async (): Promise<UserAccountType> => {
return APIClient('/api/user/account/');
};
export default loadUserAccount;

View File

@ -1,7 +1,7 @@
import { UserMeType } from '../actions/updateUserConfig';
import { UserConfigType } from '../actions/updateUserConfig';
import APIClient from '../../functions/APIClient';
const loadUserMeConfig = async (): Promise<UserMeType> => {
const loadUserMeConfig = async (): Promise<UserConfigType> => {
return APIClient('/api/user/me/');
};

View File

@ -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 <p>No channels found.</p>;

View File

@ -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);

View File

@ -69,6 +69,7 @@ const EmbeddableVideoPlayer = ({ videoId }: EmbeddableVideoPlayerProps) => {
setRefresh(false);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [videoId, refresh]);
useEffect(() => {

View File

@ -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<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
return (
<div className="view-controls three">
@ -27,13 +33,13 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
<input
id="hide_watched"
type="checkbox"
checked={userConfig.config.hide_watched}
checked={userConfig.hide_watched}
onChange={() => {
setPartialConfig({ hide_watched: !userConfig.config.hide_watched });
handleUserConfigUpdate({ hide_watched: !userConfig.hide_watched });
}}
/>
{userConfig.config.hide_watched ? (
{userConfig.hide_watched ? (
<label htmlFor="" className="onbtn">
On
</label>
@ -52,9 +58,9 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
<select
name="sort_by"
id="sort"
value={userConfig.config.sort_by}
value={userConfig.sort_by}
onChange={event => {
setPartialConfig({ sort_by: event.target.value as SortByType });
handleUserConfigUpdate({ sort_by: event.target.value as SortByType });
}}
>
<option value="published">date published</option>
@ -67,9 +73,9 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
<select
name="sort_order"
id="sort-order"
value={userConfig.config.sort_order}
value={userConfig.sort_order}
onChange={event => {
setPartialConfig({ sort_order: event.target.value as SortOrderType });
handleUserConfigUpdate({ sort_order: event.target.value as SortOrderType });
}}
>
<option value="asc">asc</option>
@ -90,22 +96,22 @@ const Filterbar = ({ hideToggleText, viewStyleName, showSort = true }: Filterbar
/>
)}
{userConfig.config.grid_items !== undefined && isGridView && (
{userConfig.grid_items !== undefined && isGridView && (
<div className="grid-count">
{userConfig.config.grid_items < 7 && (
{userConfig.grid_items < 7 && (
<img
src={iconAdd}
onClick={() => {
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 && (
<img
src={iconSubstract}
onClick={() => {
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
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ [viewStyleName]: 'grid' });
handleUserConfigUpdate({ [viewStyleName]: 'grid' });
}}
alt="grid view"
/>
<img
src={iconListView}
onClick={() => {
setPartialConfig({ [viewStyleName]: 'list' });
handleUserConfigUpdate({ [viewStyleName]: 'list' });
}}
alt="list view"
/>

View File

@ -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;

View File

@ -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 <p>No playlists found.</p>;

View File

@ -59,7 +59,6 @@ const Subtitles = ({ subtitles }: SubtitlesProp) => {
const handleTimeUpdate =
(
youtubeId: string,
duration: number,
watched: boolean,
sponsorBlock?: SponsorBlockType,
setSponsorSegmentSkipped?: Dispatch<SetStateAction<SponsorSegmentsSkippedType>>,
@ -337,7 +336,6 @@ const VideoPlayer = ({
}}
onTimeUpdate={handleTimeUpdate(
videoId,
duration,
watched,
sponsorBlock,
setSkippedSegments,

View File

@ -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:

View File

@ -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;
};

View File

@ -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: <Base />,
errorElement: <ErrorPage />,

View File

@ -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
}, []);

View File

@ -244,7 +244,7 @@ const ChannelAbout = () => {
<div className="info-box">
<div className="info-box-item">
<h2>Channel Customization</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<ul>
<li>Overwrite the download format over the format set globally.</li>
@ -334,7 +334,7 @@ const ChannelAbout = () => {
</div>
<div className="info-box-item">
<h2>Page Size Overrides</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>
Disable standard videos, shorts, or streams for this channel by setting their

View File

@ -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<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
useEffect(() => {
(async () => {
@ -52,7 +58,7 @@ const ChannelPlaylist = () => {
<input
checked={showSubedOnly}
onChange={() => {
setPartialConfig({ show_subed_only: !showSubedOnly });
handleUserConfigUpdate({ show_subed_only: !showSubedOnly });
setRefreshPlaylists(true);
}}
type="checkbox"
@ -73,14 +79,14 @@ const ChannelPlaylist = () => {
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ view_style_playlist: 'grid' });
handleUserConfigUpdate({ view_style_playlist: 'grid' });
}}
alt="grid view"
/>
<img
src={iconListView}
onClick={() => {
setPartialConfig({ view_style_playlist: 'list' });
handleUserConfigUpdate({ view_style_playlist: 'list' });
}}
alt="list view"
/>

View File

@ -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,

View File

@ -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<UserConfigType>) => {
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 = () => {
<input
id="show_subed_only"
onChange={async () => {
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 && (
<label htmlFor="" className="ofbtn">
Off
</label>
)}
{userConfig.config.show_subed_only && (
{userConfig.show_subed_only && (
<label htmlFor="" className="onbtn">
On
</label>
@ -167,7 +170,7 @@ const Channels = () => {
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ view_style_channel: 'grid' });
handleUserConfigUpdate({ view_style_channel: 'grid' });
}}
data-origin="channel"
data-value="grid"
@ -176,7 +179,7 @@ const Channels = () => {
<img
src={iconListView}
onClick={() => {
setPartialConfig({ view_style_channel: 'list' });
handleUserConfigUpdate({ view_style_channel: 'list' });
}}
data-origin="channel"
data-value="list"
@ -186,7 +189,7 @@ const Channels = () => {
</div>
{/* {hasChannels && <h2>Total channels: {channelCount}</h2>} */}
<div className={`channel-list ${userConfig.config.view_style_channel}`}>
<div className={`channel-list ${userConfig.view_style_channel}`}>
{!hasChannels && <h2>No channels found...</h2>}
{hasChannels && <ChannelList channelList={channels} refreshChannelList={setRefresh} />}

View File

@ -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<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
useEffect(() => {
(async () => {
const videos = await loadDownloadQueue(currentPage, channelFilterFromUrl, showIgnored);
@ -208,7 +214,7 @@ const Download = () => {
<input
id="showIgnored"
onChange={() => {
setPartialConfig({ show_ignored_only: !showIgnored });
handleUserConfigUpdate({ show_ignored_only: !showIgnored });
setRefresh(true);
}}
type="checkbox"
@ -263,7 +269,7 @@ const Download = () => {
<img
src={iconAdd}
onClick={() => {
setPartialConfig({ grid_items: gridItems + 1 });
handleUserConfigUpdate({ grid_items: gridItems + 1 });
}}
alt="grid plus row"
/>
@ -272,7 +278,7 @@ const Download = () => {
<img
src={iconSubstract}
onClick={() => {
setPartialConfig({ grid_items: gridItems - 1 });
handleUserConfigUpdate({ grid_items: gridItems - 1 });
}}
alt="grid minus row"
/>
@ -283,14 +289,14 @@ const Download = () => {
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ view_style_downloads: 'grid' });
handleUserConfigUpdate({ view_style_downloads: 'grid' });
}}
alt="grid view"
/>
<img
src={iconListView}
onClick={() => {
setPartialConfig({ view_style_downloads: 'list' });
handleUserConfigUpdate({ view_style_downloads: 'list' });
}}
alt="list view"
/>

View File

@ -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<VideoListByFilterResponseType>();
@ -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 = () => {
<div className="title-bar">
<h1>Continue Watching</h1>
</div>
<div className={`video-list ${userMeConfig.view_style_home} ${gridViewGrid}`}>
<div className={`video-list ${userConfig.view_style_home} ${gridViewGrid}`}>
<VideoList
videoList={continueVideos}
viewLayout={userMeConfig.view_style_home}
viewLayout={userConfig.view_style_home}
refreshVideoList={setRefreshVideoList}
/>
</div>
@ -189,7 +187,7 @@ const Home = () => {
</div>
<div className={`boxed-content ${gridView}`}>
<div className={`video-list ${userMeConfig.view_style_home} ${gridViewGrid}`}>
<div className={`video-list ${userConfig.view_style_home} ${gridViewGrid}`}>
{!hasVideos && (
<>
<h2>No videos found...</h2>
@ -204,7 +202,7 @@ const Home = () => {
{hasVideos && (
<VideoList
videoList={videoList}
viewLayout={userMeConfig.view_style_home}
viewLayout={userConfig.view_style_home}
refreshVideoList={setRefreshVideoList}
/>
)}

View File

@ -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,

View File

@ -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<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
return (
<>
@ -156,7 +162,7 @@ const Playlists = () => {
<input
checked={showSubedOnly}
onChange={() => {
setPartialConfig({ show_subed_only: !showSubedOnly });
handleUserConfigUpdate({ show_subed_only: !showSubedOnly });
}}
type="checkbox"
/>
@ -176,14 +182,14 @@ const Playlists = () => {
<img
src={iconGridView}
onClick={() => {
setPartialConfig({ view_style_playlist: 'grid' });
handleUserConfigUpdate({ view_style_playlist: 'grid' });
}}
alt="grid view"
/>
<img
src={iconListView}
onClick={() => {
setPartialConfig({ view_style_playlist: 'list' });
handleUserConfigUpdate({ view_style_playlist: 'list' });
}}
alt="list view"
/>

View File

@ -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<string>('');

View File

@ -209,7 +209,7 @@ const SettingsApplication = () => {
<div className="info-box">
<div className="info-box-item">
<h2 id="subscriptions">Subscription Scan</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>Configure how a subscription Scan tracks videos.</p>
<ul>
@ -275,7 +275,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="downloads">Downloads</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<ul>
<li>
@ -362,7 +362,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="format">Download Format</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<ul>
<li>
@ -507,7 +507,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="subtitles">Subtitles</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>Additional subtitle options show once you choose a language.</p>
<ul>
@ -586,7 +586,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="comments">Comments</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>Additional options show once you set a comment index option.</p>
<ul>
@ -650,7 +650,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="cookie">Cookie</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>
Importing your cookie will authenticate requests to YT with your user account.
@ -777,7 +777,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2 id="sntegrations">Integrations</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<ul>
<li>The API token is used to make automater requests to TA through the API.</li>
@ -868,7 +868,7 @@ const SettingsApplication = () => {
</div>
<div className="info-box-item">
<h2>Snapshots</h2>
{userConfig.config.show_help_text && (
{userConfig.show_help_text && (
<div className="help-text">
<p>
Automatically create daily deduplicated snapshots of the index, stored in

View File

@ -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<ColourVariants>(userConfig.config.stylesheet);
const [styleSheet, setStyleSheet] = useState<ColourVariants>(userConfig.stylesheet);
const [styleSheetRefresh, setStyleSheetRefresh] = useState(false);
const [pageSize, setPageSize] = useState<number>(userConfig.config.page_size);
const [showHelpText, setShowHelpText] = useState(userConfig.config.show_help_text);
const [pageSize, setPageSize] = useState<number>(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<UserConfigType>) => {
const updatedUserConfig = await updateUserConfig(config);
setUserConfig(updatedUserConfig);
};
const handlePageRefresh = () => {
@ -99,12 +104,10 @@ const SettingsUser = () => {
/>
<div className="button-box">
{userConfig.config.page_size !== pageSize && (
{userConfig.page_size !== pageSize && (
<>
<button onClick={handlePageSizeChange}>Update</button>
<button onClick={() => setPageSize(userConfig.config.page_size)}>
Cancel
</button>
<button onClick={() => setPageSize(userConfig.page_size)}>Cancel</button>
</>
)}
</div>

View File

@ -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<AccountState>(set => ({
userAccount: null,
setUserAccount: userAccount => set({ userAccount }),
}));

View File

@ -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<UserConfigType>) => void;
userConfig: UserConfigType;
setUserConfig: (userConfig: UserConfigType) => void;
}
export const useUserConfigStore = create<UserConfigState>(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<UserConfigType>) => {
const userConfigResponse = await updateUserConfig(userConfig);
set(state => ({
userConfig: state.userConfig
? { ...state.userConfig, config: userConfigResponse }
: state.userConfig,
}));
},
}));

View File

@ -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,