Add search modal for stash-box performer scraper (#1373)

* Cache tagger fingerprint lookups between renders
* Show search modal for stash-box performer scraper
This commit is contained in:
InfiniteTF 2021-05-10 01:33:08 +02:00 committed by GitHub
parent 3f97b3a1cb
commit 5a37e6cf52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 129 additions and 36 deletions

View File

@ -24,7 +24,6 @@ import {
useTagCreate,
queryScrapePerformerURL,
useConfiguration,
queryStashBoxPerformer,
} from "src/core/StashService";
import {
Icon,
@ -42,6 +41,11 @@ import { useFormik } from "formik";
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
import { PerformerScrapeDialog } from "./PerformerScrapeDialog";
import PerformerScrapeModal from "./PerformerScrapeModal";
import PerformerStashBoxModal, { IStashBox } from "./PerformerStashBoxModal";
const isScraper = (
scraper: GQL.Scraper | GQL.StashBox
): scraper is GQL.Scraper => (scraper as GQL.Scraper).id !== undefined;
interface IPerformerDetails {
performer: Partial<GQL.PerformerDataFragment>;
@ -64,7 +68,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
const history = useHistory();
// Editing state
const [scraper, setScraper] = useState<GQL.Scraper | undefined>();
const [scraper, setScraper] = useState<GQL.Scraper | IStashBox | undefined>();
const [newTags, setNewTags] = useState<GQL.ScrapedSceneTag[]>();
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
@ -495,7 +499,8 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
}
async function onScrapePerformer(
selectedPerformer: GQL.ScrapedPerformerDataFragment
selectedPerformer: GQL.ScrapedPerformerDataFragment,
selectedScraper: GQL.Scraper
) {
setScraper(undefined);
try {
@ -509,7 +514,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
...ret
} = selectedPerformer;
const result = await queryScrapePerformer(scraper.id, ret);
const result = await queryScrapePerformer(selectedScraper.id, ret);
if (!result?.data?.scrapePerformer) return;
// if this is a new performer, just dump the data
@ -548,34 +553,21 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
}
}
async function onScrapeStashBoxClicked(stashBoxIndex: number) {
if (!performer.id) return;
async function onScrapeStashBox(performerResult: GQL.ScrapedScenePerformer) {
setScraper(undefined);
setIsLoading(true);
try {
const result = await queryStashBoxPerformer(stashBoxIndex, performer.id);
if (!result.data || !result.data.queryStashBoxPerformer) {
return;
}
const result: Partial<GQL.ScrapedPerformerDataFragment> = {
...performerResult,
image: performerResult.images?.[0] ?? undefined,
country: getCountryByISO(performerResult.country),
__typename: "ScrapedPerformer",
};
if (result.data.queryStashBoxPerformer.length > 0) {
const performerResult =
result.data.queryStashBoxPerformer[0].results[0];
setScrapedPerformer({
...performerResult,
image: performerResult.images?.[0] ?? undefined,
country: getCountryByISO(performerResult.country),
__typename: "ScrapedPerformer",
});
} else {
Toast.success({
content: "No performers found",
});
}
} catch (e) {
Toast.error(e);
} finally {
setIsLoading(false);
// if this is a new performer, just dump the data
if (isNew) {
updatePerformerEditStateFromScraper(result);
} else {
setScrapedPerformer(result);
}
}
@ -593,7 +585,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
<div key={s.endpoint}>
<Button
className="minimal"
onClick={() => onScrapeStashBoxClicked(index)}
onClick={() => setScraper({ ...s, index })}
>
{s.name ?? "Stash-Box"}
</Button>
@ -732,14 +724,21 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
}
const renderScrapeModal = () =>
scraper !== undefined && (
scraper !== undefined && isScraper(scraper) ? (
<PerformerScrapeModal
scraper={scraper}
onHide={() => setScraper(undefined)}
onSelectPerformer={onScrapePerformer}
name={formik.values.name || ""}
/>
);
) : scraper !== undefined && !isScraper(scraper) ? (
<PerformerStashBoxModal
instance={scraper}
onHide={() => setScraper(undefined)}
onSelectPerformer={onScrapeStashBox}
name={formik.values.name || ""}
/>
) : undefined;
function renderDeleteAlert() {
return (

View File

@ -12,7 +12,10 @@ const CLASSNAME_LIST = `${CLASSNAME}-list`;
interface IProps {
scraper: GQL.Scraper;
onHide: () => void;
onSelectPerformer: (performer: GQL.ScrapedPerformerDataFragment) => void;
onSelectPerformer: (
performer: GQL.ScrapedPerformerDataFragment,
scraper: GQL.Scraper
) => void;
name?: string;
}
const PerformerScrapeModal: React.FC<IProps> = ({
@ -56,7 +59,10 @@ const PerformerScrapeModal: React.FC<IProps> = ({
<ul className={CLASSNAME_LIST}>
{performers.map((p) => (
<li key={p.url}>
<Button variant="link" onClick={() => onSelectPerformer(p)}>
<Button
variant="link"
onClick={() => onSelectPerformer(p, scraper)}
>
{p.name}
</Button>
</li>

View File

@ -0,0 +1,84 @@
import React, { useEffect, useRef, useState } from "react";
import { debounce } from "lodash";
import { Button, Form } from "react-bootstrap";
import * as GQL from "src/core/generated-graphql";
import { Modal, LoadingIndicator } from "src/components/Shared";
const CLASSNAME = "PerformerScrapeModal";
const CLASSNAME_LIST = `${CLASSNAME}-list`;
export interface IStashBox extends GQL.StashBox {
index: number;
}
interface IProps {
instance: IStashBox;
onHide: () => void;
onSelectPerformer: (performer: GQL.ScrapedScenePerformer) => void;
name?: string;
}
const PerformerStashBoxModal: React.FC<IProps> = ({
instance,
name,
onHide,
onSelectPerformer,
}) => {
const inputRef = useRef<HTMLInputElement>(null);
const [query, setQuery] = useState<string>(name ?? "");
const { data, loading } = GQL.useQueryStashBoxPerformerQuery({
variables: {
input: {
stash_box_index: instance.index,
q: query,
},
},
skip: query === "",
});
const performers = data?.queryStashBoxPerformer?.[0].results ?? [];
const onInputChange = debounce((input: string) => {
setQuery(input);
}, 500);
useEffect(() => inputRef.current?.focus(), []);
return (
<Modal
show
onHide={onHide}
header={`Scrape performer from ${instance.name ?? "Stash-Box"}`}
accept={{ text: "Cancel", onClick: onHide, variant: "secondary" }}
>
<div className={CLASSNAME}>
<Form.Control
onChange={(e) => onInputChange(e.currentTarget.value)}
defaultValue={name ?? ""}
placeholder="Performer name..."
className="text-input mb-4"
ref={inputRef}
/>
{loading ? (
<div className="m-4 text-center">
<LoadingIndicator inline />
</div>
) : performers.length > 0 ? (
<ul className={CLASSNAME_LIST}>
{performers.map((p) => (
<li key={p.url}>
<Button variant="link" onClick={() => onSelectPerformer(p)}>
{p.name}
</Button>
</li>
))}
</ul>
) : (
query !== "" && <h5 className="text-center">No results found.</h5>
)}
</div>
</Modal>
);
};
export default PerformerStashBoxModal;

View File

@ -150,6 +150,9 @@ interface ITaggerListProps {
clearSubmissionQueue: (endpoint: string) => void;
}
// Caches fingerprint lookups between page renders
let fingerprintCache: Record<string, IStashBoxScene[]> = {};
const TaggerList: React.FC<ITaggerListProps> = ({
scenes,
queue,
@ -181,7 +184,7 @@ const TaggerList: React.FC<ITaggerListProps> = ({
const [loadingFingerprints, setLoadingFingerprints] = useState(false);
const [fingerprints, setFingerprints] = useState<
Record<string, IStashBoxScene[]>
>({});
>(fingerprintCache);
const [hideUnmatched, setHideUnmatched] = useState(false);
const fingerprintQueue =
config.fingerprintQueue[selectedEndpoint.endpoint] ?? [];
@ -285,6 +288,7 @@ const TaggerList: React.FC<ITaggerListProps> = ({
});
setFingerprints(newFingerprints);
fingerprintCache = newFingerprints;
setLoadingFingerprints(false);
setFingerprintError("");
};