diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 0e33b2a0d4..5744b54844 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -9,5 +9,6 @@ export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'users/add', type: AsyncRouteType.Dashboard }, { path: 'users/parentalcontrol', type: AsyncRouteType.Dashboard }, { path: 'users/password', type: AsyncRouteType.Dashboard }, - { path: 'users/profile', type: AsyncRouteType.Dashboard } + { path: 'users/profile', type: AsyncRouteType.Dashboard }, + { path: 'playback/trickplay', type: AsyncRouteType.Dashboard } ]; diff --git a/src/apps/dashboard/routes/_legacyRoutes.ts b/src/apps/dashboard/routes/_legacyRoutes.ts index 175076036c..6adf825dc3 100644 --- a/src/apps/dashboard/routes/_legacyRoutes.ts +++ b/src/apps/dashboard/routes/_legacyRoutes.ts @@ -145,11 +145,5 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ view: 'dashboard/streaming.html', controller: 'dashboard/streaming' } - }, { - path: 'playback/trickplay', - pageProps: { - view: 'dashboard/trickplay.html', - controller: 'dashboard/trickplay' - } } ]; diff --git a/src/apps/dashboard/routes/playback/trickplay.tsx b/src/apps/dashboard/routes/playback/trickplay.tsx new file mode 100644 index 0000000000..dfdecd5084 --- /dev/null +++ b/src/apps/dashboard/routes/playback/trickplay.tsx @@ -0,0 +1,291 @@ +import type { ProcessPriorityClass, ServerConfiguration, TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client'; +import React, { FunctionComponent, useCallback, useEffect, useRef } from 'react'; + +import globalize from '../../../../scripts/globalize'; +import Page from '../../../../components/Page'; +import SectionTitleContainer from '../../../../elements/SectionTitleContainer'; +import ButtonElement from '../../../../elements/ButtonElement'; +import CheckBoxElement from '../../../../elements/CheckBoxElement'; +import SelectElement from '../../../../elements/SelectElement'; +import InputElement from '../../../../elements/InputElement'; +import LinkTrickplayAcceleration from '../../../../components/dashboard/playback/trickplay/LinkTrickplayAcceleration'; +import loading from '../../../../components/loading/loading'; +import toast from '../../../../components/toast/toast'; + +function onSaveComplete() { + loading.hide(); + toast(globalize.translate('SettingsSaved')); +} + +const PlaybackTrickplay: FunctionComponent = () => { + const element = useRef(null); + + const loadConfig = useCallback((config) => { + const page = element.current; + const options = config.TrickplayOptions; + + if (!page) { + console.error('Unexpected null reference'); + return; + } + + (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options.EnableHwAcceleration; + (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; + + loading.hide(); + }, []); + + const loadData = useCallback(() => { + loading.show(); + + window.ApiClient.getServerConfiguration().then(function (config) { + loadConfig(config); + }).catch(err => { + console.error('[playbacktrickplay] failed to fetch server config', err); + }); + }, [loadConfig]); + + useEffect(() => { + const page = element.current; + + if (!page) { + console.error('Unexpected null reference'); + return; + } + + const saveConfig = (config: ServerConfiguration) => { + if (!config.TrickplayOptions) { + throw new Error('Unexpected null TrickplayOptions'); + } + + const options = config.TrickplayOptions; + options.EnableHwAcceleration = (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked; + options.ScanBehavior = (page.querySelector('#selectScanBehavior') as HTMLSelectElement).value as TrickplayScanBehavior; + options.ProcessPriority = (page.querySelector('#selectProcessPriority') as HTMLSelectElement).value as ProcessPriorityClass; + options.Interval = Math.max(1, parseInt((page.querySelector('#txtInterval') as HTMLInputElement).value || '10000', 10)); + options.WidthResolutions = (page.querySelector('#txtWidthResolutions') as HTMLInputElement).value.replace(' ', '').split(',').map(Number); + options.TileWidth = Math.max(1, parseInt((page.querySelector('#txtTileWidth') as HTMLInputElement).value || '10', 10)); + options.TileHeight = Math.max(1, parseInt((page.querySelector('#txtTileHeight') as HTMLInputElement).value || '10', 10)); + options.Qscale = Math.min(31, parseInt((page.querySelector('#txtQscale') as HTMLInputElement).value || '4', 10)); + options.JpegQuality = Math.min(100, parseInt((page.querySelector('#txtJpegQuality') as HTMLInputElement).value || '90', 10)); + options.ProcessThreads = parseInt((page.querySelector('#txtProcessThreads') as HTMLInputElement).value || '1', 10); + + window.ApiClient.updateServerConfiguration(config).then(() => { + onSaveComplete(); + }).catch(err => { + console.error('[playbacktrickplay] failed to update config', err); + }); + }; + + const onSubmit = (e: Event) => { + loading.show(); + + window.ApiClient.getServerConfiguration().then(function (config) { + saveConfig(config); + }).catch(err => { + console.error('[playbacktrickplay] failed to fetch server config', err); + }); + + e.preventDefault(); + e.stopPropagation(); + return false; + }; + + (page.querySelector('.trickplayConfigurationForm') as HTMLFormElement).addEventListener('submit', onSubmit); + + loadData(); + }, [loadData]); + + const optionScanBehavior = () => { + let content = ''; + content += ``; + content += ``; + return content; + }; + + const optionProcessPriority = () => { + let content = ''; + content += ``; + content += ``; + content += ``; + content += ``; + content += ``; + return content; + }; + + return ( + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+ + {optionScanBehavior()} + +
+ {globalize.translate('LabelScanBehaviorHelp')} +
+
+
+ +
+
+ + {optionProcessPriority()} + +
+ {globalize.translate('LabelProcessPriorityHelp')} +
+
+
+ +
+
+ +
+ {globalize.translate('LabelImageIntervalHelp')} +
+
+
+ +
+
+ +
+ {globalize.translate('LabelWidthResolutionsHelp')} +
+
+
+ +
+
+ +
+ {globalize.translate('LabelTileWidthHelp')} +
+
+
+ +
+
+ +
+ {globalize.translate('LabelTileHeightHelp')} +
+
+
+ +
+
+ +
+ {globalize.translate('LabelJpegQualityHelp')} +
+
+
+ +
+
+ +
+ {globalize.translate('LabelQscaleHelp')} +
+
+
+ +
+
+ +
+ {globalize.translate('LabelTrickplayThreadsHelp')} +
+
+
+ +
+ +
+
+
+
+ ); +}; + +export default PlaybackTrickplay; diff --git a/src/components/dashboard/playback/trickplay/LinkTrickplayAcceleration.tsx b/src/components/dashboard/playback/trickplay/LinkTrickplayAcceleration.tsx new file mode 100644 index 0000000000..dc720b7b69 --- /dev/null +++ b/src/components/dashboard/playback/trickplay/LinkTrickplayAcceleration.tsx @@ -0,0 +1,33 @@ +import React, { FunctionComponent } from 'react'; +import globalize from '../../../../scripts/globalize'; + +type IProps = { + title?: string; + className?: string; + href?: string; +}; + +const createLinkElement = ({ className, title, href }: IProps) => ({ + __html: ` + ${title} + ` +}); + +const LinkTrickplayAcceleration: FunctionComponent = ({ className, title, href }: IProps) => { + return ( +
+ ); +}; + +export default LinkTrickplayAcceleration; diff --git a/src/controllers/dashboard/trickplay.html b/src/controllers/dashboard/trickplay.html deleted file mode 100644 index cf0e2d44d9..0000000000 --- a/src/controllers/dashboard/trickplay.html +++ /dev/null @@ -1,68 +0,0 @@ -
-
-
-
-
-

