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 (
|
return (
|
||||||
<GalleryScrapeDialog
|
<GalleryScrapeDialog
|
||||||
gallery={currentGallery}
|
gallery={currentGallery}
|
||||||
|
galleryTags={tags}
|
||||||
galleryPerformers={performers}
|
galleryPerformers={performers}
|
||||||
scraped={scrapedGallery}
|
scraped={scrapedGallery}
|
||||||
onClose={(data) => {
|
onClose={(data) => {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
ScrapedStringListRow,
|
ScrapedStringListRow,
|
||||||
ScrapedTextAreaRow,
|
ScrapedTextAreaRow,
|
||||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||||
import clone from "lodash-es/clone";
|
|
||||||
import {
|
import {
|
||||||
ObjectListScrapeResult,
|
ObjectListScrapeResult,
|
||||||
ScrapeResult,
|
ScrapeResult,
|
||||||
@@ -25,21 +24,20 @@ import {
|
|||||||
useCreateScrapedTag,
|
useCreateScrapedTag,
|
||||||
} from "src/components/Shared/ScrapeDialog/createObjects";
|
} from "src/components/Shared/ScrapeDialog/createObjects";
|
||||||
import { uniq } from "lodash-es";
|
import { uniq } from "lodash-es";
|
||||||
|
import { Tag } from "src/components/Tags/TagSelect";
|
||||||
|
|
||||||
interface IGalleryScrapeDialogProps {
|
interface IGalleryScrapeDialogProps {
|
||||||
gallery: Partial<GQL.GalleryUpdateInput>;
|
gallery: Partial<GQL.GalleryUpdateInput>;
|
||||||
|
galleryTags: Tag[];
|
||||||
galleryPerformers: Performer[];
|
galleryPerformers: Performer[];
|
||||||
scraped: GQL.ScrapedGallery;
|
scraped: GQL.ScrapedGallery;
|
||||||
|
|
||||||
onClose: (scrapedGallery?: GQL.ScrapedGallery) => void;
|
onClose: (scrapedGallery?: GQL.ScrapedGallery) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IHasStoredID {
|
|
||||||
stored_id?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
||||||
gallery,
|
gallery,
|
||||||
|
galleryTags,
|
||||||
galleryPerformers,
|
galleryPerformers,
|
||||||
scraped,
|
scraped,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -72,44 +70,6 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
|||||||
scraped.studio && !scraped.studio.stored_id ? scraped.studio : undefined
|
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<
|
const [performers, setPerformers] = useState<
|
||||||
ObjectListScrapeResult<GQL.ScrapedPerformer>
|
ObjectListScrapeResult<GQL.ScrapedPerformer>
|
||||||
>(
|
>(
|
||||||
@@ -127,10 +87,15 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
|||||||
scraped.performers?.filter((t) => !t.stored_id) ?? []
|
scraped.performers?.filter((t) => !t.stored_id) ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
const [tags, setTags] = useState<ObjectListScrapeResult<GQL.ScrapedTag>>(
|
||||||
new ScrapeResult<string[]>(
|
new ObjectListScrapeResult<GQL.ScrapedTag>(
|
||||||
sortIdList(gallery.tag_ids),
|
sortStoredIdObjects(
|
||||||
mapStoredIdObjects(scraped.tags ?? undefined)
|
galleryTags.map((t) => ({
|
||||||
|
stored_id: t.id,
|
||||||
|
name: t.name,
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
sortStoredIdObjects(scraped.tags ?? undefined)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const [newTags, setNewTags] = useState<GQL.ScrapedTag[]>(
|
const [newTags, setNewTags] = useState<GQL.ScrapedTag[]>(
|
||||||
@@ -198,12 +163,7 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
performers: performers.getNewValue(),
|
performers: performers.getNewValue(),
|
||||||
tags: tags.getNewValue()?.map((m) => {
|
tags: tags.getNewValue(),
|
||||||
return {
|
|
||||||
stored_id: m,
|
|
||||||
name: "",
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
details: details.getNewValue(),
|
details: details.getNewValue(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
|||||||
return (
|
return (
|
||||||
<SceneScrapeDialog
|
<SceneScrapeDialog
|
||||||
scene={currentScene}
|
scene={currentScene}
|
||||||
|
sceneTags={tags}
|
||||||
scenePerformers={performers}
|
scenePerformers={performers}
|
||||||
scraped={scrapedScene}
|
scraped={scrapedScene}
|
||||||
endpoint={endpoint}
|
endpoint={endpoint}
|
||||||
|
|||||||
@@ -28,10 +28,12 @@ import {
|
|||||||
useCreateScrapedStudio,
|
useCreateScrapedStudio,
|
||||||
useCreateScrapedTag,
|
useCreateScrapedTag,
|
||||||
} from "src/components/Shared/ScrapeDialog/createObjects";
|
} from "src/components/Shared/ScrapeDialog/createObjects";
|
||||||
|
import { Tag } from "src/components/Tags/TagSelect";
|
||||||
|
|
||||||
interface ISceneScrapeDialogProps {
|
interface ISceneScrapeDialogProps {
|
||||||
scene: Partial<GQL.SceneUpdateInput>;
|
scene: Partial<GQL.SceneUpdateInput>;
|
||||||
scenePerformers: Performer[];
|
scenePerformers: Performer[];
|
||||||
|
sceneTags: Tag[];
|
||||||
scraped: GQL.ScrapedScene;
|
scraped: GQL.ScrapedScene;
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ interface ISceneScrapeDialogProps {
|
|||||||
export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
||||||
scene,
|
scene,
|
||||||
scenePerformers,
|
scenePerformers,
|
||||||
|
sceneTags,
|
||||||
scraped,
|
scraped,
|
||||||
onClose,
|
onClose,
|
||||||
endpoint,
|
endpoint,
|
||||||
@@ -146,10 +149,15 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
|||||||
scraped.movies?.filter((t) => !t.stored_id) ?? []
|
scraped.movies?.filter((t) => !t.stored_id) ?? []
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
const [tags, setTags] = useState<ObjectListScrapeResult<GQL.ScrapedTag>>(
|
||||||
new ScrapeResult<string[]>(
|
new ObjectListScrapeResult<GQL.ScrapedTag>(
|
||||||
sortIdList(scene.tag_ids),
|
sortStoredIdObjects(
|
||||||
mapStoredIdObjects(scraped.tags ?? undefined)
|
sceneTags.map((t) => ({
|
||||||
|
stored_id: t.id,
|
||||||
|
name: t.name,
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
sortStoredIdObjects(scraped.tags ?? undefined)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const [newTags, setNewTags] = useState<GQL.ScrapedTag[]>(
|
const [newTags, setNewTags] = useState<GQL.ScrapedTag[]>(
|
||||||
@@ -240,12 +248,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
|
|||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
tags: tags.getNewValue()?.map((m) => {
|
tags: tags.getNewValue(),
|
||||||
return {
|
|
||||||
stored_id: m,
|
|
||||||
name: "",
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
details: details.getNewValue(),
|
details: details.getNewValue(),
|
||||||
image: image.getNewValue(),
|
image: image.getNewValue(),
|
||||||
remote_site_id: stashID.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.filter((o, i) => {
|
||||||
return objs.findIndex((oo) => oo.stored_id === o.stored_id) === 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)))
|
new ScrapeResult<string[]>(sortIdList(dest.movies.map((p) => p.movie.id)))
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
const [tags, setTags] = useState<ObjectListScrapeResult<GQL.ScrapedTag>>(
|
||||||
new ScrapeResult<string[]>(sortIdList(dest.tags.map((t) => t.id)))
|
new ObjectListScrapeResult<GQL.ScrapedTag>(
|
||||||
|
sortStoredIdObjects(dest.tags.map(idToStoredID))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [details, setDetails] = useState<ScrapeResult<string>>(
|
const [details, setDetails] = useState<ScrapeResult<string>>(
|
||||||
@@ -210,9 +212,9 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
setTags(
|
setTags(
|
||||||
new ScrapeResult(
|
new ObjectListScrapeResult<GQL.ScrapedTag>(
|
||||||
dest.tags.map((p) => p.id),
|
sortStoredIdObjects(dest.tags.map(idToStoredID)),
|
||||||
uniq(all.map((s) => s.tags.map((p) => p.id)).flat())
|
uniqIDStoredIDs(all.map((s) => s.tags.map(idToStoredID)).flat())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
setDetails(
|
setDetails(
|
||||||
@@ -592,7 +594,7 @@ const SceneMergeDetails: React.FC<ISceneMergeDetailsProps> = ({
|
|||||||
scene_index: found!.scene_index,
|
scene_index: found!.scene_index,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
tag_ids: tags.getNewValue(),
|
tag_ids: tags.getNewValue()?.map((t) => t.stored_id!),
|
||||||
details: details.getNewValue(),
|
details: details.getNewValue(),
|
||||||
organized: organized.getNewValue(),
|
organized: organized.getNewValue(),
|
||||||
stash_ids: stashIDs.getNewValue(),
|
stash_ids: stashIDs.getNewValue(),
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import {
|
import { MovieSelect, StudioSelect } from "src/components/Shared/Select";
|
||||||
MovieSelect,
|
|
||||||
TagSelect,
|
|
||||||
StudioSelect,
|
|
||||||
} from "src/components/Shared/Select";
|
|
||||||
import {
|
import {
|
||||||
ScrapeDialogRow,
|
ScrapeDialogRow,
|
||||||
IHasName,
|
IHasName,
|
||||||
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
} from "src/components/Shared/ScrapeDialog/ScrapeDialog";
|
||||||
import { PerformerSelect } from "src/components/Performers/PerformerSelect";
|
import { PerformerSelect } from "src/components/Performers/PerformerSelect";
|
||||||
import { ScrapeResult } from "src/components/Shared/ScrapeDialog/scrapeResult";
|
import { ScrapeResult } from "src/components/Shared/ScrapeDialog/scrapeResult";
|
||||||
|
import { TagSelect } from "src/components/Tags/TagSelect";
|
||||||
|
|
||||||
interface IScrapedStudioRow {
|
interface IScrapedStudioRow {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -230,35 +227,45 @@ export const ScrapedMoviesRow: React.FC<
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ScrapedTagsRow: React.FC<
|
export const ScrapedTagsRow: React.FC<
|
||||||
IScrapedObjectRowImpl<GQL.ScrapedTag, string>
|
IScrapedObjectRowImpl<GQL.ScrapedTag, GQL.ScrapedTag>
|
||||||
> = ({ title, result, onChange, newObjects, onCreateNew }) => {
|
> = ({ title, result, onChange, newObjects, onCreateNew }) => {
|
||||||
function renderScrapedTags(
|
function renderScrapedTags(
|
||||||
scrapeResult: ScrapeResult<string[]>,
|
scrapeResult: ScrapeResult<GQL.ScrapedTag[]>,
|
||||||
isNew?: boolean,
|
isNew?: boolean,
|
||||||
onChangeFn?: (value: string[]) => void
|
onChangeFn?: (value: GQL.ScrapedTag[]) => void
|
||||||
) {
|
) {
|
||||||
const resultValue = isNew
|
const resultValue = isNew
|
||||||
? scrapeResult.newValue
|
? scrapeResult.newValue
|
||||||
: scrapeResult.originalValue;
|
: scrapeResult.originalValue;
|
||||||
const value = resultValue ?? [];
|
const value = resultValue ?? [];
|
||||||
|
|
||||||
|
const selectValue = value.map((p) => {
|
||||||
|
const aliases: string[] = [];
|
||||||
|
return {
|
||||||
|
id: p.stored_id ?? "",
|
||||||
|
name: p.name ?? "",
|
||||||
|
aliases,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TagSelect
|
<TagSelect
|
||||||
isMulti
|
isMulti
|
||||||
className="form-control react-select"
|
className="form-control"
|
||||||
isDisabled={!isNew}
|
isDisabled={!isNew}
|
||||||
onSelect={(items) => {
|
onSelect={(items) => {
|
||||||
if (onChangeFn) {
|
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 (
|
return (
|
||||||
<ScrapedObjectsRow<GQL.ScrapedTag, string>
|
<ScrapedObjectsRow<GQL.ScrapedTag, GQL.ScrapedTag>
|
||||||
title={title}
|
title={title}
|
||||||
result={result}
|
result={result}
|
||||||
renderObjects={renderScrapedTags}
|
renderObjects={renderScrapedTags}
|
||||||
|
|||||||
@@ -69,14 +69,16 @@ export function useCreateScrapedStudio(props: IUseCreateNewStudioProps) {
|
|||||||
return useCreateObject("studio", createNewStudio);
|
return useCreateObject("studio", createNewStudio);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUseCreateNewPerformerProps {
|
interface IUseCreateNewObjectProps<T> {
|
||||||
scrapeResult: ScrapeResult<GQL.ScrapedPerformer[]>;
|
scrapeResult: ScrapeResult<T[]>;
|
||||||
setScrapeResult: (scrapeResult: ScrapeResult<GQL.ScrapedPerformer[]>) => void;
|
setScrapeResult: (scrapeResult: ScrapeResult<T[]>) => void;
|
||||||
newObjects: GQL.ScrapedPerformer[];
|
newObjects: T[];
|
||||||
setNewObjects: (newObject: GQL.ScrapedPerformer[]) => void;
|
setNewObjects: (newObject: T[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateScrapedPerformer(props: IUseCreateNewPerformerProps) {
|
export function useCreateScrapedPerformer(
|
||||||
|
props: IUseCreateNewObjectProps<GQL.ScrapedPerformer>
|
||||||
|
) {
|
||||||
const [createPerformer] = usePerformerCreate();
|
const [createPerformer] = usePerformerCreate();
|
||||||
|
|
||||||
const { scrapeResult, setScrapeResult, newObjects, setNewObjects } = props;
|
const { scrapeResult, setScrapeResult, newObjects, setNewObjects } = props;
|
||||||
@@ -173,20 +175,39 @@ export function useCreateScrapedMovie(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateScrapedTag(
|
export function useCreateScrapedTag(
|
||||||
props: IUseCreateNewObjectIDListProps<GQL.ScrapedTag>
|
props: IUseCreateNewObjectProps<GQL.ScrapedTag>
|
||||||
) {
|
) {
|
||||||
const [createTag] = useTagCreate();
|
const [createTag] = useTagCreate();
|
||||||
|
|
||||||
|
const { scrapeResult, setScrapeResult, newObjects, setNewObjects } = props;
|
||||||
|
|
||||||
async function createNewTag(toCreate: GQL.ScrapedTag) {
|
async function createNewTag(toCreate: GQL.ScrapedTag) {
|
||||||
const tagInput: GQL.TagCreateInput = { name: toCreate.name ?? "" };
|
const input: GQL.TagCreateInput = { name: toCreate.name ?? "" };
|
||||||
|
|
||||||
const result = await createTag({
|
const result = await createTag({
|
||||||
variables: {
|
variables: { input },
|
||||||
input: tagInput,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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;
|
stored_id?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortStoredIdObjects(
|
export function sortStoredIdObjects<T extends IHasStoredID>(
|
||||||
scrapedObjects?: IHasStoredID[]
|
scrapedObjects?: T[]
|
||||||
): IHasStoredID[] | undefined {
|
): T[] | undefined {
|
||||||
if (!scrapedObjects) {
|
if (!scrapedObjects) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user