feat: shorts

This commit is contained in:
2024-05-27 14:08:38 +01:00
parent 4ed2242655
commit 39f6df907d
12 changed files with 220 additions and 90 deletions

View File

@ -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
View 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>
);
}

View File

@ -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>
);
}

View File

@ -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,