From 35488c706fe051c2a317cd21c7528f2b673ac509 Mon Sep 17 00:00:00 2001 From: Gykes Date: Wed, 22 Apr 2026 23:25:01 -0700 Subject: [PATCH] initial work for autotag skip warning --- graphql/schema/types/config.graphql | 6 +++ internal/api/resolver_mutation_configure.go | 2 + internal/api/resolver_query_configuration.go | 3 ++ internal/manager/config/config.go | 6 +++ ui/v2.5/graphql/data/config.graphql | 1 + .../Settings/Tasks/LibraryTasks.tsx | 25 ++++++++++- .../Shared/AutoTagConfirmDialog.tsx | 43 ++++++++++++++++++- .../components/Shared/DetailsEditNavbar.tsx | 17 +++++++- ui/v2.5/src/docs/en/Manual/AutoTagging.md | 4 ++ ui/v2.5/src/locales/en-GB.json | 7 ++- 10 files changed, 108 insertions(+), 6 deletions(-) diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 5ab7fdfea..99caf9614 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -428,6 +428,9 @@ input ConfigInterfaceInput { "Set to true to disable creating new objects via the dropdown menus" disableDropdownCreate: ConfigDisableDropdownCreateInput + "Set to true to skip the auto tag confirmation warning" + disableAutoTagWarning: Boolean + "Handy Connection Key" handyKey: String "Funscript Time Offset" @@ -505,6 +508,9 @@ type ConfigInterfaceResult { "Fields are true if creating via dropdown menus are disabled" disableDropdownCreate: ConfigDisableDropdownCreate! + "True if the auto tag confirmation warning should be skipped" + disableAutoTagWarning: Boolean + "Handy Connection Key" handyKey: String "Funscript Time Offset" diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index 3df1c9114..747e8f9e9 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -538,6 +538,8 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI r.setConfigBool(config.DisableDropdownCreateGallery, ddc.Gallery) } + r.setConfigBool(config.DisableAutoTagWarning, input.DisableAutoTagWarning) + r.setConfigString(config.HandyKey, input.HandyKey) r.setConfigInt(config.FunscriptOffset, input.FunscriptOffset) r.setConfigBool(config.UseStashHostedFunscript, input.UseStashHostedFunscript) diff --git a/internal/api/resolver_query_configuration.go b/internal/api/resolver_query_configuration.go index cf2c0e3cc..6bd83ca42 100644 --- a/internal/api/resolver_query_configuration.go +++ b/internal/api/resolver_query_configuration.go @@ -168,6 +168,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { useStashHostedFunscript := config.GetUseStashHostedFunscript() imageLightboxOptions := config.GetImageLightboxOptions() disableDropdownCreate := config.GetDisableDropdownCreate() + disableAutoTagWarning := config.GetDisableAutoTagWarning() return &ConfigInterfaceResult{ SfwContentMode: config.GetSFWContentMode(), @@ -196,6 +197,8 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult { DisableDropdownCreate: disableDropdownCreate, + DisableAutoTagWarning: &disableAutoTagWarning, + HandyKey: &handyKey, FunscriptOffset: &scriptOffset, UseStashHostedFunscript: &useStashHostedFunscript, diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index 19e263810..5788933e4 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -237,6 +237,8 @@ const ( DisableDropdownCreateMovie = "disable_dropdown_create.movie" DisableDropdownCreateGallery = "disable_dropdown_create.gallery" + DisableAutoTagWarning = "disable_auto_tag_warning" + HandyKey = "handy_key" FunscriptOffset = "funscript_offset" UseStashHostedFunscript = "use_stash_hosted_funscript" @@ -1311,6 +1313,10 @@ func (i *Config) GetShowStudioAsText() bool { return i.getBool(ShowStudioAsText) } +func (i *Config) GetDisableAutoTagWarning() bool { + return i.getBool(DisableAutoTagWarning) +} + func (i *Config) getSlideshowDelay() int { // assume have lock diff --git a/ui/v2.5/graphql/data/config.graphql b/ui/v2.5/graphql/data/config.graphql index ba8215fe3..35b0c2b99 100644 --- a/ui/v2.5/graphql/data/config.graphql +++ b/ui/v2.5/graphql/data/config.graphql @@ -115,6 +115,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult { movie gallery } + disableAutoTagWarning handyKey funscriptOffset useStashHostedFunscript diff --git a/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx b/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx index eace4b7dc..6bf8924ad 100644 --- a/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx +++ b/ui/v2.5/src/components/Settings/Tasks/LibraryTasks.tsx @@ -75,7 +75,13 @@ const AutoTagOptions: React.FC = ({ export const LibraryTasks: React.FC = () => { const intl = useIntl(); const Toast = useToast(); - const { ui, saveUI, loading } = useSettings(); + const { + ui, + saveUI, + interface: iface, + saveInterface, + loading, + } = useSettings(); const { taskDefaults } = ui; @@ -195,6 +201,14 @@ export const LibraryTasks: React.FC = () => { }); } + function onAutoTagClick() { + if (configuration?.interface.disableAutoTagWarning) { + runAutoTag(); + return; + } + setDialogOpen({ autoTagAlert: true }); + } + function renderScanDialog() { if (!dialogOpen.scan) { return; @@ -449,7 +463,7 @@ export const LibraryTasks: React.FC = () => { variant="secondary" type="submit" className="mr-2" - onClick={() => setDialogOpen({ autoTagAlert: true })} + onClick={onAutoTagClick} > … @@ -468,6 +482,13 @@ export const LibraryTasks: React.FC = () => { options={autoTagOptions} setOptions={onSetAutoTagOptions} /> + saveInterface({ disableAutoTagWarning: v })} + /> diff --git a/ui/v2.5/src/components/Shared/AutoTagConfirmDialog.tsx b/ui/v2.5/src/components/Shared/AutoTagConfirmDialog.tsx index 0290c7e07..ece07735f 100644 --- a/ui/v2.5/src/components/Shared/AutoTagConfirmDialog.tsx +++ b/ui/v2.5/src/components/Shared/AutoTagConfirmDialog.tsx @@ -1,6 +1,9 @@ -import React from "react"; +import React, { useContext, useEffect, useState } from "react"; +import { Form } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons"; +import { useConfigureInterface } from "src/core/StashService"; +import { SettingStateContext } from "src/components/Settings/context"; import { ModalComponent } from "./Modal"; import { Icon } from "./Icon"; @@ -31,6 +34,34 @@ export const AutoTagConfirmDialog: React.FC = ({ onCancel, }) => { const intl = useIntl(); + const [dontShowAgain, setDontShowAgain] = useState(false); + const [configureInterface] = useConfigureInterface(); + + useEffect(() => { + if (show) { + setDontShowAgain(false); + } + }, [show]); + // route through SettingsContext when available so the Settings panel's + // local state reflects the change without a page refresh + const settingsContext = useContext(SettingStateContext); + + async function handleConfirm() { + if (dontShowAgain) { + try { + if (settingsContext) { + settingsContext.saveInterface({ disableAutoTagWarning: true }); + } else { + await configureInterface({ + variables: { input: { disableAutoTagWarning: true } }, + }); + } + } catch (e) { + // preference persistence failure must not block the confirmed action + } + } + onConfirm(); + } return ( = ({ accept={{ text: intl.formatMessage({ id: "actions.confirm" }), variant: "danger", - onClick: onConfirm, + onClick: handleConfirm, }} cancel={{ onClick: onCancel, }} > + setDontShowAgain(e.currentTarget.checked)} + label={intl.formatMessage({ + id: "dialogs.dont_show_again", + })} + /> ); }; diff --git a/ui/v2.5/src/components/Shared/DetailsEditNavbar.tsx b/ui/v2.5/src/components/Shared/DetailsEditNavbar.tsx index 159bb9f09..e6196e4a9 100644 --- a/ui/v2.5/src/components/Shared/DetailsEditNavbar.tsx +++ b/ui/v2.5/src/components/Shared/DetailsEditNavbar.tsx @@ -1,6 +1,7 @@ import { Button, Dropdown, Modal, SplitButton } from "react-bootstrap"; import React, { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; +import { useConfigurationContext } from "src/hooks/Config"; import { ImageInput } from "./ImageInput"; import { AutoTagConfirmDialog } from "./AutoTagConfirmDialog"; import cx from "classnames"; @@ -30,9 +31,23 @@ interface IProps { export const DetailsEditNavbar: React.FC = (props: IProps) => { const intl = useIntl(); + const { configuration } = useConfigurationContext(); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); const [isAutoTagAlertOpen, setIsAutoTagAlertOpen] = useState(false); + const skipAutoTagWarning = + configuration?.interface.disableAutoTagWarning ?? false; + + function onAutoTagClick() { + if (skipAutoTagWarning) { + if (props.onAutoTag) { + props.onAutoTag(); + } + return; + } + setIsAutoTagAlertOpen(true); + } + function renderEditButton() { if (props.isNew) return; return ( @@ -116,7 +131,7 @@ export const DetailsEditNavbar: React.FC = (props: IProps) => { diff --git a/ui/v2.5/src/docs/en/Manual/AutoTagging.md b/ui/v2.5/src/docs/en/Manual/AutoTagging.md index 4725020d1..3b2cc6914 100644 --- a/ui/v2.5/src/docs/en/Manual/AutoTagging.md +++ b/ui/v2.5/src/docs/en/Manual/AutoTagging.md @@ -48,3 +48,7 @@ Performers or Tags that have Ignore Auto tag flag added to them will be skipped - **Auto tag:** You can run the Auto tag task on your entire library from the Tasks page. - **Selective auto tag:** You can run the Auto tag task on specific directories from the Tasks page. - **Individual pages:** You can run Auto tag tasks for specific Performers, Studios, and Tags from their respective pages. + +### Skip the confirmation warning + +A confirmation warning is shown before Auto tag runs. If you use Auto tag frequently you can bypass this warning by ticking the **Don't show this warning again** checkbox on the warning dialog, or by enabling **Settings → Tasks → Auto Tag → Skip auto tag warning**. diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 4974c06ca..4327df68c 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -509,7 +509,11 @@ "anonymising_database": "Anonymising database", "auto_tag": { "auto_tagging_all_paths": "Auto tagging all paths", - "auto_tagging_paths": "Auto tagging the following paths" + "auto_tagging_paths": "Auto tagging the following paths", + "skip_warning": { + "description": "Skip the confirmation warning shown before running auto tag.", + "heading": "Skip auto tag warning" + } }, "auto_tag_based_on_filenames": "Auto tag content based on file paths.", "auto_tag_confirm": "This will attempt to match your content against existing metadata.", @@ -1002,6 +1006,7 @@ "delete_object_desc": "Are you sure you want to delete {count, plural, one {this {singularEntity}} other {these {pluralEntity}}}?", "delete_object_overflow": "…and {count} other {count, plural, one {{singularEntity}} other {{pluralEntity}}}.", "delete_object_title": "Delete {count, plural, one {{singularEntity}} other {{pluralEntity}}}", + "dont_show_again": "Don't show this warning again", "dont_show_until_updated": "Don't show until next update", "edit_entity_title": "Edit {count, plural, one {{singularEntity}} other {{pluralEntity}}}", "edit_entity_count_title": "Edit {count} {count, plural, one {{singularEntity}} other {{pluralEntity}}}",