Skip to content

Commit

Permalink
Support setting galleries in multiple images (#4608)
Browse files Browse the repository at this point in the history
  • Loading branch information
WithoutPants authored Feb 23, 2024
1 parent a302fc7 commit 4b84ec0
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 134 deletions.
29 changes: 19 additions & 10 deletions ui/v2.5/src/components/Galleries/GallerySelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,22 @@ import {
Criterion,
CriterionValue,
} from "src/models/list-filter/criteria/criterion";
import { PathCriterion } from "src/models/list-filter/criteria/path";

export type Gallery = Pick<GQL.Gallery, "id" | "title"> & {
files: Pick<GQL.GalleryFile, "path">[];
folder?: Pick<GQL.Folder, "path"> | null;
};
type Option = SelectOption<Gallery>;

type ExtraGalleryProps = {
hoverPlacement?: Placement;
excludeIds?: string[];
extraCriteria?: Array<Criterion<CriterionValue>>;
};

const _GallerySelect: React.FC<
IFilterProps &
IFilterValueProps<Gallery> & {
hoverPlacement?: Placement;
excludeIds?: string[];
} & {
extraCriteria?: Array<Criterion<CriterionValue>>;
}
IFilterProps & IFilterValueProps<Gallery> & ExtraGalleryProps
> = (props) => {
const { configuration } = React.useContext(ConfigurationContext);
const intl = useIntl();
Expand Down Expand Up @@ -187,9 +188,9 @@ const _GallerySelect: React.FC<

export const GallerySelect = PatchComponent("GallerySelect", _GallerySelect);

const _GalleryIDSelect: React.FC<IFilterProps & IFilterIDProps<Gallery>> = (
props
) => {
const _GalleryIDSelect: React.FC<
IFilterProps & IFilterIDProps<Gallery> & ExtraGalleryProps
> = (props) => {
const { ids, onSelect: onSelectValues } = props;

const [values, setValues] = useState<Gallery[]>([]);
Expand Down Expand Up @@ -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()];
105 changes: 55 additions & 50 deletions ui/v2.5/src/components/Images/EditImagesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -36,11 +37,19 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
React.useState<GQL.BulkUpdateIdMode>(GQL.BulkUpdateIdMode.Add);
const [performerIds, setPerformerIds] = useState<string[]>();
const [existingPerformerIds, setExistingPerformerIds] = useState<string[]>();

const [tagMode, setTagMode] = React.useState<GQL.BulkUpdateIdMode>(
GQL.BulkUpdateIdMode.Add
);
const [tagIds, setTagIds] = useState<string[]>();
const [existingTagIds, setExistingTagIds] = useState<string[]>();

const [galleryMode, setGalleryMode] = React.useState<GQL.BulkUpdateIdMode>(
GQL.BulkUpdateIdMode.Add
);
const [galleryIds, setGalleryIds] = useState<string[]>();
const [existingGalleryIds, setExistingGalleryIds] = useState<string[]>();

const [organized, setOrganized] = useState<boolean | undefined>();

const [updateImages] = useBulkImageUpdate();
Expand All @@ -56,6 +65,7 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
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) => {
Expand All @@ -72,6 +82,11 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
aggregatePerformerIds
);
imageInput.tag_ids = getAggregateInputIDs(tagMode, tagIds, aggregateTagIds);
imageInput.gallery_ids = getAggregateInputIDs(
galleryMode,
galleryIds,
aggregateGalleryIds
);

if (organized !== undefined) {
imageInput.organized = organized;
Expand Down Expand Up @@ -107,6 +122,7 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
let updateStudioID: string | undefined;
let updatePerformerIds: string[] = [];
let updateTagIds: string[] = [];
let updateGalleryIds: string[] = [];
let updateOrganized: boolean | undefined;
let first = true;

Expand All @@ -117,12 +133,14 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
.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 {
Expand All @@ -138,6 +156,9 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
if (!isEqual(imageTagIDs, updateTagIds)) {
updateTagIds = [];
}
if (!isEqual(imageGalleryIDs, updateGalleryIds)) {
updateGalleryIds = [];
}
if (image.organized !== updateOrganized) {
updateOrganized = undefined;
}
Expand All @@ -148,6 +169,7 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
setStudioId(updateStudioID);
setExistingPerformerIds(updatePerformerIds);
setExistingTagIds(updateTagIds);
setExistingGalleryIds(updateGalleryIds);
setOrganized(updateOrganized);
}, [props.selected, performerMode, tagMode]);

Expand All @@ -157,54 +179,6 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
}
}, [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 (
<MultiSet
type={type}
disabled={isUpdating}
onUpdate={(itemIDs) => {
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);
Expand Down Expand Up @@ -271,14 +245,45 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
<Form.Label>
<FormattedMessage id="performers" />
</Form.Label>
{renderMultiSelect("performers", performerIds)}
<MultiSet
type="performers"
disabled={isUpdating}
onUpdate={(itemIDs) => setPerformerIds(itemIDs)}
onSetMode={(newMode) => setPerformerMode(newMode)}
existingIds={existingPerformerIds ?? []}
ids={performerIds ?? []}
mode={performerMode}
/>
</Form.Group>

<Form.Group controlId="tags">
<Form.Label>
<FormattedMessage id="tags" />
</Form.Label>
{renderMultiSelect("tags", tagIds)}
<MultiSet
type="tags"
disabled={isUpdating}
onUpdate={(itemIDs) => setTagIds(itemIDs)}
onSetMode={(newMode) => setTagMode(newMode)}
existingIds={existingTagIds ?? []}
ids={tagIds ?? []}
mode={tagMode}
/>
</Form.Group>

<Form.Group controlId="galleries">
<Form.Label>
<FormattedMessage id="galleries" />
</Form.Label>
<MultiSet
type="galleries"
disabled={isUpdating}
onUpdate={(itemIDs) => setGalleryIds(itemIDs)}
onSetMode={(newMode) => setGalleryMode(newMode)}
existingIds={existingGalleryIds ?? []}
ids={galleryIds ?? []}
mode={galleryMode}
/>
</Form.Group>

<Form.Group controlId="organized">
Expand Down
15 changes: 5 additions & 10 deletions ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<IProps> = ({
image,
isVisible,
Expand Down
52 changes: 39 additions & 13 deletions ui/v2.5/src/components/Shared/MultiSet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,6 +19,39 @@ interface IMultiSetProps {
onSetMode: (mode: GQL.BulkUpdateIdMode) => void;
}

const Select: React.FC<IMultiSetProps> = (props) => {
const { type, disabled } = props;

function onUpdate(items: SelectObject[]) {
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}
/>
);
}

return (
<FilterSelect
type={type}
isDisabled={disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
/>
);
};

export const MultiSet: React.FC<IMultiSetProps> = (props) => {
const intl = useIntl();
const modes = [
Expand All @@ -23,10 +60,6 @@ export const MultiSet: React.FC<IMultiSetProps> = (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:
Expand Down Expand Up @@ -83,14 +116,7 @@ export const MultiSet: React.FC<IMultiSetProps> = (props) => {
<ButtonGroup className="button-group-above">
{modes.map((m) => renderModeButton(m))}
</ButtonGroup>
<FilterSelect
type={props.type}
isDisabled={props.disabled}
isMulti
isClearable={false}
onSelect={onUpdate}
ids={props.ids ?? []}
/>
<Select {...props} />
</div>
);
};
Loading

0 comments on commit 4b84ec0

Please sign in to comment.