mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Show and allow creation of unknown performers/tags/studios/movies from scraper dialog (#741)
* Fix toast container z-index * Make scrape operations network only * Add CollapseButton component
This commit is contained in:
@@ -11,6 +11,13 @@ import {
|
||||
ScrapedImageRow,
|
||||
} from "src/components/Shared/ScrapeDialog";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
useStudioCreate,
|
||||
usePerformerCreate,
|
||||
useMovieCreate,
|
||||
useTagCreate,
|
||||
} from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
|
||||
function renderScrapedStudio(
|
||||
result: ScrapeResult<string>,
|
||||
@@ -36,7 +43,9 @@ function renderScrapedStudio(
|
||||
|
||||
function renderScrapedStudioRow(
|
||||
result: ScrapeResult<string>,
|
||||
onChange: (value: ScrapeResult<string>) => void
|
||||
onChange: (value: ScrapeResult<string>) => void,
|
||||
newStudio?: GQL.ScrapedSceneStudio,
|
||||
onCreateNew?: (value: GQL.ScrapedSceneStudio) => void
|
||||
) {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
@@ -49,6 +58,8 @@ function renderScrapedStudioRow(
|
||||
)
|
||||
}
|
||||
onChange={onChange}
|
||||
newValues={newStudio ? [newStudio] : undefined}
|
||||
onCreateNew={onCreateNew}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -78,7 +89,9 @@ function renderScrapedPerformers(
|
||||
|
||||
function renderScrapedPerformersRow(
|
||||
result: ScrapeResult<string[]>,
|
||||
onChange: (value: ScrapeResult<string[]>) => void
|
||||
onChange: (value: ScrapeResult<string[]>) => void,
|
||||
newPerformers: GQL.ScrapedScenePerformer[],
|
||||
onCreateNew?: (value: GQL.ScrapedScenePerformer) => void
|
||||
) {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
@@ -91,6 +104,8 @@ function renderScrapedPerformersRow(
|
||||
)
|
||||
}
|
||||
onChange={onChange}
|
||||
newValues={newPerformers}
|
||||
onCreateNew={onCreateNew}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -120,7 +135,9 @@ function renderScrapedMovies(
|
||||
|
||||
function renderScrapedMoviesRow(
|
||||
result: ScrapeResult<string[]>,
|
||||
onChange: (value: ScrapeResult<string[]>) => void
|
||||
onChange: (value: ScrapeResult<string[]>) => void,
|
||||
newMovies: GQL.ScrapedSceneMovie[],
|
||||
onCreateNew?: (value: GQL.ScrapedSceneMovie) => void
|
||||
) {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
@@ -133,6 +150,8 @@ function renderScrapedMoviesRow(
|
||||
)
|
||||
}
|
||||
onChange={onChange}
|
||||
newValues={newMovies}
|
||||
onCreateNew={onCreateNew}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -162,7 +181,9 @@ function renderScrapedTags(
|
||||
|
||||
function renderScrapedTagsRow(
|
||||
result: ScrapeResult<string[]>,
|
||||
onChange: (value: ScrapeResult<string[]>) => void
|
||||
onChange: (value: ScrapeResult<string[]>) => void,
|
||||
newTags: GQL.ScrapedSceneTag[],
|
||||
onCreateNew?: (value: GQL.ScrapedSceneTag) => void
|
||||
) {
|
||||
return (
|
||||
<ScrapeDialogRow
|
||||
@@ -174,7 +195,9 @@ function renderScrapedTagsRow(
|
||||
onChange(result.cloneWithValue(value))
|
||||
)
|
||||
}
|
||||
newValues={newTags}
|
||||
onChange={onChange}
|
||||
onCreateNew={onCreateNew}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -186,8 +209,8 @@ interface ISceneScrapeDialogProps {
|
||||
onClose: (scrapedScene?: GQL.ScrapedScene) => void;
|
||||
}
|
||||
|
||||
interface IHasID {
|
||||
id?: string | null;
|
||||
interface IHasStoredID {
|
||||
stored_id?: string | null;
|
||||
}
|
||||
|
||||
export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
||||
@@ -203,15 +226,27 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
||||
new ScrapeResult<string>(props.scene.date, props.scraped.date)
|
||||
);
|
||||
const [studio, setStudio] = useState<ScrapeResult<string>>(
|
||||
new ScrapeResult<string>(props.scene.studio_id, props.scraped.studio?.id)
|
||||
new ScrapeResult<string>(
|
||||
props.scene.studio_id,
|
||||
props.scraped.studio?.stored_id
|
||||
)
|
||||
);
|
||||
const [newStudio, setNewStudio] = useState<
|
||||
GQL.ScrapedSceneStudio | undefined
|
||||
>(
|
||||
props.scraped.studio && !props.scraped.studio.stored_id
|
||||
? props.scraped.studio
|
||||
: undefined
|
||||
);
|
||||
|
||||
function mapIdObjects(scrapedObjects?: IHasID[]): string[] | undefined {
|
||||
function mapStoredIdObjects(
|
||||
scrapedObjects?: IHasStoredID[]
|
||||
): string[] | undefined {
|
||||
if (!scrapedObjects) {
|
||||
return undefined;
|
||||
}
|
||||
const ret = scrapedObjects
|
||||
.map((p) => p.id)
|
||||
.map((p) => p.stored_id)
|
||||
.filter((p) => {
|
||||
return p !== undefined && p !== null;
|
||||
}) as string[];
|
||||
@@ -245,21 +280,33 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
||||
const [performers, setPerformers] = useState<ScrapeResult<string[]>>(
|
||||
new ScrapeResult<string[]>(
|
||||
sortIdList(props.scene.performer_ids),
|
||||
mapIdObjects(props.scraped.performers ?? undefined)
|
||||
mapStoredIdObjects(props.scraped.performers ?? undefined)
|
||||
)
|
||||
);
|
||||
const [newPerformers, setNewPerformers] = useState<
|
||||
GQL.ScrapedScenePerformer[]
|
||||
>(props.scraped.performers?.filter((t) => !t.stored_id) ?? []);
|
||||
|
||||
const [movies, setMovies] = useState<ScrapeResult<string[]>>(
|
||||
new ScrapeResult<string[]>(
|
||||
sortIdList(props.scene.movies?.map((p) => p.movie_id)),
|
||||
mapIdObjects(props.scraped.movies ?? undefined)
|
||||
mapStoredIdObjects(props.scraped.movies ?? undefined)
|
||||
)
|
||||
);
|
||||
const [newMovies, setNewMovies] = useState<GQL.ScrapedSceneMovie[]>(
|
||||
props.scraped.movies?.filter((t) => !t.stored_id) ?? []
|
||||
);
|
||||
|
||||
const [tags, setTags] = useState<ScrapeResult<string[]>>(
|
||||
new ScrapeResult<string[]>(
|
||||
sortIdList(props.scene.tag_ids),
|
||||
mapIdObjects(props.scraped.tags ?? undefined)
|
||||
mapStoredIdObjects(props.scraped.tags ?? undefined)
|
||||
)
|
||||
);
|
||||
const [newTags, setNewTags] = useState<GQL.ScrapedSceneTag[]>(
|
||||
props.scraped.tags?.filter((t) => !t.stored_id) ?? []
|
||||
);
|
||||
|
||||
const [details, setDetails] = useState<ScrapeResult<string>>(
|
||||
new ScrapeResult<string>(props.scene.details, props.scraped.details)
|
||||
);
|
||||
@@ -267,6 +314,13 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
||||
new ScrapeResult<string>(props.scene.cover_image, props.scraped.image)
|
||||
);
|
||||
|
||||
const [createStudio] = useStudioCreate({ name: "" });
|
||||
const [createPerformer] = usePerformerCreate();
|
||||
const [createMovie] = useMovieCreate({ name: "" });
|
||||
const [createTag] = useTagCreate({ name: "" });
|
||||
|
||||
const Toast = useToast();
|
||||
|
||||
// don't show the dialog if nothing was scraped
|
||||
if (
|
||||
[title, url, date, studio, performers, movies, tags, details, image].every(
|
||||
@@ -277,34 +331,164 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
||||
return <></>;
|
||||
}
|
||||
|
||||
function makeNewScrapedItem() {
|
||||
const newStudio = studio.getNewValue();
|
||||
async function createNewStudio(toCreate: GQL.ScrapedSceneStudio) {
|
||||
try {
|
||||
const result = await createStudio({
|
||||
variables: {
|
||||
name: toCreate.name,
|
||||
url: toCreate.url,
|
||||
},
|
||||
});
|
||||
|
||||
// set the new studio as the value
|
||||
setStudio(studio.cloneWithValue(result.data!.studioCreate!.id));
|
||||
setNewStudio(undefined);
|
||||
|
||||
Toast.success({
|
||||
content: (
|
||||
<span>
|
||||
Created studio: <b>{toCreate.name}</b>
|
||||
</span>
|
||||
),
|
||||
});
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function createNewPerformer(toCreate: GQL.ScrapedScenePerformer) {
|
||||
let performerInput: GQL.PerformerCreateInput = {};
|
||||
try {
|
||||
performerInput = Object.assign(performerInput, toCreate);
|
||||
const result = await createPerformer({
|
||||
variables: performerInput,
|
||||
});
|
||||
|
||||
// add the new performer to the new performers value
|
||||
const performerClone = performers.cloneWithValue(performers.newValue);
|
||||
if (!performerClone.newValue) {
|
||||
performerClone.newValue = [];
|
||||
}
|
||||
performerClone.newValue.push(result.data!.performerCreate!.id);
|
||||
setPerformers(performerClone);
|
||||
|
||||
// remove the performer from the list
|
||||
const newPerformersClone = newPerformers.concat();
|
||||
const pIndex = newPerformersClone.indexOf(toCreate);
|
||||
newPerformersClone.splice(pIndex, 1);
|
||||
|
||||
setNewPerformers(newPerformersClone);
|
||||
|
||||
Toast.success({
|
||||
content: (
|
||||
<span>
|
||||
Created performer: <b>{toCreate.name}</b>
|
||||
</span>
|
||||
),
|
||||
});
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function createNewMovie(toCreate: GQL.ScrapedSceneMovie) {
|
||||
let movieInput: GQL.MovieCreateInput = { name: "" };
|
||||
try {
|
||||
movieInput = Object.assign(movieInput, toCreate);
|
||||
const result = await createMovie({
|
||||
variables: movieInput,
|
||||
});
|
||||
|
||||
// add the new movie to the new movies value
|
||||
const movieClone = movies.cloneWithValue(movies.newValue);
|
||||
if (!movieClone.newValue) {
|
||||
movieClone.newValue = [];
|
||||
}
|
||||
movieClone.newValue.push(result.data!.movieCreate!.id);
|
||||
setMovies(movieClone);
|
||||
|
||||
// remove the movie from the list
|
||||
const newMoviesClone = newMovies.concat();
|
||||
const pIndex = newMoviesClone.indexOf(toCreate);
|
||||
newMoviesClone.splice(pIndex, 1);
|
||||
|
||||
setNewMovies(newMoviesClone);
|
||||
|
||||
Toast.success({
|
||||
content: (
|
||||
<span>
|
||||
Created movie: <b>{toCreate.name}</b>
|
||||
</span>
|
||||
),
|
||||
});
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function createNewTag(toCreate: GQL.ScrapedSceneTag) {
|
||||
let tagInput: GQL.TagCreateInput = { name: "" };
|
||||
try {
|
||||
tagInput = Object.assign(tagInput, toCreate);
|
||||
const result = await createTag({
|
||||
variables: tagInput,
|
||||
});
|
||||
|
||||
// add the new tag to the new tags value
|
||||
const tagClone = tags.cloneWithValue(tags.newValue);
|
||||
if (!tagClone.newValue) {
|
||||
tagClone.newValue = [];
|
||||
}
|
||||
tagClone.newValue.push(result.data!.tagCreate!.id);
|
||||
setTags(tagClone);
|
||||
|
||||
// remove the tag from the list
|
||||
const newTagsClone = newTags.concat();
|
||||
const pIndex = newTagsClone.indexOf(toCreate);
|
||||
newTagsClone.splice(pIndex, 1);
|
||||
|
||||
setNewTags(newTagsClone);
|
||||
|
||||
Toast.success({
|
||||
content: (
|
||||
<span>
|
||||
Created tag: <b>{toCreate.name}</b>
|
||||
</span>
|
||||
),
|
||||
});
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function makeNewScrapedItem(): GQL.ScrapedSceneDataFragment {
|
||||
const newStudioValue = studio.getNewValue();
|
||||
|
||||
return {
|
||||
title: title.getNewValue(),
|
||||
url: url.getNewValue(),
|
||||
date: date.getNewValue(),
|
||||
studio: newStudio
|
||||
studio: newStudioValue
|
||||
? {
|
||||
id: newStudio,
|
||||
stored_id: newStudioValue,
|
||||
name: "",
|
||||
}
|
||||
: undefined,
|
||||
performers: performers.getNewValue()?.map((p) => {
|
||||
return {
|
||||
id: p,
|
||||
stored_id: p,
|
||||
name: "",
|
||||
};
|
||||
}),
|
||||
movies: movies.getNewValue()?.map((m) => {
|
||||
return {
|
||||
id: m,
|
||||
stored_id: m,
|
||||
name: "",
|
||||
};
|
||||
}),
|
||||
tags: tags.getNewValue()?.map((m) => {
|
||||
return {
|
||||
id: m,
|
||||
stored_id: m,
|
||||
name: "",
|
||||
};
|
||||
}),
|
||||
@@ -332,12 +516,30 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = (
|
||||
result={date}
|
||||
onChange={(value) => setDate(value)}
|
||||
/>
|
||||
{renderScrapedStudioRow(studio, (value) => setStudio(value))}
|
||||
{renderScrapedPerformersRow(performers, (value) =>
|
||||
setPerformers(value)
|
||||
{renderScrapedStudioRow(
|
||||
studio,
|
||||
(value) => setStudio(value),
|
||||
newStudio,
|
||||
createNewStudio
|
||||
)}
|
||||
{renderScrapedPerformersRow(
|
||||
performers,
|
||||
(value) => setPerformers(value),
|
||||
newPerformers,
|
||||
createNewPerformer
|
||||
)}
|
||||
{renderScrapedMoviesRow(
|
||||
movies,
|
||||
(value) => setMovies(value),
|
||||
newMovies,
|
||||
createNewMovie
|
||||
)}
|
||||
{renderScrapedTagsRow(
|
||||
tags,
|
||||
(value) => setTags(value),
|
||||
newTags,
|
||||
createNewTag
|
||||
)}
|
||||
{renderScrapedMoviesRow(movies, (value) => setMovies(value))}
|
||||
{renderScrapedTagsRow(tags, (value) => setTags(value))}
|
||||
<ScrapedTextAreaRow
|
||||
title="Details"
|
||||
result={details}
|
||||
|
||||
Reference in New Issue
Block a user