Gallery scrubber wall view (#5191)

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
yoshnopa 2024-09-05 05:06:43 +02:00 committed by GitHub
parent 8c2a25b833
commit 5721ea2b70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 83 additions and 31 deletions

View file

@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { useIntl } from "react-intl";
import { Link } from "react-router-dom";
import * as GQL from "src/core/generated-graphql";
@ -7,32 +7,50 @@ import TextUtils from "src/utils/text";
import { useGalleryLightbox } from "src/hooks/Lightbox/hooks";
import { galleryTitle } from "src/core/galleries";
import { RatingSystem } from "../Shared/Rating/RatingSystem";
import { GalleryPreviewScrubber } from "./GalleryPreviewScrubber";
import cx from "classnames";
const CLASSNAME = "GalleryWallCard";
const CLASSNAME_FOOTER = `${CLASSNAME}-footer`;
const CLASSNAME_IMG = `${CLASSNAME}-img`;
const CLASSNAME_TITLE = `${CLASSNAME}-title`;
const CLASSNAME_IMG_CONTAIN = `${CLASSNAME}-img-contain`;
interface IProps {
gallery: GQL.SlimGalleryDataFragment;
}
type Orientation = "landscape" | "portrait";
function getOrientation(width: number, height: number): Orientation {
return width > height ? "landscape" : "portrait";
}
const GalleryWallCard: React.FC<IProps> = ({ gallery }) => {
const intl = useIntl();
const [orientation, setOrientation] = React.useState<
"landscape" | "portrait"
>("landscape");
const [coverOrientation, setCoverOrientation] =
React.useState<Orientation>("landscape");
const [imageOrientation, setImageOrientation] =
React.useState<Orientation>("landscape");
const showLightbox = useGalleryLightbox(gallery.id, gallery.chapters);
const cover = gallery?.paths.cover;
function onImageLoad(e: React.SyntheticEvent<HTMLImageElement, Event>) {
function onCoverLoad(e: React.SyntheticEvent<HTMLImageElement, Event>) {
const target = e.target as HTMLImageElement;
setOrientation(
target.naturalWidth > target.naturalHeight ? "landscape" : "portrait"
setCoverOrientation(
getOrientation(target.naturalWidth, target.naturalHeight)
);
}
function onNonCoverLoad(e: React.SyntheticEvent<HTMLImageElement, Event>) {
const target = e.target as HTMLImageElement;
setImageOrientation(
getOrientation(target.naturalWidth, target.naturalHeight)
);
}
const [imgSrc, setImgSrc] = useState<string | undefined>(cover ?? undefined);
const title = galleryTitle(gallery);
const performerNames = gallery.performers.map((p) => p.name);
const performers =
@ -48,10 +66,13 @@ const GalleryWallCard: React.FC<IProps> = ({ gallery }) => {
showLightbox(0);
}
const imgClassname =
imageOrientation !== coverOrientation ? CLASSNAME_IMG_CONTAIN : "";
return (
<>
<section
className={`${CLASSNAME} ${CLASSNAME}-${orientation}`}
className={`${CLASSNAME} ${CLASSNAME}-${coverOrientation}`}
onClick={showLightboxStart}
onKeyPress={showLightboxStart}
role="button"
@ -60,29 +81,41 @@ const GalleryWallCard: React.FC<IProps> = ({ gallery }) => {
<RatingSystem value={gallery.rating100} disabled withoutContext />
<img
loading="lazy"
src={cover}
src={imgSrc}
alt=""
className={CLASSNAME_IMG}
onLoad={onImageLoad}
className={cx(CLASSNAME_IMG, imgClassname)}
// set orientation based on cover only
onLoad={imgSrc === cover ? onCoverLoad : onNonCoverLoad}
/>
<footer className={CLASSNAME_FOOTER}>
<Link
to={`/galleries/${gallery.id}`}
onClick={(e) => e.stopPropagation()}
>
{title && (
<TruncatedText
text={title}
lineCount={1}
className={CLASSNAME_TITLE}
/>
)}
<TruncatedText text={performers.join(", ")} />
<div>
{gallery.date && TextUtils.formatDate(intl, gallery.date)}
</div>
</Link>
</footer>
<div className="lineargradient">
<footer className={CLASSNAME_FOOTER}>
<Link
to={`/galleries/${gallery.id}`}
onClick={(e) => e.stopPropagation()}
>
{title && (
<TruncatedText
text={title}
lineCount={1}
className={CLASSNAME_TITLE}
/>
)}
<TruncatedText text={performers.join(", ")} />
<div>
{gallery.date && TextUtils.formatDate(intl, gallery.date)}
</div>
</Link>
</footer>
<GalleryPreviewScrubber
previewPath={gallery.paths.preview}
defaultPath={cover ?? ""}
imageCount={gallery.image_count}
onClick={(i) => {
showLightbox(i);
}}
onPathChanged={setImgSrc}
/>
</div>
</section>
</>
);

View file

@ -237,6 +237,18 @@ $galleryTabWidth: 450px;
width: 96vw;
}
.lineargradient {
background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3));
bottom: 100px;
height: 100px;
position: relative;
}
.preview-scrubber {
top: 0;
z-index: 1;
}
@mixin galleryWidth($width) {
height: math.div($width, 3) * 2;
@ -264,6 +276,11 @@ $galleryTabWidth: 450px;
object-fit: cover;
object-position: center 20%;
width: 100%;
&.GalleryWallCard-img-contain {
object-fit: contain;
object-position: initial;
}
}
&-title {
@ -271,13 +288,13 @@ $galleryTabWidth: 450px;
}
&-footer {
background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3));
bottom: 0;
bottom: 20px;
padding: 1rem;
position: absolute;
text-shadow: 1px 1px 3px black;
transition: 0s opacity;
width: 100%;
z-index: 2;
@media (min-width: 768px) {
opacity: 0;
@ -310,6 +327,7 @@ $galleryTabWidth: 450px;
right: 1rem;
text-shadow: 1px 1px 3px black;
top: 1rem;
z-index: 2;
}
.rating-stars {

View file

@ -76,6 +76,7 @@ export const HoverScrubber: React.FC<IHoverScrubber> = ({
return;
e.preventDefault();
e.stopPropagation();
onClick();
}