From 4b84ec0d85c29a7a4bfc4ba96da4660b38ccb397 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:42:04 +1100 Subject: [PATCH] Support setting galleries in multiple images (#4608) --- .../components/Galleries/GallerySelect.tsx | 29 +++-- .../components/Images/EditImagesDialog.tsx | 105 +++++++++--------- .../Images/ImageDetails/ImageEditPanel.tsx | 15 +-- ui/v2.5/src/components/Shared/MultiSet.tsx | 52 ++++++--- ui/v2.5/src/utils/bulkUpdate.ts | 70 ++++-------- 5 files changed, 137 insertions(+), 134 deletions(-) diff --git a/ui/v2.5/src/components/Galleries/GallerySelect.tsx b/ui/v2.5/src/components/Galleries/GallerySelect.tsx index b2d0cd09b30..5ccd0d5e41d 100644 --- a/ui/v2.5/src/components/Galleries/GallerySelect.tsx +++ b/ui/v2.5/src/components/Galleries/GallerySelect.tsx @@ -32,6 +32,7 @@ import { Criterion, CriterionValue, } from "src/models/list-filter/criteria/criterion"; +import { PathCriterion } from "src/models/list-filter/criteria/path"; export type Gallery = Pick & { files: Pick[]; @@ -39,14 +40,14 @@ export type Gallery = Pick & { }; type Option = SelectOption; +type ExtraGalleryProps = { + hoverPlacement?: Placement; + excludeIds?: string[]; + extraCriteria?: Array>; +}; + const _GallerySelect: React.FC< - IFilterProps & - IFilterValueProps & { - hoverPlacement?: Placement; - excludeIds?: string[]; - } & { - extraCriteria?: Array>; - } + IFilterProps & IFilterValueProps & ExtraGalleryProps > = (props) => { const { configuration } = React.useContext(ConfigurationContext); const intl = useIntl(); @@ -187,9 +188,9 @@ const _GallerySelect: React.FC< export const GallerySelect = PatchComponent("GallerySelect", _GallerySelect); -const _GalleryIDSelect: React.FC> = ( - props -) => { +const _GalleryIDSelect: React.FC< + IFilterProps & IFilterIDProps & ExtraGalleryProps +> = (props) => { const { ids, onSelect: onSelectValues } = props; const [values, setValues] = useState([]); @@ -238,3 +239,11 @@ export const GalleryIDSelect = PatchComponent( "GalleryIDSelect", _GalleryIDSelect ); + +function getExcludeFilebaseGalleriesFilter() { + const ret = new PathCriterion(); + ret.modifier = GQL.CriterionModifier.IsNull; + return ret; +} + +export const excludeFileBasedGalleries = [getExcludeFilebaseGalleriesFilter()]; diff --git a/ui/v2.5/src/components/Images/EditImagesDialog.tsx b/ui/v2.5/src/components/Images/EditImagesDialog.tsx index f024a5f06cb..9c2459b083c 100644 --- a/ui/v2.5/src/components/Images/EditImagesDialog.tsx +++ b/ui/v2.5/src/components/Images/EditImagesDialog.tsx @@ -11,6 +11,7 @@ import * as FormUtils from "src/utils/form"; import { MultiSet } from "../Shared/MultiSet"; import { RatingSystem } from "../Shared/Rating/RatingSystem"; import { + getAggregateGalleryIds, getAggregateInputIDs, getAggregateInputValue, getAggregatePerformerIds, @@ -36,11 +37,19 @@ export const EditImagesDialog: React.FC = ( React.useState(GQL.BulkUpdateIdMode.Add); const [performerIds, setPerformerIds] = useState(); const [existingPerformerIds, setExistingPerformerIds] = useState(); + const [tagMode, setTagMode] = React.useState( GQL.BulkUpdateIdMode.Add ); const [tagIds, setTagIds] = useState(); const [existingTagIds, setExistingTagIds] = useState(); + + const [galleryMode, setGalleryMode] = React.useState( + GQL.BulkUpdateIdMode.Add + ); + const [galleryIds, setGalleryIds] = useState(); + const [existingGalleryIds, setExistingGalleryIds] = useState(); + const [organized, setOrganized] = useState(); const [updateImages] = useBulkImageUpdate(); @@ -56,6 +65,7 @@ export const EditImagesDialog: React.FC = ( const aggregateStudioId = getAggregateStudioId(props.selected); const aggregatePerformerIds = getAggregatePerformerIds(props.selected); const aggregateTagIds = getAggregateTagIds(props.selected); + const aggregateGalleryIds = getAggregateGalleryIds(props.selected); const imageInput: GQL.BulkImageUpdateInput = { ids: props.selected.map((image) => { @@ -72,6 +82,11 @@ export const EditImagesDialog: React.FC = ( aggregatePerformerIds ); imageInput.tag_ids = getAggregateInputIDs(tagMode, tagIds, aggregateTagIds); + imageInput.gallery_ids = getAggregateInputIDs( + galleryMode, + galleryIds, + aggregateGalleryIds + ); if (organized !== undefined) { imageInput.organized = organized; @@ -107,6 +122,7 @@ export const EditImagesDialog: React.FC = ( let updateStudioID: string | undefined; let updatePerformerIds: string[] = []; let updateTagIds: string[] = []; + let updateGalleryIds: string[] = []; let updateOrganized: boolean | undefined; let first = true; @@ -117,12 +133,14 @@ export const EditImagesDialog: React.FC = ( .map((p) => p.id) .sort(); const imageTagIDs = (image.tags ?? []).map((p) => p.id).sort(); + const imageGalleryIDs = (image.galleries ?? []).map((p) => p.id).sort(); if (first) { updateRating = imageRating ?? undefined; updateStudioID = imageStudioID; updatePerformerIds = imagePerformerIDs; updateTagIds = imageTagIDs; + updateGalleryIds = imageGalleryIDs; updateOrganized = image.organized; first = false; } else { @@ -138,6 +156,9 @@ export const EditImagesDialog: React.FC = ( if (!isEqual(imageTagIDs, updateTagIds)) { updateTagIds = []; } + if (!isEqual(imageGalleryIDs, updateGalleryIds)) { + updateGalleryIds = []; + } if (image.organized !== updateOrganized) { updateOrganized = undefined; } @@ -148,6 +169,7 @@ export const EditImagesDialog: React.FC = ( setStudioId(updateStudioID); setExistingPerformerIds(updatePerformerIds); setExistingTagIds(updateTagIds); + setExistingGalleryIds(updateGalleryIds); setOrganized(updateOrganized); }, [props.selected, performerMode, tagMode]); @@ -157,54 +179,6 @@ export const EditImagesDialog: React.FC = ( } }, [organized, checkboxRef]); - function renderMultiSelect( - type: "performers" | "tags", - ids: string[] | undefined - ) { - let mode = GQL.BulkUpdateIdMode.Add; - let existingIds: string[] | undefined = []; - switch (type) { - case "performers": - mode = performerMode; - existingIds = existingPerformerIds; - break; - case "tags": - mode = tagMode; - existingIds = existingTagIds; - break; - } - - return ( - { - switch (type) { - case "performers": - setPerformerIds(itemIDs); - break; - case "tags": - setTagIds(itemIDs); - break; - } - }} - onSetMode={(newMode) => { - switch (type) { - case "performers": - setPerformerMode(newMode); - break; - case "tags": - setTagMode(newMode); - break; - } - }} - existingIds={existingIds ?? []} - ids={ids ?? []} - mode={mode} - /> - ); - } - function cycleOrganized() { if (organized) { setOrganized(undefined); @@ -271,14 +245,45 @@ export const EditImagesDialog: React.FC = ( - {renderMultiSelect("performers", performerIds)} + setPerformerIds(itemIDs)} + onSetMode={(newMode) => setPerformerMode(newMode)} + existingIds={existingPerformerIds ?? []} + ids={performerIds ?? []} + mode={performerMode} + /> - {renderMultiSelect("tags", tagIds)} + setTagIds(itemIDs)} + onSetMode={(newMode) => setTagMode(newMode)} + existingIds={existingTagIds ?? []} + ids={tagIds ?? []} + mode={tagMode} + /> + + + + + + + setGalleryIds(itemIDs)} + onSetMode={(newMode) => setGalleryMode(newMode)} + existingIds={existingGalleryIds ?? []} + ids={galleryIds ?? []} + mode={galleryMode} + /> diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx index de93cb7f045..0d55c734031 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx @@ -24,8 +24,11 @@ import { formikUtils } from "src/utils/form"; import { Tag, TagSelect } from "src/components/Tags/TagSelect"; import { Studio, StudioSelect } from "src/components/Studios/StudioSelect"; import { galleryTitle } from "src/core/galleries"; -import { Gallery, GallerySelect } from "src/components/Galleries/GallerySelect"; -import { PathCriterion } from "src/models/list-filter/criteria/path"; +import { + Gallery, + GallerySelect, + excludeFileBasedGalleries, +} from "src/components/Galleries/GallerySelect"; interface IProps { image: GQL.ImageDataFragment; @@ -34,14 +37,6 @@ interface IProps { onDelete: () => void; } -function getExcludeFilebaseGalleriesFilter() { - const ret = new PathCriterion(); - ret.modifier = GQL.CriterionModifier.IsNull; - return ret; -} - -const excludeFileBasedGalleries = [getExcludeFilebaseGalleriesFilter()]; - export const ImageEditPanel: React.FC = ({ image, isVisible, diff --git a/ui/v2.5/src/components/Shared/MultiSet.tsx b/ui/v2.5/src/components/Shared/MultiSet.tsx index c199226831f..f92b57ff313 100644 --- a/ui/v2.5/src/components/Shared/MultiSet.tsx +++ b/ui/v2.5/src/components/Shared/MultiSet.tsx @@ -4,9 +4,13 @@ import { useIntl } from "react-intl"; import * as GQL from "src/core/generated-graphql"; import { Button, ButtonGroup } from "react-bootstrap"; import { FilterSelect, SelectObject } from "./Select"; +import { + GalleryIDSelect, + excludeFileBasedGalleries, +} from "../Galleries/GallerySelect"; interface IMultiSetProps { - type: "performers" | "studios" | "tags" | "movies"; + type: "performers" | "studios" | "tags" | "movies" | "galleries"; existingIds?: string[]; ids?: string[]; mode: GQL.BulkUpdateIdMode; @@ -15,6 +19,39 @@ interface IMultiSetProps { onSetMode: (mode: GQL.BulkUpdateIdMode) => void; } +const Select: React.FC = (props) => { + const { type, disabled } = props; + + function onUpdate(items: SelectObject[]) { + props.onUpdate(items.map((i) => i.id)); + } + + if (type === "galleries") { + return ( + + ); + } + + return ( + + ); +}; + export const MultiSet: React.FC = (props) => { const intl = useIntl(); const modes = [ @@ -23,10 +60,6 @@ export const MultiSet: React.FC = (props) => { GQL.BulkUpdateIdMode.Remove, ]; - function onUpdate(items: SelectObject[]) { - props.onUpdate(items.map((i) => i.id)); - } - function getModeText(mode: GQL.BulkUpdateIdMode) { switch (mode) { case GQL.BulkUpdateIdMode.Set: @@ -83,14 +116,7 @@ export const MultiSet: React.FC = (props) => { {modes.map((m) => renderModeButton(m))} - +