mirror of https://github.com/stashapp/stash.git
Add scraper list page (#833)
This commit is contained in:
parent
aca2c7c5f4
commit
12cba97192
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = () =>
|
||||
|
|
Loading…
Reference in New Issue