feat: support for video events
This commit is contained in:
@@ -9,11 +9,11 @@ import {
|
||||
EmbeddedWebsocketUrlParser,
|
||||
parseContent
|
||||
} from '@/lib/content-parser'
|
||||
import { getImageInfosFromEvent } from '@/lib/event'
|
||||
import { getEmojiInfosFromEmojiTags, getImageInfoFromImetaTag } from '@/lib/tag'
|
||||
import { getImetaInfosFromEvent } from '@/lib/event'
|
||||
import { getEmojiInfosFromEmojiTags, getImetaInfoFromImetaTag } from '@/lib/tag'
|
||||
import { cn } from '@/lib/utils'
|
||||
import mediaUpload from '@/services/media-upload.service'
|
||||
import { TImageInfo } from '@/types'
|
||||
import { TImetaInfo } from '@/types'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
@@ -46,30 +46,30 @@ const Content = memo(
|
||||
EmbeddedEmojiParser
|
||||
])
|
||||
|
||||
const imageInfos = event ? getImageInfosFromEvent(event) : []
|
||||
const imetaInfos = event ? getImetaInfosFromEvent(event) : []
|
||||
const allImages = nodes
|
||||
.map((node) => {
|
||||
if (node.type === 'image') {
|
||||
const imageInfo = imageInfos.find((image) => image.url === node.data)
|
||||
const imageInfo = imetaInfos.find((image) => image.url === node.data)
|
||||
if (imageInfo) {
|
||||
return imageInfo
|
||||
}
|
||||
const tag = mediaUpload.getImetaTagByUrl(node.data)
|
||||
return tag
|
||||
? getImageInfoFromImetaTag(tag, event?.pubkey)
|
||||
? getImetaInfoFromImetaTag(tag, event?.pubkey)
|
||||
: { url: node.data, pubkey: event?.pubkey }
|
||||
}
|
||||
if (node.type === 'images') {
|
||||
const urls = Array.isArray(node.data) ? node.data : [node.data]
|
||||
return urls.map((url) => {
|
||||
const imageInfo = imageInfos.find((image) => image.url === url)
|
||||
const imageInfo = imetaInfos.find((image) => image.url === url)
|
||||
return imageInfo ?? { url, pubkey: event?.pubkey }
|
||||
})
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter(Boolean)
|
||||
.flat() as TImageInfo[]
|
||||
.flat() as TImetaInfo[]
|
||||
let imageIndex = 0
|
||||
|
||||
const emojiInfos = getEmojiInfosFromEmojiTags(event?.tags)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { cn } from '@/lib/utils'
|
||||
import client from '@/services/client.service'
|
||||
import { TImageInfo } from '@/types'
|
||||
import { TImetaInfo } from '@/types'
|
||||
import { getHashFromURL } from 'blossom-client-sdk'
|
||||
import { decode } from 'blurhash'
|
||||
import { ImageOff } from 'lucide-react'
|
||||
@@ -20,7 +20,7 @@ export default function Image({
|
||||
wrapper?: string
|
||||
errorPlaceholder?: string
|
||||
}
|
||||
image: TImageInfo
|
||||
image: TImetaInfo
|
||||
alt?: string
|
||||
hideIfError?: boolean
|
||||
errorPlaceholder?: React.ReactNode
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { randomString } from '@/lib/random'
|
||||
import { cn } from '@/lib/utils'
|
||||
import modalManager from '@/services/modal-manager.service'
|
||||
import { TImageInfo } from '@/types'
|
||||
import { TImetaInfo } from '@/types'
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import Lightbox from 'yet-another-react-lightbox'
|
||||
@@ -15,7 +15,7 @@ export default function ImageGallery({
|
||||
end = images.length
|
||||
}: {
|
||||
className?: string
|
||||
images: TImageInfo[]
|
||||
images: TImetaInfo[]
|
||||
start?: number
|
||||
end?: number
|
||||
}) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { randomString } from '@/lib/random'
|
||||
import { cn } from '@/lib/utils'
|
||||
import modalManager from '@/services/modal-manager.service'
|
||||
import { TImageInfo } from '@/types'
|
||||
import { TImetaInfo } from '@/types'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import Lightbox from 'yet-another-react-lightbox'
|
||||
@@ -12,7 +12,7 @@ export default function ImageWithLightbox({
|
||||
image,
|
||||
className
|
||||
}: {
|
||||
image: TImageInfo
|
||||
image: TImetaInfo
|
||||
className?: string
|
||||
}) {
|
||||
const id = useMemo(() => `image-with-lightbox-${randomString()}`, [])
|
||||
|
||||
@@ -3,23 +3,24 @@ import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTrigger } from '@/components/ui/drawer'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { DEFAULT_SHOW_KINDS, ExtendedKind } from '@/constants'
|
||||
import { ExtendedKind, SUPPORTED_KINDS } from '@/constants'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useKindFilter } from '@/providers/KindFilterProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { ListFilter } from 'lucide-react'
|
||||
import { kinds } from 'nostr-tools'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const SUPPORTED_KINDS = [
|
||||
const KIND_FILTER_OPTIONS = [
|
||||
{ kindGroup: [kinds.ShortTextNote, ExtendedKind.COMMENT], label: 'Posts' },
|
||||
{ kindGroup: [kinds.Repost], label: 'Reposts' },
|
||||
{ kindGroup: [kinds.LongFormArticle], label: 'Articles' },
|
||||
{ kindGroup: [kinds.Highlights], label: 'Highlights' },
|
||||
{ kindGroup: [ExtendedKind.POLL], label: 'Polls' },
|
||||
{ kindGroup: [ExtendedKind.VOICE, ExtendedKind.VOICE_COMMENT], label: 'Voice Posts' },
|
||||
{ kindGroup: [ExtendedKind.PICTURE], label: 'Photo Posts' }
|
||||
{ kindGroup: [ExtendedKind.PICTURE], label: 'Photo Posts' },
|
||||
{ kindGroup: [ExtendedKind.VIDEO, ExtendedKind.SHORT_VIDEO], label: 'Video Posts' }
|
||||
]
|
||||
|
||||
export default function KindFilter({
|
||||
@@ -35,9 +36,6 @@ export default function KindFilter({
|
||||
const { updateShowKinds } = useKindFilter()
|
||||
const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds)
|
||||
const [isPersistent, setIsPersistent] = useState(false)
|
||||
const isFilterApplied = useMemo(() => {
|
||||
return showKinds.length !== DEFAULT_SHOW_KINDS.length
|
||||
}, [showKinds])
|
||||
|
||||
useEffect(() => {
|
||||
setTemporaryShowKinds(showKinds)
|
||||
@@ -74,7 +72,7 @@ export default function KindFilter({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="titlebar-icon"
|
||||
className={cn('mr-1', !isFilterApplied && 'text-muted-foreground')}
|
||||
className="mr-1"
|
||||
onClick={() => {
|
||||
if (isSmallScreen) {
|
||||
setOpen(true)
|
||||
@@ -88,7 +86,7 @@ export default function KindFilter({
|
||||
const content = (
|
||||
<div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{SUPPORTED_KINDS.map(({ kindGroup, label }) => {
|
||||
{KIND_FILTER_OPTIONS.map(({ kindGroup, label }) => {
|
||||
const checked = kindGroup.every((k) => temporaryShowKinds.includes(k))
|
||||
return (
|
||||
<div
|
||||
@@ -118,7 +116,7 @@ export default function KindFilter({
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setTemporaryShowKinds(DEFAULT_SHOW_KINDS)
|
||||
setTemporaryShowKinds(SUPPORTED_KINDS)
|
||||
}}
|
||||
className="flex-1"
|
||||
>
|
||||
|
||||
16
src/components/Note/PictureNote.tsx
Normal file
16
src/components/Note/PictureNote.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { getImetaInfosFromEvent } from '@/lib/event'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import Content from '../Content'
|
||||
import ImageGallery from '../ImageGallery'
|
||||
|
||||
export default function PictureNote({ event, className }: { event: Event; className?: string }) {
|
||||
const imageInfos = useMemo(() => getImetaInfosFromEvent(event), [event])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Content event={event} />
|
||||
{imageInfos.length > 0 && <ImageGallery images={imageInfos} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
18
src/components/Note/VideoNote.tsx
Normal file
18
src/components/Note/VideoNote.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { getImetaInfosFromEvent } from '@/lib/event'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import Content from '../Content'
|
||||
import MediaPlayer from '../MediaPlayer'
|
||||
|
||||
export default function VideoNote({ event, className }: { event: Event; className?: string }) {
|
||||
const videoInfos = useMemo(() => getImetaInfosFromEvent(event), [event])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Content event={event} />
|
||||
{videoInfos.map((video) => (
|
||||
<MediaPlayer src={video.url} key={video.url} className="mt-2" />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { ExtendedKind } from '@/constants'
|
||||
import {
|
||||
getImageInfosFromEvent,
|
||||
getParentBech32Id,
|
||||
getUsingClient,
|
||||
isNsfwEvent,
|
||||
isPictureEvent
|
||||
} from '@/lib/event'
|
||||
import { ExtendedKind, SUPPORTED_KINDS } from '@/constants'
|
||||
import { getParentBech32Id, getUsingClient, isNsfwEvent } from '@/lib/event'
|
||||
import { toNote } from '@/lib/link'
|
||||
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
||||
import { useMuteList } from '@/providers/MuteListProvider'
|
||||
@@ -16,7 +10,6 @@ import { useMemo, useState } from 'react'
|
||||
import AudioPlayer from '../AudioPlayer'
|
||||
import Content from '../Content'
|
||||
import { FormattedTimestamp } from '../FormattedTimestamp'
|
||||
import ImageGallery from '../ImageGallery'
|
||||
import Nip05 from '../Nip05'
|
||||
import NoteOptions from '../NoteOptions'
|
||||
import ParentNotePreview from '../ParentNotePreview'
|
||||
@@ -32,8 +25,10 @@ import LongFormArticle from './LongFormArticle'
|
||||
import LongFormArticlePreview from './LongFormArticlePreview'
|
||||
import MutedNote from './MutedNote'
|
||||
import NsfwNote from './NsfwNote'
|
||||
import PictureNote from './PictureNote'
|
||||
import Poll from './Poll'
|
||||
import UnknownNote from './UnknownNote'
|
||||
import VideoNote from './VideoNote'
|
||||
|
||||
export default function Note({
|
||||
event,
|
||||
@@ -56,10 +51,6 @@ export default function Note({
|
||||
() => (hideParentNotePreview ? undefined : getParentBech32Id(event)),
|
||||
[event, hideParentNotePreview]
|
||||
)
|
||||
const imageInfos = useMemo(
|
||||
() => (isPictureEvent(event) ? getImageInfosFromEvent(event) : []),
|
||||
[event]
|
||||
)
|
||||
const usingClient = useMemo(() => getUsingClient(event), [event])
|
||||
const { defaultShowNsfw } = useContentPolicy()
|
||||
const [showNsfw, setShowNsfw] = useState(false)
|
||||
@@ -67,21 +58,7 @@ export default function Note({
|
||||
const [showMuted, setShowMuted] = useState(false)
|
||||
|
||||
let content: React.ReactNode
|
||||
if (
|
||||
![
|
||||
kinds.ShortTextNote,
|
||||
kinds.Highlights,
|
||||
kinds.LongFormArticle,
|
||||
kinds.LiveEvent,
|
||||
kinds.CommunityDefinition,
|
||||
ExtendedKind.GROUP_METADATA,
|
||||
ExtendedKind.PICTURE,
|
||||
ExtendedKind.COMMENT,
|
||||
ExtendedKind.POLL,
|
||||
ExtendedKind.VOICE,
|
||||
ExtendedKind.VOICE_COMMENT
|
||||
].includes(event.kind)
|
||||
) {
|
||||
if (!SUPPORTED_KINDS.includes(event.kind)) {
|
||||
content = <UnknownNote className="mt-2" event={event} />
|
||||
} else if (mutePubkeys.includes(event.pubkey) && !showMuted) {
|
||||
content = <MutedNote show={() => setShowMuted(true)} />
|
||||
@@ -110,6 +87,10 @@ export default function Note({
|
||||
)
|
||||
} else if (event.kind === ExtendedKind.VOICE || event.kind === ExtendedKind.VOICE_COMMENT) {
|
||||
content = <AudioPlayer className="mt-2" src={event.content} />
|
||||
} else if (event.kind === ExtendedKind.PICTURE) {
|
||||
content = <PictureNote className="mt-2" event={event} />
|
||||
} else if (event.kind === ExtendedKind.VIDEO || event.kind === ExtendedKind.SHORT_VIDEO) {
|
||||
content = <VideoNote className="mt-2" event={event} />
|
||||
} else {
|
||||
content = <Content className="mt-2" event={event} />
|
||||
}
|
||||
@@ -159,7 +140,6 @@ export default function Note({
|
||||
)}
|
||||
<IValue event={event} className="mt-2" />
|
||||
{content}
|
||||
{imageInfos.length > 0 && <ImageGallery images={imageInfos} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user