From d3be1d8e299ea0060afdf6e0326639db397d43f4 Mon Sep 17 00:00:00 2001 From: Infinite Date: Wed, 16 Dec 2020 10:10:38 +0000 Subject: [PATCH 01/12] WIP --- ui/v2.5/src/components/Galleries/GalleryList.tsx | 12 +++++++++++- ui/v2.5/src/models/list-filter/filter.ts | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index b5d7fe66334..1e86b01fc08 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -13,6 +13,7 @@ import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { queryFindGalleries } from "src/core/StashService"; import { GalleryCard } from "./GalleryCard"; +import GalleryWallCard from "./GalleryWallCard"; import { EditGalleriesDialog } from "./EditGalleriesDialog"; import { DeleteGalleriesDialog } from "./DeleteGalleriesDialog"; import { ExportDialog } from "../Shared/ExportDialog"; @@ -212,7 +213,16 @@ export const GalleryList: React.FC = ({ ); } if (filter.displayMode === DisplayMode.Wall) { - return

TODO

; + return ( +
+ {result.data.findGalleries.galleries.map((gallery) => ( + + ))} +
+ ); } } diff --git a/ui/v2.5/src/models/list-filter/filter.ts b/ui/v2.5/src/models/list-filter/filter.ts index ae58e37b88f..4d6bb3e6518 100644 --- a/ui/v2.5/src/models/list-filter/filter.ts +++ b/ui/v2.5/src/models/list-filter/filter.ts @@ -248,6 +248,11 @@ export class ListFilterModel { new PerformersCriterionOption(), new StudiosCriterionOption(), ]; + this.displayModeOptions = [ + DisplayMode.Grid, + DisplayMode.List, + DisplayMode.Wall, + ]; break; case FilterMode.SceneMarkers: this.sortBy = "title"; From bfb9a5fdf01019e5763db939a687523506a5e382 Mon Sep 17 00:00:00 2001 From: Infinite Date: Thu, 17 Dec 2020 18:04:14 +0000 Subject: [PATCH 02/12] WIP --- .../src/components/Galleries/GalleryList.tsx | 16 +++-- ui/v2.5/src/components/Galleries/styles.scss | 65 +++++++++++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index 1e86b01fc08..13d59879473 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -214,13 +214,15 @@ export const GalleryList: React.FC = ({ } if (filter.displayMode === DisplayMode.Wall) { return ( -
- {result.data.findGalleries.galleries.map((gallery) => ( - - ))} +
+
+ {result.data.findGalleries.galleries.map((gallery) => ( + + ))} +
); } diff --git a/ui/v2.5/src/components/Galleries/styles.scss b/ui/v2.5/src/components/Galleries/styles.scss index 15e4c040a5c..fd328faa4d8 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -96,3 +96,68 @@ $galleryTabWidth: 450px; height: calc(1.5em + 0.75rem + 2px); } } + + +.GalleryWall { + display: flex; + flex-wrap: wrap; + margin: 0 auto; + width: 96vw; + + /* Prevents last row from consuming all space and stretching images to oblivion */ + &::after { + content: ""; + flex: auto; + flex-grow: 9999; + } +} + +.GalleryWallCard { + position: relative; + height: 21.333333vw; + padding: 2px; + + &-landscape { + flex-grow: 2; + width: 32vw; + + } + &-portrait { + flex-grow: 1; + width: 16vw; + } + + &-img { + object-fit: cover; + object-position: center 20%; + width: 100%; + max-height: 100%; + } + + &-title { + &::after { + content: "\2022"; + margin: 0 .5rem; + } + } + + &-footer { + bottom: 0; + opacity: 0; + padding: 1rem; + position: absolute; + width: 100%; + background-color: rgba(0,0,0, .3); + text-shadow: 1px 1px 3px black; + transition: 0s opacity; + + a { + color: white; + } + } + + &:hover &-footer { + opacity: 1; + transition: 1s opacity; + } +} From 720da64fcc6420f3180b7041c56ebf08753d1d45 Mon Sep 17 00:00:00 2001 From: Infinite Date: Fri, 18 Dec 2020 20:43:08 +0000 Subject: [PATCH 03/12] WIP --- .../components/Galleries/GalleryWallCard.tsx | 54 ++++++ ui/v2.5/src/components/Galleries/styles.scss | 34 +++- ui/v2.5/src/components/Shared/RatingStars.tsx | 22 +++ ui/v2.5/src/components/Shared/index.ts | 1 + ui/v2.5/src/components/Shared/styles.scss | 13 ++ ui/v2.5/src/hooks/Lightbox/Lightbox.tsx | 164 ++++++++++++++++++ ui/v2.5/src/hooks/Lightbox/index.ts | 1 + ui/v2.5/src/hooks/Lightbox/lightbox.scss | 84 +++++++++ ui/v2.5/src/hooks/index.ts | 1 + ui/v2.5/src/index.scss | 1 + 10 files changed, 369 insertions(+), 6 deletions(-) create mode 100644 ui/v2.5/src/components/Galleries/GalleryWallCard.tsx create mode 100644 ui/v2.5/src/components/Shared/RatingStars.tsx create mode 100644 ui/v2.5/src/hooks/Lightbox/Lightbox.tsx create mode 100644 ui/v2.5/src/hooks/Lightbox/index.ts create mode 100644 ui/v2.5/src/hooks/Lightbox/lightbox.scss diff --git a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx new file mode 100644 index 00000000000..87d5c1ace4e --- /dev/null +++ b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { useIntl } from 'react-intl'; +import { Link } from 'react-router-dom'; +import * as GQL from "src/core/generated-graphql"; +import { RatingStars, TruncatedText } from 'src/components/Shared'; +import { TextUtils } from 'src/utils'; +import { useGalleryLightbox } from 'src/hooks'; + +const CLASSNAME = 'GalleryWallCard'; +const CLASSNAME_FOOTER = `${CLASSNAME}-footer`; +const CLASSNAME_IMG = `${CLASSNAME}-img`; +const CLASSNAME_TITLE = `${CLASSNAME}-title`; + +interface IProps { + gallery: GQL.GallerySlimDataFragment; +} + +const GalleryWallCard: React.FC = ({ gallery }) => { + const intl = useIntl(); + const [showLightbox, lightbox] = useGalleryLightbox(gallery.id); + + const orientation = (gallery?.cover?.file.width ?? 0) > (gallery.cover?.file.height ?? 0) ? 'landscape' : 'portrait'; + const cover = gallery?.cover?.paths.thumbnail ?? ''; + const title = gallery.title ?? gallery.path; + const performerNames = gallery.performers.map(p => p.name); + const performers = performerNames.length >= 2 ? [...performerNames.slice(0, -2), performerNames.slice(-2).join(' & ')] : performerNames; + + return ( + <> +
+ + +
+ + { title && ( + + )} + +
{ gallery.date && TextUtils.formatDate(intl, gallery.date) }
+ +
+
+ { lightbox } + + ); +}; + +export default GalleryWallCard; diff --git a/ui/v2.5/src/components/Galleries/styles.scss b/ui/v2.5/src/components/Galleries/styles.scss index fd328faa4d8..d89a8a47240 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -131,26 +131,29 @@ $galleryTabWidth: 450px; object-fit: cover; object-position: center 20%; width: 100%; - max-height: 100%; + height: 100%; } &-title { - &::after { - content: "\2022"; - margin: 0 .5rem; - } + font-weight: bold; } &-footer { + background-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0, 0.3)); bottom: 0; opacity: 0; padding: 1rem; position: absolute; width: 100%; - background-color: rgba(0,0,0, .3); text-shadow: 1px 1px 3px black; transition: 0s opacity; + &:hover { + .GalleryWallCard-title { + text-decoration: underline; + } + } + a { color: white; } @@ -159,5 +162,24 @@ $galleryTabWidth: 450px; &:hover &-footer { opacity: 1; transition: 1s opacity; + transition-delay: 500ms; + + a { + text-decoration: none; + } + } + + .RatingStars { + position: absolute; + top: 1rem; + right: 1rem; + + &-unfilled { + display: none; + } + + &-filled { + filter: drop-shadow(1px 1px 1px #222); + } } } diff --git a/ui/v2.5/src/components/Shared/RatingStars.tsx b/ui/v2.5/src/components/Shared/RatingStars.tsx new file mode 100644 index 00000000000..65b79f48ade --- /dev/null +++ b/ui/v2.5/src/components/Shared/RatingStars.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import Icon from './Icon'; + +const CLASSNAME = 'RatingStars'; +const CLASSNAME_FILLED = `${CLASSNAME}-filled`; +const CLASSNAME_UNFILLED = `${CLASSNAME}-unfilled`; + +interface IProps { + rating?: number | null; +} + +export const RatingStars: React.FC = ({ rating }) => ( + rating ? ( +
+ + = 2 ? 'fas' : 'far', 'star']} className={rating >= 2 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} /> + = 3 ? 'fas' : 'far', 'star']} className={rating >= 3 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} /> + = 4 ? 'fas' : 'far', 'star']} className={rating >= 4 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} /> + +
+ ) : <> +); diff --git a/ui/v2.5/src/components/Shared/index.ts b/ui/v2.5/src/components/Shared/index.ts index e299a1eb06e..6395cf0aea8 100644 --- a/ui/v2.5/src/components/Shared/index.ts +++ b/ui/v2.5/src/components/Shared/index.ts @@ -23,3 +23,4 @@ export { default as SuccessIcon } from "./SuccessIcon"; export { default as ErrorMessage } from "./ErrorMessage"; export { default as TruncatedText } from "./TruncatedText"; export { BasicCard } from "./BasicCard"; +export { RatingStars } from "./RatingStars"; diff --git a/ui/v2.5/src/components/Shared/styles.scss b/ui/v2.5/src/components/Shared/styles.scss index e99b87f8212..6dedf416efb 100644 --- a/ui/v2.5/src/components/Shared/styles.scss +++ b/ui/v2.5/src/components/Shared/styles.scss @@ -182,3 +182,16 @@ button.collapse-button.btn-primary:not(:disabled):not(.disabled):active { max-width: 300px; } } + +.RatingStars { + &-unfilled { + path { + fill: white; + } + } + &-filled { + path { + fill: gold; + } + } +} diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx new file mode 100644 index 00000000000..62046fdf29f --- /dev/null +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -0,0 +1,164 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import * as GQL from 'src/core/generated-graphql'; +import { Button } from 'react-bootstrap'; +import cx from 'classnames'; +import Mousetrap from "mousetrap"; +import { debounce } from 'lodash'; + +import { Icon, LoadingIndicator } from 'src/components/Shared'; + +const CLASSNAME = 'Lightbox'; +const CLASSNAME_HEADER = `${CLASSNAME}-header`; +const CLASSNAME_DISPLAY = `${CLASSNAME}-display`; +const CLASSNAME_CAROUSEL = `${CLASSNAME}-carousel`; +const CLASSNAME_INSTANT = `${CLASSNAME_CAROUSEL}-instant`; +const CLASSNAME_IMAGE = `${CLASSNAME_CAROUSEL}-image`; +const CLASSNAME_SELECTED = `${CLASSNAME_IMAGE}-selected`; +const CLASSNAME_PRELOAD = `${CLASSNAME_IMAGE}-preload`; +const CLASSNAME_NAV = `${CLASSNAME}-nav`; +const CLASSNAME_NAVIMAGE = `${CLASSNAME}-nav-image`; +const CLASSNAME_NAVSELECTED = `${CLASSNAME}-nav-selected`; + +type Image = Pick; +type LightboxHookResult = [ + (index?: number) => void, + React.ReactNode +]; + +export const useLightbox = (images: Image[], showNavigation = true): LightboxHookResult => { + const [isVisible, setVisible] = useState(false); + const [index, setIndex] = useState(0); + const [instantTransition, setInstantTransition] = useState(false); + const [isFullscreen, setFullscreen] = useState(false); + const containerRef = useRef(null); + + const disableInstantTransition = debounce(() => setInstantTransition(false), 400); + const setInstant = useCallback(() => { + setInstantTransition(true); + disableInstantTransition(); + }, [disableInstantTransition]); + + const selectIndex = (e: React.MouseEvent, i: number) => { + setIndex(i); + e.stopPropagation(); + } + + const exitFullscreen = () => document.exitFullscreen().then(() => setFullscreen(false)); + + const close = useCallback(() => { + if (!isFullscreen) { + setVisible(false) + document.body.style.overflow = 'auto'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (Mousetrap as any).unpause(); + } + else + exitFullscreen(); + }, [isFullscreen]); + + const handleClose = (e: React.MouseEvent) => { + if ((e.target as Node).nodeName === 'DIV') + close(); + } + + const handleLeft = useCallback(() => setIndex(i => i === 0 ? images.length - 1 : i - 1), [images]); + const handleRight = useCallback(() => setIndex(i => i === images.length - 1 ? 0 : i + 1), [images]); + + const handleKey = useCallback((e: KeyboardEvent) => { + if (e.repeat && (e.key === "ArrowRight" || e.key === "ArrowLeft")) + setInstant(); + if (e.key === "ArrowLeft") + handleLeft(); + else if (e.key === "ArrowRight") + handleRight(); + else if (e.key === "Escape") + close(); + }, [setInstant, handleLeft, handleRight, close]); + + const show = (showIndex: number = 0) => { + setIndex(showIndex); + setVisible(true); + document.body.style.overflow = 'hidden'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (Mousetrap as any).pause(); + } + + useEffect(() => { + if (isVisible) + document.addEventListener('keydown', handleKey); + return () => document.removeEventListener('keydown', handleKey); + }, [isVisible, handleKey]); + + const toggleFullscreen = useCallback(() => { + if (!isFullscreen) + containerRef.current?.requestFullscreen().then(() => setFullscreen(true)); + else + exitFullscreen(); + }, [isFullscreen]); + + const shouldPreload = (i: number) => { + const distance = Math.abs(index - i); + return (distance <= 2) || (distance >= (images.length - 2)); + } + + const navItems = images.map((image, i) => ( + selectIndex(e, i)} role="presentation" loading="lazy" /> + )); + + const element = isVisible ? ( +
+ { images.length > 0 ? ( + <> +
+ + +
+
+ + +
+ { images.map((image, i) => { + const preload = shouldPreload(i); + return ( +
+ +
+ ); + })} +
+ + +
+ { showNavigation && !isFullscreen && images.length > 1 && ( +
+ { navItems } +
+ )} + + ) : } +
+ ) : <>; + + return [show, element]; +} + +export const useGalleryLightbox = (id: string) => { + const [fetchGallery, { data }] = GQL.useFindGalleryLazyQuery({ variables: { id } }); + const [showGallery, container] = useLightbox(data?.findGallery?.images ?? []) + + const show = () => { + fetchGallery(); + showGallery(); + return false; + } + + return [show, container] as const; +} diff --git a/ui/v2.5/src/hooks/Lightbox/index.ts b/ui/v2.5/src/hooks/Lightbox/index.ts new file mode 100644 index 00000000000..3d1465be3ca --- /dev/null +++ b/ui/v2.5/src/hooks/Lightbox/index.ts @@ -0,0 +1 @@ +export * from './Lightbox'; diff --git a/ui/v2.5/src/hooks/Lightbox/lightbox.scss b/ui/v2.5/src/hooks/Lightbox/lightbox.scss new file mode 100644 index 00000000000..91c4c2ba1c0 --- /dev/null +++ b/ui/v2.5/src/hooks/Lightbox/lightbox.scss @@ -0,0 +1,84 @@ +.Lightbox { + display: flex; + flex-direction: column; + position: fixed; + top: 0; + right: 0; + left: 0; + bottom: 0; + background-color: rgba(20, 20, 20, 0.8); + z-index: 1040; + + &-header { + display: flex; + justify-content: flex-end; + height: 4rem; + flex-shrink: 0; + + button { + width: 1.5rem; + height: 1.5rem; + } + + .fa-icon { + width: 1.5rem; + height: 1.5rem + + path { + fill: white; + } + } + } + + &-display { + display: flex; + position: relative; + height: 100%; + } + + &-carousel { + display: flex; + height: 100%; + position: absolute; + transition: left 400ms; + + &-instant { + transition-duration: 0ms; + } + + &-image { + display: flex; + width: 100vw; + height: 100%; + content-visibility: hidden; + + img { + height: 100%; + margin: auto; + } + + &-preload { + content-visibility: visible; + } + } + } + + &-nav { + height: 10rem; + display: flex; + flex-direction: row; + flex-shrink: 0; + margin: 2rem auto; + padding: 0 10rem; + + &-selected { + box-shadow: 4px 4px 4px black, -4px -4px 4px black, 4px -4px 4px black, -4px 4px 4px black; + } + + &-image { + height: 100%; + margin-right: 1rem; + cursor: pointer; + } + } +} diff --git a/ui/v2.5/src/hooks/index.ts b/ui/v2.5/src/hooks/index.ts index 2afa5efc4d9..9f02b33c534 100644 --- a/ui/v2.5/src/hooks/index.ts +++ b/ui/v2.5/src/hooks/index.ts @@ -8,3 +8,4 @@ export { useStudiosList, usePerformersList, } from "./ListHook"; +export * from './Lightbox'; diff --git a/ui/v2.5/src/index.scss b/ui/v2.5/src/index.scss index 17ee99fde22..5270b858c0e 100755 --- a/ui/v2.5/src/index.scss +++ b/ui/v2.5/src/index.scss @@ -18,6 +18,7 @@ @import "src/components/Wall/styles.scss"; @import "../node_modules/flag-icon-css/css/flag-icon.min.css"; @import "src/components/Tagger/styles.scss"; +@import "src/hooks/Lightbox/lightbox.scss"; /* stylelint-disable */ #root { From 5005d7daa47bf760a9f9034a89b6d407c938353f Mon Sep 17 00:00:00 2001 From: Infinite Date: Fri, 18 Dec 2020 21:42:48 +0000 Subject: [PATCH 04/12] WIP --- ui/v2.5/src/hooks/Lightbox/Lightbox.tsx | 4 ++-- ui/v2.5/src/hooks/Lightbox/lightbox.scss | 16 +++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx index 62046fdf29f..7b9006c01fc 100644 --- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -110,10 +110,10 @@ export const useLightbox = (images: Image[], showNavigation = true): LightboxHoo { images.length > 0 ? ( <>
- -
diff --git a/ui/v2.5/src/hooks/Lightbox/lightbox.scss b/ui/v2.5/src/hooks/Lightbox/lightbox.scss index 91c4c2ba1c0..01d77597058 100644 --- a/ui/v2.5/src/hooks/Lightbox/lightbox.scss +++ b/ui/v2.5/src/hooks/Lightbox/lightbox.scss @@ -9,24 +9,22 @@ background-color: rgba(20, 20, 20, 0.8); z-index: 1040; + .fa-icon { + path { + fill: white; + } + } + &-header { display: flex; justify-content: flex-end; height: 4rem; flex-shrink: 0; - - button { - width: 1.5rem; - height: 1.5rem; - } + margin-right: 1rem; .fa-icon { width: 1.5rem; height: 1.5rem - - path { - fill: white; - } } } From 10d1c5a93f7b02bd7f45b087282545f383d4f4bd Mon Sep 17 00:00:00 2001 From: Infinite Date: Sun, 20 Dec 2020 22:08:22 +0000 Subject: [PATCH 05/12] Finish implementatino --- ui/v2.5/package.json | 3 - ui/v2.5/src/App.tsx | 41 +-- .../Galleries/GalleryDetails/Gallery.tsx | 1 - .../components/Galleries/GalleryViewer.tsx | 58 ++-- .../components/Galleries/GalleryWallCard.tsx | 5 +- ui/v2.5/src/components/Galleries/styles.scss | 29 +- ui/v2.5/src/components/Images/ImageList.tsx | 97 +++--- .../Performers/PerformerDetails/Performer.tsx | 9 +- .../components/Scenes/SceneDetails/Scene.tsx | 2 +- ui/v2.5/src/hooks/Lightbox/Lightbox.tsx | 284 +++++++++++++----- ui/v2.5/src/hooks/Lightbox/context.tsx | 49 +++ ui/v2.5/src/hooks/Lightbox/hooks.ts | 61 ++++ ui/v2.5/src/hooks/Lightbox/index.ts | 3 +- ui/v2.5/src/hooks/Lightbox/lightbox.scss | 48 ++- ui/v2.5/src/hooks/ListHook.tsx | 10 +- ui/v2.5/src/hooks/index.ts | 5 +- ui/v2.5/yarn.lock | 93 +----- 17 files changed, 500 insertions(+), 298 deletions(-) create mode 100644 ui/v2.5/src/hooks/Lightbox/context.tsx create mode 100644 ui/v2.5/src/hooks/Lightbox/hooks.ts diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json index 716609f0fdf..e4b0a13f6b4 100644 --- a/ui/v2.5/package.json +++ b/ui/v2.5/package.json @@ -39,7 +39,6 @@ "flag-icon-css": "^3.5.0", "flexbin": "^0.2.0", "formik": "^2.2.1", - "fslightbox-react": "^1.5.0", "graphql": "^15.4.0", "graphql-tag": "^2.11.0", "i18n-iso-countries": "^6.0.0", @@ -52,11 +51,9 @@ "react": "17.0.1", "react-bootstrap": "1.4.0", "react-dom": "17.0.1", - "react-images": "0.5.19", "react-intl": "^5.8.8", "react-jw-player": "1.19.1", "react-markdown": "^5.0.2", - "react-photo-gallery": "^8.0.0", "react-router-bootstrap": "^0.25.0", "react-router-dom": "^5.2.0", "react-router-hash-link": "^2.2.2", diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index 29f2dda7748..9fcb99f6672 100755 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; import { IntlProvider } from "react-intl"; import { ToastProvider } from "src/hooks/Toast"; +import LightboxProvider from "src/hooks/Lightbox/context"; import { library } from "@fortawesome/fontawesome-svg-core"; import { fas } from "@fortawesome/free-solid-svg-icons"; import "@formatjs/intl-numberformat/polyfill"; @@ -53,25 +54,27 @@ export const App: React.FC = () => { - -
- - - - - - - - - - - - - -
+ + +
+ + + + + + + + + + + + + +
+
diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx index 9bd621dc944..e41aa0fd53d 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx @@ -186,7 +186,6 @@ export const Gallery: React.FC = () => { - {/* */} diff --git a/ui/v2.5/src/components/Galleries/GalleryViewer.tsx b/ui/v2.5/src/components/Galleries/GalleryViewer.tsx index 0865f873574..6bb753ef3d2 100644 --- a/ui/v2.5/src/components/Galleries/GalleryViewer.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryViewer.tsx @@ -1,52 +1,36 @@ -import React, { useState } from "react"; +import React from "react"; import * as GQL from "src/core/generated-graphql"; -import FsLightbox from "fslightbox-react"; +import { useLightbox } from "src/hooks"; import "flexbin/flexbin.css"; interface IProps { - gallery: Partial; + gallery: GQL.GalleryDataFragment; } export const GalleryViewer: React.FC = ({ gallery }) => { - const [lightboxToggle, setLightboxToggle] = useState(false); - const [currentIndex, setCurrentIndex] = useState(0); + const images = (gallery?.images ?? []); + const showLightbox = useLightbox({ images, showNavigation: false }); - const openImage = (index: number) => { - setCurrentIndex(index); - setLightboxToggle(!lightboxToggle); - }; - - const photos = !gallery.images - ? [] - : gallery.images.map((file) => file.paths.image ?? ""); - const thumbs = !gallery.images - ? [] - : gallery.images.map((file, index) => ( -
openImage(index)} - onKeyPress={() => openImage(index)} - > - {file.title -
- )); + const thumbs = images.map((file, index) => ( +
showLightbox(index)} + onKeyPress={() => showLightbox(index)} + > + {file.title +
+ )); return (
{thumbs}
-
); }; diff --git a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx index 87d5c1ace4e..cd28f993890 100644 --- a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx @@ -17,7 +17,7 @@ interface IProps { const GalleryWallCard: React.FC = ({ gallery }) => { const intl = useIntl(); - const [showLightbox, lightbox] = useGalleryLightbox(gallery.id); + const showLightbox = useGalleryLightbox(gallery.id); const orientation = (gallery?.cover?.file.width ?? 0) > (gallery.cover?.file.height ?? 0) ? 'landscape' : 'portrait'; const cover = gallery?.cover?.paths.thumbnail ?? ''; @@ -37,7 +37,7 @@ const GalleryWallCard: React.FC = ({ gallery }) => {
- + e.stopPropagation()}> { title && ( )} @@ -46,7 +46,6 @@ const GalleryWallCard: React.FC = ({ gallery }) => {
- { lightbox } ); }; diff --git a/ui/v2.5/src/components/Galleries/styles.scss b/ui/v2.5/src/components/Galleries/styles.scss index d89a8a47240..c8140ab09f6 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -114,17 +114,35 @@ $galleryTabWidth: 450px; .GalleryWallCard { position: relative; - height: 21.333333vw; padding: 2px; + $width: 96vw; + height: auto; + &-landscape { flex-grow: 2; - width: 32vw; + width: 96vw } &-portrait { flex-grow: 1; - width: 16vw; + width: 96vw + } + + @mixin galleryWidth($width) { + height: ($width / 3) * 2; + &-landscape { width: $width; } + &-portrait { width: $width / 2; } + } + + @media (min-width: 576px) { + @include galleryWidth(96vw); + } + @media (min-width: 768px) { + @include galleryWidth(48vw); + } + @media (min-width: 1200px) { + @include galleryWidth(32vw); } &-img { @@ -141,13 +159,16 @@ $galleryTabWidth: 450px; &-footer { background-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0, 0.3)); bottom: 0; - opacity: 0; padding: 1rem; position: absolute; width: 100%; text-shadow: 1px 1px 3px black; transition: 0s opacity; + @media (min-width: 768px) { + opacity: 0; + } + &:hover { .GalleryWallCard-title { text-decoration: underline; diff --git a/ui/v2.5/src/components/Images/ImageList.tsx b/ui/v2.5/src/components/Images/ImageList.tsx index 394b8c03ee3..dc8f756f4e3 100644 --- a/ui/v2.5/src/components/Images/ImageList.tsx +++ b/ui/v2.5/src/components/Images/ImageList.tsx @@ -1,7 +1,6 @@ -import React, { useState } from "react"; +import React, { useCallback, useState } from "react"; import _ from "lodash"; import { useHistory } from "react-router-dom"; -import FsLightbox from "fslightbox-react"; import Mousetrap from "mousetrap"; import { FindImagesQueryResult, @@ -9,7 +8,7 @@ import { } from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql"; import { queryFindImages } from "src/core/StashService"; -import { useImagesList } from "src/hooks"; +import { useImagesList, useLightbox } from "src/hooks"; import { TextUtils } from "src/utils"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; @@ -22,25 +21,40 @@ import { ExportDialog } from "../Shared/ExportDialog"; interface IImageWallProps { images: GQL.SlimImageDataFragment[]; + onChangePage: (page: number) => void; + currentPage: number; + pageCount: number; } -const ImageWall: React.FC = ({ images }) => { - const [lightboxToggle, setLightboxToggle] = useState(false); - const [currentIndex, setCurrentIndex] = useState(0); +const ImageWall: React.FC = ({ images, onChangePage, currentPage, pageCount }) => { + const handleLightBoxPage = useCallback((direction: number) => { + if (direction === -1) { + if (currentPage === 1) + return false; + onChangePage(currentPage - 1); + } + else { + if (currentPage === pageCount) + return false; + onChangePage(currentPage + 1); + } + return direction === -1 || direction === 1; + }, [onChangePage, currentPage, pageCount]); - const openImage = (index: number) => { - setCurrentIndex(index); - setLightboxToggle(!lightboxToggle); - }; + const showLightbox = useLightbox({ + images, + showNavigation: false, + pageCallback: handleLightBoxPage, + pageHeader: `Page ${currentPage} / ${pageCount}`, + }); - const photos = images.map((image) => image.paths.image ?? ""); const thumbs = images.map((image, index) => (
openImage(index)} - onKeyPress={() => openImage(index)} + onClick={() => showLightbox(index)} + onKeyPress={() => showLightbox(index)} > = ({ images }) => {
)); - // FsLightbox doesn't update unless the key updates - const key = images.map((i) => i.id).join(","); - - function onLightboxOpen() { - // disable mousetrap - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (Mousetrap as any).pause(); - } - - function onLightboxClose() { - // re-enable mousetrap - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (Mousetrap as any).unpause(); - } - return (
{thumbs}
-
); }; @@ -125,7 +116,7 @@ export const ImageList: React.FC = ({ }; }; - const listData = useImagesList({ + const { template, onSelectChange } = useImagesList({ zoomable: true, selectable: true, otherOperations, @@ -150,12 +141,7 @@ export const ImageList: React.FC = ({ filterCopy.itemsPerPage = 1; filterCopy.currentPage = index + 1; const singleResult = await queryFindImages(filterCopy); - if ( - singleResult && - singleResult.data && - singleResult.data.findImages && - singleResult.data.findImages.images.length === 1 - ) { + if (singleResult.data.findImages.images.length === 1) { const { id } = singleResult!.data!.findImages!.images[0]; // navigate to the image player page history.push(`/images/${id}`); @@ -228,7 +214,7 @@ export const ImageList: React.FC = ({ selecting={selectedIds.size > 0} selected={selectedIds.has(image.id)} onSelectedChanged={(selected: boolean, shiftKey: boolean) => - listData.onSelectChange(image.id, selected, shiftKey) + onSelectChange(image.id, selected, shiftKey) } /> ); @@ -238,7 +224,9 @@ export const ImageList: React.FC = ({ result: FindImagesQueryResult, filter: ListFilterModel, selectedIds: Set, - zoomIndex: number + zoomIndex: number, + onChangePage: (page: number) => void, + pageCount: number, ) { if (!result.data || !result.data.findImages) { return; @@ -252,11 +240,14 @@ export const ImageList: React.FC = ({
); } - // if (filter.displayMode === DisplayMode.List) { - // return ; - // } if (filter.displayMode === DisplayMode.Wall) { - return ; + return ( + ); } } @@ -264,15 +255,17 @@ export const ImageList: React.FC = ({ result: FindImagesQueryResult, filter: ListFilterModel, selectedIds: Set, - zoomIndex: number + zoomIndex: number, + onChangePage: (page: number) => void, + pageCount: number, ) { return ( <> {maybeRenderImageExportDialog(selectedIds)} - {renderImages(result, filter, selectedIds, zoomIndex)} + {renderImages(result, filter, selectedIds, zoomIndex, onChangePage, pageCount)} ); } - return listData.template; + return template; }; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 5fe01d9efc3..8bd7b388702 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -16,9 +16,8 @@ import { Icon, LoadingIndicator, } from "src/components/Shared"; -import { useToast } from "src/hooks"; +import { useLightbox, useToast } from "src/hooks"; import { TextUtils } from "src/utils"; -import FsLightbox from "fslightbox-react"; import { PerformerDetailsPanel } from "./PerformerDetailsPanel"; import { PerformerOperationsPanel } from "./PerformerOperationsPanel"; import { PerformerScenesPanel } from "./PerformerScenesPanel"; @@ -39,7 +38,6 @@ export const Performer: React.FC = () => { // Performer state const [imagePreview, setImagePreview] = useState(); const [imageEncoding, setImageEncoding] = useState(false); - const [lightboxToggle, setLightboxToggle] = useState(false); const { data, loading: performerLoading, error } = useFindPerformer(id); const performer = data?.findPerformer || ({} as Partial); @@ -51,6 +49,8 @@ export const Performer: React.FC = () => { ? performer.image_path ?? "" : imagePreview ?? `${performer.image_path}?default=true`; + const showLightbox = useLightbox({ images: [{ paths: { thumbnail: activeImage, image: activeImage } }] }); + // Network state const [loading, setIsLoading] = useState(false); const isLoading = performerLoading || loading; @@ -320,7 +320,7 @@ export const Performer: React.FC = () => { ) : ( @@ -342,7 +342,6 @@ export const Performer: React.FC = () => {
{renderTabs()}
- ); }; diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index 4f1ca2d66d3..149fa6fd35b 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -17,7 +17,7 @@ import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; import { useToast } from "src/hooks"; import { ScenePlayer } from "src/components/ScenePlayer"; import { TextUtils, JWUtils } from "src/utils"; -import * as Mousetrap from "mousetrap"; +import Mousetrap from "mousetrap"; import { SceneMarkersPanel } from "./SceneMarkersPanel"; import { SceneFileInfoPanel } from "./SceneFileInfoPanel"; import { SceneEditPanel } from "./SceneEditPanel"; diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx index 7b9006c01fc..62b340677a9 100644 --- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -9,28 +9,52 @@ import { Icon, LoadingIndicator } from 'src/components/Shared'; const CLASSNAME = 'Lightbox'; const CLASSNAME_HEADER = `${CLASSNAME}-header`; +const CLASSNAME_INDICATOR = `${CLASSNAME_HEADER}-indicator`; const CLASSNAME_DISPLAY = `${CLASSNAME}-display`; const CLASSNAME_CAROUSEL = `${CLASSNAME}-carousel`; const CLASSNAME_INSTANT = `${CLASSNAME_CAROUSEL}-instant`; const CLASSNAME_IMAGE = `${CLASSNAME_CAROUSEL}-image`; -const CLASSNAME_SELECTED = `${CLASSNAME_IMAGE}-selected`; -const CLASSNAME_PRELOAD = `${CLASSNAME_IMAGE}-preload`; +const CLASSNAME_NAVBUTTON = `${CLASSNAME}-navbutton`; const CLASSNAME_NAV = `${CLASSNAME}-nav`; -const CLASSNAME_NAVIMAGE = `${CLASSNAME}-nav-image`; -const CLASSNAME_NAVSELECTED = `${CLASSNAME}-nav-selected`; +const CLASSNAME_NAVIMAGE = `${CLASSNAME_NAV}-image`; +const CLASSNAME_NAVSELECTED = `${CLASSNAME_NAV}-selected`; type Image = Pick; -type LightboxHookResult = [ - (index?: number) => void, - React.ReactNode -]; - -export const useLightbox = (images: Image[], showNavigation = true): LightboxHookResult => { - const [isVisible, setVisible] = useState(false); - const [index, setIndex] = useState(0); +interface IProps { + images: Image[]; + isVisible: boolean; + isLoading: boolean; + initialIndex?: number; + showNavigation: boolean; + pageHeader?: string; + pageCallback?: (direction: number) => boolean; + hide: () => void; +} + +export const LightboxComponent: React.FC = ({ + images, + isVisible, + isLoading, + initialIndex = 0, + showNavigation, + pageHeader, + pageCallback, + hide, +}) => { + const index = useRef(null); const [instantTransition, setInstantTransition] = useState(false); + const [isSwitchingPage, setIsSwitchingPage] = useState(false); const [isFullscreen, setFullscreen] = useState(false); const containerRef = useRef(null); + const carouselRef = useRef(null); + const indicatorRef = useRef(null); + const navRef = useRef(null); + + useEffect(() => { + setIsSwitchingPage(false); + if (index.current === -1) + index.current = images.length - 1; + }, [images]); const disableInstantTransition = debounce(() => setInstantTransition(false), 400); const setInstant = useCallback(() => { @@ -38,31 +62,102 @@ export const useLightbox = (images: Image[], showNavigation = true): LightboxHoo disableInstantTransition(); }, [disableInstantTransition]); + const setIndex = useCallback((i: number) => { + if (images.length < 2) + return; + + index.current = i; + if (carouselRef.current) + carouselRef.current.style.left = `${(i * -100)}vw`; + if (indicatorRef.current) + indicatorRef.current.innerHTML = `${i + 1} / ${images.length}`; + if (navRef.current) { + const currentThumb = navRef.current.children[i + 1]; + if (currentThumb instanceof HTMLImageElement) { + const offset = -1 * (currentThumb.offsetLeft - (document.documentElement.clientWidth / 2)); + navRef.current.style.left = `${offset}px`; + + const previouslySelected = navRef.current.getElementsByClassName(CLASSNAME_NAVSELECTED)?.[0] + if (previouslySelected) + previouslySelected.className = CLASSNAME_NAVIMAGE; + + currentThumb.className = `${CLASSNAME_NAVIMAGE} ${CLASSNAME_NAVSELECTED}`; + } + } + }, [images]); + const selectIndex = (e: React.MouseEvent, i: number) => { - setIndex(i); + setIndex(i) e.stopPropagation(); } - const exitFullscreen = () => document.exitFullscreen().then(() => setFullscreen(false)); + useEffect(() => { + if (isVisible) { + if (index.current === null) + setIndex(initialIndex); + document.body.style.overflow = 'visible'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (Mousetrap as any).pause(); + } + }, [initialIndex, isVisible, setIndex]); const close = useCallback(() => { if (!isFullscreen) { - setVisible(false) + hide(); document.body.style.overflow = 'auto'; // eslint-disable-next-line @typescript-eslint/no-explicit-any (Mousetrap as any).unpause(); } else - exitFullscreen(); - }, [isFullscreen]); + document.exitFullscreen(); + }, [isFullscreen, hide]); const handleClose = (e: React.MouseEvent) => { if ((e.target as Node).nodeName === 'DIV') close(); } - const handleLeft = useCallback(() => setIndex(i => i === 0 ? images.length - 1 : i - 1), [images]); - const handleRight = useCallback(() => setIndex(i => i === images.length - 1 ? 0 : i + 1), [images]); + const handleLeft = useCallback(() => { + if (isSwitchingPage || index.current === -1) + return; + + if (index.current === 0) { + if (pageCallback) { + setIsSwitchingPage(true); + setIndex(-1); + // Check if calling page wants to swap page + const repage = pageCallback(-1); + if (!repage) { + setIsSwitchingPage(false); + setIndex(0); + } + } + else + setIndex(images.length - 1); + } + else + setIndex((index.current ?? 0) - 1); + }, [images, setIndex, pageCallback, isSwitchingPage]); + const handleRight = useCallback(() => { + if (isSwitchingPage) + return; + + if (index.current === images.length - 1) { + if (pageCallback) { + setIsSwitchingPage(true); + setIndex(0); + const repage = pageCallback?.(1); + if (!repage) { + setIsSwitchingPage(false); + setIndex(images.length - 1); + } + } + else + setIndex(0); + } + else + setIndex((index.current ?? 0) + 1); + }, [images, setIndex, pageCallback, isSwitchingPage]); const handleKey = useCallback((e: KeyboardEvent) => { if (e.repeat && (e.key === "ArrowRight" || e.key === "ArrowLeft")) @@ -74,72 +169,136 @@ export const useLightbox = (images: Image[], showNavigation = true): LightboxHoo else if (e.key === "Escape") close(); }, [setInstant, handleLeft, handleRight, close]); + const handleFullScreenChange = () => setFullscreen(document.fullscreenElement !== null); + + const handleTouchStart = (ev: React.TouchEvent) => { + setInstantTransition(true); + + const el = ev.currentTarget; + if (ev.touches.length !== 1) + return; - const show = (showIndex: number = 0) => { - setIndex(showIndex); - setVisible(true); - document.body.style.overflow = 'hidden'; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (Mousetrap as any).pause(); + const startX = ev.touches[0].clientX; + let position = 0; + + const resetPosition = () => { + if (carouselRef.current) + carouselRef.current.style.left = `${((index.current ?? 0) * -100)}vw`; + } + const handleMove = (e: TouchEvent) => { + position = e.touches[0].clientX; + if (carouselRef.current) + carouselRef.current.style.left = `calc(${((index.current ?? 0) * -100)}vw + ${e.touches[0].clientX - startX}px)`; + } + const handleEnd = () => { + const diff = position - startX; + if (diff <= -50) + handleRight(); + else if (diff >= 50) + handleLeft(); + else + resetPosition(); + // eslint-disable-next-line @typescript-eslint/no-use-before-define + cleanup(); + } + const handleCancel = () => { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + cleanup(); + resetPosition(); + } + const cleanup = () => { + el.removeEventListener('touchmove', handleMove); + el.removeEventListener('touchend', handleEnd); + el.removeEventListener('touchcancel', handleCancel); + setInstantTransition(false); + } + + el.addEventListener('touchmove', handleMove); + el.addEventListener('touchend', handleEnd); + el.addEventListener('touchcancel', handleCancel); } useEffect(() => { - if (isVisible) + if (isVisible) { document.addEventListener('keydown', handleKey); - return () => document.removeEventListener('keydown', handleKey); + document.addEventListener('fullscreenchange', handleFullScreenChange); + } + return () => { + document.removeEventListener('keydown', handleKey); + document.removeEventListener('fullscreenchange', handleFullScreenChange); + } }, [isVisible, handleKey]); const toggleFullscreen = useCallback(() => { if (!isFullscreen) - containerRef.current?.requestFullscreen().then(() => setFullscreen(true)); + containerRef.current?.requestFullscreen(); else - exitFullscreen(); + document.exitFullscreen(); }, [isFullscreen]); - const shouldPreload = (i: number) => { - const distance = Math.abs(index - i); - return (distance <= 2) || (distance >= (images.length - 2)); - } - const navItems = images.map((image, i) => ( - selectIndex(e, i)} role="presentation" loading="lazy" /> + selectIndex(e, i)} role="presentation" loading="lazy" /> )); + const currentIndex = index.current === null ? initialIndex : index.current; + const element = isVisible ? (
- { images.length > 0 ? ( + { images.length > 0 && !isLoading && !isSwitchingPage ? ( <>
- +
+ { pageHeader } + + { `${currentIndex + 1} / ${images.length}` } + +
+ { document.fullscreenEnabled && ( + + )}
-
- +
+ { images.length > 1 && ( + + )} -
- { images.map((image, i) => { - const preload = shouldPreload(i); - return ( -
- +
+ { images.map(image => ( +
+ + + +
- ); - })} + ))}
- + { images.length > 1 && ( + + )}
{ showNavigation && !isFullscreen && images.length > 1 && ( -
+
+ { navItems } +
)} @@ -147,18 +306,5 @@ export const useLightbox = (images: Image[], showNavigation = true): LightboxHoo
) : <>; - return [show, element]; -} - -export const useGalleryLightbox = (id: string) => { - const [fetchGallery, { data }] = GQL.useFindGalleryLazyQuery({ variables: { id } }); - const [showGallery, container] = useLightbox(data?.findGallery?.images ?? []) - - const show = () => { - fetchGallery(); - showGallery(); - return false; - } - - return [show, container] as const; + return element; } diff --git a/ui/v2.5/src/hooks/Lightbox/context.tsx b/ui/v2.5/src/hooks/Lightbox/context.tsx new file mode 100644 index 00000000000..b4d50358b07 --- /dev/null +++ b/ui/v2.5/src/hooks/Lightbox/context.tsx @@ -0,0 +1,49 @@ +import React, { useCallback, useState } from 'react'; +import * as GQL from 'src/core/generated-graphql'; +import { LightboxComponent } from './Lightbox'; + +type Image = Pick; + +export interface IState { + images: Image[]; + isVisible: boolean; + isLoading: boolean; + showNavigation: boolean; + initialIndex?: number; + pageCallback?: (direction: number) => boolean; + pageHeader?: string; +} +interface IContext { + setLightboxState: (state: Partial) => void; +}; + +export const LightboxContext = React.createContext({ setLightboxState: () => {} }); +const Lightbox: React.FC = ({ children }) => { + const [lightboxState, setLightboxState] = useState({ + images: [], + isVisible: false, + isLoading: false, + showNavigation: true, + }); + + const setPartialState = useCallback((state: Partial) => { + setLightboxState((currentState: IState) => ({ + ...currentState, + ...state, + })); + }, [setLightboxState]); + + return ( + + { children } + { lightboxState.isVisible && ( + setLightboxState({ ...lightboxState, isVisible: false }) } + /> + )} + + ); +} + +export default Lightbox; diff --git a/ui/v2.5/src/hooks/Lightbox/hooks.ts b/ui/v2.5/src/hooks/Lightbox/hooks.ts new file mode 100644 index 00000000000..a895b818ef7 --- /dev/null +++ b/ui/v2.5/src/hooks/Lightbox/hooks.ts @@ -0,0 +1,61 @@ +import { useCallback, useContext, useEffect } from 'react'; +import * as GQL from 'src/core/generated-graphql'; +import { LightboxContext, IState } from './context'; + +export const useLightbox = (state: Partial>) => { + const { setLightboxState } = useContext(LightboxContext); + + useEffect(() => { + setLightboxState({ + images: state.images, + showNavigation: state.showNavigation, + pageCallback: state.pageCallback, + initialIndex: state.initialIndex, + pageHeader: state.pageHeader, + }); + }, [setLightboxState, state.images, state.showNavigation, state.pageCallback, state.initialIndex, state.pageHeader]); + + const show = useCallback((index?: number) => { + setLightboxState({ + initialIndex: index, + isVisible: true, + }); + }, [setLightboxState]); + return show; +} + +export const useGalleryLightbox = (id: string) => { + const { setLightboxState } = useContext(LightboxContext); + const [fetchGallery, { data }] = GQL.useFindGalleryLazyQuery({ variables: { id } }); + + useEffect(() => { + if (data) + setLightboxState({ + images: data.findGallery?.images ?? [], + isLoading: false, + isVisible: true, + }); + }, [setLightboxState, data]); + + const show = () => { + if (data) + setLightboxState({ + isLoading: false, + isVisible: true, + images: data.findGallery?.images ?? [], + pageCallback: undefined, + pageHeader: undefined, + }); + else { + setLightboxState({ + isLoading: true, + isVisible: true, + pageCallback: undefined, + pageHeader: undefined, + }); + fetchGallery(); + } + } + + return show; +} diff --git a/ui/v2.5/src/hooks/Lightbox/index.ts b/ui/v2.5/src/hooks/Lightbox/index.ts index 3d1465be3ca..472021f7e91 100644 --- a/ui/v2.5/src/hooks/Lightbox/index.ts +++ b/ui/v2.5/src/hooks/Lightbox/index.ts @@ -1 +1,2 @@ -export * from './Lightbox'; +export * from './context'; +export * from './hooks'; diff --git a/ui/v2.5/src/hooks/Lightbox/lightbox.scss b/ui/v2.5/src/hooks/Lightbox/lightbox.scss index 01d77597058..35709b863a2 100644 --- a/ui/v2.5/src/hooks/Lightbox/lightbox.scss +++ b/ui/v2.5/src/hooks/Lightbox/lightbox.scss @@ -17,10 +17,17 @@ &-header { display: flex; - justify-content: flex-end; + align-items: center; height: 4rem; flex-shrink: 0; - margin-right: 1rem; + + &-indicator { + display: flex; + flex-direction: column; + margin-left: 49%; + margin-right: auto; + text-align: center; + } .fa-icon { width: 1.5rem; @@ -32,6 +39,8 @@ display: flex; position: relative; height: 100%; + margin-bottom: 2rem; + justify-content: space-between; } &-carousel { @@ -48,16 +57,31 @@ display: flex; width: 100vw; height: 100%; - content-visibility: hidden; + content-visibility: auto; - img { + picture, img { height: 100%; + max-width: 100%; + object-fit: contain; margin: auto; } + } + } - &-preload { - content-visibility: visible; - } + &-navbutton { + z-index: 1045; + + .fa-icon { + width: 4rem; + height: 4rem; + } + + &:focus { + box-shadow: none; + } + + &:hover { + filter: drop-shadow(2px 2px 2px black); } } @@ -66,11 +90,17 @@ display: flex; flex-direction: row; flex-shrink: 0; - margin: 2rem auto; + margin: 0 auto 2rem 0; padding: 0 10rem; + position: relative; + transition: left 400ms; + + @media (max-height: 800px) { + display: none; + } &-selected { - box-shadow: 4px 4px 4px black, -4px -4px 4px black, 4px -4px 4px black, -4px 4px 4px black; + box-shadow: 0 0 0 6px white; } &-image { diff --git a/ui/v2.5/src/hooks/ListHook.tsx b/ui/v2.5/src/hooks/ListHook.tsx index 2f83b8966e3..47be05612ab 100644 --- a/ui/v2.5/src/hooks/ListHook.tsx +++ b/ui/v2.5/src/hooks/ListHook.tsx @@ -64,6 +64,7 @@ interface IListHookData { filter: ListFilterModel; template: React.ReactElement; onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void; + onChangePage: (page: number) => void; } export interface IListHookOperation { @@ -92,7 +93,9 @@ interface IListHookOptions { result: T, filter: ListFilterModel, selectedIds: Set, - zoomIndex: number + zoomIndex: number, + onChangePage: (page: number) => void, + pageCount: number, ) => React.ReactNode; renderEditDialog?: ( selected: E[], @@ -350,10 +353,12 @@ const RenderList = < return; } + const pages = Math.ceil(totalCount / filter.itemsPerPage); + return ( <> {renderPagination()} - {renderContent(result, filter, selectedIds, zoomIndex)} + {renderContent(result, filter, selectedIds, zoomIndex, onChangePage, pages)} ( filter, template, onSelectChange, + onChangePage, }; }; diff --git a/ui/v2.5/src/hooks/index.ts b/ui/v2.5/src/hooks/index.ts index 9f02b33c534..19221f26805 100644 --- a/ui/v2.5/src/hooks/index.ts +++ b/ui/v2.5/src/hooks/index.ts @@ -8,4 +8,7 @@ export { useStudiosList, usePerformersList, } from "./ListHook"; -export * from './Lightbox'; +export { + useLightbox, + useGalleryLightbox, +} from './Lightbox'; diff --git a/ui/v2.5/yarn.lock b/ui/v2.5/yarn.lock index bd9b92ccf02..04153c904e5 100644 --- a/ui/v2.5/yarn.lock +++ b/ui/v2.5/yarn.lock @@ -3281,14 +3281,6 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -aphrodite@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/aphrodite/-/aphrodite-0.5.0.tgz#a4b9a8902662395d2702e70ac7a2b4ca66f25703" - integrity sha1-pLmokCZiOV0nAucKx6K0ymbyVwM= - dependencies: - asap "^2.0.3" - inline-style-prefixer "^2.0.0" - apollo-upload-client@^14.1.2: version "14.1.2" resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-14.1.2.tgz#7a72b000f1cd67eaf8f12b4bda2796d0898c0dae" @@ -3419,7 +3411,7 @@ arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@^2.0.3, asap@~2.0.3, asap@~2.0.6: +asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -3948,11 +3940,6 @@ bootstrap@^4.5.3: resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6" integrity sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ== -bowser@^1.0.0: - version "1.9.4" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" - integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -5544,13 +5531,6 @@ dom-converter@^0.2: dependencies: utila "~0.4" -dom-helpers@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" - integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== - dependencies: - "@babel/runtime" "^7.1.2" - dom-helpers@^5.0.1, dom-helpers@^5.1.2, dom-helpers@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" @@ -6285,11 +6265,6 @@ execall@^2.0.0: dependencies: clone-regexp "^2.1.0" -exenv@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" - integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= - exif-parser@^0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922" @@ -6873,11 +6848,6 @@ fsevents@^2.1.2, fsevents@^2.1.3, fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== -fslightbox-react@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/fslightbox-react/-/fslightbox-react-1.5.0.tgz#07cf41d7ff8b02a79a0886d13519550b79dc50e5" - integrity sha512-xBe1K06pa3opWar/xBtArsHMnxMJWsmg5EmNdDtheDL9nMCqk2AXYlNnstfYVqtJJjqNReqeL21wc52Yy4rwWg== - fstream@^1.0.0, fstream@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" @@ -7541,11 +7511,6 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== -hyphenate-style-name@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" - integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== - i18n-iso-countries@^6.0.0: version "6.2.2" resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-6.2.2.tgz#6b63d00e90ee4022e8c159a9e688d2a8156b0e0b" @@ -7744,14 +7709,6 @@ ini@^1.3.5, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inline-style-prefixer@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-2.0.5.tgz#c153c7e88fd84fef5c602e95a8168b2770671fe7" - integrity sha1-wVPH6I/YT+9cYC6VqBaLJ3BnH+c= - dependencies: - bowser "^1.0.0" - hyphenate-style-name "^1.0.1" - inquirer@7.3.3, inquirer@^7.3.3: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" @@ -11903,7 +11860,7 @@ prop-types-extra@^1.1.0: react-is "^16.3.2" warning "^4.0.0" -prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@~15.7.2: +prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -12176,16 +12133,6 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react-images@0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/react-images/-/react-images-0.5.19.tgz#9339570029e065f9f28a19f03fdb5d9d5aa109d3" - integrity sha512-B3d4W1uFJj+m17K8S65iAyEJShKGBjPk7n7N1YsPiAydEm8mIq9a6CoeQFMY1d7N2QMs6FBCjT9vELyc5jP5JA== - dependencies: - aphrodite "^0.5.0" - prop-types "^15.6.0" - react-scrolllock "^2.0.1" - react-transition-group "2" - react-input-autosize@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2" @@ -12263,19 +12210,6 @@ react-overlays@^4.1.0: uncontrollable "^7.0.0" warning "^4.0.3" -react-photo-gallery@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/react-photo-gallery/-/react-photo-gallery-8.0.0.tgz#04ff9f902a2342660e63e6817b4f010488db02b8" - integrity sha512-Y9458yygEB9cIZAWlBWuenlR+ghin1RopmmU3Vice8BeJl0Se7hzfxGDq8W1armB/ic/kphGg+G1jq5fOEd0sw== - dependencies: - prop-types "~15.7.2" - resize-observer-polyfill "^1.5.0" - -react-prop-toggle@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/react-prop-toggle/-/react-prop-toggle-1.0.2.tgz#8b0b7e74653606b1427cfcf6c4eaa9198330568e" - integrity sha512-JmerjAXs7qJ959+d0Ygt7Cb2+4fG+n3I2VXO6JO0AcAY1vkRN/JpZKAN67CMXY889xEJcfylmMPhzvf6nWO68Q== - react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -12389,14 +12323,6 @@ react-scripts@^4.0.0: optionalDependencies: fsevents "^2.1.3" -react-scrolllock@^2.0.1: - version "2.0.7" - resolved "https://registry.yarnpkg.com/react-scrolllock/-/react-scrolllock-2.0.7.tgz#3b879e1fe308fc900ab76e226e9be594c41226fd" - integrity sha512-Gzpu8+ulxdYcybAgJOFTXc70xs7SBZDQbZNpKzchZUgLCJKjz6lrgESx6LHHZgfELx1xYL4yHu3kYQGQPFas/g== - dependencies: - exenv "^1.2.2" - react-prop-toggle "^1.0.2" - react-select@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27" @@ -12411,16 +12337,6 @@ react-select@^3.1.0: react-input-autosize "^2.2.2" react-transition-group "^4.3.0" -react-transition-group@2: - version "2.9.0" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" - integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== - dependencies: - dom-helpers "^3.4.0" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react-lifecycles-compat "^3.0.4" - react-transition-group@^4.3.0, react-transition-group@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" @@ -12865,11 +12781,6 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -resize-observer-polyfill@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" From f45127a9aaedbe490ab3d632d432481c97b553fd Mon Sep 17 00:00:00 2001 From: Infinite Date: Sun, 20 Dec 2020 22:34:42 +0000 Subject: [PATCH 06/12] Linting and bugfixes --- ui/v2.5/src/components/Galleries/styles.scss | 18 +++++----- ui/v2.5/src/components/Shared/styles.scss | 1 + ui/v2.5/src/hooks/Lightbox/Lightbox.tsx | 5 +-- ui/v2.5/src/hooks/Lightbox/lightbox.scss | 38 +++++++++++--------- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/ui/v2.5/src/components/Galleries/styles.scss b/ui/v2.5/src/components/Galleries/styles.scss index c8140ab09f6..d857096a5c1 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -97,7 +97,6 @@ $galleryTabWidth: 450px; } } - .GalleryWall { display: flex; flex-wrap: wrap; @@ -113,20 +112,21 @@ $galleryTabWidth: 450px; } .GalleryWallCard { - position: relative; + height: auto; padding: 2px; + position: relative; $width: 96vw; - height: auto; &-landscape { flex-grow: 2; - width: 96vw + width: 96vw; } + &-portrait { flex-grow: 1; - width: 96vw + width: 96vw; } @mixin galleryWidth($width) { @@ -146,10 +146,10 @@ $galleryTabWidth: 450px; } &-img { + height: 100%; object-fit: cover; object-position: center 20%; width: 100%; - height: 100%; } &-title { @@ -157,13 +157,13 @@ $galleryTabWidth: 450px; } &-footer { - background-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0, 0.3)); + background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .3)); bottom: 0; padding: 1rem; position: absolute; - width: 100%; text-shadow: 1px 1px 3px black; transition: 0s opacity; + width: 100%; @media (min-width: 768px) { opacity: 0; @@ -192,8 +192,8 @@ $galleryTabWidth: 450px; .RatingStars { position: absolute; - top: 1rem; right: 1rem; + top: 1rem; &-unfilled { display: none; diff --git a/ui/v2.5/src/components/Shared/styles.scss b/ui/v2.5/src/components/Shared/styles.scss index 6dedf416efb..02bacdef5a4 100644 --- a/ui/v2.5/src/components/Shared/styles.scss +++ b/ui/v2.5/src/components/Shared/styles.scss @@ -189,6 +189,7 @@ button.collapse-button.btn-primary:not(:disabled):not(.disabled):active { fill: white; } } + &-filled { path { fill: gold; diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx index 62b340677a9..4384506e616 100644 --- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -95,7 +95,7 @@ export const LightboxComponent: React.FC = ({ if (isVisible) { if (index.current === null) setIndex(initialIndex); - document.body.style.overflow = 'visible'; + document.body.style.overflow = 'hidden'; // eslint-disable-next-line @typescript-eslint/no-explicit-any (Mousetrap as any).pause(); } @@ -113,7 +113,8 @@ export const LightboxComponent: React.FC = ({ }, [isFullscreen, hide]); const handleClose = (e: React.MouseEvent) => { - if ((e.target as Node).nodeName === 'DIV') + const { nodeName } = (e.target as Node); + if (nodeName === "DIV" || nodeName === "PICTURE") close(); } diff --git a/ui/v2.5/src/hooks/Lightbox/lightbox.scss b/ui/v2.5/src/hooks/Lightbox/lightbox.scss index 35709b863a2..f28f3777992 100644 --- a/ui/v2.5/src/hooks/Lightbox/lightbox.scss +++ b/ui/v2.5/src/hooks/Lightbox/lightbox.scss @@ -1,12 +1,12 @@ .Lightbox { + background-color: rgba(20, 20, 20, 0.8); + bottom: 0; display: flex; flex-direction: column; + left: 0; position: fixed; - top: 0; right: 0; - left: 0; - bottom: 0; - background-color: rgba(20, 20, 20, 0.8); + top: 0; z-index: 1040; .fa-icon { @@ -16,10 +16,10 @@ } &-header { - display: flex; align-items: center; - height: 4rem; + display: flex; flex-shrink: 0; + height: 4rem; &-indicator { display: flex; @@ -30,17 +30,17 @@ } .fa-icon { + height: 1.5rem; width: 1.5rem; - height: 1.5rem } } &-display { display: flex; - position: relative; height: 100%; - margin-bottom: 2rem; justify-content: space-between; + margin-bottom: 2rem; + position: relative; } &-carousel { @@ -54,16 +54,22 @@ } &-image { + content-visibility: auto; display: flex; - width: 100vw; height: 100%; - content-visibility: auto; + width: 100vw; - picture, img { + picture { + display: flex; height: 100%; + margin: auto; + } + + img { + max-height: 100%; + margin: auto; max-width: 100%; object-fit: contain; - margin: auto; } } } @@ -72,8 +78,8 @@ z-index: 1045; .fa-icon { - width: 4rem; height: 4rem; + width: 4rem; } &:focus { @@ -86,10 +92,10 @@ } &-nav { - height: 10rem; display: flex; flex-direction: row; flex-shrink: 0; + height: 10rem; margin: 0 auto 2rem 0; padding: 0 10rem; position: relative; @@ -104,9 +110,9 @@ } &-image { + cursor: pointer; height: 100%; margin-right: 1rem; - cursor: pointer; } } } From dec45484784cd6d9e45882915eed22a139e3b834 Mon Sep 17 00:00:00 2001 From: Infinite Date: Sun, 20 Dec 2020 22:35:59 +0000 Subject: [PATCH 07/12] Yarn format --- .../src/components/Galleries/GalleryList.tsx | 5 +- .../components/Galleries/GalleryViewer.tsx | 2 +- .../components/Galleries/GalleryWallCard.tsx | 45 ++- ui/v2.5/src/components/Galleries/styles.scss | 11 +- ui/v2.5/src/components/Images/ImageList.tsx | 49 +-- .../Performers/PerformerDetails/Performer.tsx | 9 +- ui/v2.5/src/components/Shared/RatingStars.tsx | 33 +- ui/v2.5/src/hooks/Lightbox/Lightbox.tsx | 294 ++++++++++-------- ui/v2.5/src/hooks/Lightbox/context.tsx | 39 ++- ui/v2.5/src/hooks/Lightbox/hooks.ts | 42 ++- ui/v2.5/src/hooks/Lightbox/index.ts | 4 +- ui/v2.5/src/hooks/Lightbox/lightbox.scss | 2 +- ui/v2.5/src/hooks/ListHook.tsx | 11 +- ui/v2.5/src/hooks/index.ts | 5 +- 14 files changed, 325 insertions(+), 226 deletions(-) diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index 13d59879473..283d64e1017 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -217,10 +217,7 @@ export const GalleryList: React.FC = ({
{result.data.findGalleries.galleries.map((gallery) => ( - + ))}
diff --git a/ui/v2.5/src/components/Galleries/GalleryViewer.tsx b/ui/v2.5/src/components/Galleries/GalleryViewer.tsx index 6bb753ef3d2..5ed9f42f105 100644 --- a/ui/v2.5/src/components/Galleries/GalleryViewer.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryViewer.tsx @@ -8,7 +8,7 @@ interface IProps { } export const GalleryViewer: React.FC = ({ gallery }) => { - const images = (gallery?.images ?? []); + const images = gallery?.images ?? []; const showLightbox = useLightbox({ images, showNavigation: false }); const thumbs = images.map((file, index) => ( diff --git a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx index cd28f993890..376f1062c5f 100644 --- a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx @@ -1,12 +1,12 @@ import React from "react"; -import { useIntl } from 'react-intl'; -import { Link } from 'react-router-dom'; +import { useIntl } from "react-intl"; +import { Link } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; -import { RatingStars, TruncatedText } from 'src/components/Shared'; -import { TextUtils } from 'src/utils'; -import { useGalleryLightbox } from 'src/hooks'; +import { RatingStars, TruncatedText } from "src/components/Shared"; +import { TextUtils } from "src/utils"; +import { useGalleryLightbox } from "src/hooks"; -const CLASSNAME = 'GalleryWallCard'; +const CLASSNAME = "GalleryWallCard"; const CLASSNAME_FOOTER = `${CLASSNAME}-footer`; const CLASSNAME_IMG = `${CLASSNAME}-img`; const CLASSNAME_TITLE = `${CLASSNAME}-title`; @@ -19,11 +19,17 @@ const GalleryWallCard: React.FC = ({ gallery }) => { const intl = useIntl(); const showLightbox = useGalleryLightbox(gallery.id); - const orientation = (gallery?.cover?.file.width ?? 0) > (gallery.cover?.file.height ?? 0) ? 'landscape' : 'portrait'; - const cover = gallery?.cover?.paths.thumbnail ?? ''; + const orientation = + (gallery?.cover?.file.width ?? 0) > (gallery.cover?.file.height ?? 0) + ? "landscape" + : "portrait"; + const cover = gallery?.cover?.paths.thumbnail ?? ""; const title = gallery.title ?? gallery.path; - const performerNames = gallery.performers.map(p => p.name); - const performers = performerNames.length >= 2 ? [...performerNames.slice(0, -2), performerNames.slice(-2).join(' & ')] : performerNames; + const performerNames = gallery.performers.map((p) => p.name); + const performers = + performerNames.length >= 2 + ? [...performerNames.slice(0, -2), performerNames.slice(-2).join(" & ")] + : performerNames; return ( <> @@ -37,12 +43,21 @@ const GalleryWallCard: React.FC = ({ gallery }) => {
- e.stopPropagation()}> - { title && ( - + e.stopPropagation()} + > + {title && ( + )} - -
{ gallery.date && TextUtils.formatDate(intl, gallery.date) }
+ +
+ {gallery.date && TextUtils.formatDate(intl, gallery.date)} +
diff --git a/ui/v2.5/src/components/Galleries/styles.scss b/ui/v2.5/src/components/Galleries/styles.scss index d857096a5c1..ceae6081461 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -121,7 +121,6 @@ $galleryTabWidth: 450px; &-landscape { flex-grow: 2; width: 96vw; - } &-portrait { @@ -131,8 +130,12 @@ $galleryTabWidth: 450px; @mixin galleryWidth($width) { height: ($width / 3) * 2; - &-landscape { width: $width; } - &-portrait { width: $width / 2; } + &-landscape { + width: $width; + } + &-portrait { + width: $width / 2; + } } @media (min-width: 576px) { @@ -157,7 +160,7 @@ $galleryTabWidth: 450px; } &-footer { - background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .3)); + background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3)); bottom: 0; padding: 1rem; position: absolute; diff --git a/ui/v2.5/src/components/Images/ImageList.tsx b/ui/v2.5/src/components/Images/ImageList.tsx index dc8f756f4e3..48cdb9824fd 100644 --- a/ui/v2.5/src/components/Images/ImageList.tsx +++ b/ui/v2.5/src/components/Images/ImageList.tsx @@ -26,20 +26,25 @@ interface IImageWallProps { pageCount: number; } -const ImageWall: React.FC = ({ images, onChangePage, currentPage, pageCount }) => { - const handleLightBoxPage = useCallback((direction: number) => { - if (direction === -1) { - if (currentPage === 1) - return false; - onChangePage(currentPage - 1); - } - else { - if (currentPage === pageCount) - return false; - onChangePage(currentPage + 1); - } - return direction === -1 || direction === 1; - }, [onChangePage, currentPage, pageCount]); +const ImageWall: React.FC = ({ + images, + onChangePage, + currentPage, + pageCount, +}) => { + const handleLightBoxPage = useCallback( + (direction: number) => { + if (direction === -1) { + if (currentPage === 1) return false; + onChangePage(currentPage - 1); + } else { + if (currentPage === pageCount) return false; + onChangePage(currentPage + 1); + } + return direction === -1 || direction === 1; + }, + [onChangePage, currentPage, pageCount] + ); const showLightbox = useLightbox({ images, @@ -226,7 +231,7 @@ export const ImageList: React.FC = ({ selectedIds: Set, zoomIndex: number, onChangePage: (page: number) => void, - pageCount: number, + pageCount: number ) { if (!result.data || !result.data.findImages) { return; @@ -247,7 +252,8 @@ export const ImageList: React.FC = ({ onChangePage={onChangePage} currentPage={filter.currentPage} pageCount={pageCount} - />); + /> + ); } } @@ -257,12 +263,19 @@ export const ImageList: React.FC = ({ selectedIds: Set, zoomIndex: number, onChangePage: (page: number) => void, - pageCount: number, + pageCount: number ) { return ( <> {maybeRenderImageExportDialog(selectedIds)} - {renderImages(result, filter, selectedIds, zoomIndex, onChangePage, pageCount)} + {renderImages( + result, + filter, + selectedIds, + zoomIndex, + onChangePage, + pageCount + )} ); } diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 8bd7b388702..771f43804bb 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -49,7 +49,9 @@ export const Performer: React.FC = () => { ? performer.image_path ?? "" : imagePreview ?? `${performer.image_path}?default=true`; - const showLightbox = useLightbox({ images: [{ paths: { thumbnail: activeImage, image: activeImage } }] }); + const showLightbox = useLightbox({ + images: [{ paths: { thumbnail: activeImage, image: activeImage } }], + }); // Network state const [loading, setIsLoading] = useState(false); @@ -318,10 +320,7 @@ export const Performer: React.FC = () => { {imageEncoding ? ( ) : ( - )} diff --git a/ui/v2.5/src/components/Shared/RatingStars.tsx b/ui/v2.5/src/components/Shared/RatingStars.tsx index 65b79f48ade..0847dba0bed 100644 --- a/ui/v2.5/src/components/Shared/RatingStars.tsx +++ b/ui/v2.5/src/components/Shared/RatingStars.tsx @@ -1,7 +1,7 @@ import React from "react"; -import Icon from './Icon'; +import Icon from "./Icon"; -const CLASSNAME = 'RatingStars'; +const CLASSNAME = "RatingStars"; const CLASSNAME_FILLED = `${CLASSNAME}-filled`; const CLASSNAME_UNFILLED = `${CLASSNAME}-unfilled`; @@ -9,14 +9,27 @@ interface IProps { rating?: number | null; } -export const RatingStars: React.FC = ({ rating }) => ( +export const RatingStars: React.FC = ({ rating }) => rating ? (
- - = 2 ? 'fas' : 'far', 'star']} className={rating >= 2 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} /> - = 3 ? 'fas' : 'far', 'star']} className={rating >= 3 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} /> - = 4 ? 'fas' : 'far', 'star']} className={rating >= 4 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} /> - + + = 2 ? "fas" : "far", "star"]} + className={rating >= 2 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} + /> + = 3 ? "fas" : "far", "star"]} + className={rating >= 3 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} + /> + = 4 ? "fas" : "far", "star"]} + className={rating >= 4 ? CLASSNAME_FILLED : CLASSNAME_UNFILLED} + /> +
- ) : <> -); + ) : ( + <> + ); diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx index 4384506e616..5b4c91235fd 100644 --- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -1,13 +1,13 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import * as GQL from 'src/core/generated-graphql'; -import { Button } from 'react-bootstrap'; -import cx from 'classnames'; +import React, { useCallback, useEffect, useRef, useState } from "react"; +import * as GQL from "src/core/generated-graphql"; +import { Button } from "react-bootstrap"; +import cx from "classnames"; import Mousetrap from "mousetrap"; -import { debounce } from 'lodash'; +import { debounce } from "lodash"; -import { Icon, LoadingIndicator } from 'src/components/Shared'; +import { Icon, LoadingIndicator } from "src/components/Shared"; -const CLASSNAME = 'Lightbox'; +const CLASSNAME = "Lightbox"; const CLASSNAME_HEADER = `${CLASSNAME}-header`; const CLASSNAME_INDICATOR = `${CLASSNAME_HEADER}-indicator`; const CLASSNAME_DISPLAY = `${CLASSNAME}-display`; @@ -19,7 +19,7 @@ const CLASSNAME_NAV = `${CLASSNAME}-nav`; const CLASSNAME_NAVIMAGE = `${CLASSNAME_NAV}-image`; const CLASSNAME_NAVSELECTED = `${CLASSNAME_NAV}-selected`; -type Image = Pick; +type Image = Pick; interface IProps { images: Image[]; isVisible: boolean; @@ -52,50 +52,57 @@ export const LightboxComponent: React.FC = ({ useEffect(() => { setIsSwitchingPage(false); - if (index.current === -1) - index.current = images.length - 1; + if (index.current === -1) index.current = images.length - 1; }, [images]); - const disableInstantTransition = debounce(() => setInstantTransition(false), 400); + const disableInstantTransition = debounce( + () => setInstantTransition(false), + 400 + ); const setInstant = useCallback(() => { setInstantTransition(true); disableInstantTransition(); }, [disableInstantTransition]); - const setIndex = useCallback((i: number) => { - if (images.length < 2) - return; + const setIndex = useCallback( + (i: number) => { + if (images.length < 2) return; - index.current = i; - if (carouselRef.current) - carouselRef.current.style.left = `${(i * -100)}vw`; - if (indicatorRef.current) - indicatorRef.current.innerHTML = `${i + 1} / ${images.length}`; - if (navRef.current) { - const currentThumb = navRef.current.children[i + 1]; - if (currentThumb instanceof HTMLImageElement) { - const offset = -1 * (currentThumb.offsetLeft - (document.documentElement.clientWidth / 2)); - navRef.current.style.left = `${offset}px`; + index.current = i; + if (carouselRef.current) carouselRef.current.style.left = `${i * -100}vw`; + if (indicatorRef.current) + indicatorRef.current.innerHTML = `${i + 1} / ${images.length}`; + if (navRef.current) { + const currentThumb = navRef.current.children[i + 1]; + if (currentThumb instanceof HTMLImageElement) { + const offset = + -1 * + (currentThumb.offsetLeft - + document.documentElement.clientWidth / 2); + navRef.current.style.left = `${offset}px`; - const previouslySelected = navRef.current.getElementsByClassName(CLASSNAME_NAVSELECTED)?.[0] - if (previouslySelected) - previouslySelected.className = CLASSNAME_NAVIMAGE; + const previouslySelected = navRef.current.getElementsByClassName( + CLASSNAME_NAVSELECTED + )?.[0]; + if (previouslySelected) + previouslySelected.className = CLASSNAME_NAVIMAGE; - currentThumb.className = `${CLASSNAME_NAVIMAGE} ${CLASSNAME_NAVSELECTED}`; + currentThumb.className = `${CLASSNAME_NAVIMAGE} ${CLASSNAME_NAVSELECTED}`; + } } - } - }, [images]); + }, + [images] + ); const selectIndex = (e: React.MouseEvent, i: number) => { - setIndex(i) + setIndex(i); e.stopPropagation(); - } + }; useEffect(() => { if (isVisible) { - if (index.current === null) - setIndex(initialIndex); - document.body.style.overflow = 'hidden'; + if (index.current === null) setIndex(initialIndex); + document.body.style.overflow = "hidden"; // eslint-disable-next-line @typescript-eslint/no-explicit-any (Mousetrap as any).pause(); } @@ -104,23 +111,19 @@ export const LightboxComponent: React.FC = ({ const close = useCallback(() => { if (!isFullscreen) { hide(); - document.body.style.overflow = 'auto'; + document.body.style.overflow = "auto"; // eslint-disable-next-line @typescript-eslint/no-explicit-any (Mousetrap as any).unpause(); - } - else - document.exitFullscreen(); + } else document.exitFullscreen(); }, [isFullscreen, hide]); const handleClose = (e: React.MouseEvent) => { - const { nodeName } = (e.target as Node); - if (nodeName === "DIV" || nodeName === "PICTURE") - close(); - } + const { nodeName } = e.target as Node; + if (nodeName === "DIV" || nodeName === "PICTURE") close(); + }; const handleLeft = useCallback(() => { - if (isSwitchingPage || index.current === -1) - return; + if (isSwitchingPage || index.current === -1) return; if (index.current === 0) { if (pageCallback) { @@ -132,16 +135,11 @@ export const LightboxComponent: React.FC = ({ setIsSwitchingPage(false); setIndex(0); } - } - else - setIndex(images.length - 1); - } - else - setIndex((index.current ?? 0) - 1); + } else setIndex(images.length - 1); + } else setIndex((index.current ?? 0) - 1); }, [images, setIndex, pageCallback, isSwitchingPage]); const handleRight = useCallback(() => { - if (isSwitchingPage) - return; + if (isSwitchingPage) return; if (index.current === images.length - 1) { if (pageCallback) { @@ -152,160 +150,200 @@ export const LightboxComponent: React.FC = ({ setIsSwitchingPage(false); setIndex(images.length - 1); } - } - else - setIndex(0); - } - else - setIndex((index.current ?? 0) + 1); + } else setIndex(0); + } else setIndex((index.current ?? 0) + 1); }, [images, setIndex, pageCallback, isSwitchingPage]); - const handleKey = useCallback((e: KeyboardEvent) => { - if (e.repeat && (e.key === "ArrowRight" || e.key === "ArrowLeft")) - setInstant(); - if (e.key === "ArrowLeft") - handleLeft(); - else if (e.key === "ArrowRight") - handleRight(); - else if (e.key === "Escape") - close(); - }, [setInstant, handleLeft, handleRight, close]); - const handleFullScreenChange = () => setFullscreen(document.fullscreenElement !== null); + const handleKey = useCallback( + (e: KeyboardEvent) => { + if (e.repeat && (e.key === "ArrowRight" || e.key === "ArrowLeft")) + setInstant(); + if (e.key === "ArrowLeft") handleLeft(); + else if (e.key === "ArrowRight") handleRight(); + else if (e.key === "Escape") close(); + }, + [setInstant, handleLeft, handleRight, close] + ); + const handleFullScreenChange = () => + setFullscreen(document.fullscreenElement !== null); const handleTouchStart = (ev: React.TouchEvent) => { setInstantTransition(true); const el = ev.currentTarget; - if (ev.touches.length !== 1) - return; + if (ev.touches.length !== 1) return; const startX = ev.touches[0].clientX; let position = 0; const resetPosition = () => { if (carouselRef.current) - carouselRef.current.style.left = `${((index.current ?? 0) * -100)}vw`; - } + carouselRef.current.style.left = `${(index.current ?? 0) * -100}vw`; + }; const handleMove = (e: TouchEvent) => { position = e.touches[0].clientX; if (carouselRef.current) - carouselRef.current.style.left = `calc(${((index.current ?? 0) * -100)}vw + ${e.touches[0].clientX - startX}px)`; - } + carouselRef.current.style.left = `calc(${ + (index.current ?? 0) * -100 + }vw + ${e.touches[0].clientX - startX}px)`; + }; const handleEnd = () => { const diff = position - startX; - if (diff <= -50) - handleRight(); - else if (diff >= 50) - handleLeft(); - else - resetPosition(); + if (diff <= -50) handleRight(); + else if (diff >= 50) handleLeft(); + else resetPosition(); // eslint-disable-next-line @typescript-eslint/no-use-before-define cleanup(); - } + }; const handleCancel = () => { // eslint-disable-next-line @typescript-eslint/no-use-before-define cleanup(); resetPosition(); - } + }; const cleanup = () => { - el.removeEventListener('touchmove', handleMove); - el.removeEventListener('touchend', handleEnd); - el.removeEventListener('touchcancel', handleCancel); + el.removeEventListener("touchmove", handleMove); + el.removeEventListener("touchend", handleEnd); + el.removeEventListener("touchcancel", handleCancel); setInstantTransition(false); - } + }; - el.addEventListener('touchmove', handleMove); - el.addEventListener('touchend', handleEnd); - el.addEventListener('touchcancel', handleCancel); - } + el.addEventListener("touchmove", handleMove); + el.addEventListener("touchend", handleEnd); + el.addEventListener("touchcancel", handleCancel); + }; useEffect(() => { if (isVisible) { - document.addEventListener('keydown', handleKey); - document.addEventListener('fullscreenchange', handleFullScreenChange); + document.addEventListener("keydown", handleKey); + document.addEventListener("fullscreenchange", handleFullScreenChange); } return () => { - document.removeEventListener('keydown', handleKey); - document.removeEventListener('fullscreenchange', handleFullScreenChange); - } + document.removeEventListener("keydown", handleKey); + document.removeEventListener("fullscreenchange", handleFullScreenChange); + }; }, [isVisible, handleKey]); const toggleFullscreen = useCallback(() => { - if (!isFullscreen) - containerRef.current?.requestFullscreen(); - else - document.exitFullscreen(); + if (!isFullscreen) containerRef.current?.requestFullscreen(); + else document.exitFullscreen(); }, [isFullscreen]); const navItems = images.map((image, i) => ( - selectIndex(e, i)} role="presentation" loading="lazy" /> + selectIndex(e, i)} + role="presentation" + loading="lazy" + /> )); const currentIndex = index.current === null ? initialIndex : index.current; const element = isVisible ? ( -
- { images.length > 0 && !isLoading && !isSwitchingPage ? ( +
+ {images.length > 0 && !isLoading && !isSwitchingPage ? ( <>
- { pageHeader } + {pageHeader} - { `${currentIndex + 1} / ${images.length}` } + {`${currentIndex + 1} / ${images.length}`}
- { document.fullscreenEnabled && ( - )} -
- { images.length > 1 && ( - )}
- { images.map(image => ( -
- - - - -
+ {images.map((image) => ( +
+ + + + +
))}
- { images.length > 1 && ( - )}
- { showNavigation && !isFullscreen && images.length > 1 && ( + {showNavigation && !isFullscreen && images.length > 1 && (
- - { navItems } -
)} - ) : } + ) : ( + + )}
- ) : <>; + ) : ( + <> + ); return element; -} +}; diff --git a/ui/v2.5/src/hooks/Lightbox/context.tsx b/ui/v2.5/src/hooks/Lightbox/context.tsx index b4d50358b07..cff2e2e288e 100644 --- a/ui/v2.5/src/hooks/Lightbox/context.tsx +++ b/ui/v2.5/src/hooks/Lightbox/context.tsx @@ -1,8 +1,8 @@ -import React, { useCallback, useState } from 'react'; -import * as GQL from 'src/core/generated-graphql'; -import { LightboxComponent } from './Lightbox'; +import React, { useCallback, useState } from "react"; +import * as GQL from "src/core/generated-graphql"; +import { LightboxComponent } from "./Lightbox"; -type Image = Pick; +type Image = Pick; export interface IState { images: Image[]; @@ -15,9 +15,11 @@ export interface IState { } interface IContext { setLightboxState: (state: Partial) => void; -}; +} -export const LightboxContext = React.createContext({ setLightboxState: () => {} }); +export const LightboxContext = React.createContext({ + setLightboxState: () => {}, +}); const Lightbox: React.FC = ({ children }) => { const [lightboxState, setLightboxState] = useState({ images: [], @@ -26,24 +28,27 @@ const Lightbox: React.FC = ({ children }) => { showNavigation: true, }); - const setPartialState = useCallback((state: Partial) => { - setLightboxState((currentState: IState) => ({ - ...currentState, - ...state, - })); - }, [setLightboxState]); + const setPartialState = useCallback( + (state: Partial) => { + setLightboxState((currentState: IState) => ({ + ...currentState, + ...state, + })); + }, + [setLightboxState] + ); return ( - { children } - { lightboxState.isVisible && ( + {children} + {lightboxState.isVisible && ( setLightboxState({ ...lightboxState, isVisible: false }) } + {...lightboxState} + hide={() => setLightboxState({ ...lightboxState, isVisible: false })} /> )} ); -} +}; export default Lightbox; diff --git a/ui/v2.5/src/hooks/Lightbox/hooks.ts b/ui/v2.5/src/hooks/Lightbox/hooks.ts index a895b818ef7..fa5685e1752 100644 --- a/ui/v2.5/src/hooks/Lightbox/hooks.ts +++ b/ui/v2.5/src/hooks/Lightbox/hooks.ts @@ -1,8 +1,8 @@ -import { useCallback, useContext, useEffect } from 'react'; -import * as GQL from 'src/core/generated-graphql'; -import { LightboxContext, IState } from './context'; +import { useCallback, useContext, useEffect } from "react"; +import * as GQL from "src/core/generated-graphql"; +import { LightboxContext, IState } from "./context"; -export const useLightbox = (state: Partial>) => { +export const useLightbox = (state: Partial>) => { const { setLightboxState } = useContext(LightboxContext); useEffect(() => { @@ -13,20 +13,32 @@ export const useLightbox = (state: Partial>) => { initialIndex: state.initialIndex, pageHeader: state.pageHeader, }); - }, [setLightboxState, state.images, state.showNavigation, state.pageCallback, state.initialIndex, state.pageHeader]); + }, [ + setLightboxState, + state.images, + state.showNavigation, + state.pageCallback, + state.initialIndex, + state.pageHeader, + ]); - const show = useCallback((index?: number) => { - setLightboxState({ - initialIndex: index, - isVisible: true, - }); - }, [setLightboxState]); + const show = useCallback( + (index?: number) => { + setLightboxState({ + initialIndex: index, + isVisible: true, + }); + }, + [setLightboxState] + ); return show; -} +}; export const useGalleryLightbox = (id: string) => { const { setLightboxState } = useContext(LightboxContext); - const [fetchGallery, { data }] = GQL.useFindGalleryLazyQuery({ variables: { id } }); + const [fetchGallery, { data }] = GQL.useFindGalleryLazyQuery({ + variables: { id }, + }); useEffect(() => { if (data) @@ -55,7 +67,7 @@ export const useGalleryLightbox = (id: string) => { }); fetchGallery(); } - } + }; return show; -} +}; diff --git a/ui/v2.5/src/hooks/Lightbox/index.ts b/ui/v2.5/src/hooks/Lightbox/index.ts index 472021f7e91..493207ae702 100644 --- a/ui/v2.5/src/hooks/Lightbox/index.ts +++ b/ui/v2.5/src/hooks/Lightbox/index.ts @@ -1,2 +1,2 @@ -export * from './context'; -export * from './hooks'; +export * from "./context"; +export * from "./hooks"; diff --git a/ui/v2.5/src/hooks/Lightbox/lightbox.scss b/ui/v2.5/src/hooks/Lightbox/lightbox.scss index f28f3777992..643b5514449 100644 --- a/ui/v2.5/src/hooks/Lightbox/lightbox.scss +++ b/ui/v2.5/src/hooks/Lightbox/lightbox.scss @@ -66,8 +66,8 @@ } img { - max-height: 100%; margin: auto; + max-height: 100%; max-width: 100%; object-fit: contain; } diff --git a/ui/v2.5/src/hooks/ListHook.tsx b/ui/v2.5/src/hooks/ListHook.tsx index 47be05612ab..e358d11140b 100644 --- a/ui/v2.5/src/hooks/ListHook.tsx +++ b/ui/v2.5/src/hooks/ListHook.tsx @@ -95,7 +95,7 @@ interface IListHookOptions { selectedIds: Set, zoomIndex: number, onChangePage: (page: number) => void, - pageCount: number, + pageCount: number ) => React.ReactNode; renderEditDialog?: ( selected: E[], @@ -358,7 +358,14 @@ const RenderList = < return ( <> {renderPagination()} - {renderContent(result, filter, selectedIds, zoomIndex, onChangePage, pages)} + {renderContent( + result, + filter, + selectedIds, + zoomIndex, + onChangePage, + pages + )} Date: Sun, 20 Dec 2020 23:04:46 +0000 Subject: [PATCH 08/12] Linting --- ui/v2.5/src/components/Galleries/styles.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/v2.5/src/components/Galleries/styles.scss b/ui/v2.5/src/components/Galleries/styles.scss index ceae6081461..943e538dae8 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -130,9 +130,11 @@ $galleryTabWidth: 450px; @mixin galleryWidth($width) { height: ($width / 3) * 2; + &-landscape { width: $width; } + &-portrait { width: $width / 2; } From 1719b512d0696b5f7721269c465e4850957d2ea2 Mon Sep 17 00:00:00 2001 From: Infinite Date: Mon, 21 Dec 2020 23:54:33 +0000 Subject: [PATCH 09/12] Switch out arrow icons --- ui/v2.5/src/hooks/Lightbox/Lightbox.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx index 5b4c91235fd..fd1a38de7cc 100644 --- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -283,7 +283,7 @@ export const LightboxComponent: React.FC = ({ onClick={handleLeft} className={`${CLASSNAME_NAVBUTTON} d-none d-lg-block`} > - + )} @@ -313,7 +313,7 @@ export const LightboxComponent: React.FC = ({ onClick={handleRight} className={`${CLASSNAME_NAVBUTTON} d-none d-lg-block`} > - + )}
@@ -324,7 +324,7 @@ export const LightboxComponent: React.FC = ({ onClick={() => setIndex(images.length - 1)} className={CLASSNAME_NAVBUTTON} > - + {navItems}
)} From fe060fb0a3759e6856bce91d6cda8745655cc42f Mon Sep 17 00:00:00 2001 From: Infinite Date: Tue, 22 Dec 2020 00:45:50 +0000 Subject: [PATCH 10/12] Add image keys and opacity to arrows --- ui/v2.5/src/hooks/Lightbox/Lightbox.tsx | 3 ++- ui/v2.5/src/hooks/Lightbox/lightbox.scss | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx index fd1a38de7cc..43e429935fd 100644 --- a/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx +++ b/ui/v2.5/src/hooks/Lightbox/Lightbox.tsx @@ -238,6 +238,7 @@ export const LightboxComponent: React.FC = ({ onClick={(e: React.MouseEvent) => selectIndex(e, i)} role="presentation" loading="lazy" + key={image.paths.thumbnail} /> )); @@ -295,7 +296,7 @@ export const LightboxComponent: React.FC = ({ ref={carouselRef} > {images.map((image) => ( -
+
Date: Tue, 22 Dec 2020 00:47:35 +0000 Subject: [PATCH 11/12] Linting --- ui/v2.5/src/hooks/Lightbox/lightbox.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/v2.5/src/hooks/Lightbox/lightbox.scss b/ui/v2.5/src/hooks/Lightbox/lightbox.scss index a61689350a0..895c7bd80f4 100644 --- a/ui/v2.5/src/hooks/Lightbox/lightbox.scss +++ b/ui/v2.5/src/hooks/Lightbox/lightbox.scss @@ -36,8 +36,8 @@ .fa-icon { height: 1.5rem; - width: 1.5rem; opacity: 1; + width: 1.5rem; } } From 000b0b406e4a7a537416c4c764fb9c656579a8ca Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Thu, 24 Dec 2020 10:09:35 +1100 Subject: [PATCH 12/12] Add changelog entry --- ui/v2.5/src/components/Changelog/versions/v050.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/v2.5/src/components/Changelog/versions/v050.md b/ui/v2.5/src/components/Changelog/versions/v050.md index 430e95bb772..f79c0a8723a 100644 --- a/ui/v2.5/src/components/Changelog/versions/v050.md +++ b/ui/v2.5/src/components/Changelog/versions/v050.md @@ -1,10 +1,12 @@ -#### 💥 **Note: After upgrading, all scene file sizes will be 0B until a new [scan](/settings?tab=tasks) is run. +#### 💥 Note: After upgrading, all scene file sizes will be 0B until a new [scan](/settings?tab=tasks) is run. ### ✨ New Features +* Add gallery wall view. * Add organized flag for scenes, galleries and images. * Allow configuration of visible navbar items. ### 🎨 Improvements +* Pagination support and general improvements for image lightbox. * Add mouse click support for CDP scrapers. * Add gallery tabs to performer and studio pages. * Add gallery scrapers to scraper page.