mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Add Chapters for Galleries (#3289)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
Popover,
|
||||
Form,
|
||||
Row,
|
||||
Dropdown,
|
||||
} from "react-bootstrap";
|
||||
import cx from "classnames";
|
||||
import Mousetrap from "mousetrap";
|
||||
@@ -30,7 +31,7 @@ import {
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { useInterfaceLocalForage } from "../LocalForage";
|
||||
import { imageLightboxDisplayModeIntlMap } from "src/core/enums";
|
||||
import { ILightboxImage } from "./types";
|
||||
import { ILightboxImage, IChapter } from "./types";
|
||||
import {
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
@@ -42,6 +43,7 @@ import {
|
||||
faPlay,
|
||||
faSearchMinus,
|
||||
faTimes,
|
||||
faBars,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { RatingSystem } from "src/components/Shared/Rating/RatingSystem";
|
||||
import { useDebounce } from "../debounce";
|
||||
@@ -49,6 +51,8 @@ import { useDebounce } from "../debounce";
|
||||
const CLASSNAME = "Lightbox";
|
||||
const CLASSNAME_HEADER = `${CLASSNAME}-header`;
|
||||
const CLASSNAME_LEFT_SPACER = `${CLASSNAME_HEADER}-left-spacer`;
|
||||
const CLASSNAME_CHAPTERS = `${CLASSNAME_HEADER}-chapters`;
|
||||
const CLASSNAME_CHAPTER_BUTTON = `${CLASSNAME_HEADER}-chapter-button`;
|
||||
const CLASSNAME_INDICATOR = `${CLASSNAME_HEADER}-indicator`;
|
||||
const CLASSNAME_OPTIONS = `${CLASSNAME_HEADER}-options`;
|
||||
const CLASSNAME_OPTIONS_ICON = `${CLASSNAME_OPTIONS}-icon`;
|
||||
@@ -76,8 +80,11 @@ interface IProps {
|
||||
initialIndex?: number;
|
||||
showNavigation: boolean;
|
||||
slideshowEnabled?: boolean;
|
||||
pageHeader?: string;
|
||||
pageCallback?: (direction: number) => void;
|
||||
page?: number;
|
||||
pages?: number;
|
||||
pageSize?: number;
|
||||
pageCallback?: (props: { direction?: number; page?: number }) => void;
|
||||
chapters?: IChapter[];
|
||||
hide: () => void;
|
||||
}
|
||||
|
||||
@@ -88,12 +95,16 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||
initialIndex = 0,
|
||||
showNavigation,
|
||||
slideshowEnabled = false,
|
||||
pageHeader,
|
||||
page,
|
||||
pages,
|
||||
pageSize: pageSize = 40,
|
||||
pageCallback,
|
||||
chapters = [],
|
||||
hide,
|
||||
}) => {
|
||||
const [updateImage] = useImageUpdate();
|
||||
|
||||
// zero-based
|
||||
const [index, setIndex] = useState<number | null>(null);
|
||||
const [movingLeft, setMovingLeft] = useState(false);
|
||||
const oldIndex = useRef<number | null>(null);
|
||||
@@ -101,6 +112,7 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||
const [isSwitchingPage, setIsSwitchingPage] = useState(true);
|
||||
const [isFullscreen, setFullscreen] = useState(false);
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
const [showChapters, setShowChapters] = useState(false);
|
||||
const [imagesLoaded, setImagesLoaded] = useState(0);
|
||||
const [navOffset, setNavOffset] = useState<React.CSSProperties | undefined>();
|
||||
|
||||
@@ -310,12 +322,13 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||
(isUserAction = true) => {
|
||||
if (isSwitchingPage || index === -1) return;
|
||||
|
||||
setShowChapters(false);
|
||||
setMovingLeft(true);
|
||||
|
||||
if (index === 0) {
|
||||
// go to next page, or loop back if no callback is set
|
||||
if (pageCallback) {
|
||||
pageCallback(-1);
|
||||
pageCallback({ direction: -1 });
|
||||
setIndex(-1);
|
||||
oldImages.current = images;
|
||||
setIsSwitchingPage(true);
|
||||
@@ -334,11 +347,12 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||
if (isSwitchingPage) return;
|
||||
|
||||
setMovingLeft(false);
|
||||
setShowChapters(false);
|
||||
|
||||
if (index === images.length - 1) {
|
||||
// go to preview page, or loop back if no callback is set
|
||||
if (pageCallback) {
|
||||
pageCallback(1);
|
||||
pageCallback({ direction: 1 });
|
||||
oldImages.current = images;
|
||||
setIsSwitchingPage(true);
|
||||
setIndex(0);
|
||||
@@ -449,6 +463,65 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||
|
||||
const currentIndex = index === null ? initialIndex : index;
|
||||
|
||||
function gotoPage(imageIndex: number) {
|
||||
const indexInPage = (imageIndex - 1) % pageSize;
|
||||
if (pageCallback) {
|
||||
let jumppage = Math.floor(imageIndex / pageSize) + 1;
|
||||
if (page !== jumppage) {
|
||||
pageCallback({ page: jumppage });
|
||||
oldImages.current = images;
|
||||
setIsSwitchingPage(true);
|
||||
}
|
||||
}
|
||||
|
||||
setIndex(indexInPage);
|
||||
setShowChapters(false);
|
||||
}
|
||||
|
||||
function chapterHeader() {
|
||||
const imageNumber = (index ?? 0) + 1;
|
||||
const globalIndex = page
|
||||
? (page - 1) * pageSize + imageNumber
|
||||
: imageNumber;
|
||||
|
||||
let chapterTitle = "";
|
||||
chapters.forEach(function (chapter) {
|
||||
if (chapter.image_index > globalIndex) {
|
||||
return;
|
||||
}
|
||||
chapterTitle = chapter.title;
|
||||
});
|
||||
|
||||
return chapterTitle ?? "";
|
||||
}
|
||||
|
||||
const renderChapterMenu = () => {
|
||||
if (chapters.length <= 0) return;
|
||||
|
||||
const popoverContent = chapters.map(({ id, title, image_index }) => (
|
||||
<Dropdown.Item key={id} onClick={() => gotoPage(image_index)}>
|
||||
{" "}
|
||||
{title}
|
||||
{title.length > 0 ? " - #" : "#"}
|
||||
{image_index}
|
||||
</Dropdown.Item>
|
||||
));
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
show={showChapters}
|
||||
onToggle={() => setShowChapters(!showChapters)}
|
||||
>
|
||||
<Dropdown.Toggle className={`minimal ${CLASSNAME_CHAPTER_BUTTON}`}>
|
||||
<Icon icon={showChapters ? faTimes : faBars} />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className={`${CLASSNAME_CHAPTERS}`}>
|
||||
{popoverContent}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
// #2451: making OptionsForm an inline component means it
|
||||
// get re-rendered each time. This makes the text
|
||||
// field lose focus on input. Use function instead.
|
||||
@@ -634,6 +707,14 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const pageHeader =
|
||||
page && pages
|
||||
? intl.formatMessage(
|
||||
{ id: "dialogs.lightbox.page_header" },
|
||||
{ page, total: pages }
|
||||
)
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={CLASSNAME}
|
||||
@@ -642,9 +723,11 @@ export const LightboxComponent: React.FC<IProps> = ({
|
||||
onClick={handleClose}
|
||||
>
|
||||
<div className={CLASSNAME_HEADER}>
|
||||
<div className={CLASSNAME_LEFT_SPACER} />
|
||||
<div className={CLASSNAME_LEFT_SPACER}>{renderChapterMenu()}</div>
|
||||
<div className={CLASSNAME_INDICATOR}>
|
||||
<span>{pageHeader}</span>
|
||||
<span>
|
||||
{chapterHeader()} {pageHeader}
|
||||
</span>
|
||||
{images.length > 1 ? (
|
||||
<b ref={indicatorRef}>{`${currentIndex + 1} / ${images.length}`}</b>
|
||||
) : undefined}
|
||||
|
||||
Reference in New Issue
Block a user