From 96fce90cc320744950965473cf574d8bb6cc00fa Mon Sep 17 00:00:00 2001 From: 7dJx1qP <38586902+7dJx1qP@users.noreply.github.com> Date: Thu, 28 Oct 2021 01:45:44 -0400 Subject: [PATCH] Add delete file and generated files by default config options (#1852) * add delete file and generated files by default config options * add alert message with files to be deleted Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com> --- graphql/documents/data/config.graphql | 3 + graphql/schema/types/config.graphql | 10 ++ pkg/api/resolver_mutation_configure.go | 8 ++ pkg/api/resolver_query_configuration.go | 6 +- pkg/manager/config/config.go | 17 +++ .../components/Changelog/versions/v0110.md | 2 + .../Galleries/DeleteGalleriesDialog.tsx | 60 ++++++++++- .../Galleries/GalleryDetails/Gallery.tsx | 2 +- .../components/Images/DeleteImagesDialog.tsx | 50 ++++++++- .../components/Scenes/DeleteScenesDialog.tsx | 50 ++++++++- .../SettingsInterfacePanel.tsx | 101 ++++++++++++++---- ui/v2.5/src/locales/en-GB.json | 13 +++ 12 files changed, 288 insertions(+), 34 deletions(-) diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index 6765167a2..a9cf7b903 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -116,6 +116,9 @@ fragment ConfigDefaultSettingsData on ConfigDefaultSettingsResult { ...IdentifyMetadataOptionsData } } + + deleteFile + deleteGenerated } fragment ConfigData on ConfigResult { diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 3ce410d60..a0eadff42 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -304,10 +304,20 @@ type ConfigScrapingResult { type ConfigDefaultSettingsResult { identify: IdentifyMetadataTaskOptions + + """If true, delete file checkbox will be checked by default""" + deleteFile: Boolean + """If true, delete generated supporting files checkbox will be checked by default""" + deleteGenerated: Boolean } input ConfigDefaultSettingsInput { identify: IdentifyMetadataInput + + """If true, delete file checkbox will be checked by default""" + deleteFile: Boolean + """If true, delete generated files checkbox will be checked by default""" + deleteGenerated: Boolean } """All configuration settings""" diff --git a/pkg/api/resolver_mutation_configure.go b/pkg/api/resolver_mutation_configure.go index cd9e010f2..d3ff51e9b 100644 --- a/pkg/api/resolver_mutation_configure.go +++ b/pkg/api/resolver_mutation_configure.go @@ -359,6 +359,14 @@ func (r *mutationResolver) ConfigureDefaults(ctx context.Context, input models.C c.Set(config.DefaultIdentifySettings, input.Identify) } + if input.DeleteFile != nil { + c.Set(config.DeleteFileDefault, *input.DeleteFile) + } + + if input.DeleteGenerated != nil { + c.Set(config.DeleteGeneratedDefault, *input.DeleteGenerated) + } + if err := c.Write(); err != nil { return makeConfigDefaultsResult(), err } diff --git a/pkg/api/resolver_query_configuration.go b/pkg/api/resolver_query_configuration.go index 2c28476cc..6242f9cab 100644 --- a/pkg/api/resolver_query_configuration.go +++ b/pkg/api/resolver_query_configuration.go @@ -163,8 +163,12 @@ func makeConfigScrapingResult() *models.ConfigScrapingResult { func makeConfigDefaultsResult() *models.ConfigDefaultSettingsResult { config := config.GetInstance() + deleteFileDefault := config.GetDeleteFileDefault() + deleteGeneratedDefault := config.GetDeleteGeneratedDefault() return &models.ConfigDefaultSettingsResult{ - Identify: config.GetDefaultIdentifySettings(), + Identify: config.GetDefaultIdentifySettings(), + DeleteFile: &deleteFileDefault, + DeleteGenerated: &deleteGeneratedDefault, } } diff --git a/pkg/manager/config/config.go b/pkg/manager/config/config.go index aff7fc752..7790c7dfe 100644 --- a/pkg/manager/config/config.go +++ b/pkg/manager/config/config.go @@ -147,6 +147,9 @@ const FunscriptOffset = "funscript_offset" // Default settings const ( DefaultIdentifySettings = "defaults.identify_task" + + DeleteFileDefault = "defaults.delete_file" + DeleteGeneratedDefault = "defaults.delete_generated" ) // Security @@ -880,6 +883,20 @@ func (i *Instance) GetFunscriptOffset() int { return viper.GetInt(FunscriptOffset) } +func (i *Instance) GetDeleteFileDefault() bool { + i.Lock() + defer i.Unlock() + viper.SetDefault(DeleteFileDefault, false) + return viper.GetBool(DeleteFileDefault) +} + +func (i *Instance) GetDeleteGeneratedDefault() bool { + i.Lock() + defer i.Unlock() + viper.SetDefault(DeleteGeneratedDefault, true) + return viper.GetBool(DeleteGeneratedDefault) +} + // GetDefaultIdentifySettings returns the default Identify task settings. // Returns nil if the settings could not be unmarshalled, or if it // has not been set. diff --git a/ui/v2.5/src/components/Changelog/versions/v0110.md b/ui/v2.5/src/components/Changelog/versions/v0110.md index ead8e66b6..23a8828fc 100644 --- a/ui/v2.5/src/components/Changelog/versions/v0110.md +++ b/ui/v2.5/src/components/Changelog/versions/v0110.md @@ -1,4 +1,5 @@ ### ✨ New Features +* Support setting defaults for Delete File and Delete Generated Files in the Interface Settings. ([#1852](https://github.com/stashapp/stash/pull/1852)) * Added Identify task to automatically identify scenes from stash-box/scraper sources. See manual entry for details. ([#1839](https://github.com/stashapp/stash/pull/1839)) * Added support for matching scenes using perceptual hashes when querying stash-box. ([#1858](https://github.com/stashapp/stash/pull/1858)) * Generalised Tagger view to support tagging using supported scene scrapers. ([#1812](https://github.com/stashapp/stash/pull/1812)) @@ -6,6 +7,7 @@ * Added interface options to disable creating performers/studios/tags from dropdown selectors. ([#1814](https://github.com/stashapp/stash/pull/1814)) ### 🎨 Improvements +* Show files being deleted in the Delete dialogs. ([#1852](https://github.com/stashapp/stash/pull/1852)) * Added specific page titles. ([#1831](https://github.com/stashapp/stash/pull/1831)) * Added es-ES language option. ([#1886](https://github.com/stashapp/stash/pull/1886)) * Show pagination at top and bottom of page. ([#1776](https://github.com/stashapp/stash/pull/1776)) diff --git a/ui/v2.5/src/components/Galleries/DeleteGalleriesDialog.tsx b/ui/v2.5/src/components/Galleries/DeleteGalleriesDialog.tsx index c786a2311..c241cec1f 100644 --- a/ui/v2.5/src/components/Galleries/DeleteGalleriesDialog.tsx +++ b/ui/v2.5/src/components/Galleries/DeleteGalleriesDialog.tsx @@ -4,10 +4,11 @@ import { useGalleryDestroy } from "src/core/StashService"; import * as GQL from "src/core/generated-graphql"; import { Modal } from "src/components/Shared"; import { useToast } from "src/hooks"; -import { useIntl } from "react-intl"; +import { ConfigurationContext } from "src/hooks/Config"; +import { FormattedMessage, useIntl } from "react-intl"; interface IDeleteGalleryDialogProps { - selected: Pick[]; + selected: GQL.SlimGalleryDataFragment[]; onClose: (confirmed: boolean) => void; } @@ -31,8 +32,14 @@ export const DeleteGalleriesDialog: React.FC = ( { count: props.selected.length, singularEntity, pluralEntity } ); - const [deleteFile, setDeleteFile] = useState(false); - const [deleteGenerated, setDeleteGenerated] = useState(true); + const { configuration: config } = React.useContext(ConfigurationContext); + + const [deleteFile, setDeleteFile] = useState( + config?.defaults.deleteFile ?? false + ); + const [deleteGenerated, setDeleteGenerated] = useState( + config?.defaults.deleteGenerated ?? true + ); const Toast = useToast(); const [deleteGallery] = useGalleryDestroy(getGalleriesDeleteInput()); @@ -60,6 +67,50 @@ export const DeleteGalleriesDialog: React.FC = ( props.onClose(true); } + function maybeRenderDeleteFileAlert() { + if (!deleteFile) { + return; + } + + const fsGalleries = props.selected.filter((g) => g.path); + if (fsGalleries.length === 0) { + return; + } + + return ( +
+

+ +

+
    + {fsGalleries.slice(0, 5).map((s) => ( +
  • {s.path}
  • + ))} + {fsGalleries.length > 5 && ( + + )} +
  • + +
  • +
+
+ ); + } + return ( = ( isRunning={isDeleting} >

{message}

+ {maybeRenderDeleteFileAlert()}
= ({ gallery }) => { if (isDeleteAlertOpen && gallery) { return ( ); diff --git a/ui/v2.5/src/components/Images/DeleteImagesDialog.tsx b/ui/v2.5/src/components/Images/DeleteImagesDialog.tsx index 7adc9d404..9e04248d7 100644 --- a/ui/v2.5/src/components/Images/DeleteImagesDialog.tsx +++ b/ui/v2.5/src/components/Images/DeleteImagesDialog.tsx @@ -4,7 +4,8 @@ import { useImagesDestroy } from "src/core/StashService"; import * as GQL from "src/core/generated-graphql"; import { Modal } from "src/components/Shared"; import { useToast } from "src/hooks"; -import { useIntl } from "react-intl"; +import { ConfigurationContext } from "src/hooks/Config"; +import { FormattedMessage, useIntl } from "react-intl"; interface IDeleteImageDialogProps { selected: GQL.SlimImageDataFragment[]; @@ -31,8 +32,14 @@ export const DeleteImagesDialog: React.FC = ( { count: props.selected.length, singularEntity, pluralEntity } ); - const [deleteFile, setDeleteFile] = useState(false); - const [deleteGenerated, setDeleteGenerated] = useState(true); + const { configuration: config } = React.useContext(ConfigurationContext); + + const [deleteFile, setDeleteFile] = useState( + config?.defaults.deleteFile ?? false + ); + const [deleteGenerated, setDeleteGenerated] = useState( + config?.defaults.deleteGenerated ?? true + ); const Toast = useToast(); const [deleteImage] = useImagesDestroy(getImagesDeleteInput()); @@ -60,6 +67,42 @@ export const DeleteImagesDialog: React.FC = ( props.onClose(true); } + function maybeRenderDeleteFileAlert() { + if (!deleteFile) { + return; + } + + return ( +
+

+ +

+
    + {props.selected.slice(0, 5).map((s) => ( +
  • {s.path}
  • + ))} + {props.selected.length > 5 && ( + + )} +
+
+ ); + } + return ( = ( isRunning={isDeleting} >

{message}

+ {maybeRenderDeleteFileAlert()} = ( { count: props.selected.length, singularEntity, pluralEntity } ); - const [deleteFile, setDeleteFile] = useState(false); - const [deleteGenerated, setDeleteGenerated] = useState(true); + const { configuration: config } = React.useContext(ConfigurationContext); + + const [deleteFile, setDeleteFile] = useState( + config?.defaults.deleteFile ?? false + ); + const [deleteGenerated, setDeleteGenerated] = useState( + config?.defaults.deleteGenerated ?? true + ); const Toast = useToast(); const [deleteScene] = useScenesDestroy(getScenesDeleteInput()); @@ -60,6 +67,42 @@ export const DeleteScenesDialog: React.FC = ( props.onClose(true); } + function maybeRenderDeleteFileAlert() { + if (!deleteFile) { + return; + } + + return ( +
+

+ +

+
    + {props.selected.slice(0, 5).map((s) => ( +
  • {s.path}
  • + ))} + {props.selected.length > 5 && ( + + )} +
+
+ ); + } + return ( = ( isRunning={isDeleting} >

{message}

+ {maybeRenderDeleteFileAlert()} { const [language, setLanguage] = useState("en"); const [handyKey, setHandyKey] = useState(); const [funscriptOffset, setFunscriptOffset] = useState(0); + const [deleteFileDefault, setDeleteFileDefault] = useState(false); + const [deleteGeneratedDefault, setDeleteGeneratedDefault] = useState( + true + ); const [ disableDropdownCreate, setDisableDropdownCreate, @@ -61,35 +70,51 @@ export const SettingsInterfacePanel: React.FC = () => { disableDropdownCreate, }); + const [updateDefaultsConfig] = useConfigureDefaults(); + useEffect(() => { - const iCfg = config?.configuration?.interface; - setMenuItemIds(iCfg?.menuItems ?? allMenuItems.map((item) => item.id)); - setSoundOnPreview(iCfg?.soundOnPreview ?? true); - setWallShowTitle(iCfg?.wallShowTitle ?? true); - setWallPlayback(iCfg?.wallPlayback ?? "video"); - setMaximumLoopDuration(iCfg?.maximumLoopDuration ?? 0); - setAutostartVideo(iCfg?.autostartVideo ?? false); - setShowStudioAsText(iCfg?.showStudioAsText ?? false); - setCSS(iCfg?.css ?? ""); - setCSSEnabled(iCfg?.cssEnabled ?? false); - setLanguage(iCfg?.language ?? "en-US"); - setSlideshowDelay(iCfg?.slideshowDelay ?? 5000); - setHandyKey(iCfg?.handyKey ?? ""); - setFunscriptOffset(iCfg?.funscriptOffset ?? 0); - setDisableDropdownCreate({ - performer: iCfg?.disabledDropdownCreate.performer, - studio: iCfg?.disabledDropdownCreate.studio, - tag: iCfg?.disabledDropdownCreate.tag, - }); + if (config) { + const { interface: iCfg, defaults } = config.configuration; + setMenuItemIds(iCfg.menuItems ?? allMenuItems.map((item) => item.id)); + setSoundOnPreview(iCfg.soundOnPreview ?? true); + setWallShowTitle(iCfg.wallShowTitle ?? true); + setWallPlayback(iCfg.wallPlayback ?? "video"); + setMaximumLoopDuration(iCfg.maximumLoopDuration ?? 0); + setAutostartVideo(iCfg.autostartVideo ?? false); + setShowStudioAsText(iCfg.showStudioAsText ?? false); + setCSS(iCfg.css ?? ""); + setCSSEnabled(iCfg.cssEnabled ?? false); + setLanguage(iCfg.language ?? "en-US"); + setSlideshowDelay(iCfg.slideshowDelay ?? 5000); + setHandyKey(iCfg.handyKey ?? ""); + setFunscriptOffset(iCfg.funscriptOffset ?? 0); + setDisableDropdownCreate({ + performer: iCfg.disabledDropdownCreate.performer, + studio: iCfg.disabledDropdownCreate.studio, + tag: iCfg.disabledDropdownCreate.tag, + }); + + setDeleteFileDefault(defaults.deleteFile ?? false); + setDeleteGeneratedDefault(defaults.deleteGenerated ?? true); + } }, [config]); async function onSave() { const prevCSS = config?.configuration.interface.css; const prevCSSenabled = config?.configuration.interface.cssEnabled; try { + if (config?.configuration.defaults) { + await updateDefaultsConfig({ + variables: { + input: { + ...withoutTypename(config?.configuration.defaults), + deleteFile: deleteFileDefault, + deleteGenerated: deleteGeneratedDefault, + }, + }, + }); + } const result = await updateInterfaceConfig(); - // eslint-disable-next-line no-console - console.log(result); // Force refetch of custom css if it was changed if ( @@ -389,6 +414,38 @@ export const SettingsInterfacePanel: React.FC = () => { + +
+ {intl.formatMessage({ id: "config.ui.delete_options.heading" })} +
+ { + setDeleteFileDefault(!deleteFileDefault); + }} + /> + { + setDeleteGeneratedDefault(!deleteGeneratedDefault); + }} + /> + + {intl.formatMessage({ + id: "config.ui.delete_options.description", + })} + +
+