diff --git a/ui/v2.5/src/components/Changelog/versions/v040.md b/ui/v2.5/src/components/Changelog/versions/v040.md index 9b1ff4f3916..4ac7e7e1df9 100644 --- a/ui/v2.5/src/components/Changelog/versions/v040.md +++ b/ui/v2.5/src/components/Changelog/versions/v040.md @@ -1,4 +1,5 @@ ### ✨ New Features +* Add scrapers list setting page. * Add support for individual images and manual creation of galleries. * Add various fields to galleries. * Add partial import from zip file. diff --git a/ui/v2.5/src/components/Settings/Settings.tsx b/ui/v2.5/src/components/Settings/Settings.tsx index 09e107a815c..7f8dac83eae 100644 --- a/ui/v2.5/src/components/Settings/Settings.tsx +++ b/ui/v2.5/src/components/Settings/Settings.tsx @@ -8,6 +8,7 @@ import { SettingsInterfacePanel } from "./SettingsInterfacePanel"; import { SettingsLogsPanel } from "./SettingsLogsPanel"; import { SettingsTasksPanel } from "./SettingsTasksPanel/SettingsTasksPanel"; import { SettingsPluginsPanel } from "./SettingsPluginsPanel"; +import { SettingsScrapersPanel } from "./SettingsScrapersPanel"; export const Settings: React.FC = () => { const location = useLocation(); @@ -35,6 +36,9 @@ export const Settings: React.FC = () => { Tasks + + Scrapers + Plugins @@ -58,6 +62,9 @@ export const Settings: React.FC = () => { + + + diff --git a/ui/v2.5/src/components/Settings/SettingsScrapersPanel.tsx b/ui/v2.5/src/components/Settings/SettingsScrapersPanel.tsx new file mode 100644 index 00000000000..8f0dfb7ac07 --- /dev/null +++ b/ui/v2.5/src/components/Settings/SettingsScrapersPanel.tsx @@ -0,0 +1,238 @@ +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; +import { + mutateReloadScrapers, + useListMovieScrapers, + useListPerformerScrapers, + useListSceneScrapers, +} from "src/core/StashService"; +import { useToast } from "src/hooks"; +import { TextUtils } from "src/utils"; +import { Icon, LoadingIndicator } from "src/components/Shared"; +import { ScrapeType } from "src/core/generated-graphql"; + +interface IURLList { + urls: string[]; +} + +const URLList: React.FC = ({ urls }) => { + const maxCollapsedItems = 5; + const [expanded, setExpanded] = useState(false); + + function linkSite(url: string) { + const u = new URL(url); + return `${u.protocol}//${u.host}`; + } + + function renderLink(url?: string) { + if (url) { + const sanitised = TextUtils.sanitiseURL(url); + const siteURL = linkSite(sanitised!); + + return ( + + {sanitised} + + ); + } + } + + function getListItems() { + const items = urls.map((u) =>
  • {renderLink(u)}
  • ); + + if (items.length > maxCollapsedItems) { + if (!expanded) { + items.length = maxCollapsedItems; + } + + items.push( +
  • + +
  • + ); + } + + return items; + } + + return ; +}; + +export const SettingsScrapersPanel: React.FC = () => { + const Toast = useToast(); + const { + data: performerScrapers, + loading: loadingPerformers, + } = useListPerformerScrapers(); + const { + data: sceneScrapers, + loading: loadingScenes, + } = useListSceneScrapers(); + const { + data: movieScrapers, + loading: loadingMovies, + } = useListMovieScrapers(); + + async function onReloadScrapers() { + await mutateReloadScrapers().catch((e) => Toast.error(e)); + } + + function renderPerformerScrapeTypes(types: ScrapeType[]) { + const typeStrings = types + .filter((t) => t !== ScrapeType.Fragment) + .map((t) => { + switch (t) { + case ScrapeType.Name: + return "Search by name"; + default: + return t; + } + }); + + return ( + + ); + } + + function renderSceneScrapeTypes(types: ScrapeType[]) { + const typeStrings = types.map((t) => { + switch (t) { + case ScrapeType.Fragment: + return "Scene Metadata"; + default: + return t; + } + }); + + return ( + + ); + } + + function renderMovieScrapeTypes(types: ScrapeType[]) { + const typeStrings = types.map((t) => { + switch (t) { + case ScrapeType.Fragment: + return "Movie Metadata"; + default: + return t; + } + }); + + return ( + + ); + } + + function renderURLs(urls: string[]) { + return ; + } + + function renderSceneScrapers() { + const elements = (sceneScrapers?.listSceneScrapers ?? []).map((scraper) => ( + + {scraper.name} + + {renderSceneScrapeTypes(scraper.scene?.supported_scrapes ?? [])} + + {renderURLs(scraper.scene?.urls ?? [])} + + )); + + return renderTable("Scene scrapers", elements); + } + + function renderPerformerScrapers() { + const elements = (performerScrapers?.listPerformerScrapers ?? []).map( + (scraper) => ( + + {scraper.name} + + {renderPerformerScrapeTypes( + scraper.performer?.supported_scrapes ?? [] + )} + + {renderURLs(scraper.performer?.urls ?? [])} + + ) + ); + + return renderTable("Performer scrapers", elements); + } + + function renderMovieScrapers() { + const elements = (movieScrapers?.listMovieScrapers ?? []).map((scraper) => ( + + {scraper.name} + + {renderMovieScrapeTypes(scraper.movie?.supported_scrapes ?? [])} + + {renderURLs(scraper.movie?.urls ?? [])} + + )); + + return renderTable("Movie scrapers", elements); + } + + function renderTable(title: string, elements: JSX.Element[]) { + if (elements.length > 0) { + return ( +
    +
    {title}
    + + + + + + + + + {elements} +
    NameSupported typesURLs
    +
    + ); + } + } + + if (loadingScenes || loadingPerformers || loadingMovies) + return ; + + return ( + <> +

    Scrapers

    +
    + +
    + +
    + {renderSceneScrapers()} + {renderPerformerScrapers()} + {renderMovieScrapers()} +
    + + ); +}; diff --git a/ui/v2.5/src/components/Settings/styles.scss b/ui/v2.5/src/components/Settings/styles.scss index ecef8049ae8..89844ce1474 100644 --- a/ui/v2.5/src/components/Settings/styles.scss +++ b/ui/v2.5/src/components/Settings/styles.scss @@ -40,3 +40,33 @@ #configuration-tabs-tabpane-tasks h5 { margin-bottom: 1em; } + +.scraper-table { + display: block; + margin-bottom: 16px; + overflow: auto; + width: 100%; + + tr { + border-top: 1px solid #181513; + + &:nth-child(2n) { + background-color: #2c3b47; + } + } + + th, + td { + border: 1px solid #181513; + padding: 6px 13px; + } + + ul { + margin-bottom: 0; + padding-left: 0; + } + + li { + list-style: none; + } +} diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index 4d920fe981a..f4c99107abc 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -638,6 +638,11 @@ export const queryStashBoxScene = (stashBoxIndex: number, sceneID: string) => export const mutateReloadScrapers = () => client.mutate({ mutation: GQL.ReloadScrapersDocument, + refetchQueries: [ + GQL.refetchListMovieScrapersQuery(), + GQL.refetchListPerformerScrapersQuery(), + GQL.refetchListSceneScrapersQuery(), + ], }); export const mutateReloadPlugins = () =>