fix: disable preview media in picture item ai
This commit is contained in:
parent
c58c83b50e
commit
756b57c7bf
|
|
@ -0,0 +1,171 @@
|
|||
import {
|
||||
MasonryIntersectionContext,
|
||||
useMasonryItemRatio,
|
||||
useMasonryItemWidth,
|
||||
useSetStableMasonryItemRatio,
|
||||
} from "@follow/components/ui/masonry/contexts.jsx"
|
||||
import { FeedViewType } from "@follow/constants"
|
||||
import { useEntry } from "@follow/store/entry/hooks"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import type { PropsWithChildren } from "react"
|
||||
import { memo, use, useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { SwipeMedia } from "~/components/ui/media/SwipeMedia"
|
||||
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
|
||||
import { useImageDimensions } from "~/store/image"
|
||||
|
||||
import { EntryItemWrapper } from "../layouts/EntryItemWrapper"
|
||||
import { GridItem, GridItemFooter } from "../templates/grid-item-template"
|
||||
import type { UniversalItemProps } from "../types"
|
||||
|
||||
export function PictureItem({ entryId, translation }: UniversalItemProps) {
|
||||
const entry = useEntry(entryId, (state) => ({ media: state.media, id: state.id }))
|
||||
const entryMedia = entry?.media || []
|
||||
|
||||
const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.id)
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!entry) return null
|
||||
return (
|
||||
<GridItem entryId={entryId} translation={translation}>
|
||||
<div className="relative flex gap-2 overflow-x-auto">
|
||||
{entryMedia ? (
|
||||
<SwipeMedia
|
||||
media={entryMedia}
|
||||
className={cn(
|
||||
"aspect-square",
|
||||
"w-full shrink-0 rounded-md [&_img]:rounded-md",
|
||||
isActive && "rounded-b-none",
|
||||
)}
|
||||
imgClassName="object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="center bg-material-medium text-text-secondary aspect-square w-full flex-col gap-1 rounded-md text-xs">
|
||||
<i className="i-mgc-sad-cute-re size-6" />
|
||||
{t("entry_content.no_content")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</GridItem>
|
||||
)
|
||||
}
|
||||
|
||||
const proxySize = {
|
||||
width: 600,
|
||||
height: 0,
|
||||
}
|
||||
|
||||
export const PictureWaterFallItem = memo(function PictureWaterFallItem({
|
||||
entryId,
|
||||
translation,
|
||||
index,
|
||||
className,
|
||||
}: UniversalItemProps & { index: number; className?: string }) {
|
||||
const entry = useEntry(entryId, (state) => ({
|
||||
media: state.media,
|
||||
id: state.id,
|
||||
}))
|
||||
|
||||
const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.id)
|
||||
const itemWidth = useMasonryItemWidth()
|
||||
|
||||
const [ref, setRef] = useState<HTMLDivElement | null>(null)
|
||||
const intersectionObserver = use(MasonryIntersectionContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref || !intersectionObserver) return
|
||||
|
||||
intersectionObserver.observe(ref)
|
||||
|
||||
return () => {
|
||||
intersectionObserver.unobserve(ref)
|
||||
}
|
||||
}, [ref, intersectionObserver])
|
||||
|
||||
const media = entry?.media || []
|
||||
|
||||
if (media?.length === 0) return null
|
||||
if (!entry) return null
|
||||
|
||||
return (
|
||||
<div ref={setRef} data-entry-id={entryId} data-index={index} className={className}>
|
||||
<EntryItemWrapper
|
||||
view={FeedViewType.Pictures}
|
||||
entryId={entryId}
|
||||
itemClassName="group rounded-md hover:bg-transparent"
|
||||
style={{
|
||||
width: itemWidth,
|
||||
}}
|
||||
>
|
||||
{media && media.length > 0 ? (
|
||||
<MasonryItemFixedDimensionWrapper url={media[0]!.url}>
|
||||
<SwipeMedia
|
||||
media={media}
|
||||
className={cn(
|
||||
"w-full grow rounded-md after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:transition-colors after:duration-300 group-hover:after:bg-black/20",
|
||||
isActive && "rounded-b-none",
|
||||
)}
|
||||
proxySize={proxySize}
|
||||
imgClassName="object-cover"
|
||||
/>
|
||||
|
||||
<div className="z-[3] shrink-0 overflow-hidden rounded-b-md pb-1">
|
||||
<GridItemFooter entryId={entryId} translation={translation} />
|
||||
</div>
|
||||
</MasonryItemFixedDimensionWrapper>
|
||||
) : (
|
||||
<div className="center bg-material-medium text-text-secondary aspect-video flex-col gap-1 rounded-md text-xs">
|
||||
<i className="i-mgc-sad-cute-re size-6" />
|
||||
No media available
|
||||
</div>
|
||||
)}
|
||||
</EntryItemWrapper>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const MasonryItemFixedDimensionWrapper = (
|
||||
props: PropsWithChildren<{
|
||||
url: string
|
||||
}>,
|
||||
) => {
|
||||
const { url, children } = props
|
||||
const dim = useImageDimensions(url)
|
||||
const itemWidth = useMasonryItemWidth()
|
||||
|
||||
const stableRadio = useMemo(() => {
|
||||
return dim ? dim.ratio : 1
|
||||
}, [dim])
|
||||
const setItemStableRatio = useSetStableMasonryItemRatio()
|
||||
|
||||
const stableRadioCtx = useMasonryItemRatio(url)
|
||||
|
||||
useEffect(() => {
|
||||
if (dim) {
|
||||
setItemStableRatio(url, stableRadio)
|
||||
}
|
||||
}, [setItemStableRatio, stableRadio, url, dim])
|
||||
|
||||
const finalRatio = stableRadioCtx || stableRadio
|
||||
const style = useMemo(
|
||||
() => ({
|
||||
width: itemWidth,
|
||||
height: itemWidth / finalRatio + 60,
|
||||
}),
|
||||
[itemWidth, finalRatio],
|
||||
)
|
||||
|
||||
if (!style.height || style.height === Infinity) return null
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full flex-col overflow-x-auto overflow-y-hidden" style={style}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
MasonryItemFixedDimensionWrapper.whyDidYouRender = {
|
||||
logOnDifferentValues: true,
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
import {
|
||||
MasonryIntersectionContext,
|
||||
useMasonryItemRatio,
|
||||
useMasonryItemWidth,
|
||||
useSetStableMasonryItemRatio,
|
||||
} from "@follow/components/ui/masonry/contexts.jsx"
|
||||
import { FeedViewType } from "@follow/constants"
|
||||
import { useEntry } from "@follow/store/entry/hooks"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import type { PropsWithChildren } from "react"
|
||||
import { memo, use, useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { SwipeMedia } from "~/components/ui/media/SwipeMedia"
|
||||
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
|
||||
import { EntryContent } from "~/modules/entry-content/components/entry-content"
|
||||
import { useImageDimensions } from "~/store/image"
|
||||
|
||||
import { usePreviewMedia } from "../../../components/ui/media/hooks"
|
||||
import { EntryItemWrapper } from "../layouts/EntryItemWrapper"
|
||||
import { GridItem, GridItemFooter } from "../templates/grid-item-template"
|
||||
import type { UniversalItemProps } from "../types"
|
||||
|
||||
export function PictureItem({ entryId, translation }: UniversalItemProps) {
|
||||
const entry = useEntry(entryId, (state) => ({ media: state.media, id: state.id }))
|
||||
const entryMedia = entry?.media || []
|
||||
|
||||
const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.id)
|
||||
|
||||
const { t } = useTranslation()
|
||||
const entryContent = useMemo(() => <EntryContent entryId={entryId} noMedia compact />, [entryId])
|
||||
const previewMedia = usePreviewMedia(entryContent)
|
||||
if (!entry) return null
|
||||
return (
|
||||
<GridItem entryId={entryId} translation={translation}>
|
||||
<div className="relative flex gap-2 overflow-x-auto">
|
||||
{entryMedia ? (
|
||||
<SwipeMedia
|
||||
media={entryMedia}
|
||||
className={cn(
|
||||
"aspect-square",
|
||||
"w-full shrink-0 rounded-md [&_img]:rounded-md",
|
||||
isActive && "rounded-b-none",
|
||||
)}
|
||||
imgClassName="object-cover"
|
||||
onPreview={previewMedia}
|
||||
/>
|
||||
) : (
|
||||
<div className="center bg-material-medium text-text-secondary aspect-square w-full flex-col gap-1 rounded-md text-xs">
|
||||
<i className="i-mgc-sad-cute-re size-6" />
|
||||
{t("entry_content.no_content")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</GridItem>
|
||||
)
|
||||
}
|
||||
|
||||
const proxySize = {
|
||||
width: 600,
|
||||
height: 0,
|
||||
}
|
||||
|
||||
export const PictureWaterFallItem = memo(function PictureWaterFallItem({
|
||||
entryId,
|
||||
translation,
|
||||
index,
|
||||
className,
|
||||
}: UniversalItemProps & { index: number; className?: string }) {
|
||||
const entry = useEntry(entryId, (state) => ({
|
||||
media: state.media,
|
||||
id: state.id,
|
||||
}))
|
||||
|
||||
const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.id)
|
||||
const entryContent = useMemo(() => <EntryContent entryId={entryId} noMedia compact />, [entryId])
|
||||
const previewMedia = usePreviewMedia(entryContent)
|
||||
const itemWidth = useMasonryItemWidth()
|
||||
|
||||
const [ref, setRef] = useState<HTMLDivElement | null>(null)
|
||||
const intersectionObserver = use(MasonryIntersectionContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref || !intersectionObserver) return
|
||||
|
||||
intersectionObserver.observe(ref)
|
||||
|
||||
return () => {
|
||||
intersectionObserver.unobserve(ref)
|
||||
}
|
||||
}, [ref, intersectionObserver])
|
||||
|
||||
const media = entry?.media || []
|
||||
|
||||
if (media?.length === 0) return null
|
||||
if (!entry) return null
|
||||
|
||||
return (
|
||||
<div ref={setRef} data-entry-id={entryId} data-index={index} className={className}>
|
||||
<EntryItemWrapper
|
||||
view={FeedViewType.Pictures}
|
||||
entryId={entryId}
|
||||
itemClassName="group rounded-md hover:bg-transparent"
|
||||
style={{
|
||||
width: itemWidth,
|
||||
}}
|
||||
>
|
||||
{media && media.length > 0 ? (
|
||||
<MasonryItemFixedDimensionWrapper url={media[0]!.url}>
|
||||
<SwipeMedia
|
||||
media={media}
|
||||
className={cn(
|
||||
"w-full grow rounded-md after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:transition-colors after:duration-300 group-hover:after:bg-black/20",
|
||||
isActive && "rounded-b-none",
|
||||
)}
|
||||
proxySize={proxySize}
|
||||
imgClassName="object-cover"
|
||||
onPreview={previewMedia}
|
||||
/>
|
||||
|
||||
<div className="z-[3] shrink-0 overflow-hidden rounded-b-md pb-1">
|
||||
<GridItemFooter entryId={entryId} translation={translation} />
|
||||
</div>
|
||||
</MasonryItemFixedDimensionWrapper>
|
||||
) : (
|
||||
<div className="center bg-material-medium text-text-secondary aspect-video flex-col gap-1 rounded-md text-xs">
|
||||
<i className="i-mgc-sad-cute-re size-6" />
|
||||
No media available
|
||||
</div>
|
||||
)}
|
||||
</EntryItemWrapper>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const MasonryItemFixedDimensionWrapper = (
|
||||
props: PropsWithChildren<{
|
||||
url: string
|
||||
}>,
|
||||
) => {
|
||||
const { url, children } = props
|
||||
const dim = useImageDimensions(url)
|
||||
const itemWidth = useMasonryItemWidth()
|
||||
|
||||
const stableRadio = useMemo(() => {
|
||||
return dim ? dim.ratio : 1
|
||||
}, [dim])
|
||||
const setItemStableRatio = useSetStableMasonryItemRatio()
|
||||
|
||||
const stableRadioCtx = useMasonryItemRatio(url)
|
||||
|
||||
useEffect(() => {
|
||||
if (dim) {
|
||||
setItemStableRatio(url, stableRadio)
|
||||
}
|
||||
}, [setItemStableRatio, stableRadio, url, dim])
|
||||
|
||||
const finalRatio = stableRadioCtx || stableRadio
|
||||
const style = useMemo(
|
||||
() => ({
|
||||
width: itemWidth,
|
||||
height: itemWidth / finalRatio + 60,
|
||||
}),
|
||||
[itemWidth, finalRatio],
|
||||
)
|
||||
|
||||
if (!style.height || style.height === Infinity) return null
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full flex-col overflow-x-auto overflow-y-hidden" style={style}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
MasonryItemFixedDimensionWrapper.whyDidYouRender = {
|
||||
logOnDifferentValues: true,
|
||||
}
|
||||
|
|
@ -1,181 +1,18 @@
|
|||
import { withFeature } from "~/lib/features"
|
||||
|
||||
import {
|
||||
MasonryIntersectionContext,
|
||||
useMasonryItemRatio,
|
||||
useMasonryItemWidth,
|
||||
useSetStableMasonryItemRatio,
|
||||
} from "@follow/components/ui/masonry/contexts.jsx"
|
||||
import { FeedViewType } from "@follow/constants"
|
||||
import { useEntry } from "@follow/store/entry/hooks"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import type { PropsWithChildren } from "react"
|
||||
import { memo, use, useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
PictureItem as PictureItemAI,
|
||||
PictureWaterFallItem as PictureWaterFallItemAI,
|
||||
} from "./picture-item.ai"
|
||||
import {
|
||||
PictureItem as PictureItemLegacy,
|
||||
PictureWaterFallItem as PictureWaterFallItemLegacy,
|
||||
} from "./picture-item.legacy"
|
||||
|
||||
import { SwipeMedia } from "~/components/ui/media/SwipeMedia"
|
||||
import { useFeature } from "~/hooks/biz/useFeature"
|
||||
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
|
||||
import { EntryContent } from "~/modules/entry-content/components/entry-content"
|
||||
import { useImageDimensions } from "~/store/image"
|
||||
export const PictureItem = withFeature("ai")(PictureItemAI, PictureItemLegacy)
|
||||
export const PictureWaterFallItem = withFeature("ai")(
|
||||
PictureWaterFallItemAI,
|
||||
PictureWaterFallItemLegacy,
|
||||
)
|
||||
|
||||
import { usePreviewMedia } from "../../../components/ui/media/hooks"
|
||||
import { EntryItemWrapper } from "../layouts/EntryItemWrapper"
|
||||
import { GridItem, GridItemFooter } from "../templates/grid-item-template"
|
||||
import type { UniversalItemProps } from "../types"
|
||||
|
||||
export function PictureItem({ entryId, translation }: UniversalItemProps) {
|
||||
const entry = useEntry(entryId, (state) => ({ media: state.media, id: state.id }))
|
||||
const entryMedia = entry?.media || []
|
||||
|
||||
const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.id)
|
||||
|
||||
const { t } = useTranslation()
|
||||
const entryContent = useMemo(() => <EntryContent entryId={entryId} noMedia compact />, [entryId])
|
||||
const previewMedia = usePreviewMedia(entryContent)
|
||||
if (!entry) return null
|
||||
return (
|
||||
<GridItem entryId={entryId} translation={translation}>
|
||||
<div className="relative flex gap-2 overflow-x-auto">
|
||||
{entryMedia ? (
|
||||
<SwipeMedia
|
||||
media={entryMedia}
|
||||
className={cn(
|
||||
"aspect-square",
|
||||
"w-full shrink-0 rounded-md [&_img]:rounded-md",
|
||||
isActive && "rounded-b-none",
|
||||
)}
|
||||
imgClassName="object-cover"
|
||||
onPreview={previewMedia}
|
||||
/>
|
||||
) : (
|
||||
<div className="center bg-material-medium text-text-secondary aspect-square w-full flex-col gap-1 rounded-md text-xs">
|
||||
<i className="i-mgc-sad-cute-re size-6" />
|
||||
{t("entry_content.no_content")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</GridItem>
|
||||
)
|
||||
}
|
||||
|
||||
const proxySize = {
|
||||
width: 600,
|
||||
height: 0,
|
||||
}
|
||||
|
||||
export const PictureWaterFallItem = memo(function PictureWaterFallItem({
|
||||
entryId,
|
||||
|
||||
translation,
|
||||
index,
|
||||
className,
|
||||
}: UniversalItemProps & { index: number; className?: string }) {
|
||||
const entry = useEntry(entryId, (state) => ({
|
||||
media: state.media,
|
||||
id: state.id,
|
||||
}))
|
||||
|
||||
const aiEnabled = useFeature("ai")
|
||||
const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.id)
|
||||
const entryContent = useMemo(() => <EntryContent entryId={entryId} noMedia compact />, [entryId])
|
||||
const previewMedia = usePreviewMedia(entryContent)
|
||||
const itemWidth = useMasonryItemWidth()
|
||||
|
||||
const [ref, setRef] = useState<HTMLDivElement | null>(null)
|
||||
const intersectionObserver = use(MasonryIntersectionContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref || !intersectionObserver) return
|
||||
|
||||
intersectionObserver.observe(ref)
|
||||
|
||||
return () => {
|
||||
intersectionObserver.unobserve(ref)
|
||||
}
|
||||
}, [ref, intersectionObserver])
|
||||
|
||||
const media = entry?.media || []
|
||||
|
||||
if (media?.length === 0) return null
|
||||
if (!entry) return null
|
||||
|
||||
return (
|
||||
<div ref={setRef} data-entry-id={entryId} data-index={index} className={className}>
|
||||
<EntryItemWrapper
|
||||
view={FeedViewType.Pictures}
|
||||
entryId={entryId}
|
||||
itemClassName="group rounded-md hover:bg-transparent"
|
||||
style={{
|
||||
width: itemWidth,
|
||||
}}
|
||||
>
|
||||
{media && media.length > 0 ? (
|
||||
<MasonryItemFixedDimensionWrapper url={media[0]!.url}>
|
||||
<SwipeMedia
|
||||
media={media}
|
||||
className={cn(
|
||||
"w-full grow rounded-md after:pointer-events-none after:absolute after:inset-0 after:bg-transparent after:transition-colors after:duration-300 group-hover:after:bg-black/20",
|
||||
isActive && "rounded-b-none",
|
||||
)}
|
||||
proxySize={proxySize}
|
||||
imgClassName="object-cover"
|
||||
onPreview={aiEnabled ? undefined : previewMedia}
|
||||
/>
|
||||
|
||||
<div className="z-[3] shrink-0 overflow-hidden rounded-b-md pb-1">
|
||||
<GridItemFooter entryId={entryId} translation={translation} />
|
||||
</div>
|
||||
</MasonryItemFixedDimensionWrapper>
|
||||
) : (
|
||||
<div className="center bg-material-medium text-text-secondary aspect-video flex-col gap-1 rounded-md text-xs">
|
||||
<i className="i-mgc-sad-cute-re size-6" />
|
||||
No media available
|
||||
</div>
|
||||
)}
|
||||
</EntryItemWrapper>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const MasonryItemFixedDimensionWrapper = (
|
||||
props: PropsWithChildren<{
|
||||
url: string
|
||||
}>,
|
||||
) => {
|
||||
const { url, children } = props
|
||||
const dim = useImageDimensions(url)
|
||||
const itemWidth = useMasonryItemWidth()
|
||||
|
||||
const stableRadio = useMemo(() => {
|
||||
return dim ? dim.ratio : 1
|
||||
}, [dim])
|
||||
const setItemStableRatio = useSetStableMasonryItemRatio()
|
||||
|
||||
const stableRadioCtx = useMasonryItemRatio(url)
|
||||
|
||||
useEffect(() => {
|
||||
if (dim) {
|
||||
setItemStableRatio(url, stableRadio)
|
||||
}
|
||||
}, [setItemStableRatio, stableRadio, url, dim])
|
||||
|
||||
const finalRatio = stableRadioCtx || stableRadio
|
||||
const style = useMemo(
|
||||
() => ({
|
||||
width: itemWidth,
|
||||
height: itemWidth / finalRatio + 60,
|
||||
}),
|
||||
[itemWidth, finalRatio],
|
||||
)
|
||||
|
||||
if (!style.height || style.height === Infinity) return null
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full flex-col overflow-x-auto overflow-y-hidden" style={style}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
MasonryItemFixedDimensionWrapper.whyDidYouRender = {
|
||||
logOnDifferentValues: true,
|
||||
}
|
||||
export { PictureItemSkeleton } from "./picture-item-skeleton"
|
||||
|
|
|
|||
Loading…
Reference in New Issue