Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scraper list page #833

Merged
merged 4 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/v2.5/src/components/Changelog/versions/v040.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
### ✨ New Features
* Add scrapers list setting page.
* Add partial import from zip file.
* Add selective scene export.

Expand Down
7 changes: 7 additions & 0 deletions ui/v2.5/src/components/Settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand Down
238 changes: 238 additions & 0 deletions ui/v2.5/src/components/Settings/SettingsScrapersPanel.tsx
Original file line number Diff line number Diff line change
@@ -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>
</>
);
};
30 changes: 30 additions & 0 deletions ui/v2.5/src/components/Settings/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
5 changes: 5 additions & 0 deletions ui/v2.5/src/core/StashService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,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 = () =>
Expand Down