mirror of
https://github.com/stashapp/stash.git
synced 2026-04-19 13:31:15 +02:00
Show dialog when refreshing tags
This commit is contained in:
parent
091ac26f88
commit
56cb04fcc4
3 changed files with 142 additions and 58 deletions
|
|
@ -295,7 +295,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
&-studio {
|
||||
&-studio,
|
||||
&-tag {
|
||||
background-color: #495b68;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
|
|
@ -303,7 +304,8 @@
|
|||
max-width: 100%;
|
||||
padding: 1rem;
|
||||
|
||||
.studio-card {
|
||||
.studio-card,
|
||||
.tag-card {
|
||||
box-shadow: none;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
|
|
@ -47,6 +47,10 @@ const TagModal: React.FC<ITagModalProps> = ({
|
|||
!!tag.parent && !tag.parent.stored_id
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCreateParentTag(!excluded.parent_ids && !!tag.parent);
|
||||
}, [excluded.parent_ids, tag.parent]);
|
||||
|
||||
// Check if a tag with the parent name already exists locally.
|
||||
// Categories don't have stash IDs, so stored_id may be null even when the
|
||||
// parent tag has already been created (e.g. by tagging a sibling tag first).
|
||||
|
|
@ -70,6 +74,7 @@ const TagModal: React.FC<ITagModalProps> = ({
|
|||
const [parentExcluded, setParentExcluded] = useState<Record<string, boolean>>(
|
||||
excludedTagFields.reduce((dict, field) => ({ ...dict, [field]: true }), {})
|
||||
);
|
||||
|
||||
const toggleParentField = (name: string) =>
|
||||
setParentExcluded({
|
||||
...parentExcluded,
|
||||
|
|
@ -79,9 +84,11 @@ const TagModal: React.FC<ITagModalProps> = ({
|
|||
function maybeRenderField(
|
||||
id: string,
|
||||
text: string | null | undefined,
|
||||
isSelectable: boolean = true
|
||||
isSelectable: boolean = true,
|
||||
messageId?: string
|
||||
) {
|
||||
if (!text) return;
|
||||
if (!messageId) messageId = id;
|
||||
|
||||
return (
|
||||
<div className="row no-gutters">
|
||||
|
|
@ -96,7 +103,7 @@ const TagModal: React.FC<ITagModalProps> = ({
|
|||
</Button>
|
||||
)}
|
||||
<strong>
|
||||
<FormattedMessage id={id} />:
|
||||
<FormattedMessage id={messageId} />:
|
||||
</strong>
|
||||
</div>
|
||||
<TruncatedText className="col-7" text={text} lineCount={3} />
|
||||
|
|
@ -159,10 +166,18 @@ const TagModal: React.FC<ITagModalProps> = ({
|
|||
|
||||
function maybeRenderParentTag() {
|
||||
// No parent tag, or parent already exists locally
|
||||
if (!tag.parent || tag.parent.stored_id || !sendParentTag) {
|
||||
if (
|
||||
!tag.parent ||
|
||||
tag.parent.stored_id ||
|
||||
!sendParentTag ||
|
||||
excluded.parent_ids
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// force create if there is no current parent tag and parent tag is not excluded
|
||||
const mustCreateParent = true;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4 mt-4">
|
||||
|
|
@ -172,6 +187,7 @@ const TagModal: React.FC<ITagModalProps> = ({
|
|||
label={intl.formatMessage({
|
||||
id: "actions.create_parent_tag",
|
||||
})}
|
||||
disabled={mustCreateParent}
|
||||
onChange={() => setCreateParentTag(!createParentTag)}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -251,7 +267,12 @@ const TagModal: React.FC<ITagModalProps> = ({
|
|||
{maybeRenderField("name", tag.name)}
|
||||
{maybeRenderField("description", tag.description)}
|
||||
{maybeRenderField("aliases", tag.alias_list?.join(", "))}
|
||||
{maybeRenderField("parent_tags", tag.parent?.name, false)}
|
||||
{maybeRenderField(
|
||||
"parent_ids",
|
||||
tag.parent?.name,
|
||||
true,
|
||||
"parent_tags"
|
||||
)}
|
||||
{maybeRenderStashBoxLink()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
useJobsSubscribe,
|
||||
mutateStashBoxBatchTagTag,
|
||||
getClient,
|
||||
useTagCreate,
|
||||
} from "src/core/StashService";
|
||||
import { useConfigurationContext } from "src/hooks/Config";
|
||||
|
||||
|
|
@ -27,6 +28,10 @@ import {
|
|||
BatchAddModal,
|
||||
} from "src/components/Shared/BatchModals";
|
||||
import { StashBoxSelectorField } from "../StashBoxSelector";
|
||||
import { apolloError } from "src/utils";
|
||||
import TagModal from "./TagModal";
|
||||
import { faTags } from "@fortawesome/free-solid-svg-icons";
|
||||
import { uniq } from "lodash-es";
|
||||
|
||||
type JobFragment = Pick<
|
||||
GQL.Job,
|
||||
|
|
@ -59,6 +64,7 @@ const TagTaggerList: React.FC<ITagTaggerListProps> = ({
|
|||
const intl = useIntl();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [searchResults, setSearchResults] = useState<
|
||||
Record<string, GQL.ScrapedSceneTagDataFragment[]>
|
||||
>({});
|
||||
|
|
@ -94,6 +100,13 @@ const TagTaggerList: React.FC<ITagTaggerListProps> = ({
|
|||
},
|
||||
});
|
||||
|
||||
const [modalTag, setModalTag] = useState<
|
||||
| {
|
||||
existingTag: GQL.TagListDataFragment;
|
||||
scrapedTag: GQL.ScrapedSceneTagDataFragment;
|
||||
}
|
||||
| undefined
|
||||
>();
|
||||
const [error, setError] = useState<
|
||||
Record<string, { message?: string; details?: string } | undefined>
|
||||
>({});
|
||||
|
|
@ -128,64 +141,30 @@ const TagTaggerList: React.FC<ITagTaggerListProps> = ({
|
|||
setLoading(true);
|
||||
};
|
||||
|
||||
const [createTag] = useTagCreate();
|
||||
const updateTag = useUpdateTag();
|
||||
|
||||
const doBoxUpdate = (tagID: string, stashID: string, endpoint: string) => {
|
||||
const doBoxUpdate = (
|
||||
tag: GQL.TagListDataFragment,
|
||||
stashID: string,
|
||||
endpoint: string
|
||||
) => {
|
||||
setLoadingUpdate(stashID);
|
||||
setError({
|
||||
...error,
|
||||
[tagID]: undefined,
|
||||
[tag.id]: undefined,
|
||||
});
|
||||
stashBoxTagQuery(stashID, endpoint)
|
||||
.then(async (queryData) => {
|
||||
const data = queryData.data?.scrapeSingleTag ?? [];
|
||||
if (data.length > 0) {
|
||||
const stashboxTag = data[0];
|
||||
const updateData: GQL.TagUpdateInput = {
|
||||
id: tagID,
|
||||
};
|
||||
|
||||
if (
|
||||
!(config.excludedTagFields ?? []).includes("name") &&
|
||||
stashboxTag.name
|
||||
) {
|
||||
updateData.name = stashboxTag.name;
|
||||
}
|
||||
|
||||
if (
|
||||
stashboxTag.description &&
|
||||
!(config.excludedTagFields ?? []).includes("description")
|
||||
) {
|
||||
updateData.description = stashboxTag.description;
|
||||
}
|
||||
|
||||
if (
|
||||
stashboxTag.alias_list &&
|
||||
stashboxTag.alias_list.length > 0 &&
|
||||
!(config.excludedTagFields ?? []).includes("aliases")
|
||||
) {
|
||||
updateData.aliases = stashboxTag.alias_list;
|
||||
}
|
||||
|
||||
if (stashboxTag.remote_site_id) {
|
||||
updateData.stash_ids = await mergeTagStashIDs(tagID, [
|
||||
{
|
||||
endpoint,
|
||||
stash_id: stashboxTag.remote_site_id,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const res = await updateTag(updateData);
|
||||
if (!res?.data?.tagUpdate) {
|
||||
setError({
|
||||
...error,
|
||||
[tagID]: {
|
||||
message: `Failed to update tag`,
|
||||
details: res?.errors?.[0]?.message ?? "",
|
||||
},
|
||||
});
|
||||
}
|
||||
setModalTag({
|
||||
scrapedTag: {
|
||||
...data[0],
|
||||
stored_id: tag.id,
|
||||
},
|
||||
existingTag: tag,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => setLoadingUpdate(undefined));
|
||||
|
|
@ -205,6 +184,75 @@ const TagTaggerList: React.FC<ITagTaggerListProps> = ({
|
|||
setShowBatchUpdate(false);
|
||||
};
|
||||
|
||||
function handleSaveError(tagID: string, name: string, message: string) {
|
||||
setError({
|
||||
...error,
|
||||
[tagID]: {
|
||||
message: intl.formatMessage(
|
||||
{ id: "tag_tagger.failed_to_save_tag" },
|
||||
{ tag: name }
|
||||
),
|
||||
details:
|
||||
message === "UNIQUE constraint failed: tags.name"
|
||||
? intl.formatMessage({
|
||||
id: "tag_tagger.name_already_exists",
|
||||
})
|
||||
: message,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const handleTagUpdate = async (
|
||||
input: GQL.TagCreateInput,
|
||||
parentInput?: GQL.TagCreateInput
|
||||
) => {
|
||||
const { existingTag, scrapedTag: tag } = modalTag!;
|
||||
const tagID = existingTag.id;
|
||||
setModalTag(undefined);
|
||||
|
||||
if (tagID) {
|
||||
if (parentInput) {
|
||||
try {
|
||||
// cannot update parent tags, since there may be many
|
||||
if (!!input.parent_ids?.length) {
|
||||
// ignore
|
||||
} else {
|
||||
const parentRes = await createTag({
|
||||
variables: { input: parentInput },
|
||||
});
|
||||
const parentID = parentRes.data?.tagCreate?.id;
|
||||
if (parentID) {
|
||||
// merge parent ids below
|
||||
input.parent_ids = [parentID];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
handleSaveError(tagID, parentInput.name, apolloError(e));
|
||||
}
|
||||
}
|
||||
|
||||
// always merge parent ids if included
|
||||
if (input.parent_ids) {
|
||||
input.parent_ids = uniq(
|
||||
existingTag.parents.map((p) => p.id).concat(input.parent_ids)
|
||||
);
|
||||
}
|
||||
|
||||
const updateData: GQL.TagUpdateInput = {
|
||||
...input,
|
||||
id: tagID,
|
||||
};
|
||||
updateData.stash_ids = await mergeTagStashIDs(
|
||||
tagID,
|
||||
input.stash_ids ?? []
|
||||
);
|
||||
|
||||
const res = await updateTag(updateData);
|
||||
if (!res?.data?.tagUpdate)
|
||||
handleSaveError(tagID, tag.name ?? "", res?.errors?.[0]?.message ?? "");
|
||||
}
|
||||
};
|
||||
|
||||
const handleTaggedTag = (
|
||||
tag: Pick<GQL.TagListDataFragment, "id"> &
|
||||
Partial<Omit<GQL.TagListDataFragment, "id">>
|
||||
|
|
@ -292,7 +340,7 @@ const TagTaggerList: React.FC<ITagTaggerListProps> = ({
|
|||
<InputGroup.Append>
|
||||
<Button
|
||||
onClick={() =>
|
||||
doBoxUpdate(tag.id, stashID.stash_id, stashID.endpoint)
|
||||
doBoxUpdate(tag, stashID.stash_id, stashID.endpoint)
|
||||
}
|
||||
disabled={!!loadingUpdate}
|
||||
>
|
||||
|
|
@ -344,11 +392,11 @@ const TagTaggerList: React.FC<ITagTaggerListProps> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div key={tag.id} className={`${CLASSNAME}-studio`}>
|
||||
<div key={tag.id} className={`${CLASSNAME}-tag`}>
|
||||
<div className={`${CLASSNAME}-details`}>
|
||||
<div></div>
|
||||
<div>
|
||||
<Card className="studio-card">
|
||||
<Card className="tag-card">
|
||||
<img loading="lazy" src={tag.image_path ?? ""} alt="" />
|
||||
</Card>
|
||||
</div>
|
||||
|
|
@ -395,6 +443,19 @@ const TagTaggerList: React.FC<ITagTaggerListProps> = ({
|
|||
entityName="tag"
|
||||
/>
|
||||
)}
|
||||
|
||||
{modalTag && (
|
||||
<TagModal
|
||||
closeModal={() => setModalTag(undefined)}
|
||||
modalVisible={modalTag !== undefined}
|
||||
tag={modalTag.scrapedTag}
|
||||
onSave={handleTagUpdate}
|
||||
icon={faTags}
|
||||
header="Update Tag"
|
||||
excludedTagFields={config.excludedTagFields}
|
||||
endpoint={selectedEndpoint.endpoint}
|
||||
/>
|
||||
)}
|
||||
<div className="ml-auto mb-3">
|
||||
<Button onClick={() => setShowBatchAdd(true)}>
|
||||
<FormattedMessage id="tag_tagger.batch_add_tags" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue