Skip to content

Commit

Permalink
feat(social): improve image gallery grid
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <tukon479@gmail.com>
  • Loading branch information
Innei committed Dec 2, 2024
1 parent cff217a commit 5dc6d9e
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 65 deletions.
6 changes: 5 additions & 1 deletion apps/renderer/src/components/ui/media.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,11 @@ const MediaImpl: FC<MediaProps> = ({
)
} else {
return (
<div className={cn("relative rounded", className)} style={props.style}>
<div
className={cn("relative rounded", className)}
data-state={mediaLoadState}
style={props.style}
>
<span
className={cn(
"relative inline-block max-w-full bg-theme-placeholder-image",
Expand Down
205 changes: 141 additions & 64 deletions apps/renderer/src/modules/entry-column/Items/social-media-item.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMobile } from "@follow/components/hooks/useMobile.js"
import { ActionButton } from "@follow/components/ui/button/index.js"
import { Skeleton } from "@follow/components/ui/skeleton/index.jsx"
import type { MediaModel } from "@follow/shared/hono"
import { cn } from "@follow/utils/utils"
import { atom } from "jotai"
import { useLayoutEffect, useMemo, useRef, useState } from "react"
Expand Down Expand Up @@ -29,7 +30,6 @@ const socialMediaContentWidthAtom = atom(0)
export const SocialMediaItem: EntryListItemFC = ({ entryId, entryPreview, translation }) => {
const entry = useEntry(entryId) || entryPreview

const previewMedia = usePreviewMedia()
const asRead = useAsRead(entry)
const feed = useFeedById(entry?.feedId)

Expand Down Expand Up @@ -110,69 +110,7 @@ export const SocialMediaItem: EntryListItemFC = ({ entryId, entryPreview, transl
{!!entry.collections && <StarIcon className="absolute right-0 top-0" />}
</div>
</div>
{!!media?.length && (
<div className="mt-4 flex gap-[8px] overflow-x-auto pb-2">
{media.map((media, i, mediaList) => {
const style: Partial<{
width: string
height: string
}> = {}
const boundsWidth = jotaiStore.get(socialMediaContentWidthAtom)
if (media.height && media.width) {
// has 1 picture, max width is container width, but max height is less than window height: 2/3
if (mediaList.length === 1) {
style.width = `${boundsWidth}px`
style.height = `${(boundsWidth * media.height) / media.width}px`
if (Number.parseInt(style.height) > (window.innerHeight * 2) / 3) {
style.height = `${(window.innerHeight * 2) / 3}px`
style.width = `${(Number.parseInt(style.height) * media.width) / media.height}px`
}
}
// has 2 pictures, max width is container half width, and - gap 8px
else if (mediaList.length === 2) {
style.width = `${(boundsWidth - 8) / 2}px`
style.height = `${(((boundsWidth - 8) / 2) * media.height) / media.width}px`
}
// has over 2 pictures, max width is container 1/3 width
else if (mediaList.length > 2) {
style.width = `${boundsWidth / 3}px`
style.height = `${((boundsWidth / 3) * media.height) / media.width}px`
}
}

const proxySize = {
width: Number.parseInt(style.width || "0") * 2 || 0,
height: Number.parseInt(style.height || "0") * 2 || 0,
}
return (
<Media
style={style}
key={media.url}
src={media.url}
type={media.type}
previewImageUrl={media.preview_image_url}
blurhash={media.blurhash}
className="size-28 shrink-0 data-[state=loading]:!bg-theme-placeholder-image"
loading="lazy"
proxy={proxySize}
onClick={(e) => {
e.stopPropagation()
previewMedia(
mediaList.map((m) => ({
url: m.url,
type: m.type,
blurhash: m.blurhash,
fallbackUrl:
m.preview_image_url ?? getImageProxyUrl({ url: m.url, ...proxySize }),
})),
i,
)
}}
/>
)
})}
</div>
)}
{!!media?.length && <SocialMediaGallery media={media} />}
</div>

{showAction && (
Expand Down Expand Up @@ -278,3 +216,142 @@ export const SocialMediaItemSkeleton = (
</div>
</div>
)

const SocialMediaGallery = ({ media }: { media: MediaModel[] }) => {
const previewMedia = usePreviewMedia()

const isAllMediaSameRatio = useMemo(() => {
let ratio = 0
for (const m of media) {
if (m.height && m.width) {
const currentRatio = m.height / m.width
if (ratio === 0) {
ratio = currentRatio
} else if (ratio !== currentRatio) {
return false
}
} else {
return false
}
}
return true
}, [media])

// all media has same ratio, use horizontal layout
if (isAllMediaSameRatio) {
return (
<div className="mt-4 flex gap-[8px] overflow-x-auto pb-2">
{media.map((media, i, mediaList) => {
const style: Partial<{
width: string
height: string
}> = {}
const boundsWidth = jotaiStore.get(socialMediaContentWidthAtom)
if (media.height && media.width) {
// has 1 picture, max width is container width, but max height is less than window height: 2/3
if (mediaList.length === 1) {
style.width = `${boundsWidth}px`
style.height = `${(boundsWidth * media.height) / media.width}px`
if (Number.parseInt(style.height) > (window.innerHeight * 2) / 3) {
style.height = `${(window.innerHeight * 2) / 3}px`
style.width = `${(Number.parseInt(style.height) * media.width) / media.height}px`
}
}
// has 2 pictures, max width is container half width, and - gap 8px
else if (mediaList.length === 2) {
style.width = `${(boundsWidth - 8) / 2}px`
style.height = `${(((boundsWidth - 8) / 2) * media.height) / media.width}px`
}
// has over 2 pictures, max width is container 1/3 width
else if (mediaList.length > 2) {
style.width = `${boundsWidth / 3}px`
style.height = `${((boundsWidth / 3) * media.height) / media.width}px`
}
}

const proxySize = {
width: Number.parseInt(style.width || "0") * 2 || 0,
height: Number.parseInt(style.height || "0") * 2 || 0,
}
return (
<Media
style={style}
key={media.url}
src={media.url}
type={media.type}
previewImageUrl={media.preview_image_url}
blurhash={media.blurhash}
className="size-28 shrink-0 data-[state=loading]:!bg-theme-placeholder-image"
loading="lazy"
proxy={proxySize}
onClick={(e) => {
e.stopPropagation()
previewMedia(
mediaList.map((m) => ({
url: m.url,
type: m.type,
blurhash: m.blurhash,
fallbackUrl:
m.preview_image_url ?? getImageProxyUrl({ url: m.url, ...proxySize }),
})),
i,
)
}}
/>
)
})}
</div>
)
}

// all media has different ratio, use grid layout
return (
<div className="mt-4">
<div
className={cn(
"grid gap-2",
media.length === 2 && "grid-cols-2",
media.length === 3 && "grid-cols-2",
media.length === 4 && "grid-cols-2",
media.length >= 5 && "grid-cols-3",
)}
>
{media.map((m, i) => {
const proxySize = {
width: 400,
height: 400,
}

const style = media.length === 3 && i === 2 ? { gridRow: "span 2" } : {}

return (
<Media
style={style}
key={m.url}
src={m.url}
type={m.type}
previewImageUrl={m.preview_image_url}
blurhash={m.blurhash}
className="aspect-square w-full rounded object-cover"
loading="lazy"
proxy={proxySize}
onClick={(e) => {
e.stopPropagation()
previewMedia(
media.map((m) => ({
url: m.url,
type: m.type,
blurhash: m.blurhash,
fallbackUrl:
m.preview_image_url ?? getImageProxyUrl({ url: m.url, ...proxySize }),
})),
i,
)
}}
/>
)
})}
</div>
</div>
)
}

0 comments on commit 5dc6d9e

Please sign in to comment.