From f037c3adbc53df37a32199b14d5c3f46fc55673d Mon Sep 17 00:00:00 2001 From: Bob <160986752+bob2402@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:43:40 +0800 Subject: [PATCH] new nav bar (#492) --- app/UI/icons/caret-down-icon.tsx | 26 +++ app/UI/icons/pound-icon.tsx | 20 ++ app/UI/new-nav.test.tsx | 58 +++++ app/UI/new-nav.tsx | 382 +++++++++++++++++++++++++++++++ 4 files changed, 486 insertions(+) create mode 100644 app/UI/icons/caret-down-icon.tsx create mode 100644 app/UI/icons/pound-icon.tsx create mode 100644 app/UI/new-nav.test.tsx create mode 100644 app/UI/new-nav.tsx diff --git a/app/UI/icons/caret-down-icon.tsx b/app/UI/icons/caret-down-icon.tsx new file mode 100644 index 0000000..54bf117 --- /dev/null +++ b/app/UI/icons/caret-down-icon.tsx @@ -0,0 +1,26 @@ +/** @jsx h */ +import { h } from "preact"; + +export function CaretDownIcon(props: { + class?: string | h.JSX.SignalLike | undefined; + style?: + | string + | h.JSX.CSSProperties + | h.JSX.SignalLike + | undefined; +}) { + return ( + + + + ); +} diff --git a/app/UI/icons/pound-icon.tsx b/app/UI/icons/pound-icon.tsx new file mode 100644 index 0000000..a9a12fd --- /dev/null +++ b/app/UI/icons/pound-icon.tsx @@ -0,0 +1,20 @@ +/** @jsx h */ +import { h } from "preact"; + +export function PoundIcon(props: { + class?: string | h.JSX.SignalLike | undefined; + style?: + | string + | h.JSX.CSSProperties + | h.JSX.SignalLike + | undefined; +}) { + return ( + + + + ); +} diff --git a/app/UI/new-nav.test.tsx b/app/UI/new-nav.test.tsx new file mode 100644 index 0000000..22ed2fd --- /dev/null +++ b/app/UI/new-nav.test.tsx @@ -0,0 +1,58 @@ +/** @jsx h */ +import { h, render } from "preact"; +import { NewNav } from "./new-nav.tsx"; +import { prepareProfileEvent, testEventBus } from "./_setup.test.ts"; +import { ConnectionPool, InMemoryAccountContext } from "@blowater/nostr-sdk"; + +const pool = new ConnectionPool(); +await pool.addRelayURLs( + [ + "blowater.nostr1.com", + "nos.lol", + "relay.damus.io", + "nostr.wine", + "wss://relay.nostr.wirednet.jp", + "wss://relay.nostr.moctane.com", + "wss://remnant.cloud", + // "wss://nostr.cahlen.org", + // "wss://fog.dedyn.io", + // "wss://global-relay.cesc.trade", + // "wss://nostr.dakukitsune.ca", + // "wss://africa.nostr.joburg", + // "wss://nostr-relay.ktwo.io", + // "wss://bevo.nostr1.com", + // "wss://relay.corpum.com", + // "wss://relay.nostr.directory", + // "wss://nostr.1f52b.xyz", + // "wss://lnbits.eldamar.icu/nostrrelay/relay", + // "wss://relay.cosmicbolt.net", + // "wss://island.nostr1.com", + // "wss://nostr.codingarena.de", + // "wss://nostr.madco.me", + // "wss://nostr-relay.bitcoin.ninja", + ], +); +const ctx = InMemoryAccountContext.Generate(); +const profileEvent = await prepareProfileEvent(ctx, { + name: "test_name", + display_name: "Orionna Lumis", + about: + "Celestial bodies move in a harmonious dance, bound by the tether of gravity. Their ballet paints stories in the sky.", + website: "https://github.com", + picture: "https://image.nostr.build/655007ae74f24ea1c611889f48b25cb485b83ab67408daddd98f95782f47e1b5.jpg", +}); + +render( + , + document.body, +); + +for await (const event of testEventBus.onChange()) { + console.log(event); +} diff --git a/app/UI/new-nav.tsx b/app/UI/new-nav.tsx new file mode 100644 index 0000000..22b309d --- /dev/null +++ b/app/UI/new-nav.tsx @@ -0,0 +1,382 @@ +/** @jsx h */ +import { Component, Fragment, h } from "preact"; +import { emitFunc, EventSubscriber } from "../event-bus.ts"; +import { NavigationUpdate, NavTabID, SelectSpace, ShowProfileSetting } from "./nav.tsx"; +import { ViewSpaceSettings } from "./setting.tsx"; +import { ConnectionPool, getRelayInformation, RelayInformation, robohash } from "@blowater/nostr-sdk"; +import { setState } from "./_helper.ts"; +import { Avatar, RelayAvatar } from "./components/avatar.tsx"; +import { CaretDownIcon } from "./icons/caret-down-icon.tsx"; +import { PoundIcon } from "./icons/pound-icon.tsx"; +import { Profile_Nostr_Event } from "../nostr.ts"; +import { ConversationSummary } from "./conversation-list.ts"; +import { ProfileData } from "../features/profile.ts"; +import { PinIcon } from "./icons/pin-icon.tsx"; +import { PrimaryTextColor } from "./style/colors.ts"; +import { + ContactUpdate, + ConversationListRetriever, + ConversationType, + NewMessageChecker, + PinListGetter, +} from "./conversation-list.tsx"; +import { SearchUpdate, SelectConversation } from "./search_model.ts"; +import { TagSelected } from "./contact-tags.tsx"; +import { ViewUserDetail } from "./message-panel.tsx"; +import { UI_Interaction_Event, UserBlocker } from "./app_update.tsx"; +import { func_GetProfileByPublicKey, func_GetProfilesByText } from "./search.tsx"; + +type NewNavProps = { + pool: ConnectionPool; + activeNav: NavTabID; + currentSpace: string; + emit: emitFunc< + | SelectSpace + | NavigationUpdate + | ViewSpaceSettings + | ShowProfileSetting + | ContactUpdate + | TagSelected + | ViewUserDetail + >; + profile: Profile_Nostr_Event | undefined; +}; + +export class NewNav extends Component { + render(props: NewNavProps) { + return ( +
+ r.url))} + emit={props.emit} + /> + {/* */} + + + +
+ ); + } +} + +type SpaceDropDownPanelProps = { + spaceList: Set; + emit: emitFunc; + currentSpace: string; +}; + +type SpaceDropDownState = { + showDropDown: boolean; + spaceInformation: Map; + searchSpaceValue: string; +}; + +class SpaceDropDownPanel extends Component { + state: Readonly = { + spaceInformation: new Map(), + showDropDown: false, + searchSpaceValue: "", + }; + + async componentDidMount() { + for (const url of this.props.spaceList) { + getRelayInformation(url).then((info) => { + if (info instanceof Error) { + console.error(info); + return; + } + setState(this, { + spaceInformation: this.state.spaceInformation.set(url, info), + }); + }); + } + } + + handleSearchRelayInput = async (e: Event) => { + await setState(this, { + searchSpaceValue: (e.target as HTMLInputElement).value, + }); + }; + + render() { + const spaceList = []; + for (const url of this.props.spaceList) { + if (!url.includes(this.state.searchSpaceValue)) { + continue; + } + spaceList.push( + this.SpaceListItem(url, this.props.currentSpace == url), + ); + } + return ( +
+ {this.TopIconButton()} + {this.state.showDropDown ? this.DropDown(spaceList) : undefined} +
+ ); + } + + TopIconButton = () => { + return ( +
+
+ {this.props.currentSpace + ? ( + + ) + : } +
+
+ {this.props.currentSpace + ?
{this.state.spaceInformation.get(this.props.currentSpace)?.name}
+ :
current space url
} +
+
+ +
+
+ ); + }; + + DropDown = (spaceList: h.JSX.Element[]) => { + return ( +
+ {this.SettingsButton()} + {/* {this.InviteButton()} */} +
+
+ +
+
+ {spaceList} +
+ {this.NewSpaceButton()} +
+ ); + }; + + SpaceListItem(spaceURL: string, isCurrentRelay: boolean) { + const selected = isCurrentRelay ? " border-[#000] border" : ""; + return ( +
+
+
+ +
+
+
+
{this.state.spaceInformation.get(spaceURL)?.name}
+
{new URL(spaceURL).hostname}
+
+
+ ); + } + + SettingsButton = () => ( +
{ + this.props.emit({ + type: "ViewSpaceSettings", + url: this.props.currentSpace, + }); + setState(this, { + showDropDown: false, + }); + }} + > + + + + +

Settings

+
+ ); + + InviteButton = () => ( +
+ + + + +

Invite

+
+ ); + + NewSpaceButton = () => { + return ( +
+ New Space +
+ ); + }; + + toggleRelayList = async () => { + await setState(this, { + showDropDown: !this.state.showDropDown, + }); + }; + + onSpaceSelected = (spaceURL: string) => async () => { + await setState(this, { + showDropDown: false, + }); + this.props.emit({ + type: "SelectSpace", + spaceURL, + }); + }; + + onAddRelay = async () => { + await setState(this, { + showDropDown: false, + }); + this.props.emit({ + type: "ChangeNavigation", + id: "Setting", + }); + }; +} + +function GlobalSearch() { + return
Search
; +} + +function GroupChatList() { + return ( +
+ +
Public
+
+ ); +} + +type DirectMessageListProps = { + //TODO: The list is based on private messages both received and sent, as well as other sources. + profile: Profile_Nostr_Event | undefined; +}; + +// type DirectMessageListState = {}; + +class DirectMessageList extends Component { + render(props: DirectMessageListProps) { + return ( +
+
+
+
+ +
+
+ {props.profile?.profile.name || props.profile?.profile.display_name || + props.profile?.pubkey} +
+
+
+
+ +
+
+ {props.profile?.profile.name || props.profile?.profile.display_name || + props.profile?.pubkey} +
+
+
+
+ +
+
+ {props.profile?.profile.name || props.profile?.profile.display_name || + props.profile?.pubkey} +
+
+
+
+ ); + } +} + +type ProfileMenuProps = { + profile: Profile_Nostr_Event | undefined; + emit: emitFunc; +}; + +class ProfileMenu extends Component { + render(props: ProfileMenuProps) { + return ( +
+
{ + props.emit({ + type: "ShowProfileSetting", + }); + }} + > + +
+ {props.profile?.profile.name || props.profile?.profile.display_name || + props.profile?.pubkey} +
+
+
+ ); + } +}