diff --git a/package-lock.json b/package-lock.json index cad0b97516..cb8c14b1a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,9 +51,9 @@ "material-design-icons-iconfont": "6.7.0", "native-promise-only": "0.8.1", "pdfjs-dist": "3.11.174", - "react": "17.0.2", + "react": "18.3.1", "react-blurhash": "0.3.0", - "react-dom": "17.0.2", + "react-dom": "18.3.1", "react-lazy-load-image-component": "1.6.0", "react-router-dom": "6.23.1", "resize-observer-polyfill": "1.5.1", @@ -75,8 +75,8 @@ "@types/loadable__component": "5.13.9", "@types/lodash-es": "4.17.12", "@types/markdown-it": "14.1.1", - "@types/react": "17.0.80", - "@types/react-dom": "17.0.25", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "@types/sortablejs": "1.15.8", "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", @@ -3638,28 +3638,28 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz", + "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", "dependencies": { - "@floating-ui/utils": "^0.2.1" + "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz", + "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==", "dependencies": { "@floating-ui/core": "^1.0.0", "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", - "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz", + "integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==", "dependencies": { - "@floating-ui/dom": "^1.6.1" + "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -3667,9 +3667,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" }, "node_modules/@fontsource/noto-sans": { "version": "5.0.22", @@ -4974,23 +4974,21 @@ "dev": true }, "node_modules/@types/react": { - "version": "17.0.80", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", - "integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==", - "license": "MIT", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "^0.16", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "17.0.25", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", - "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "dev": true, "dependencies": { - "@types/react": "^17" + "@types/react": "*" } }, "node_modules/@types/react-lazy-load-image-component": { @@ -5018,11 +5016,6 @@ "dev": true, "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": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", @@ -17198,12 +17191,11 @@ } }, "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" @@ -17219,16 +17211,15 @@ } }, "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "17.0.2" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -18085,12 +18076,11 @@ } }, "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "node_modules/schema-utils": { @@ -26310,34 +26300,34 @@ "dev": true }, "@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz", + "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", "requires": { - "@floating-ui/utils": "^0.2.1" + "@floating-ui/utils": "^0.2.0" } }, "@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz", + "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==", "requires": { "@floating-ui/core": "^1.0.0", "@floating-ui/utils": "^0.2.0" } }, "@floating-ui/react-dom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", - "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz", + "integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==", "requires": { - "@floating-ui/dom": "^1.6.1" + "@floating-ui/dom": "^1.0.0" } }, "@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" }, "@fontsource/noto-sans": { "version": "5.0.22", @@ -27211,22 +27201,21 @@ "dev": true }, "@types/react": { - "version": "17.0.80", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.80.tgz", - "integrity": "sha512-LrgHIu2lEtIo8M7d1FcI3BdwXWoRQwMoXOZ7+dPTW0lYREjmlHl3P0U1VD0i/9tppOuv8/sam7sOjx34TxSFbA==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "requires": { "@types/prop-types": "*", - "@types/scheduler": "^0.16", "csstype": "^3.0.2" } }, "@types/react-dom": { - "version": "17.0.25", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", - "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "dev": true, "requires": { - "@types/react": "^17" + "@types/react": "*" } }, "@types/react-lazy-load-image-component": { @@ -27252,11 +27241,6 @@ "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "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": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", @@ -35943,12 +35927,11 @@ } }, "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "react-blurhash": { @@ -35958,13 +35941,12 @@ "requires": {} }, "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.2" } }, "react-is": { @@ -36563,12 +36545,11 @@ } }, "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "schema-utils": { diff --git a/package.json b/package.json index 103c3a9742..f9c6b5af89 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "@types/loadable__component": "5.13.9", "@types/lodash-es": "4.17.12", "@types/markdown-it": "14.1.1", - "@types/react": "17.0.80", - "@types/react-dom": "17.0.25", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", "@types/sortablejs": "1.15.8", "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", @@ -112,9 +112,9 @@ "material-design-icons-iconfont": "6.7.0", "native-promise-only": "0.8.1", "pdfjs-dist": "3.11.174", - "react": "17.0.2", + "react": "18.3.1", "react-blurhash": "0.3.0", - "react-dom": "17.0.2", + "react-dom": "18.3.1", "react-lazy-load-image-component": "1.6.0", "react-router-dom": "6.23.1", "resize-observer-polyfill": "1.5.1", diff --git a/src/apps/dashboard/routes/activity.tsx b/src/apps/dashboard/routes/activity.tsx index b5460a18c0..7da39a7f5b 100644 --- a/src/apps/dashboard/routes/activity.tsx +++ b/src/apps/dashboard/routes/activity.tsx @@ -147,7 +147,7 @@ const Activity = () => { } ]; - const onViewChange = useCallback((_e, newView: ActivityView | null) => { + const onViewChange = useCallback((_e: React.MouseEvent, newView: ActivityView | null) => { if (newView !== null) { setActivityView(newView); } diff --git a/src/apps/dashboard/routes/playback/trickplay.tsx b/src/apps/dashboard/routes/playback/trickplay.tsx index 26f0360b6c..00609aec6e 100644 --- a/src/apps/dashboard/routes/playback/trickplay.tsx +++ b/src/apps/dashboard/routes/playback/trickplay.tsx @@ -1,5 +1,7 @@ -import type { ProcessPriorityClass, ServerConfiguration, TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client'; -import React, { type FunctionComponent, useCallback, useEffect, useRef } from 'react'; +import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration'; +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 Page from '../../../../components/Page'; @@ -17,10 +19,10 @@ function onSaveComplete() { toast(globalize.translate('SettingsSaved')); } -const PlaybackTrickplay: FunctionComponent = () => { +const PlaybackTrickplay: FC = () => { const element = useRef(null); - const loadConfig = useCallback((config) => { + const loadConfig = useCallback((config: ServerConfiguration) => { const page = element.current; const options = config.TrickplayOptions; @@ -29,17 +31,17 @@ const PlaybackTrickplay: FunctionComponent = () => { return; } - (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options.EnableHwAcceleration; - (page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked = options.EnableHwEncoding; - (page.querySelector('#selectScanBehavior') as HTMLSelectElement).value = options.ScanBehavior; - (page.querySelector('#selectProcessPriority') as HTMLSelectElement).value = options.ProcessPriority; - (page.querySelector('#txtInterval') as HTMLInputElement).value = options.Interval; - (page.querySelector('#txtWidthResolutions') as HTMLInputElement).value = options.WidthResolutions.join(','); - (page.querySelector('#txtTileWidth') as HTMLInputElement).value = options.TileWidth; - (page.querySelector('#txtTileHeight') as HTMLInputElement).value = options.TileHeight; - (page.querySelector('#txtQscale') as HTMLInputElement).value = options.Qscale; - (page.querySelector('#txtJpegQuality') as HTMLInputElement).value = options.JpegQuality; - (page.querySelector('#txtProcessThreads') as HTMLInputElement).value = options.ProcessThreads; + (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options?.EnableHwAcceleration || false; + (page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked = options?.EnableHwEncoding || false; + (page.querySelector('#selectScanBehavior') as HTMLSelectElement).value = (options?.ScanBehavior || TrickplayScanBehavior.NonBlocking); + (page.querySelector('#selectProcessPriority') as HTMLSelectElement).value = (options?.ProcessPriority || ProcessPriorityClass.Normal); + (page.querySelector('#txtInterval') as HTMLInputElement).value = String(options?.Interval); + (page.querySelector('#txtWidthResolutions') as HTMLInputElement).value = options?.WidthResolutions?.join(',') || ''; + (page.querySelector('#txtTileWidth') as HTMLInputElement).value = String(options?.TileWidth); + (page.querySelector('#txtTileHeight') as HTMLInputElement).value = String(options?.TileHeight); + (page.querySelector('#txtQscale') as HTMLInputElement).value = String(options?.Qscale); + (page.querySelector('#txtJpegQuality') as HTMLInputElement).value = String(options?.JpegQuality); + (page.querySelector('#txtProcessThreads') as HTMLInputElement).value = String(options?.ProcessThreads); loading.hide(); }, []); diff --git a/src/apps/dashboard/routes/users/access.tsx b/src/apps/dashboard/routes/users/access.tsx index 812ad80c5b..8953593de7 100644 --- a/src/apps/dashboard/routes/users/access.tsx +++ b/src/apps/dashboard/routes/users/access.tsx @@ -1,5 +1,5 @@ -import type { UserDto } from '@jellyfin/sdk/lib/generated-client'; -import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react'; +import type { BaseItemDto, DeviceInfo, UserDto } from '@jellyfin/sdk/lib/generated-client'; +import React, { useCallback, useEffect, useState, useRef } from 'react'; import loading from '../../../../components/loading/loading'; import libraryMenu from '../../../../scripts/libraryMenu'; @@ -14,13 +14,13 @@ import CheckBoxElement from '../../../../elements/CheckBoxElement'; import Page from '../../../../components/Page'; type ItemsArr = { - Name?: string; - Id?: string; - AppName?: string; + Name?: string | null; + Id?: string | null; + AppName?: string | null; checkedAttribute?: string }; -const UserLibraryAccess: FunctionComponent = () => { +const UserLibraryAccess = () => { const [ userName, setUserName ] = useState(''); const [channelsItems, setChannelsItems] = useState([]); const [mediaFoldersItems, setMediaFoldersItems] = useState([]); @@ -33,7 +33,7 @@ const UserLibraryAccess: FunctionComponent = () => { select.dispatchEvent(evt); }; - const loadMediaFolders = useCallback((user, mediaFolders) => { + const loadMediaFolders = useCallback((user: UserDto, mediaFolders: BaseItemDto[]) => { const page = element.current; if (!page) { @@ -44,7 +44,7 @@ const UserLibraryAccess: FunctionComponent = () => { const itemsArr: ItemsArr[] = []; 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"' : ''; itemsArr.push({ Id: folder.Id, @@ -56,11 +56,11 @@ const UserLibraryAccess: FunctionComponent = () => { setMediaFoldersItems(itemsArr); const chkEnableAllFolders = page.querySelector('.chkEnableAllFolders') as HTMLInputElement; - chkEnableAllFolders.checked = user.Policy.EnableAllFolders; + chkEnableAllFolders.checked = Boolean(user.Policy?.EnableAllFolders); triggerChange(chkEnableAllFolders); }, []); - const loadChannels = useCallback((user, channels) => { + const loadChannels = useCallback((user: UserDto, channels: BaseItemDto[]) => { const page = element.current; if (!page) { @@ -71,7 +71,7 @@ const UserLibraryAccess: FunctionComponent = () => { const itemsArr: ItemsArr[] = []; 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"' : ''; itemsArr.push({ Id: folder.Id, @@ -89,11 +89,11 @@ const UserLibraryAccess: FunctionComponent = () => { } const chkEnableAllChannels = page.querySelector('.chkEnableAllChannels') as HTMLInputElement; - chkEnableAllChannels.checked = user.Policy.EnableAllChannels; + chkEnableAllChannels.checked = Boolean(user.Policy?.EnableAllChannels); triggerChange(chkEnableAllChannels); }, []); - const loadDevices = useCallback((user, devices) => { + const loadDevices = useCallback((user: UserDto, devices: DeviceInfo[]) => { const page = element.current; if (!page) { @@ -104,7 +104,7 @@ const UserLibraryAccess: FunctionComponent = () => { const itemsArr: ItemsArr[] = []; 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"' : ''; itemsArr.push({ Id: device.Id, @@ -117,18 +117,18 @@ const UserLibraryAccess: FunctionComponent = () => { setDevicesItems(itemsArr); const chkEnableAllDevices = page.querySelector('.chkEnableAllDevices') as HTMLInputElement; - chkEnableAllDevices.checked = user.Policy.EnableAllDevices; + chkEnableAllDevices.checked = Boolean(user.Policy?.EnableAllDevices); triggerChange(chkEnableAllDevices); - if (user.Policy.IsAdministrator) { + if (user.Policy?.IsAdministrator) { (page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.add('hide'); } else { (page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.remove('hide'); } }, []); - const loadUser = useCallback((user, mediaFolders, channels, devices) => { - setUserName(user.Name); + const loadUser = useCallback((user: UserDto, mediaFolders: BaseItemDto[], channels: BaseItemDto[], devices: DeviceInfo[]) => { + setUserName(user.Name || ''); libraryMenu.setTitle(user.Name); loadChannels(user, channels); loadMediaFolders(user, mediaFolders); diff --git a/src/apps/dashboard/routes/users/add.tsx b/src/apps/dashboard/routes/users/add.tsx index 116895e947..07213e6ed3 100644 --- a/src/apps/dashboard/routes/users/add.tsx +++ b/src/apps/dashboard/routes/users/add.tsx @@ -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 globalize from '../../../../scripts/globalize'; @@ -17,16 +18,16 @@ type userInput = { }; type ItemsArr = { - Name?: string; + Name?: string | null; Id?: string; }; -const UserNew: FunctionComponent = () => { +const UserNew = () => { const [ channelsItems, setChannelsItems ] = useState([]); const [ mediaFoldersItems, setMediaFoldersItems ] = useState([]); const element = useRef(null); - const getItemsResult = (items: ItemsArr[]) => { + const getItemsResult = (items: BaseItemDto[]) => { return items.map(item => ({ Id: item.Id, @@ -35,7 +36,7 @@ const UserNew: FunctionComponent = () => { ); }; - const loadMediaFolders = useCallback((result) => { + const loadMediaFolders = useCallback((result: BaseItemDto[]) => { const page = element.current; if (!page) { @@ -53,7 +54,7 @@ const UserNew: FunctionComponent = () => { (page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked = false; }, []); - const loadChannels = useCallback((result) => { + const loadChannels = useCallback((result: BaseItemDto[]) => { const page = element.current; if (!page) { diff --git a/src/apps/dashboard/routes/users/index.tsx b/src/apps/dashboard/routes/users/index.tsx index f758b85016..78676b3487 100644 --- a/src/apps/dashboard/routes/users/index.tsx +++ b/src/apps/dashboard/routes/users/index.tsx @@ -1,5 +1,5 @@ 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 globalize from '../../../../scripts/globalize'; @@ -21,7 +21,7 @@ type MenuEntry = { icon?: string; }; -const UserProfiles: FunctionComponent = () => { +const UserProfiles = () => { const [ users, setUsers ] = useState([]); const element = useRef(null); diff --git a/src/apps/dashboard/routes/users/parentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx index 14231245cc..3f9dd9095f 100644 --- a/src/apps/dashboard/routes/users/parentalcontrol.tsx +++ b/src/apps/dashboard/routes/users/parentalcontrol.tsx @@ -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 React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react'; +import React, { useCallback, useEffect, useState, useRef } from 'react'; import escapeHTML from 'escape-html'; import globalize from '../../../../scripts/globalize'; @@ -19,9 +19,12 @@ import Page from '../../../../components/Page'; import prompt from '../../../../components/prompt/prompt'; import ServerConnections from 'components/ServerConnections'; -type UnratedItem = { +type ItemArr = { name: string; - value: string; + value: UnratedItem; +}; + +type UnratedItemArr = ItemArr & { checkedAttribute: string }; @@ -56,17 +59,17 @@ function handleSaveUser( }; } -const UserParentalControl: FunctionComponent = () => { +const UserParentalControl = () => { const [ userName, setUserName ] = useState(''); const [ parentalRatings, setParentalRatings ] = useState([]); - const [ unratedItems, setUnratedItems ] = useState([]); + const [ unratedItems, setUnratedItems ] = useState([]); const [ accessSchedules, setAccessSchedules ] = useState([]); const [ allowedTags, setAllowedTags ] = useState([]); const [ blockedTags, setBlockedTags ] = useState([]); const element = useRef(null); - const populateRatings = useCallback((allParentalRatings) => { + const populateRatings = useCallback((allParentalRatings: ParentalRating[]) => { let rating; const ratings: ParentalRating[] = []; @@ -91,7 +94,7 @@ const UserParentalControl: FunctionComponent = () => { setParentalRatings(ratings); }, []); - const loadUnratedItems = useCallback((user) => { + const loadUnratedItems = useCallback((user: UserDto) => { const page = element.current; if (!page) { @@ -99,33 +102,33 @@ const UserParentalControl: FunctionComponent = () => { return; } - const items = [{ + const items: ItemArr[] = [{ name: globalize.translate('Books'), - value: 'Book' + value: UnratedItem.Book }, { name: globalize.translate('Channels'), - value: 'ChannelContent' + value: UnratedItem.ChannelContent }, { name: globalize.translate('LiveTV'), - value: 'LiveTvChannel' + value: UnratedItem.LiveTvChannel }, { name: globalize.translate('Movies'), - value: 'Movie' + value: UnratedItem.Movie }, { name: globalize.translate('Music'), - value: 'Music' + value: UnratedItem.Music }, { name: globalize.translate('Trailers'), - value: 'Trailer' + value: UnratedItem.Trailer }, { name: globalize.translate('Shows'), - value: 'Series' + value: UnratedItem.Series }]; - const itemsArr: UnratedItem[] = []; + const itemsArr: UnratedItemArr[] = []; 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"' : ''; itemsArr.push({ value: item.value, @@ -182,7 +185,7 @@ const UserParentalControl: FunctionComponent = () => { } }, []); - const renderAccessSchedule = useCallback((schedules) => { + const renderAccessSchedule = useCallback((schedules: AccessSchedule[]) => { const page = element.current; if (!page) { @@ -198,7 +201,7 @@ const UserParentalControl: FunctionComponent = () => { btnDelete.addEventListener('click', function () { const index = parseInt(btnDelete.getAttribute('data-index') ?? '0', 10); schedules.splice(index, 1); - const newindex = schedules.filter((i: number) => i != index); + const newindex = schedules.filter((_, i) => i != index); 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) { (page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide'); diff --git a/src/apps/dashboard/routes/users/password.tsx b/src/apps/dashboard/routes/users/password.tsx index 544e8c6dec..1766f960cb 100644 --- a/src/apps/dashboard/routes/users/password.tsx +++ b/src/apps/dashboard/routes/users/password.tsx @@ -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 UserPasswordForm from '../../../../components/dashboard/users/UserPasswordForm'; @@ -7,7 +7,7 @@ import SectionTitleContainer from '../../../../elements/SectionTitleContainer'; import Page from '../../../../components/Page'; import loading from '../../../../components/loading/loading'; -const UserPassword: FunctionComponent = () => { +const UserPassword = () => { const userId = getParameterByName('userId'); const [ userName, setUserName ] = useState(''); diff --git a/src/apps/dashboard/routes/users/profile.tsx b/src/apps/dashboard/routes/users/profile.tsx index 7ef62f8261..34fd2a90e8 100644 --- a/src/apps/dashboard/routes/users/profile.tsx +++ b/src/apps/dashboard/routes/users/profile.tsx @@ -1,5 +1,5 @@ -import type { SyncPlayUserAccessType, UserDto } from '@jellyfin/sdk/lib/generated-client'; -import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react'; +import type { BaseItemDto, NameIdPair, SyncPlayUserAccessType, UserDto } from '@jellyfin/sdk/lib/generated-client'; +import React, { useCallback, useEffect, useState, useRef } from 'react'; import escapeHTML from 'escape-html'; import Dashboard from '../../../../utils/dashboard'; @@ -17,15 +17,10 @@ import { getParameterByName } from '../../../../utils/url'; import SelectElement from '../../../../elements/SelectElement'; import Page from '../../../../components/Page'; -type ResetProvider = AuthProvider & { +type ResetProvider = BaseItemDto & { checkedAttribute: string }; -type AuthProvider = { - Name?: string; - Id?: string; -}; - const getCheckedElementDataIds = (elements: NodeListOf) => ( Array.prototype.filter.call(elements, e => e.checked) .map(e => e.getAttribute('data-id')) @@ -40,11 +35,11 @@ function onSaveComplete() { toast(globalize.translate('SettingsSaved')); } -const UserEdit: FunctionComponent = () => { +const UserEdit = () => { const [ userName, setUserName ] = useState(''); const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState([]); - const [ authProviders, setAuthProviders ] = useState([]); - const [ passwordResetProviders, setPasswordResetProviders ] = useState([]); + const [ authProviders, setAuthProviders ] = useState([]); + const [ passwordResetProviders, setPasswordResetProviders ] = useState([]); const [ authenticationProviderId, setAuthenticationProviderId ] = useState(''); const [ passwordResetProviderId, setPasswordResetProviderId ] = useState(''); @@ -61,48 +56,27 @@ const UserEdit: FunctionComponent = () => { return window.ApiClient.getUser(userId); }; - const loadAuthProviders = useCallback((user, providers) => { - const page = element.current; - - if (!page) { - console.error('Unexpected null reference'); - return; - } - + const loadAuthProviders = useCallback((page: HTMLDivElement, user: UserDto, providers: NameIdPair[]) => { const fldSelectLoginProvider = page.querySelector('.fldSelectLoginProvider') as HTMLDivElement; fldSelectLoginProvider.classList.toggle('hide', providers.length <= 1); setAuthProviders(providers); - const currentProviderId = user.Policy.AuthenticationProviderId; + const currentProviderId = user.Policy?.AuthenticationProviderId || ''; setAuthenticationProviderId(currentProviderId); }, []); - const loadPasswordResetProviders = useCallback((user, providers) => { - const page = element.current; - - if (!page) { - console.error('Unexpected null reference'); - return; - } - + const loadPasswordResetProviders = useCallback((page: HTMLDivElement, user: UserDto, providers: NameIdPair[]) => { const fldSelectPasswordResetProvider = page.querySelector('.fldSelectPasswordResetProvider') as HTMLDivElement; fldSelectPasswordResetProvider.classList.toggle('hide', providers.length <= 1); setPasswordResetProviders(providers); - const currentProviderId = user.Policy.PasswordResetProviderId; + const currentProviderId = user.Policy?.PasswordResetProviderId || ''; setPasswordResetProviderId(currentProviderId); }, []); - const loadDeleteFolders = useCallback((user, mediaFolders) => { - const page = element.current; - - if (!page) { - console.error('Unexpected null reference'); - return; - } - + const loadDeleteFolders = useCallback((page: HTMLDivElement, user: UserDto, mediaFolders: BaseItemDto[]) => { window.ApiClient.getJSON(window.ApiClient.getUrl('Channels', { SupportsMediaDeletion: true })).then(function (channelsResult) { @@ -110,22 +84,20 @@ const UserEdit: FunctionComponent = () => { let checkedAttribute; const itemsArr: ResetProvider[] = []; - for (const folder of mediaFolders) { - isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1; + for (const mediaFolder of mediaFolders) { + isChecked = user.Policy?.EnableContentDeletion || user.Policy?.EnableContentDeletionFromFolders?.indexOf(mediaFolder.Id || '') != -1; checkedAttribute = isChecked ? ' checked="checked"' : ''; itemsArr.push({ - Id: folder.Id, - Name: folder.Name, + ...mediaFolder, checkedAttribute: checkedAttribute }); } - for (const folder of channelsResult.Items) { - isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1; + for (const channel of channelsResult.Items) { + isChecked = user.Policy?.EnableContentDeletion || user.Policy?.EnableContentDeletionFromFolders?.indexOf(channel.Id || '') != -1; checkedAttribute = isChecked ? ' checked="checked"' : ''; itemsArr.push({ - Id: folder.Id, - Name: folder.Name, + ...channel, checkedAttribute: checkedAttribute }); } @@ -133,14 +105,14 @@ const UserEdit: FunctionComponent = () => { setDeleteFoldersAccess(itemsArr); const chkEnableDeleteAllFolders = page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement; - chkEnableDeleteAllFolders.checked = user.Policy.EnableContentDeletion; + chkEnableDeleteAllFolders.checked = user.Policy?.EnableContentDeletion || false; triggerChange(chkEnableDeleteAllFolders); }).catch(err => { console.error('[useredit] failed to fetch channels', err); }); }, []); - const loadUser = useCallback((user) => { + const loadUser = useCallback((user: UserDto) => { const page = element.current; if (!page) { @@ -149,25 +121,25 @@ const UserEdit: FunctionComponent = () => { } window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/Providers')).then(function (providers) { - loadAuthProviders(user, providers); + loadAuthProviders(page, user, providers); }).catch(err => { console.error('[useredit] failed to fetch auth providers', err); }); window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/PasswordResetProviders')).then(function (providers) { - loadPasswordResetProviders(user, providers); + loadPasswordResetProviders(page, user, providers); }).catch(err => { console.error('[useredit] failed to fetch password reset providers', err); }); window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', { IsHidden: false })).then(function (folders) { - loadDeleteFolders(user, folders.Items); + loadDeleteFolders(page, user, folders.Items); }).catch(err => { console.error('[useredit] failed to fetch media folders', err); }); 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; txtUserName.disabled = false; @@ -176,30 +148,30 @@ const UserEdit: FunctionComponent = () => { const lnkEditUserPreferences = page.querySelector('.lnkEditUserPreferences') as HTMLDivElement; lnkEditUserPreferences.setAttribute('href', 'mypreferencesmenu.html?userId=' + user.Id); LibraryMenu.setTitle(user.Name); - setUserName(user.Name); - (page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name; - (page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = user.Policy.IsAdministrator; - (page.querySelector('.chkDisabled') as HTMLInputElement).checked = user.Policy.IsDisabled; - (page.querySelector('.chkIsHidden') as HTMLInputElement).checked = user.Policy.IsHidden; - (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked = user.Policy.EnableCollectionManagement; - (page.querySelector('.chkEnableSubtitleManagement') as HTMLInputElement).checked = user.Policy.EnableSubtitleManagement; - (page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = user.Policy.EnableSharedDeviceControl; - (page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = user.Policy.EnableRemoteControlOfOtherUsers; - (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = user.Policy.EnableContentDownloading; - (page.querySelector('.chkManageLiveTv') as HTMLInputElement).checked = user.Policy.EnableLiveTvManagement; - (page.querySelector('.chkEnableLiveTvAccess') as HTMLInputElement).checked = user.Policy.EnableLiveTvAccess; - (page.querySelector('.chkEnableMediaPlayback') as HTMLInputElement).checked = user.Policy.EnableMediaPlayback; - (page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked = user.Policy.EnableAudioPlaybackTranscoding; - (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked = user.Policy.EnableVideoPlaybackTranscoding; - (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked = user.Policy.EnablePlaybackRemuxing; - (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked = user.Policy.ForceRemoteSourceTranscoding; - (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked = user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess; - (page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value = user.Policy.RemoteClientBitrateLimit > 0 ? - (user.Policy.RemoteClientBitrateLimit / 1e6).toLocaleString(undefined, { maximumFractionDigits: 6 }) : ''; - (page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value = user.Policy.LoginAttemptsBeforeLockout || '0'; - (page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value = user.Policy.MaxActiveSessions || '0'; + setUserName(user.Name || ''); + (page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name || ''; + (page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = !!user.Policy?.IsAdministrator; + (page.querySelector('.chkDisabled') as HTMLInputElement).checked = !!user.Policy?.IsDisabled; + (page.querySelector('.chkIsHidden') as HTMLInputElement).checked = !!user.Policy?.IsHidden; + (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked = !!user.Policy?.EnableCollectionManagement; + (page.querySelector('.chkEnableSubtitleManagement') as HTMLInputElement).checked = !!user.Policy?.EnableSubtitleManagement; + (page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = !!user.Policy?.EnableSharedDeviceControl; + (page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = !!user.Policy?.EnableRemoteControlOfOtherUsers; + (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = !!user.Policy?.EnableContentDownloading; + (page.querySelector('.chkManageLiveTv') as HTMLInputElement).checked = !!user.Policy?.EnableLiveTvManagement; + (page.querySelector('.chkEnableLiveTvAccess') as HTMLInputElement).checked = !!user.Policy?.EnableLiveTvAccess; + (page.querySelector('.chkEnableMediaPlayback') as HTMLInputElement).checked = !!user.Policy?.EnableMediaPlayback; + (page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked = !!user.Policy?.EnableAudioPlaybackTranscoding; + (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked = !!user.Policy?.EnableVideoPlaybackTranscoding; + (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked = !!user.Policy?.EnablePlaybackRemuxing; + (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked = !!user.Policy?.ForceRemoteSourceTranscoding; + (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked = user.Policy?.EnableRemoteAccess == null || user.Policy?.EnableRemoteAccess; + (page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value = user.Policy?.RemoteClientBitrateLimit && user.Policy?.RemoteClientBitrateLimit > 0 ? + (user.Policy?.RemoteClientBitrateLimit / 1e6).toLocaleString(undefined, { maximumFractionDigits: 6 }) : ''; + (page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value = String(user.Policy?.MaxActiveSessions) || '0'; + (page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value = String(user.Policy?.SyncPlayAccess) || '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(); }, [loadAuthProviders, loadPasswordResetProviders, loadDeleteFolders ]); diff --git a/src/apps/experimental/components/AppToolbar/RemotePlayButton.tsx b/src/apps/experimental/components/AppToolbar/RemotePlayButton.tsx index 6749b61284..b94532b13f 100644 --- a/src/apps/experimental/components/AppToolbar/RemotePlayButton.tsx +++ b/src/apps/experimental/components/AppToolbar/RemotePlayButton.tsx @@ -33,7 +33,7 @@ const RemotePlayButton = () => { const [ remotePlayMenuAnchorEl, setRemotePlayMenuAnchorEl ] = useState(null); const isRemotePlayMenuOpen = Boolean(remotePlayMenuAnchorEl); - const onRemotePlayButtonClick = useCallback((event) => { + const onRemotePlayButtonClick = useCallback((event: React.MouseEvent) => { setRemotePlayMenuAnchorEl(event.currentTarget); }, [ setRemotePlayMenuAnchorEl ]); @@ -44,7 +44,7 @@ const RemotePlayButton = () => { const [ remotePlayActiveMenuAnchorEl, setRemotePlayActiveMenuAnchorEl ] = useState(null); const isRemotePlayActiveMenuOpen = Boolean(remotePlayActiveMenuAnchorEl); - const onRemotePlayActiveButtonClick = useCallback((event) => { + const onRemotePlayActiveButtonClick = useCallback((event: React.MouseEvent) => { setRemotePlayActiveMenuAnchorEl(event.currentTarget); }, [ setRemotePlayActiveMenuAnchorEl ]); diff --git a/src/apps/experimental/components/AppToolbar/SyncPlayButton.tsx b/src/apps/experimental/components/AppToolbar/SyncPlayButton.tsx index 94a0834876..46955c9b9a 100644 --- a/src/apps/experimental/components/AppToolbar/SyncPlayButton.tsx +++ b/src/apps/experimental/components/AppToolbar/SyncPlayButton.tsx @@ -17,7 +17,7 @@ const SyncPlayButton = () => { const [ syncPlayMenuAnchorEl, setSyncPlayMenuAnchorEl ] = useState(null); const isSyncPlayMenuOpen = Boolean(syncPlayMenuAnchorEl); - const onSyncPlayButtonClick = useCallback((event) => { + const onSyncPlayButtonClick = useCallback((event: React.MouseEvent) => { setSyncPlayMenuAnchorEl(event.currentTarget); }, [ setSyncPlayMenuAnchorEl ]); diff --git a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx index 6e08b3fea6..476eb94fbd 100644 --- a/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx +++ b/src/apps/experimental/components/AppToolbar/menus/SyncPlayMenu.tsx @@ -22,7 +22,7 @@ import { useApi } from 'hooks/useApi'; import { useSyncPlayGroups } from 'hooks/useSyncPlayGroups'; import globalize from 'scripts/globalize'; import { PluginType } from 'types/plugin'; -import Events from 'utils/events'; +import Events, { Event } from 'utils/events'; export const ID = 'app-sync-play-menu'; @@ -136,7 +136,7 @@ const SyncPlayMenu: FC = ({ } }, [ __legacyApiClient__, onMenuClose, syncPlay ]); - const updateSyncPlayGroup = useCallback((_e, enabled) => { + const updateSyncPlayGroup = useCallback((_e: Event, enabled: boolean) => { if (syncPlay && enabled) { setCurrentGroup(syncPlay.Manager.getGroupInfo() ?? undefined); } else { diff --git a/src/apps/experimental/routes/home.tsx b/src/apps/experimental/routes/home.tsx index 837db942b1..ae48e60055 100644 --- a/src/apps/experimental/routes/home.tsx +++ b/src/apps/experimental/routes/home.tsx @@ -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 globalize from '../../../scripts/globalize'; @@ -25,7 +25,7 @@ type ControllerProps = { destroy: () => void; }; -const Home: FunctionComponent = () => { +const Home = () => { const [ searchParams ] = useSearchParams(); const initialTabIndex = parseInt(searchParams.get('tab') ?? '0', 10); diff --git a/src/apps/experimental/routes/user/display/hooks/useDisplaySettingForm.ts b/src/apps/experimental/routes/user/display/hooks/useDisplaySettingForm.ts index d7986fa42c..54a919eb84 100644 --- a/src/apps/experimental/routes/user/display/hooks/useDisplaySettingForm.ts +++ b/src/apps/experimental/routes/user/display/hooks/useDisplaySettingForm.ts @@ -6,6 +6,11 @@ import globalize from 'scripts/globalize'; import { DisplaySettingsValues } from '../types'; import { useDisplaySettings } from './useDisplaySettings'; +type UpdateField = { + name: keyof DisplaySettingsValues; + value: string | boolean; +}; + export function useDisplaySettingForm() { const [urlParams] = useSearchParams(); const { @@ -21,7 +26,7 @@ export function useDisplaySettingForm() { } }, [formValues, loading, displaySettings]); - const updateField = useCallback(({ name, value }) => { + const updateField = useCallback(({ name, value }: UpdateField) => { if (formValues) { setFormValues({ ...formValues, diff --git a/src/components/AppBody.tsx b/src/components/AppBody.tsx index 5d10bca2c9..d8530ae865 100644 --- a/src/components/AppBody.tsx +++ b/src/components/AppBody.tsx @@ -1,11 +1,11 @@ -import React, { FC, useEffect } from 'react'; +import React, { type FC, type PropsWithChildren, useEffect } from 'react'; import viewContainer from './viewContainer'; /** * A simple component that includes the correct structure for ViewManager pages * to exist alongside standard React pages. */ -const AppBody: FC = ({ children }) => { +const AppBody: FC> = ({ children }) => { useEffect(() => () => { // Reset view container state on unload viewContainer.reset(); diff --git a/src/components/Page.tsx b/src/components/Page.tsx index 7c072b5376..c3dacd8383 100644 --- a/src/components/Page.tsx +++ b/src/components/Page.tsx @@ -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'; @@ -9,14 +9,14 @@ type PageProps = { isMenuButtonEnabled?: boolean, isNowPlayingBarEnabled?: boolean, isThemeMediaSupported?: boolean, - backDropType?: string + backDropType?: string, }; /** * 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. */ -const Page: FunctionComponent> = ({ +const Page: FC>> = ({ children, id, className = '', diff --git a/src/components/ResponsiveDrawer.tsx b/src/components/ResponsiveDrawer.tsx index 15f3d2c32f..7a2344ff28 100644 --- a/src/components/ResponsiveDrawer.tsx +++ b/src/components/ResponsiveDrawer.tsx @@ -3,7 +3,7 @@ import Box from '@mui/material/Box'; import Drawer from '@mui/material/Drawer'; import SwipeableDrawer from '@mui/material/SwipeableDrawer'; import useMediaQuery from '@mui/material/useMediaQuery'; -import React, { FC } from 'react'; +import React, { type FC, type PropsWithChildren } from 'react'; import browser from 'scripts/browser'; @@ -15,7 +15,7 @@ export interface ResponsiveDrawerProps { onOpen: () => void } -const ResponsiveDrawer: FC = ({ +const ResponsiveDrawer: FC> = ({ children, open = false, onClose, diff --git a/src/components/cardbuilder/Card/CardWrapper.tsx b/src/components/cardbuilder/Card/CardWrapper.tsx index 4c8ec854ea..91119aee1b 100644 --- a/src/components/cardbuilder/Card/CardWrapper.tsx +++ b/src/components/cardbuilder/Card/CardWrapper.tsx @@ -1,4 +1,4 @@ -import React, { type FC } from 'react'; +import React, { type FC, type PropsWithChildren } from 'react'; import layoutManager from 'components/layoutManager'; import type { DataAttributes } from 'types/dataAttributes'; @@ -7,7 +7,7 @@ interface CardWrapperProps { dataAttributes: DataAttributes; } -const CardWrapper: FC = ({ +const CardWrapper: FC> = ({ className, dataAttributes, children diff --git a/src/components/dashboard/users/AccessContainer.tsx b/src/components/dashboard/users/AccessContainer.tsx index 88727f396a..bc73a579d3 100644 --- a/src/components/dashboard/users/AccessContainer.tsx +++ b/src/components/dashboard/users/AccessContainer.tsx @@ -1,8 +1,8 @@ -import React, { FunctionComponent } from 'react'; +import React, { type FC, type PropsWithChildren } from 'react'; import globalize from '../../../scripts/globalize'; import CheckBoxElement from '../../../elements/CheckBoxElement'; -type IProps = { +interface AccessContainerProps { containerClassName?: string; headerTitle?: string; checkBoxClassName?: string; @@ -11,10 +11,19 @@ type IProps = { accessClassName?: string; listTitle?: string; description?: string; - children?: React.ReactNode -}; +} -const AccessContainer: FunctionComponent = ({ containerClassName, headerTitle, checkBoxClassName, checkBoxTitle, listContainerClassName, accessClassName, listTitle, description, children }: IProps) => { +const AccessContainer: FC> = ({ + containerClassName, + headerTitle, + checkBoxClassName, + checkBoxTitle, + listContainerClassName, + accessClassName, + listTitle, + description, + children +}) => { return (

{globalize.translate(headerTitle)}

@@ -28,9 +37,12 @@ const AccessContainer: FunctionComponent = ({ containerClassName, header

{globalize.translate(listTitle)}

-
+
{children}
diff --git a/src/components/listview/List/ListContentWrapper.tsx b/src/components/listview/List/ListContentWrapper.tsx index 59323dec73..f65778a624 100644 --- a/src/components/listview/List/ListContentWrapper.tsx +++ b/src/components/listview/List/ListContentWrapper.tsx @@ -1,4 +1,4 @@ -import React, { type FC } from 'react'; +import React, { type FC, type PropsWithChildren } from 'react'; import Box from '@mui/material/Box'; interface ListContentWrapperProps { @@ -7,7 +7,7 @@ interface ListContentWrapperProps { enableOverview?: boolean; } -const ListContentWrapper: FC = ({ +const ListContentWrapper: FC> = ({ itemOverview, enableContentWrapper, enableOverview, diff --git a/src/components/listview/List/ListGroupHeaderWrapper.tsx b/src/components/listview/List/ListGroupHeaderWrapper.tsx index fd17d83120..a287ab12f9 100644 --- a/src/components/listview/List/ListGroupHeaderWrapper.tsx +++ b/src/components/listview/List/ListGroupHeaderWrapper.tsx @@ -1,11 +1,11 @@ -import React, { type FC } from 'react'; +import React, { type FC, type PropsWithChildren } from 'react'; import Typography from '@mui/material/Typography'; interface ListGroupHeaderWrapperProps { index?: number; } -const ListGroupHeaderWrapper: FC = ({ +const ListGroupHeaderWrapper: FC> = ({ index, children }) => { diff --git a/src/components/listview/List/ListTextWrapper.tsx b/src/components/listview/List/ListTextWrapper.tsx index 675ebe99d4..be2d4ecd09 100644 --- a/src/components/listview/List/ListTextWrapper.tsx +++ b/src/components/listview/List/ListTextWrapper.tsx @@ -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 Typography from '@mui/material/Typography'; @@ -7,7 +7,7 @@ interface ListTextWrapperProps { isLargeStyle?: boolean; } -const ListTextWrapper: FC = ({ +const ListTextWrapper: FC> = ({ index, isLargeStyle, children diff --git a/src/components/listview/List/ListWrapper.tsx b/src/components/listview/List/ListWrapper.tsx index 9b394f9839..800706409f 100644 --- a/src/components/listview/List/ListWrapper.tsx +++ b/src/components/listview/List/ListWrapper.tsx @@ -1,5 +1,5 @@ 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 Button from '@mui/material/Button'; import layoutManager from '../../layoutManager'; @@ -13,7 +13,7 @@ interface ListWrapperProps { className?: string; } -const ListWrapper: FC = ({ +const ListWrapper: FC> = ({ index, action, title, diff --git a/src/components/toolbar/AppToolbar.tsx b/src/components/toolbar/AppToolbar.tsx index 8283860f3c..ee31312c8f 100644 --- a/src/components/toolbar/AppToolbar.tsx +++ b/src/components/toolbar/AppToolbar.tsx @@ -4,7 +4,7 @@ import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; import Toolbar from '@mui/material/Toolbar'; 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 { useApi } from 'hooks/useApi'; @@ -27,7 +27,7 @@ const onBackButtonClick = () => { }); }; -const AppToolbar: FC = ({ +const AppToolbar: FC> = ({ buttons, children, isDrawerAvailable, diff --git a/src/components/toolbar/UserMenuButton.tsx b/src/components/toolbar/UserMenuButton.tsx index fa47ce85e6..1e37f5be55 100644 --- a/src/components/toolbar/UserMenuButton.tsx +++ b/src/components/toolbar/UserMenuButton.tsx @@ -14,7 +14,7 @@ const UserMenuButton = () => { const [ userMenuAnchorEl, setUserMenuAnchorEl ] = useState(null); const isUserMenuOpen = Boolean(userMenuAnchorEl); - const onUserButtonClick = useCallback((event) => { + const onUserButtonClick = useCallback((event: React.MouseEvent) => { setUserMenuAnchorEl(event.currentTarget); }, [ setUserMenuAnchorEl ]); diff --git a/src/elements/CheckBoxElement.tsx b/src/elements/CheckBoxElement.tsx index c8d861a0bb..3b3adfeb8e 100644 --- a/src/elements/CheckBoxElement.tsx +++ b/src/elements/CheckBoxElement.tsx @@ -1,8 +1,27 @@ import escapeHTML from 'escape-html'; -import React, { FunctionComponent } from 'react'; +import React, { type FC } from 'react'; 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: `