mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Advanced settings (#4378)
* Add advanced settings mode toggle * Add advanced settings
This commit is contained in:
@@ -12,6 +12,7 @@ import { withoutTypename } from "src/utils/data";
|
||||
import { GenerateOptions } from "../Settings/Tasks/GenerateOptions";
|
||||
import { SettingSection } from "../Settings/SettingSection";
|
||||
import { faCogs, faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { SettingsContext } from "../Settings/context";
|
||||
|
||||
interface ISceneGenerateDialog {
|
||||
selectedIds?: string[];
|
||||
@@ -196,13 +197,15 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
>
|
||||
<Form>
|
||||
{selectionStatus}
|
||||
<SettingSection>
|
||||
<GenerateOptions
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
selection
|
||||
/>
|
||||
</SettingSection>
|
||||
<SettingsContext>
|
||||
<SettingSection>
|
||||
<GenerateOptions
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
selection
|
||||
/>
|
||||
</SettingSection>
|
||||
</SettingsContext>
|
||||
</Form>
|
||||
</ModalComponent>
|
||||
);
|
||||
|
||||
@@ -5,9 +5,11 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { Icon } from "../Shared/Icon";
|
||||
import { StringListInput } from "../Shared/StringListInput";
|
||||
import { PatchComponent } from "src/pluginApi";
|
||||
import { useSettings } from "./context";
|
||||
|
||||
interface ISetting {
|
||||
id?: string;
|
||||
advanced?: boolean;
|
||||
className?: string;
|
||||
heading?: React.ReactNode;
|
||||
headingID?: string;
|
||||
@@ -32,8 +34,11 @@ export const Setting: React.FC<PropsWithChildren<ISetting>> = PatchComponent(
|
||||
tooltipID,
|
||||
onClick,
|
||||
disabled,
|
||||
advanced,
|
||||
} = props;
|
||||
|
||||
const { advancedMode } = useSettings();
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
function renderHeading() {
|
||||
@@ -61,6 +66,8 @@ export const Setting: React.FC<PropsWithChildren<ISetting>> = PatchComponent(
|
||||
: undefined;
|
||||
const disabledClassName = disabled ? "disabled" : "";
|
||||
|
||||
if (advanced && !advancedMode) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`setting ${className ?? ""} ${disabledClassName}`}
|
||||
@@ -172,9 +179,15 @@ export const SelectSetting: React.FC<PropsWithChildren<ISelectSetting>> = ({
|
||||
value,
|
||||
children,
|
||||
onChange,
|
||||
advanced,
|
||||
}) => {
|
||||
return (
|
||||
<Setting headingID={headingID} subHeadingID={subHeadingID} id={id}>
|
||||
<Setting
|
||||
advanced={advanced}
|
||||
headingID={headingID}
|
||||
subHeadingID={subHeadingID}
|
||||
id={id}
|
||||
>
|
||||
<Form.Control
|
||||
className="input-control"
|
||||
as="select"
|
||||
@@ -346,8 +359,12 @@ export const ModalSetting = <T extends {}>(props: IModalSetting<T>) => {
|
||||
buttonTextID,
|
||||
modalProps,
|
||||
disabled,
|
||||
advanced,
|
||||
} = props;
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const { advancedMode } = useSettings();
|
||||
|
||||
if (advanced && !advancedMode) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { Card } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
import { useSettings } from "./context";
|
||||
|
||||
interface ISettingGroup {
|
||||
id?: string;
|
||||
headingID?: string;
|
||||
subHeadingID?: string;
|
||||
advanced?: boolean;
|
||||
}
|
||||
|
||||
export const SettingSection: React.FC<PropsWithChildren<ISettingGroup>> = ({
|
||||
@@ -13,8 +15,12 @@ export const SettingSection: React.FC<PropsWithChildren<ISettingGroup>> = ({
|
||||
children,
|
||||
headingID,
|
||||
subHeadingID,
|
||||
advanced,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { advancedMode } = useSettings();
|
||||
|
||||
if (advanced && !advancedMode) return null;
|
||||
|
||||
return (
|
||||
<div className="setting-section" id={id}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Tab, Nav, Row, Col } from "react-bootstrap";
|
||||
import { Redirect, RouteComponentProps } from "react-router-dom";
|
||||
import { Tab, Nav, Row, Col, Form } from "react-bootstrap";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import { LinkContainer } from "react-router-bootstrap";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Helmet } from "react-helmet";
|
||||
@@ -14,7 +14,7 @@ import { SettingsPluginsPanel } from "./SettingsPluginsPanel";
|
||||
import { SettingsScrapingPanel } from "./SettingsScrapingPanel";
|
||||
import { SettingsToolsPanel } from "./SettingsToolsPanel";
|
||||
import { SettingsServicesPanel } from "./SettingsServicesPanel";
|
||||
import { SettingsContext } from "./context";
|
||||
import { SettingsContext, useSettings } from "./context";
|
||||
import { SettingsLibraryPanel } from "./SettingsLibraryPanel";
|
||||
import { SettingsSecurityPanel } from "./SettingsSecurityPanel";
|
||||
import Changelog from "../Changelog/Changelog";
|
||||
@@ -41,9 +41,11 @@ function isTabKey(tab: string | null): tab is TabKey {
|
||||
return validTabs.includes(tab as TabKey);
|
||||
}
|
||||
|
||||
const Settings: React.FC<RouteComponentProps> = ({ location }) => {
|
||||
const SettingTabs: React.FC = () => {
|
||||
const tab = new URLSearchParams(location.search).get("tab");
|
||||
|
||||
const { advancedMode, setAdvancedMode } = useSettings();
|
||||
|
||||
const titleProps = useTitleProps({ id: "settings" });
|
||||
|
||||
if (!isTabKey(tab)) {
|
||||
@@ -147,6 +149,18 @@ const Settings: React.FC<RouteComponentProps> = ({ location }) => {
|
||||
</Nav.Link>
|
||||
</LinkContainer>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<div className="advanced-switch">
|
||||
<Form.Label htmlFor="advanced-settings">
|
||||
<FormattedMessage id="config.advanced_mode" />
|
||||
</Form.Label>
|
||||
<Form.Switch
|
||||
id="advanced-settings"
|
||||
checked={advancedMode}
|
||||
onChange={() => setAdvancedMode(!advancedMode)}
|
||||
/>
|
||||
</div>
|
||||
</Nav.Item>
|
||||
<hr className="d-sm-none" />
|
||||
</Nav>
|
||||
</Col>
|
||||
@@ -156,50 +170,56 @@ const Settings: React.FC<RouteComponentProps> = ({ location }) => {
|
||||
md={{ offset: 3 }}
|
||||
xl={{ offset: 2 }}
|
||||
>
|
||||
<SettingsContext>
|
||||
<Tab.Content className="mx-auto">
|
||||
<Tab.Pane eventKey="library">
|
||||
<SettingsLibraryPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="interface">
|
||||
<SettingsInterfacePanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="security">
|
||||
<SettingsSecurityPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="tasks">
|
||||
<SettingsTasksPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="services" unmountOnExit>
|
||||
<SettingsServicesPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="tools" unmountOnExit>
|
||||
<SettingsToolsPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="metadata-providers" unmountOnExit>
|
||||
<SettingsScrapingPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="system">
|
||||
<SettingsConfigurationPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="plugins" unmountOnExit>
|
||||
<SettingsPluginsPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="logs" unmountOnExit>
|
||||
<SettingsLogsPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="changelog" unmountOnExit>
|
||||
<Changelog />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="about" unmountOnExit>
|
||||
<SettingsAboutPanel />
|
||||
</Tab.Pane>
|
||||
</Tab.Content>
|
||||
</SettingsContext>
|
||||
<Tab.Content className="mx-auto">
|
||||
<Tab.Pane eventKey="library">
|
||||
<SettingsLibraryPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="interface">
|
||||
<SettingsInterfacePanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="security">
|
||||
<SettingsSecurityPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="tasks">
|
||||
<SettingsTasksPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="services" unmountOnExit>
|
||||
<SettingsServicesPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="tools" unmountOnExit>
|
||||
<SettingsToolsPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="metadata-providers" unmountOnExit>
|
||||
<SettingsScrapingPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="system">
|
||||
<SettingsConfigurationPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="plugins" unmountOnExit>
|
||||
<SettingsPluginsPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="logs" unmountOnExit>
|
||||
<SettingsLogsPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="changelog" unmountOnExit>
|
||||
<Changelog />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="about" unmountOnExit>
|
||||
<SettingsAboutPanel />
|
||||
</Tab.Pane>
|
||||
</Tab.Content>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export const Settings: React.FC = () => {
|
||||
return (
|
||||
<SettingsContext>
|
||||
<SettingTabs />
|
||||
</SettingsContext>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -240,6 +240,7 @@ export const SettingsInterfacePanel: React.FC = () => {
|
||||
/>
|
||||
|
||||
<SelectSetting
|
||||
advanced
|
||||
id="wall-preview"
|
||||
headingID="config.ui.preview_type.heading"
|
||||
subHeadingID="config.ui.preview_type.description"
|
||||
|
||||
@@ -210,7 +210,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
/>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection headingID="config.general.hashing">
|
||||
<SettingSection advanced headingID="config.general.hashing">
|
||||
<BooleanSetting
|
||||
id="calculate-md5-and-ohash"
|
||||
headingID="config.general.calculate_md5_and_ohash_label"
|
||||
@@ -240,6 +240,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
|
||||
<SettingSection headingID="config.system.transcoding">
|
||||
<SelectSetting
|
||||
advanced
|
||||
id="transcode-size"
|
||||
headingID="config.general.maximum_transcode_size_head"
|
||||
subHeadingID="config.general.maximum_transcode_size_desc"
|
||||
@@ -282,6 +283,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
/>
|
||||
|
||||
<StringListSetting
|
||||
advanced
|
||||
id="transcode-input-args"
|
||||
headingID="config.general.ffmpeg.transcode.input_args.heading"
|
||||
subHeadingID="config.general.ffmpeg.transcode.input_args.desc"
|
||||
@@ -289,6 +291,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
value={general.transcodeInputArgs ?? []}
|
||||
/>
|
||||
<StringListSetting
|
||||
advanced
|
||||
id="transcode-output-args"
|
||||
headingID="config.general.ffmpeg.transcode.output_args.heading"
|
||||
subHeadingID="config.general.ffmpeg.transcode.output_args.desc"
|
||||
@@ -297,6 +300,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
/>
|
||||
|
||||
<StringListSetting
|
||||
advanced
|
||||
id="live-transcode-input-args"
|
||||
headingID="config.general.ffmpeg.live_transcode.input_args.heading"
|
||||
subHeadingID="config.general.ffmpeg.live_transcode.input_args.desc"
|
||||
@@ -304,6 +308,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
value={general.liveTranscodeInputArgs ?? []}
|
||||
/>
|
||||
<StringListSetting
|
||||
advanced
|
||||
id="live-transcode-output-args"
|
||||
headingID="config.general.ffmpeg.live_transcode.output_args.heading"
|
||||
subHeadingID="config.general.ffmpeg.live_transcode.output_args.desc"
|
||||
|
||||
@@ -583,6 +583,7 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
|
||||
|
||||
<SettingSection headingID="config.tasks.migrations">
|
||||
<Setting
|
||||
advanced
|
||||
headingID="actions.rename_gen_files"
|
||||
subHeadingID="config.tasks.migrate_hash_files"
|
||||
>
|
||||
|
||||
@@ -40,6 +40,7 @@ export const GenerateOptions: React.FC<IGenerateOptions> = ({
|
||||
onChange={(v) => setOptions({ previews: v })}
|
||||
/>
|
||||
<BooleanSetting
|
||||
advanced
|
||||
className="sub-setting"
|
||||
id="image-preview-task"
|
||||
checked={options.imagePreviews ?? false}
|
||||
@@ -88,6 +89,7 @@ export const GenerateOptions: React.FC<IGenerateOptions> = ({
|
||||
onChange={(v) => setOptions({ markers: v })}
|
||||
/>
|
||||
<BooleanSetting
|
||||
advanced
|
||||
id="marker-image-preview-task"
|
||||
className="sub-setting"
|
||||
checked={options.markerImagePreviews ?? false}
|
||||
@@ -101,6 +103,7 @@ export const GenerateOptions: React.FC<IGenerateOptions> = ({
|
||||
}
|
||||
/>
|
||||
<BooleanSetting
|
||||
advanced
|
||||
id="marker-screenshot-task"
|
||||
className="sub-setting"
|
||||
checked={options.markerScreenshots ?? false}
|
||||
@@ -111,6 +114,7 @@ export const GenerateOptions: React.FC<IGenerateOptions> = ({
|
||||
/>
|
||||
|
||||
<BooleanSetting
|
||||
advanced
|
||||
id="transcode-task"
|
||||
checked={options.transcodes ?? false}
|
||||
headingID="dialogs.scene_gen.transcodes"
|
||||
@@ -119,6 +123,7 @@ export const GenerateOptions: React.FC<IGenerateOptions> = ({
|
||||
/>
|
||||
{selection ? (
|
||||
<BooleanSetting
|
||||
advanced
|
||||
id="force-transcode"
|
||||
className="sub-setting"
|
||||
checked={options.forceTranscodes ?? false}
|
||||
|
||||
@@ -327,7 +327,7 @@ export const LibraryTasks: React.FC = () => {
|
||||
</SettingGroup>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection>
|
||||
<SettingSection advanced>
|
||||
<Setting
|
||||
heading={
|
||||
<>
|
||||
@@ -349,7 +349,7 @@ export const LibraryTasks: React.FC = () => {
|
||||
</Setting>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection>
|
||||
<SettingSection advanced>
|
||||
<SettingGroup
|
||||
settingProps={{
|
||||
heading: (
|
||||
|
||||
@@ -41,6 +41,7 @@ export const ScanOptions: React.FC<IScanOptions> = ({
|
||||
onChange={(v) => setOptions({ scanGeneratePreviews: v })}
|
||||
/>
|
||||
<BooleanSetting
|
||||
advanced
|
||||
id="scan-generate-image-previews"
|
||||
className="sub-setting"
|
||||
headingID="config.tasks.generate_previews_during_scan"
|
||||
|
||||
@@ -34,6 +34,8 @@ export interface ISettingsContextState {
|
||||
ui: IUIConfig;
|
||||
plugins: PluginSettings;
|
||||
|
||||
advancedMode: boolean;
|
||||
|
||||
// apikey isn't directly settable, so expose it here
|
||||
apiKey: string;
|
||||
|
||||
@@ -44,6 +46,7 @@ export interface ISettingsContextState {
|
||||
saveDLNA: (input: Partial<GQL.ConfigDlnaInput>) => void;
|
||||
saveUI: (input: Partial<IUIConfig>) => void;
|
||||
savePluginSettings: (pluginID: string, input: {}) => void;
|
||||
setAdvancedMode: (value: boolean) => void;
|
||||
|
||||
refetch: () => void;
|
||||
}
|
||||
@@ -91,7 +94,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
||||
const [pendingDLNA, setPendingDLNA] = useState<GQL.ConfigDlnaInput>();
|
||||
const [updateDLNAConfig] = useConfigureDLNA();
|
||||
|
||||
const [ui, setUI] = useState({});
|
||||
const [ui, setUI] = useState<IUIConfig>({});
|
||||
const [pendingUI, setPendingUI] = useState<{}>();
|
||||
const [updateUIConfig] = useConfigureUI();
|
||||
|
||||
@@ -430,6 +433,12 @@ export const SettingsContext: React.FC = ({ children }) => {
|
||||
});
|
||||
}
|
||||
|
||||
function setAdvancedMode(value: boolean) {
|
||||
saveUI({
|
||||
advancedMode: value,
|
||||
});
|
||||
}
|
||||
|
||||
// saves the configuration if no further changes are made after a half second
|
||||
const savePluginConfig = useDebounce(async (input: PluginSettings) => {
|
||||
try {
|
||||
@@ -536,6 +545,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
||||
dlna,
|
||||
ui,
|
||||
plugins,
|
||||
advancedMode: ui.advancedMode ?? false,
|
||||
saveGeneral,
|
||||
saveInterface,
|
||||
saveDefaults,
|
||||
@@ -544,6 +554,7 @@ export const SettingsContext: React.FC = ({ children }) => {
|
||||
saveUI,
|
||||
refetch,
|
||||
savePluginSettings,
|
||||
setAdvancedMode,
|
||||
}}
|
||||
>
|
||||
{maybeRenderLoadingIndicator()}
|
||||
|
||||
@@ -410,3 +410,18 @@
|
||||
.empty-queue-message {
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.advanced-switch {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
.form-label {
|
||||
color: $text-muted;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.custom-switch {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ export interface IUIConfig {
|
||||
|
||||
vrTag?: string;
|
||||
pinnedFilters?: PinnedFilters;
|
||||
|
||||
advancedMode?: boolean;
|
||||
}
|
||||
|
||||
interface ISavedFilterRowBroken extends ISavedFilterRow {
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
"stash_wiki": "Stash {url} page",
|
||||
"version": "Version"
|
||||
},
|
||||
"advanced_mode": "Advanced Mode",
|
||||
"application_paths": {
|
||||
"heading": "Application Paths"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user