mirror of
https://github.com/stashapp/stash.git
synced 2025-12-07 17:02:38 +01:00
Fix batch performer tagging with multiple endpoints (#3548)
* Set stash ids correctly during performer batch add * Refactor performer tagger dialogs
This commit is contained in:
parent
7cff71c35f
commit
5a41001246
4 changed files with 332 additions and 236 deletions
|
|
@ -336,6 +336,10 @@ func (s *Manager) StashBoxBatchPerformerTag(ctx context.Context, input StashBoxB
|
||||||
for _, performerID := range input.PerformerIds {
|
for _, performerID := range input.PerformerIds {
|
||||||
if id, err := strconv.Atoi(performerID); err == nil {
|
if id, err := strconv.Atoi(performerID); err == nil {
|
||||||
performer, err := performerQuery.Find(ctx, id)
|
performer, err := performerQuery.Find(ctx, id)
|
||||||
|
if err == nil {
|
||||||
|
err = performer.LoadStashIDs(ctx, performerQuery)
|
||||||
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tasks = append(tasks, StashBoxPerformerTagTask{
|
tasks = append(tasks, StashBoxPerformerTagTask{
|
||||||
performer: performer,
|
performer: performer,
|
||||||
|
|
@ -382,6 +386,10 @@ func (s *Manager) StashBoxBatchPerformerTag(ctx context.Context, input StashBoxB
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, performer := range performers {
|
for _, performer := range performers {
|
||||||
|
if err := performer.LoadStashIDs(ctx, performerQuery); err != nil {
|
||||||
|
return fmt.Errorf("error loading stash ids for performer %s: %v", performer.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
tasks = append(tasks, StashBoxPerformerTagTask{
|
tasks = append(tasks, StashBoxPerformerTagTask{
|
||||||
performer: performer,
|
performer: performer,
|
||||||
refresh: input.Refresh,
|
refresh: input.Refresh,
|
||||||
|
|
|
||||||
|
|
@ -50,17 +50,10 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
|
||||||
|
|
||||||
if t.refresh {
|
if t.refresh {
|
||||||
var performerID string
|
var performerID string
|
||||||
txnErr := txn.WithReadTxn(ctx, instance.Repository, func(ctx context.Context) error {
|
for _, id := range t.performer.StashIDs.List() {
|
||||||
stashids, _ := instance.Repository.Performer.GetStashIDs(ctx, t.performer.ID)
|
if id.Endpoint == t.box.Endpoint {
|
||||||
for _, id := range stashids {
|
performerID = id.StashID
|
||||||
if id.Endpoint == t.box.Endpoint {
|
|
||||||
performerID = id.StashID
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txnErr != nil {
|
|
||||||
logger.Warnf("error while executing read transaction: %v", err)
|
|
||||||
}
|
}
|
||||||
if performerID != "" {
|
if performerID != "" {
|
||||||
performer, err = client.FindStashBoxPerformerByID(ctx, performerID)
|
performer, err = client.FindStashBoxPerformerByID(ctx, performerID)
|
||||||
|
|
@ -87,80 +80,7 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
|
||||||
|
|
||||||
if performer != nil {
|
if performer != nil {
|
||||||
if t.performer != nil {
|
if t.performer != nil {
|
||||||
partial := models.NewPerformerPartial()
|
partial := t.getPartial(performer, excluded)
|
||||||
|
|
||||||
if performer.Aliases != nil && !excluded["aliases"] {
|
|
||||||
partial.Aliases = &models.UpdateStrings{
|
|
||||||
Values: stringslice.FromString(*performer.Aliases, ","),
|
|
||||||
Mode: models.RelationshipUpdateModeSet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if performer.Birthdate != nil && *performer.Birthdate != "" && !excluded["birthdate"] {
|
|
||||||
value := getDate(performer.Birthdate)
|
|
||||||
partial.Birthdate = models.NewOptionalDate(*value)
|
|
||||||
}
|
|
||||||
if performer.CareerLength != nil && !excluded["career_length"] {
|
|
||||||
partial.CareerLength = models.NewOptionalString(*performer.CareerLength)
|
|
||||||
}
|
|
||||||
if performer.Country != nil && !excluded["country"] {
|
|
||||||
partial.Country = models.NewOptionalString(*performer.Country)
|
|
||||||
}
|
|
||||||
if performer.Ethnicity != nil && !excluded["ethnicity"] {
|
|
||||||
partial.Ethnicity = models.NewOptionalString(*performer.Ethnicity)
|
|
||||||
}
|
|
||||||
if performer.EyeColor != nil && !excluded["eye_color"] {
|
|
||||||
partial.EyeColor = models.NewOptionalString(*performer.EyeColor)
|
|
||||||
}
|
|
||||||
if performer.FakeTits != nil && !excluded["fake_tits"] {
|
|
||||||
partial.FakeTits = models.NewOptionalString(*performer.FakeTits)
|
|
||||||
}
|
|
||||||
if performer.Gender != nil && !excluded["gender"] {
|
|
||||||
partial.Gender = models.NewOptionalString(*performer.Gender)
|
|
||||||
}
|
|
||||||
if performer.Height != nil && !excluded["height"] {
|
|
||||||
h, err := strconv.Atoi(*performer.Height)
|
|
||||||
if err == nil {
|
|
||||||
partial.Height = models.NewOptionalInt(h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if performer.Weight != nil && !excluded["weight"] {
|
|
||||||
w, err := strconv.Atoi(*performer.Weight)
|
|
||||||
if err == nil {
|
|
||||||
partial.Weight = models.NewOptionalInt(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if performer.Instagram != nil && !excluded["instagram"] {
|
|
||||||
partial.Instagram = models.NewOptionalString(*performer.Instagram)
|
|
||||||
}
|
|
||||||
if performer.Measurements != nil && !excluded["measurements"] {
|
|
||||||
partial.Measurements = models.NewOptionalString(*performer.Measurements)
|
|
||||||
}
|
|
||||||
if excluded["name"] && performer.Name != nil {
|
|
||||||
partial.Name = models.NewOptionalString(*performer.Name)
|
|
||||||
}
|
|
||||||
if performer.Piercings != nil && !excluded["piercings"] {
|
|
||||||
partial.Piercings = models.NewOptionalString(*performer.Piercings)
|
|
||||||
}
|
|
||||||
if performer.Tattoos != nil && !excluded["tattoos"] {
|
|
||||||
partial.Tattoos = models.NewOptionalString(*performer.Tattoos)
|
|
||||||
}
|
|
||||||
if performer.Twitter != nil && !excluded["twitter"] {
|
|
||||||
partial.Twitter = models.NewOptionalString(*performer.Twitter)
|
|
||||||
}
|
|
||||||
if performer.URL != nil && !excluded["url"] {
|
|
||||||
partial.URL = models.NewOptionalString(*performer.URL)
|
|
||||||
}
|
|
||||||
if !t.refresh {
|
|
||||||
partial.StashIDs = &models.UpdateStashIDs{
|
|
||||||
StashIDs: []models.StashID{
|
|
||||||
{
|
|
||||||
Endpoint: t.box.Endpoint,
|
|
||||||
StashID: *performer.RemoteSiteID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Mode: models.RelationshipUpdateModeSet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
txnErr := txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error {
|
txnErr := txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error {
|
||||||
r := instance.Repository
|
r := instance.Repository
|
||||||
|
|
@ -168,12 +88,13 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
|
||||||
|
|
||||||
if len(performer.Images) > 0 && !excluded["image"] {
|
if len(performer.Images) > 0 && !excluded["image"] {
|
||||||
image, err := utils.ReadImageFromURL(ctx, performer.Images[0])
|
image, err := utils.ReadImageFromURL(ctx, performer.Images[0])
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return err
|
err = r.Performer.UpdateImage(ctx, t.performer.ID, image)
|
||||||
}
|
if err != nil {
|
||||||
err = r.Performer.UpdateImage(ctx, t.performer.ID, image)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
return err
|
} else {
|
||||||
|
logger.Warnf("Failed to read performer image: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +108,7 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if txnErr != nil {
|
if txnErr != nil {
|
||||||
logger.Warnf("failure to execute partial update of performer: %v", err)
|
logger.Warnf("failure to execute partial update of performer: %v", txnErr)
|
||||||
}
|
}
|
||||||
} else if t.name != nil && performer.Name != nil {
|
} else if t.name != nil && performer.Name != nil {
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
|
|
@ -258,6 +179,87 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *StashBoxPerformerTagTask) getPartial(performer *models.ScrapedPerformer, excluded map[string]bool) models.PerformerPartial {
|
||||||
|
partial := models.NewPerformerPartial()
|
||||||
|
|
||||||
|
if performer.Aliases != nil && !excluded["aliases"] {
|
||||||
|
partial.Aliases = &models.UpdateStrings{
|
||||||
|
Values: stringslice.FromString(*performer.Aliases, ","),
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if performer.Birthdate != nil && *performer.Birthdate != "" && !excluded["birthdate"] {
|
||||||
|
value := getDate(performer.Birthdate)
|
||||||
|
partial.Birthdate = models.NewOptionalDate(*value)
|
||||||
|
}
|
||||||
|
if performer.CareerLength != nil && !excluded["career_length"] {
|
||||||
|
partial.CareerLength = models.NewOptionalString(*performer.CareerLength)
|
||||||
|
}
|
||||||
|
if performer.Country != nil && !excluded["country"] {
|
||||||
|
partial.Country = models.NewOptionalString(*performer.Country)
|
||||||
|
}
|
||||||
|
if performer.Ethnicity != nil && !excluded["ethnicity"] {
|
||||||
|
partial.Ethnicity = models.NewOptionalString(*performer.Ethnicity)
|
||||||
|
}
|
||||||
|
if performer.EyeColor != nil && !excluded["eye_color"] {
|
||||||
|
partial.EyeColor = models.NewOptionalString(*performer.EyeColor)
|
||||||
|
}
|
||||||
|
if performer.FakeTits != nil && !excluded["fake_tits"] {
|
||||||
|
partial.FakeTits = models.NewOptionalString(*performer.FakeTits)
|
||||||
|
}
|
||||||
|
if performer.Gender != nil && !excluded["gender"] {
|
||||||
|
partial.Gender = models.NewOptionalString(*performer.Gender)
|
||||||
|
}
|
||||||
|
if performer.Height != nil && !excluded["height"] {
|
||||||
|
h, err := strconv.Atoi(*performer.Height)
|
||||||
|
if err == nil {
|
||||||
|
partial.Height = models.NewOptionalInt(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if performer.Weight != nil && !excluded["weight"] {
|
||||||
|
w, err := strconv.Atoi(*performer.Weight)
|
||||||
|
if err == nil {
|
||||||
|
partial.Weight = models.NewOptionalInt(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if performer.Instagram != nil && !excluded["instagram"] {
|
||||||
|
partial.Instagram = models.NewOptionalString(*performer.Instagram)
|
||||||
|
}
|
||||||
|
if performer.Measurements != nil && !excluded["measurements"] {
|
||||||
|
partial.Measurements = models.NewOptionalString(*performer.Measurements)
|
||||||
|
}
|
||||||
|
if excluded["name"] && performer.Name != nil {
|
||||||
|
partial.Name = models.NewOptionalString(*performer.Name)
|
||||||
|
}
|
||||||
|
if performer.Piercings != nil && !excluded["piercings"] {
|
||||||
|
partial.Piercings = models.NewOptionalString(*performer.Piercings)
|
||||||
|
}
|
||||||
|
if performer.Tattoos != nil && !excluded["tattoos"] {
|
||||||
|
partial.Tattoos = models.NewOptionalString(*performer.Tattoos)
|
||||||
|
}
|
||||||
|
if performer.Twitter != nil && !excluded["twitter"] {
|
||||||
|
partial.Twitter = models.NewOptionalString(*performer.Twitter)
|
||||||
|
}
|
||||||
|
if performer.URL != nil && !excluded["url"] {
|
||||||
|
partial.URL = models.NewOptionalString(*performer.URL)
|
||||||
|
}
|
||||||
|
if !t.refresh {
|
||||||
|
// #3547 - need to overwrite the stash id for the endpoint, but preserve
|
||||||
|
// existing stash ids for other endpoints
|
||||||
|
partial.StashIDs = &models.UpdateStashIDs{
|
||||||
|
StashIDs: t.performer.StashIDs.List(),
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
partial.StashIDs.Set(models.StashID{
|
||||||
|
Endpoint: t.box.Endpoint,
|
||||||
|
StashID: *performer.RemoteSiteID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return partial
|
||||||
|
}
|
||||||
|
|
||||||
func getDate(val *string) *models.Date {
|
func getDate(val *string) *models.Date {
|
||||||
if val == nil {
|
if val == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,18 @@ func (u *UpdateStashIDs) AddUnique(v StashID) {
|
||||||
u.StashIDs = append(u.StashIDs, v)
|
u.StashIDs = append(u.StashIDs, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set sets or replaces the stash id for the endpoint in the provided value.
|
||||||
|
func (u *UpdateStashIDs) Set(v StashID) {
|
||||||
|
for i, vv := range u.StashIDs {
|
||||||
|
if vv.Endpoint == v.Endpoint {
|
||||||
|
u.StashIDs[i] = v
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.StashIDs = append(u.StashIDs, v)
|
||||||
|
}
|
||||||
|
|
||||||
type StashIDCriterionInput struct {
|
type StashIDCriterionInput struct {
|
||||||
// If present, this value is treated as a predicate.
|
// If present, this value is treated as a predicate.
|
||||||
// That is, it will filter based on stash_ids with the matching endpoint
|
// That is, it will filter based on stash_ids with the matching endpoint
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Button, Card, Form, InputGroup, ProgressBar } from "react-bootstrap";
|
import { Button, Card, Form, InputGroup, ProgressBar } from "react-bootstrap";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
@ -30,6 +30,204 @@ type JobFragment = Pick<
|
||||||
|
|
||||||
const CLASSNAME = "PerformerTagger";
|
const CLASSNAME = "PerformerTagger";
|
||||||
|
|
||||||
|
interface IPerformerBatchUpdateModal {
|
||||||
|
performers: GQL.PerformerDataFragment[];
|
||||||
|
isIdle: boolean;
|
||||||
|
selectedEndpoint: { endpoint: string; index: number };
|
||||||
|
onBatchUpdate: (queryAll: boolean, refresh: boolean) => void;
|
||||||
|
close: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PerformerBatchUpdateModal: React.FC<IPerformerBatchUpdateModal> = ({
|
||||||
|
performers,
|
||||||
|
isIdle,
|
||||||
|
selectedEndpoint,
|
||||||
|
onBatchUpdate,
|
||||||
|
close,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [queryAll, setQueryAll] = useState(false);
|
||||||
|
|
||||||
|
const [refresh, setRefresh] = useState(false);
|
||||||
|
const { data: allPerformers } = GQL.useFindPerformersQuery({
|
||||||
|
variables: {
|
||||||
|
performer_filter: {
|
||||||
|
stash_id_endpoint: {
|
||||||
|
endpoint: selectedEndpoint.endpoint,
|
||||||
|
modifier: refresh
|
||||||
|
? GQL.CriterionModifier.NotNull
|
||||||
|
: GQL.CriterionModifier.IsNull,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
per_page: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const performerCount = useMemo(() => {
|
||||||
|
// get all stash ids for the selected endpoint
|
||||||
|
const filteredStashIDs = performers.map((p) =>
|
||||||
|
p.stash_ids.filter((s) => s.endpoint === selectedEndpoint.endpoint)
|
||||||
|
);
|
||||||
|
|
||||||
|
return queryAll
|
||||||
|
? allPerformers?.findPerformers.count
|
||||||
|
: filteredStashIDs.filter((s) =>
|
||||||
|
// if refresh, then we filter out the performers without a stash id
|
||||||
|
// otherwise, we want untagged performers, filtering out those with a stash id
|
||||||
|
refresh ? s.length > 0 : s.length === 0
|
||||||
|
).length;
|
||||||
|
}, [queryAll, refresh, performers, allPerformers, selectedEndpoint.endpoint]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalComponent
|
||||||
|
show
|
||||||
|
icon={faTags}
|
||||||
|
header={intl.formatMessage({
|
||||||
|
id: "performer_tagger.update_performers",
|
||||||
|
})}
|
||||||
|
accept={{
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: "performer_tagger.update_performers",
|
||||||
|
}),
|
||||||
|
onClick: () => onBatchUpdate(queryAll, refresh),
|
||||||
|
}}
|
||||||
|
cancel={{
|
||||||
|
text: intl.formatMessage({ id: "actions.cancel" }),
|
||||||
|
variant: "danger",
|
||||||
|
onClick: () => close(),
|
||||||
|
}}
|
||||||
|
disabled={!isIdle}
|
||||||
|
>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>
|
||||||
|
<h6>
|
||||||
|
<FormattedMessage id="performer_tagger.performer_selection" />
|
||||||
|
</h6>
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Check
|
||||||
|
id="query-page"
|
||||||
|
type="radio"
|
||||||
|
name="performer-query"
|
||||||
|
label={<FormattedMessage id="performer_tagger.current_page" />}
|
||||||
|
defaultChecked
|
||||||
|
onChange={() => setQueryAll(false)}
|
||||||
|
/>
|
||||||
|
<Form.Check
|
||||||
|
id="query-all"
|
||||||
|
type="radio"
|
||||||
|
name="performer-query"
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: "performer_tagger.query_all_performers_in_the_database",
|
||||||
|
})}
|
||||||
|
defaultChecked={false}
|
||||||
|
onChange={() => setQueryAll(true)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>
|
||||||
|
<h6>
|
||||||
|
<FormattedMessage id="performer_tagger.tag_status" />
|
||||||
|
</h6>
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Check
|
||||||
|
id="untagged-performers"
|
||||||
|
type="radio"
|
||||||
|
name="performer-refresh"
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: "performer_tagger.untagged_performers",
|
||||||
|
})}
|
||||||
|
defaultChecked
|
||||||
|
onChange={() => setRefresh(false)}
|
||||||
|
/>
|
||||||
|
<Form.Text>
|
||||||
|
<FormattedMessage id="performer_tagger.updating_untagged_performers_description" />
|
||||||
|
</Form.Text>
|
||||||
|
<Form.Check
|
||||||
|
id="tagged-performers"
|
||||||
|
type="radio"
|
||||||
|
name="performer-refresh"
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: "performer_tagger.refresh_tagged_performers",
|
||||||
|
})}
|
||||||
|
defaultChecked={false}
|
||||||
|
onChange={() => setRefresh(true)}
|
||||||
|
/>
|
||||||
|
<Form.Text>
|
||||||
|
<FormattedMessage id="performer_tagger.refreshing_will_update_the_data" />
|
||||||
|
</Form.Text>
|
||||||
|
</Form.Group>
|
||||||
|
<b>
|
||||||
|
<FormattedMessage
|
||||||
|
id="performer_tagger.number_of_performers_will_be_processed"
|
||||||
|
values={{
|
||||||
|
performer_count: performerCount,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</b>
|
||||||
|
</ModalComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IPerformerBatchAddModal {
|
||||||
|
isIdle: boolean;
|
||||||
|
onBatchAdd: (input: string) => void;
|
||||||
|
close: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PerformerBatchAddModal: React.FC<IPerformerBatchAddModal> = ({
|
||||||
|
isIdle,
|
||||||
|
onBatchAdd,
|
||||||
|
close,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const performerInput = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalComponent
|
||||||
|
show
|
||||||
|
icon={faStar}
|
||||||
|
header={intl.formatMessage({
|
||||||
|
id: "performer_tagger.add_new_performers",
|
||||||
|
})}
|
||||||
|
accept={{
|
||||||
|
text: intl.formatMessage({
|
||||||
|
id: "performer_tagger.add_new_performers",
|
||||||
|
}),
|
||||||
|
onClick: () => {
|
||||||
|
if (performerInput.current) {
|
||||||
|
onBatchAdd(performerInput.current.value);
|
||||||
|
} else {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
cancel={{
|
||||||
|
text: intl.formatMessage({ id: "actions.cancel" }),
|
||||||
|
variant: "danger",
|
||||||
|
onClick: () => close(),
|
||||||
|
}}
|
||||||
|
disabled={!isIdle}
|
||||||
|
>
|
||||||
|
<Form.Control
|
||||||
|
className="text-input"
|
||||||
|
as="textarea"
|
||||||
|
ref={performerInput}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: "performer_tagger.performer_names_separated_by_comma",
|
||||||
|
})}
|
||||||
|
rows={6}
|
||||||
|
/>
|
||||||
|
<Form.Text>
|
||||||
|
<FormattedMessage id="performer_tagger.any_names_entered_will_be_queried" />
|
||||||
|
</Form.Text>
|
||||||
|
</ModalComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface IPerformerTaggerListProps {
|
interface IPerformerTaggerListProps {
|
||||||
performers: GQL.PerformerDataFragment[];
|
performers: GQL.PerformerDataFragment[];
|
||||||
selectedEndpoint: { endpoint: string; index: number };
|
selectedEndpoint: { endpoint: string; index: number };
|
||||||
|
|
@ -61,27 +259,9 @@ const PerformerTaggerList: React.FC<IPerformerTaggerListProps> = ({
|
||||||
Record<string, Partial<GQL.SlimPerformerDataFragment>>
|
Record<string, Partial<GQL.SlimPerformerDataFragment>>
|
||||||
>({});
|
>({});
|
||||||
const [queries, setQueries] = useState<Record<string, string>>({});
|
const [queries, setQueries] = useState<Record<string, string>>({});
|
||||||
const [queryAll, setQueryAll] = useState(false);
|
|
||||||
|
|
||||||
const [refresh, setRefresh] = useState(false);
|
|
||||||
const { data: allPerformers } = GQL.useFindPerformersQuery({
|
|
||||||
variables: {
|
|
||||||
performer_filter: {
|
|
||||||
stash_id: {
|
|
||||||
value: "",
|
|
||||||
modifier: refresh
|
|
||||||
? GQL.CriterionModifier.NotNull
|
|
||||||
: GQL.CriterionModifier.IsNull,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
filter: {
|
|
||||||
per_page: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const [showBatchAdd, setShowBatchAdd] = useState(false);
|
const [showBatchAdd, setShowBatchAdd] = useState(false);
|
||||||
const [showBatchUpdate, setShowBatchUpdate] = useState(false);
|
const [showBatchUpdate, setShowBatchUpdate] = useState(false);
|
||||||
const performerInput = useRef<HTMLTextAreaElement | null>(null);
|
|
||||||
|
|
||||||
const [error, setError] = useState<
|
const [error, setError] = useState<
|
||||||
Record<string, { message?: string; details?: string } | undefined>
|
Record<string, { message?: string; details?: string } | undefined>
|
||||||
|
|
@ -144,14 +324,12 @@ const PerformerTaggerList: React.FC<IPerformerTaggerListProps> = ({
|
||||||
.finally(() => setLoadingUpdate(undefined));
|
.finally(() => setLoadingUpdate(undefined));
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handleBatchAdd() {
|
async function handleBatchAdd(input: string) {
|
||||||
if (performerInput.current) {
|
onBatchAdd(input);
|
||||||
onBatchAdd(performerInput.current.value);
|
|
||||||
}
|
|
||||||
setShowBatchAdd(false);
|
setShowBatchAdd(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBatchUpdate = () => {
|
const handleBatchUpdate = (queryAll: boolean, refresh: boolean) => {
|
||||||
onBatchUpdate(!queryAll ? performers.map((p) => p.id) : undefined, refresh);
|
onBatchUpdate(!queryAll ? performers.map((p) => p.id) : undefined, refresh);
|
||||||
setShowBatchUpdate(false);
|
setShowBatchUpdate(false);
|
||||||
};
|
};
|
||||||
|
|
@ -388,128 +566,24 @@ const PerformerTaggerList: React.FC<IPerformerTaggerListProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<ModalComponent
|
{showBatchUpdate && (
|
||||||
show={showBatchUpdate}
|
<PerformerBatchUpdateModal
|
||||||
icon={faTags}
|
close={() => setShowBatchUpdate(false)}
|
||||||
header={intl.formatMessage({
|
isIdle={isIdle}
|
||||||
id: "performer_tagger.update_performers",
|
selectedEndpoint={selectedEndpoint}
|
||||||
})}
|
performers={performers}
|
||||||
accept={{
|
onBatchUpdate={handleBatchUpdate}
|
||||||
text: intl.formatMessage({
|
|
||||||
id: "performer_tagger.update_performers",
|
|
||||||
}),
|
|
||||||
onClick: handleBatchUpdate,
|
|
||||||
}}
|
|
||||||
cancel={{
|
|
||||||
text: intl.formatMessage({ id: "actions.cancel" }),
|
|
||||||
variant: "danger",
|
|
||||||
onClick: () => setShowBatchUpdate(false),
|
|
||||||
}}
|
|
||||||
disabled={!isIdle}
|
|
||||||
>
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Label>
|
|
||||||
<h6>
|
|
||||||
<FormattedMessage id="performer_tagger.performer_selection" />
|
|
||||||
</h6>
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Check
|
|
||||||
id="query-page"
|
|
||||||
type="radio"
|
|
||||||
name="performer-query"
|
|
||||||
label={<FormattedMessage id="performer_tagger.current_page" />}
|
|
||||||
defaultChecked
|
|
||||||
onChange={() => setQueryAll(false)}
|
|
||||||
/>
|
|
||||||
<Form.Check
|
|
||||||
id="query-all"
|
|
||||||
type="radio"
|
|
||||||
name="performer-query"
|
|
||||||
label={intl.formatMessage({
|
|
||||||
id: "performer_tagger.query_all_performers_in_the_database",
|
|
||||||
})}
|
|
||||||
defaultChecked={false}
|
|
||||||
onChange={() => setQueryAll(true)}
|
|
||||||
/>
|
|
||||||
</Form.Group>
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Label>
|
|
||||||
<h6>
|
|
||||||
<FormattedMessage id="performer_tagger.tag_status" />
|
|
||||||
</h6>
|
|
||||||
</Form.Label>
|
|
||||||
<Form.Check
|
|
||||||
id="untagged-performers"
|
|
||||||
type="radio"
|
|
||||||
name="performer-refresh"
|
|
||||||
label={intl.formatMessage({
|
|
||||||
id: "performer_tagger.untagged_performers",
|
|
||||||
})}
|
|
||||||
defaultChecked
|
|
||||||
onChange={() => setRefresh(false)}
|
|
||||||
/>
|
|
||||||
<Form.Text>
|
|
||||||
<FormattedMessage id="performer_tagger.updating_untagged_performers_description" />
|
|
||||||
</Form.Text>
|
|
||||||
<Form.Check
|
|
||||||
id="tagged-performers"
|
|
||||||
type="radio"
|
|
||||||
name="performer-refresh"
|
|
||||||
label={intl.formatMessage({
|
|
||||||
id: "performer_tagger.refresh_tagged_performers",
|
|
||||||
})}
|
|
||||||
defaultChecked={false}
|
|
||||||
onChange={() => setRefresh(true)}
|
|
||||||
/>
|
|
||||||
<Form.Text>
|
|
||||||
<FormattedMessage id="performer_tagger.refreshing_will_update_the_data" />
|
|
||||||
</Form.Text>
|
|
||||||
</Form.Group>
|
|
||||||
<b>
|
|
||||||
<FormattedMessage
|
|
||||||
id="performer_tagger.number_of_performers_will_be_processed"
|
|
||||||
values={{
|
|
||||||
performer_count: queryAll
|
|
||||||
? allPerformers?.findPerformers.count
|
|
||||||
: performers.filter((p) =>
|
|
||||||
refresh ? p.stash_ids.length > 0 : p.stash_ids.length === 0
|
|
||||||
).length,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</b>
|
|
||||||
</ModalComponent>
|
|
||||||
<ModalComponent
|
|
||||||
show={showBatchAdd}
|
|
||||||
icon={faStar}
|
|
||||||
header={intl.formatMessage({
|
|
||||||
id: "performer_tagger.add_new_performers",
|
|
||||||
})}
|
|
||||||
accept={{
|
|
||||||
text: intl.formatMessage({
|
|
||||||
id: "performer_tagger.add_new_performers",
|
|
||||||
}),
|
|
||||||
onClick: handleBatchAdd,
|
|
||||||
}}
|
|
||||||
cancel={{
|
|
||||||
text: intl.formatMessage({ id: "actions.cancel" }),
|
|
||||||
variant: "danger",
|
|
||||||
onClick: () => setShowBatchAdd(false),
|
|
||||||
}}
|
|
||||||
disabled={!isIdle}
|
|
||||||
>
|
|
||||||
<Form.Control
|
|
||||||
className="text-input"
|
|
||||||
as="textarea"
|
|
||||||
ref={performerInput}
|
|
||||||
placeholder={intl.formatMessage({
|
|
||||||
id: "performer_tagger.performer_names_separated_by_comma",
|
|
||||||
})}
|
|
||||||
rows={6}
|
|
||||||
/>
|
/>
|
||||||
<Form.Text>
|
)}
|
||||||
<FormattedMessage id="performer_tagger.any_names_entered_will_be_queried" />
|
|
||||||
</Form.Text>
|
{showBatchAdd && (
|
||||||
</ModalComponent>
|
<PerformerBatchAddModal
|
||||||
|
close={() => setShowBatchAdd(false)}
|
||||||
|
isIdle={isIdle}
|
||||||
|
onBatchAdd={handleBatchAdd}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="ml-auto mb-3">
|
<div className="ml-auto mb-3">
|
||||||
<Button onClick={() => setShowBatchAdd(true)}>
|
<Button onClick={() => setShowBatchAdd(true)}>
|
||||||
<FormattedMessage id="performer_tagger.batch_add_performers" />
|
<FormattedMessage id="performer_tagger.batch_add_performers" />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue