mirror of
https://github.com/stashapp/stash.git
synced 2025-12-07 17:02:38 +01:00
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:
parent
2541e9d1eb
commit
5d3d02e1e7
17 changed files with 145 additions and 315 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
import { Button, ButtonGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
|
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 * 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 { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { SceneLink, TagLink } from "../Shared/TagLink";
|
import { SceneLink, TagLink } from "../Shared/TagLink";
|
||||||
|
|
@ -12,7 +12,6 @@ import NavUtils from "src/utils/navigation";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
import { faBox, faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
|
import { faBox, faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { galleryTitle } from "src/core/galleries";
|
import { galleryTitle } from "src/core/galleries";
|
||||||
import ScreenUtils from "src/utils/screen";
|
|
||||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||||
import { GalleryPreviewScrubber } from "./GalleryPreviewScrubber";
|
import { GalleryPreviewScrubber } from "./GalleryPreviewScrubber";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
|
@ -56,7 +55,7 @@ export const GalleryPreview: React.FC<IGalleryPreviewProps> = ({
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
gallery: GQL.SlimGalleryDataFragment;
|
gallery: GQL.SlimGalleryDataFragment;
|
||||||
containerWidth?: number;
|
cardWidth?: number;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
selected?: boolean | undefined;
|
selected?: boolean | undefined;
|
||||||
zoomIndex?: number;
|
zoomIndex?: number;
|
||||||
|
|
@ -65,37 +64,6 @@ interface IProps {
|
||||||
|
|
||||||
export const GalleryCard: React.FC<IProps> = (props) => {
|
export const GalleryCard: React.FC<IProps> = (props) => {
|
||||||
const history = useHistory();
|
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() {
|
function maybeRenderScenePopoverButton() {
|
||||||
if (props.gallery.scenes.length === 0) return;
|
if (props.gallery.scenes.length === 0) return;
|
||||||
|
|
@ -207,7 +175,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
||||||
<GridCard
|
<GridCard
|
||||||
className={`gallery-card zoom-${props.zoomIndex}`}
|
className={`gallery-card zoom-${props.zoomIndex}`}
|
||||||
url={`/galleries/${props.gallery.id}`}
|
url={`/galleries/${props.gallery.id}`}
|
||||||
width={cardWidth}
|
width={props.cardWidth}
|
||||||
title={galleryTitle(props.gallery)}
|
title={galleryTitle(props.gallery)}
|
||||||
linkClassName="gallery-card-header"
|
linkClassName="gallery-card-header"
|
||||||
image={
|
image={
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { GalleryCard } from "./GalleryCard";
|
import { GalleryCard } from "./GalleryCard";
|
||||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
import {
|
||||||
|
useCardWidth,
|
||||||
|
useContainerDimensions,
|
||||||
|
} from "../Shared/GridCard/GridCard";
|
||||||
|
|
||||||
interface IGalleryCardGrid {
|
interface IGalleryCardGrid {
|
||||||
galleries: GQL.SlimGalleryDataFragment[];
|
galleries: GQL.SlimGalleryDataFragment[];
|
||||||
|
|
@ -10,19 +13,23 @@ interface IGalleryCardGrid {
|
||||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomWidths = [280, 340, 480, 640];
|
||||||
|
|
||||||
export const GalleryCardGrid: React.FC<IGalleryCardGrid> = ({
|
export const GalleryCardGrid: React.FC<IGalleryCardGrid> = ({
|
||||||
galleries,
|
galleries,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
zoomIndex,
|
zoomIndex,
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [componentRef, { width }] = useContainerDimensions();
|
const [componentRef, { width: containerWidth }] = useContainerDimensions();
|
||||||
|
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center" ref={componentRef}>
|
<div className="row justify-content-center" ref={componentRef}>
|
||||||
{galleries.map((gallery) => (
|
{galleries.map((gallery) => (
|
||||||
<GalleryCard
|
<GalleryCard
|
||||||
key={gallery.id}
|
key={gallery.id}
|
||||||
containerWidth={width}
|
cardWidth={cardWidth}
|
||||||
gallery={gallery}
|
gallery={gallery}
|
||||||
zoomIndex={zoomIndex}
|
zoomIndex={zoomIndex}
|
||||||
selecting={selectedIds.size > 0}
|
selecting={selectedIds.size > 0}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { Button, ButtonGroup } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
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 { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { SceneLink, TagLink } from "../Shared/TagLink";
|
import { SceneLink, TagLink } from "../Shared/TagLink";
|
||||||
|
|
@ -9,7 +9,6 @@ import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
import { faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
|
import { faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
|
||||||
import ScreenUtils from "src/utils/screen";
|
|
||||||
import { RelatedGroupPopoverButton } from "./RelatedGroupPopover";
|
import { RelatedGroupPopoverButton } from "./RelatedGroupPopover";
|
||||||
|
|
||||||
const Description: React.FC<{
|
const Description: React.FC<{
|
||||||
|
|
@ -37,7 +36,7 @@ const Description: React.FC<{
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
group: GQL.GroupDataFragment;
|
group: GQL.GroupDataFragment;
|
||||||
containerWidth?: number;
|
cardWidth?: number;
|
||||||
sceneNumber?: number;
|
sceneNumber?: number;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
|
@ -50,7 +49,7 @@ interface IProps {
|
||||||
export const GroupCard: React.FC<IProps> = ({
|
export const GroupCard: React.FC<IProps> = ({
|
||||||
group,
|
group,
|
||||||
sceneNumber,
|
sceneNumber,
|
||||||
containerWidth,
|
cardWidth,
|
||||||
selecting,
|
selecting,
|
||||||
selected,
|
selected,
|
||||||
zoomIndex,
|
zoomIndex,
|
||||||
|
|
@ -58,8 +57,6 @@ export const GroupCard: React.FC<IProps> = ({
|
||||||
fromGroupId,
|
fromGroupId,
|
||||||
onMove,
|
onMove,
|
||||||
}) => {
|
}) => {
|
||||||
const [cardWidth, setCardWidth] = useState<number>();
|
|
||||||
|
|
||||||
const groupDescription = useMemo(() => {
|
const groupDescription = useMemo(() => {
|
||||||
if (!fromGroupId) {
|
if (!fromGroupId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
@ -72,32 +69,6 @@ export const GroupCard: React.FC<IProps> = ({
|
||||||
return containingGroup?.description ?? undefined;
|
return containingGroup?.description ?? undefined;
|
||||||
}, [fromGroupId, group.containing_groups]);
|
}, [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() {
|
function maybeRenderScenesPopoverButton() {
|
||||||
if (group.scenes.length === 0) return;
|
if (group.scenes.length === 0) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { GroupCard } from "./GroupCard";
|
import { GroupCard } from "./GroupCard";
|
||||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
import {
|
||||||
|
useCardWidth,
|
||||||
|
useContainerDimensions,
|
||||||
|
} from "../Shared/GridCard/GridCard";
|
||||||
|
|
||||||
interface IGroupCardGrid {
|
interface IGroupCardGrid {
|
||||||
groups: GQL.GroupDataFragment[];
|
groups: GQL.GroupDataFragment[];
|
||||||
|
|
@ -12,6 +15,8 @@ interface IGroupCardGrid {
|
||||||
onMove?: (srcIds: string[], targetId: string, after: boolean) => void;
|
onMove?: (srcIds: string[], targetId: string, after: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomWidths = [210, 250, 300, 375];
|
||||||
|
|
||||||
export const GroupCardGrid: React.FC<IGroupCardGrid> = ({
|
export const GroupCardGrid: React.FC<IGroupCardGrid> = ({
|
||||||
groups,
|
groups,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
|
|
@ -20,13 +25,15 @@ export const GroupCardGrid: React.FC<IGroupCardGrid> = ({
|
||||||
fromGroupId,
|
fromGroupId,
|
||||||
onMove,
|
onMove,
|
||||||
}) => {
|
}) => {
|
||||||
const [componentRef, { width }] = useContainerDimensions();
|
const [componentRef, { width: containerWidth }] = useContainerDimensions();
|
||||||
|
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center" ref={componentRef}>
|
<div className="row justify-content-center" ref={componentRef}>
|
||||||
{groups.map((p) => (
|
{groups.map((p) => (
|
||||||
<GroupCard
|
<GroupCard
|
||||||
key={p.id}
|
key={p.id}
|
||||||
containerWidth={width}
|
cardWidth={cardWidth}
|
||||||
group={p}
|
group={p}
|
||||||
zoomIndex={zoomIndex}
|
zoomIndex={zoomIndex}
|
||||||
selecting={selectedIds.size > 0}
|
selecting={selectedIds.size > 0}
|
||||||
|
|
|
||||||
|
|
@ -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 { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
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 { HoverPopover } from "src/components/Shared/HoverPopover";
|
||||||
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
||||||
import { PerformerPopoverButton } from "src/components/Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "src/components/Shared/PerformerPopoverButton";
|
||||||
import {
|
import { GridCard } from "src/components/Shared/GridCard/GridCard";
|
||||||
GridCard,
|
|
||||||
calculateCardWidth,
|
|
||||||
} from "src/components/Shared/GridCard/GridCard";
|
|
||||||
import { RatingBanner } from "src/components/Shared/RatingBanner";
|
import { RatingBanner } from "src/components/Shared/RatingBanner";
|
||||||
import {
|
import {
|
||||||
faBox,
|
faBox,
|
||||||
|
|
@ -20,12 +17,11 @@ import {
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { imageTitle } from "src/core/files";
|
import { imageTitle } from "src/core/files";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
import ScreenUtils from "src/utils/screen";
|
|
||||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||||
|
|
||||||
interface IImageCardProps {
|
interface IImageCardProps {
|
||||||
image: GQL.SlimImageDataFragment;
|
image: GQL.SlimImageDataFragment;
|
||||||
containerWidth?: number;
|
cardWidth?: number;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
selected?: boolean | undefined;
|
selected?: boolean | undefined;
|
||||||
zoomIndex: number;
|
zoomIndex: number;
|
||||||
|
|
@ -36,38 +32,6 @@ interface IImageCardProps {
|
||||||
export const ImageCard: React.FC<IImageCardProps> = (
|
export const ImageCard: React.FC<IImageCardProps> = (
|
||||||
props: 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(
|
const file = useMemo(
|
||||||
() =>
|
() =>
|
||||||
props.image.visual_files.length > 0
|
props.image.visual_files.length > 0
|
||||||
|
|
@ -196,7 +160,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||||
<GridCard
|
<GridCard
|
||||||
className={`image-card zoom-${props.zoomIndex}`}
|
className={`image-card zoom-${props.zoomIndex}`}
|
||||||
url={`/images/${props.image.id}`}
|
url={`/images/${props.image.id}`}
|
||||||
width={cardWidth}
|
width={props.cardWidth}
|
||||||
title={imageTitle(props.image)}
|
title={imageTitle(props.image)}
|
||||||
linkClassName="image-card-link"
|
linkClassName="image-card-link"
|
||||||
image={
|
image={
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { ImageCard } from "./ImageCard";
|
import { ImageCard } from "./ImageCard";
|
||||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
import {
|
||||||
|
useCardWidth,
|
||||||
|
useContainerDimensions,
|
||||||
|
} from "../Shared/GridCard/GridCard";
|
||||||
|
|
||||||
interface IImageCardGrid {
|
interface IImageCardGrid {
|
||||||
images: GQL.SlimImageDataFragment[];
|
images: GQL.SlimImageDataFragment[];
|
||||||
|
|
@ -11,6 +14,8 @@ interface IImageCardGrid {
|
||||||
onPreview: (index: number, ev: React.MouseEvent<Element, MouseEvent>) => void;
|
onPreview: (index: number, ev: React.MouseEvent<Element, MouseEvent>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomWidths = [280, 340, 480, 640];
|
||||||
|
|
||||||
export const ImageGridCard: React.FC<IImageCardGrid> = ({
|
export const ImageGridCard: React.FC<IImageCardGrid> = ({
|
||||||
images,
|
images,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
|
|
@ -18,13 +23,15 @@ export const ImageGridCard: React.FC<IImageCardGrid> = ({
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
onPreview,
|
onPreview,
|
||||||
}) => {
|
}) => {
|
||||||
const [componentRef, { width }] = useContainerDimensions();
|
const [componentRef, { width: containerWidth }] = useContainerDimensions();
|
||||||
|
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center" ref={componentRef}>
|
<div className="row justify-content-center" ref={componentRef}>
|
||||||
{images.map((image, index) => (
|
{images.map((image, index) => (
|
||||||
<ImageCard
|
<ImageCard
|
||||||
key={image.id}
|
key={image.id}
|
||||||
containerWidth={width}
|
cardWidth={cardWidth}
|
||||||
image={image}
|
image={image}
|
||||||
zoomIndex={zoomIndex}
|
zoomIndex={zoomIndex}
|
||||||
selecting={selectedIds.size > 0}
|
selecting={selectedIds.size > 0}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import NavUtils from "src/utils/navigation";
|
import NavUtils from "src/utils/navigation";
|
||||||
import TextUtils from "src/utils/text";
|
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 { CountryFlag } from "../Shared/CountryFlag";
|
||||||
import { SweatDrops } from "../Shared/SweatDrops";
|
import { SweatDrops } from "../Shared/SweatDrops";
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
|
|
@ -21,7 +21,6 @@ import { faTag } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
import { usePerformerUpdate } from "src/core/StashService";
|
import { usePerformerUpdate } from "src/core/StashService";
|
||||||
import { ILabeledId } from "src/models/list-filter/types";
|
import { ILabeledId } from "src/models/list-filter/types";
|
||||||
import ScreenUtils from "src/utils/screen";
|
|
||||||
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent } from "src/patch";
|
||||||
|
|
||||||
|
|
@ -35,7 +34,7 @@ export interface IPerformerCardExtraCriteria {
|
||||||
|
|
||||||
interface IPerformerCardProps {
|
interface IPerformerCardProps {
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
containerWidth?: number;
|
cardWidth?: number;
|
||||||
ageFromDate?: string;
|
ageFromDate?: string;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
|
@ -300,41 +299,13 @@ export const PerformerCard: React.FC<IPerformerCardProps> = PatchComponent(
|
||||||
(props) => {
|
(props) => {
|
||||||
const {
|
const {
|
||||||
performer,
|
performer,
|
||||||
containerWidth,
|
cardWidth,
|
||||||
selecting,
|
selecting,
|
||||||
selected,
|
selected,
|
||||||
onSelectedChanged,
|
onSelectedChanged,
|
||||||
zoomIndex,
|
zoomIndex,
|
||||||
} = props;
|
} = 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 (
|
return (
|
||||||
<GridCard
|
<GridCard
|
||||||
className={`performer-card zoom-${zoomIndex}`}
|
className={`performer-card zoom-${zoomIndex}`}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { IPerformerCardExtraCriteria, PerformerCard } from "./PerformerCard";
|
import { IPerformerCardExtraCriteria, PerformerCard } from "./PerformerCard";
|
||||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
import {
|
||||||
|
useCardWidth,
|
||||||
|
useContainerDimensions,
|
||||||
|
} from "../Shared/GridCard/GridCard";
|
||||||
|
|
||||||
interface IPerformerCardGrid {
|
interface IPerformerCardGrid {
|
||||||
performers: GQL.PerformerDataFragment[];
|
performers: GQL.PerformerDataFragment[];
|
||||||
|
|
@ -11,6 +14,8 @@ interface IPerformerCardGrid {
|
||||||
extraCriteria?: IPerformerCardExtraCriteria;
|
extraCriteria?: IPerformerCardExtraCriteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomWidths = [240, 300, 375, 470];
|
||||||
|
|
||||||
export const PerformerCardGrid: React.FC<IPerformerCardGrid> = ({
|
export const PerformerCardGrid: React.FC<IPerformerCardGrid> = ({
|
||||||
performers,
|
performers,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
|
|
@ -18,13 +23,15 @@ export const PerformerCardGrid: React.FC<IPerformerCardGrid> = ({
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
extraCriteria,
|
extraCriteria,
|
||||||
}) => {
|
}) => {
|
||||||
const [componentRef, { width }] = useContainerDimensions();
|
const [componentRef, { width: containerWidth }] = useContainerDimensions();
|
||||||
|
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center" ref={componentRef}>
|
<div className="row justify-content-center" ref={componentRef}>
|
||||||
{performers.map((p) => (
|
{performers.map((p) => (
|
||||||
<PerformerCard
|
<PerformerCard
|
||||||
key={p.id}
|
key={p.id}
|
||||||
containerWidth={width}
|
cardWidth={cardWidth}
|
||||||
performer={p}
|
performer={p}
|
||||||
zoomIndex={zoomIndex}
|
zoomIndex={zoomIndex}
|
||||||
selecting={selectedIds.size > 0}
|
selecting={selectedIds.size > 0}
|
||||||
|
|
|
||||||
|
|
@ -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 { Button, ButtonGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
|
@ -13,7 +13,7 @@ import TextUtils from "src/utils/text";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
|
import { GridCard } from "../Shared/GridCard/GridCard";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import {
|
import {
|
||||||
|
|
@ -27,7 +27,6 @@ import {
|
||||||
import { objectPath, objectTitle } from "src/core/files";
|
import { objectPath, objectTitle } from "src/core/files";
|
||||||
import { PreviewScrubber } from "./PreviewScrubber";
|
import { PreviewScrubber } from "./PreviewScrubber";
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent } from "src/patch";
|
||||||
import ScreenUtils from "src/utils/screen";
|
|
||||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||||
import { GroupTag } from "../Groups/GroupTag";
|
import { GroupTag } from "../Groups/GroupTag";
|
||||||
import { FileSize } from "../Shared/FileSize";
|
import { FileSize } from "../Shared/FileSize";
|
||||||
|
|
@ -94,7 +93,7 @@ export const ScenePreview: React.FC<IScenePreviewProps> = ({
|
||||||
|
|
||||||
interface ISceneCardProps {
|
interface ISceneCardProps {
|
||||||
scene: GQL.SlimSceneDataFragment;
|
scene: GQL.SlimSceneDataFragment;
|
||||||
containerWidth?: number;
|
width?: number;
|
||||||
previewHeight?: number;
|
previewHeight?: number;
|
||||||
index?: number;
|
index?: number;
|
||||||
queue?: SceneQueue;
|
queue?: SceneQueue;
|
||||||
|
|
@ -439,7 +438,6 @@ export const SceneCard = PatchComponent(
|
||||||
"SceneCard",
|
"SceneCard",
|
||||||
(props: ISceneCardProps) => {
|
(props: ISceneCardProps) => {
|
||||||
const { configuration } = React.useContext(ConfigurationContext);
|
const { configuration } = React.useContext(ConfigurationContext);
|
||||||
const [cardWidth, setCardWidth] = useState<number>();
|
|
||||||
|
|
||||||
const file = useMemo(
|
const file = useMemo(
|
||||||
() => (props.scene.files.length > 0 ? props.scene.files[0] : undefined),
|
() => (props.scene.files.length > 0 ? props.scene.files[0] : undefined),
|
||||||
|
|
@ -462,36 +460,6 @@ export const SceneCard = PatchComponent(
|
||||||
return "";
|
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 cont = configuration?.interface.continuePlaylistDefault ?? false;
|
||||||
|
|
||||||
const sceneLink = props.queue
|
const sceneLink = props.queue
|
||||||
|
|
@ -506,7 +474,7 @@ export const SceneCard = PatchComponent(
|
||||||
className={`scene-card ${zoomIndex()} ${filelessClass()}`}
|
className={`scene-card ${zoomIndex()} ${filelessClass()}`}
|
||||||
url={sceneLink}
|
url={sceneLink}
|
||||||
title={objectTitle(props.scene)}
|
title={objectTitle(props.scene)}
|
||||||
width={cardWidth}
|
width={props.width}
|
||||||
linkClassName="scene-card-link"
|
linkClassName="scene-card-link"
|
||||||
thumbnailSectionClassName="video-section"
|
thumbnailSectionClassName="video-section"
|
||||||
resumeTime={props.scene.resume_time ?? undefined}
|
resumeTime={props.scene.resume_time ?? undefined}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { SceneQueue } from "src/models/sceneQueue";
|
import { SceneQueue } from "src/models/sceneQueue";
|
||||||
import { SceneCard } from "./SceneCard";
|
import { SceneCard } from "./SceneCard";
|
||||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
import {
|
||||||
|
useCardWidth,
|
||||||
|
useContainerDimensions,
|
||||||
|
} from "../Shared/GridCard/GridCard";
|
||||||
|
|
||||||
interface ISceneCardsGrid {
|
interface ISceneCardsGrid {
|
||||||
scenes: GQL.SlimSceneDataFragment[];
|
scenes: GQL.SlimSceneDataFragment[];
|
||||||
|
|
@ -13,6 +16,8 @@ interface ISceneCardsGrid {
|
||||||
fromGroupId?: string;
|
fromGroupId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomWidths = [280, 340, 480, 640];
|
||||||
|
|
||||||
export const SceneCardsGrid: React.FC<ISceneCardsGrid> = ({
|
export const SceneCardsGrid: React.FC<ISceneCardsGrid> = ({
|
||||||
scenes,
|
scenes,
|
||||||
queue,
|
queue,
|
||||||
|
|
@ -21,13 +26,16 @@ export const SceneCardsGrid: React.FC<ISceneCardsGrid> = ({
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
fromGroupId,
|
fromGroupId,
|
||||||
}) => {
|
}) => {
|
||||||
const [componentRef, { width }] = useContainerDimensions();
|
const [componentRef, { width: containerWidth }] = useContainerDimensions();
|
||||||
|
|
||||||
|
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center" ref={componentRef}>
|
<div className="row justify-content-center" ref={componentRef}>
|
||||||
{scenes.map((scene, index) => (
|
{scenes.map((scene, index) => (
|
||||||
<SceneCard
|
<SceneCard
|
||||||
key={scene.id}
|
key={scene.id}
|
||||||
containerWidth={width}
|
width={cardWidth}
|
||||||
scene={scene}
|
scene={scene}
|
||||||
queue={queue}
|
queue={queue}
|
||||||
index={index}
|
index={index}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { Button, ButtonGroup } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
|
|
@ -7,9 +7,8 @@ import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import NavUtils from "src/utils/navigation";
|
import NavUtils from "src/utils/navigation";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
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 { faTag } from "@fortawesome/free-solid-svg-icons";
|
||||||
import ScreenUtils from "src/utils/screen";
|
|
||||||
import { markerTitle } from "src/core/markers";
|
import { markerTitle } from "src/core/markers";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { objectTitle } from "src/core/files";
|
import { objectTitle } from "src/core/files";
|
||||||
|
|
@ -19,7 +18,7 @@ import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
|
|
||||||
interface ISceneMarkerCardProps {
|
interface ISceneMarkerCardProps {
|
||||||
marker: GQL.SceneMarkerDataFragment;
|
marker: GQL.SceneMarkerDataFragment;
|
||||||
containerWidth?: number;
|
cardWidth?: number;
|
||||||
previewHeight?: number;
|
previewHeight?: number;
|
||||||
index?: number;
|
index?: number;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
|
|
@ -154,8 +153,6 @@ const SceneMarkerCardImage = (props: ISceneMarkerCardProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SceneMarkerCard = (props: ISceneMarkerCardProps) => {
|
export const SceneMarkerCard = (props: ISceneMarkerCardProps) => {
|
||||||
const [cardWidth, setCardWidth] = useState<number>();
|
|
||||||
|
|
||||||
function zoomIndex() {
|
function zoomIndex() {
|
||||||
if (!props.compact && props.zoomIndex !== undefined) {
|
if (!props.compact && props.zoomIndex !== undefined) {
|
||||||
return `zoom-${props.zoomIndex}`;
|
return `zoom-${props.zoomIndex}`;
|
||||||
|
|
@ -164,42 +161,12 @@ export const SceneMarkerCard = (props: ISceneMarkerCardProps) => {
|
||||||
return "";
|
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 (
|
return (
|
||||||
<GridCard
|
<GridCard
|
||||||
className={`scene-marker-card ${zoomIndex()}`}
|
className={`scene-marker-card ${zoomIndex()}`}
|
||||||
url={NavUtils.makeSceneMarkerUrl(props.marker)}
|
url={NavUtils.makeSceneMarkerUrl(props.marker)}
|
||||||
title={markerTitle(props.marker)}
|
title={markerTitle(props.marker)}
|
||||||
width={cardWidth}
|
width={props.cardWidth}
|
||||||
linkClassName="scene-marker-card-link"
|
linkClassName="scene-marker-card-link"
|
||||||
thumbnailSectionClassName="video-section"
|
thumbnailSectionClassName="video-section"
|
||||||
resumeTime={props.marker.seconds}
|
resumeTime={props.marker.seconds}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { SceneMarkerCard } from "./SceneMarkerCard";
|
import { SceneMarkerCard } from "./SceneMarkerCard";
|
||||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
import {
|
||||||
|
useCardWidth,
|
||||||
|
useContainerDimensions,
|
||||||
|
} from "../Shared/GridCard/GridCard";
|
||||||
|
|
||||||
interface ISceneMarkerCardsGrid {
|
interface ISceneMarkerCardsGrid {
|
||||||
markers: GQL.SceneMarkerDataFragment[];
|
markers: GQL.SceneMarkerDataFragment[];
|
||||||
|
|
@ -10,19 +13,23 @@ interface ISceneMarkerCardsGrid {
|
||||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomWidths = [240, 340, 480, 640];
|
||||||
|
|
||||||
export const SceneMarkerCardsGrid: React.FC<ISceneMarkerCardsGrid> = ({
|
export const SceneMarkerCardsGrid: React.FC<ISceneMarkerCardsGrid> = ({
|
||||||
markers,
|
markers,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
zoomIndex,
|
zoomIndex,
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [componentRef, { width }] = useContainerDimensions();
|
const [componentRef, { width: containerWidth }] = useContainerDimensions();
|
||||||
|
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center" ref={componentRef}>
|
<div className="row justify-content-center" ref={componentRef}>
|
||||||
{markers.map((marker, index) => (
|
{markers.map((marker, index) => (
|
||||||
<SceneMarkerCard
|
<SceneMarkerCard
|
||||||
key={marker.id}
|
key={marker.id}
|
||||||
containerWidth={width}
|
cardWidth={cardWidth}
|
||||||
marker={marker}
|
marker={marker}
|
||||||
index={index}
|
index={index}
|
||||||
zoomIndex={zoomIndex}
|
zoomIndex={zoomIndex}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React, {
|
import React, {
|
||||||
MutableRefObject,
|
MutableRefObject,
|
||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
@ -13,6 +14,7 @@ import useResizeObserver from "@react-hook/resize-observer";
|
||||||
import { Icon } from "../Icon";
|
import { Icon } from "../Icon";
|
||||||
import { faGripLines } from "@fortawesome/free-solid-svg-icons";
|
import { faGripLines } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { DragSide, useDragMoveSelect } from "./dragMoveSelect";
|
import { DragSide, useDragMoveSelect } from "./dragMoveSelect";
|
||||||
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
|
||||||
interface ICardProps {
|
interface ICardProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
@ -63,7 +65,7 @@ export const useContainerDimensions = <T extends HTMLElement = HTMLDivElement>(
|
||||||
height: 0,
|
height: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
useResizeObserver(target, (entry) => {
|
const debouncedSetDimension = useDebounce((entry: ResizeObserverEntry) => {
|
||||||
const { inlineSize: width, blockSize: height } = entry.contentBoxSize[0];
|
const { inlineSize: width, blockSize: height } = entry.contentBoxSize[0];
|
||||||
let difference = Math.abs(dimension.width - width);
|
let difference = Math.abs(dimension.width - width);
|
||||||
// Only adjust when width changed by a significant margin. This addresses the cornercase that sees
|
// 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) {
|
if (difference > sensitivityThreshold) {
|
||||||
setDimension({ width, height });
|
setDimension({ width, height });
|
||||||
}
|
}
|
||||||
});
|
}, 50);
|
||||||
|
|
||||||
|
useResizeObserver(target, debouncedSetDimension);
|
||||||
|
|
||||||
return [target, dimension];
|
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<{
|
const Checkbox: React.FC<{
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import NavUtils from "src/utils/navigation";
|
import NavUtils from "src/utils/navigation";
|
||||||
import {
|
import { GridCard } from "src/components/Shared/GridCard/GridCard";
|
||||||
GridCard,
|
|
||||||
calculateCardWidth,
|
|
||||||
} from "src/components/Shared/GridCard/GridCard";
|
|
||||||
import { HoverPopover } from "../Shared/HoverPopover";
|
import { HoverPopover } from "../Shared/HoverPopover";
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { TagLink } from "../Shared/TagLink";
|
import { TagLink } from "../Shared/TagLink";
|
||||||
|
|
@ -13,14 +10,13 @@ import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||||
import { RatingBanner } from "../Shared/RatingBanner";
|
import { RatingBanner } from "../Shared/RatingBanner";
|
||||||
import ScreenUtils from "src/utils/screen";
|
|
||||||
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
import { FavoriteIcon } from "../Shared/FavoriteIcon";
|
||||||
import { useStudioUpdate } from "src/core/StashService";
|
import { useStudioUpdate } from "src/core/StashService";
|
||||||
import { faTag } from "@fortawesome/free-solid-svg-icons";
|
import { faTag } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
studio: GQL.StudioDataFragment;
|
studio: GQL.StudioDataFragment;
|
||||||
containerWidth?: number;
|
cardWidth?: number;
|
||||||
hideParent?: boolean;
|
hideParent?: boolean;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
|
@ -75,7 +71,7 @@ function maybeRenderChildren(studio: GQL.StudioDataFragment) {
|
||||||
|
|
||||||
export const StudioCard: React.FC<IProps> = ({
|
export const StudioCard: React.FC<IProps> = ({
|
||||||
studio,
|
studio,
|
||||||
containerWidth,
|
cardWidth,
|
||||||
hideParent,
|
hideParent,
|
||||||
selecting,
|
selecting,
|
||||||
selected,
|
selected,
|
||||||
|
|
@ -83,34 +79,6 @@ export const StudioCard: React.FC<IProps> = ({
|
||||||
onSelectedChanged,
|
onSelectedChanged,
|
||||||
}) => {
|
}) => {
|
||||||
const [updateStudio] = useStudioUpdate();
|
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) {
|
function onToggleFavorite(v: boolean) {
|
||||||
if (studio.id) {
|
if (studio.id) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
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";
|
import { StudioCard } from "./StudioCard";
|
||||||
|
|
||||||
interface IStudioCardGrid {
|
interface IStudioCardGrid {
|
||||||
|
|
@ -11,6 +14,8 @@ interface IStudioCardGrid {
|
||||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomWidths = [280, 340, 420, 560];
|
||||||
|
|
||||||
export const StudioCardGrid: React.FC<IStudioCardGrid> = ({
|
export const StudioCardGrid: React.FC<IStudioCardGrid> = ({
|
||||||
studios,
|
studios,
|
||||||
fromParent,
|
fromParent,
|
||||||
|
|
@ -18,13 +23,15 @@ export const StudioCardGrid: React.FC<IStudioCardGrid> = ({
|
||||||
zoomIndex,
|
zoomIndex,
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [componentRef, { width }] = useContainerDimensions();
|
const [componentRef, { width: containerWidth }] = useContainerDimensions();
|
||||||
|
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center" ref={componentRef}>
|
<div className="row justify-content-center" ref={componentRef}>
|
||||||
{studios.map((studio) => (
|
{studios.map((studio) => (
|
||||||
<StudioCard
|
<StudioCard
|
||||||
key={studio.id}
|
key={studio.id}
|
||||||
containerWidth={width}
|
cardWidth={cardWidth}
|
||||||
studio={studio}
|
studio={studio}
|
||||||
zoomIndex={zoomIndex}
|
zoomIndex={zoomIndex}
|
||||||
hideParent={fromParent}
|
hideParent={fromParent}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { PatchComponent } from "src/patch";
|
import { PatchComponent } from "src/patch";
|
||||||
import { Button, ButtonGroup } from "react-bootstrap";
|
import { Button, ButtonGroup } from "react-bootstrap";
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import NavUtils from "src/utils/navigation";
|
import NavUtils from "src/utils/navigation";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { TruncatedText } from "../Shared/TruncatedText";
|
import { TruncatedText } from "../Shared/TruncatedText";
|
||||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
|
import { GridCard } from "../Shared/GridCard/GridCard";
|
||||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||||
import ScreenUtils from "src/utils/screen";
|
|
||||||
import { Icon } from "../Shared/Icon";
|
import { Icon } from "../Shared/Icon";
|
||||||
import { faHeart } from "@fortawesome/free-solid-svg-icons";
|
import { faHeart } from "@fortawesome/free-solid-svg-icons";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
|
|
@ -16,7 +15,7 @@ import { useTagUpdate } from "src/core/StashService";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tag: GQL.TagDataFragment;
|
tag: GQL.TagDataFragment;
|
||||||
containerWidth?: number;
|
cardWidth?: number;
|
||||||
zoomIndex: number;
|
zoomIndex: number;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
|
@ -234,40 +233,8 @@ const TagCardTitle: React.FC<IProps> = PatchComponent(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const TagCard: React.FC<IProps> = PatchComponent("TagCard", (props) => {
|
export const TagCard: React.FC<IProps> = PatchComponent("TagCard", (props) => {
|
||||||
const {
|
const { tag, cardWidth, zoomIndex, selecting, selected, onSelectedChanged } =
|
||||||
tag,
|
props;
|
||||||
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]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridCard
|
<GridCard
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
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";
|
import { TagCard } from "./TagCard";
|
||||||
|
|
||||||
interface ITagCardGrid {
|
interface ITagCardGrid {
|
||||||
|
|
@ -10,19 +13,23 @@ interface ITagCardGrid {
|
||||||
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomWidths = [280, 340, 480, 640];
|
||||||
|
|
||||||
export const TagCardGrid: React.FC<ITagCardGrid> = ({
|
export const TagCardGrid: React.FC<ITagCardGrid> = ({
|
||||||
tags,
|
tags,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
zoomIndex,
|
zoomIndex,
|
||||||
onSelectChange,
|
onSelectChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [componentRef, { width }] = useContainerDimensions();
|
const [componentRef, { width: containerWidth }] = useContainerDimensions();
|
||||||
|
const cardWidth = useCardWidth(containerWidth, zoomIndex, zoomWidths);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center" ref={componentRef}>
|
<div className="row justify-content-center" ref={componentRef}>
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
<TagCard
|
<TagCard
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
containerWidth={width}
|
cardWidth={cardWidth}
|
||||||
tag={tag}
|
tag={tag}
|
||||||
zoomIndex={zoomIndex}
|
zoomIndex={zoomIndex}
|
||||||
selecting={selectedIds.size > 0}
|
selecting={selectedIds.size > 0}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue