mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Movie card visual consistency (#1758)
* Support getting scenes on movies in the API * Make movie card visually consistent with scene etc * Add date * Add synopsis * Show scene count with hover listing the scenes * Move scene index to button * Move scene number to own section Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
parent
5fdab995f5
commit
1d04b550b9
7 changed files with 82 additions and 16 deletions
|
|
@ -2,4 +2,4 @@ fragment SlimMovieData on Movie {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
front_image_path
|
front_image_path
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,10 @@ fragment MovieData on Movie {
|
||||||
front_image_path
|
front_image_path
|
||||||
back_image_path
|
back_image_path
|
||||||
scene_count
|
scene_count
|
||||||
|
|
||||||
|
scenes {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ type Movie {
|
||||||
front_image_path: String # Resolver
|
front_image_path: String # Resolver
|
||||||
back_image_path: String # Resolver
|
back_image_path: String # Resolver
|
||||||
scene_count: Int # Resolver
|
scene_count: Int # Resolver
|
||||||
|
scenes: [Scene!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
input MovieCreateInput {
|
input MovieCreateInput {
|
||||||
|
|
@ -60,4 +61,4 @@ input MovieDestroyInput {
|
||||||
type FindMoviesResultType {
|
type FindMoviesResultType {
|
||||||
count: Int!
|
count: Int!
|
||||||
movies: [Movie!]!
|
movies: [Movie!]!
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,18 @@ func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret
|
||||||
return &res, err
|
return &res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*models.Scene, err error) {
|
||||||
|
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||||
|
var err error
|
||||||
|
ret, err = repo.Scene().FindByMovieID(obj.ID)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *movieResolver) CreatedAt(ctx context.Context, obj *models.Movie) (*time.Time, error) {
|
func (r *movieResolver) CreatedAt(ctx context.Context, obj *models.Movie) (*time.Time, error) {
|
||||||
return &obj.CreatedAt.Timestamp, nil
|
return &obj.CreatedAt.Timestamp, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
* Support filtering Movies by Performers. ([#1675](https://github.com/stashapp/stash/pull/1675))
|
* Support filtering Movies by Performers. ([#1675](https://github.com/stashapp/stash/pull/1675))
|
||||||
|
|
||||||
### 🎨 Improvements
|
### 🎨 Improvements
|
||||||
|
* Added date and details to Movie card, and move scene count to icon. ([#1758](https://github.com/stashapp/stash/pull/1758))
|
||||||
* Added date and details to Gallery card, and move image count to icon. ([#1763](https://github.com/stashapp/stash/pull/1763))
|
* Added date and details to Gallery card, and move image count to icon. ([#1763](https://github.com/stashapp/stash/pull/1763))
|
||||||
* Optimised image thumbnail generation (optionally using `libvips`) and made optional. ([#1655](https://github.com/stashapp/stash/pull/1655))
|
* Optimised image thumbnail generation (optionally using `libvips`) and made optional. ([#1655](https://github.com/stashapp/stash/pull/1655))
|
||||||
* Added missing image table indexes, resulting in a significant performance improvement. ([#1740](https://github.com/stashapp/stash/pull/1740))
|
* Added missing image table indexes, resulting in a significant performance improvement. ([#1740](https://github.com/stashapp/stash/pull/1740))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
import React, { FunctionComponent } from "react";
|
import React, { FunctionComponent } from "react";
|
||||||
import { FormattedPlural } from "react-intl";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { GridCard } from "src/components/Shared";
|
import {
|
||||||
|
GridCard,
|
||||||
|
HoverPopover,
|
||||||
|
Icon,
|
||||||
|
TagLink,
|
||||||
|
TruncatedText,
|
||||||
|
} from "src/components/Shared";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
|
@ -14,20 +21,47 @@ interface IProps {
|
||||||
|
|
||||||
export const MovieCard: FunctionComponent<IProps> = (props: IProps) => {
|
export const MovieCard: FunctionComponent<IProps> = (props: IProps) => {
|
||||||
function maybeRenderSceneNumber() {
|
function maybeRenderSceneNumber() {
|
||||||
if (!props.sceneIndex) {
|
if (!props.sceneIndex) return;
|
||||||
return (
|
|
||||||
<span>
|
return (
|
||||||
{props.movie.scene_count}
|
<>
|
||||||
<FormattedPlural
|
<hr />
|
||||||
value={props.movie.scene_count ?? 0}
|
<span className="movie-scene-number">
|
||||||
one="scene"
|
<FormattedMessage id="scene" /> #{props.sceneIndex}
|
||||||
other="scenes"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeRenderScenesPopoverButton() {
|
||||||
|
if (props.movie.scenes.length === 0) return;
|
||||||
|
|
||||||
|
const popoverContent = props.movie.scenes.map((scene) => (
|
||||||
|
<TagLink key={scene.id} scene={scene} />
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HoverPopover placement="bottom" content={popoverContent}>
|
||||||
|
<Button className="minimal">
|
||||||
|
<Icon icon="play-circle" />
|
||||||
|
<span>{props.movie.scenes.length}</span>
|
||||||
|
</Button>
|
||||||
|
</HoverPopover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeRenderPopoverButtonGroup() {
|
||||||
|
if (props.sceneIndex || props.movie.scenes.length > 0) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{maybeRenderSceneNumber()}
|
||||||
|
<hr />
|
||||||
|
<ButtonGroup className="card-popovers">
|
||||||
|
{maybeRenderScenesPopoverButton()}
|
||||||
|
</ButtonGroup>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <span>Scene number: {props.sceneIndex}</span>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -46,10 +80,18 @@ export const MovieCard: FunctionComponent<IProps> = (props: IProps) => {
|
||||||
<RatingBanner rating={props.movie.rating} />
|
<RatingBanner rating={props.movie.rating} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
details={maybeRenderSceneNumber()}
|
details={
|
||||||
|
<>
|
||||||
|
<span>{props.movie.date}</span>
|
||||||
|
<p>
|
||||||
|
<TruncatedText text={props.movie.synopsis} lineCount={3} />
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
selected={props.selected}
|
selected={props.selected}
|
||||||
selecting={props.selecting}
|
selecting={props.selecting}
|
||||||
onSelectedChanged={props.onSelectedChanged}
|
onSelectedChanged={props.onSelectedChanged}
|
||||||
|
popovers={maybeRenderPopoverButtonGroup()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.movie-scene-number {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie-images {
|
.movie-images {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue