mirror of
https://github.com/stashapp/stash.git
synced 2025-12-15 04:44:28 +01:00
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:
parent
abf0281903
commit
f0ec37c343
5 changed files with 113 additions and 54 deletions
|
|
@ -14,7 +14,7 @@ interface IScenePreviewProps {
|
|||
soundActive: boolean;
|
||||
}
|
||||
|
||||
const ScenePreview: React.FC<IScenePreviewProps> = ({
|
||||
export const ScenePreview: React.FC<IScenePreviewProps> = ({
|
||||
image,
|
||||
video,
|
||||
isPortrait,
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ textarea.scene-description {
|
|||
&-video {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
|
|||
setSaveState("");
|
||||
};
|
||||
|
||||
const classname = cx("row no-gutters mt-2 search-result", {
|
||||
const classname = cx("row mx-0 mt-2 search-result", {
|
||||
"selected-result": isActive,
|
||||
});
|
||||
|
||||
|
|
@ -336,6 +336,11 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
|
|||
Object.keys(performers ?? []).every((id) => performers?.[id].type) &&
|
||||
saveState === "";
|
||||
|
||||
const endpointBase = endpoint.match(/https?:\/\/.*?\//)?.[0];
|
||||
const stashBoxURL = endpointBase
|
||||
? `${endpointBase}scenes/${scene.stash_id}`
|
||||
: "";
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
|
||||
<li
|
||||
|
|
@ -345,11 +350,13 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
|
|||
>
|
||||
<div className="col-lg-6">
|
||||
<div className="row">
|
||||
<img
|
||||
src={scene.images[0]}
|
||||
alt=""
|
||||
className="align-self-center scene-image"
|
||||
/>
|
||||
<a href={stashBoxURL} target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
src={scene.images[0]}
|
||||
alt=""
|
||||
className="align-self-center scene-image"
|
||||
/>
|
||||
</a>
|
||||
<div className="d-flex flex-column justify-content-center scene-metadata">
|
||||
<h4 className="text-truncate" title={scene?.title ?? ""}>
|
||||
{sceneTitle}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||
import { Button, Card, Form, InputGroup } from "react-bootstrap";
|
||||
import { Link } from "react-router-dom";
|
||||
import { HashLink } from "react-router-hash-link";
|
||||
import { ScenePreview } from "src/components/Scenes/SceneCard";
|
||||
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { LoadingIndicator } from "src/components/Shared";
|
||||
|
|
@ -233,14 +234,19 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||
null;
|
||||
const isTagged = taggedScenes[scene.id];
|
||||
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) {
|
||||
maincontent = (
|
||||
<h5 className="text-right text-bold">Scene already tagged</h5>
|
||||
mainContent = (
|
||||
<div className="text-right">
|
||||
<h5 className="text-bold">Scene already tagged</h5>
|
||||
</div>
|
||||
);
|
||||
} else if (!isTagged && !hasStashIDs) {
|
||||
maincontent = (
|
||||
mainContent = (
|
||||
<InputGroup>
|
||||
<Form.Control
|
||||
className="text-input"
|
||||
|
|
@ -275,27 +281,52 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||
</InputGroup>
|
||||
);
|
||||
} else if (isTagged) {
|
||||
maincontent = (
|
||||
<h5 className="row no-gutters">
|
||||
<b className="col-4">Scene successfully tagged:</b>
|
||||
<Link
|
||||
className="offset-1 col-7 text-right"
|
||||
to={`/scenes/${scene.id}`}
|
||||
>
|
||||
{taggedScenes[scene.id].title}
|
||||
</Link>
|
||||
</h5>
|
||||
mainContent = (
|
||||
<div className="d-flex flex-column text-right">
|
||||
<h5>Scene successfully tagged:</h5>
|
||||
<h6>
|
||||
<Link className="bold" to={`/scenes/${scene.id}`}>
|
||||
{taggedScenes[scene.id].title}
|
||||
</Link>
|
||||
</h6>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let searchResult;
|
||||
if (searchErrors[scene.id]) {
|
||||
searchResult = (
|
||||
let subContent;
|
||||
if (scene.stash_ids.length > 0) {
|
||||
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">
|
||||
{searchErrors[scene.id]}
|
||||
</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 = (
|
||||
<StashSearchResult
|
||||
showMales={config.showMales}
|
||||
|
|
@ -317,7 +348,7 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||
!fingerprintMatch
|
||||
) {
|
||||
searchResult = (
|
||||
<ul className="pl-0 mt-4">
|
||||
<ul className="pl-0 mt-3 mb-0">
|
||||
{sortScenesByDuration(
|
||||
searchResults[scene.id],
|
||||
scene.file.duration ?? undefined
|
||||
|
|
@ -347,19 +378,25 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||
)}
|
||||
</ul>
|
||||
);
|
||||
} else if (searchResults[scene.id]?.length === 0) {
|
||||
searchResult = (
|
||||
<div className="text-danger font-weight-bold">No results found.</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={scene.id} className="my-2 search-item">
|
||||
<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
|
||||
to={`/scenes/${scene.id}`}
|
||||
className="scene-link"
|
||||
className="scene-link text-truncate w-100"
|
||||
title={scene.path}
|
||||
>
|
||||
{originalDir}
|
||||
|
|
@ -367,7 +404,10 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||
{`${file}.${ext}`}
|
||||
</Link>
|
||||
</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>
|
||||
{searchResult}
|
||||
</div>
|
||||
|
|
@ -376,9 +416,9 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||
|
||||
return (
|
||||
<Card className="tagger-table">
|
||||
<div className="tagger-table-header row flex-nowrap mb-4 align-items-center">
|
||||
<div className="col-md-6">
|
||||
<b>Path</b>
|
||||
<div className="tagger-table-header d-flex flex-nowrap align-items-center">
|
||||
<div className="col-md-6 pl-0">
|
||||
<b>Scene</b>
|
||||
</div>
|
||||
<div className="col-md-2">
|
||||
<b>Query</b>
|
||||
|
|
@ -400,18 +440,14 @@ const TaggerList: React.FC<ITaggerListProps> = ({
|
|||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="mr-2">
|
||||
<Button
|
||||
onClick={handleFingerprintSearch}
|
||||
disabled={!canFingerprintSearch() && !loadingFingerprints}
|
||||
>
|
||||
{canFingerprintSearch() && <span>Match Fingerprints</span>}
|
||||
{!canFingerprintSearch() && getFingerprintCount()}
|
||||
{loadingFingerprints && (
|
||||
<LoadingIndicator message="" inline small />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleFingerprintSearch}
|
||||
disabled={!canFingerprintSearch() && !loadingFingerprints}
|
||||
>
|
||||
{canFingerprintSearch() && <span>Match Fingerprints</span>}
|
||||
{!canFingerprintSearch() && getFingerprintCount()}
|
||||
{loadingFingerprints && <LoadingIndicator message="" inline small />}
|
||||
</Button>
|
||||
</div>
|
||||
{renderScenes()}
|
||||
</Card>
|
||||
|
|
@ -467,7 +503,7 @@ export const Tagger: React.FC<ITaggerProps> = ({ scenes }) => {
|
|||
onClose={() => setShowManual(false)}
|
||||
defaultActiveTab="Tagger.md"
|
||||
/>
|
||||
<div className="tagger-container mx-auto">
|
||||
<div className="tagger-container row mx-md-auto">
|
||||
{selectedEndpointIndex !== -1 && selectedEndpoint ? (
|
||||
<>
|
||||
<div className="row mb-2 no-gutters">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
.tagger-container {
|
||||
max-width: 1400px;
|
||||
// min-width: 1200px;
|
||||
max-width: 1600px;
|
||||
|
||||
.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 {
|
||||
|
|
@ -9,14 +24,13 @@
|
|||
|
||||
.search-item {
|
||||
background-color: #495b68;
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
border-radius: 3px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.search-result {
|
||||
background-color: rgba(61, 80, 92, 0.3);
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 1rem 0;
|
||||
|
||||
&:hover {
|
||||
background-color: hsl(204, 20, 30);
|
||||
|
|
@ -43,6 +57,7 @@
|
|||
max-height: 10rem;
|
||||
max-width: 14rem;
|
||||
min-width: 168px;
|
||||
object-fit: contain;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue