mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #6524 from viown/mui-trickplay
Convert trickplay to mui
This commit is contained in:
commit
3477e0930b
4 changed files with 242 additions and 296 deletions
|
@ -9,10 +9,11 @@ import TextField from '@mui/material/TextField';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
|
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
|
||||||
import { ActionData } from 'types/actionData';
|
import { ActionData } from 'types/actionData';
|
||||||
import { useConfiguration } from 'hooks/useConfiguration';
|
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
import ServerConnections from 'components/ServerConnections';
|
import ServerConnections from 'components/ServerConnections';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
||||||
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
|
|
||||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
const api = ServerConnections.getCurrentApi();
|
const api = ServerConnections.getCurrentApi();
|
||||||
|
@ -36,6 +37,10 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
await getConfigurationApi(api)
|
await getConfigurationApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [ QUERY_KEY ]
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSaved: true
|
isSaved: true
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,9 +10,10 @@ import Typography from '@mui/material/Typography';
|
||||||
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
|
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
|
||||||
import ServerConnections from 'components/ServerConnections';
|
import ServerConnections from 'components/ServerConnections';
|
||||||
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
||||||
import { useConfiguration } from 'hooks/useConfiguration';
|
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
import { ActionData } from 'types/actionData';
|
import { ActionData } from 'types/actionData';
|
||||||
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
|
|
||||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
const api = ServerConnections.getCurrentApi();
|
const api = ServerConnections.getCurrentApi();
|
||||||
|
@ -27,6 +28,10 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
await getConfigurationApi(api)
|
await getConfigurationApi(api)
|
||||||
.updateConfiguration({ serverConfiguration: config });
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
|
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [ QUERY_KEY ]
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSaved: true
|
isSaved: true
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,325 +1,259 @@
|
||||||
import type { ServerConfiguration } from '@jellyfin/sdk/lib/generated-client/models/server-configuration';
|
import React from 'react';
|
||||||
|
|
||||||
|
import globalize from 'lib/globalize';
|
||||||
|
import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom';
|
||||||
|
import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration';
|
||||||
|
import Page from 'components/Page';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import FormControl from '@mui/material/FormControl';
|
||||||
|
import Switch from '@mui/material/Switch';
|
||||||
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Alert from '@mui/material/Alert';
|
||||||
|
import ServerConnections from 'components/ServerConnections';
|
||||||
|
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
||||||
import { TrickplayScanBehavior } from '@jellyfin/sdk/lib/generated-client/models/trickplay-scan-behavior';
|
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 { ProcessPriorityClass } from '@jellyfin/sdk/lib/generated-client/models/process-priority-class';
|
||||||
import React, { type FC, useCallback, useEffect, useRef } from 'react';
|
import { ActionData } from 'types/actionData';
|
||||||
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
|
|
||||||
import globalize from '../../../../lib/globalize';
|
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||||
import Page from '../../../../components/Page';
|
const api = ServerConnections.getCurrentApi();
|
||||||
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
|
if (!api) throw new Error('No Api instance available');
|
||||||
import ButtonElement from '../../../../elements/ButtonElement';
|
|
||||||
import CheckBoxElement from '../../../../elements/CheckBoxElement';
|
|
||||||
import SelectElement from '../../../../elements/SelectElement';
|
|
||||||
import InputElement from '../../../../elements/InputElement';
|
|
||||||
import loading from '../../../../components/loading/loading';
|
|
||||||
import toast from '../../../../components/toast/toast';
|
|
||||||
import ServerConnections from '../../../../components/ServerConnections';
|
|
||||||
|
|
||||||
function onSaveComplete() {
|
const formData = await request.formData();
|
||||||
loading.hide();
|
const data = Object.fromEntries(formData);
|
||||||
toast(globalize.translate('SettingsSaved'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const PlaybackTrickplay: FC = () => {
|
const { data: config } = await getConfigurationApi(api).getConfiguration();
|
||||||
const element = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const loadConfig = useCallback((config: ServerConfiguration) => {
|
const options = config.TrickplayOptions;
|
||||||
const page = element.current;
|
if (!options) throw new Error('Unexpected null TrickplayOptions');
|
||||||
const options = config.TrickplayOptions;
|
|
||||||
|
|
||||||
if (!page) {
|
options.EnableHwAcceleration = data.HwAcceleration?.toString() === 'on';
|
||||||
console.error('Unexpected null reference');
|
options.EnableHwEncoding = data.HwEncoding?.toString() === 'on';
|
||||||
return;
|
options.EnableKeyFrameOnlyExtraction = data.KeyFrameOnlyExtraction?.toString() === 'on';
|
||||||
}
|
options.ScanBehavior = data.ScanBehavior.toString() as TrickplayScanBehavior;
|
||||||
|
options.ProcessPriority = data.ProcessPriority.toString() as ProcessPriorityClass;
|
||||||
|
options.Interval = parseInt(data.ImageInterval.toString() || '10000', 10);
|
||||||
|
options.WidthResolutions = data.WidthResolutions.toString().replace(' ', '').split(',').map(Number);
|
||||||
|
options.TileWidth = parseInt(data.TileWidth.toString() || '10', 10);
|
||||||
|
options.TileHeight = parseInt(data.TileHeight.toString() || '10', 10);
|
||||||
|
options.Qscale = parseInt(data.Qscale.toString() || '4', 10);
|
||||||
|
options.JpegQuality = parseInt(data.JpegQuality.toString() || '90', 10);
|
||||||
|
options.ProcessThreads = parseInt(data.TrickplayThreads.toString() || '1', 10);
|
||||||
|
|
||||||
(page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options?.EnableHwAcceleration || false;
|
await getConfigurationApi(api)
|
||||||
(page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked = options?.EnableHwEncoding || false;
|
.updateConfiguration({ serverConfiguration: config });
|
||||||
(page.querySelector('.chkEnableKeyFrameOnlyExtraction') as HTMLInputElement).checked = options?.EnableKeyFrameOnlyExtraction || 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 = options?.Interval?.toString() || '10000';
|
|
||||||
(page.querySelector('#txtWidthResolutions') as HTMLInputElement).value = options?.WidthResolutions?.join(',') || '';
|
|
||||||
(page.querySelector('#txtTileWidth') as HTMLInputElement).value = options?.TileWidth?.toString() || '10';
|
|
||||||
(page.querySelector('#txtTileHeight') as HTMLInputElement).value = options?.TileHeight?.toString() || '10';
|
|
||||||
(page.querySelector('#txtQscale') as HTMLInputElement).value = options?.Qscale?.toString() || '4';
|
|
||||||
(page.querySelector('#txtJpegQuality') as HTMLInputElement).value = options?.JpegQuality?.toString() || '90';
|
|
||||||
(page.querySelector('#txtProcessThreads') as HTMLInputElement).value = options?.ProcessThreads?.toString() || '1';
|
|
||||||
|
|
||||||
loading.hide();
|
void queryClient.invalidateQueries({
|
||||||
}, []);
|
queryKey: [ QUERY_KEY ]
|
||||||
|
});
|
||||||
|
|
||||||
const loadData = useCallback(() => {
|
return {
|
||||||
loading.show();
|
isSaved: true
|
||||||
|
|
||||||
ServerConnections.currentApiClient()?.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) => {
|
|
||||||
const apiClient = ServerConnections.currentApiClient();
|
|
||||||
|
|
||||||
if (!apiClient) {
|
|
||||||
console.error('[PlaybackTrickplay] No current apiclient instance');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.TrickplayOptions) {
|
|
||||||
throw new Error('Unexpected null TrickplayOptions');
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = config.TrickplayOptions;
|
|
||||||
options.EnableHwAcceleration = (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked;
|
|
||||||
options.EnableHwEncoding = (page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked;
|
|
||||||
options.EnableKeyFrameOnlyExtraction = (page.querySelector('.chkEnableKeyFrameOnlyExtraction') 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);
|
|
||||||
|
|
||||||
apiClient.updateServerConfiguration(config).then(() => {
|
|
||||||
onSaveComplete();
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[PlaybackTrickplay] failed to update config', err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = (e: Event) => {
|
|
||||||
const apiClient = ServerConnections.currentApiClient();
|
|
||||||
|
|
||||||
if (!apiClient) {
|
|
||||||
console.error('[PlaybackTrickplay] No current apiclient instance');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.show();
|
|
||||||
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 += `<option value='NonBlocking'>${globalize.translate('NonBlockingScan')}</option>`;
|
|
||||||
content += `<option value='Blocking'>${globalize.translate('BlockingScan')}</option>`;
|
|
||||||
return content;
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const optionProcessPriority = () => {
|
export const Component = () => {
|
||||||
let content = '';
|
const navigation = useNavigation();
|
||||||
content += `<option value='High'>${globalize.translate('PriorityHigh')}</option>`;
|
const actionData = useActionData() as ActionData | undefined;
|
||||||
content += `<option value='AboveNormal'>${globalize.translate('PriorityAboveNormal')}</option>`;
|
const { data: defaultConfig, isPending } = useConfiguration();
|
||||||
content += `<option value='Normal'>${globalize.translate('PriorityNormal')}</option>`;
|
const isSubmitting = navigation.state === 'submitting';
|
||||||
content += `<option value='BelowNormal'>${globalize.translate('PriorityBelowNormal')}</option>`;
|
|
||||||
content += `<option value='Idle'>${globalize.translate('PriorityIdle')}</option>`;
|
if (!defaultConfig || isPending) {
|
||||||
return content;
|
return <Loading />;
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
id='trickplayConfigurationPage'
|
id='trickplayConfigurationPage'
|
||||||
className='mainAnimatedPage type-interior playbackConfigurationPage'
|
className='mainAnimatedPage type-interior'
|
||||||
title={globalize.translate('Trickplay')}
|
title={globalize.translate('Trickplay')}
|
||||||
>
|
>
|
||||||
<div ref={element} className='content-primary'>
|
<Box className='content-primary'>
|
||||||
<div className='verticalSection'>
|
<Form method='POST'>
|
||||||
<SectionTitleContainer
|
<Stack spacing={3}>
|
||||||
title={globalize.translate('Trickplay')}
|
<Typography variant='h1'>
|
||||||
/>
|
{globalize.translate('Trickplay')}
|
||||||
</div>
|
</Typography>
|
||||||
|
|
||||||
<form className='trickplayConfigurationForm'>
|
{!isSubmitting && actionData?.isSaved && (
|
||||||
<div className='checkboxContainer checkboxContainer-withDescription'>
|
<Alert severity='success'>
|
||||||
<CheckBoxElement
|
{globalize.translate('SettingsSaved')}
|
||||||
className='chkEnableHwAcceleration'
|
</Alert>
|
||||||
title='LabelTrickplayAccel'
|
)}
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
name='HwAcceleration'
|
||||||
|
defaultChecked={defaultConfig.TrickplayOptions?.EnableHwAcceleration}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={globalize.translate('LabelTrickplayAccel')}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
name='HwEncoding'
|
||||||
|
defaultChecked={defaultConfig.TrickplayOptions?.EnableHwEncoding}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={globalize.translate('LabelTrickplayAccelEncoding')}
|
||||||
|
/>
|
||||||
|
<FormHelperText>{globalize.translate('LabelTrickplayAccelEncodingHelp')}</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
name='KeyFrameOnlyExtraction'
|
||||||
|
defaultChecked={defaultConfig.TrickplayOptions?.EnableKeyFrameOnlyExtraction}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={globalize.translate('LabelTrickplayKeyFrameOnlyExtraction')}
|
||||||
|
/>
|
||||||
|
<FormHelperText>{globalize.translate('LabelTrickplayKeyFrameOnlyExtractionHelp')}</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name='ScanBehavior'
|
||||||
|
select
|
||||||
|
defaultValue={defaultConfig.TrickplayOptions?.ScanBehavior}
|
||||||
|
label={globalize.translate('LabelScanBehavior')}
|
||||||
|
helperText={globalize.translate('LabelScanBehaviorHelp')}
|
||||||
|
>
|
||||||
|
<MenuItem value={TrickplayScanBehavior.NonBlocking}>{globalize.translate('NonBlockingScan')}</MenuItem>
|
||||||
|
<MenuItem value={TrickplayScanBehavior.Blocking}>{globalize.translate('BlockingScan')}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name='ProcessPriority'
|
||||||
|
select
|
||||||
|
defaultValue={defaultConfig.TrickplayOptions?.ProcessPriority}
|
||||||
|
label={globalize.translate('LabelProcessPriority')}
|
||||||
|
helperText={globalize.translate('LabelProcessPriorityHelp')}
|
||||||
|
>
|
||||||
|
<MenuItem value={ProcessPriorityClass.High}>{globalize.translate('PriorityHigh')}</MenuItem>
|
||||||
|
<MenuItem value={ProcessPriorityClass.AboveNormal}>{globalize.translate('PriorityAboveNormal')}</MenuItem>
|
||||||
|
<MenuItem value={ProcessPriorityClass.Normal}>{globalize.translate('PriorityNormal')}</MenuItem>
|
||||||
|
<MenuItem value={ProcessPriorityClass.BelowNormal}>{globalize.translate('PriorityBelowNormal')}</MenuItem>
|
||||||
|
<MenuItem value={ProcessPriorityClass.Idle}>{globalize.translate('PriorityIdle')}</MenuItem>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label={globalize.translate('LabelImageInterval')}
|
||||||
|
name='ImageInterval'
|
||||||
|
type='number'
|
||||||
|
inputMode='numeric'
|
||||||
|
defaultValue={defaultConfig.TrickplayOptions?.Interval}
|
||||||
|
inputProps={{
|
||||||
|
min: 1,
|
||||||
|
required: true
|
||||||
|
}}
|
||||||
|
helperText={globalize.translate('LabelImageIntervalHelp')}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className='checkboxContainer checkboxContainer-withDescription'>
|
<TextField
|
||||||
<CheckBoxElement
|
label={globalize.translate('LabelWidthResolutions')}
|
||||||
className='chkEnableHwEncoding'
|
name='WidthResolutions'
|
||||||
title='LabelTrickplayAccelEncoding'
|
defaultValue={defaultConfig.TrickplayOptions?.WidthResolutions}
|
||||||
|
inputProps={{
|
||||||
|
required: true,
|
||||||
|
pattern: '[0-9,]*'
|
||||||
|
}}
|
||||||
|
helperText={globalize.translate('LabelWidthResolutionsHelp')}
|
||||||
/>
|
/>
|
||||||
<div className='fieldDescription checkboxFieldDescription'>
|
|
||||||
<div className='fieldDescription'>
|
<TextField
|
||||||
{globalize.translate('LabelTrickplayAccelEncodingHelp')}
|
label={globalize.translate('LabelTileWidth')}
|
||||||
</div>
|
name='TileWidth'
|
||||||
</div>
|
type='number'
|
||||||
</div>
|
inputMode='numeric'
|
||||||
<div className='checkboxContainer checkboxContainer-withDescription'>
|
defaultValue={defaultConfig.TrickplayOptions?.TileWidth}
|
||||||
<CheckBoxElement
|
inputProps={{
|
||||||
className='chkEnableKeyFrameOnlyExtraction'
|
min: 1,
|
||||||
title='LabelTrickplayKeyFrameOnlyExtraction'
|
required: true
|
||||||
|
}}
|
||||||
|
helperText={globalize.translate('LabelTileWidthHelp')}
|
||||||
/>
|
/>
|
||||||
<div className='fieldDescription checkboxFieldDescription'>
|
|
||||||
<div className='fieldDescription'>
|
|
||||||
{globalize.translate('LabelTrickplayKeyFrameOnlyExtractionHelp')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='verticalSection'>
|
<TextField
|
||||||
<div className='selectContainer fldSelectScanBehavior'>
|
label={globalize.translate('LabelTileHeight')}
|
||||||
<SelectElement
|
name='TileHeight'
|
||||||
id='selectScanBehavior'
|
type='number'
|
||||||
label='LabelScanBehavior'
|
inputMode='numeric'
|
||||||
>
|
defaultValue={defaultConfig.TrickplayOptions?.TileHeight}
|
||||||
{optionScanBehavior()}
|
inputProps={{
|
||||||
</SelectElement>
|
min: 1,
|
||||||
<div className='fieldDescription'>
|
required: true
|
||||||
{globalize.translate('LabelScanBehaviorHelp')}
|
}}
|
||||||
</div>
|
helperText={globalize.translate('LabelTileHeightHelp')}
|
||||||
</div>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='verticalSection'>
|
<TextField
|
||||||
<div className='selectContainer fldSelectProcessPriority'>
|
label={globalize.translate('LabelJpegQuality')}
|
||||||
<SelectElement
|
name='JpegQuality'
|
||||||
id='selectProcessPriority'
|
type='number'
|
||||||
label='LabelProcessPriority'
|
inputMode='numeric'
|
||||||
>
|
defaultValue={defaultConfig.TrickplayOptions?.JpegQuality}
|
||||||
{optionProcessPriority()}
|
inputProps={{
|
||||||
</SelectElement>
|
min: 1,
|
||||||
<div className='fieldDescription'>
|
max: 100,
|
||||||
{globalize.translate('LabelProcessPriorityHelp')}
|
required: true
|
||||||
</div>
|
}}
|
||||||
</div>
|
helperText={globalize.translate('LabelJpegQualityHelp')}
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
<div className='verticalSection'>
|
<TextField
|
||||||
<div className='inputContainer'>
|
label={globalize.translate('LabelQscale')}
|
||||||
<InputElement
|
name='Qscale'
|
||||||
type='number'
|
type='number'
|
||||||
id='txtInterval'
|
inputMode='numeric'
|
||||||
label='LabelImageInterval'
|
defaultValue={defaultConfig.TrickplayOptions?.Qscale}
|
||||||
options={'required inputMode="numeric" pattern="[0-9]*" min="1"'}
|
inputProps={{
|
||||||
/>
|
min: 2,
|
||||||
<div className='fieldDescription'>
|
max: 31,
|
||||||
{globalize.translate('LabelImageIntervalHelp')}
|
required: true
|
||||||
</div>
|
}}
|
||||||
</div>
|
helperText={globalize.translate('LabelQscaleHelp')}
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
<div className='verticalSection'>
|
<TextField
|
||||||
<div className='inputContainer'>
|
label={globalize.translate('LabelTrickplayThreads')}
|
||||||
<InputElement
|
name='TrickplayThreads'
|
||||||
type='text'
|
type='number'
|
||||||
id='txtWidthResolutions'
|
inputMode='numeric'
|
||||||
label='LabelWidthResolutions'
|
defaultValue={defaultConfig.TrickplayOptions?.ProcessThreads}
|
||||||
options={'required pattern="[0-9,]*"'}
|
inputProps={{
|
||||||
/>
|
min: 0,
|
||||||
<div className='fieldDescription'>
|
required: true
|
||||||
{globalize.translate('LabelWidthResolutionsHelp')}
|
}}
|
||||||
</div>
|
helperText={globalize.translate('LabelTrickplayThreadsHelp')}
|
||||||
</div>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='verticalSection'>
|
<Button
|
||||||
<div className='inputContainer'>
|
|
||||||
<InputElement
|
|
||||||
type='number'
|
|
||||||
id='txtTileWidth'
|
|
||||||
label='LabelTileWidth'
|
|
||||||
options={'required inputMode="numeric" pattern="[0-9]*" min="1"'}
|
|
||||||
/>
|
|
||||||
<div className='fieldDescription'>
|
|
||||||
{globalize.translate('LabelTileWidthHelp')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='verticalSection'>
|
|
||||||
<div className='inputContainer'>
|
|
||||||
<InputElement
|
|
||||||
type='number'
|
|
||||||
id='txtTileHeight'
|
|
||||||
label='LabelTileHeight'
|
|
||||||
options={'required inputMode="numeric" pattern="[0-9]*" min="1"'}
|
|
||||||
/>
|
|
||||||
<div className='fieldDescription'>
|
|
||||||
{globalize.translate('LabelTileHeightHelp')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='verticalSection'>
|
|
||||||
<div className='inputContainer'>
|
|
||||||
<InputElement
|
|
||||||
type='number'
|
|
||||||
id='txtJpegQuality'
|
|
||||||
label='LabelJpegQuality'
|
|
||||||
options={'required inputMode="numeric" pattern="[0-9]*" min="1" max="100"'}
|
|
||||||
/>
|
|
||||||
<div className='fieldDescription'>
|
|
||||||
{globalize.translate('LabelJpegQualityHelp')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='verticalSection'>
|
|
||||||
<div className='inputContainer'>
|
|
||||||
<InputElement
|
|
||||||
type='number'
|
|
||||||
id='txtQscale'
|
|
||||||
label='LabelQscale'
|
|
||||||
options={'required inputMode="numeric" pattern="[0-9]*" min="2" max="31"'}
|
|
||||||
/>
|
|
||||||
<div className='fieldDescription'>
|
|
||||||
{globalize.translate('LabelQscaleHelp')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='verticalSection'>
|
|
||||||
<div className='inputContainer'>
|
|
||||||
<InputElement
|
|
||||||
type='number'
|
|
||||||
id='txtProcessThreads'
|
|
||||||
label='LabelTrickplayThreads'
|
|
||||||
options={'required inputMode="numeric" pattern="[0-9]*" min="0"'}
|
|
||||||
/>
|
|
||||||
<div className='fieldDescription'>
|
|
||||||
{globalize.translate('LabelTrickplayThreadsHelp')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<ButtonElement
|
|
||||||
type='submit'
|
type='submit'
|
||||||
className='raised button-submit block'
|
size='large'
|
||||||
title='Save'
|
>
|
||||||
/>
|
{globalize.translate('Save')}
|
||||||
</div>
|
</Button>
|
||||||
</form>
|
</Stack>
|
||||||
</div>
|
</Form>
|
||||||
|
</Box>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PlaybackTrickplay;
|
Component.displayName = 'TrickplayPage';
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { useQuery } from '@tanstack/react-query';
|
||||||
import { useApi } from 'hooks/useApi';
|
import { useApi } from 'hooks/useApi';
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
export const QUERY_KEY = 'Configuration';
|
||||||
|
|
||||||
export const fetchConfiguration = async (api?: Api, options?: AxiosRequestConfig) => {
|
export const fetchConfiguration = async (api?: Api, options?: AxiosRequestConfig) => {
|
||||||
if (!api) {
|
if (!api) {
|
||||||
console.error('[useLogOptions] No API instance available');
|
console.error('[useLogOptions] No API instance available');
|
||||||
|
@ -19,7 +21,7 @@ export const useConfiguration = () => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['Configuration'],
|
queryKey: [QUERY_KEY],
|
||||||
queryFn: ({ signal }) => fetchConfiguration(api, { signal }),
|
queryFn: ({ signal }) => fetchConfiguration(api, { signal }),
|
||||||
enabled: !!api
|
enabled: !!api
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue