Fix tag selector in scrape dialogs (#4526)

This commit is contained in:
WithoutPants
2024-02-06 10:26:16 +11:00
committed by GitHub
parent 3ea31aeb76
commit 217c02f181
8 changed files with 93 additions and 98 deletions

View File

@@ -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) => {

View File

@@ -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(),
}; };
} }

View File

@@ -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}

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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}

View File

@@ -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);
} }

View File

@@ -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;
} }