Add WebUI settings (#460)
* Add WebUI settings * [VersionMapping] Require server version "r1427" for preview
This commit is contained in:
@@ -34,6 +34,7 @@ import { LibrarySettings } from '@/screens/settings/LibrarySettings';
|
||||
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
|
||||
import { DownloadSettings } from '@/screens/settings/DownloadSettings.tsx';
|
||||
import { ServerSettings } from '@/screens/settings/ServerSettings.tsx';
|
||||
import { WebUISettings } from '@/screens/settings/WebUISettings.tsx';
|
||||
|
||||
if (__DEV__) {
|
||||
// Adds messages only in a dev environment
|
||||
@@ -68,6 +69,7 @@ export const App: React.FC = () => (
|
||||
<Route path="downloadSettings" element={<DownloadSettings />} />
|
||||
<Route path="backup" element={<Backup />} />
|
||||
<Route path="server" element={<ServerSettings />} />
|
||||
<Route path="webUI" element={<WebUISettings />} />
|
||||
</Route>
|
||||
|
||||
{/* Manga Routes */}
|
||||
|
||||
158
src/components/settings/SelectSetting.tsx
Normal file
158
src/components/settings/SelectSetting.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
Select,
|
||||
Stack,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import { TranslationKey } from '@/typings.ts';
|
||||
|
||||
export type SelectSettingValueDisplayInfo = {
|
||||
text: TranslationKey;
|
||||
description?: TranslationKey;
|
||||
disclaimer?: TranslationKey;
|
||||
};
|
||||
|
||||
export type SelectSettingValue<Value> = [Value: Value, DisplayInfo: SelectSettingValueDisplayInfo];
|
||||
|
||||
export const SelectSetting = <SettingValue extends string | number>({
|
||||
settingName,
|
||||
dialogDescription,
|
||||
value,
|
||||
defaultValue,
|
||||
values,
|
||||
handleChange,
|
||||
disabled = false,
|
||||
}: {
|
||||
settingName: string;
|
||||
dialogDescription?: string;
|
||||
value?: SettingValue;
|
||||
defaultValue: SettingValue;
|
||||
values: SelectSettingValue<SettingValue>[];
|
||||
handleChange: (value: SettingValue) => void;
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [dialogValue, setDialogValue] = useState(value ?? defaultValue);
|
||||
|
||||
const valueDisplayText = useMemo(() => values.find(([key]) => key === value)?.[1]?.text, [value]);
|
||||
const dialogValueDisplayInfo = useMemo(() => values.find(([key]) => key === dialogValue)![1], [dialogValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogValue(value);
|
||||
}, [value]);
|
||||
|
||||
const closeDialog = (resetValue: boolean = true) => {
|
||||
if (resetValue) {
|
||||
setDialogValue(value ?? defaultValue);
|
||||
}
|
||||
|
||||
setIsDialogOpen(false);
|
||||
};
|
||||
|
||||
const updateSetting = () => {
|
||||
closeDialog(false);
|
||||
handleChange(dialogValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItemButton disabled={disabled} onClick={() => setIsDialogOpen(true)}>
|
||||
<ListItemText
|
||||
primary={settingName}
|
||||
secondary={valueDisplayText ? t(valueDisplayText) : t('global.label.loading')}
|
||||
secondaryTypographyProps={{ style: { display: 'flex', flexDirection: 'column' } }}
|
||||
/>
|
||||
</ListItemButton>
|
||||
|
||||
<Dialog open={isDialogOpen} onClose={() => closeDialog()} fullWidth>
|
||||
<DialogContent>
|
||||
<DialogTitle sx={{ paddingLeft: 0 }}>{settingName}</DialogTitle>
|
||||
{!!dialogDescription && (
|
||||
<DialogContentText sx={{ paddingBottom: '10px' }}>{dialogDescription}</DialogContentText>
|
||||
)}
|
||||
{(!!dialogValueDisplayInfo.description || !!dialogValueDisplayInfo.disclaimer) && (
|
||||
<DialogContentText sx={{ paddingBottom: '10px' }} component="div">
|
||||
{dialogValueDisplayInfo.description && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
whiteSpace: 'pre-line',
|
||||
}}
|
||||
>
|
||||
{t(dialogValueDisplayInfo.description)}
|
||||
</Typography>
|
||||
)}
|
||||
{dialogValueDisplayInfo.disclaimer && (
|
||||
<Stack direction="row" alignItems="center">
|
||||
<InfoIcon color="warning" />
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
marginLeft: '10px',
|
||||
marginTop: '5px',
|
||||
whiteSpace: 'pre-line',
|
||||
}}
|
||||
>
|
||||
{t(dialogValueDisplayInfo.disclaimer)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContentText>
|
||||
)}
|
||||
{/* {!!dialogValueDisplayInfo.disclaimer && ( */}
|
||||
{/* <DialogContentText sx={{ paddingBottom: '10px', color: 'orange' }}> */}
|
||||
{/* {t(dialogValueDisplayInfo.disclaimer)} */}
|
||||
{/* </DialogContentText> */}
|
||||
{/* )} */}
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
id="dialog-select"
|
||||
value={dialogValue}
|
||||
onChange={(e) => setDialogValue(e.target.value as SettingValue)}
|
||||
>
|
||||
{values.map(([selectValue, { text: selectText }]) => (
|
||||
<MenuItem key={selectValue} value={selectValue}>
|
||||
{t(selectText)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => closeDialog()} color="primary">
|
||||
{t('global.button.cancel')}
|
||||
</Button>
|
||||
<Button onClick={() => updateSetting()} color="primary">
|
||||
{t('global.button.ok')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
84
src/components/settings/webUI/WebUIUpdateIntervalSetting.tsx
Normal file
84
src/components/settings/webUI/WebUIUpdateIntervalSetting.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { List, ListItem, ListItemText, Switch } from '@mui/material';
|
||||
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
|
||||
import { useCallback } from 'react';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
|
||||
import { getPersistedServerSetting, usePersistedValue } from '@/util/usePersistedValue.tsx';
|
||||
|
||||
const DEFAULT_VALUE = 23;
|
||||
const MIN_VALUE = 1;
|
||||
const MAX_VALUE = 23; // 1 month
|
||||
|
||||
export const WebUIUpdateIntervalSetting = ({ disabled = false }: { disabled?: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data } = requestManager.useGetServerSettings();
|
||||
const updateCheckInterval = data?.settings.webUIUpdateCheckInterval;
|
||||
const shouldAutoUpdate = !!updateCheckInterval;
|
||||
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||
const [currentUpdateCheckInterval, persistUpdateCheckInterval] = usePersistedValue(
|
||||
'lastUpdateCheckInterval',
|
||||
DEFAULT_VALUE,
|
||||
updateCheckInterval,
|
||||
getPersistedServerSetting,
|
||||
);
|
||||
|
||||
const updateSetting = useCallback(
|
||||
(webUIUpdateCheckInterval: number) => {
|
||||
persistUpdateCheckInterval(
|
||||
webUIUpdateCheckInterval === 0 ? currentUpdateCheckInterval : webUIUpdateCheckInterval,
|
||||
);
|
||||
mutateSettings({ variables: { input: { settings: { webUIUpdateCheckInterval } } } });
|
||||
},
|
||||
[currentUpdateCheckInterval],
|
||||
);
|
||||
|
||||
const setDoAutoUpdates = (enable: boolean) => {
|
||||
const globalUpdateInterval = enable ? currentUpdateCheckInterval : 0;
|
||||
updateSetting(globalUpdateInterval);
|
||||
};
|
||||
|
||||
return (
|
||||
<List>
|
||||
<ListItem disabled={disabled}>
|
||||
<ListItemText primary={t('settings.webui.auto_update.label.title')} />
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
disabled={disabled}
|
||||
edge="end"
|
||||
checked={shouldAutoUpdate}
|
||||
onChange={(e) => setDoAutoUpdates(e.target.checked)}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<NumberSetting
|
||||
settingTitle={t('settings.webui.auto_update.label.interval')}
|
||||
settingValue={
|
||||
updateCheckInterval !== undefined
|
||||
? t('library.settings.global_update.auto_update.interval.label.value', {
|
||||
hours: currentUpdateCheckInterval,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
value={currentUpdateCheckInterval}
|
||||
minValue={MIN_VALUE}
|
||||
maxValue={MAX_VALUE}
|
||||
defaultValue={DEFAULT_VALUE}
|
||||
showSlider
|
||||
dialogTitle={t('settings.webui.auto_update.label.interval')}
|
||||
valueUnit={t('global.time.hour_short')}
|
||||
handleUpdate={updateSetting}
|
||||
disabled={disabled || !shouldAutoUpdate}
|
||||
/>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
@@ -620,7 +620,84 @@
|
||||
"settings": "Server Settings"
|
||||
}
|
||||
},
|
||||
"title": "Settings"
|
||||
"title": "Settings",
|
||||
"webui": {
|
||||
"auto_update": {
|
||||
"label": {
|
||||
"interval": "Update interval",
|
||||
"title": "Automatically download the latest version"
|
||||
}
|
||||
},
|
||||
"channel": {
|
||||
"label": {
|
||||
"title": "Channel"
|
||||
},
|
||||
"option": {
|
||||
"bundled": {
|
||||
"label": {
|
||||
"description": "Use the version that was delivered with the server release",
|
||||
"title": "Bundled"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"label": {
|
||||
"description": "Use the latest features and help us get them ready for a stable release.",
|
||||
"disclaimer": "Features and changes in this version might not be completely ready yet and can cause bugs.\nMake sure that you have automatic backups enabled to prevent loss of your library!",
|
||||
"title": "Preview"
|
||||
}
|
||||
},
|
||||
"stable": {
|
||||
"label": {
|
||||
"description": "Use the latest released version.",
|
||||
"title": "Stable"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron_path": {
|
||||
"label": {
|
||||
"description": "The path to the electron installation on the server",
|
||||
"title": "Electron path"
|
||||
}
|
||||
},
|
||||
"flavor": {
|
||||
"label": {
|
||||
"title": "Flavor"
|
||||
},
|
||||
"option": {
|
||||
"custom": {
|
||||
"label": {
|
||||
"description": "Use a custom $t(settings.webui.title.webui).\nTo use a custom $t(settings.webui.title.webui) replace the content of the \"webUI\" directory in the root directory on the server with the files of the custom $t(settings.webui.title.webui)",
|
||||
"title": "Custom"
|
||||
}
|
||||
},
|
||||
"webui": {
|
||||
"label": {
|
||||
"description": "Use the default $t(settings.webui.title.webui)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"interface": {
|
||||
"label": {
|
||||
"description": "Where to start the $t(settings.webui.title.webui) when starting the server",
|
||||
"title": "Interface"
|
||||
},
|
||||
"option": {
|
||||
"label": {
|
||||
"browser": "Browser",
|
||||
"electron": "Electron"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"initial_open_browser": "Open the $t(settings.webui.title.webui) when starting the server"
|
||||
},
|
||||
"title": {
|
||||
"settings": "$t(settings.webui.title.webui) Settings",
|
||||
"webui": "WebUI"
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"configuration": {
|
||||
|
||||
@@ -26,6 +26,7 @@ import CollectionsOutlinedBookmarkIcon from '@mui/icons-material/CollectionsBook
|
||||
import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined';
|
||||
import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
||||
import DnsIcon from '@mui/icons-material/Dns';
|
||||
import WebIcon from '@mui/icons-material/Web';
|
||||
import { langCodeToName } from '@/util/language';
|
||||
import { useLocalStorage } from '@/util/useLocalStorage';
|
||||
import { ListItemLink } from '@/components/util/ListItemLink';
|
||||
@@ -145,6 +146,12 @@ export function Settings() {
|
||||
</Select>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<ListItemLink to="/settings/webUI">
|
||||
<ListItemIcon>
|
||||
<WebIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('settings.webui.title.webui')} />
|
||||
</ListItemLink>
|
||||
<ListItemLink to="/settings/server">
|
||||
<ListItemIcon>
|
||||
<DnsIcon />
|
||||
|
||||
169
src/screens/settings/WebUISettings.tsx
Normal file
169
src/screens/settings/WebUISettings.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import List from '@mui/material/List';
|
||||
import { ListItem, ListItemText, Switch } from '@mui/material';
|
||||
import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
|
||||
import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
|
||||
import { ServerSettings } from '@/typings.ts';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { WebUIUpdateIntervalSetting } from '@/components/settings/webUI/WebUIUpdateIntervalSetting.tsx';
|
||||
import { TextSetting } from '@/components/settings/TextSetting.tsx';
|
||||
import {
|
||||
SelectSetting,
|
||||
SelectSettingValue,
|
||||
SelectSettingValueDisplayInfo,
|
||||
} from '@/components/settings/SelectSetting.tsx';
|
||||
import { WebUiChannel, WebUiFlavor, WebUiInterface } from '@/lib/graphql/generated/graphql.ts';
|
||||
|
||||
type WebUISettingsType = Pick<
|
||||
ServerSettings,
|
||||
| 'webUIFlavor'
|
||||
| 'initialOpenInBrowserEnabled'
|
||||
| 'webUIInterface'
|
||||
| 'electronPath'
|
||||
| 'webUIChannel'
|
||||
| 'webUIUpdateCheckInterval'
|
||||
>;
|
||||
|
||||
const FLAVORS = Object.values(WebUiFlavor);
|
||||
const FLAVOR_TO_TRANSLATION_KEY: { [flavor in WebUiFlavor]: SelectSettingValueDisplayInfo } = {
|
||||
[WebUiFlavor.Webui]: {
|
||||
text: 'settings.webui.title.webui',
|
||||
description: 'settings.webui.flavor.option.webui.label.description',
|
||||
},
|
||||
[WebUiFlavor.Custom]: {
|
||||
text: 'settings.webui.flavor.option.custom.label.title',
|
||||
description: 'settings.webui.flavor.option.custom.label.description',
|
||||
},
|
||||
};
|
||||
const FLAVOR_SELECT_VALUES: SelectSettingValue<WebUiFlavor>[] = FLAVORS.map((flavor) => [
|
||||
flavor,
|
||||
FLAVOR_TO_TRANSLATION_KEY[flavor],
|
||||
]);
|
||||
|
||||
const CHANNELS = Object.values(WebUiChannel);
|
||||
const CHANNEL_TO_TRANSLATION_KEYS: {
|
||||
[channel in WebUiChannel]: SelectSettingValueDisplayInfo;
|
||||
} = {
|
||||
[WebUiChannel.Bundled]: {
|
||||
text: 'settings.webui.channel.option.bundled.label.title',
|
||||
description: 'settings.webui.channel.option.bundled.label.description',
|
||||
},
|
||||
[WebUiChannel.Stable]: {
|
||||
text: 'settings.webui.channel.option.stable.label.title',
|
||||
description: 'settings.webui.channel.option.stable.label.description',
|
||||
},
|
||||
[WebUiChannel.Preview]: {
|
||||
text: 'settings.webui.channel.option.preview.label.title',
|
||||
description: 'settings.webui.channel.option.preview.label.description',
|
||||
disclaimer: 'settings.webui.channel.option.preview.label.disclaimer',
|
||||
},
|
||||
};
|
||||
const CHANNEL_SELECT_VALUES: SelectSettingValue<WebUiChannel>[] = CHANNELS.map((channel) => [
|
||||
channel,
|
||||
CHANNEL_TO_TRANSLATION_KEYS[channel],
|
||||
]);
|
||||
|
||||
const INTERFACES = Object.values(WebUiInterface);
|
||||
const INTERFACE_TO_TRANSLATION_KEYS: {
|
||||
[webUIInterface in WebUiInterface]: SelectSettingValueDisplayInfo;
|
||||
} = {
|
||||
[WebUiInterface.Browser]: {
|
||||
text: 'settings.webui.interface.option.label.browser',
|
||||
description: 'settings.webui.interface.label.description',
|
||||
},
|
||||
[WebUiInterface.Electron]: {
|
||||
text: 'settings.webui.interface.option.label.electron',
|
||||
description: 'settings.webui.interface.label.description',
|
||||
},
|
||||
};
|
||||
const INTERFACE_SELECT_VALUES: SelectSettingValue<WebUiInterface>[] = INTERFACES.map((webUIInterface) => [
|
||||
webUIInterface,
|
||||
INTERFACE_TO_TRANSLATION_KEYS[webUIInterface],
|
||||
]);
|
||||
|
||||
const extractWebUISettings = (settings: ServerSettings): WebUISettingsType => ({
|
||||
webUIFlavor: settings.webUIFlavor,
|
||||
initialOpenInBrowserEnabled: settings.initialOpenInBrowserEnabled,
|
||||
webUIInterface: settings.webUIInterface,
|
||||
electronPath: settings.electronPath,
|
||||
webUIChannel: settings.webUIChannel,
|
||||
webUIUpdateCheckInterval: settings.webUIUpdateCheckInterval,
|
||||
});
|
||||
|
||||
export const WebUISettings = () => {
|
||||
const { t } = useTranslation();
|
||||
const { setTitle, setAction } = useContext(NavBarContext);
|
||||
|
||||
useSetDefaultBackTo('settings');
|
||||
|
||||
useEffect(() => {
|
||||
setTitle(t('settings.webui.title.settings'));
|
||||
setAction(null);
|
||||
}, [t]);
|
||||
|
||||
const { data } = requestManager.useGetServerSettings();
|
||||
const webUISettings = data ? extractWebUISettings(data.settings) : undefined;
|
||||
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||
|
||||
const isDefaultWebUI = webUISettings?.webUIFlavor === WebUiFlavor.Webui;
|
||||
|
||||
const updateSetting = <Setting extends keyof WebUISettingsType>(
|
||||
setting: Setting,
|
||||
value: WebUISettingsType[Setting],
|
||||
) => {
|
||||
mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
|
||||
};
|
||||
|
||||
return (
|
||||
<List>
|
||||
<SelectSetting<WebUiFlavor>
|
||||
settingName={t('settings.webui.flavor.label.title')}
|
||||
value={webUISettings?.webUIFlavor}
|
||||
defaultValue={WebUiFlavor.Webui}
|
||||
values={FLAVOR_SELECT_VALUES}
|
||||
handleChange={(flavor) => updateSetting('webUIFlavor', flavor)}
|
||||
/>
|
||||
<ListItem>
|
||||
<ListItemText primary={t('settings.webui.label.initial_open_browser')} />
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={!!webUISettings?.initialOpenInBrowserEnabled}
|
||||
onChange={(e) => updateSetting('initialOpenInBrowserEnabled', e.target.checked)}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<SelectSetting<WebUiInterface>
|
||||
settingName={t('settings.webui.interface.label.title')}
|
||||
value={webUISettings?.webUIInterface}
|
||||
defaultValue={WebUiInterface.Browser}
|
||||
values={INTERFACE_SELECT_VALUES}
|
||||
handleChange={(webUIInterface) => updateSetting('webUIInterface', webUIInterface)}
|
||||
/>
|
||||
<TextSetting
|
||||
settingName={t('settings.webui.electron_path.label.title')}
|
||||
dialogDescription={t('settings.webui.electron_path.label.description')}
|
||||
value={webUISettings?.electronPath}
|
||||
handleChange={(path) => updateSetting('electronPath', path)}
|
||||
/>
|
||||
<SelectSetting<WebUiChannel>
|
||||
settingName={t('settings.webui.channel.label.title')}
|
||||
value={webUISettings?.webUIChannel}
|
||||
defaultValue={WebUiChannel.Stable}
|
||||
values={CHANNEL_SELECT_VALUES}
|
||||
handleChange={(channel) => updateSetting('webUIChannel', channel)}
|
||||
disabled={!isDefaultWebUI}
|
||||
/>
|
||||
<WebUIUpdateIntervalSetting disabled={!isDefaultWebUI} />
|
||||
</List>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"uiVersion": "PREVIEW",
|
||||
"serverVersion": "r1410"
|
||||
"serverVersion": "r1427"
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user