From 12917f51d0d442d4411be2e3bd2e4b843b10b47f Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:01:35 +1000 Subject: [PATCH] Scraper menu filter (#5041) * Move scene scraper menu into reusable component * Reuse ScraperMenu for scene query menu * Reuse scraper menu in GalleryEditPanel * Add filter to scraper menu * Add divider between stashboxes and scrapers --- .../GalleryDetails/GalleryEditPanel.tsx | 72 ++++------- .../Scenes/SceneDetails/SceneEditPanel.tsx | 117 +++--------------- ui/v2.5/src/components/Shared/ScraperMenu.tsx | 100 +++++++++++++++ ui/v2.5/src/components/Shared/styles.scss | 9 ++ 4 files changed, 148 insertions(+), 150 deletions(-) create mode 100644 ui/v2.5/src/components/Shared/ScraperMenu.tsx diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx index 9f018e0d1..6acefcf7d 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx @@ -1,14 +1,7 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { Prompt } from "react-router-dom"; -import { - Button, - Dropdown, - DropdownButton, - Form, - Col, - Row, -} from "react-bootstrap"; +import { Button, Form, Col, Row } from "react-bootstrap"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import * as yup from "yup"; @@ -18,12 +11,10 @@ import { useListGalleryScrapers, mutateReloadScrapers, } from "src/core/StashService"; -import { Icon } from "src/components/Shared/Icon"; import { LoadingIndicator } from "src/components/Shared/LoadingIndicator"; import { useToast } from "src/hooks/Toast"; import { useFormik } from "formik"; import { GalleryScrapeDialog } from "./GalleryScrapeDialog"; -import { faSyncAlt } from "@fortawesome/free-solid-svg-icons"; import isEqual from "lodash-es/isEqual"; import { handleUnsavedChanges } from "src/utils/navigation"; import { @@ -39,6 +30,7 @@ import { formikUtils } from "src/utils/form"; import { Studio, StudioSelect } from "src/components/Studios/StudioSelect"; import { Scene, SceneSelect } from "src/components/Scenes/SceneSelect"; import { useTagsEdit } from "src/hooks/tagsEdit"; +import { ScraperMenu } from "src/components/Shared/ScraperMenu"; interface IProps { gallery: Partial; @@ -62,8 +54,7 @@ export const GalleryEditPanel: React.FC = ({ const isNew = gallery.id === undefined; - const Scrapers = useListGalleryScrapers(); - const [queryableScrapers, setQueryableScrapers] = useState([]); + const scrapers = useListGalleryScrapers(); const [scrapedGallery, setScrapedGallery] = useState(); @@ -165,13 +156,11 @@ export const GalleryEditPanel: React.FC = ({ } }); - useEffect(() => { - const newQueryableScrapers = (Scrapers?.data?.listScrapers ?? []).filter( - (s) => s.gallery?.supported_scrapes.includes(GQL.ScrapeType.Fragment) + const fragmentScrapers = useMemo(() => { + return (scrapers?.data?.listScrapers ?? []).filter((s) => + s.gallery?.supported_scrapes.includes(GQL.ScrapeType.Fragment) ); - - setQueryableScrapers(newQueryableScrapers); - }, [Scrapers]); + }, [scrapers]); async function onSave(input: InputValues) { setIsLoading(true); @@ -184,12 +173,12 @@ export const GalleryEditPanel: React.FC = ({ setIsLoading(false); } - async function onScrapeClicked(scraper: GQL.Scraper) { + async function onScrapeClicked(s: GQL.ScraperSourceInput) { if (!gallery || !gallery.id) return; setIsLoading(true); try { - const result = await queryScrapeGallery(scraper.id, gallery.id); + const result = await queryScrapeGallery(s.scraper_id!, gallery.id); if (!result.data || !result.data.scrapeSingleGallery?.length) { Toast.success("No galleries found"); return; @@ -244,36 +233,8 @@ export const GalleryEditPanel: React.FC = ({ ); } - function renderScraperMenu() { - if (isNew) { - return; - } - - return ( - - {queryableScrapers.map((s) => ( - onScrapeClicked(s)}> - {s.name} - - ))} - onReloadScrapers()}> - - - - - - - - - ); - } - function urlScrapable(scrapedUrl: string): boolean { - return (Scrapers?.data?.listScrapers ?? []).some((s) => + return (scrapers?.data?.listScrapers ?? []).some((s) => (s?.gallery?.urls ?? []).some((u) => scrapedUrl.includes(u)) ); } @@ -461,7 +422,16 @@ export const GalleryEditPanel: React.FC = ({ -
{renderScraperMenu()}
+
+ {!isNew && ( + + )} +
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index d41d30817..2eef3de1f 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -1,14 +1,6 @@ import React, { useEffect, useState, useMemo } from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { - Button, - Dropdown, - DropdownButton, - Form, - Col, - Row, - ButtonGroup, -} from "react-bootstrap"; +import { Button, Form, Col, Row, ButtonGroup } from "react-bootstrap"; import Mousetrap from "mousetrap"; import * as GQL from "src/core/generated-graphql"; import * as yup from "yup"; @@ -28,9 +20,8 @@ import { getStashIDs } from "src/utils/stashIds"; import { useFormik } from "formik"; import { Prompt } from "react-router-dom"; import { ConfigurationContext } from "src/hooks/Config"; -import { stashboxDisplayName } from "src/utils/stashbox"; import { IGroupEntry, SceneGroupTable } from "./SceneGroupTable"; -import { faSearch, faSyncAlt } from "@fortawesome/free-solid-svg-icons"; +import { faSearch } from "@fortawesome/free-solid-svg-icons"; import { objectTitle } from "src/core/files"; import { galleryTitle } from "src/core/galleries"; import { lazyComponent } from "src/utils/lazyComponent"; @@ -49,6 +40,7 @@ import { Studio, StudioSelect } from "src/components/Studios/StudioSelect"; import { Gallery, GallerySelect } from "src/components/Galleries/GallerySelect"; import { Group } from "src/components/Groups/GroupSelect"; import { useTagsEdit } from "src/hooks/tagsEdit"; +import { ScraperMenu } from "src/components/Shared/ScraperMenu"; const SceneScrapeDialog = lazyComponent(() => import("./SceneScrapeDialog")); const SceneQueryModal = lazyComponent(() => import("./SceneQueryModal")); @@ -394,51 +386,6 @@ export const SceneEditPanel: React.FC = ({ ); } - function renderScrapeQueryMenu() { - const stashBoxes = stashConfig?.general.stashBoxes ?? []; - - if (stashBoxes.length === 0 && queryableScrapers.length === 0) return; - - return ( - - - - - - - {stashBoxes.map((s, index) => ( - - onScrapeQueryClicked({ - stash_box_endpoint: s.endpoint, - }) - } - > - {stashboxDisplayName(s.name, index)} - - ))} - {queryableScrapers.map((s) => ( - onScrapeQueryClicked({ scraper_id: s.id })} - > - {s.name} - - ))} - onReloadScrapers()}> - - - - - - - - - - ); - } - function onSceneSelected(s: GQL.ScrapedSceneDataFragment) { if (!scraper) return; @@ -468,47 +415,6 @@ export const SceneEditPanel: React.FC = ({ ); }; - function renderScraperMenu() { - const stashBoxes = stashConfig?.general.stashBoxes ?? []; - - return ( - - {stashBoxes.map((s, index) => ( - - onScrapeClicked({ - stash_box_endpoint: s.endpoint, - }) - } - > - {stashboxDisplayName(s.name, index)} - - ))} - {fragmentScrapers.map((s) => ( - onScrapeClicked({ scraper_id: s.id })} - > - {s.name} - - ))} - onReloadScrapers()}> - - - - - - - - - ); - } - function urlScrapable(scrapedUrl: string): boolean { return (Scrapers?.data?.listScrapers ?? []).some((s) => (s?.scene?.urls ?? []).some((u) => scrapedUrl.includes(u)) @@ -801,8 +707,21 @@ export const SceneEditPanel: React.FC = ({ {!isNew && (
- {renderScraperMenu()} - {renderScrapeQueryMenu()} + + } + stashBoxes={stashConfig?.general.stashBoxes ?? []} + scrapers={queryableScrapers} + onScraperClicked={onScrapeQueryClicked} + onReloadScrapers={onReloadScrapers} + />
)} diff --git a/ui/v2.5/src/components/Shared/ScraperMenu.tsx b/ui/v2.5/src/components/Shared/ScraperMenu.tsx new file mode 100644 index 000000000..2152854b0 --- /dev/null +++ b/ui/v2.5/src/components/Shared/ScraperMenu.tsx @@ -0,0 +1,100 @@ +import React, { useMemo, useState } from "react"; +import { Dropdown } from "react-bootstrap"; +import { FormattedMessage, useIntl } from "react-intl"; +import { Icon } from "./Icon"; +import { stashboxDisplayName } from "src/utils/stashbox"; +import { ScraperSourceInput, StashBox } from "src/core/generated-graphql"; +import { faSyncAlt } from "@fortawesome/free-solid-svg-icons"; +import { ClearableInput } from "./ClearableInput"; + +const minFilteredScrapers = 5; + +export const ScraperMenu: React.FC<{ + toggle: React.ReactNode; + variant?: string; + stashBoxes?: StashBox[]; + scrapers: { id: string; name: string }[]; + onScraperClicked: (s: ScraperSourceInput) => void; + onReloadScrapers: () => void; +}> = ({ + toggle, + variant, + stashBoxes, + scrapers, + onScraperClicked, + onReloadScrapers, +}) => { + const intl = useIntl(); + const [filter, setFilter] = useState(""); + + const filteredStashboxes = useMemo(() => { + if (!stashBoxes) return []; + if (!filter) return stashBoxes; + + return stashBoxes.filter((s) => + s.name.toLowerCase().includes(filter.toLowerCase()) + ); + }, [stashBoxes, filter]); + + const filteredScrapers = useMemo(() => { + if (!filter) return scrapers; + + return scrapers.filter( + (s) => + s.name.toLowerCase().includes(filter.toLowerCase()) || + s.id.toLowerCase().includes(filter.toLowerCase()) + ); + }, [scrapers, filter]); + + return ( + + {toggle} + + + {(stashBoxes?.length ?? 0) + scrapers.length > minFilteredScrapers && ( + + )} + {filteredStashboxes.map((s, index) => ( + + onScraperClicked({ + stash_box_endpoint: s.endpoint, + }) + } + > + {stashboxDisplayName(s.name, index)} + + ))} + + {filteredStashboxes.length > 0 && filteredScrapers.length > 0 && ( + + )} + + {filteredScrapers.map((s) => ( + onScraperClicked({ scraper_id: s.id })} + > + {s.name} + + ))} + onReloadScrapers()}> + + + + + + + + + + ); +}; diff --git a/ui/v2.5/src/components/Shared/styles.scss b/ui/v2.5/src/components/Shared/styles.scss index 983e51783..00ce5e663 100644 --- a/ui/v2.5/src/components/Shared/styles.scss +++ b/ui/v2.5/src/components/Shared/styles.scss @@ -596,3 +596,12 @@ button.btn.favorite-button { .external-links-button { display: inline-block; } + +.scraper-menu .dropdown-menu { + min-width: 250px; + + .dropdown-divider { + border-top-color: $textfield-bg; + margin: 0; + } +}