1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Update to React 18

This commit is contained in:
grafixeyehero 2024-06-02 20:58:11 +03:00
parent b5d6e37fb3
commit be891c3a98
36 changed files with 339 additions and 311 deletions

161
package-lock.json generated
View file

@ -51,9 +51,9 @@
"material-design-icons-iconfont": "6.7.0", "material-design-icons-iconfont": "6.7.0",
"native-promise-only": "0.8.1", "native-promise-only": "0.8.1",
"pdfjs-dist": "3.11.174", "pdfjs-dist": "3.11.174",
"react": "17.0.2", "react": "18.3.1",
"react-blurhash": "0.3.0", "react-blurhash": "0.3.0",
"react-dom": "17.0.2", "react-dom": "18.3.1",
"react-lazy-load-image-component": "1.6.0", "react-lazy-load-image-component": "1.6.0",
"react-router-dom": "6.23.1", "react-router-dom": "6.23.1",
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",
@ -75,8 +75,8 @@
"@types/loadable__component": "5.13.9", "@types/loadable__component": "5.13.9",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "4.17.12",
"@types/markdown-it": "14.1.1", "@types/markdown-it": "14.1.1",
"@types/react": "17.0.80", "@types/react": "18.3.3",
"@types/react-dom": "17.0.25", "@types/react-dom": "18.3.0",
"@types/sortablejs": "1.15.8", "@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0", "@typescript-eslint/parser": "5.62.0",
@ -3638,28 +3638,28 @@
} }
}, },
"node_modules/@floating-ui/core": { "node_modules/@floating-ui/core": {
"version": "1.6.0", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz",
"integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==",
"dependencies": { "dependencies": {
"@floating-ui/utils": "^0.2.1" "@floating-ui/utils": "^0.2.0"
} }
}, },
"node_modules/@floating-ui/dom": { "node_modules/@floating-ui/dom": {
"version": "1.6.3", "version": "1.6.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz",
"integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
"dependencies": { "dependencies": {
"@floating-ui/core": "^1.0.0", "@floating-ui/core": "^1.0.0",
"@floating-ui/utils": "^0.2.0" "@floating-ui/utils": "^0.2.0"
} }
}, },
"node_modules/@floating-ui/react-dom": { "node_modules/@floating-ui/react-dom": {
"version": "2.0.8", "version": "2.0.9",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz",
"integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.6.1" "@floating-ui/dom": "^1.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=16.8.0", "react": ">=16.8.0",
@ -3667,9 +3667,9 @@
} }
}, },
"node_modules/@floating-ui/utils": { "node_modules/@floating-ui/utils": {
"version": "0.2.1", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz",
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
}, },
"node_modules/@fontsource/noto-sans": { "node_modules/@fontsource/noto-sans": {
"version": "5.0.22", "version": "5.0.22",
@ -4974,23 +4974,21 @@
"dev": true "dev": true
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "17.0.80", "version": "18.3.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
"integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "^0.16",
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "17.0.25", "version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
"integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/react": "^17" "@types/react": "*"
} }
}, },
"node_modules/@types/react-lazy-load-image-component": { "node_modules/@types/react-lazy-load-image-component": {
@ -5018,11 +5016,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "7.5.5", "version": "7.5.5",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz",
@ -17198,12 +17191,11 @@
} }
}, },
"node_modules/react": { "node_modules/react": {
"version": "17.0.2", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0"
"object-assign": "^4.1.1"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -17219,16 +17211,15 @@
} }
}, },
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "17.0.2", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "scheduler": "^0.23.2"
"scheduler": "^0.20.2"
}, },
"peerDependencies": { "peerDependencies": {
"react": "17.0.2" "react": "^18.3.1"
} }
}, },
"node_modules/react-is": { "node_modules/react-is": {
@ -18085,12 +18076,11 @@
} }
}, },
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.20.2", "version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0"
"object-assign": "^4.1.1"
} }
}, },
"node_modules/schema-utils": { "node_modules/schema-utils": {
@ -26310,34 +26300,34 @@
"dev": true "dev": true
}, },
"@floating-ui/core": { "@floating-ui/core": {
"version": "1.6.0", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz",
"integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==",
"requires": { "requires": {
"@floating-ui/utils": "^0.2.1" "@floating-ui/utils": "^0.2.0"
} }
}, },
"@floating-ui/dom": { "@floating-ui/dom": {
"version": "1.6.3", "version": "1.6.5",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz",
"integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==",
"requires": { "requires": {
"@floating-ui/core": "^1.0.0", "@floating-ui/core": "^1.0.0",
"@floating-ui/utils": "^0.2.0" "@floating-ui/utils": "^0.2.0"
} }
}, },
"@floating-ui/react-dom": { "@floating-ui/react-dom": {
"version": "2.0.8", "version": "2.0.9",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz",
"integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==",
"requires": { "requires": {
"@floating-ui/dom": "^1.6.1" "@floating-ui/dom": "^1.0.0"
} }
}, },
"@floating-ui/utils": { "@floating-ui/utils": {
"version": "0.2.1", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz",
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
}, },
"@fontsource/noto-sans": { "@fontsource/noto-sans": {
"version": "5.0.22", "version": "5.0.22",
@ -27211,22 +27201,21 @@
"dev": true "dev": true
}, },
"@types/react": { "@types/react": {
"version": "17.0.80", "version": "18.3.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
"integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "^0.16",
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"@types/react-dom": { "@types/react-dom": {
"version": "17.0.25", "version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
"integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/react": "^17" "@types/react": "*"
} }
}, },
"@types/react-lazy-load-image-component": { "@types/react-lazy-load-image-component": {
@ -27252,11 +27241,6 @@
"integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==",
"dev": true "dev": true
}, },
"@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"@types/semver": { "@types/semver": {
"version": "7.5.5", "version": "7.5.5",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz",
@ -35943,12 +35927,11 @@
} }
}, },
"react": { "react": {
"version": "17.0.2", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0"
"object-assign": "^4.1.1"
} }
}, },
"react-blurhash": { "react-blurhash": {
@ -35958,13 +35941,12 @@
"requires": {} "requires": {}
}, },
"react-dom": { "react-dom": {
"version": "17.0.2", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "scheduler": "^0.23.2"
"scheduler": "^0.20.2"
} }
}, },
"react-is": { "react-is": {
@ -36563,12 +36545,11 @@
} }
}, },
"scheduler": { "scheduler": {
"version": "0.20.2", "version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"requires": { "requires": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0"
"object-assign": "^4.1.1"
} }
}, },
"schema-utils": { "schema-utils": {

View file

@ -15,8 +15,8 @@
"@types/loadable__component": "5.13.9", "@types/loadable__component": "5.13.9",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "4.17.12",
"@types/markdown-it": "14.1.1", "@types/markdown-it": "14.1.1",
"@types/react": "17.0.80", "@types/react": "18.3.3",
"@types/react-dom": "17.0.25", "@types/react-dom": "18.3.0",
"@types/sortablejs": "1.15.8", "@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0", "@typescript-eslint/parser": "5.62.0",
@ -112,9 +112,9 @@
"material-design-icons-iconfont": "6.7.0", "material-design-icons-iconfont": "6.7.0",
"native-promise-only": "0.8.1", "native-promise-only": "0.8.1",
"pdfjs-dist": "3.11.174", "pdfjs-dist": "3.11.174",
"react": "17.0.2", "react": "18.3.1",
"react-blurhash": "0.3.0", "react-blurhash": "0.3.0",
"react-dom": "17.0.2", "react-dom": "18.3.1",
"react-lazy-load-image-component": "1.6.0", "react-lazy-load-image-component": "1.6.0",
"react-router-dom": "6.23.1", "react-router-dom": "6.23.1",
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",

View file

@ -147,7 +147,7 @@ const Activity = () => {
} }
]; ];
const onViewChange = useCallback((_e, newView: ActivityView | null) => { const onViewChange = useCallback((_e: React.MouseEvent<HTMLElement, MouseEvent>, newView: ActivityView | null) => {
if (newView !== null) { if (newView !== null) {
setActivityView(newView); setActivityView(newView);
} }

View file

@ -1,5 +1,7 @@
import type { ProcessPriorityClass, ServerConfiguration, TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client'; import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration';
import React, { type FunctionComponent, useCallback, useEffect, useRef } from 'react'; import { TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client/models/trickplay-scan-behavior';
import { ProcessPriorityClass } from '@jellyfin/sdk/lib/generated-client/models/process-priority-class';
import React, { type FC, useCallback, useEffect, useRef } from 'react';
import globalize from '../../../../scripts/globalize'; import globalize from '../../../../scripts/globalize';
import Page from '../../../../components/Page'; import Page from '../../../../components/Page';
@ -17,10 +19,10 @@ function onSaveComplete() {
toast(globalize.translate('SettingsSaved')); toast(globalize.translate('SettingsSaved'));
} }
const PlaybackTrickplay: FunctionComponent = () => { const PlaybackTrickplay: FC = () => {
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
const loadConfig = useCallback((config) => { const loadConfig = useCallback((config: ServerConfiguration) => {
const page = element.current; const page = element.current;
const options = config.TrickplayOptions; const options = config.TrickplayOptions;
@ -29,17 +31,17 @@ const PlaybackTrickplay: FunctionComponent = () => {
return; return;
} }
(page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options.EnableHwAcceleration; (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options?.EnableHwAcceleration || false;
(page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked = options.EnableHwEncoding; (page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked = options?.EnableHwEncoding || false;
(page.querySelector('#selectScanBehavior') as HTMLSelectElement).value = options.ScanBehavior; (page.querySelector('#selectScanBehavior') as HTMLSelectElement).value = (options?.ScanBehavior || TrickplayScanBehavior.NonBlocking);
(page.querySelector('#selectProcessPriority') as HTMLSelectElement).value = options.ProcessPriority; (page.querySelector('#selectProcessPriority') as HTMLSelectElement).value = (options?.ProcessPriority || ProcessPriorityClass.Normal);
(page.querySelector('#txtInterval') as HTMLInputElement).value = options.Interval; (page.querySelector('#txtInterval') as HTMLInputElement).value = String(options?.Interval);
(page.querySelector('#txtWidthResolutions') as HTMLInputElement).value = options.WidthResolutions.join(','); (page.querySelector('#txtWidthResolutions') as HTMLInputElement).value = options?.WidthResolutions?.join(',') || '';
(page.querySelector('#txtTileWidth') as HTMLInputElement).value = options.TileWidth; (page.querySelector('#txtTileWidth') as HTMLInputElement).value = String(options?.TileWidth);
(page.querySelector('#txtTileHeight') as HTMLInputElement).value = options.TileHeight; (page.querySelector('#txtTileHeight') as HTMLInputElement).value = String(options?.TileHeight);
(page.querySelector('#txtQscale') as HTMLInputElement).value = options.Qscale; (page.querySelector('#txtQscale') as HTMLInputElement).value = String(options?.Qscale);
(page.querySelector('#txtJpegQuality') as HTMLInputElement).value = options.JpegQuality; (page.querySelector('#txtJpegQuality') as HTMLInputElement).value = String(options?.JpegQuality);
(page.querySelector('#txtProcessThreads') as HTMLInputElement).value = options.ProcessThreads; (page.querySelector('#txtProcessThreads') as HTMLInputElement).value = String(options?.ProcessThreads);
loading.hide(); loading.hide();
}, []); }, []);

View file

@ -1,5 +1,5 @@
import type { UserDto } from '@jellyfin/sdk/lib/generated-client'; import type { BaseItemDto, DeviceInfo, UserDto } from '@jellyfin/sdk/lib/generated-client';
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react'; import React, { useCallback, useEffect, useState, useRef } from 'react';
import loading from '../../../../components/loading/loading'; import loading from '../../../../components/loading/loading';
import libraryMenu from '../../../../scripts/libraryMenu'; import libraryMenu from '../../../../scripts/libraryMenu';
@ -14,13 +14,13 @@ import CheckBoxElement from '../../../../elements/CheckBoxElement';
import Page from '../../../../components/Page'; import Page from '../../../../components/Page';
type ItemsArr = { type ItemsArr = {
Name?: string; Name?: string | null;
Id?: string; Id?: string | null;
AppName?: string; AppName?: string | null;
checkedAttribute?: string checkedAttribute?: string
}; };
const UserLibraryAccess: FunctionComponent = () => { const UserLibraryAccess = () => {
const [ userName, setUserName ] = useState(''); const [ userName, setUserName ] = useState('');
const [channelsItems, setChannelsItems] = useState<ItemsArr[]>([]); const [channelsItems, setChannelsItems] = useState<ItemsArr[]>([]);
const [mediaFoldersItems, setMediaFoldersItems] = useState<ItemsArr[]>([]); const [mediaFoldersItems, setMediaFoldersItems] = useState<ItemsArr[]>([]);
@ -33,7 +33,7 @@ const UserLibraryAccess: FunctionComponent = () => {
select.dispatchEvent(evt); select.dispatchEvent(evt);
}; };
const loadMediaFolders = useCallback((user, mediaFolders) => { const loadMediaFolders = useCallback((user: UserDto, mediaFolders: BaseItemDto[]) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -44,7 +44,7 @@ const UserLibraryAccess: FunctionComponent = () => {
const itemsArr: ItemsArr[] = []; const itemsArr: ItemsArr[] = [];
for (const folder of mediaFolders) { for (const folder of mediaFolders) {
const isChecked = user.Policy.EnableAllFolders || user.Policy.EnabledFolders.indexOf(folder.Id) != -1; const isChecked = user.Policy?.EnableAllFolders || user.Policy?.EnabledFolders?.indexOf(folder.Id || '') != -1;
const checkedAttribute = isChecked ? ' checked="checked"' : ''; const checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({ itemsArr.push({
Id: folder.Id, Id: folder.Id,
@ -56,11 +56,11 @@ const UserLibraryAccess: FunctionComponent = () => {
setMediaFoldersItems(itemsArr); setMediaFoldersItems(itemsArr);
const chkEnableAllFolders = page.querySelector('.chkEnableAllFolders') as HTMLInputElement; const chkEnableAllFolders = page.querySelector('.chkEnableAllFolders') as HTMLInputElement;
chkEnableAllFolders.checked = user.Policy.EnableAllFolders; chkEnableAllFolders.checked = Boolean(user.Policy?.EnableAllFolders);
triggerChange(chkEnableAllFolders); triggerChange(chkEnableAllFolders);
}, []); }, []);
const loadChannels = useCallback((user, channels) => { const loadChannels = useCallback((user: UserDto, channels: BaseItemDto[]) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -71,7 +71,7 @@ const UserLibraryAccess: FunctionComponent = () => {
const itemsArr: ItemsArr[] = []; const itemsArr: ItemsArr[] = [];
for (const folder of channels) { for (const folder of channels) {
const isChecked = user.Policy.EnableAllChannels || user.Policy.EnabledChannels.indexOf(folder.Id) != -1; const isChecked = user.Policy?.EnableAllChannels || user.Policy?.EnabledChannels?.indexOf(folder.Id || '') != -1;
const checkedAttribute = isChecked ? ' checked="checked"' : ''; const checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({ itemsArr.push({
Id: folder.Id, Id: folder.Id,
@ -89,11 +89,11 @@ const UserLibraryAccess: FunctionComponent = () => {
} }
const chkEnableAllChannels = page.querySelector('.chkEnableAllChannels') as HTMLInputElement; const chkEnableAllChannels = page.querySelector('.chkEnableAllChannels') as HTMLInputElement;
chkEnableAllChannels.checked = user.Policy.EnableAllChannels; chkEnableAllChannels.checked = Boolean(user.Policy?.EnableAllChannels);
triggerChange(chkEnableAllChannels); triggerChange(chkEnableAllChannels);
}, []); }, []);
const loadDevices = useCallback((user, devices) => { const loadDevices = useCallback((user: UserDto, devices: DeviceInfo[]) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -104,7 +104,7 @@ const UserLibraryAccess: FunctionComponent = () => {
const itemsArr: ItemsArr[] = []; const itemsArr: ItemsArr[] = [];
for (const device of devices) { for (const device of devices) {
const isChecked = user.Policy.EnableAllDevices || user.Policy.EnabledDevices.indexOf(device.Id) != -1; const isChecked = user.Policy?.EnableAllDevices || user.Policy?.EnabledDevices?.indexOf(device.Id || '') != -1;
const checkedAttribute = isChecked ? ' checked="checked"' : ''; const checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({ itemsArr.push({
Id: device.Id, Id: device.Id,
@ -117,18 +117,18 @@ const UserLibraryAccess: FunctionComponent = () => {
setDevicesItems(itemsArr); setDevicesItems(itemsArr);
const chkEnableAllDevices = page.querySelector('.chkEnableAllDevices') as HTMLInputElement; const chkEnableAllDevices = page.querySelector('.chkEnableAllDevices') as HTMLInputElement;
chkEnableAllDevices.checked = user.Policy.EnableAllDevices; chkEnableAllDevices.checked = Boolean(user.Policy?.EnableAllDevices);
triggerChange(chkEnableAllDevices); triggerChange(chkEnableAllDevices);
if (user.Policy.IsAdministrator) { if (user.Policy?.IsAdministrator) {
(page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.add('hide'); (page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.add('hide');
} else { } else {
(page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.remove('hide'); (page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.remove('hide');
} }
}, []); }, []);
const loadUser = useCallback((user, mediaFolders, channels, devices) => { const loadUser = useCallback((user: UserDto, mediaFolders: BaseItemDto[], channels: BaseItemDto[], devices: DeviceInfo[]) => {
setUserName(user.Name); setUserName(user.Name || '');
libraryMenu.setTitle(user.Name); libraryMenu.setTitle(user.Name);
loadChannels(user, channels); loadChannels(user, channels);
loadMediaFolders(user, mediaFolders); loadMediaFolders(user, mediaFolders);

View file

@ -1,4 +1,5 @@
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react'; import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import React, { useCallback, useEffect, useState, useRef } from 'react';
import Dashboard from '../../../../utils/dashboard'; import Dashboard from '../../../../utils/dashboard';
import globalize from '../../../../scripts/globalize'; import globalize from '../../../../scripts/globalize';
@ -17,16 +18,16 @@ type userInput = {
}; };
type ItemsArr = { type ItemsArr = {
Name?: string; Name?: string | null;
Id?: string; Id?: string;
}; };
const UserNew: FunctionComponent = () => { const UserNew = () => {
const [ channelsItems, setChannelsItems ] = useState<ItemsArr[]>([]); const [ channelsItems, setChannelsItems ] = useState<ItemsArr[]>([]);
const [ mediaFoldersItems, setMediaFoldersItems ] = useState<ItemsArr[]>([]); const [ mediaFoldersItems, setMediaFoldersItems ] = useState<ItemsArr[]>([]);
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
const getItemsResult = (items: ItemsArr[]) => { const getItemsResult = (items: BaseItemDto[]) => {
return items.map(item => return items.map(item =>
({ ({
Id: item.Id, Id: item.Id,
@ -35,7 +36,7 @@ const UserNew: FunctionComponent = () => {
); );
}; };
const loadMediaFolders = useCallback((result) => { const loadMediaFolders = useCallback((result: BaseItemDto[]) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -53,7 +54,7 @@ const UserNew: FunctionComponent = () => {
(page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked = false; (page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked = false;
}, []); }, []);
const loadChannels = useCallback((result) => { const loadChannels = useCallback((result: BaseItemDto[]) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {

View file

@ -1,5 +1,5 @@
import type { UserDto } from '@jellyfin/sdk/lib/generated-client'; import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
import React, { FunctionComponent, useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import Dashboard from '../../../../utils/dashboard'; import Dashboard from '../../../../utils/dashboard';
import globalize from '../../../../scripts/globalize'; import globalize from '../../../../scripts/globalize';
@ -21,7 +21,7 @@ type MenuEntry = {
icon?: string; icon?: string;
}; };
const UserProfiles: FunctionComponent = () => { const UserProfiles = () => {
const [ users, setUsers ] = useState<UserDto[]>([]); const [ users, setUsers ] = useState<UserDto[]>([]);
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);

View file

@ -1,6 +1,6 @@
import type { AccessSchedule, ParentalRating, UserDto } from '@jellyfin/sdk/lib/generated-client'; import { UnratedItem, type AccessSchedule, type ParentalRating, type UserDto } from '@jellyfin/sdk/lib/generated-client';
import { DynamicDayOfWeek } from '@jellyfin/sdk/lib/generated-client/models/dynamic-day-of-week'; import { DynamicDayOfWeek } from '@jellyfin/sdk/lib/generated-client/models/dynamic-day-of-week';
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react'; import React, { useCallback, useEffect, useState, useRef } from 'react';
import escapeHTML from 'escape-html'; import escapeHTML from 'escape-html';
import globalize from '../../../../scripts/globalize'; import globalize from '../../../../scripts/globalize';
@ -19,9 +19,12 @@ import Page from '../../../../components/Page';
import prompt from '../../../../components/prompt/prompt'; import prompt from '../../../../components/prompt/prompt';
import ServerConnections from 'components/ServerConnections'; import ServerConnections from 'components/ServerConnections';
type UnratedItem = { type ItemArr = {
name: string; name: string;
value: string; value: UnratedItem;
};
type UnratedItemArr = ItemArr & {
checkedAttribute: string checkedAttribute: string
}; };
@ -56,17 +59,17 @@ function handleSaveUser(
}; };
} }
const UserParentalControl: FunctionComponent = () => { const UserParentalControl = () => {
const [ userName, setUserName ] = useState(''); const [ userName, setUserName ] = useState('');
const [ parentalRatings, setParentalRatings ] = useState<ParentalRating[]>([]); const [ parentalRatings, setParentalRatings ] = useState<ParentalRating[]>([]);
const [ unratedItems, setUnratedItems ] = useState<UnratedItem[]>([]); const [ unratedItems, setUnratedItems ] = useState<UnratedItemArr[]>([]);
const [ accessSchedules, setAccessSchedules ] = useState<AccessSchedule[]>([]); const [ accessSchedules, setAccessSchedules ] = useState<AccessSchedule[]>([]);
const [ allowedTags, setAllowedTags ] = useState<string[]>([]); const [ allowedTags, setAllowedTags ] = useState<string[]>([]);
const [ blockedTags, setBlockedTags ] = useState<string[]>([]); const [ blockedTags, setBlockedTags ] = useState<string[]>([]);
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
const populateRatings = useCallback((allParentalRatings) => { const populateRatings = useCallback((allParentalRatings: ParentalRating[]) => {
let rating; let rating;
const ratings: ParentalRating[] = []; const ratings: ParentalRating[] = [];
@ -91,7 +94,7 @@ const UserParentalControl: FunctionComponent = () => {
setParentalRatings(ratings); setParentalRatings(ratings);
}, []); }, []);
const loadUnratedItems = useCallback((user) => { const loadUnratedItems = useCallback((user: UserDto) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -99,33 +102,33 @@ const UserParentalControl: FunctionComponent = () => {
return; return;
} }
const items = [{ const items: ItemArr[] = [{
name: globalize.translate('Books'), name: globalize.translate('Books'),
value: 'Book' value: UnratedItem.Book
}, { }, {
name: globalize.translate('Channels'), name: globalize.translate('Channels'),
value: 'ChannelContent' value: UnratedItem.ChannelContent
}, { }, {
name: globalize.translate('LiveTV'), name: globalize.translate('LiveTV'),
value: 'LiveTvChannel' value: UnratedItem.LiveTvChannel
}, { }, {
name: globalize.translate('Movies'), name: globalize.translate('Movies'),
value: 'Movie' value: UnratedItem.Movie
}, { }, {
name: globalize.translate('Music'), name: globalize.translate('Music'),
value: 'Music' value: UnratedItem.Music
}, { }, {
name: globalize.translate('Trailers'), name: globalize.translate('Trailers'),
value: 'Trailer' value: UnratedItem.Trailer
}, { }, {
name: globalize.translate('Shows'), name: globalize.translate('Shows'),
value: 'Series' value: UnratedItem.Series
}]; }];
const itemsArr: UnratedItem[] = []; const itemsArr: UnratedItemArr[] = [];
for (const item of items) { for (const item of items) {
const isChecked = user.Policy.BlockUnratedItems.indexOf(item.value) != -1; const isChecked = user.Policy?.BlockUnratedItems?.indexOf(item.value) != -1;
const checkedAttribute = isChecked ? ' checked="checked"' : ''; const checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({ itemsArr.push({
value: item.value, value: item.value,
@ -182,7 +185,7 @@ const UserParentalControl: FunctionComponent = () => {
} }
}, []); }, []);
const renderAccessSchedule = useCallback((schedules) => { const renderAccessSchedule = useCallback((schedules: AccessSchedule[]) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -198,7 +201,7 @@ const UserParentalControl: FunctionComponent = () => {
btnDelete.addEventListener('click', function () { btnDelete.addEventListener('click', function () {
const index = parseInt(btnDelete.getAttribute('data-index') ?? '0', 10); const index = parseInt(btnDelete.getAttribute('data-index') ?? '0', 10);
schedules.splice(index, 1); schedules.splice(index, 1);
const newindex = schedules.filter((i: number) => i != index); const newindex = schedules.filter((_, i) => i != index);
renderAccessSchedule(newindex); renderAccessSchedule(newindex);
}); });
} }
@ -229,7 +232,7 @@ const UserParentalControl: FunctionComponent = () => {
}); });
} }
(page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value = ratingValue; (page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value = String(ratingValue);
if (user.Policy?.IsAdministrator) { if (user.Policy?.IsAdministrator) {
(page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide'); (page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide');

View file

@ -1,4 +1,4 @@
import React, { FunctionComponent, useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import SectionTabs from '../../../../components/dashboard/users/SectionTabs'; import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
import UserPasswordForm from '../../../../components/dashboard/users/UserPasswordForm'; import UserPasswordForm from '../../../../components/dashboard/users/UserPasswordForm';
@ -7,7 +7,7 @@ import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import Page from '../../../../components/Page'; import Page from '../../../../components/Page';
import loading from '../../../../components/loading/loading'; import loading from '../../../../components/loading/loading';
const UserPassword: FunctionComponent = () => { const UserPassword = () => {
const userId = getParameterByName('userId'); const userId = getParameterByName('userId');
const [ userName, setUserName ] = useState(''); const [ userName, setUserName ] = useState('');

View file

@ -1,5 +1,5 @@
import type { SyncPlayUserAccessType, UserDto } from '@jellyfin/sdk/lib/generated-client'; import type { BaseItemDto, NameIdPair, SyncPlayUserAccessType, UserDto } from '@jellyfin/sdk/lib/generated-client';
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react'; import React, { useCallback, useEffect, useState, useRef } from 'react';
import escapeHTML from 'escape-html'; import escapeHTML from 'escape-html';
import Dashboard from '../../../../utils/dashboard'; import Dashboard from '../../../../utils/dashboard';
@ -17,15 +17,10 @@ import { getParameterByName } from '../../../../utils/url';
import SelectElement from '../../../../elements/SelectElement'; import SelectElement from '../../../../elements/SelectElement';
import Page from '../../../../components/Page'; import Page from '../../../../components/Page';
type ResetProvider = AuthProvider & { type ResetProvider = BaseItemDto & {
checkedAttribute: string checkedAttribute: string
}; };
type AuthProvider = {
Name?: string;
Id?: string;
};
const getCheckedElementDataIds = (elements: NodeListOf<Element>) => ( const getCheckedElementDataIds = (elements: NodeListOf<Element>) => (
Array.prototype.filter.call(elements, e => e.checked) Array.prototype.filter.call(elements, e => e.checked)
.map(e => e.getAttribute('data-id')) .map(e => e.getAttribute('data-id'))
@ -40,11 +35,11 @@ function onSaveComplete() {
toast(globalize.translate('SettingsSaved')); toast(globalize.translate('SettingsSaved'));
} }
const UserEdit: FunctionComponent = () => { const UserEdit = () => {
const [ userName, setUserName ] = useState(''); const [ userName, setUserName ] = useState('');
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState<ResetProvider[]>([]); const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState<ResetProvider[]>([]);
const [ authProviders, setAuthProviders ] = useState<AuthProvider[]>([]); const [ authProviders, setAuthProviders ] = useState<NameIdPair[]>([]);
const [ passwordResetProviders, setPasswordResetProviders ] = useState<ResetProvider[]>([]); const [ passwordResetProviders, setPasswordResetProviders ] = useState<NameIdPair[]>([]);
const [ authenticationProviderId, setAuthenticationProviderId ] = useState(''); const [ authenticationProviderId, setAuthenticationProviderId ] = useState('');
const [ passwordResetProviderId, setPasswordResetProviderId ] = useState(''); const [ passwordResetProviderId, setPasswordResetProviderId ] = useState('');
@ -61,48 +56,27 @@ const UserEdit: FunctionComponent = () => {
return window.ApiClient.getUser(userId); return window.ApiClient.getUser(userId);
}; };
const loadAuthProviders = useCallback((user, providers) => { const loadAuthProviders = useCallback((page: HTMLDivElement, user: UserDto, providers: NameIdPair[]) => {
const page = element.current;
if (!page) {
console.error('Unexpected null reference');
return;
}
const fldSelectLoginProvider = page.querySelector('.fldSelectLoginProvider') as HTMLDivElement; const fldSelectLoginProvider = page.querySelector('.fldSelectLoginProvider') as HTMLDivElement;
fldSelectLoginProvider.classList.toggle('hide', providers.length <= 1); fldSelectLoginProvider.classList.toggle('hide', providers.length <= 1);
setAuthProviders(providers); setAuthProviders(providers);
const currentProviderId = user.Policy.AuthenticationProviderId; const currentProviderId = user.Policy?.AuthenticationProviderId || '';
setAuthenticationProviderId(currentProviderId); setAuthenticationProviderId(currentProviderId);
}, []); }, []);
const loadPasswordResetProviders = useCallback((user, providers) => { const loadPasswordResetProviders = useCallback((page: HTMLDivElement, user: UserDto, providers: NameIdPair[]) => {
const page = element.current;
if (!page) {
console.error('Unexpected null reference');
return;
}
const fldSelectPasswordResetProvider = page.querySelector('.fldSelectPasswordResetProvider') as HTMLDivElement; const fldSelectPasswordResetProvider = page.querySelector('.fldSelectPasswordResetProvider') as HTMLDivElement;
fldSelectPasswordResetProvider.classList.toggle('hide', providers.length <= 1); fldSelectPasswordResetProvider.classList.toggle('hide', providers.length <= 1);
setPasswordResetProviders(providers); setPasswordResetProviders(providers);
const currentProviderId = user.Policy.PasswordResetProviderId; const currentProviderId = user.Policy?.PasswordResetProviderId || '';
setPasswordResetProviderId(currentProviderId); setPasswordResetProviderId(currentProviderId);
}, []); }, []);
const loadDeleteFolders = useCallback((user, mediaFolders) => { const loadDeleteFolders = useCallback((page: HTMLDivElement, user: UserDto, mediaFolders: BaseItemDto[]) => {
const page = element.current;
if (!page) {
console.error('Unexpected null reference');
return;
}
window.ApiClient.getJSON(window.ApiClient.getUrl('Channels', { window.ApiClient.getJSON(window.ApiClient.getUrl('Channels', {
SupportsMediaDeletion: true SupportsMediaDeletion: true
})).then(function (channelsResult) { })).then(function (channelsResult) {
@ -110,22 +84,20 @@ const UserEdit: FunctionComponent = () => {
let checkedAttribute; let checkedAttribute;
const itemsArr: ResetProvider[] = []; const itemsArr: ResetProvider[] = [];
for (const folder of mediaFolders) { for (const mediaFolder of mediaFolders) {
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1; isChecked = user.Policy?.EnableContentDeletion || user.Policy?.EnableContentDeletionFromFolders?.indexOf(mediaFolder.Id || '') != -1;
checkedAttribute = isChecked ? ' checked="checked"' : ''; checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({ itemsArr.push({
Id: folder.Id, ...mediaFolder,
Name: folder.Name,
checkedAttribute: checkedAttribute checkedAttribute: checkedAttribute
}); });
} }
for (const folder of channelsResult.Items) { for (const channel of channelsResult.Items) {
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1; isChecked = user.Policy?.EnableContentDeletion || user.Policy?.EnableContentDeletionFromFolders?.indexOf(channel.Id || '') != -1;
checkedAttribute = isChecked ? ' checked="checked"' : ''; checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({ itemsArr.push({
Id: folder.Id, ...channel,
Name: folder.Name,
checkedAttribute: checkedAttribute checkedAttribute: checkedAttribute
}); });
} }
@ -133,14 +105,14 @@ const UserEdit: FunctionComponent = () => {
setDeleteFoldersAccess(itemsArr); setDeleteFoldersAccess(itemsArr);
const chkEnableDeleteAllFolders = page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement; const chkEnableDeleteAllFolders = page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement;
chkEnableDeleteAllFolders.checked = user.Policy.EnableContentDeletion; chkEnableDeleteAllFolders.checked = user.Policy?.EnableContentDeletion || false;
triggerChange(chkEnableDeleteAllFolders); triggerChange(chkEnableDeleteAllFolders);
}).catch(err => { }).catch(err => {
console.error('[useredit] failed to fetch channels', err); console.error('[useredit] failed to fetch channels', err);
}); });
}, []); }, []);
const loadUser = useCallback((user) => { const loadUser = useCallback((user: UserDto) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -149,25 +121,25 @@ const UserEdit: FunctionComponent = () => {
} }
window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/Providers')).then(function (providers) { window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/Providers')).then(function (providers) {
loadAuthProviders(user, providers); loadAuthProviders(page, user, providers);
}).catch(err => { }).catch(err => {
console.error('[useredit] failed to fetch auth providers', err); console.error('[useredit] failed to fetch auth providers', err);
}); });
window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/PasswordResetProviders')).then(function (providers) { window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/PasswordResetProviders')).then(function (providers) {
loadPasswordResetProviders(user, providers); loadPasswordResetProviders(page, user, providers);
}).catch(err => { }).catch(err => {
console.error('[useredit] failed to fetch password reset providers', err); console.error('[useredit] failed to fetch password reset providers', err);
}); });
window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', { window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', {
IsHidden: false IsHidden: false
})).then(function (folders) { })).then(function (folders) {
loadDeleteFolders(user, folders.Items); loadDeleteFolders(page, user, folders.Items);
}).catch(err => { }).catch(err => {
console.error('[useredit] failed to fetch media folders', err); console.error('[useredit] failed to fetch media folders', err);
}); });
const disabledUserBanner = page.querySelector('.disabledUserBanner') as HTMLDivElement; const disabledUserBanner = page.querySelector('.disabledUserBanner') as HTMLDivElement;
disabledUserBanner.classList.toggle('hide', !user.Policy.IsDisabled); disabledUserBanner.classList.toggle('hide', !user.Policy?.IsDisabled);
const txtUserName = page.querySelector('#txtUserName') as HTMLInputElement; const txtUserName = page.querySelector('#txtUserName') as HTMLInputElement;
txtUserName.disabled = false; txtUserName.disabled = false;
@ -176,30 +148,30 @@ const UserEdit: FunctionComponent = () => {
const lnkEditUserPreferences = page.querySelector('.lnkEditUserPreferences') as HTMLDivElement; const lnkEditUserPreferences = page.querySelector('.lnkEditUserPreferences') as HTMLDivElement;
lnkEditUserPreferences.setAttribute('href', 'mypreferencesmenu.html?userId=' + user.Id); lnkEditUserPreferences.setAttribute('href', 'mypreferencesmenu.html?userId=' + user.Id);
LibraryMenu.setTitle(user.Name); LibraryMenu.setTitle(user.Name);
setUserName(user.Name); setUserName(user.Name || '');
(page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name; (page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name || '';
(page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = user.Policy.IsAdministrator; (page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = !!user.Policy?.IsAdministrator;
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = user.Policy.IsDisabled; (page.querySelector('.chkDisabled') as HTMLInputElement).checked = !!user.Policy?.IsDisabled;
(page.querySelector('.chkIsHidden') as HTMLInputElement).checked = user.Policy.IsHidden; (page.querySelector('.chkIsHidden') as HTMLInputElement).checked = !!user.Policy?.IsHidden;
(page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked = user.Policy.EnableCollectionManagement; (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked = !!user.Policy?.EnableCollectionManagement;
(page.querySelector('.chkEnableSubtitleManagement') as HTMLInputElement).checked = user.Policy.EnableSubtitleManagement; (page.querySelector('.chkEnableSubtitleManagement') as HTMLInputElement).checked = !!user.Policy?.EnableSubtitleManagement;
(page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = user.Policy.EnableSharedDeviceControl; (page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = !!user.Policy?.EnableSharedDeviceControl;
(page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = user.Policy.EnableRemoteControlOfOtherUsers; (page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = !!user.Policy?.EnableRemoteControlOfOtherUsers;
(page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = user.Policy.EnableContentDownloading; (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = !!user.Policy?.EnableContentDownloading;
(page.querySelector('.chkManageLiveTv') as HTMLInputElement).checked = user.Policy.EnableLiveTvManagement; (page.querySelector('.chkManageLiveTv') as HTMLInputElement).checked = !!user.Policy?.EnableLiveTvManagement;
(page.querySelector('.chkEnableLiveTvAccess') as HTMLInputElement).checked = user.Policy.EnableLiveTvAccess; (page.querySelector('.chkEnableLiveTvAccess') as HTMLInputElement).checked = !!user.Policy?.EnableLiveTvAccess;
(page.querySelector('.chkEnableMediaPlayback') as HTMLInputElement).checked = user.Policy.EnableMediaPlayback; (page.querySelector('.chkEnableMediaPlayback') as HTMLInputElement).checked = !!user.Policy?.EnableMediaPlayback;
(page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked = user.Policy.EnableAudioPlaybackTranscoding; (page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked = !!user.Policy?.EnableAudioPlaybackTranscoding;
(page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked = user.Policy.EnableVideoPlaybackTranscoding; (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked = !!user.Policy?.EnableVideoPlaybackTranscoding;
(page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked = user.Policy.EnablePlaybackRemuxing; (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked = !!user.Policy?.EnablePlaybackRemuxing;
(page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked = user.Policy.ForceRemoteSourceTranscoding; (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked = !!user.Policy?.ForceRemoteSourceTranscoding;
(page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked = user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess; (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked = user.Policy?.EnableRemoteAccess == null || user.Policy?.EnableRemoteAccess;
(page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value = user.Policy.RemoteClientBitrateLimit > 0 ? (page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value = user.Policy?.RemoteClientBitrateLimit && user.Policy?.RemoteClientBitrateLimit > 0 ?
(user.Policy.RemoteClientBitrateLimit / 1e6).toLocaleString(undefined, { maximumFractionDigits: 6 }) : ''; (user.Policy?.RemoteClientBitrateLimit / 1e6).toLocaleString(undefined, { maximumFractionDigits: 6 }) : '';
(page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value = user.Policy.LoginAttemptsBeforeLockout || '0'; (page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value = String(user.Policy?.MaxActiveSessions) || '0';
(page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value = user.Policy.MaxActiveSessions || '0'; (page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value = String(user.Policy?.SyncPlayAccess) || '0';
if (window.ApiClient.isMinServerVersion('10.6.0')) { if (window.ApiClient.isMinServerVersion('10.6.0')) {
(page.querySelector('#selectSyncPlayAccess') as HTMLSelectElement).value = user.Policy.SyncPlayAccess; (page.querySelector('#selectSyncPlayAccess') as HTMLSelectElement).value = String(user.Policy?.SyncPlayAccess);
} }
loading.hide(); loading.hide();
}, [loadAuthProviders, loadPasswordResetProviders, loadDeleteFolders ]); }, [loadAuthProviders, loadPasswordResetProviders, loadDeleteFolders ]);

View file

@ -33,7 +33,7 @@ const RemotePlayButton = () => {
const [ remotePlayMenuAnchorEl, setRemotePlayMenuAnchorEl ] = useState<null | HTMLElement>(null); const [ remotePlayMenuAnchorEl, setRemotePlayMenuAnchorEl ] = useState<null | HTMLElement>(null);
const isRemotePlayMenuOpen = Boolean(remotePlayMenuAnchorEl); const isRemotePlayMenuOpen = Boolean(remotePlayMenuAnchorEl);
const onRemotePlayButtonClick = useCallback((event) => { const onRemotePlayButtonClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
setRemotePlayMenuAnchorEl(event.currentTarget); setRemotePlayMenuAnchorEl(event.currentTarget);
}, [ setRemotePlayMenuAnchorEl ]); }, [ setRemotePlayMenuAnchorEl ]);
@ -44,7 +44,7 @@ const RemotePlayButton = () => {
const [ remotePlayActiveMenuAnchorEl, setRemotePlayActiveMenuAnchorEl ] = useState<null | HTMLElement>(null); const [ remotePlayActiveMenuAnchorEl, setRemotePlayActiveMenuAnchorEl ] = useState<null | HTMLElement>(null);
const isRemotePlayActiveMenuOpen = Boolean(remotePlayActiveMenuAnchorEl); const isRemotePlayActiveMenuOpen = Boolean(remotePlayActiveMenuAnchorEl);
const onRemotePlayActiveButtonClick = useCallback((event) => { const onRemotePlayActiveButtonClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
setRemotePlayActiveMenuAnchorEl(event.currentTarget); setRemotePlayActiveMenuAnchorEl(event.currentTarget);
}, [ setRemotePlayActiveMenuAnchorEl ]); }, [ setRemotePlayActiveMenuAnchorEl ]);

View file

@ -17,7 +17,7 @@ const SyncPlayButton = () => {
const [ syncPlayMenuAnchorEl, setSyncPlayMenuAnchorEl ] = useState<null | HTMLElement>(null); const [ syncPlayMenuAnchorEl, setSyncPlayMenuAnchorEl ] = useState<null | HTMLElement>(null);
const isSyncPlayMenuOpen = Boolean(syncPlayMenuAnchorEl); const isSyncPlayMenuOpen = Boolean(syncPlayMenuAnchorEl);
const onSyncPlayButtonClick = useCallback((event) => { const onSyncPlayButtonClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
setSyncPlayMenuAnchorEl(event.currentTarget); setSyncPlayMenuAnchorEl(event.currentTarget);
}, [ setSyncPlayMenuAnchorEl ]); }, [ setSyncPlayMenuAnchorEl ]);

View file

@ -22,7 +22,7 @@ import { useApi } from 'hooks/useApi';
import { useSyncPlayGroups } from 'hooks/useSyncPlayGroups'; import { useSyncPlayGroups } from 'hooks/useSyncPlayGroups';
import globalize from 'scripts/globalize'; import globalize from 'scripts/globalize';
import { PluginType } from 'types/plugin'; import { PluginType } from 'types/plugin';
import Events from 'utils/events'; import Events, { Event } from 'utils/events';
export const ID = 'app-sync-play-menu'; export const ID = 'app-sync-play-menu';
@ -136,7 +136,7 @@ const SyncPlayMenu: FC<SyncPlayMenuProps> = ({
} }
}, [ __legacyApiClient__, onMenuClose, syncPlay ]); }, [ __legacyApiClient__, onMenuClose, syncPlay ]);
const updateSyncPlayGroup = useCallback((_e, enabled) => { const updateSyncPlayGroup = useCallback((_e: Event, enabled: boolean) => {
if (syncPlay && enabled) { if (syncPlay && enabled) {
setCurrentGroup(syncPlay.Manager.getGroupInfo() ?? undefined); setCurrentGroup(syncPlay.Manager.getGroupInfo() ?? undefined);
} else { } else {

View file

@ -1,4 +1,4 @@
import React, { FunctionComponent, useCallback, useEffect, useMemo, useRef } from 'react'; import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import globalize from '../../../scripts/globalize'; import globalize from '../../../scripts/globalize';
@ -25,7 +25,7 @@ type ControllerProps = {
destroy: () => void; destroy: () => void;
}; };
const Home: FunctionComponent = () => { const Home = () => {
const [ searchParams ] = useSearchParams(); const [ searchParams ] = useSearchParams();
const initialTabIndex = parseInt(searchParams.get('tab') ?? '0', 10); const initialTabIndex = parseInt(searchParams.get('tab') ?? '0', 10);

View file

@ -6,6 +6,11 @@ import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from '../types'; import { DisplaySettingsValues } from '../types';
import { useDisplaySettings } from './useDisplaySettings'; import { useDisplaySettings } from './useDisplaySettings';
type UpdateField = {
name: keyof DisplaySettingsValues;
value: string | boolean;
};
export function useDisplaySettingForm() { export function useDisplaySettingForm() {
const [urlParams] = useSearchParams(); const [urlParams] = useSearchParams();
const { const {
@ -21,7 +26,7 @@ export function useDisplaySettingForm() {
} }
}, [formValues, loading, displaySettings]); }, [formValues, loading, displaySettings]);
const updateField = useCallback(({ name, value }) => { const updateField = useCallback(({ name, value }: UpdateField) => {
if (formValues) { if (formValues) {
setFormValues({ setFormValues({
...formValues, ...formValues,

View file

@ -1,11 +1,11 @@
import React, { FC, useEffect } from 'react'; import React, { type FC, type PropsWithChildren, useEffect } from 'react';
import viewContainer from './viewContainer'; import viewContainer from './viewContainer';
/** /**
* A simple component that includes the correct structure for ViewManager pages * A simple component that includes the correct structure for ViewManager pages
* to exist alongside standard React pages. * to exist alongside standard React pages.
*/ */
const AppBody: FC = ({ children }) => { const AppBody: FC<PropsWithChildren<unknown>> = ({ children }) => {
useEffect(() => () => { useEffect(() => () => {
// Reset view container state on unload // Reset view container state on unload
viewContainer.reset(); viewContainer.reset();

View file

@ -1,4 +1,4 @@
import React, { FunctionComponent, HTMLAttributes, useEffect, useRef } from 'react'; import React, { type FC, type PropsWithChildren, type HTMLAttributes, useEffect, useRef } from 'react';
import viewManager from './viewManager/viewManager'; import viewManager from './viewManager/viewManager';
@ -9,14 +9,14 @@ type PageProps = {
isMenuButtonEnabled?: boolean, isMenuButtonEnabled?: boolean,
isNowPlayingBarEnabled?: boolean, isNowPlayingBarEnabled?: boolean,
isThemeMediaSupported?: boolean, isThemeMediaSupported?: boolean,
backDropType?: string backDropType?: string,
}; };
/** /**
* Page component that handles hiding active non-react views, triggering the required events for * Page component that handles hiding active non-react views, triggering the required events for
* navigation and appRouter state updates, and setting the correct classes and data attributes. * navigation and appRouter state updates, and setting the correct classes and data attributes.
*/ */
const Page: FunctionComponent<PageProps & HTMLAttributes<HTMLDivElement>> = ({ const Page: FC<PropsWithChildren<PageProps & HTMLAttributes<HTMLDivElement>>> = ({
children, children,
id, id,
className = '', className = '',

View file

@ -3,7 +3,7 @@ import Box from '@mui/material/Box';
import Drawer from '@mui/material/Drawer'; import Drawer from '@mui/material/Drawer';
import SwipeableDrawer from '@mui/material/SwipeableDrawer'; import SwipeableDrawer from '@mui/material/SwipeableDrawer';
import useMediaQuery from '@mui/material/useMediaQuery'; import useMediaQuery from '@mui/material/useMediaQuery';
import React, { FC } from 'react'; import React, { type FC, type PropsWithChildren } from 'react';
import browser from 'scripts/browser'; import browser from 'scripts/browser';
@ -15,7 +15,7 @@ export interface ResponsiveDrawerProps {
onOpen: () => void onOpen: () => void
} }
const ResponsiveDrawer: FC<ResponsiveDrawerProps> = ({ const ResponsiveDrawer: FC<PropsWithChildren<ResponsiveDrawerProps>> = ({
children, children,
open = false, open = false,
onClose, onClose,

View file

@ -1,4 +1,4 @@
import React, { type FC } from 'react'; import React, { type FC, type PropsWithChildren } from 'react';
import layoutManager from 'components/layoutManager'; import layoutManager from 'components/layoutManager';
import type { DataAttributes } from 'types/dataAttributes'; import type { DataAttributes } from 'types/dataAttributes';
@ -7,7 +7,7 @@ interface CardWrapperProps {
dataAttributes: DataAttributes; dataAttributes: DataAttributes;
} }
const CardWrapper: FC<CardWrapperProps> = ({ const CardWrapper: FC<PropsWithChildren<CardWrapperProps>> = ({
className, className,
dataAttributes, dataAttributes,
children children

View file

@ -1,8 +1,8 @@
import React, { FunctionComponent } from 'react'; import React, { type FC, type PropsWithChildren } from 'react';
import globalize from '../../../scripts/globalize'; import globalize from '../../../scripts/globalize';
import CheckBoxElement from '../../../elements/CheckBoxElement'; import CheckBoxElement from '../../../elements/CheckBoxElement';
type IProps = { interface AccessContainerProps {
containerClassName?: string; containerClassName?: string;
headerTitle?: string; headerTitle?: string;
checkBoxClassName?: string; checkBoxClassName?: string;
@ -11,10 +11,19 @@ type IProps = {
accessClassName?: string; accessClassName?: string;
listTitle?: string; listTitle?: string;
description?: string; description?: string;
children?: React.ReactNode }
};
const AccessContainer: FunctionComponent<IProps> = ({ containerClassName, headerTitle, checkBoxClassName, checkBoxTitle, listContainerClassName, accessClassName, listTitle, description, children }: IProps) => { const AccessContainer: FC<PropsWithChildren<AccessContainerProps>> = ({
containerClassName,
headerTitle,
checkBoxClassName,
checkBoxTitle,
listContainerClassName,
accessClassName,
listTitle,
description,
children
}) => {
return ( return (
<div className={containerClassName}> <div className={containerClassName}>
<h2>{globalize.translate(headerTitle)}</h2> <h2>{globalize.translate(headerTitle)}</h2>
@ -28,9 +37,12 @@ const AccessContainer: FunctionComponent<IProps> = ({ containerClassName, header
<h3 className='checkboxListLabel'> <h3 className='checkboxListLabel'>
{globalize.translate(listTitle)} {globalize.translate(listTitle)}
</h3> </h3>
<div className='checkboxList paperList' style={{ <div
padding: '.5em 1em' className='checkboxList paperList'
}}> style={{
padding: '.5em 1em'
}}
>
{children} {children}
</div> </div>
</div> </div>

View file

@ -1,4 +1,4 @@
import React, { type FC } from 'react'; import React, { type FC, type PropsWithChildren } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
interface ListContentWrapperProps { interface ListContentWrapperProps {
@ -7,7 +7,7 @@ interface ListContentWrapperProps {
enableOverview?: boolean; enableOverview?: boolean;
} }
const ListContentWrapper: FC<ListContentWrapperProps> = ({ const ListContentWrapper: FC<PropsWithChildren<ListContentWrapperProps>> = ({
itemOverview, itemOverview,
enableContentWrapper, enableContentWrapper,
enableOverview, enableOverview,

View file

@ -1,11 +1,11 @@
import React, { type FC } from 'react'; import React, { type FC, type PropsWithChildren } from 'react';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
interface ListGroupHeaderWrapperProps { interface ListGroupHeaderWrapperProps {
index?: number; index?: number;
} }
const ListGroupHeaderWrapper: FC<ListGroupHeaderWrapperProps> = ({ const ListGroupHeaderWrapper: FC<PropsWithChildren<ListGroupHeaderWrapperProps>> = ({
index, index,
children children
}) => { }) => {

View file

@ -1,4 +1,4 @@
import React, { type FC } from 'react'; import React, { type FC, type PropsWithChildren } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
@ -7,7 +7,7 @@ interface ListTextWrapperProps {
isLargeStyle?: boolean; isLargeStyle?: boolean;
} }
const ListTextWrapper: FC<ListTextWrapperProps> = ({ const ListTextWrapper: FC<PropsWithChildren<ListTextWrapperProps>> = ({
index, index,
isLargeStyle, isLargeStyle,
children children

View file

@ -1,5 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React, { type FC } from 'react'; import React, { type FC, type PropsWithChildren } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import layoutManager from '../../layoutManager'; import layoutManager from '../../layoutManager';
@ -13,7 +13,7 @@ interface ListWrapperProps {
className?: string; className?: string;
} }
const ListWrapper: FC<ListWrapperProps> = ({ const ListWrapper: FC<PropsWithChildren<ListWrapperProps>> = ({
index, index,
action, action,
title, title,

View file

@ -4,7 +4,7 @@ import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import React, { FC, ReactNode } from 'react'; import React, { type FC, type PropsWithChildren, ReactNode } from 'react';
import { appRouter } from 'components/router/appRouter'; import { appRouter } from 'components/router/appRouter';
import { useApi } from 'hooks/useApi'; import { useApi } from 'hooks/useApi';
@ -27,7 +27,7 @@ const onBackButtonClick = () => {
}); });
}; };
const AppToolbar: FC<AppToolbarProps> = ({ const AppToolbar: FC<PropsWithChildren<AppToolbarProps>> = ({
buttons, buttons,
children, children,
isDrawerAvailable, isDrawerAvailable,

View file

@ -14,7 +14,7 @@ const UserMenuButton = () => {
const [ userMenuAnchorEl, setUserMenuAnchorEl ] = useState<null | HTMLElement>(null); const [ userMenuAnchorEl, setUserMenuAnchorEl ] = useState<null | HTMLElement>(null);
const isUserMenuOpen = Boolean(userMenuAnchorEl); const isUserMenuOpen = Boolean(userMenuAnchorEl);
const onUserButtonClick = useCallback((event) => { const onUserButtonClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
setUserMenuAnchorEl(event.currentTarget); setUserMenuAnchorEl(event.currentTarget);
}, [ setUserMenuAnchorEl ]); }, [ setUserMenuAnchorEl ]);

View file

@ -1,8 +1,27 @@
import escapeHTML from 'escape-html'; import escapeHTML from 'escape-html';
import React, { FunctionComponent } from 'react'; import React, { type FC } from 'react';
import globalize from '../scripts/globalize'; import globalize from '../scripts/globalize';
const createCheckBoxElement = ({ labelClassName, className, id, dataFilter, dataItemType, dataId, checkedAttribute, renderContent }: { labelClassName?: string, type?: string, className?: string, id?: string, dataFilter?: string, dataItemType?: string, dataId?: string, checkedAttribute?: string, renderContent?: string }) => ({ const createCheckBoxElement = ({
labelClassName,
className,
id,
dataFilter,
dataItemType,
dataId,
checkedAttribute,
renderContent
}: {
labelClassName?: string;
type?: string;
className?: string;
id?: string;
dataFilter?: string;
dataItemType?: string;
dataId?: string;
checkedAttribute?: string;
renderContent?: string;
}) => ({
__html: `<label ${labelClassName}> __html: `<label ${labelClassName}>
<input <input
is="emby-checkbox" is="emby-checkbox"
@ -18,20 +37,31 @@ const createCheckBoxElement = ({ labelClassName, className, id, dataFilter, data
</label>` </label>`
}); });
type IProps = { interface CheckBoxElementProps {
labelClassName?: string; labelClassName?: string;
className?: string; className?: string;
elementId?: string; elementId?: string;
dataFilter?: string; dataFilter?: string;
itemType?: string; itemType?: string;
itemId?: string; itemId?: string | null;
itemAppName?: string; itemAppName?: string | null;
itemCheckedAttribute?: string; itemCheckedAttribute?: string;
itemName?: string itemName?: string | null;
title?: string title?: string;
}; }
const CheckBoxElement: FunctionComponent<IProps> = ({ labelClassName, className, elementId, dataFilter, itemType, itemId, itemAppName, itemCheckedAttribute, itemName, title }: IProps) => { const CheckBoxElement: FC<CheckBoxElementProps> = ({
labelClassName,
className,
elementId,
dataFilter,
itemType,
itemId,
itemAppName,
itemCheckedAttribute,
itemName,
title
}) => {
const appName = itemAppName ? `- ${itemAppName}` : ''; const appName = itemAppName ? `- ${itemAppName}` : '';
const renderContent = itemName ? const renderContent = itemName ?
`<span>${escapeHTML(itemName || '')} ${appName}</span>` : `<span>${escapeHTML(itemName || '')} ${appName}</span>` :
@ -41,13 +71,17 @@ const CheckBoxElement: FunctionComponent<IProps> = ({ labelClassName, className,
<div <div
className='sectioncheckbox' className='sectioncheckbox'
dangerouslySetInnerHTML={createCheckBoxElement({ dangerouslySetInnerHTML={createCheckBoxElement({
labelClassName: labelClassName ? `class='${labelClassName}'` : '', labelClassName: labelClassName ?
`class='${labelClassName}'` :
'',
className: className, className: className,
id: elementId ? `id='${elementId}'` : '', id: elementId ? `id='${elementId}'` : '',
dataFilter: dataFilter ? `data-filter='${dataFilter}'` : '', dataFilter: dataFilter ? `data-filter='${dataFilter}'` : '',
dataItemType: itemType ? `data-itemtype='${itemType}'` : '', dataItemType: itemType ? `data-itemtype='${itemType}'` : '',
dataId: itemId ? `data-id='${itemId}'` : '', dataId: itemId ? `data-id='${itemId}'` : '',
checkedAttribute: itemCheckedAttribute ? itemCheckedAttribute : '', checkedAttribute: itemCheckedAttribute ?
itemCheckedAttribute :
'',
renderContent: renderContent renderContent: renderContent
})} })}
/> />

View file

@ -1,11 +1,21 @@
import classNames from 'classnames'; import classNames from 'classnames';
import React, { type DetailedHTMLProps, type InputHTMLAttributes, useState, useCallback, forwardRef } from 'react'; import React, {
type DetailedHTMLProps,
type InputHTMLAttributes,
useState,
useCallback,
forwardRef
} from 'react';
import './emby-input.scss'; import './emby-input.scss';
interface InputProps extends DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> { interface InputProps
id: string, extends DetailedHTMLProps<
label?: string InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
> {
id: string;
label?: string;
} }
const Input = forwardRef<HTMLInputElement, InputProps>( const Input = forwardRef<HTMLInputElement, InputProps>(
@ -13,7 +23,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const onBlurInternal = useCallback( const onBlurInternal = useCallback(
(e) => { (e: React.FocusEvent<HTMLInputElement, Element>) => {
setIsFocused(false); setIsFocused(false);
onBlur?.(e); onBlur?.(e);
}, },
@ -21,7 +31,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
); );
const onFocusInternal = useCallback( const onFocusInternal = useCallback(
(e) => { (e: React.FocusEvent<HTMLInputElement, Element>) => {
setIsFocused(true); setIsFocused(true);
onFocus?.(e); onFocus?.(e);
}, },

View file

@ -1,3 +1,4 @@
import { ApiClient } from 'jellyfin-apiclient';
import React, { type FC, useCallback, useEffect, useState } from 'react'; import React, { type FC, useCallback, useEffect, useState } from 'react';
import Events, { Event } from 'utils/events'; import Events, { Event } from 'utils/events';
import serverNotifications from 'scripts/serverNotifications'; import serverNotifications from 'scripts/serverNotifications';
@ -50,7 +51,7 @@ interface RefreshIndicatorProps {
const RefreshIndicator: FC<RefreshIndicatorProps> = ({ item, className }) => { const RefreshIndicator: FC<RefreshIndicatorProps> = ({ item, className }) => {
const [progress, setProgress] = useState(item.RefreshProgress || 0); const [progress, setProgress] = useState(item.RefreshProgress || 0);
const onRefreshProgress = useCallback((_e: Event, apiClient, info) => { const onRefreshProgress = useCallback((_e: Event, _apiClient: ApiClient, info: { ItemId: string | null | undefined; Progress: string; }) => {
if (info.ItemId === item?.Id) { if (info.ItemId === item?.Id) {
setProgress(parseFloat(info.Progress)); setProgress(parseFloat(info.Progress));
} }

View file

@ -3,13 +3,13 @@ import {
type LibraryUpdateInfo type LibraryUpdateInfo
} from '@jellyfin/sdk/lib/generated-client'; } from '@jellyfin/sdk/lib/generated-client';
import { ApiClient } from 'jellyfin-apiclient'; import { ApiClient } from 'jellyfin-apiclient';
import React, { type FC, useCallback, useEffect, useRef } from 'react'; import React, { type FC, type PropsWithChildren, useCallback, useEffect, useRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { usePlaylistsMoveItemMutation } from 'hooks/useFetchItems'; import { usePlaylistsMoveItemMutation } from 'hooks/useFetchItems';
import Events, { Event } from 'utils/events'; import Events, { type Event } from 'utils/events';
import serverNotifications from 'scripts/serverNotifications'; import serverNotifications from 'scripts/serverNotifications';
import inputManager from 'scripts/inputManager'; import inputManager from 'scripts/inputManager';
import dom from 'scripts/dom'; import dom from 'scripts/dom';
@ -48,7 +48,7 @@ interface ItemsContainerProps {
queryKey?: string[] queryKey?: string[]
} }
const ItemsContainer: FC<ItemsContainerProps> = ({ const ItemsContainer: FC<PropsWithChildren<ItemsContainerProps>> = ({
className, className,
isContextMenuEnabled, isContextMenuEnabled,
isMultiSelectEnabled, isMultiSelectEnabled,

View file

@ -1,4 +1,4 @@
import React, { type FC, useCallback, useEffect, useRef, useState } from 'react'; import React, { type FC, type PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import useElementSize from 'hooks/useElementSize'; import useElementSize from 'hooks/useElementSize';
import layoutManager from '../../components/layoutManager'; import layoutManager from '../../components/layoutManager';
@ -21,7 +21,7 @@ interface ScrollerProps {
isAllowNativeSmoothScrollEnabled?: boolean; isAllowNativeSmoothScrollEnabled?: boolean;
} }
const Scroller: FC<ScrollerProps> = ({ const Scroller: FC<PropsWithChildren<ScrollerProps>> = ({
className, className,
isHorizontalEnabled, isHorizontalEnabled,
isMouseWheelEnabled, isMouseWheelEnabled,
@ -126,7 +126,7 @@ const Scroller: FC<ScrollerProps> = ({
}); });
}, [getScrollPosition, getScrollSize, getScrollWidth]); }, [getScrollPosition, getScrollSize, getScrollWidth]);
const initCenterFocus = useCallback((elem, scrollerInstance: ScrollerFactory) => { const initCenterFocus = useCallback((elem: HTMLElement, scrollerInstance: ScrollerFactory) => {
dom.addEventListener(elem, 'focus', function (e: FocusEvent) { dom.addEventListener(elem, 'focus', function (e: FocusEvent) {
const focused = focusManager.focusableParent(e.target); const focused = focusManager.focusableParent(e.target);
if (focused) { if (focused) {
@ -138,19 +138,26 @@ const Scroller: FC<ScrollerProps> = ({
}); });
}, []); }, []);
const addScrollEventListener = useCallback((fn, options) => { const addScrollEventListener = useCallback((fn: () => void, options: AddEventListenerOptions | undefined) => {
if (scrollerFactoryRef.current) { if (scrollerFactoryRef.current) {
dom.addEventListener(scrollerFactoryRef.current.getScrollFrame(), scrollerFactoryRef.current.getScrollEventName(), fn, options); dom.addEventListener(scrollerFactoryRef.current.getScrollFrame(), scrollerFactoryRef.current.getScrollEventName(), fn, options);
} }
}, [scrollerFactoryRef]); }, [scrollerFactoryRef]);
const removeScrollEventListener = useCallback((fn, options) => { const removeScrollEventListener = useCallback((fn: () => void, options: AddEventListenerOptions | undefined) => {
if (scrollerFactoryRef.current) { if (scrollerFactoryRef.current) {
dom.removeEventListener(scrollerFactoryRef.current.getScrollFrame(), scrollerFactoryRef.current.getScrollEventName(), fn, options); dom.removeEventListener(scrollerFactoryRef.current.getScrollFrame(), scrollerFactoryRef.current.getScrollEventName(), fn, options);
} }
}, [scrollerFactoryRef]); }, [scrollerFactoryRef]);
useEffect(() => { useEffect(() => {
const frame = scrollRef.current;
if (!frame) {
console.error('Unexpected null reference');
return;
}
const horizontal = isHorizontalEnabled !== false; const horizontal = isHorizontalEnabled !== false;
const scrollbuttons = isScrollButtonsEnabled !== false; const scrollbuttons = isScrollButtonsEnabled !== false;
const mousewheel = isMouseWheelEnabled !== false; const mousewheel = isMouseWheelEnabled !== false;
@ -179,12 +186,12 @@ const Scroller: FC<ScrollerProps> = ({
}; };
// If just inserted it might not have any height yet - yes this is a hack // If just inserted it might not have any height yet - yes this is a hack
scrollerFactoryRef.current = new ScrollerFactory(scrollRef.current, options); scrollerFactoryRef.current = new ScrollerFactory(frame, options);
scrollerFactoryRef.current.init(); scrollerFactoryRef.current.init();
scrollerFactoryRef.current.reload(); scrollerFactoryRef.current.reload();
if (layoutManager.tv && isCenterFocusEnabled) { if (layoutManager.tv && isCenterFocusEnabled) {
initCenterFocus(scrollRef.current, scrollerFactoryRef.current); initCenterFocus(frame, scrollerFactoryRef.current);
} }
if (enableScrollButtons) { if (enableScrollButtons) {

View file

@ -1,7 +1,7 @@
import type { Api } from '@jellyfin/sdk'; import type { Api } from '@jellyfin/sdk';
import type { UserDto } from '@jellyfin/sdk/lib/generated-client'; import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
import type { ApiClient, Event } from 'jellyfin-apiclient'; import type { ApiClient, Event } from 'jellyfin-apiclient';
import React, { createContext, FC, useContext, useEffect, useMemo, useState } from 'react'; import React, { type FC, type PropsWithChildren, createContext, useContext, useEffect, useMemo, useState } from 'react';
import ServerConnections from '../components/ServerConnections'; import ServerConnections from '../components/ServerConnections';
import events from '../utils/events'; import events from '../utils/events';
@ -16,7 +16,7 @@ export interface JellyfinApiContext {
export const ApiContext = createContext<JellyfinApiContext>({}); export const ApiContext = createContext<JellyfinApiContext>({});
export const useApi = () => useContext(ApiContext); export const useApi = () => useContext(ApiContext);
export const ApiProvider: FC = ({ children }) => { export const ApiProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
const [ legacyApiClient, setLegacyApiClient ] = useState<ApiClient>(); const [ legacyApiClient, setLegacyApiClient ] = useState<ApiClient>();
const [ api, setApi ] = useState<Api>(); const [ api, setApi ] = useState<Api>();
const [ user, setUser ] = useState<UserDto>(); const [ user, setUser ] = useState<UserDto>();

View file

@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { currentSettings as userSettings } from 'scripts/settings/userSettings'; import { currentSettings as userSettings } from 'scripts/settings/userSettings';
import Events from 'utils/events'; import Events, { type Event } from 'utils/events';
import { useApi } from './useApi'; import { useApi } from './useApi';
import { useThemes } from './useThemes'; import { useThemes } from './useThemes';
@ -30,7 +30,7 @@ export function useUserTheme() {
if (userDashboardTheme) setDashboardTheme(userDashboardTheme); if (userDashboardTheme) setDashboardTheme(userDashboardTheme);
}, []); }, []);
const onUserSettingsChange = useCallback((_e, name?: string) => { const onUserSettingsChange = useCallback((_e: Event, name?: string) => {
if (name && THEME_FIELD_NAMES.includes(name)) { if (name && THEME_FIELD_NAMES.includes(name)) {
updateThemesFromSettings(); updateThemesFromSettings();
} }

View file

@ -1,4 +1,4 @@
import React, { createContext, FC, useContext, useEffect, useState } from 'react'; import React, { type FC, type PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
import type { WebConfig } from '../types/webConfig'; import type { WebConfig } from '../types/webConfig';
import defaultConfig from '../config.json'; import defaultConfig from '../config.json';
@ -7,7 +7,7 @@ import fetchLocal from '../utils/fetchLocal';
export const WebConfigContext = createContext<WebConfig>(defaultConfig); export const WebConfigContext = createContext<WebConfig>(defaultConfig);
export const useWebConfig = () => useContext(WebConfigContext); export const useWebConfig = () => useContext(WebConfigContext);
export const WebConfigProvider: FC = ({ children }) => { export const WebConfigProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
const [ config, setConfig ] = useState<WebConfig>(defaultConfig); const [ config, setConfig ] = useState<WebConfig>(defaultConfig);
useEffect(() => { useEffect(() => {

View file

@ -9,7 +9,7 @@ import 'abortcontroller-polyfill'; // requires fetch
import 'resize-observer-polyfill'; import 'resize-observer-polyfill';
import './styles/site.scss'; import './styles/site.scss';
import React, { StrictMode } from 'react'; import React, { StrictMode } from 'react';
import * as ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import Events from './utils/events.ts'; import Events from './utils/events.ts';
import ServerConnections from './components/ServerConnections'; import ServerConnections from './components/ServerConnections';
import globalize from './scripts/globalize'; import globalize from './scripts/globalize';
@ -154,17 +154,17 @@ async function onAppReady() {
ServerConnections.currentApiClient()?.ensureWebSocket(); ServerConnections.currentApiClient()?.ensureWebSocket();
}); });
const root = document.getElementById('reactRoot'); const container = document.getElementById('reactRoot');
// Remove the splash logo // Remove the splash logo
root.innerHTML = ''; container.innerHTML = '';
await appRouter.start(); await appRouter.start();
ReactDOM.render( const root = createRoot(container);
root.render(
<StrictMode> <StrictMode>
<RootApp history={history} /> <RootApp history={history} />
</StrictMode>, </StrictMode>
root
); );
if (!browser.tv && !browser.xboxOne && !browser.ps4) { if (!browser.tv && !browser.xboxOne && !browser.ps4) {

View file

@ -1,5 +1,5 @@
import { ThemeProvider } from '@mui/material'; import { ThemeProvider } from '@mui/material';
import React, { type FC, useState, useEffect } from 'react'; import React, { type FC, type PropsWithChildren, useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { DASHBOARD_APP_PATHS } from 'apps/dashboard/routes/routes'; import { DASHBOARD_APP_PATHS } from 'apps/dashboard/routes/routes';
@ -13,7 +13,7 @@ const isDashboardThemePage = (pathname: string) => [
DASHBOARD_APP_PATHS.PluginConfig DASHBOARD_APP_PATHS.PluginConfig
].some(path => pathname.startsWith(`/${path}`)); ].some(path => pathname.startsWith(`/${path}`));
const UserThemeProvider: FC = ({ children }) => { const UserThemeProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
const [ isDashboard, setIsDashboard ] = useState(false); const [ isDashboard, setIsDashboard ] = useState(false);
const [ muiTheme, setMuiTheme ] = useState(DEFAULT_THEME); const [ muiTheme, setMuiTheme ] = useState(DEFAULT_THEME);