${Trickplay}

-
-
-
- -
- -
-
- -
${LabelScanBehaviorHelp}
-
-
- -
${LabelProcessPriorityHelp}
-
-
- -
${LabelImageIntervalHelp}
-
-
- -
${LabelWidthResolutionsHelp}
-
-
- -
${LabelTileWidthHelp}
-
-
- -
${LabelTileHeightHelp}
-
-
- -
${LabelJpegQualityHelp}
-
-
- -
${LabelQscaleHelp}
-
-
- -
-
-
-
-
-
-
\ No newline at end of file diff --git a/src/controllers/dashboard/trickplay.js b/src/controllers/dashboard/trickplay.js deleted file mode 100644 index 674434274a..0000000000 --- a/src/controllers/dashboard/trickplay.js +++ /dev/null @@ -1,71 +0,0 @@ -import 'jquery'; -import loading from '../../components/loading/loading'; -import libraryMenu from '../../scripts/libraryMenu'; -import globalize from '../../scripts/globalize'; -import Dashboard from '../../utils/dashboard'; - -function loadPage(page, config) { - const trickplayOptions = config.TrickplayOptions; - - page.querySelector('#chkEnableHwAcceleration').checked = trickplayOptions.EnableHwAcceleration; - $('#selectScanBehavior', page).val(trickplayOptions.ScanBehavior); - $('#selectProcessPriority', page).val(trickplayOptions.ProcessPriority); - $('#txtInterval', page).val(trickplayOptions.Interval); - $('#txtWidthResolutions', page).val(trickplayOptions.WidthResolutions.join(',')); - $('#txtTileWidth', page).val(trickplayOptions.TileWidth); - $('#txtTileHeight', page).val(trickplayOptions.TileHeight); - $('#txtQscale', page).val(trickplayOptions.Qscale); - $('#txtJpegQuality', page).val(trickplayOptions.JpegQuality); - $('#txtProcessThreads', page).val(trickplayOptions.ProcessThreads); - loading.hide(); -} - -function onSubmit() { - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function (config) { - const trickplayOptions = config.TrickplayOptions; - - trickplayOptions.EnableHwAcceleration = form.querySelector('#chkEnableHwAcceleration').checked; - trickplayOptions.ScanBehavior = $('#selectScanBehavior', form).val(); - trickplayOptions.ProcessPriority = $('#selectProcessPriority', form).val(); - trickplayOptions.Interval = Math.max(0, parseInt($('#txtInterval', form).val() || '10000', 10)); - trickplayOptions.WidthResolutions = $('#txtWidthResolutions', form).val().replace(' ', '').split(',').map(Number); - trickplayOptions.TileWidth = Math.max(1, parseInt($('#txtTileWidth', form).val() || '10', 10)); - trickplayOptions.TileHeight = Math.max(1, parseInt($('#txtTileHeight', form).val() || '10', 10)); - trickplayOptions.Qscale = Math.min(31, parseInt($('#txtQscale', form).val() || '10', 10)); - trickplayOptions.JpegQuality = Math.min(100, parseInt($('#txtJpegQuality', form).val() || '80', 10)); - trickplayOptions.ProcessThreads = parseInt($('#txtProcessThreads', form).val() || '0', 10); - - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - - return false; -} - -function getTabs() { - return [{ - href: '#/dashboard/playback/transcoding', - name: globalize.translate('Transcoding') - }, { - href: '#/dashboard/playback/resume', - name: globalize.translate('ButtonResume') - }, { - href: '#/dashboard/playback/streaming', - name: globalize.translate('TabStreaming') - }, { - href: '#/dashboard/playback/trickplay', - name: globalize.translate('Trickplay') - }]; -} - -$(document).on('pageinit', '#trickplayConfigurationPage', function () { - $('.trickplayConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); -}).on('pageshow', '#trickplayConfigurationPage', function () { - loading.show(); - libraryMenu.setTabs('playback', 3, getTabs); - const page = this; - ApiClient.getServerConfiguration().then(function (config) { - loadPage(page, config); - }); -});