Added screenshots/previews to tagger list (#939)

* Added screenshots/previews to tagger list
* Move errors and stashids to subcontent container, and tweak layout
* Fix search-result margin
Co-authored-by: Infinite <infinitekittens@protonmail.com>
This commit is contained in:
JoeSmithStarkers
2020-11-24 08:02:31 +11:00
committed by GitHub
parent abf0281903
commit f0ec37c343
5 changed files with 113 additions and 54 deletions

View File

@@ -14,7 +14,7 @@ interface IScenePreviewProps {
soundActive: boolean; soundActive: boolean;
} }
const ScenePreview: React.FC<IScenePreviewProps> = ({ export const ScenePreview: React.FC<IScenePreviewProps> = ({
image, image,
video, video,
isPortrait, isPortrait,

View File

@@ -196,6 +196,7 @@ textarea.scene-description {
&-video { &-video {
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
object-position: top;
width: 100%; width: 100%;
} }

View File

@@ -313,7 +313,7 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
setSaveState(""); setSaveState("");
}; };
const classname = cx("row no-gutters mt-2 search-result", { const classname = cx("row mx-0 mt-2 search-result", {
"selected-result": isActive, "selected-result": isActive,
}); });
@@ -336,6 +336,11 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
Object.keys(performers ?? []).every((id) => performers?.[id].type) && Object.keys(performers ?? []).every((id) => performers?.[id].type) &&
saveState === ""; saveState === "";
const endpointBase = endpoint.match(/https?:\/\/.*?\//)?.[0];
const stashBoxURL = endpointBase
? `${endpointBase}scenes/${scene.stash_id}`
: "";
return ( return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
<li <li
@@ -345,11 +350,13 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
> >
<div className="col-lg-6"> <div className="col-lg-6">
<div className="row"> <div className="row">
<img <a href={stashBoxURL} target="_blank" rel="noopener noreferrer">
src={scene.images[0]} <img
alt="" src={scene.images[0]}
className="align-self-center scene-image" alt=""
/> className="align-self-center scene-image"
/>
</a>
<div className="d-flex flex-column justify-content-center scene-metadata"> <div className="d-flex flex-column justify-content-center scene-metadata">
<h4 className="text-truncate" title={scene?.title ?? ""}> <h4 className="text-truncate" title={scene?.title ?? ""}>
{sceneTitle} {sceneTitle}

View File

@@ -2,6 +2,7 @@ import React, { useState } from "react";
import { Button, Card, Form, InputGroup } from "react-bootstrap"; import { Button, Card, Form, InputGroup } from "react-bootstrap";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { HashLink } from "react-router-hash-link"; import { HashLink } from "react-router-hash-link";
import { ScenePreview } from "src/components/Scenes/SceneCard";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { LoadingIndicator } from "src/components/Shared"; import { LoadingIndicator } from "src/components/Shared";
@@ -233,14 +234,19 @@ const TaggerList: React.FC<ITaggerListProps> = ({
null; null;
const isTagged = taggedScenes[scene.id]; const isTagged = taggedScenes[scene.id];
const hasStashIDs = scene.stash_ids.length > 0; const hasStashIDs = scene.stash_ids.length > 0;
const width = scene.file.width ? scene.file.width : 0;
const height = scene.file.height ? scene.file.height : 0;
const isPortrait = height > width;
let maincontent; let mainContent;
if (!isTagged && hasStashIDs) { if (!isTagged && hasStashIDs) {
maincontent = ( mainContent = (
<h5 className="text-right text-bold">Scene already tagged</h5> <div className="text-right">
<h5 className="text-bold">Scene already tagged</h5>
</div>
); );
} else if (!isTagged && !hasStashIDs) { } else if (!isTagged && !hasStashIDs) {
maincontent = ( mainContent = (
<InputGroup> <InputGroup>
<Form.Control <Form.Control
className="text-input" className="text-input"
@@ -275,27 +281,52 @@ const TaggerList: React.FC<ITaggerListProps> = ({
</InputGroup> </InputGroup>
); );
} else if (isTagged) { } else if (isTagged) {
maincontent = ( mainContent = (
<h5 className="row no-gutters"> <div className="d-flex flex-column text-right">
<b className="col-4">Scene successfully tagged:</b> <h5>Scene successfully tagged:</h5>
<Link <h6>
className="offset-1 col-7 text-right" <Link className="bold" to={`/scenes/${scene.id}`}>
to={`/scenes/${scene.id}`} {taggedScenes[scene.id].title}
> </Link>
{taggedScenes[scene.id].title} </h6>
</Link> </div>
</h5>
); );
} }
let searchResult; let subContent;
if (searchErrors[scene.id]) { if (scene.stash_ids.length > 0) {
searchResult = ( const stashLinks = scene.stash_ids.map((stashID) => {
const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0];
const link = base ? (
<a
className="small d-block"
href={`${base}scenes/${stashID.stash_id}`}
target="_blank"
rel="noopener noreferrer"
>
{stashID.stash_id}
</a>
) : (
<div className="small">{stashID.stash_id}</div>
);
return link;
});
subContent = <>{stashLinks}</>;
} else if (searchErrors[scene.id]) {
subContent = (
<div className="text-danger font-weight-bold"> <div className="text-danger font-weight-bold">
{searchErrors[scene.id]} {searchErrors[scene.id]}
</div> </div>
); );
} else if (fingerprintMatch && !isTagged && !hasStashIDs) { } else if (searchResults[scene.id]?.length === 0) {
subContent = (
<div className="text-danger font-weight-bold">No results found.</div>
);
}
let searchResult;
if (fingerprintMatch && !isTagged && !hasStashIDs) {
searchResult = ( searchResult = (
<StashSearchResult <StashSearchResult
showMales={config.showMales} showMales={config.showMales}
@@ -317,7 +348,7 @@ const TaggerList: React.FC<ITaggerListProps> = ({
!fingerprintMatch !fingerprintMatch
) { ) {
searchResult = ( searchResult = (
<ul className="pl-0 mt-4"> <ul className="pl-0 mt-3 mb-0">
{sortScenesByDuration( {sortScenesByDuration(
searchResults[scene.id], searchResults[scene.id],
scene.file.duration ?? undefined scene.file.duration ?? undefined
@@ -347,19 +378,25 @@ const TaggerList: React.FC<ITaggerListProps> = ({
)} )}
</ul> </ul>
); );
} else if (searchResults[scene.id]?.length === 0) {
searchResult = (
<div className="text-danger font-weight-bold">No results found.</div>
);
} }
return ( return (
<div key={scene.id} className="my-2 search-item"> <div key={scene.id} className="my-2 search-item">
<div className="row"> <div className="row">
<div className="col-md-6 my-1 text-truncate align-self-center"> <div className="col col-lg-6 overflow-hidden align-items-center d-flex flex-column flex-sm-row">
<div className="scene-card mr-3">
<Link to={`/scenes/${scene.id}`}>
<ScenePreview
image={scene.paths.screenshot ?? undefined}
video={scene.paths.preview ?? undefined}
isPortrait={isPortrait}
soundActive={false}
/>
</Link>
</div>
<Link <Link
to={`/scenes/${scene.id}`} to={`/scenes/${scene.id}`}
className="scene-link" className="scene-link text-truncate w-100"
title={scene.path} title={scene.path}
> >
{originalDir} {originalDir}
@@ -367,7 +404,10 @@ const TaggerList: React.FC<ITaggerListProps> = ({
{`${file}.${ext}`} {`${file}.${ext}`}
</Link> </Link>
</div> </div>
<div className="col-md-6 my-1">{maincontent}</div> <div className="col-md-6 my-1 align-self-center">
{mainContent}
<div className="sub-content text-right">{subContent}</div>
</div>
</div> </div>
{searchResult} {searchResult}
</div> </div>
@@ -376,9 +416,9 @@ const TaggerList: React.FC<ITaggerListProps> = ({
return ( return (
<Card className="tagger-table"> <Card className="tagger-table">
<div className="tagger-table-header row flex-nowrap mb-4 align-items-center"> <div className="tagger-table-header d-flex flex-nowrap align-items-center">
<div className="col-md-6"> <div className="col-md-6 pl-0">
<b>Path</b> <b>Scene</b>
</div> </div>
<div className="col-md-2"> <div className="col-md-2">
<b>Query</b> <b>Query</b>
@@ -400,18 +440,14 @@ const TaggerList: React.FC<ITaggerListProps> = ({
</Button> </Button>
)} )}
</div> </div>
<div className="mr-2"> <Button
<Button onClick={handleFingerprintSearch}
onClick={handleFingerprintSearch} disabled={!canFingerprintSearch() && !loadingFingerprints}
disabled={!canFingerprintSearch() && !loadingFingerprints} >
> {canFingerprintSearch() && <span>Match Fingerprints</span>}
{canFingerprintSearch() && <span>Match Fingerprints</span>} {!canFingerprintSearch() && getFingerprintCount()}
{!canFingerprintSearch() && getFingerprintCount()} {loadingFingerprints && <LoadingIndicator message="" inline small />}
{loadingFingerprints && ( </Button>
<LoadingIndicator message="" inline small />
)}
</Button>
</div>
</div> </div>
{renderScenes()} {renderScenes()}
</Card> </Card>
@@ -467,7 +503,7 @@ export const Tagger: React.FC<ITaggerProps> = ({ scenes }) => {
onClose={() => setShowManual(false)} onClose={() => setShowManual(false)}
defaultActiveTab="Tagger.md" defaultActiveTab="Tagger.md"
/> />
<div className="tagger-container mx-auto"> <div className="tagger-container row mx-md-auto">
{selectedEndpointIndex !== -1 && selectedEndpoint ? ( {selectedEndpointIndex !== -1 && selectedEndpoint ? (
<> <>
<div className="row mb-2 no-gutters"> <div className="row mb-2 no-gutters">

View File

@@ -1,6 +1,21 @@
.tagger-container { .tagger-container {
max-width: 1400px; max-width: 1600px;
// min-width: 1200px;
.scene-card-preview {
border-radius: 3px;
margin-bottom: 0;
max-height: 100px;
overflow: hidden;
width: 150px;
&-video {
background-color: #495b68;
}
}
.sub-content {
min-height: 1.5rem;
}
} }
.tagger-table { .tagger-table {
@@ -9,14 +24,13 @@
.search-item { .search-item {
background-color: #495b68; background-color: #495b68;
margin-left: -20px; border-radius: 3px;
margin-right: -20px;
padding: 1rem; padding: 1rem;
} }
.search-result { .search-result {
background-color: rgba(61, 80, 92, 0.3); background-color: rgba(61, 80, 92, 0.3);
padding: 0.5rem 1rem; padding: 1rem 0;
&:hover { &:hover {
background-color: hsl(204, 20, 30); background-color: hsl(204, 20, 30);
@@ -43,6 +57,7 @@
max-height: 10rem; max-height: 10rem;
max-width: 14rem; max-width: 14rem;
min-width: 168px; min-width: 168px;
object-fit: contain;
padding: 0 1rem; padding: 0 1rem;
} }