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:
gitgiggety 2021-09-26 05:52:08 +02:00 committed by GitHub
parent 5fdab995f5
commit 1d04b550b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 82 additions and 16 deletions

View file

@ -2,4 +2,4 @@ fragment SlimMovieData on Movie {
id id
name name
front_image_path front_image_path
} }

View file

@ -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
}
} }

View file

@ -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!]!
} }

View file

@ -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
} }

View file

@ -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))

View file

@ -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}&nbsp; <>
<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()}
/> />
); );
}; };

View file

@ -17,6 +17,10 @@
object-fit: contain; object-fit: contain;
width: 100%; width: 100%;
} }
.movie-scene-number {
text-align: center;
}
} }
.movie-images { .movie-images {