mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
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:
parent
b1883f3df5
commit
cc97e96eaa
8 changed files with 148 additions and 37 deletions
|
|
@ -149,7 +149,7 @@ export const GalleryList: React.FC<IGalleryList> = ({
|
|||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="GalleryWall">
|
||||
<div className={`GalleryWall zoom-${filter.zoomIndex}`}>
|
||||
{result.data.findGalleries.galleries.map((gallery) => (
|
||||
<GalleryWallCard key={gallery.id} gallery={gallery} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -35,9 +35,22 @@ interface IImageWallProps {
|
|||
currentPage: number;
|
||||
pageCount: number;
|
||||
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 uiConfig = configuration?.ui;
|
||||
|
||||
|
|
@ -76,11 +89,21 @@ const ImageWall: React.FC<IImageWallProps> = ({ 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 (
|
||||
<div className="gallery">
|
||||
{photos.length ? (
|
||||
|
|
@ -91,6 +114,7 @@ const ImageWall: React.FC<IImageWallProps> = ({ images, handleImageOpen }) => {
|
|||
margin={uiConfig?.imageWallOptions?.margin!}
|
||||
direction={uiConfig?.imageWallOptions?.direction!}
|
||||
columns={columns}
|
||||
targetRowHeight={targetRowHeight}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
@ -211,6 +235,7 @@ const ImageListImages: React.FC<IImageListImages> = ({
|
|||
currentPage={filter.currentPage}
|
||||
pageCount={pageCount}
|
||||
handleImageOpen={handleImageOpen}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,8 @@ export const ListViewOptions: React.FC<IListViewOptionsProps> = ({
|
|||
<div className="display-mode-menu">
|
||||
{onSetZoom &&
|
||||
zoomIndex !== undefined &&
|
||||
displayMode === DisplayMode.Grid ? (
|
||||
(displayMode === DisplayMode.Grid ||
|
||||
displayMode === DisplayMode.Wall) ? (
|
||||
<div className="zoom-slider-container">
|
||||
<ZoomSelect
|
||||
minZoom={minZoom}
|
||||
|
|
|
|||
|
|
@ -219,7 +219,13 @@ const SceneList: React.FC<{
|
|||
);
|
||||
}
|
||||
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) {
|
||||
return <Tagger scenes={scenes} queue={queue} />;
|
||||
|
|
|
|||
|
|
@ -95,7 +95,10 @@ export const SceneMarkerList: React.FC<ISceneMarkerList> = ({
|
|||
|
||||
if (filter.displayMode === DisplayMode.Wall) {
|
||||
return (
|
||||
<MarkerWallPanel markers={result.data.findSceneMarkers.scene_markers} />
|
||||
<MarkerWallPanel
|
||||
markers={result.data.findSceneMarkers.scene_markers}
|
||||
zoomIndex={filter.zoomIndex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ export const MarkerWallItem: React.FC<RenderImageProps<IMarkerPhoto>> = (
|
|||
|
||||
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<IMarkerWallProps> = ({ markers }) => {
|
||||
const MarkerWall: React.FC<IMarkerWallProps> = ({ markers, zoomIndex }) => {
|
||||
const history = useHistory();
|
||||
|
||||
const margin = 3;
|
||||
|
|
@ -202,6 +208,16 @@ const MarkerWall: React.FC<IMarkerWallProps> = ({ 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<IMarkerPhoto>) => {
|
||||
return <MarkerWallItem {...props} />;
|
||||
}, []);
|
||||
|
|
@ -216,7 +232,7 @@ const MarkerWall: React.FC<IMarkerWallProps> = ({ markers }) => {
|
|||
margin={margin}
|
||||
direction={direction}
|
||||
columns={columns}
|
||||
targetRowHeight={defaultTargetRowHeight}
|
||||
targetRowHeight={targetRowHeight}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
@ -225,10 +241,12 @@ const MarkerWall: React.FC<IMarkerWallProps> = ({ markers }) => {
|
|||
|
||||
interface IMarkerWallPanelProps {
|
||||
markers: GQL.SceneMarkerDataFragment[];
|
||||
zoomIndex: number;
|
||||
}
|
||||
|
||||
export const MarkerWallPanel: React.FC<IMarkerWallPanelProps> = ({
|
||||
markers,
|
||||
zoomIndex,
|
||||
}) => {
|
||||
return <MarkerWall markers={markers} />;
|
||||
return <MarkerWall markers={markers} zoomIndex={zoomIndex} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<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 margin = 3;
|
||||
|
|
@ -186,6 +196,16 @@ const SceneWall: React.FC<ISceneWallProps> = ({ 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<IScenePhoto>) => {
|
||||
return <SceneWallItem {...props} />;
|
||||
}, []);
|
||||
|
|
@ -200,7 +220,7 @@ const SceneWall: React.FC<ISceneWallProps> = ({ scenes, sceneQueue }) => {
|
|||
margin={margin}
|
||||
direction={direction}
|
||||
columns={columns}
|
||||
targetRowHeight={defaultTargetRowHeight}
|
||||
targetRowHeight={targetRowHeight}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
@ -210,11 +230,15 @@ const SceneWall: React.FC<ISceneWallProps> = ({ scenes, sceneQueue }) => {
|
|||
interface ISceneWallPanelProps {
|
||||
scenes: GQL.SlimSceneDataFragment[];
|
||||
sceneQueue?: SceneQueue;
|
||||
zoomIndex: number;
|
||||
}
|
||||
|
||||
export const SceneWallPanel: React.FC<ISceneWallPanelProps> = ({
|
||||
scenes,
|
||||
sceneQueue,
|
||||
zoomIndex,
|
||||
}) => {
|
||||
return <SceneWall scenes={scenes} sceneQueue={sceneQueue} />;
|
||||
return (
|
||||
<SceneWall scenes={scenes} sceneQueue={sceneQueue} zoomIndex={zoomIndex} />
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue