From a8913254c6a95268ff9d3530ffee95bf268a4dd6 Mon Sep 17 00:00:00 2001 From: ghuds540 Date: Wed, 3 Dec 2025 21:48:44 -0500 Subject: [PATCH] Allow creation of galleries from multi-select form --- gqlgen.yml | 3 ++ graphql/schema/types/config.graphql | 2 + internal/api/resolver.go | 4 ++ internal/api/resolver_model_config.go | 4 ++ internal/api/resolver_mutation_configure.go | 1 + internal/manager/config/config.go | 2 + internal/manager/config/ui.go | 1 + ui/v2.5/graphql/data/config.graphql | 1 + .../components/Galleries/GallerySelect.tsx | 45 +++++++++++++++++++ .../SettingsInterfacePanel.tsx | 13 ++++++ 10 files changed, 76 insertions(+) diff --git a/gqlgen.yml b/gqlgen.yml index b949d44dc..f111bf95b 100644 --- a/gqlgen.yml +++ b/gqlgen.yml @@ -66,6 +66,9 @@ models: model: github.com/stashapp/stash/internal/manager/config.ImageLightboxScrollMode ConfigDisableDropdownCreate: model: github.com/stashapp/stash/internal/manager/config.ConfigDisableDropdownCreate + fields: + gallery: + resolver: true ScanMetadataOptions: model: github.com/stashapp/stash/internal/manager/config.ScanMetadataOptions CleanGeneratedInput: diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index e82ea93e2..b6f52091b 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -319,6 +319,7 @@ input ConfigDisableDropdownCreateInput { tag: Boolean studio: Boolean movie: Boolean + gallery: Boolean } enum ImageLightboxDisplayMode { @@ -419,6 +420,7 @@ type ConfigDisableDropdownCreate { tag: Boolean! studio: Boolean! movie: Boolean! + gallery: Boolean! } type ConfigInterfaceResult { diff --git a/internal/api/resolver.go b/internal/api/resolver.go index 061d0e1a9..19e0279c2 100644 --- a/internal/api/resolver.go +++ b/internal/api/resolver.go @@ -110,6 +110,9 @@ func (r *Resolver) Plugin() PluginResolver { func (r *Resolver) ConfigResult() ConfigResultResolver { return &configResultResolver{r} } +func (r *Resolver) ConfigDisableDropdownCreate() ConfigDisableDropdownCreateResolver { + return &configDisableDropdownCreateResolver{r} +} type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } @@ -136,6 +139,7 @@ type folderResolver struct{ *Resolver } type savedFilterResolver struct{ *Resolver } type pluginResolver struct{ *Resolver } type configResultResolver struct{ *Resolver } +type configDisableDropdownCreateResolver struct{ *Resolver } func (r *Resolver) withTxn(ctx context.Context, fn func(ctx context.Context) error) error { return r.repository.WithTxn(ctx, fn) diff --git a/internal/api/resolver_model_config.go b/internal/api/resolver_model_config.go index d02583217..54962fa93 100644 --- a/internal/api/resolver_model_config.go +++ b/internal/api/resolver_model_config.go @@ -6,6 +6,10 @@ import ( "github.com/stashapp/stash/internal/manager/config" ) +func (r *configDisableDropdownCreateResolver) Gallery(ctx context.Context, obj *config.ConfigDisableDropdownCreate) (bool, error) { + return obj.Gallery, nil +} + func (r *configResultResolver) Plugins(ctx context.Context, obj *ConfigResult, include []string) (map[string]map[string]interface{}, error) { if len(include) == 0 { ret := config.GetInstance().GetAllPluginConfiguration() diff --git a/internal/api/resolver_mutation_configure.go b/internal/api/resolver_mutation_configure.go index b39cf373a..daed0b5b7 100644 --- a/internal/api/resolver_mutation_configure.go +++ b/internal/api/resolver_mutation_configure.go @@ -521,6 +521,7 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI r.setConfigBool(config.DisableDropdownCreateStudio, ddc.Studio) r.setConfigBool(config.DisableDropdownCreateTag, ddc.Tag) r.setConfigBool(config.DisableDropdownCreateMovie, ddc.Movie) + r.setConfigBool(config.DisableDropdownCreateGallery, ddc.Gallery) } r.setConfigString(config.HandyKey, input.HandyKey) diff --git a/internal/manager/config/config.go b/internal/manager/config/config.go index 73b9de3ab..2cc3994f4 100644 --- a/internal/manager/config/config.go +++ b/internal/manager/config/config.go @@ -219,6 +219,7 @@ const ( DisableDropdownCreateStudio = "disable_dropdown_create.studio" DisableDropdownCreateTag = "disable_dropdown_create.tag" DisableDropdownCreateMovie = "disable_dropdown_create.movie" + DisableDropdownCreateGallery = "disable_dropdown_create.gallery" HandyKey = "handy_key" FunscriptOffset = "funscript_offset" @@ -1311,6 +1312,7 @@ func (i *Config) GetDisableDropdownCreate() *ConfigDisableDropdownCreate { Studio: i.getBool(DisableDropdownCreateStudio), Tag: i.getBool(DisableDropdownCreateTag), Movie: i.getBool(DisableDropdownCreateMovie), + Gallery: i.getBool(DisableDropdownCreateGallery), } } diff --git a/internal/manager/config/ui.go b/internal/manager/config/ui.go index b7033f193..de769304f 100644 --- a/internal/manager/config/ui.go +++ b/internal/manager/config/ui.go @@ -105,4 +105,5 @@ type ConfigDisableDropdownCreate struct { Tag bool `json:"tag"` Studio bool `json:"studio"` Movie bool `json:"movie"` + Gallery bool `json:"gallery"` } diff --git a/ui/v2.5/graphql/data/config.graphql b/ui/v2.5/graphql/data/config.graphql index 1c3e9dc1b..b65ba21cc 100644 --- a/ui/v2.5/graphql/data/config.graphql +++ b/ui/v2.5/graphql/data/config.graphql @@ -107,6 +107,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult { tag studio movie + gallery } handyKey funscriptOffset diff --git a/ui/v2.5/src/components/Galleries/GallerySelect.tsx b/ui/v2.5/src/components/Galleries/GallerySelect.tsx index c76266cf7..0e02b8cb3 100644 --- a/ui/v2.5/src/components/Galleries/GallerySelect.tsx +++ b/ui/v2.5/src/components/Galleries/GallerySelect.tsx @@ -11,6 +11,7 @@ import * as GQL from "src/core/generated-graphql"; import { queryFindGalleriesForSelect, queryFindGalleriesByIDForSelect, + useGalleryCreate, } from "src/core/StashService"; import { useConfigurationContext } from "src/hooks/Config"; import { useIntl } from "react-intl"; @@ -70,10 +71,14 @@ const gallerySelectSort = PatchFunction( const _GallerySelect: React.FC< IFilterProps & IFilterValueProps & ExtraGalleryProps > = (props) => { + const [createGallery] = useGalleryCreate(); + const { configuration } = useConfigurationContext(); const intl = useIntl(); const maxOptionsShown = configuration?.ui.maxOptionsShown ?? defaultMaxOptionsShown; + const defaultCreatable = + !configuration?.interface.disableDropdownCreate.gallery; const exclude = useMemo(() => props.excludeIds ?? [], [props.excludeIds]); @@ -203,6 +208,42 @@ const _GallerySelect: React.FC< return ; }; + const onCreate = async (name: string) => { + const result = await createGallery({ + variables: { input: { title: name } }, + }); + return { + value: result.data!.galleryCreate!.id, + item: result.data!.galleryCreate!, + message: "Created gallery", + }; + }; + + const getNamedObject = (id: string, name: string): Gallery => { + return { + id, + title: name, + files: [], + folder: null, + }; + }; + + const isValidNewOption = (inputValue: string, options: Gallery[]) => { + if (!inputValue) { + return false; + } + + if ( + options.some((o) => { + return galleryTitle(o).toLowerCase() === inputValue.toLowerCase(); + }) + ) { + return false; + } + + return true; + }; + return ( {...props} @@ -214,12 +255,16 @@ const _GallerySelect: React.FC< props.className )} loadOptions={loadGalleries} + getNamedObject={getNamedObject} + isValidNewOption={isValidNewOption} components={{ Option: GalleryOption, MultiValueLabel: GalleryMultiValueLabel, SingleValue: GalleryValueLabel, }} isMulti={props.isMulti ?? false} + creatable={props.creatable ?? defaultCreatable} + onCreate={onCreate} placeholder={ props.noSelectionString ?? intl.formatMessage( diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx index bbc334a96..7b3f936d3 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx @@ -735,6 +735,19 @@ export const SettingsInterfacePanel: React.FC = PatchComponent( }) } /> + + saveInterface({ + disableDropdownCreate: { + ...iface.disableDropdownCreate, + gallery: v, + }, + }) + } + />