mirror of https://github.com/stashapp/stash.git
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:
parent
3f97b3a1cb
commit
5a37e6cf52
|
@ -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 (
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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("");
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue