mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Fix tag selector in scrape dialogs (#4526)
This commit is contained in:
@@ -252,6 +252,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
|
||||
return (
|
||||
<GalleryScrapeDialog
|
||||
gallery={currentGallery}
|
||||
galleryTags={tags}
|
||||
galleryPerformers={performers}
|
||||
scraped={scrapedGallery}
|
||||
onClose={(data) => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
ScrapedStringListRow,
|
||||
ScrapedTextAreaRow,
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
import clone from "lodash-es/clone";
|
||||
import {
|
||||
ObjectListScrapeResult,
|
||||
ScrapeResult,
|
||||
@@ -25,21 +24,20 @@ import {
|
||||
useCreateScrapedTag,
|
||||
} from "src/components/Shared/ScrapeDialog/createObjects";
|
||||
import { uniq } from "lodash-es";
|
||||
import { Tag } from "src/components/Tags/TagSelect";
|
||||
|
||||
interface IGalleryScrapeDialogProps {
|
||||
gallery: Partial<GQL.GalleryUpdateInput>;
|
||||
galleryTags: Tag[];
|
||||
galleryPerformers: Performer[];
|
||||
scraped: GQL.ScrapedGallery;
|
||||
|
||||
onClose: (scrapedGallery?: GQL.ScrapedGallery) => void;
|
||||
}
|
||||
|
||||
interface IHasStoredID {
|
||||
stored_id?: string | null;
|
||||
}
|
||||
|
||||
export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||
gallery,
|
||||
galleryTags,
|
||||
galleryPerformers,
|
||||
scraped,
|
||||
onClose,
|
||||
@@ -72,44 +70,6 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||
scraped.studio && !scraped.studio.stored_id ? scraped.studio : undefined
|
||||
);
|
||||
|
||||
function mapStoredIdObjects(
|
||||
scrapedObjects?: IHasStoredID[]
|
||||
): string[] | undefined {
|
||||
if (!scrapedObjects) {
|
||||
return undefined;
|
||||
}
|
||||
const ret = scrapedObjects
|
||||
.map((p) => p.stored_id)
|
||||
.filter((p) => {
|
||||
return p !== undefined && p !== null;
|
||||
}) as string[];
|
||||
|
||||
if (ret.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// sort by id numerically
|
||||
ret.sort((a, b) => {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function sortIdList(idList?: string[] | null) {
|
||||
if (!idList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ret = clone(idList);
|
||||
// sort by id numerically
|
||||
ret.sort((a, b) => {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const [performers, setPerformers] = useState<
|
||||
ObjectListScrapeResult<GQL.ScrapedPerformer>
|
||||
>(
|
||||
@@ -127,10 +87,15 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||
scraped.performers?.filter((t) => !t.stored_id) ?? []
|
||||
);
|
||||
|
||||
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
||||
new ScrapeResult<string[]>(
|
||||
sortIdList(gallery.tag_ids),
|
||||
mapStoredIdObjects(scraped.tags ?? undefined)
|
||||
const [tags, setTags] = useState<ObjectListScrapeResult<GQL.ScrapedTag>>(
|
||||
new ObjectListScrapeResult<GQL.ScrapedTag>(
|
||||
sortStoredIdObjects(
|
||||
galleryTags.map((t) => ({
|
||||
stored_id: t.id,
|
||||
name: t.name,
|
||||
}))
|
||||
),
|
||||
sortStoredIdObjects(scraped.tags ?? undefined)
|
||||
)
|
||||
);
|
||||
const [newTags, setNewTags] = useState<GQL.ScrapedTag[]>(
|
||||
@@ -198,12 +163,7 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||
}
|
||||
: undefined,
|
||||
performers: performers.getNewValue(),
|
||||
tags: tags.getNewValue()?.map((m) => {
|
||||
return {
|
||||
stored_id: m,
|
||||
name: "",
|
||||
};
|
||||
}),
|
||||
tags: tags.getNewValue(),
|
||||
details: details.getNewValue(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -394,6 +394,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
return (
|
||||
<SceneScrapeDialog
|
||||
scene={currentScene}
|
||||
sceneTags={tags}
|
||||
scenePerformers={performers}
|
||||
scraped={scrapedScene}
|
||||
endpoint={endpoint}
|
||||
|
||||
@@ -28,10 +28,12 @@ import {
|
||||
useCreateScrapedStudio,
|
||||
useCreateScrapedTag,
|
||||
} from "src/components/Shared/ScrapeDialog/createObjects";
|
||||
import { Tag } from "src/components/Tags/TagSelect";
|
||||
|
||||
interface ISceneScrapeDialogProps {
|
||||
scene: Partial<GQL.SceneUpdateInput>;
|
||||
scenePerformers: Performer[];
|
||||
sceneTags: Tag[];
|
||||
scraped: GQL.ScrapedScene;
|
||||
endpoint?: string;
|
||||
|
||||
@@ -41,6 +43,7 @@ interface ISceneScrapeDialogProps {
|
||||
export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||
scene,
|
||||
scenePerformers,
|
||||
sceneTags,
|
||||
scraped,
|
||||
onClose,
|
||||
endpoint,
|
||||
@@ -146,10 +149,15 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||
scraped.movies?.filter((t) => !t.stored_id) ?? []
|
||||
);
|
||||
|
||||
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
||||
new ScrapeResult<string[]>(
|
||||
sortIdList(scene.tag_ids),
|
||||
mapStoredIdObjects(scraped.tags ?? undefined)
|
||||
const [tags, setTags] = useState<ObjectListScrapeResult<GQL.ScrapedTag>>(
|
||||
new ObjectListScrapeResult<GQL.ScrapedTag>(
|
||||
sortStoredIdObjects(
|
||||
sceneTags.map((t) => ({
|
||||
stored_id: t.id,
|
||||
name: t.name,
|
||||
}))
|
||||
),
|
||||
sortStoredIdObjects(scraped.tags ?? undefined)
|
||||
)
|
||||
);
|
||||
const [newTags, setNewTags] = useState<GQL.ScrapedTag[]>(
|
||||
@@ -240,12 +248,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||
name: "",
|
||||
};
|
||||
}),
|
||||
tags: tags.getNewValue()?.map((m) => {
|
||||
return {
|
||||
stored_id: m,
|
||||
name: "",
|
||||
};
|
||||
}),
|
||||
tags: tags.getNewValue(),
|
||||
details: details.getNewValue(),
|
||||
image: image.getNewValue(),
|
||||
remote_site_id: stashID.getNewValue(),
|
||||
|
||||
@@ -112,7 +112,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
};
|
||||
}
|
||||
|
||||
function uniqIDStoredIDs(objs: IHasStoredID[]) {
|
||||
function uniqIDStoredIDs<T extends IHasStoredID>(objs: T[]) {
|
||||
return objs.filter((o, i) => {
|
||||
return objs.findIndex((oo) => oo.stored_id === o.stored_id) === i;
|
||||
});
|
||||
@@ -130,8 +130,10 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
new ScrapeResult<string[]>(sortIdList(dest.movies.map((p) => p.movie.id)))
|
||||
);
|
||||
|
||||
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
||||
new ScrapeResult<string[]>(sortIdList(dest.tags.map((t) => t.id)))
|
||||
const [tags, setTags] = useState<ObjectListScrapeResult<GQL.ScrapedTag>>(
|
||||
new ObjectListScrapeResult<GQL.ScrapedTag>(
|
||||
sortStoredIdObjects(dest.tags.map(idToStoredID))
|
||||
)
|
||||
);
|
||||
|
||||
const [details, setDetails] = useState<ScrapeResult<string>>(
|
||||
@@ -210,9 +212,9 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
)
|
||||
);
|
||||
setTags(
|
||||
new ScrapeResult(
|
||||
dest.tags.map((p) => p.id),
|
||||
uniq(all.map((s) => s.tags.map((p) => p.id)).flat())
|
||||
new ObjectListScrapeResult<GQL.ScrapedTag>(
|
||||
sortStoredIdObjects(dest.tags.map(idToStoredID)),
|
||||
uniqIDStoredIDs(all.map((s) => s.tags.map(idToStoredID)).flat())
|
||||
)
|
||||
);
|
||||
setDetails(
|
||||
@@ -592,7 +594,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
||||
scene_index: found!.scene_index,
|
||||
};
|
||||
}),
|
||||
tag_ids: tags.getNewValue(),
|
||||
tag_ids: tags.getNewValue()?.map((t) => t.stored_id!),
|
||||
details: details.getNewValue(),
|
||||
organized: organized.getNewValue(),
|
||||
stash_ids: stashIDs.getNewValue(),
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import React, { useMemo } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import {
|
||||
MovieSelect,
|
||||
TagSelect,
|
||||
StudioSelect,
|
||||
} from "src/components/Shared/Select";
|
||||
import { MovieSelect, StudioSelect } from "src/components/Shared/Select";
|
||||
import {
|
||||
ScrapeDialogRow,
|
||||
IHasName,
|
||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||
import { PerformerSelect } from "src/components/Performers/PerformerSelect";
|
||||
import { ScrapeResult } from "src/components/Shared/ScrapeDialog/scrapeResult";
|
||||
import { TagSelect } from "src/components/Tags/TagSelect";
|
||||
|
||||
interface IScrapedStudioRow {
|
||||
title: string;
|
||||
@@ -230,35 +227,45 @@ export const ScrapedMoviesRow: React.FC<
|
||||
};
|
||||
|
||||
export const ScrapedTagsRow: React.FC<
|
||||
IScrapedObjectRowImpl<GQL.ScrapedTag, string>
|
||||
IScrapedObjectRowImpl<GQL.ScrapedTag, GQL.ScrapedTag>
|
||||
> = ({ title, result, onChange, newObjects, onCreateNew }) => {
|
||||
function renderScrapedTags(
|
||||
scrapeResult: ScrapeResult<string[]>,
|
||||
scrapeResult: ScrapeResult<GQL.ScrapedTag[]>,
|
||||
isNew?: boolean,
|
||||
onChangeFn?: (value: string[]) => void
|
||||
onChangeFn?: (value: GQL.ScrapedTag[]) => void
|
||||
) {
|
||||
const resultValue = isNew
|
||||
? scrapeResult.newValue
|
||||
: scrapeResult.originalValue;
|
||||
const value = resultValue ?? [];
|
||||
|
||||
const selectValue = value.map((p) => {
|
||||
const aliases: string[] = [];
|
||||
return {
|
||||
id: p.stored_id ?? "",
|
||||
name: p.name ?? "",
|
||||
aliases,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<TagSelect
|
||||
isMulti
|
||||
className="form-control react-select"
|
||||
className="form-control"
|
||||
isDisabled={!isNew}
|
||||
onSelect={(items) => {
|
||||
if (onChangeFn) {
|
||||
onChangeFn(items.map((i) => i.id));
|
||||
// map the id back to stored_id
|
||||
onChangeFn(items.map((p) => ({ ...p, stored_id: p.id })));
|
||||
}
|
||||
}}
|
||||
ids={value}
|
||||
values={selectValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrapedObjectsRow<GQL.ScrapedTag, string>
|
||||
<ScrapedObjectsRow<GQL.ScrapedTag, GQL.ScrapedTag>
|
||||
title={title}
|
||||
result={result}
|
||||
renderObjects={renderScrapedTags}
|
||||
|
||||
@@ -69,14 +69,16 @@ export function useCreateScrapedStudio(props: IUseCreateNewStudioProps) {
|
||||
return useCreateObject("studio", createNewStudio);
|
||||
}
|
||||
|
||||
interface IUseCreateNewPerformerProps {
|
||||
scrapeResult: ScrapeResult<GQL.ScrapedPerformer[]>;
|
||||
setScrapeResult: (scrapeResult: ScrapeResult<GQL.ScrapedPerformer[]>) => void;
|
||||
newObjects: GQL.ScrapedPerformer[];
|
||||
setNewObjects: (newObject: GQL.ScrapedPerformer[]) => void;
|
||||
interface IUseCreateNewObjectProps<T> {
|
||||
scrapeResult: ScrapeResult<T[]>;
|
||||
setScrapeResult: (scrapeResult: ScrapeResult<T[]>) => void;
|
||||
newObjects: T[];
|
||||
setNewObjects: (newObject: T[]) => void;
|
||||
}
|
||||
|
||||
export function useCreateScrapedPerformer(props: IUseCreateNewPerformerProps) {
|
||||
export function useCreateScrapedPerformer(
|
||||
props: IUseCreateNewObjectProps<GQL.ScrapedPerformer>
|
||||
) {
|
||||
const [createPerformer] = usePerformerCreate();
|
||||
|
||||
const { scrapeResult, setScrapeResult, newObjects, setNewObjects } = props;
|
||||
@@ -173,20 +175,39 @@ export function useCreateScrapedMovie(
|
||||
}
|
||||
|
||||
export function useCreateScrapedTag(
|
||||
props: IUseCreateNewObjectIDListProps<GQL.ScrapedTag>
|
||||
props: IUseCreateNewObjectProps<GQL.ScrapedTag>
|
||||
) {
|
||||
const [createTag] = useTagCreate();
|
||||
|
||||
const { scrapeResult, setScrapeResult, newObjects, setNewObjects } = props;
|
||||
|
||||
async function createNewTag(toCreate: GQL.ScrapedTag) {
|
||||
const tagInput: GQL.TagCreateInput = { name: toCreate.name ?? "" };
|
||||
const input: GQL.TagCreateInput = { name: toCreate.name ?? "" };
|
||||
|
||||
const result = await createTag({
|
||||
variables: {
|
||||
input: tagInput,
|
||||
},
|
||||
variables: { input },
|
||||
});
|
||||
|
||||
return result.data?.tagCreate?.id ?? "";
|
||||
const newValue = [...(scrapeResult.newValue ?? [])];
|
||||
if (result.data?.tagCreate)
|
||||
newValue.push({
|
||||
stored_id: result.data.tagCreate.id,
|
||||
name: result.data.tagCreate.name,
|
||||
});
|
||||
|
||||
// add the new tag to the new tags value
|
||||
const tagClone = scrapeResult.cloneWithValue(newValue);
|
||||
setScrapeResult(tagClone);
|
||||
|
||||
// remove the tag from the list
|
||||
const newTagsClone = newObjects.concat();
|
||||
const pIndex = newTagsClone.findIndex((p) => p.name === toCreate.name);
|
||||
if (pIndex === -1) throw new Error("Could not find tag to remove");
|
||||
|
||||
newTagsClone.splice(pIndex, 1);
|
||||
|
||||
setNewObjects(newTagsClone);
|
||||
}
|
||||
|
||||
return useCreateNewObjectIDList("tag", props, createNewTag);
|
||||
return useCreateObject("tag", createNewTag);
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ export interface IHasStoredID {
|
||||
stored_id?: string | null;
|
||||
}
|
||||
|
||||
export function sortStoredIdObjects(
|
||||
scrapedObjects?: IHasStoredID[]
|
||||
): IHasStoredID[] | undefined {
|
||||
export function sortStoredIdObjects<T extends IHasStoredID>(
|
||||
scrapedObjects?: T[]
|
||||
): T[] | undefined {
|
||||
if (!scrapedObjects) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user