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:
WithoutPants
2020-08-22 18:12:39 +10:00
committed by GitHub
parent 2cdec6bde1
commit 1fd3fcc6a8
14 changed files with 388 additions and 46 deletions

View File

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