mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03: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:
@@ -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,
|
||||||
|
|||||||
@@ -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%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user