diff --git a/ui/v2.5/src/components/Changelog/versions/v060.md b/ui/v2.5/src/components/Changelog/versions/v060.md index 980a0c59c..539043e72 100644 --- a/ui/v2.5/src/components/Changelog/versions/v060.md +++ b/ui/v2.5/src/components/Changelog/versions/v060.md @@ -2,6 +2,7 @@ * Added Performer tags. ### 🎨 Improvements +* Improve performer scraper search modal. * Add galleries tab to Tag details page. * Allow scene/performer/studio image upload via URL. * Add button to hide unmatched scenes in Tagger view. diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index 1f6fcad40..25b4a3128 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -26,11 +26,10 @@ import { } from "src/core/StashService"; import { Icon, - Modal, ImageInput, - ScrapePerformerSuggest, LoadingIndicator, CollapseButton, + Modal, TagSelect, } from "src/components/Shared"; import { ImageUtils } from "src/utils"; @@ -38,6 +37,7 @@ import { useToast } from "src/hooks"; import { Prompt, useHistory } from "react-router-dom"; import { useFormik } from "formik"; import { PerformerScrapeDialog } from "./PerformerScrapeDialog"; +import PerformerScrapeModal from "./PerformerScrapeModal"; interface IPerformerDetails { performer: Partial; @@ -60,14 +60,7 @@ export const PerformerEditPanel: React.FC = ({ const history = useHistory(); // Editing state - const [ - isDisplayingScraperDialog, - setIsDisplayingScraperDialog, - ] = useState(); - const [ - scrapePerformerDetails, - setScrapePerformerDetails, - ] = useState(); + const [scraper, setScraper] = useState(); const [newTags, setNewTags] = useState(); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); @@ -422,10 +415,6 @@ export const PerformerEditPanel: React.FC = ({ formik.setFieldValue("image", url); } - function onDisplayScrapeDialog(scraper: GQL.Scraper) { - setIsDisplayingScraperDialog(scraper); - } - async function onReloadScrapers() { setIsLoading(true); try { @@ -440,29 +429,22 @@ export const PerformerEditPanel: React.FC = ({ } } - function getQueryScraperPerformerInput() { - if (!scrapePerformerDetails) return {}; - - // image is not supported - // remove tags as well - const { - __typename, - image: _image, - tags: _tags, - ...ret - } = scrapePerformerDetails; - return ret; - } - - async function onScrapePerformer() { - setIsDisplayingScraperDialog(undefined); + async function onScrapePerformer( + selectedPerformer: GQL.ScrapedPerformerDataFragment + ) { + setScraper(undefined); try { - if (!scrapePerformerDetails || !isDisplayingScraperDialog) return; + if (!scraper) return; setIsLoading(true); - const result = await queryScrapePerformer( - isDisplayingScraperDialog.id, - getQueryScraperPerformerInput() - ); + + const { + __typename, + image: _image, + tags: _tags, + ...ret + } = selectedPerformer; + + const result = await queryScrapePerformer(scraper.id, ret); if (!result?.data?.scrapePerformer) return; // if this is a new performer, just dump the data @@ -516,7 +498,7 @@ export const PerformerEditPanel: React.FC = ({ @@ -550,27 +532,6 @@ export const PerformerEditPanel: React.FC = ({ ); } - function renderScraperDialog() { - return ( - setIsDisplayingScraperDialog(undefined)} - header="Scrape" - accept={{ onClick: onScrapePerformer, text: "Scrape" }} - > -
- setScrapePerformerDetails(query)} - /> -
-
- ); - } - function urlScrapable(scrapedUrl?: string) { return ( !!scrapedUrl && @@ -662,6 +623,16 @@ export const PerformerEditPanel: React.FC = ({ ); } + const renderScrapeModal = () => + scraper !== undefined && ( + setScraper(undefined)} + onSelectPerformer={onScrapePerformer} + name={formik.values.name || ""} + /> + ); + function renderDeleteAlert() { return ( = ({ return ( <> {renderDeleteAlert()} - {renderScraperDialog()} + {renderScrapeModal()} {maybeRenderScrapeDialog()} void; + onSelectPerformer: (performer: GQL.ScrapedPerformerDataFragment) => void; + name?: string; +} +const PerformerScrapeModal: React.FC = ({ + scraper, + name, + onHide, + onSelectPerformer, +}) => { + const inputRef = useRef(null); + const [query, setQuery] = useState(name ?? ""); + const { data, loading } = useScrapePerformerList(scraper.id, query); + + const performers = data?.scrapePerformerList ?? []; + + const onInputChange = debounce((input: string) => { + setQuery(input); + }, 500); + + useEffect(() => inputRef.current?.focus(), []); + + return ( + +
+ onInputChange(e.currentTarget.value)} + defaultValue={name ?? ""} + placeholder="Performer name..." + className="text-input mb-4" + ref={inputRef} + /> + {loading ? ( +
+ +
+ ) : ( +
    + {performers.map((p) => ( +
  • + +
  • + ))} +
+ )} +
+
+ ); +}; + +export default PerformerScrapeModal; diff --git a/ui/v2.5/src/components/Performers/styles.scss b/ui/v2.5/src/components/Performers/styles.scss index c77d10436..47f58d437 100644 --- a/ui/v2.5/src/components/Performers/styles.scss +++ b/ui/v2.5/src/components/Performers/styles.scss @@ -90,3 +90,16 @@ #performer-scraper-popover { z-index: 1; } + +.PerformerScrapeModal { + &-list { + list-style-type: none; + max-height: 50vh; + overflow-x: auto; + padding-left: 1rem; + + .btn { + font-size: 1.2rem; + } + } +} diff --git a/ui/v2.5/src/components/Shared/Select.tsx b/ui/v2.5/src/components/Shared/Select.tsx index c9126ba5f..ff8e4a8d1 100644 --- a/ui/v2.5/src/components/Shared/Select.tsx +++ b/ui/v2.5/src/components/Shared/Select.tsx @@ -10,7 +10,6 @@ import { useAllStudiosForFilter, useAllPerformersForFilter, useMarkerStrings, - useScrapePerformerList, useTagCreate, useStudioCreate, usePerformerCreate, @@ -342,48 +341,6 @@ export const SceneSelect: React.FC = (props) => { ); }; -interface IScrapePerformerSuggestProps { - scraperId: string; - onSelectPerformer: (performer: GQL.ScrapedPerformerDataFragment) => void; - placeholder?: string; -} -export const ScrapePerformerSuggest: React.FC = ( - props -) => { - const [query, setQuery] = useState(""); - const { data, loading } = useScrapePerformerList(props.scraperId, query); - - const performers = data?.scrapePerformerList ?? []; - const items = performers.map((item) => ({ - label: item.name ?? "", - value: item.name ?? "", - })); - - const onInputChange = debounce((input: string) => { - setQuery(input); - }, 500); - - const onChange = (option: ValueType) => { - const performer = performers.find((p) => p.name === option?.value); - if (performer) props.onSelectPerformer(performer); - }; - - return ( - - ); -}; - interface IMarkerSuggestProps { initialMarkerTitle?: string; onChange: (title: string) => void; diff --git a/ui/v2.5/src/components/Shared/index.ts b/ui/v2.5/src/components/Shared/index.ts index f74648125..3a711091a 100644 --- a/ui/v2.5/src/components/Shared/index.ts +++ b/ui/v2.5/src/components/Shared/index.ts @@ -1,14 +1,4 @@ -export { - GallerySelect, - ScrapePerformerSuggest, - MarkerTitleSuggest, - FilterSelect, - PerformerSelect, - StudioSelect, - TagSelect, - SceneSelect, -} from "./Select"; - +export * from "./Select"; export { default as Icon } from "./Icon"; export { default as Modal } from "./Modal"; export { CollapseButton } from "./CollapseButton";