feat: audio and video icon in all item
This commit is contained in:
parent
bb8208d31e
commit
922df61be0
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
Loading…
Reference in New Issue