feat: introduce media layout to unify video and picture layout

This commit is contained in:
Stephen Zhou 2025-09-22 15:18:30 +08:00
parent 7eec68a830
commit 9ab7400a33
No known key found for this signature in database
7 changed files with 76 additions and 50 deletions

View File

@ -30,18 +30,9 @@ import { EntryTitle } from "../EntryTitle"
import { SupportCreator } from "../SupportCreator"
import { MediaTranscript, TranscriptToggle, useTranscription } from "./shared"
import { ArticleAudioPlayer } from "./shared/AudioPlayer"
import type { EntryLayoutProps } from "./types"
interface ArticleLayoutProps {
entryId: string
compact?: boolean
noMedia?: boolean
translation?: {
content?: string
title?: string
}
}
export const ArticleLayout: React.FC<ArticleLayoutProps> = ({
export const ArticleLayout: React.FC<EntryLayoutProps> = ({
entryId,
compact = false,
noMedia = false,

View File

@ -0,0 +1,46 @@
import { useEntry } from "@follow/store/entry/hooks"
import { transformVideoUrl } from "@follow/utils/url-for-video"
import { ArticleLayout } from "./ArticleLayout"
import { PicturesLayout } from "./PicturesLayout"
import type { EntryLayoutProps } from "./types"
import { VideosLayout } from "./VideosLayout"
export const MediaLayout: React.FC<EntryLayoutProps> = (props) => {
const entry = useEntry(props.entryId, (state) => ({
media: state.media,
id: state.id,
url: state.url,
attachments: state.attachments,
}))
if (!entry) return null
// Detect media types - more comprehensive video detection
const hasVideoMedia = entry.media?.some((media) => media.type === "video")
const hasVideoUrl =
transformVideoUrl({
url: entry.url ?? "",
isIframe: true,
attachments: entry.attachments,
}) !== null
const hasVideo = hasVideoMedia || hasVideoUrl
const hasImages = entry.media?.some((media) => media.type === "photo")
// Video has absolute priority - show video whenever it exists, regardless of noMedia
const shouldShowVideo = hasVideo
const shouldShowImages = !hasVideo && hasImages && !props.noMedia
if (shouldShowVideo) {
// Use VideosLayout for video content
return <VideosLayout {...props} noMedia={false} />
}
if (shouldShowImages) {
// Use PicturesLayout for image content
return <PicturesLayout {...props} />
}
// Fallback: use ArticleLayout when no media content is detected
return <ArticleLayout {...props} />
}

View File

@ -6,18 +6,9 @@ import { SwipeMedia } from "~/components/ui/media/SwipeMedia"
import { readableContentMaxWidthClassName } from "~/constants/ui"
import { AuthorHeader, ContentBody } from "./shared"
import type { EntryLayoutProps } from "./types"
interface PicturesLayoutProps {
entryId: string
compact?: boolean
noMedia?: boolean
translation?: {
content?: string
title?: string
}
}
export const PicturesLayout: React.FC<PicturesLayoutProps> = ({
export const PicturesLayout: React.FC<EntryLayoutProps> = ({
entryId,
compact = false,
noMedia = false,

View File

@ -7,18 +7,9 @@ import { readableContentMaxWidthClassName } from "~/constants/ui"
import { AuthorHeader } from "./shared/AuthorHeader"
import { ContentBody } from "./shared/ContentBody"
import type { EntryLayoutProps } from "./types"
interface SocialMediaLayoutProps {
entryId: string
compact?: boolean
noMedia?: boolean
translation?: {
content?: string
title?: string
}
}
export const SocialMediaLayout: React.FC<SocialMediaLayoutProps> = ({
export const SocialMediaLayout: React.FC<EntryLayoutProps> = ({
entryId,
compact = false,
noMedia = false,

View File

@ -4,18 +4,9 @@ import { useState } from "react"
import { EntryTitle } from "../EntryTitle"
import { ContentBody, MediaTranscript, TranscriptToggle, useTranscription } from "./shared"
import { VideoPlayer } from "./shared/VideoPlayer"
import type { EntryLayoutProps } from "./types"
interface VideosLayoutProps {
entryId: string
compact?: boolean
noMedia?: boolean
translation?: {
content?: string
title?: string
}
}
export const VideosLayout: React.FC<VideosLayoutProps> = ({
export const VideosLayout: React.FC<EntryLayoutProps> = ({
entryId,
compact = false,
noMedia = false,

View File

@ -1,19 +1,23 @@
import { FeedViewType } from "@follow/constants"
import type { FC } from "react"
import { ArticleLayout } from "./ArticleLayout"
import { PicturesLayout } from "./PicturesLayout"
import { MediaLayout } from "./MediaLayout"
import { SocialMediaLayout } from "./SocialMediaLayout"
import { VideosLayout } from "./VideosLayout"
import type { EntryLayoutProps } from "./types"
const EntryContentLayoutFactory = {
type EntryLayoutComponent = FC<EntryLayoutProps>
const EntryContentLayoutFactory: Record<FeedViewType, EntryLayoutComponent> = {
[FeedViewType.All]: ArticleLayout, // Use article layout as fallback for all view
[FeedViewType.Articles]: ArticleLayout,
[FeedViewType.SocialMedia]: SocialMediaLayout,
[FeedViewType.Pictures]: PicturesLayout,
[FeedViewType.Videos]: VideosLayout,
[FeedViewType.Pictures]: MediaLayout, // Use unified media layout for pictures
[FeedViewType.Videos]: MediaLayout, // Use unified media layout for videos
[FeedViewType.Audios]: ArticleLayout, // Use article layout as fallback for audio
[FeedViewType.Notifications]: ArticleLayout, // Use article layout as fallback for notifications
}
export const getEntryContentLayout = (viewType: FeedViewType) => {
export const getEntryContentLayout = (viewType: FeedViewType): EntryLayoutComponent => {
return EntryContentLayoutFactory[viewType] || ArticleLayout
}

View File

@ -0,0 +1,12 @@
/**
* Shared props interface for all entry content layout components
*/
export interface EntryLayoutProps {
entryId: string
compact?: boolean
noMedia?: boolean
translation?: {
content?: string
title?: string
}
}