feat: shorts
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { VIDEO_KIND } from "@/const";
|
||||
import { SHORTS_KIND, VIDEO_KIND } from "@/const";
|
||||
import { useStreamLink } from "@/hooks/stream-link";
|
||||
import { getEventFromLocationState } from "@/utils";
|
||||
import { NostrPrefix, EventKind } from "@snort/system";
|
||||
@ -9,6 +9,7 @@ import { EventEmbed as NostrEventElement } from "@/element/event-embed";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useLayout } from "./layout/context";
|
||||
import classNames from "classnames";
|
||||
import { ShortPage } from "./short";
|
||||
|
||||
export function LinkHandler() {
|
||||
const location = useLocation();
|
||||
@ -32,6 +33,8 @@ export function LinkHandler() {
|
||||
);
|
||||
} else if (link.kind === VIDEO_KIND) {
|
||||
return <VideoPage link={link} evPreload={evPreload} />;
|
||||
} else if (link.kind === SHORTS_KIND) {
|
||||
return <ShortPage link={link} evPreload={evPreload} />;
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
|
25
src/pages/short.tsx
Normal file
25
src/pages/short.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import EventReactions from "@/element/event-reactions";
|
||||
import { VideoInfo } from "@/element/video-info";
|
||||
import { VideoPlayerContextProvider } from "@/element/video/context";
|
||||
import VideoPlayer from "@/element/video/player";
|
||||
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||
|
||||
export function ShortPage({ link, evPreload }: { link: NostrLink; evPreload?: TaggedNostrEvent }) {
|
||||
const ev = useCurrentStreamFeed(link, true, evPreload);
|
||||
|
||||
if (!ev) return;
|
||||
return (
|
||||
<VideoPlayerContextProvider event={ev}>
|
||||
<div className="max-xl:py-2 max-xl:w-full xl:w-[550px] mx-auto">
|
||||
<div className="relative">
|
||||
<VideoPlayer showPip={false} showWideMode={false} loop={true} />
|
||||
<div className="absolute bottom-0 -right-14">
|
||||
<EventReactions ev={ev} vertical={true} replyKind={1} className="text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<VideoInfo showComments={false} showZap={false} />
|
||||
</div>
|
||||
</VideoPlayerContextProvider>
|
||||
);
|
||||
}
|
@ -1,3 +1,34 @@
|
||||
import { SHORTS_KIND } from "@/const";
|
||||
import VideoGrid from "@/element/video-grid";
|
||||
import { VideoTile } from "@/element/video/video-tile";
|
||||
import { findTag } from "@/utils";
|
||||
import { RequestBuilder } from "@snort/system";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export function ShortsPage() {
|
||||
return <>Coming soon...</>;
|
||||
const rb = new RequestBuilder("shorts");
|
||||
rb.withFilter().kinds([SHORTS_KIND]);
|
||||
|
||||
const videos = useRequestBuilder(rb);
|
||||
|
||||
const sorted = videos.sort((a, b) => {
|
||||
const pubA = findTag(a, "published_at");
|
||||
const pubB = findTag(b, "published_at");
|
||||
return Number(pubA) > Number(pubB) ? -1 : 1;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h2>
|
||||
<FormattedMessage defaultMessage="Latest Shorts" />
|
||||
</h2>
|
||||
<br />
|
||||
<VideoGrid>
|
||||
{sorted.map(a => (
|
||||
<VideoTile ev={a} key={a.id} style="grid" />
|
||||
))}
|
||||
</VideoGrid>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,32 +1,23 @@
|
||||
import { WriteMessage } from "@/element/chat/write-message";
|
||||
import { FollowButton } from "@/element/follow-button";
|
||||
import { Profile, getName } from "@/element/profile";
|
||||
import { SendZapsDialog } from "@/element/send-zap";
|
||||
import { ShareMenu } from "@/element/share-menu";
|
||||
import { StreamSummary } from "@/element/stream/summary";
|
||||
import VideoComments from "@/element/video/comments";
|
||||
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||
import { getHost, findTag } from "@/utils";
|
||||
import { NostrLink, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||
import { useRequestBuilder, useUserProfile } from "@snort/system-react";
|
||||
import { useRequestBuilder } from "@snort/system-react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import classNames from "classnames";
|
||||
import { StreamTile } from "@/element/stream/stream-tile";
|
||||
import { VIDEO_KIND } from "@/const";
|
||||
import { VideoInfo } from "@/service/video/info";
|
||||
import { VideoPlayerContextProvider, useVideoPlayerContext } from "@/element/video/context";
|
||||
import VideoPlayer from "@/element/video/player";
|
||||
import { VideoInfo } from "@/element/video-info";
|
||||
|
||||
export function VideoPage({ link, evPreload }: { link: NostrLink; evPreload?: TaggedNostrEvent }) {
|
||||
const ev = useCurrentStreamFeed(link, true, evPreload);
|
||||
|
||||
if (!ev) return;
|
||||
const video = VideoInfo.parse(ev);
|
||||
|
||||
return (
|
||||
<VideoPlayerContextProvider info={video}>
|
||||
<VideoPlayerContextProvider event={ev}>
|
||||
<VideoPageInner ev={ev} />
|
||||
</VideoPlayerContextProvider>
|
||||
);
|
||||
@ -37,9 +28,6 @@ function VideoPageInner({ ev }: { ev: TaggedNostrEvent }) {
|
||||
const ctx = useVideoPlayerContext();
|
||||
const link = NostrLink.fromEvent(ev);
|
||||
|
||||
const profile = useUserProfile(host);
|
||||
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames("xl:p-4 grow xl:grid xl:gap-2 xl:grid-cols-[auto_450px]", {
|
||||
@ -52,43 +40,7 @@ function VideoPageInner({ ev }: { ev: TaggedNostrEvent }) {
|
||||
<VideoPlayer />
|
||||
</div>
|
||||
{/* VIDEO INFO & COMMENTS */}
|
||||
<div
|
||||
className={classNames("row-start-2 col-start-1 max-xl:px-4 flex flex-col gap-4", {
|
||||
"mx-auto w-[40dvw]": ctx.widePlayer,
|
||||
})}>
|
||||
<div className="font-medium text-xl">{ctx.video?.title}</div>
|
||||
<div className="flex justify-between">
|
||||
{/* PROFILE SECTION */}
|
||||
<div className="flex gap-2 items-center">
|
||||
<Profile pubkey={host} />
|
||||
<FollowButton pubkey={host} />
|
||||
</div>
|
||||
{/* ACTIONS */}
|
||||
<div className="flex gap-2">
|
||||
{ev && (
|
||||
<>
|
||||
<ShareMenu ev={ev} />
|
||||
{zapTarget && (
|
||||
<SendZapsDialog
|
||||
lnurl={zapTarget}
|
||||
pubkey={host}
|
||||
aTag={link.tagKey}
|
||||
targetName={getName(ev.pubkey, profile)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{ctx.video?.summary && <StreamSummary text={ctx.video.summary} />}
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Comments" />
|
||||
</h3>
|
||||
<div>
|
||||
<WriteMessage link={link} emojiPacks={[]} kind={1} />
|
||||
</div>
|
||||
<VideoComments link={link} />
|
||||
</div>
|
||||
<VideoInfo showComments={true} />
|
||||
<div
|
||||
className={classNames("p-2 col-start-2", {
|
||||
"row-start-1 row-span-3": !ctx.widePlayer,
|
||||
|
Reference in New Issue
Block a user