From cc97e96eaad64bae50c013affe914fc5356b3935 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:45:29 +1000 Subject: [PATCH] Add wall zoom functionality (#6011) * Show zoom slider when wall view active * Add zoom functionality to scene wall * Add zoom functionality to image wall * Add zoom functionality to gallery wall * Add zoom functionality for marker wall --- .../src/components/Galleries/GalleryList.tsx | 2 +- ui/v2.5/src/components/Galleries/styles.scss | 80 +++++++++++++------ ui/v2.5/src/components/Images/ImageList.tsx | 29 ++++++- .../src/components/List/ListViewOptions.tsx | 3 +- ui/v2.5/src/components/Scenes/SceneList.tsx | 8 +- .../src/components/Scenes/SceneMarkerList.tsx | 5 +- .../Scenes/SceneMarkerWallPanel.tsx | 26 +++++- .../src/components/Scenes/SceneWallPanel.tsx | 32 +++++++- 8 files changed, 148 insertions(+), 37 deletions(-) diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index 7becbe93a..a0930b927 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -149,7 +149,7 @@ export const GalleryList: React.FC = ({ if (filter.displayMode === DisplayMode.Wall) { return (
-
+
{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 12439a94d..58116e936 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -206,7 +206,7 @@ $galleryTabWidth: 450px; } } -.GalleryWall { +div.GalleryWall { display: flex; flex-wrap: wrap; margin: 0 auto; @@ -249,28 +249,6 @@ $galleryTabWidth: 450px; z-index: 1; } - @mixin galleryWidth($width) { - height: math.div($width, 3) * 2; - - &-landscape { - width: $width; - } - - &-portrait { - width: math.div($width, 2); - } - } - - @media (min-width: 576px) { - @include galleryWidth(96vw); - } - @media (min-width: 768px) { - @include galleryWidth(48vw); - } - @media (min-width: 1200px) { - @include galleryWidth(32vw); - } - &-img { height: 100%; object-fit: cover; @@ -355,6 +333,62 @@ $galleryTabWidth: 450px; } } +div.GalleryWall { + @mixin galleryWidth($width) { + height: math.div($width, 3) * 2; + + &-landscape { + width: $width; + } + + &-portrait { + width: math.div($width, 2); + } + } + + .GalleryWallCard { + @media (min-width: 576px) { + @include galleryWidth(96vw); + } + } + + &.zoom-0 .GalleryWallCard { + @media (min-width: 768px) { + @include galleryWidth(16vw); + } + @media (min-width: 1200px) { + @include galleryWidth(10vw); + } + } + + &.zoom-1 .GalleryWallCard { + @media (min-width: 768px) { + @include galleryWidth(24vw); + } + @media (min-width: 1200px) { + @include galleryWidth(16vw); + } + } + + &.zoom-2 .GalleryWallCard { + @media (min-width: 768px) { + @include galleryWidth(32vw); + } + @media (min-width: 1200px) { + @include galleryWidth(24vw); + } + } + + &.zoom-3 .GalleryWallCard { + @media (min-width: 768px) { + @include galleryWidth(48vw); + } + @media (min-width: 1200px) { + @include galleryWidth(32vw); + } + } +} + .gallery-file-card.card { margin: 0; padding: 0; diff --git a/ui/v2.5/src/components/Images/ImageList.tsx b/ui/v2.5/src/components/Images/ImageList.tsx index 12eb264b1..a468c2815 100644 --- a/ui/v2.5/src/components/Images/ImageList.tsx +++ b/ui/v2.5/src/components/Images/ImageList.tsx @@ -35,9 +35,22 @@ interface IImageWallProps { currentPage: number; pageCount: number; handleImageOpen: (index: number) => void; + zoomIndex: number; } -const ImageWall: React.FC = ({ images, handleImageOpen }) => { +const zoomWidths = [280, 340, 480, 640]; +const breakpointZoomHeights = [ + { minWidth: 576, heights: [100, 120, 240, 360] }, + { minWidth: 768, heights: [120, 160, 240, 480] }, + { minWidth: 1200, heights: [120, 160, 240, 300] }, + { minWidth: 1400, heights: [160, 240, 300, 480] }, +]; + +const ImageWall: React.FC = ({ + images, + zoomIndex, + handleImageOpen, +}) => { const { configuration } = useContext(ConfigurationContext); const uiConfig = configuration?.ui; @@ -76,11 +89,21 @@ const ImageWall: React.FC = ({ images, handleImageOpen }) => { ); function columns(containerWidth: number) { - let preferredSize = 300; + let preferredSize = zoomWidths[zoomIndex]; let columnCount = containerWidth / preferredSize; return Math.round(columnCount); } + function targetRowHeight(containerWidth: number) { + let zoomHeight = 280; + breakpointZoomHeights.forEach((e) => { + if (containerWidth >= e.minWidth) { + zoomHeight = e.heights[zoomIndex]; + } + }); + return zoomHeight; + } + return (
{photos.length ? ( @@ -91,6 +114,7 @@ const ImageWall: React.FC = ({ images, handleImageOpen }) => { margin={uiConfig?.imageWallOptions?.margin!} direction={uiConfig?.imageWallOptions?.direction!} columns={columns} + targetRowHeight={targetRowHeight} /> ) : null}
@@ -211,6 +235,7 @@ const ImageListImages: React.FC = ({ currentPage={filter.currentPage} pageCount={pageCount} handleImageOpen={handleImageOpen} + zoomIndex={filter.zoomIndex} /> ); } diff --git a/ui/v2.5/src/components/List/ListViewOptions.tsx b/ui/v2.5/src/components/List/ListViewOptions.tsx index e83ff9290..1ea928983 100644 --- a/ui/v2.5/src/components/List/ListViewOptions.tsx +++ b/ui/v2.5/src/components/List/ListViewOptions.tsx @@ -130,7 +130,8 @@ export const ListViewOptions: React.FC = ({
{onSetZoom && zoomIndex !== undefined && - displayMode === DisplayMode.Grid ? ( + (displayMode === DisplayMode.Grid || + displayMode === DisplayMode.Wall) ? (
; + return ( + + ); } if (filter.displayMode === DisplayMode.Tagger) { return ; diff --git a/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx b/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx index f28ac718b..94eb6e133 100644 --- a/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx +++ b/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx @@ -95,7 +95,10 @@ export const SceneMarkerList: React.FC = ({ if (filter.displayMode === DisplayMode.Wall) { return ( - + ); } diff --git a/ui/v2.5/src/components/Scenes/SceneMarkerWallPanel.tsx b/ui/v2.5/src/components/Scenes/SceneMarkerWallPanel.tsx index f240b36e6..5202b94d1 100644 --- a/ui/v2.5/src/components/Scenes/SceneMarkerWallPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneMarkerWallPanel.tsx @@ -120,6 +120,7 @@ export const MarkerWallItem: React.FC> = ( interface IMarkerWallProps { markers: GQL.SceneMarkerDataFragment[]; + zoomIndex: number; } // HACK: typescript doesn't allow Gallery to accept a parameter for some reason @@ -152,9 +153,14 @@ function getDimensions(file?: IFile) { }; } -const defaultTargetRowHeight = 250; +const breakpointZoomHeights = [ + { minWidth: 576, heights: [100, 120, 240, 360] }, + { minWidth: 768, heights: [120, 160, 240, 480] }, + { minWidth: 1200, heights: [120, 160, 240, 300] }, + { minWidth: 1400, heights: [160, 240, 300, 480] }, +]; -const MarkerWall: React.FC = ({ markers }) => { +const MarkerWall: React.FC = ({ markers, zoomIndex }) => { const history = useHistory(); const margin = 3; @@ -202,6 +208,16 @@ const MarkerWall: React.FC = ({ markers }) => { return Math.round(columnCount); } + function targetRowHeight(containerWidth: number) { + let zoomHeight = 280; + breakpointZoomHeights.forEach((e) => { + if (containerWidth >= e.minWidth) { + zoomHeight = e.heights[zoomIndex]; + } + }); + return zoomHeight; + } + const renderImage = useCallback((props: RenderImageProps) => { return ; }, []); @@ -216,7 +232,7 @@ const MarkerWall: React.FC = ({ markers }) => { margin={margin} direction={direction} columns={columns} - targetRowHeight={defaultTargetRowHeight} + targetRowHeight={targetRowHeight} /> ) : null}
@@ -225,10 +241,12 @@ const MarkerWall: React.FC = ({ markers }) => { interface IMarkerWallPanelProps { markers: GQL.SceneMarkerDataFragment[]; + zoomIndex: number; } export const MarkerWallPanel: React.FC = ({ markers, + zoomIndex, }) => { - return ; + return ; }; diff --git a/ui/v2.5/src/components/Scenes/SceneWallPanel.tsx b/ui/v2.5/src/components/Scenes/SceneWallPanel.tsx index 546a1488a..dcc0bf734 100644 --- a/ui/v2.5/src/components/Scenes/SceneWallPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneWallPanel.tsx @@ -126,14 +126,24 @@ function getDimensions(s: GQL.SlimSceneDataFragment) { interface ISceneWallProps { scenes: GQL.SlimSceneDataFragment[]; sceneQueue?: SceneQueue; + zoomIndex: number; } // HACK: typescript doesn't allow Gallery to accept a parameter for some reason const SceneGallery = Gallery as unknown as GalleryI; -const defaultTargetRowHeight = 250; +const breakpointZoomHeights = [ + { minWidth: 576, heights: [100, 120, 240, 360] }, + { minWidth: 768, heights: [120, 160, 240, 480] }, + { minWidth: 1200, heights: [120, 160, 240, 300] }, + { minWidth: 1400, heights: [160, 240, 300, 480] }, +]; -const SceneWall: React.FC = ({ scenes, sceneQueue }) => { +const SceneWall: React.FC = ({ + scenes, + sceneQueue, + zoomIndex, +}) => { const history = useHistory(); const margin = 3; @@ -186,6 +196,16 @@ const SceneWall: React.FC = ({ scenes, sceneQueue }) => { return Math.round(columnCount); } + function targetRowHeight(containerWidth: number) { + let zoomHeight = 280; + breakpointZoomHeights.forEach((e) => { + if (containerWidth >= e.minWidth) { + zoomHeight = e.heights[zoomIndex]; + } + }); + return zoomHeight; + } + const renderImage = useCallback((props: RenderImageProps) => { return ; }, []); @@ -200,7 +220,7 @@ const SceneWall: React.FC = ({ scenes, sceneQueue }) => { margin={margin} direction={direction} columns={columns} - targetRowHeight={defaultTargetRowHeight} + targetRowHeight={targetRowHeight} /> ) : null}
@@ -210,11 +230,15 @@ const SceneWall: React.FC = ({ scenes, sceneQueue }) => { interface ISceneWallPanelProps { scenes: GQL.SlimSceneDataFragment[]; sceneQueue?: SceneQueue; + zoomIndex: number; } export const SceneWallPanel: React.FC = ({ scenes, sceneQueue, + zoomIndex, }) => { - return ; + return ( + + ); };