mirror of https://github.com/stashapp/stash.git
Improve bulk performer editing (#2467)
* Cleanup Edit Performers dialog * Add bulk text inputs to edit performers dialog * Make bulk update code more generic * Add remaining performer fields
This commit is contained in:
parent
9e2261a813
commit
f9cf77e3ed
|
@ -2,8 +2,21 @@ fragment SlimPerformerData on Performer {
|
|||
id
|
||||
name
|
||||
gender
|
||||
url
|
||||
twitter
|
||||
instagram
|
||||
image_path
|
||||
favorite
|
||||
country
|
||||
birthdate
|
||||
ethnicity
|
||||
hair_color
|
||||
eye_color
|
||||
height
|
||||
fake_tits
|
||||
career_length
|
||||
tattoos
|
||||
piercings
|
||||
tags {
|
||||
id
|
||||
name
|
||||
|
@ -13,4 +26,6 @@ fragment SlimPerformerData on Performer {
|
|||
stash_id
|
||||
}
|
||||
rating
|
||||
death_date
|
||||
weight
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Add python location in System Settings for script scrapers and plugins. ([#2409](https://github.com/stashapp/stash/pull/2409))
|
||||
|
||||
### 🎨 Improvements
|
||||
* Added support for bulk editing most performer fields. ([#2467](https://github.com/stashapp/stash/pull/2467))
|
||||
* Changed video player to videojs. ([#2100](https://github.com/stashapp/stash/pull/2100))
|
||||
* Maintain lightbox settings and add lightbox settings to Interface settings page. ([#2406](https://github.com/stashapp/stash/pull/2406))
|
||||
* Image lightbox now transitions to next/previous image when scrolling in pan-Y mode. ([#2403](https://github.com/stashapp/stash/pull/2403))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Form, Col, Row } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import _ from "lodash";
|
||||
import { useBulkPerformerUpdate } from "src/core/StashService";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { Modal } from "src/components/Shared";
|
||||
|
@ -10,39 +9,63 @@ import { FormUtils } from "src/utils";
|
|||
import MultiSet from "../Shared/MultiSet";
|
||||
import { RatingStars } from "../Scenes/SceneDetails/RatingStars";
|
||||
import {
|
||||
getAggregateInputIDs,
|
||||
getAggregateInputValue,
|
||||
getAggregateRating,
|
||||
getAggregateTagIds,
|
||||
getAggregateState,
|
||||
getAggregateStateObject,
|
||||
} from "src/utils/bulkUpdate";
|
||||
import { genderStrings, stringToGender } from "src/utils/gender";
|
||||
import {
|
||||
genderStrings,
|
||||
genderToString,
|
||||
stringToGender,
|
||||
} from "src/utils/gender";
|
||||
import { IndeterminateCheckbox } from "../Shared/IndeterminateCheckbox";
|
||||
import { BulkUpdateTextInput } from "../Shared/BulkUpdateTextInput";
|
||||
|
||||
interface IListOperationProps {
|
||||
selected: GQL.SlimPerformerDataFragment[];
|
||||
onClose: (applied: boolean) => void;
|
||||
}
|
||||
|
||||
const performerFields = [
|
||||
"favorite",
|
||||
"url",
|
||||
"instagram",
|
||||
"twitter",
|
||||
"rating",
|
||||
"gender",
|
||||
"birthdate",
|
||||
"death_date",
|
||||
"career_length",
|
||||
"country",
|
||||
"ethnicity",
|
||||
"eye_color",
|
||||
"height",
|
||||
// "weight",
|
||||
"measurements",
|
||||
"fake_tits",
|
||||
"hair_color",
|
||||
"tattoos",
|
||||
"piercings",
|
||||
];
|
||||
|
||||
export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
||||
props: IListOperationProps
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
const Toast = useToast();
|
||||
const [rating, setRating] = useState<number>();
|
||||
const [tagMode, setTagMode] = React.useState<GQL.BulkUpdateIdMode>(
|
||||
GQL.BulkUpdateIdMode.Add
|
||||
);
|
||||
const [tagIds, setTagIds] = useState<string[]>();
|
||||
const [tagIds, setTagIds] = useState<GQL.BulkUpdateIds>({
|
||||
mode: GQL.BulkUpdateIdMode.Add,
|
||||
});
|
||||
const [existingTagIds, setExistingTagIds] = useState<string[]>();
|
||||
const [favorite, setFavorite] = useState<boolean | undefined>();
|
||||
const [ethnicity, setEthnicity] = useState<string | undefined>();
|
||||
const [country, setCountry] = useState<string | undefined>();
|
||||
const [eyeColor, setEyeColor] = useState<string | undefined>();
|
||||
const [fakeTits, setFakeTits] = useState<string | undefined>();
|
||||
const [careerLength, setCareerLength] = useState<string | undefined>();
|
||||
const [tattoos, setTattoos] = useState<string | undefined>();
|
||||
const [piercings, setPiercings] = useState<string | undefined>();
|
||||
const [hairColor, setHairColor] = useState<string | undefined>();
|
||||
const [gender, setGender] = useState<GQL.GenderEnum | undefined>();
|
||||
const [
|
||||
aggregateState,
|
||||
setAggregateState,
|
||||
] = useState<GQL.BulkPerformerUpdateInput>({});
|
||||
// weight needs conversion to/from number
|
||||
const [weight, setWeight] = useState<string | undefined>();
|
||||
const [updateInput, setUpdateInput] = useState<GQL.BulkPerformerUpdateInput>(
|
||||
{}
|
||||
);
|
||||
const genderOptions = [""].concat(genderStrings);
|
||||
|
||||
const [updatePerformers] = useBulkPerformerUpdate(getPerformerInput());
|
||||
|
@ -50,37 +73,36 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||
// Network state
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
const checkboxRef = React.createRef<HTMLInputElement>();
|
||||
function setUpdateField(input: Partial<GQL.BulkPerformerUpdateInput>) {
|
||||
setUpdateInput({ ...updateInput, ...input });
|
||||
}
|
||||
|
||||
function getPerformerInput(): GQL.BulkPerformerUpdateInput {
|
||||
// need to determine what we are actually setting on each performer
|
||||
const aggregateTagIds = getAggregateTagIds(props.selected);
|
||||
const aggregateRating = getAggregateRating(props.selected);
|
||||
|
||||
const performerInput: GQL.BulkPerformerUpdateInput = {
|
||||
ids: props.selected.map((performer) => {
|
||||
return performer.id;
|
||||
}),
|
||||
...updateInput,
|
||||
tag_ids: tagIds,
|
||||
};
|
||||
|
||||
performerInput.rating = getAggregateInputValue(rating, aggregateRating);
|
||||
|
||||
performerInput.tag_ids = getAggregateInputIDs(
|
||||
tagMode,
|
||||
tagIds,
|
||||
aggregateTagIds
|
||||
// we don't have unset functionality for the rating star control
|
||||
// so need to determine if we are setting a rating or not
|
||||
performerInput.rating = getAggregateInputValue(
|
||||
updateInput.rating,
|
||||
aggregateState.rating
|
||||
);
|
||||
|
||||
performerInput.favorite = favorite;
|
||||
performerInput.ethnicity = ethnicity;
|
||||
performerInput.country = country;
|
||||
performerInput.eye_color = eyeColor;
|
||||
performerInput.fake_tits = fakeTits;
|
||||
performerInput.career_length = careerLength;
|
||||
performerInput.tattoos = tattoos;
|
||||
performerInput.piercings = piercings;
|
||||
performerInput.hair_color = hairColor;
|
||||
performerInput.gender = gender;
|
||||
// gender dropdown doesn't have unset functionality
|
||||
// so need to determine what we are setting
|
||||
performerInput.gender = getAggregateInputValue(
|
||||
updateInput.gender,
|
||||
aggregateState.gender
|
||||
);
|
||||
|
||||
if (weight !== undefined) {
|
||||
performerInput.weight = parseFloat(weight);
|
||||
}
|
||||
|
||||
return performerInput;
|
||||
}
|
||||
|
@ -107,74 +129,39 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const updateState: GQL.BulkPerformerUpdateInput = {};
|
||||
|
||||
const state = props.selected;
|
||||
let updateTagIds: string[] = [];
|
||||
let updateFavorite: boolean | undefined;
|
||||
let updateRating: number | undefined;
|
||||
let updateGender: GQL.GenderEnum | undefined;
|
||||
let updateWeight: string | undefined | null = undefined;
|
||||
let first = true;
|
||||
|
||||
state.forEach((performer: GQL.SlimPerformerDataFragment) => {
|
||||
const performerTagIDs = (performer.tags ?? []).map((p) => p.id).sort();
|
||||
const performerRating = performer.rating;
|
||||
getAggregateStateObject(updateState, performer, performerFields, first);
|
||||
|
||||
if (first) {
|
||||
updateTagIds = performerTagIDs;
|
||||
first = false;
|
||||
updateFavorite = performer.favorite;
|
||||
updateRating = performerRating ?? undefined;
|
||||
updateGender = performer.gender ?? undefined;
|
||||
} else {
|
||||
if (!_.isEqual(performerTagIDs, updateTagIds)) {
|
||||
updateTagIds = [];
|
||||
}
|
||||
if (performer.favorite !== updateFavorite) {
|
||||
updateFavorite = undefined;
|
||||
}
|
||||
if (performerRating !== updateRating) {
|
||||
updateRating = undefined;
|
||||
}
|
||||
if (performer.gender !== updateGender) {
|
||||
updateGender = undefined;
|
||||
}
|
||||
}
|
||||
const performerTagIDs = (performer.tags ?? []).map((p) => p.id).sort();
|
||||
|
||||
updateTagIds =
|
||||
getAggregateState(updateTagIds, performerTagIDs, first) ?? [];
|
||||
|
||||
const thisWeight =
|
||||
performer.weight !== undefined && performer.weight !== null
|
||||
? performer.weight.toString()
|
||||
: performer.weight;
|
||||
updateWeight = getAggregateState(updateWeight, thisWeight, first);
|
||||
|
||||
first = false;
|
||||
});
|
||||
|
||||
setExistingTagIds(updateTagIds);
|
||||
setFavorite(updateFavorite);
|
||||
setRating(updateRating);
|
||||
setGender(updateGender);
|
||||
|
||||
// these fields are not part of SlimPerformerDataFragment
|
||||
setEthnicity(undefined);
|
||||
setCountry(undefined);
|
||||
setEyeColor(undefined);
|
||||
setFakeTits(undefined);
|
||||
setCareerLength(undefined);
|
||||
setTattoos(undefined);
|
||||
setPiercings(undefined);
|
||||
setHairColor(undefined);
|
||||
}, [props.selected, tagMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkboxRef.current) {
|
||||
checkboxRef.current.indeterminate = favorite === undefined;
|
||||
}
|
||||
}, [favorite, checkboxRef]);
|
||||
|
||||
function cycleFavorite() {
|
||||
if (favorite) {
|
||||
setFavorite(undefined);
|
||||
} else if (favorite === undefined) {
|
||||
setFavorite(false);
|
||||
} else {
|
||||
setFavorite(true);
|
||||
}
|
||||
}
|
||||
setWeight(updateWeight);
|
||||
setAggregateState(updateState);
|
||||
setUpdateInput(updateState);
|
||||
}, [props.selected]);
|
||||
|
||||
function renderTextField(
|
||||
name: string,
|
||||
value: string | undefined,
|
||||
value: string | undefined | null,
|
||||
setter: (newValue: string | undefined) => void
|
||||
) {
|
||||
return (
|
||||
|
@ -182,12 +169,10 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||
<Form.Label>
|
||||
<FormattedMessage id={name} />
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="input-control"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(event) => setter(event.currentTarget.value)}
|
||||
placeholder={intl.formatMessage({ id: name })}
|
||||
<BulkUpdateTextInput
|
||||
value={value === null ? "" : value ?? undefined}
|
||||
valueChanged={(newValue) => setter(newValue)}
|
||||
unsetDisabled={props.selected.length < 2}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
|
@ -219,20 +204,18 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||
})}
|
||||
<Col xs={9}>
|
||||
<RatingStars
|
||||
value={rating}
|
||||
onSetRating={(value) => setRating(value)}
|
||||
value={updateInput.rating ?? undefined}
|
||||
onSetRating={(value) => setUpdateField({ rating: value })}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<Form>
|
||||
<Form.Group controlId="favorite">
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
label="Favorite"
|
||||
checked={favorite}
|
||||
ref={checkboxRef}
|
||||
onChange={() => cycleFavorite()}
|
||||
<IndeterminateCheckbox
|
||||
setChecked={(checked) => setUpdateField({ favorite: checked })}
|
||||
checked={updateInput.favorite ?? undefined}
|
||||
label={intl.formatMessage({ id: "favourite" })}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
|
@ -243,8 +226,11 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||
<Form.Control
|
||||
as="select"
|
||||
className="input-control"
|
||||
value={genderToString(updateInput.gender ?? undefined)}
|
||||
onChange={(event) =>
|
||||
setGender(stringToGender(event.currentTarget.value))
|
||||
setUpdateField({
|
||||
gender: stringToGender(event.currentTarget.value),
|
||||
})
|
||||
}
|
||||
>
|
||||
{genderOptions.map((opt) => (
|
||||
|
@ -255,14 +241,52 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||
</Form.Control>
|
||||
</Form.Group>
|
||||
|
||||
{renderTextField("country", country, setCountry)}
|
||||
{renderTextField("ethnicity", ethnicity, setEthnicity)}
|
||||
{renderTextField("hair_color", hairColor, setHairColor)}
|
||||
{renderTextField("eye_color", eyeColor, setEyeColor)}
|
||||
{renderTextField("fake_tits", fakeTits, setFakeTits)}
|
||||
{renderTextField("tattoos", tattoos, setTattoos)}
|
||||
{renderTextField("piercings", piercings, setPiercings)}
|
||||
{renderTextField("career_length", careerLength, setCareerLength)}
|
||||
{renderTextField("birthdate", updateInput.birthdate, (v) =>
|
||||
setUpdateField({ birthdate: v })
|
||||
)}
|
||||
{renderTextField("death_date", updateInput.death_date, (v) =>
|
||||
setUpdateField({ death_date: v })
|
||||
)}
|
||||
{renderTextField("country", updateInput.country, (v) =>
|
||||
setUpdateField({ country: v })
|
||||
)}
|
||||
{renderTextField("ethnicity", updateInput.ethnicity, (v) =>
|
||||
setUpdateField({ ethnicity: v })
|
||||
)}
|
||||
{renderTextField("hair_color", updateInput.hair_color, (v) =>
|
||||
setUpdateField({ hair_color: v })
|
||||
)}
|
||||
{renderTextField("eye_color", updateInput.eye_color, (v) =>
|
||||
setUpdateField({ eye_color: v })
|
||||
)}
|
||||
{renderTextField("height", updateInput.height, (v) =>
|
||||
setUpdateField({ height: v })
|
||||
)}
|
||||
{renderTextField("weight", weight, (v) => setWeight(v))}
|
||||
{renderTextField("measurements", updateInput.measurements, (v) =>
|
||||
setUpdateField({ measurements: v })
|
||||
)}
|
||||
{renderTextField("fake_tits", updateInput.fake_tits, (v) =>
|
||||
setUpdateField({ fake_tits: v })
|
||||
)}
|
||||
{renderTextField("tattoos", updateInput.tattoos, (v) =>
|
||||
setUpdateField({ tattoos: v })
|
||||
)}
|
||||
{renderTextField("piercings", updateInput.piercings, (v) =>
|
||||
setUpdateField({ piercings: v })
|
||||
)}
|
||||
{renderTextField("career_length", updateInput.career_length, (v) =>
|
||||
setUpdateField({ career_length: v })
|
||||
)}
|
||||
{renderTextField("url", updateInput.url, (v) =>
|
||||
setUpdateField({ url: v })
|
||||
)}
|
||||
{renderTextField("twitter", updateInput.twitter, (v) =>
|
||||
setUpdateField({ twitter: v })
|
||||
)}
|
||||
{renderTextField("instagram", updateInput.instagram, (v) =>
|
||||
setUpdateField({ instagram: v })
|
||||
)}
|
||||
|
||||
<Form.Group controlId="tags">
|
||||
<Form.Label>
|
||||
|
@ -271,11 +295,11 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||
<MultiSet
|
||||
type="tags"
|
||||
disabled={isUpdating}
|
||||
onUpdate={(itemIDs) => setTagIds(itemIDs)}
|
||||
onSetMode={(newMode) => setTagMode(newMode)}
|
||||
onUpdate={(itemIDs) => setTagIds({ ...tagIds, ids: itemIDs })}
|
||||
onSetMode={(newMode) => setTagIds({ ...tagIds, mode: newMode })}
|
||||
existingIds={existingTagIds ?? []}
|
||||
ids={tagIds ?? []}
|
||||
mode={tagMode}
|
||||
ids={tagIds.ids ?? []}
|
||||
mode={tagIds.mode}
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import React from "react";
|
||||
import { Button, Form, FormControlProps, InputGroup } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Icon } from ".";
|
||||
|
||||
interface IBulkUpdateTextInputProps extends FormControlProps {
|
||||
valueChanged: (value: string | undefined) => void;
|
||||
unsetDisabled?: boolean;
|
||||
}
|
||||
|
||||
export const BulkUpdateTextInput: React.FC<IBulkUpdateTextInputProps> = ({
|
||||
valueChanged,
|
||||
unsetDisabled,
|
||||
...props
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const unsetClassName = props.value === undefined ? "unset" : "";
|
||||
|
||||
return (
|
||||
<InputGroup className={`bulk-update-text-input ${unsetClassName}`}>
|
||||
<Form.Control
|
||||
{...props}
|
||||
className="input-control"
|
||||
type="text"
|
||||
value={props.value ?? ""}
|
||||
placeholder={
|
||||
props.value === undefined
|
||||
? `<${intl.formatMessage({ id: "existing_value" })}>`
|
||||
: undefined
|
||||
}
|
||||
onChange={(event) => valueChanged(event.currentTarget.value)}
|
||||
/>
|
||||
{!unsetDisabled ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => valueChanged(undefined)}
|
||||
title={intl.formatMessage({ id: "actions.unset" })}
|
||||
>
|
||||
<Icon icon="ban" />
|
||||
</Button>
|
||||
) : undefined}
|
||||
</InputGroup>
|
||||
);
|
||||
};
|
|
@ -20,7 +20,7 @@ interface IIcon {
|
|||
const Icon: React.FC<IIcon> = ({ icon, className, color, size }) => (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={`fa-icon ${className}`}
|
||||
className={`fa-icon ${className ?? ""}`}
|
||||
color={color}
|
||||
size={size}
|
||||
/>
|
||||
|
|
|
@ -48,7 +48,7 @@ export const IndeterminateCheckbox: React.FC<IIndeterminateCheckbox> = ({
|
|||
checked === undefined ? indeterminateClassname : ""
|
||||
}`}
|
||||
ref={ref}
|
||||
checked={checked}
|
||||
checked={checked ?? false}
|
||||
onChange={() => setChecked(cycleState())}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -74,6 +74,7 @@ const MultiSet: React.FunctionComponent<IMultiSetProps> = (
|
|||
function renderModeButton(mode: GQL.BulkUpdateIdMode) {
|
||||
return (
|
||||
<Button
|
||||
key={mode}
|
||||
variant="primary"
|
||||
active={props.mode === mode}
|
||||
size="sm"
|
||||
|
|
|
@ -271,3 +271,30 @@ button.collapse-button.btn-primary:not(:disabled):not(.disabled):active {
|
|||
.string-list-input .input-group {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.bulk-update-text-input {
|
||||
button {
|
||||
background-color: $secondary;
|
||||
color: $text-muted;
|
||||
font-size: $btn-font-size-sm;
|
||||
margin: $btn-padding-y $btn-padding-x;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 4;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active,
|
||||
&:not(:disabled):not(.disabled):active,
|
||||
&:not(:disabled):not(.disabled):active:focus {
|
||||
background-color: $secondary;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.unset button {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
},
|
||||
"temp_disable": "Disable temporarily…",
|
||||
"temp_enable": "Enable temporarily…",
|
||||
"unset": "Unset",
|
||||
"use_default": "Use default",
|
||||
"view_random": "View Random",
|
||||
"continue": "Continue",
|
||||
|
@ -584,6 +585,7 @@
|
|||
"delete_object_overflow": "…and {count} other {count, plural, one {{singularEntity}} other {{pluralEntity}}}.",
|
||||
"delete_object_title": "Delete {count, plural, one {{singularEntity}} other {{pluralEntity}}}",
|
||||
"edit_entity_title": "Edit {count, plural, one {{singularEntity}} other {{pluralEntity}}}",
|
||||
"existing_value": "existing value",
|
||||
"export_include_related_objects": "Include related objects in export",
|
||||
"export_title": "Export",
|
||||
"lightbox": {
|
||||
|
@ -701,6 +703,7 @@
|
|||
"warmth": "Warmth"
|
||||
},
|
||||
"ethnicity": "Ethnicity",
|
||||
"existing_value": "existing value",
|
||||
"eye_color": "Eye Colour",
|
||||
"fake_tits": "Fake Tits",
|
||||
"false": "False",
|
||||
|
|
|
@ -124,7 +124,7 @@ export function getAggregateMovieIds(state: IHasMovies[]) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
function makeBulkUpdateIds(
|
||||
export function makeBulkUpdateIds(
|
||||
ids: string[],
|
||||
mode: GQL.BulkUpdateIdMode
|
||||
): GQL.BulkUpdateIds {
|
||||
|
@ -152,6 +152,7 @@ export function getAggregateInputValue<V>(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO - remove - this is incorrect
|
||||
export function getAggregateInputIDs(
|
||||
mode: GQL.BulkUpdateIdMode,
|
||||
inputIds: string[] | undefined,
|
||||
|
@ -173,3 +174,46 @@ export function getAggregateInputIDs(
|
|||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getAggregateState<T>(
|
||||
currentValue: T,
|
||||
newValue: T,
|
||||
first: boolean
|
||||
) {
|
||||
if (!first && !_.isEqual(currentValue, newValue)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function setProperty<T, K extends keyof T>(obj: T, key: K, value: any) {
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
function getProperty<T, K extends keyof T>(obj: T, key: K) {
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
export function getAggregateStateObject<O, I>(
|
||||
output: O,
|
||||
input: I,
|
||||
fields: string[],
|
||||
first: boolean
|
||||
) {
|
||||
fields.forEach((key) => {
|
||||
const outputKey = key as keyof O;
|
||||
const inputKey = key as keyof I;
|
||||
|
||||
const currentValue = getProperty(output, outputKey);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const performerValue = getProperty(input, inputKey) as any;
|
||||
|
||||
setProperty(
|
||||
output,
|
||||
outputKey,
|
||||
getAggregateState(currentValue, performerValue, first)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue