Add scraper list page (#833)

This commit is contained in:
WithoutPants 2020-10-13 15:19:54 +11:00 committed by GitHub
parent aca2c7c5f4
commit 12cba97192
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 281 additions and 0 deletions

View File

@ -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.

View File

@ -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 = () => {
<Nav.Item>
<Nav.Link eventKey="tasks">Tasks</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="scrapers">Scrapers</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="plugins">Plugins</Nav.Link>
</Nav.Item>
@ -58,6 +62,9 @@ export const Settings: React.FC = () => {
<Tab.Pane eventKey="tasks">
<SettingsTasksPanel />
</Tab.Pane>
<Tab.Pane eventKey="scrapers">
<SettingsScrapersPanel />
</Tab.Pane>
<Tab.Pane eventKey="plugins">
<SettingsPluginsPanel />
</Tab.Pane>

View File

@ -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<IURLList> = ({ urls }) => {
const maxCollapsedItems = 5;
const [expanded, setExpanded] = useState<boolean>(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 (
<a
href={siteURL}
className="link"
target="_blank"
rel="noopener noreferrer"
>
{sanitised}
</a>
);
}
}
function getListItems() {
const items = urls.map((u) => <li key={u}>{renderLink(u)}</li>);
if (items.length > maxCollapsedItems) {
if (!expanded) {
items.length = maxCollapsedItems;
}
items.push(
<li key="expand/collapse">
<Button onClick={() => setExpanded(!expanded)} variant="link">
{expanded ? "less" : "more"}
</Button>
</li>
);
}
return items;
}
return <ul>{getListItems()}</ul>;
};
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 (
<ul>
{typeStrings.map((t) => (
<li key={t}>{t}</li>
))}
</ul>
);
}
function renderSceneScrapeTypes(types: ScrapeType[]) {
const typeStrings = types.map((t) => {
switch (t) {
case ScrapeType.Fragment:
return "Scene Metadata";
default:
return t;
}
});
return (
<ul>
{typeStrings.map((t) => (
<li key={t}>{t}</li>
))}
</ul>
);
}
function renderMovieScrapeTypes(types: ScrapeType[]) {
const typeStrings = types.map((t) => {
switch (t) {
case ScrapeType.Fragment:
return "Movie Metadata";
default:
return t;
}
});
return (
<ul>
{typeStrings.map((t) => (
<li key={t}>{t}</li>
))}
</ul>
);
}
function renderURLs(urls: string[]) {
return <URLList urls={urls} />;
}
function renderSceneScrapers() {
const elements = (sceneScrapers?.listSceneScrapers ?? []).map((scraper) => (
<tr key={scraper.id}>
<td>{scraper.name}</td>
<td>
{renderSceneScrapeTypes(scraper.scene?.supported_scrapes ?? [])}
</td>
<td>{renderURLs(scraper.scene?.urls ?? [])}</td>
</tr>
));
return renderTable("Scene scrapers", elements);
}
function renderPerformerScrapers() {
const elements = (performerScrapers?.listPerformerScrapers ?? []).map(
(scraper) => (
<tr key={scraper.id}>
<td>{scraper.name}</td>
<td>
{renderPerformerScrapeTypes(
scraper.performer?.supported_scrapes ?? []
)}
</td>
<td>{renderURLs(scraper.performer?.urls ?? [])}</td>
</tr>
)
);
return renderTable("Performer scrapers", elements);
}
function renderMovieScrapers() {
const elements = (movieScrapers?.listMovieScrapers ?? []).map((scraper) => (
<tr key={scraper.id}>
<td>{scraper.name}</td>
<td>
{renderMovieScrapeTypes(scraper.movie?.supported_scrapes ?? [])}
</td>
<td>{renderURLs(scraper.movie?.urls ?? [])}</td>
</tr>
));
return renderTable("Movie scrapers", elements);
}
function renderTable(title: string, elements: JSX.Element[]) {
if (elements.length > 0) {
return (
<div className="mb-2">
<h5>{title}</h5>
<table className="scraper-table">
<thead>
<tr>
<th>Name</th>
<th>Supported types</th>
<th>URLs</th>
</tr>
</thead>
<tbody>{elements}</tbody>
</table>
</div>
);
}
}
if (loadingScenes || loadingPerformers || loadingMovies)
return <LoadingIndicator />;
return (
<>
<h4>Scrapers</h4>
<div className="mb-3">
<Button onClick={() => onReloadScrapers()}>
<span className="fa-icon">
<Icon icon="sync-alt" />
</span>
<span>Reload scrapers</span>
</Button>
</div>
<div>
{renderSceneScrapers()}
{renderPerformerScrapers()}
{renderMovieScrapers()}
</div>
</>
);
};

View File

@ -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;
}
}

View File

@ -638,6 +638,11 @@ export const queryStashBoxScene = (stashBoxIndex: number, sceneID: string) =>
export const mutateReloadScrapers = () =>
client.mutate<GQL.ReloadScrapersMutation>({
mutation: GQL.ReloadScrapersDocument,
refetchQueries: [
GQL.refetchListMovieScrapersQuery(),
GQL.refetchListPerformerScrapersQuery(),
GQL.refetchListSceneScrapersQuery(),
],
});
export const mutateReloadPlugins = () =>