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
This commit is contained in:
WithoutPants 2025-09-09 16:45:29 +10:00 committed by GitHub
parent b1883f3df5
commit cc97e96eaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 148 additions and 37 deletions

View file

@ -149,7 +149,7 @@ export const GalleryList: React.FC<IGalleryList> = ({
if (filter.displayMode === DisplayMode.Wall) { if (filter.displayMode === DisplayMode.Wall) {
return ( return (
<div className="row"> <div className="row">
<div className="GalleryWall"> <div className={`GalleryWall zoom-${filter.zoomIndex}`}>
{result.data.findGalleries.galleries.map((gallery) => ( {result.data.findGalleries.galleries.map((gallery) => (
<GalleryWallCard key={gallery.id} gallery={gallery} /> <GalleryWallCard key={gallery.id} gallery={gallery} />
))} ))}

View file

@ -206,7 +206,7 @@ $galleryTabWidth: 450px;
} }
} }
.GalleryWall { div.GalleryWall {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin: 0 auto; margin: 0 auto;
@ -249,28 +249,6 @@ $galleryTabWidth: 450px;
z-index: 1; 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 { &-img {
height: 100%; height: 100%;
object-fit: cover; 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 { .gallery-file-card.card {
margin: 0; margin: 0;
padding: 0; padding: 0;

View file

@ -35,9 +35,22 @@ interface IImageWallProps {
currentPage: number; currentPage: number;
pageCount: number; pageCount: number;
handleImageOpen: (index: number) => void; handleImageOpen: (index: number) => void;
zoomIndex: number;
} }
const ImageWall: React.FC<IImageWallProps> = ({ 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<IImageWallProps> = ({
images,
zoomIndex,
handleImageOpen,
}) => {
const { configuration } = useContext(ConfigurationContext); const { configuration } = useContext(ConfigurationContext);
const uiConfig = configuration?.ui; const uiConfig = configuration?.ui;
@ -76,11 +89,21 @@ const ImageWall: React.FC<IImageWallProps> = ({ images, handleImageOpen }) => {
); );
function columns(containerWidth: number) { function columns(containerWidth: number) {
let preferredSize = 300; let preferredSize = zoomWidths[zoomIndex];
let columnCount = containerWidth / preferredSize; let columnCount = containerWidth / preferredSize;
return Math.round(columnCount); 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 ( return (
<div className="gallery"> <div className="gallery">
{photos.length ? ( {photos.length ? (
@ -91,6 +114,7 @@ const ImageWall: React.FC<IImageWallProps> = ({ images, handleImageOpen }) => {
margin={uiConfig?.imageWallOptions?.margin!} margin={uiConfig?.imageWallOptions?.margin!}
direction={uiConfig?.imageWallOptions?.direction!} direction={uiConfig?.imageWallOptions?.direction!}
columns={columns} columns={columns}
targetRowHeight={targetRowHeight}
/> />
) : null} ) : null}
</div> </div>
@ -211,6 +235,7 @@ const ImageListImages: React.FC<IImageListImages> = ({
currentPage={filter.currentPage} currentPage={filter.currentPage}
pageCount={pageCount} pageCount={pageCount}
handleImageOpen={handleImageOpen} handleImageOpen={handleImageOpen}
zoomIndex={filter.zoomIndex}
/> />
); );
} }

View file

@ -130,7 +130,8 @@ export const ListViewOptions: React.FC<IListViewOptionsProps> = ({
<div className="display-mode-menu"> <div className="display-mode-menu">
{onSetZoom && {onSetZoom &&
zoomIndex !== undefined && zoomIndex !== undefined &&
displayMode === DisplayMode.Grid ? ( (displayMode === DisplayMode.Grid ||
displayMode === DisplayMode.Wall) ? (
<div className="zoom-slider-container"> <div className="zoom-slider-container">
<ZoomSelect <ZoomSelect
minZoom={minZoom} minZoom={minZoom}

View file

@ -219,7 +219,13 @@ const SceneList: React.FC<{
); );
} }
if (filter.displayMode === DisplayMode.Wall) { if (filter.displayMode === DisplayMode.Wall) {
return <SceneWallPanel scenes={scenes} sceneQueue={queue} />; return (
<SceneWallPanel
scenes={scenes}
sceneQueue={queue}
zoomIndex={filter.zoomIndex}
/>
);
} }
if (filter.displayMode === DisplayMode.Tagger) { if (filter.displayMode === DisplayMode.Tagger) {
return <Tagger scenes={scenes} queue={queue} />; return <Tagger scenes={scenes} queue={queue} />;

View file

@ -95,7 +95,10 @@ export const SceneMarkerList: React.FC<ISceneMarkerList> = ({
if (filter.displayMode === DisplayMode.Wall) { if (filter.displayMode === DisplayMode.Wall) {
return ( return (
<MarkerWallPanel markers={result.data.findSceneMarkers.scene_markers} /> <MarkerWallPanel
markers={result.data.findSceneMarkers.scene_markers}
zoomIndex={filter.zoomIndex}
/>
); );
} }

View file

@ -120,6 +120,7 @@ export const MarkerWallItem: React.FC<RenderImageProps<IMarkerPhoto>> = (
interface IMarkerWallProps { interface IMarkerWallProps {
markers: GQL.SceneMarkerDataFragment[]; markers: GQL.SceneMarkerDataFragment[];
zoomIndex: number;
} }
// HACK: typescript doesn't allow Gallery to accept a parameter for some reason // 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<IMarkerWallProps> = ({ markers }) => { const MarkerWall: React.FC<IMarkerWallProps> = ({ markers, zoomIndex }) => {
const history = useHistory(); const history = useHistory();
const margin = 3; const margin = 3;
@ -202,6 +208,16 @@ const MarkerWall: React.FC<IMarkerWallProps> = ({ markers }) => {
return Math.round(columnCount); 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<IMarkerPhoto>) => { const renderImage = useCallback((props: RenderImageProps<IMarkerPhoto>) => {
return <MarkerWallItem {...props} />; return <MarkerWallItem {...props} />;
}, []); }, []);
@ -216,7 +232,7 @@ const MarkerWall: React.FC<IMarkerWallProps> = ({ markers }) => {
margin={margin} margin={margin}
direction={direction} direction={direction}
columns={columns} columns={columns}
targetRowHeight={defaultTargetRowHeight} targetRowHeight={targetRowHeight}
/> />
) : null} ) : null}
</div> </div>
@ -225,10 +241,12 @@ const MarkerWall: React.FC<IMarkerWallProps> = ({ markers }) => {
interface IMarkerWallPanelProps { interface IMarkerWallPanelProps {
markers: GQL.SceneMarkerDataFragment[]; markers: GQL.SceneMarkerDataFragment[];
zoomIndex: number;
} }
export const MarkerWallPanel: React.FC<IMarkerWallPanelProps> = ({ export const MarkerWallPanel: React.FC<IMarkerWallPanelProps> = ({
markers, markers,
zoomIndex,
}) => { }) => {
return <MarkerWall markers={markers} />; return <MarkerWall markers={markers} zoomIndex={zoomIndex} />;
}; };

View file

@ -126,14 +126,24 @@ function getDimensions(s: GQL.SlimSceneDataFragment) {
interface ISceneWallProps { interface ISceneWallProps {
scenes: GQL.SlimSceneDataFragment[]; scenes: GQL.SlimSceneDataFragment[];
sceneQueue?: SceneQueue; sceneQueue?: SceneQueue;
zoomIndex: number;
} }
// HACK: typescript doesn't allow Gallery to accept a parameter for some reason // HACK: typescript doesn't allow Gallery to accept a parameter for some reason
const SceneGallery = Gallery as unknown as GalleryI<IScenePhoto>; const SceneGallery = Gallery as unknown as GalleryI<IScenePhoto>;
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<ISceneWallProps> = ({ scenes, sceneQueue }) => { const SceneWall: React.FC<ISceneWallProps> = ({
scenes,
sceneQueue,
zoomIndex,
}) => {
const history = useHistory(); const history = useHistory();
const margin = 3; const margin = 3;
@ -186,6 +196,16 @@ const SceneWall: React.FC<ISceneWallProps> = ({ scenes, sceneQueue }) => {
return Math.round(columnCount); 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<IScenePhoto>) => { const renderImage = useCallback((props: RenderImageProps<IScenePhoto>) => {
return <SceneWallItem {...props} />; return <SceneWallItem {...props} />;
}, []); }, []);
@ -200,7 +220,7 @@ const SceneWall: React.FC<ISceneWallProps> = ({ scenes, sceneQueue }) => {
margin={margin} margin={margin}
direction={direction} direction={direction}
columns={columns} columns={columns}
targetRowHeight={defaultTargetRowHeight} targetRowHeight={targetRowHeight}
/> />
) : null} ) : null}
</div> </div>
@ -210,11 +230,15 @@ const SceneWall: React.FC<ISceneWallProps> = ({ scenes, sceneQueue }) => {
interface ISceneWallPanelProps { interface ISceneWallPanelProps {
scenes: GQL.SlimSceneDataFragment[]; scenes: GQL.SlimSceneDataFragment[];
sceneQueue?: SceneQueue; sceneQueue?: SceneQueue;
zoomIndex: number;
} }
export const SceneWallPanel: React.FC<ISceneWallPanelProps> = ({ export const SceneWallPanel: React.FC<ISceneWallPanelProps> = ({
scenes, scenes,
sceneQueue, sceneQueue,
zoomIndex,
}) => { }) => {
return <SceneWall scenes={scenes} sceneQueue={sceneQueue} />; return (
<SceneWall scenes={scenes} sceneQueue={sceneQueue} zoomIndex={zoomIndex} />
);
}; };