Respect user preference for type-to-create in image/scene multi-select form (#6376)

This commit is contained in:
ghuds540
2025-12-10 16:22:29 -05:00
committed by GitHub
parent ba0102f2a6
commit a4816b4cc9
9 changed files with 140 additions and 26 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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),
}
}

View File

@@ -105,4 +105,5 @@ type ConfigDisableDropdownCreate struct {
Tag bool `json:"tag"`
Studio bool `json:"studio"`
Movie bool `json:"movie"`
Gallery bool `json:"gallery"`
}

View File

@@ -107,6 +107,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
tag
studio
movie
gallery
}
handyKey
funscriptOffset

View File

@@ -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<Gallery> & 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 <reactSelectComponents.SingleValue {...thisOptionProps} />;
};
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 (
<FilterSelectComponent<Gallery, boolean>
{...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(

View File

@@ -735,6 +735,19 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
})
}
/>
<BooleanSetting
id="disableDropdownCreate_gallery"
headingID="gallery"
checked={iface.disableDropdownCreate?.gallery ?? undefined}
onChange={(v) =>
saveInterface({
disableDropdownCreate: {
...iface.disableDropdownCreate,
gallery: v,
},
})
}
/>
</div>
<NumberSetting
id="max_options_shown"

View File

@@ -8,6 +8,10 @@ import {
GalleryIDSelect,
excludeFileBasedGalleries,
} from "../Galleries/GallerySelect";
import { PerformerIDSelect } from "../Performers/PerformerSelect";
import { StudioIDSelect } from "../Studios/StudioSelect";
import { TagIDSelect } from "../Tags/TagSelect";
import { GroupIDSelect } from "../Groups/GroupSelect";
interface IMultiSetProps {
type: "performers" | "studios" | "tags" | "groups" | "galleries";
@@ -27,32 +31,77 @@ const Select: React.FC<IMultiSetProps> = (props) => {
props.onUpdate(items.map((i) => i.id));
}
if (type === "galleries") {
return (
<GalleryIDSelect
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
// exclude file-based galleries when setting galleries
extraCriteria={excludeFileBasedGalleries}
menuPortalTarget={props.menuPortalTarget}
/>
);
switch (type) {
case "performers":
return (
<PerformerIDSelect
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
menuPortalTarget={props.menuPortalTarget}
/>
);
case "studios":
return (
<StudioIDSelect
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
menuPortalTarget={props.menuPortalTarget}
/>
);
case "tags":
return (
<TagIDSelect
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
menuPortalTarget={props.menuPortalTarget}
/>
);
case "groups":
return (
<GroupIDSelect
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
menuPortalTarget={props.menuPortalTarget}
/>
);
case "galleries":
return (
<GalleryIDSelect
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
// exclude file-based galleries when setting galleries
extraCriteria={excludeFileBasedGalleries}
menuPortalTarget={props.menuPortalTarget}
/>
);
default:
return (
<FilterSelect
type={type}
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
menuPortalTarget={props.menuPortalTarget}
/>
);
}
return (
<FilterSelect
type={type}
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
menuPortalTarget={props.menuPortalTarget}
/>
);
};
function getModeText(intl: IntlShape, mode: GQL.BulkUpdateIdMode) {

View File

@@ -385,7 +385,7 @@ export const FilterSelect: React.FC<IFilterProps & ITypeProps> = (props) => {
case "groups":
return <GroupSelect {...props} creatable={false} />;
case "galleries":
return <GallerySelect {...props} />;
return <GallerySelect {...props} creatable={false} />;
default:
return <TagSelect {...props} creatable={false} />;
}