feat: audio and video icon in all item

This commit is contained in:
DIYgod 2025-09-10 17:41:07 +08:00
parent bb8208d31e
commit 922df61be0
No known key found for this signature in database
4 changed files with 86 additions and 23 deletions

View File

@ -7,15 +7,18 @@ import {
TooltipTrigger,
} from "@follow/components/ui/tooltip/index.js"
import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/index.js"
import { IN_ELECTRON } from "@follow/shared/constants"
import { useCollectionEntry, useIsEntryStarred } from "@follow/store/collection/hooks"
import { useEntry } from "@follow/store/entry/hooks"
import type { EntryModel } from "@follow/store/entry/types"
import { useFeedById } from "@follow/store/feed/hooks"
import { useInboxById } from "@follow/store/inbox/hooks"
import { transformVideoUrl } from "@follow/utils/url-for-video"
import { cn, isSafari } from "@follow/utils/utils"
import { useMemo } from "react"
import { titleCase } from "title-case"
import { AudioPlayer, useAudioPlayerAtomSelector } from "~/atoms/player"
import { useGeneralSettingKey } from "~/atoms/settings/general"
import { useUISettingKey } from "~/atoms/settings/ui"
import { RelativeTime } from "~/components/ui/datetime"
@ -34,11 +37,19 @@ import { readableContentMaxWidth } from "../styles"
import type { EntryItemStatelessProps, UniversalItemProps } from "../types"
import { MediaGallery } from "./media-gallery"
const ViewTag = IN_ELECTRON ? "webview" : "iframe"
const entrySelector = (state: EntryModel) => {
const { feedId, inboxHandle, read } = state
const { authorAvatar, authorUrl, description, publishedAt, title } = state
const audios = state.attachments?.filter((a) => a.mime_type?.startsWith("audio") && a.url)
const video = transformVideoUrl({
url: state?.url ?? "",
isIframe: !IN_ELECTRON,
attachments: state?.attachments,
mini: true,
})
const firstAudio = audios?.[0]
const media = state.media || []
const firstMedia = media?.[0]
@ -59,6 +70,7 @@ const entrySelector = (state: EntryModel) => {
read,
title,
titleEntry,
video,
}
}
export function AllItem({ entryId, entryPreview, translation }: UniversalItemProps) {
@ -179,28 +191,8 @@ export function AllItem({ entryId, entryPreview, translation }: UniversalItemPro
</TooltipRoot>
</Tooltip>
)}
{/* TODO */}
{/* {hasAudio && entry.firstAudio && (
<AudioCover
entryId={entryId}
src={entry.firstAudio.url}
durationInSeconds={entry.firstAudio.duration_in_seconds}
feedIcon={
<FeedIcon
fallback={true}
fallbackElement={
<div className={clsx("bg-material-ultra-thick", "size-[80px]", "rounded")} />
}
feed={feed || inbox}
entry={entry?.iconEntry}
size={80}
className="m-0 rounded"
useMedia
noMargin
/>
}
/>
)} */}
{entry.firstAudio && <AudioIcon entryId={entryId} src={entry.firstAudio.url} />}
{entry.video && <VideoIcon src={entry.video} />}
<div
className={cn(
"relative flex min-w-0 items-center truncate break-words",
@ -290,3 +282,69 @@ export const AllItemSkeleton = (
</div>
</div>
)
function AudioIcon({ entryId, src }: { entryId: string; src: string }) {
const playStatus = useAudioPlayerAtomSelector((playerValue) =>
playerValue.src === src && playerValue.show ? playerValue.status : false,
)
const handleClickPlay = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
e.preventDefault()
if (!playStatus) {
// switch this to play
AudioPlayer.mount({
type: "audio",
entryId,
src,
currentTime: 0,
})
} else {
// switch between play and pause
AudioPlayer.togglePlayAndPause()
}
}
return (
<div className="relative mr-1 shrink-0 text-base">
<div
className={cn("center w-full transition-all duration-200 ease-in-out")}
onClick={handleClickPlay}
>
<button type="button" className="center text-text/90">
<i
className={cn({
"i-mingcute-pause-fill": playStatus && playStatus === "playing",
"i-mingcute-loading-fill animate-spin": playStatus && playStatus === "loading",
"i-mgc-music-2-cute-fi": !playStatus || playStatus === "paused",
})}
/>
</button>
</div>
</div>
)
}
function VideoIcon({ src }: { src: string }) {
return (
<Tooltip>
<TooltipRoot>
<TooltipTrigger asChild>
<i className="i-mgc-video-cute-fi text-text/90 mr-1 shrink-0 text-lg" />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent className="flex-col gap-1" side={"bottom"}>
<div className="flex items-center gap-1">
<ViewTag
src={src}
className={cn(
"pointer-events-none aspect-video w-[575px] shrink-0 rounded-md bg-black object-cover",
)}
/>
</div>
</TooltipContent>
</TooltipPortal>
</TooltipRoot>
</Tooltip>
)
}

View File

@ -252,7 +252,10 @@ const ActionBar = ({ entryId }: { entryId: string }) => {
animate={{ opacity: 1, scale: 1, translateY: "-1/2" }}
exit={{ opacity: 0, scale: 0.9, translateY: "-1/2" }}
transition={Spring.presets.smooth}
className="absolute -right-2 top-0 -translate-y-1/2 rounded-lg border border-gray-200 bg-white/90 p-1 shadow-sm backdrop-blur-sm dark:border-neutral-900 dark:bg-neutral-900"
className={cn(
"absolute -right-2 top-0 -translate-y-1/2 rounded-lg border border-gray-200 bg-white/90 p-1 shadow-sm backdrop-blur-sm dark:border-neutral-900 dark:bg-neutral-900",
view === FeedViewType.All && "top-1/2",
)}
onClick={(e) => {
e.stopPropagation()
e.preventDefault()

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path fill="#10161F" fill-rule="evenodd" d="m14 10.72 1.885-.628c.978-.326 2.078-.612 2.85-1.335a4 4 0 0 0 .899-1.248c.442-.961.366-2.094.366-3.125 0-.614.075-1.306-.204-1.876a2 2 0 0 0-1.53-1.103c-.577-.077-1.148.168-1.683.347-1.113.37-1.88.625-2.497 1.068a5 5 0 0 0-1.862 2.584C11.989 6.16 12 7.009 12 8.27v5.488a4.5 4.5 0 1 0 2 3.742z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 497 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path stroke="#10161F" stroke-width="2" d="M2.5 11.5c0-3.771 0-5.657 1.172-6.828C4.843 3.5 6.729 3.5 10.5 3.5h3c3.771 0 5.657 0 6.828 1.172C21.5 5.843 21.5 7.729 21.5 11.5v1c0 3.771 0 5.657-1.172 6.828C19.157 20.5 17.271 20.5 13.5 20.5h-3c-3.771 0-5.657 0-6.828-1.172C2.5 18.157 2.5 16.271 2.5 12.5z"/><path stroke="#10161F" stroke-width="2" d="M11.905 9.51c-.95-.51-1.426-.764-1.908-.486s-.499.817-.532 1.896a31.063 31.063 0 0 0 0 1.95c.034 1.077.05 1.614.533 1.893.482.278.956.023 1.904-.486a31.97 31.97 0 0 0 1.696-.98c.912-.564 1.368-.847 1.369-1.403 0-.557-.456-.84-1.368-1.406a31.033 31.033 0 0 0-1.694-.978Z"/></svg>

After

Width:  |  Height:  |  Size: 754 B