mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 21:31:43 +01:00
Add Streams and Tools to launchpad
This commit is contained in:
parent
d5aa803eae
commit
c648923508
5
.changeset/shy-knives-deny.md
Normal file
5
.changeset/shy-knives-deny.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Add Streams and Tools to launchpad
|
@ -3,9 +3,12 @@ import { DraftNostrEvent, NostrEvent, isPTag } from "../../types/nostr-event";
|
||||
import { unique } from "../array";
|
||||
import { ensureNotifyContentMentions } from "./post";
|
||||
import { getEventCoordinate } from "./event";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
export const STREAM_KIND = 30311;
|
||||
export const STREAM_CHAT_MESSAGE_KIND = 1311;
|
||||
/** @deprecated use kinds.LiveEvent instead */
|
||||
export const STREAM_KIND = kinds.LiveEvent;
|
||||
/** @deprecated use kinds.LiveChatMessage instead */
|
||||
export const STREAM_CHAT_MESSAGE_KIND = kinds.LiveChatMessage;
|
||||
|
||||
export type ParsedStream = {
|
||||
event: NostrEvent;
|
||||
|
@ -105,6 +105,9 @@ export default function DMsCard({ ...props }: Omit<CardProps, "children">) {
|
||||
{conversations.slice(0, 4).map((conversation) => (
|
||||
<Conversation key={conversation.pubkeys.join("-")} conversation={conversation} />
|
||||
))}
|
||||
<Button as={RouterLink} to="/dm" flexShrink={0} variant="link" size="lg" py="4">
|
||||
View More
|
||||
</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useCallback } from "react";
|
||||
import { Button, Card, CardBody, CardHeader, CardProps, Heading, Link } from "@chakra-ui/react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
|
||||
import KeyboardShortcut from "../../../components/keyboard-shortcut";
|
||||
import { useNotifications } from "../../../providers/global/notifications-provider";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { NotificationType, typeSymbol } from "../../../classes/notifications";
|
||||
import NotificationItem from "../../notifications/components/notification-item";
|
||||
import { useCallback } from "react";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
|
||||
export default function NotificationsCard({ ...props }: Omit<CardProps, "children">) {
|
||||
const navigate = useNavigate();
|
||||
@ -40,11 +40,11 @@ export default function NotificationsCard({ ...props }: Omit<CardProps, "childre
|
||||
</Heading>
|
||||
<KeyboardShortcut letter="i" requireMeta ml="auto" onPress={() => navigate("/notifications")} />
|
||||
</CardHeader>
|
||||
<CardBody overflowX="hidden" overflowY="auto" pt="4" display="flex" gap="2" flexDirection="column" maxH="50vh">
|
||||
<CardBody overflowX="hidden" overflowY="auto" pt="4" display="flex" gap="2" flexDirection="column">
|
||||
{limit.map((event) => (
|
||||
<NotificationItem event={event} key={event.id} onClick={handleClick} />
|
||||
))}
|
||||
<Button as={RouterLink} to="/notifications" flexShrink={0} variant="link" size="lg" py="6">
|
||||
<Button as={RouterLink} to="/notifications" flexShrink={0} variant="link" size="lg" py="4">
|
||||
View More
|
||||
</Button>
|
||||
</CardBody>
|
||||
|
89
src/views/launchpad/components/streams-card.tsx
Normal file
89
src/views/launchpad/components/streams-card.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Button, Card, CardBody, CardHeader, CardProps, Flex, Heading, Link, LinkBox } from "@chakra-ui/react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
import { Filter, kinds, NostrEvent } from "nostr-tools";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useClientSideMuteFilter from "../../../hooks/use-client-side-mute-filter";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../../providers/local/people-list-provider";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { ParsedStream, parseStreamEvent } from "../../../helpers/nostr/stream";
|
||||
import UserAvatar from "../../../components/user/user-avatar";
|
||||
import UserName from "../../../components/user/user-name";
|
||||
import HoverLinkOverlay from "../../../components/hover-link-overlay";
|
||||
import useShareableEventAddress from "../../../hooks/use-shareable-event-address";
|
||||
import KeyboardShortcut from "../../../components/keyboard-shortcut";
|
||||
|
||||
function LiveStream({ stream }: { stream: ParsedStream }) {
|
||||
const naddr = useShareableEventAddress(stream.event);
|
||||
|
||||
return (
|
||||
<Flex as={LinkBox} alignItems="center" gap="2">
|
||||
<UserAvatar pubkey={stream.host} size="sm" />
|
||||
|
||||
<HoverLinkOverlay as={RouterLink} to={`/streams/${naddr}`}></HoverLinkOverlay>
|
||||
{stream.title || <UserName pubkey={stream.host} />}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
function StreamsCardContent({ ...props }: Omit<CardProps, "children">) {
|
||||
const navigate = useNavigate();
|
||||
const relays = useReadRelays();
|
||||
const userMuteFilter = useClientSideMuteFilter();
|
||||
|
||||
const eventFilter = useCallback(
|
||||
(event: NostrEvent) => {
|
||||
if (userMuteFilter(event)) return false;
|
||||
return true;
|
||||
},
|
||||
[userMuteFilter],
|
||||
);
|
||||
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
const query = useMemo<Filter[] | undefined>(() => {
|
||||
if (!filter) return undefined;
|
||||
return [
|
||||
{ authors: filter.authors, kinds: [kinds.LiveEvent] },
|
||||
{ "#p": filter.authors, kinds: [kinds.LiveEvent] },
|
||||
];
|
||||
}, [filter]);
|
||||
|
||||
const timeline = useTimelineLoader(`${listId ?? "global"}-streams`, relays, query, { eventFilter });
|
||||
|
||||
const streams = useSubject(timeline.timeline)
|
||||
.map((event) => parseStreamEvent(event))
|
||||
.filter((stream) => !!stream.streaming)
|
||||
.slice(0, 6);
|
||||
|
||||
return (
|
||||
<Card variant="outline" {...props}>
|
||||
<CardHeader display="flex" justifyContent="space-between" alignItems="center" pb="2">
|
||||
<Heading size="lg">
|
||||
<Link as={RouterLink} to="/streams">
|
||||
Streams
|
||||
</Link>
|
||||
</Heading>
|
||||
<KeyboardShortcut letter="l" requireMeta ml="auto" onPress={() => navigate("/streams")} />
|
||||
</CardHeader>
|
||||
<CardBody overflowX="hidden" overflowY="auto" pt="4" display="flex" gap="2" flexDirection="column" maxH="50vh">
|
||||
{streams.map((stream) => (
|
||||
<LiveStream key={getEventUID(stream.event)} stream={stream} />
|
||||
))}
|
||||
<Button as={RouterLink} to="/streams" flexShrink={0} variant="link" size="lg" py="4">
|
||||
View More
|
||||
</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default function StreamsCard({ ...props }: Omit<CardProps, "children">) {
|
||||
return (
|
||||
<PeopleListProvider initList="following">
|
||||
<StreamsCardContent {...props} />
|
||||
</PeopleListProvider>
|
||||
);
|
||||
}
|
57
src/views/launchpad/components/tools-card.tsx
Normal file
57
src/views/launchpad/components/tools-card.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { useState } from "react";
|
||||
import { Button, Card, CardBody, CardHeader, CardProps, Heading, Input, Link, SimpleGrid } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import useRecentIds from "../../../hooks/use-recent-ids";
|
||||
import { allApps } from "../../other-stuff/apps";
|
||||
import AppCard from "../../other-stuff/component/app-card";
|
||||
|
||||
export default function ToolsCard({ ...props }: Omit<CardProps, "children">) {
|
||||
const { recent: recentApps, useThing: useApp } = useRecentIds("apps");
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const apps = Array(6)
|
||||
.fill(0)
|
||||
.map((_, i) => {
|
||||
if (recentApps[i]) {
|
||||
return allApps.find((a) => a.id === recentApps[i]) || allApps[i];
|
||||
} else return allApps[i];
|
||||
});
|
||||
|
||||
return (
|
||||
<Card variant="outline" {...props}>
|
||||
<CardHeader display="flex" justifyContent="space-between" alignItems="center" pb="2">
|
||||
<Heading size="lg">
|
||||
<Link as={RouterLink} to="/other-stuff">
|
||||
Tools
|
||||
</Link>
|
||||
</Heading>
|
||||
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search apps"
|
||||
maxW="sm"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardBody overflowX="hidden" overflowY="auto" pt="4" display="flex" gap="2" flexDirection="column" maxH="50vh">
|
||||
<SimpleGrid spacing="2" columns={{ base: 1, md: 2 }}>
|
||||
{search.length > 2
|
||||
? allApps
|
||||
.filter(
|
||||
(app) =>
|
||||
app.title.toLowerCase().includes(search.toLowerCase()) ||
|
||||
app.description.toLowerCase().includes(search.toLowerCase()),
|
||||
)
|
||||
.map((app) => <AppCard key={app.id} app={app} onClick={() => useApp(app.id)} />)
|
||||
: apps.map((app) => <AppCard key={app.id} app={app} onClick={() => useApp(app.id)} />)}
|
||||
</SimpleGrid>
|
||||
<Button as={RouterLink} to="/other-stuff" flexShrink={0} variant="link" size="lg" py="4">
|
||||
View More
|
||||
</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -13,6 +13,8 @@ import SearchForm from "./components/search-form";
|
||||
import KeyboardShortcut from "../../components/keyboard-shortcut";
|
||||
import DMsCard from "./components/dms-card";
|
||||
import NotificationsCard from "./components/notifications-card";
|
||||
import ToolsCard from "./components/tools-card";
|
||||
import StreamsCard from "./components/streams-card";
|
||||
|
||||
function LaunchpadPage() {
|
||||
const { openModal } = useContext(PostModalContext);
|
||||
@ -39,8 +41,10 @@ function LaunchpadPage() {
|
||||
<SearchForm flex={1} />
|
||||
</Flex>
|
||||
<FeedsCard w="full" />
|
||||
<NotificationsCard w={{ base: "full", md: "calc(60% - 1rem)" }} />
|
||||
<NotificationsCard w={{ base: "full", md: "calc(60% - 1rem)" }} maxH="40vh" />
|
||||
<DMsCard w={{ base: "full", md: "40%" }} />
|
||||
<StreamsCard w={{ base: "full", md: "40%" }} />
|
||||
<ToolsCard w={{ base: "full", md: "calc(60% - 1rem)" }} />
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -57,6 +57,27 @@ export const internalApps: App[] = [
|
||||
];
|
||||
|
||||
export const internalTools: App[] = [
|
||||
{
|
||||
title: "Event Console",
|
||||
description: "Find events based on nostr filters",
|
||||
icon: SearchIcon,
|
||||
id: "console",
|
||||
to: "/tools/console",
|
||||
},
|
||||
{
|
||||
title: "Event Publisher",
|
||||
description: "Write and publish events",
|
||||
icon: UploadCloud01,
|
||||
id: "publisher",
|
||||
to: "/tools/publisher ",
|
||||
},
|
||||
{
|
||||
title: "Unknown Events",
|
||||
description: "A timeline of unknown events",
|
||||
icon: MessageQuestionSquare,
|
||||
id: "unknown",
|
||||
to: "/tools/unknown",
|
||||
},
|
||||
{
|
||||
title: "Satellite CDN",
|
||||
description: "Scalable media hosting for the nostr ecosystem",
|
||||
@ -93,27 +114,6 @@ export const internalTools: App[] = [
|
||||
id: "dm-timeline",
|
||||
to: "/tools/dm-timeline",
|
||||
},
|
||||
{
|
||||
title: "Unknown Events",
|
||||
description: "A timeline of unknown events",
|
||||
icon: MessageQuestionSquare,
|
||||
id: "unknown",
|
||||
to: "/tools/unknown",
|
||||
},
|
||||
{
|
||||
title: "Event Console",
|
||||
description: "Find events based on nostr filters",
|
||||
icon: SearchIcon,
|
||||
id: "console",
|
||||
to: "/tools/console",
|
||||
},
|
||||
{
|
||||
title: "Event Publisher",
|
||||
description: "Write and publish events",
|
||||
icon: UploadCloud01,
|
||||
id: "publisher",
|
||||
to: "/tools/publisher ",
|
||||
},
|
||||
{
|
||||
title: "Corrections Feed",
|
||||
description: "A feed of post edits",
|
||||
|
@ -8,7 +8,6 @@ import { useAdditionalRelayContext } from "../../providers/local/additional-rela
|
||||
import { RelayIconStack } from "../../components/relay-icon-stack";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import TimelineViewType from "../../components/timeline-page/timeline-view-type";
|
||||
import TimelinePage, { useTimelinePageEventFilter } from "../../components/timeline-page";
|
||||
import NoteFilterTypeButtons from "../../components/note-filter-type-buttons";
|
||||
@ -35,7 +34,7 @@ export default function UserNotesTab() {
|
||||
readRelays,
|
||||
{
|
||||
authors: [pubkey],
|
||||
kinds: [kinds.ShortTextNote, kinds.Repost, kinds.GenericRepost, kinds.LongFormArticle, STREAM_KIND, 2],
|
||||
kinds: [kinds.ShortTextNote, kinds.Repost, kinds.GenericRepost, 2],
|
||||
},
|
||||
{ eventFilter },
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user