Optimise card width calculation (#5713)

* Add hook for grid card width calculation
* Move card width calculation into grid instead of card

Now calculates once instead of per card

* Debounce resize observer
This commit is contained in:
WithoutPants 2025-03-25 10:28:57 +11:00 committed by GitHub
parent 2541e9d1eb
commit 5d3d02e1e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 145 additions and 315 deletions

View file

@ -1,7 +1,7 @@
import { Button, ButtonGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import * as GQL from "src/core/generated-graphql";
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
import { GridCard } from "../Shared/GridCard/GridCard";
import { HoverPopover } from "../Shared/HoverPopover";
import { Icon } from "../Shared/Icon";
import { SceneLink, TagLink } from "../Shared/TagLink";
@ -12,7 +12,6 @@ import NavUtils from "src/utils/navigation";
import { RatingBanner } from "../Shared/RatingBanner";
import { faBox, faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
import { galleryTitle } from "src/core/galleries";
import ScreenUtils from "src/utils/screen";
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
import { GalleryPreviewScrubber } from "./GalleryPreviewScrubber";
import cx from "classnames";
@ -56,7 +55,7 @@ export const GalleryPreview: React.FC<IGalleryPreviewProps> = ({
interface IProps {
gallery: GQL.SlimGalleryDataFragment;
containerWidth?: number;
cardWidth?: number;
selecting?: boolean;
selected?: boolean | undefined;
zoomIndex?: number;
@ -65,37 +64,6 @@ interface IProps {
export const GalleryCard: React.FC<IProps> = (props) => {
const history = useHistory();
const [cardWidth, setCardWidth] = useState<number>();
useEffect(() => {
if (
!props.containerWidth ||
props.zoomIndex === undefined ||
ScreenUtils.isMobile()
)
return;
let zoomValue = props.zoomIndex;
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 280;
break;
case 1:
preferredCardWidth = 340;
break;
case 2:
preferredCardWidth = 480;
break;
case 3:
preferredCardWidth = 640;
}
let fittedCardWidth = calculateCardWidth(
props.containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [props.containerWidth, props.zoomIndex]);
function maybeRenderScenePopoverButton() {
if (props.gallery.scenes.length === 0) return;
@ -207,7 +175,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
<GridCard
className={`gallery-card zoom-${props.zoomIndex}`}
url={`/galleries/${props.gallery.id}`}
width={cardWidth}
width={props.cardWidth}
title={galleryTitle(props.gallery)}
linkClassName="gallery-card-header"
image={

View file

@ -1,7 +1,10 @@
import React from "react";
import * as GQL from "src/core/generated-graphql";
import { GalleryCard } from "./GalleryCard";
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
import {
useCardWidth,
useContainerDimensions,
} from "../Shared/GridCard/GridCard";
interface IGalleryCardGrid {
galleries: GQL.SlimGalleryDataFragment[];
@ -10,19 +13,23 @@ interface IGalleryCardGrid {
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
}
const zoomWidths = [280, 340, 480, 640];
export const GalleryCardGrid: React.FC<IGalleryCardGrid> = ({
galleries,
selectedIds,
zoomIndex,
onSelectChange,
}) => {
const [componentRef, { width }] = useContainerDimensions();
const [componentRef, { width: containerWidth }] = useContainerDimensions();
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
return (
<div className="row justify-content-center" ref={componentRef}>
{galleries.map((gallery) => (
<GalleryCard
key={gallery.id}
containerWidth={width}
cardWidth={cardWidth}
gallery={gallery}
zoomIndex={zoomIndex}
selecting={selectedIds.size > 0}

View file

@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useMemo } from "react";
import { Button, ButtonGroup } from "react-bootstrap";
import * as GQL from "src/core/generated-graphql";
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
import { GridCard } from "../Shared/GridCard/GridCard";
import { HoverPopover } from "../Shared/HoverPopover";
import { Icon } from "../Shared/Icon";
import { SceneLink, TagLink } from "../Shared/TagLink";
@ -9,7 +9,6 @@ import { TruncatedText } from "../Shared/TruncatedText";
import { FormattedMessage } from "react-intl";
import { RatingBanner } from "../Shared/RatingBanner";
import { faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
import ScreenUtils from "src/utils/screen";
import { RelatedGroupPopoverButton } from "./RelatedGroupPopover";
const Description: React.FC<{
@ -37,7 +36,7 @@ const Description: React.FC<{
interface IProps {
group: GQL.GroupDataFragment;
containerWidth?: number;
cardWidth?: number;
sceneNumber?: number;
selecting?: boolean;
selected?: boolean;
@ -50,7 +49,7 @@ interface IProps {
export const GroupCard: React.FC<IProps> = ({
group,
sceneNumber,
containerWidth,
cardWidth,
selecting,
selected,
zoomIndex,
@ -58,8 +57,6 @@ export const GroupCard: React.FC<IProps> = ({
fromGroupId,
onMove,
}) => {
const [cardWidth, setCardWidth] = useState<number>();
const groupDescription = useMemo(() => {
if (!fromGroupId) {
return undefined;
@ -72,32 +69,6 @@ export const GroupCard: React.FC<IProps> = ({
return containingGroup?.description ?? undefined;
}, [fromGroupId, group.containing_groups]);
useEffect(() => {
if (!containerWidth || zoomIndex === undefined || ScreenUtils.isMobile())
return;
let zoomValue = zoomIndex;
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 210;
break;
case 1:
preferredCardWidth = 250;
break;
case 2:
preferredCardWidth = 300;
break;
case 3:
preferredCardWidth = 375;
}
let fittedCardWidth = calculateCardWidth(
containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [containerWidth, zoomIndex]);
function maybeRenderScenesPopoverButton() {
if (group.scenes.length === 0) return;

View file

@ -1,7 +1,10 @@
import React from "react";
import * as GQL from "src/core/generated-graphql";
import { GroupCard } from "./GroupCard";
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
import {
useCardWidth,
useContainerDimensions,
} from "../Shared/GridCard/GridCard";
interface IGroupCardGrid {
groups: GQL.GroupDataFragment[];
@ -12,6 +15,8 @@ interface IGroupCardGrid {
onMove?: (srcIds: string[], targetId: string, after: boolean) => void;
}
const zoomWidths = [210, 250, 300, 375];
export const GroupCardGrid: React.FC<IGroupCardGrid> = ({
groups,
selectedIds,
@ -20,13 +25,15 @@ export const GroupCardGrid: React.FC<IGroupCardGrid> = ({
fromGroupId,
onMove,
}) => {
const [componentRef, { width }] = useContainerDimensions();
const [componentRef, { width: containerWidth }] = useContainerDimensions();
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
return (
<div className="row justify-content-center" ref={componentRef}>
{groups.map((p) => (
<GroupCard
key={p.id}
containerWidth={width}
cardWidth={cardWidth}
group={p}
zoomIndex={zoomIndex}
selecting={selectedIds.size > 0}

View file

@ -1,4 +1,4 @@
import React, { MouseEvent, useEffect, useMemo, useState } from "react";
import React, { MouseEvent, useMemo } from "react";
import { Button, ButtonGroup } from "react-bootstrap";
import cx from "classnames";
import * as GQL from "src/core/generated-graphql";
@ -7,10 +7,7 @@ import { GalleryLink, TagLink } from "src/components/Shared/TagLink";
import { HoverPopover } from "src/components/Shared/HoverPopover";
import { SweatDrops } from "src/components/Shared/SweatDrops";
import { PerformerPopoverButton } from "src/components/Shared/PerformerPopoverButton";
import {
GridCard,
calculateCardWidth,
} from "src/components/Shared/GridCard/GridCard";
import { GridCard } from "src/components/Shared/GridCard/GridCard";
import { RatingBanner } from "src/components/Shared/RatingBanner";
import {
faBox,
@ -20,12 +17,11 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { imageTitle } from "src/core/files";
import { TruncatedText } from "../Shared/TruncatedText";
import ScreenUtils from "src/utils/screen";
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
interface IImageCardProps {
image: GQL.SlimImageDataFragment;
containerWidth?: number;
cardWidth?: number;
selecting?: boolean;
selected?: boolean | undefined;
zoomIndex: number;
@ -36,38 +32,6 @@ interface IImageCardProps {
export const ImageCard: React.FC<IImageCardProps> = (
props: IImageCardProps
) => {
const [cardWidth, setCardWidth] = useState<number>();
useEffect(() => {
if (
!props.containerWidth ||
props.zoomIndex === undefined ||
ScreenUtils.isMobile()
)
return;
let zoomValue = props.zoomIndex;
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 280;
break;
case 1:
preferredCardWidth = 340;
break;
case 2:
preferredCardWidth = 480;
break;
case 3:
preferredCardWidth = 640;
}
let fittedCardWidth = calculateCardWidth(
props.containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [props.containerWidth, props.zoomIndex]);
const file = useMemo(
() =>
props.image.visual_files.length > 0
@ -196,7 +160,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
<GridCard
className={`image-card zoom-${props.zoomIndex}`}
url={`/images/${props.image.id}`}
width={cardWidth}
width={props.cardWidth}
title={imageTitle(props.image)}
linkClassName="image-card-link"
image={

View file

@ -1,7 +1,10 @@
import React from "react";
import * as GQL from "src/core/generated-graphql";
import { ImageCard } from "./ImageCard";
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
import {
useCardWidth,
useContainerDimensions,
} from "../Shared/GridCard/GridCard";
interface IImageCardGrid {
images: GQL.SlimImageDataFragment[];
@ -11,6 +14,8 @@ interface IImageCardGrid {
onPreview: (index: number, ev: React.MouseEvent<Element, MouseEvent>) => void;
}
const zoomWidths = [280, 340, 480, 640];
export const ImageGridCard: React.FC<IImageCardGrid> = ({
images,
selectedIds,
@ -18,13 +23,15 @@ export const ImageGridCard: React.FC<IImageCardGrid> = ({
onSelectChange,
onPreview,
}) => {
const [componentRef, { width }] = useContainerDimensions();
const [componentRef, { width: containerWidth }] = useContainerDimensions();
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
return (
<div className="row justify-content-center" ref={componentRef}>
{images.map((image, index) => (
<ImageCard
key={image.id}
containerWidth={width}
cardWidth={cardWidth}
image={image}
zoomIndex={zoomIndex}
selecting={selectedIds.size > 0}

View file

@ -1,10 +1,10 @@
import React, { useEffect, useState } from "react";
import React from "react";
import { Link } from "react-router-dom";
import { useIntl } from "react-intl";
import * as GQL from "src/core/generated-graphql";
import NavUtils from "src/utils/navigation";
import TextUtils from "src/utils/text";
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
import { GridCard } from "../Shared/GridCard/GridCard";
import { CountryFlag } from "../Shared/CountryFlag";
import { SweatDrops } from "../Shared/SweatDrops";
import { HoverPopover } from "../Shared/HoverPopover";
@ -21,7 +21,6 @@ import { faTag } from "@fortawesome/free-solid-svg-icons";
import { RatingBanner } from "../Shared/RatingBanner";
import { usePerformerUpdate } from "src/core/StashService";
import { ILabeledId } from "src/models/list-filter/types";
import ScreenUtils from "src/utils/screen";
import { FavoriteIcon } from "../Shared/FavoriteIcon";
import { PatchComponent } from "src/patch";
@ -35,7 +34,7 @@ export interface IPerformerCardExtraCriteria {
interface IPerformerCardProps {
performer: GQL.PerformerDataFragment;
containerWidth?: number;
cardWidth?: number;
ageFromDate?: string;
selecting?: boolean;
selected?: boolean;
@ -300,41 +299,13 @@ export const PerformerCard: React.FC<IPerformerCardProps> = PatchComponent(
(props) => {
const {
performer,
containerWidth,
cardWidth,
selecting,
selected,
onSelectedChanged,
zoomIndex,
} = props;
const [cardWidth, setCardWidth] = useState<number>();
useEffect(() => {
if (!containerWidth || zoomIndex === undefined || ScreenUtils.isMobile())
return;
let zoomValue = zoomIndex;
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 240;
break;
case 1:
preferredCardWidth = 300;
break;
case 2:
preferredCardWidth = 375;
break;
case 3:
preferredCardWidth = 470;
}
let fittedCardWidth = calculateCardWidth(
containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [containerWidth, zoomIndex]);
return (
<GridCard
className={`performer-card zoom-${zoomIndex}`}

View file

@ -1,7 +1,10 @@
import React from "react";
import * as GQL from "src/core/generated-graphql";
import { IPerformerCardExtraCriteria, PerformerCard } from "./PerformerCard";
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
import {
useCardWidth,
useContainerDimensions,
} from "../Shared/GridCard/GridCard";
interface IPerformerCardGrid {
performers: GQL.PerformerDataFragment[];
@ -11,6 +14,8 @@ interface IPerformerCardGrid {
extraCriteria?: IPerformerCardExtraCriteria;
}
const zoomWidths = [240, 300, 375, 470];
export const PerformerCardGrid: React.FC<IPerformerCardGrid> = ({
performers,
selectedIds,
@ -18,13 +23,15 @@ export const PerformerCardGrid: React.FC<IPerformerCardGrid> = ({
onSelectChange,
extraCriteria,
}) => {
const [componentRef, { width }] = useContainerDimensions();
const [componentRef, { width: containerWidth }] = useContainerDimensions();
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
return (
<div className="row justify-content-center" ref={componentRef}>
{performers.map((p) => (
<PerformerCard
key={p.id}
containerWidth={width}
cardWidth={cardWidth}
performer={p}
zoomIndex={zoomIndex}
selecting={selectedIds.size > 0}

View file

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef } from "react";
import { Button, ButtonGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
import { useHistory } from "react-router-dom";
import cx from "classnames";
@ -13,7 +13,7 @@ import TextUtils from "src/utils/text";
import { SceneQueue } from "src/models/sceneQueue";
import { ConfigurationContext } from "src/hooks/Config";
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
import { GridCard } from "../Shared/GridCard/GridCard";
import { RatingBanner } from "../Shared/RatingBanner";
import { FormattedMessage } from "react-intl";
import {
@ -27,7 +27,6 @@ import {
import { objectPath, objectTitle } from "src/core/files";
import { PreviewScrubber } from "./PreviewScrubber";
import { PatchComponent } from "src/patch";
import ScreenUtils from "src/utils/screen";
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
import { GroupTag } from "../Groups/GroupTag";
import { FileSize } from "../Shared/FileSize";
@ -94,7 +93,7 @@ export const ScenePreview: React.FC<IScenePreviewProps> = ({
interface ISceneCardProps {
scene: GQL.SlimSceneDataFragment;
containerWidth?: number;
width?: number;
previewHeight?: number;
index?: number;
queue?: SceneQueue;
@ -439,7 +438,6 @@ export const SceneCard = PatchComponent(
"SceneCard",
(props: ISceneCardProps) => {
const { configuration } = React.useContext(ConfigurationContext);
const [cardWidth, setCardWidth] = useState<number>();
const file = useMemo(
() => (props.scene.files.length > 0 ? props.scene.files[0] : undefined),
@ -462,36 +460,6 @@ export const SceneCard = PatchComponent(
return "";
}
useEffect(() => {
if (
!props.containerWidth ||
props.zoomIndex === undefined ||
ScreenUtils.isMobile()
)
return;
let zoomValue = props.zoomIndex;
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 280;
break;
case 1:
preferredCardWidth = 340; // this value is intentionally higher than 320
break;
case 2:
preferredCardWidth = 480;
break;
case 3:
preferredCardWidth = 640;
}
let fittedCardWidth = calculateCardWidth(
props.containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [props.containerWidth, props.zoomIndex]);
const cont = configuration?.interface.continuePlaylistDefault ?? false;
const sceneLink = props.queue
@ -506,7 +474,7 @@ export const SceneCard = PatchComponent(
className={`scene-card ${zoomIndex()} ${filelessClass()}`}
url={sceneLink}
title={objectTitle(props.scene)}
width={cardWidth}
width={props.width}
linkClassName="scene-card-link"
thumbnailSectionClassName="video-section"
resumeTime={props.scene.resume_time ?? undefined}

View file

@ -2,7 +2,10 @@ import React from "react";
import * as GQL from "src/core/generated-graphql";
import { SceneQueue } from "src/models/sceneQueue";
import { SceneCard } from "./SceneCard";
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
import {
useCardWidth,
useContainerDimensions,
} from "../Shared/GridCard/GridCard";
interface ISceneCardsGrid {
scenes: GQL.SlimSceneDataFragment[];
@ -13,6 +16,8 @@ interface ISceneCardsGrid {
fromGroupId?: string;
}
const zoomWidths = [280, 340, 480, 640];
export const SceneCardsGrid: React.FC<ISceneCardsGrid> = ({
scenes,
queue,
@ -21,13 +26,16 @@ export const SceneCardsGrid: React.FC<ISceneCardsGrid> = ({
onSelectChange,
fromGroupId,
}) => {
const [componentRef, { width }] = useContainerDimensions();
const [componentRef, { width: containerWidth }] = useContainerDimensions();
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
return (
<div className="row justify-content-center" ref={componentRef}>
{scenes.map((scene, index) => (
<SceneCard
key={scene.id}
containerWidth={width}
width={cardWidth}
scene={scene}
queue={queue}
index={index}

View file

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useMemo } from "react";
import { Button, ButtonGroup } from "react-bootstrap";
import * as GQL from "src/core/generated-graphql";
import { Icon } from "../Shared/Icon";
@ -7,9 +7,8 @@ import { HoverPopover } from "../Shared/HoverPopover";
import NavUtils from "src/utils/navigation";
import TextUtils from "src/utils/text";
import { ConfigurationContext } from "src/hooks/Config";
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
import { GridCard } from "../Shared/GridCard/GridCard";
import { faTag } from "@fortawesome/free-solid-svg-icons";
import ScreenUtils from "src/utils/screen";
import { markerTitle } from "src/core/markers";
import { Link } from "react-router-dom";
import { objectTitle } from "src/core/files";
@ -19,7 +18,7 @@ import { TruncatedText } from "../Shared/TruncatedText";
interface ISceneMarkerCardProps {
marker: GQL.SceneMarkerDataFragment;
containerWidth?: number;
cardWidth?: number;
previewHeight?: number;
index?: number;
compact?: boolean;
@ -154,8 +153,6 @@ const SceneMarkerCardImage = (props: ISceneMarkerCardProps) => {
};
export const SceneMarkerCard = (props: ISceneMarkerCardProps) => {
const [cardWidth, setCardWidth] = useState<number>();
function zoomIndex() {
if (!props.compact && props.zoomIndex !== undefined) {
return `zoom-${props.zoomIndex}`;
@ -164,42 +161,12 @@ export const SceneMarkerCard = (props: ISceneMarkerCardProps) => {
return "";
}
useEffect(() => {
if (
!props.containerWidth ||
props.zoomIndex === undefined ||
ScreenUtils.isMobile()
)
return;
let zoomValue = props.zoomIndex;
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 240;
break;
case 1:
preferredCardWidth = 340; // this value is intentionally higher than 320
break;
case 2:
preferredCardWidth = 480;
break;
case 3:
preferredCardWidth = 640;
}
let fittedCardWidth = calculateCardWidth(
props.containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [props, props.containerWidth, props.zoomIndex]);
return (
<GridCard
className={`scene-marker-card ${zoomIndex()}`}
url={NavUtils.makeSceneMarkerUrl(props.marker)}
title={markerTitle(props.marker)}
width={cardWidth}
width={props.cardWidth}
linkClassName="scene-marker-card-link"
thumbnailSectionClassName="video-section"
resumeTime={props.marker.seconds}

View file

@ -1,7 +1,10 @@
import React from "react";
import * as GQL from "src/core/generated-graphql";
import { SceneMarkerCard } from "./SceneMarkerCard";
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
import {
useCardWidth,
useContainerDimensions,
} from "../Shared/GridCard/GridCard";
interface ISceneMarkerCardsGrid {
markers: GQL.SceneMarkerDataFragment[];
@ -10,19 +13,23 @@ interface ISceneMarkerCardsGrid {
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
}
const zoomWidths = [240, 340, 480, 640];
export const SceneMarkerCardsGrid: React.FC<ISceneMarkerCardsGrid> = ({
markers,
selectedIds,
zoomIndex,
onSelectChange,
}) => {
const [componentRef, { width }] = useContainerDimensions();
const [componentRef, { width: containerWidth }] = useContainerDimensions();
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
return (
<div className="row justify-content-center" ref={componentRef}>
{markers.map((marker, index) => (
<SceneMarkerCard
key={marker.id}
containerWidth={width}
cardWidth={cardWidth}
marker={marker}
index={index}
zoomIndex={zoomIndex}

View file

@ -1,6 +1,7 @@
import React, {
MutableRefObject,
PropsWithChildren,
useMemo,
useRef,
useState,
} from "react";
@ -13,6 +14,7 @@ import useResizeObserver from "@react-hook/resize-observer";
import { Icon } from "../Icon";
import { faGripLines } from "@fortawesome/free-solid-svg-icons";
import { DragSide, useDragMoveSelect } from "./dragMoveSelect";
import { useDebounce } from "src/hooks/debounce";
interface ICardProps {
className?: string;
@ -63,7 +65,7 @@ export const useContainerDimensions = <T extends HTMLElement = HTMLDivElement>(
height: 0,
});
useResizeObserver(target, (entry) => {
const debouncedSetDimension = useDebounce((entry: ResizeObserverEntry) => {
const { inlineSize: width, blockSize: height } = entry.contentBoxSize[0];
let difference = Math.abs(dimension.width - width);
// Only adjust when width changed by a significant margin. This addresses the cornercase that sees
@ -73,11 +75,38 @@ export const useContainerDimensions = <T extends HTMLElement = HTMLDivElement>(
if (difference > sensitivityThreshold) {
setDimension({ width, height });
}
});
}, 50);
useResizeObserver(target, debouncedSetDimension);
return [target, dimension];
};
export function useCardWidth(
containerWidth: number,
zoomIndex: number,
zoomWidths: number[]
) {
return useMemo(() => {
if (
!containerWidth ||
zoomIndex === undefined ||
zoomIndex < 0 ||
zoomIndex >= zoomWidths.length ||
ScreenUtils.isMobile()
)
return;
let zoomValue = zoomIndex;
const preferredCardWidth = zoomWidths[zoomValue];
let fittedCardWidth = calculateCardWidth(
containerWidth,
preferredCardWidth!
);
return fittedCardWidth;
}, [containerWidth, zoomIndex, zoomWidths]);
}
const Checkbox: React.FC<{
selected?: boolean;
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;

View file

@ -1,11 +1,8 @@
import React, { useEffect, useState } from "react";
import React from "react";
import { Link } from "react-router-dom";
import * as GQL from "src/core/generated-graphql";
import NavUtils from "src/utils/navigation";
import {
GridCard,
calculateCardWidth,
} from "src/components/Shared/GridCard/GridCard";
import { GridCard } from "src/components/Shared/GridCard/GridCard";
import { HoverPopover } from "../Shared/HoverPopover";
import { Icon } from "../Shared/Icon";
import { TagLink } from "../Shared/TagLink";
@ -13,14 +10,13 @@ import { Button, ButtonGroup } from "react-bootstrap";
import { FormattedMessage } from "react-intl";
import { PopoverCountButton } from "../Shared/PopoverCountButton";
import { RatingBanner } from "../Shared/RatingBanner";
import ScreenUtils from "src/utils/screen";
import { FavoriteIcon } from "../Shared/FavoriteIcon";
import { useStudioUpdate } from "src/core/StashService";
import { faTag } from "@fortawesome/free-solid-svg-icons";
interface IProps {
studio: GQL.StudioDataFragment;
containerWidth?: number;
cardWidth?: number;
hideParent?: boolean;
selecting?: boolean;
selected?: boolean;
@ -75,7 +71,7 @@ function maybeRenderChildren(studio: GQL.StudioDataFragment) {
export const StudioCard: React.FC<IProps> = ({
studio,
containerWidth,
cardWidth,
hideParent,
selecting,
selected,
@ -83,34 +79,6 @@ export const StudioCard: React.FC<IProps> = ({
onSelectedChanged,
}) => {
const [updateStudio] = useStudioUpdate();
const [cardWidth, setCardWidth] = useState<number>();
useEffect(() => {
if (!containerWidth || zoomIndex === undefined || ScreenUtils.isMobile())
return;
let zoomValue = zoomIndex;
console.log(zoomValue);
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 280;
break;
case 1:
preferredCardWidth = 340;
break;
case 2:
preferredCardWidth = 420;
break;
case 3:
preferredCardWidth = 560;
}
let fittedCardWidth = calculateCardWidth(
containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [containerWidth, zoomIndex]);
function onToggleFavorite(v: boolean) {
if (studio.id) {

View file

@ -1,6 +1,9 @@
import React from "react";
import * as GQL from "src/core/generated-graphql";
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
import {
useCardWidth,
useContainerDimensions,
} from "../Shared/GridCard/GridCard";
import { StudioCard } from "./StudioCard";
interface IStudioCardGrid {
@ -11,6 +14,8 @@ interface IStudioCardGrid {
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
}
const zoomWidths = [280, 340, 420, 560];
export const StudioCardGrid: React.FC<IStudioCardGrid> = ({
studios,
fromParent,
@ -18,13 +23,15 @@ export const StudioCardGrid: React.FC<IStudioCardGrid> = ({
zoomIndex,
onSelectChange,
}) => {
const [componentRef, { width }] = useContainerDimensions();
const [componentRef, { width: containerWidth }] = useContainerDimensions();
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
return (
<div className="row justify-content-center" ref={componentRef}>
{studios.map((studio) => (
<StudioCard
key={studio.id}
containerWidth={width}
cardWidth={cardWidth}
studio={studio}
zoomIndex={zoomIndex}
hideParent={fromParent}

View file

@ -1,14 +1,13 @@
import { PatchComponent } from "src/patch";
import { Button, ButtonGroup } from "react-bootstrap";
import React, { useEffect, useState } from "react";
import React from "react";
import { Link } from "react-router-dom";
import * as GQL from "src/core/generated-graphql";
import NavUtils from "src/utils/navigation";
import { FormattedMessage } from "react-intl";
import { TruncatedText } from "../Shared/TruncatedText";
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
import { GridCard } from "../Shared/GridCard/GridCard";
import { PopoverCountButton } from "../Shared/PopoverCountButton";
import ScreenUtils from "src/utils/screen";
import { Icon } from "../Shared/Icon";
import { faHeart } from "@fortawesome/free-solid-svg-icons";
import cx from "classnames";
@ -16,7 +15,7 @@ import { useTagUpdate } from "src/core/StashService";
interface IProps {
tag: GQL.TagDataFragment;
containerWidth?: number;
cardWidth?: number;
zoomIndex: number;
selecting?: boolean;
selected?: boolean;
@ -234,40 +233,8 @@ const TagCardTitle: React.FC<IProps> = PatchComponent(
);
export const TagCard: React.FC<IProps> = PatchComponent("TagCard", (props) => {
const {
tag,
containerWidth,
zoomIndex,
selecting,
selected,
onSelectedChanged,
} = props;
const [cardWidth, setCardWidth] = useState<number>();
useEffect(() => {
if (!containerWidth || zoomIndex === undefined || ScreenUtils.isMobile())
return;
let zoomValue = zoomIndex;
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 280;
break;
case 1:
preferredCardWidth = 340;
break;
case 2:
preferredCardWidth = 480;
break;
case 3:
preferredCardWidth = 640;
}
let fittedCardWidth = calculateCardWidth(
containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [containerWidth, zoomIndex]);
const { tag, cardWidth, zoomIndex, selecting, selected, onSelectedChanged } =
props;
return (
<GridCard

View file

@ -1,6 +1,9 @@
import React from "react";
import * as GQL from "src/core/generated-graphql";
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
import {
useCardWidth,
useContainerDimensions,
} from "../Shared/GridCard/GridCard";
import { TagCard } from "./TagCard";
interface ITagCardGrid {
@ -10,19 +13,23 @@ interface ITagCardGrid {
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
}
const zoomWidths = [280, 340, 480, 640];
export const TagCardGrid: React.FC<ITagCardGrid> = ({
tags,
selectedIds,
zoomIndex,
onSelectChange,
}) => {
const [componentRef, { width }] = useContainerDimensions();
const [componentRef, { width: containerWidth }] = useContainerDimensions();
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
return (
<div className="row justify-content-center" ref={componentRef}>
{tags.map((tag) => (
<TagCard
key={tag.id}
containerWidth={width}
cardWidth={cardWidth}
tag={tag}
zoomIndex={zoomIndex}
selecting={selectedIds.size > 0}