mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
new conversation list (#496)
Co-authored-by: BlowaterNostr <blowater.nostr@proton.me>
This commit is contained in:
parent
f037c3adbc
commit
9b7373de99
@ -2,7 +2,7 @@
|
||||
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";
|
||||
import { ConnectionPool, InMemoryAccountContext, PublicKey } from "@blowater/nostr-sdk";
|
||||
|
||||
const pool = new ConnectionPool();
|
||||
await pool.addRelayURLs(
|
||||
@ -14,22 +14,22 @@ await pool.addRelayURLs(
|
||||
"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",
|
||||
"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();
|
||||
@ -42,13 +42,29 @@ const profileEvent = await prepareProfileEvent(ctx, {
|
||||
picture: "https://image.nostr.build/655007ae74f24ea1c611889f48b25cb485b83ab67408daddd98f95782f47e1b5.jpg",
|
||||
});
|
||||
|
||||
let currentConversation: PublicKey | undefined;
|
||||
const convoList = new Set<PublicKey>();
|
||||
const pinList = new Set<PublicKey>();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const pubkey = InMemoryAccountContext.Generate().publicKey;
|
||||
if (i % 4 == 0) pinList.add(pubkey);
|
||||
if (i == 5) currentConversation = pubkey;
|
||||
convoList.add(pubkey);
|
||||
}
|
||||
|
||||
render(
|
||||
<NewNav
|
||||
currentSpace="wss://blowater.nostr1.com"
|
||||
emit={testEventBus.emit}
|
||||
pool={pool}
|
||||
activeNav="DM"
|
||||
activeNav={"Public"}
|
||||
profile={profileEvent}
|
||||
currentConversation={currentConversation}
|
||||
getters={{
|
||||
getProfileByPublicKey: () => profileEvent,
|
||||
getConversationList: () => convoList,
|
||||
getPinList: () => pinList,
|
||||
}}
|
||||
/>,
|
||||
document.body,
|
||||
);
|
||||
|
@ -1,35 +1,38 @@
|
||||
/** @jsx h */
|
||||
import { Component, Fragment, h } from "preact";
|
||||
import { emitFunc, EventSubscriber } from "../event-bus.ts";
|
||||
import { Component, h } from "preact";
|
||||
import { emitFunc } 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 {
|
||||
ConnectionPool,
|
||||
getRelayInformation,
|
||||
PublicKey,
|
||||
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 { ContactUpdate } from "./conversation-list.tsx";
|
||||
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";
|
||||
import { func_GetProfileByPublicKey } from "./search.tsx";
|
||||
import { PinIcon } from "./icons/pin-icon.tsx";
|
||||
import { SelectConversation } from "./search_model.ts";
|
||||
|
||||
type NewNavProps = {
|
||||
pool: ConnectionPool;
|
||||
activeNav: NavTabID;
|
||||
currentSpace: string;
|
||||
profile: Profile_Nostr_Event | undefined;
|
||||
currentConversation: PublicKey | undefined;
|
||||
getters: {
|
||||
getProfileByPublicKey: func_GetProfileByPublicKey;
|
||||
getConversationList: func_GetConversationList;
|
||||
getPinList: func_GetPinList;
|
||||
};
|
||||
emit: emitFunc<
|
||||
| SelectSpace
|
||||
| NavigationUpdate
|
||||
@ -39,22 +42,26 @@ type NewNavProps = {
|
||||
| TagSelected
|
||||
| ViewUserDetail
|
||||
>;
|
||||
profile: Profile_Nostr_Event | undefined;
|
||||
};
|
||||
|
||||
export class NewNav extends Component<NewNavProps> {
|
||||
render(props: NewNavProps) {
|
||||
return (
|
||||
<div class="h-screen w-64 flex flex-col gap-y-4 overflow-y-auto bg-neutral-900 p-2 items-center">
|
||||
<div class="h-screen w-64 flex flex-col gap-y-4 overflow-y-auto bg-neutral-900 p-2 items-center select-none">
|
||||
<SpaceDropDownPanel
|
||||
currentSpace={props.currentSpace}
|
||||
spaceList={new Set(Array.from(props.pool.getRelays()).map((r) => r.url))}
|
||||
emit={props.emit}
|
||||
/>
|
||||
{/* <GlobalSearch /> */}
|
||||
<GroupChatList />
|
||||
<DirectMessageList profile={props.profile} />
|
||||
<ProfileMenu profile={props.profile} emit={props.emit} />
|
||||
<GroupChatList emit={props.emit} activeNav={props.activeNav} />
|
||||
<DirectMessageList
|
||||
emit={props.emit}
|
||||
currentSpace={props.currentSpace}
|
||||
currentConversation={props.currentConversation}
|
||||
getters={props.getters}
|
||||
/>
|
||||
<UserIndicator profile={props.profile} emit={props.emit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -111,13 +118,13 @@ class SpaceDropDownPanel extends Component<SpaceDropDownPanelProps, SpaceDropDow
|
||||
}
|
||||
return (
|
||||
<div class="w-full">
|
||||
{this.TopIconButton()}
|
||||
{this.CurrentSpaceIndicator()}
|
||||
{this.state.showDropDown ? this.DropDown(spaceList) : undefined}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TopIconButton = () => {
|
||||
CurrentSpaceIndicator = () => {
|
||||
return (
|
||||
<div
|
||||
class="flex flex-row items-center gap-1 p-1 rounded cursor-pointer hover:bg-neutral-500"
|
||||
@ -139,7 +146,9 @@ class SpaceDropDownPanel extends Component<SpaceDropDownPanelProps, SpaceDropDow
|
||||
: <div>current space url</div>}
|
||||
</div>
|
||||
<div class="w-8 h-8 flex justify-center items-center rounded">
|
||||
<CaretDownIcon class="w-6 h-6 text-neutral-600" />
|
||||
{this.state.showDropDown
|
||||
? <CaretDownIcon class="w-6 h-6 text-neutral-600 transition-transform -rotate-180" />
|
||||
: <CaretDownIcon class="w-6 h-6 text-neutral-600 transition-transform" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -147,7 +156,7 @@ class SpaceDropDownPanel extends Component<SpaceDropDownPanelProps, SpaceDropDow
|
||||
|
||||
DropDown = (spaceList: h.JSX.Element[]) => {
|
||||
return (
|
||||
<div class="absolute z-10 min-w-64 rounded-lg bg-neutral-700 text-white p-4">
|
||||
<div class="absolute z-10 min-w-64 rounded-lg bg-neutral-700 text-white p-4 mt-1">
|
||||
{this.SettingsButton()}
|
||||
{/* {this.InviteButton()} */}
|
||||
<div class="border border-neutral-600 my-3"></div>
|
||||
@ -296,67 +305,148 @@ function GlobalSearch() {
|
||||
return <div>Search</div>;
|
||||
}
|
||||
|
||||
function GroupChatList() {
|
||||
return (
|
||||
<div class="flex items-center justify-start gap-1 w-full p-2">
|
||||
<PoundIcon class="w-4 h-4 text-neutral-500" />
|
||||
<div class="text-white text-sm font-medium font-sans leading-5">Public</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 GroupChatListProps = {
|
||||
activeNav: NavTabID;
|
||||
emit: emitFunc<NavigationUpdate>;
|
||||
};
|
||||
|
||||
// type DirectMessageListState = {};
|
||||
class GroupChatList extends Component<GroupChatListProps> {
|
||||
render(props: GroupChatListProps) {
|
||||
return (
|
||||
<div
|
||||
class={`flex items-center justify-start gap-1 w-full p-2 rounded-md px-2 py-1 hover:bg-neutral-950 cursor-pointer ${
|
||||
props.activeNav == "Public" ? "bg-neutral-800" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
props.emit({
|
||||
type: "ChangeNavigation",
|
||||
id: "Public",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PoundIcon class="w-4 h-4 text-neutral-500" />
|
||||
<div class="text-white text-sm font-medium font-sans leading-5">Public</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type func_GetConversationList = () => Set<PublicKey>;
|
||||
type func_GetPinList = () => Set<PublicKey>;
|
||||
|
||||
type DirectMessageListProps = {
|
||||
emit: emitFunc<ContactUpdate>;
|
||||
currentSpace: string;
|
||||
currentConversation: PublicKey | undefined;
|
||||
getters: {
|
||||
getProfileByPublicKey: func_GetProfileByPublicKey;
|
||||
getConversationList: func_GetConversationList;
|
||||
getPinList: func_GetPinList;
|
||||
};
|
||||
};
|
||||
|
||||
class DirectMessageList extends Component<DirectMessageListProps> {
|
||||
render(props: DirectMessageListProps) {
|
||||
const pinList = props.getters.getPinList();
|
||||
const pinned = [];
|
||||
const unpinned = [];
|
||||
for (const pubkey of props.getters.getConversationList()) {
|
||||
if (pinList.has(pubkey)) {
|
||||
pinned.push(pubkey);
|
||||
} else {
|
||||
unpinned.push(pubkey);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div class="flex-1 w-full">
|
||||
<div class="flex flex-col gap-2 w-full justify-start items-center">
|
||||
<div class="flex gap-2 rounded-md px-2 py-1 bg-neutral-800 w-full">
|
||||
<div class="w-4 flex justify-center items-center">
|
||||
<Avatar picture={props.profile?.profile.picture || "./logo.webp"} />
|
||||
</div>
|
||||
<div class="text-white text-sm font-medium font-sans leading-5">
|
||||
{props.profile?.profile.name || props.profile?.profile.display_name ||
|
||||
props.profile?.pubkey}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 rounded-md px-2 py-1 w-full">
|
||||
<div class="w-4 flex justify-center items-center">
|
||||
<Avatar picture={props.profile?.profile.picture || "./logo.webp"} />
|
||||
</div>
|
||||
<div class="text-white text-sm font-medium font-sans leading-5">
|
||||
{props.profile?.profile.name || props.profile?.profile.display_name ||
|
||||
props.profile?.pubkey}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 rounded-md px-2 py-1 w-full">
|
||||
<div class="w-4 flex justify-center items-center">
|
||||
<Avatar picture={props.profile?.profile.picture || "./logo.webp"} />
|
||||
</div>
|
||||
<div class="text-white text-sm font-medium font-sans leading-5">
|
||||
{props.profile?.profile.name || props.profile?.profile.display_name ||
|
||||
props.profile?.pubkey}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 w-full overflow-y-auto">
|
||||
<div class="flex flex-col gap-1 w-full justify-start items-center">
|
||||
{pinned.map((pubkey: PublicKey) => (
|
||||
<DireactMessageItem
|
||||
emit={props.emit}
|
||||
pubkey={pubkey}
|
||||
isPined={true}
|
||||
currentConversation={props.currentConversation}
|
||||
currentSpace={props.currentSpace}
|
||||
getters={props.getters}
|
||||
/>
|
||||
))}
|
||||
{unpinned.map((pubkey: PublicKey) => (
|
||||
<DireactMessageItem
|
||||
emit={props.emit}
|
||||
pubkey={pubkey}
|
||||
isPined={false}
|
||||
currentConversation={props.currentConversation}
|
||||
currentSpace={props.currentSpace}
|
||||
getters={props.getters}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type ProfileMenuProps = {
|
||||
type DireactMessageItemProps = {
|
||||
pubkey: PublicKey;
|
||||
emit: emitFunc<SelectConversation>;
|
||||
isPined: boolean;
|
||||
currentSpace: string;
|
||||
currentConversation: PublicKey | undefined;
|
||||
getters: {
|
||||
getProfileByPublicKey: func_GetProfileByPublicKey;
|
||||
};
|
||||
};
|
||||
|
||||
class DireactMessageItem extends Component<DireactMessageItemProps> {
|
||||
render(props: DireactMessageItemProps) {
|
||||
const profile = props.getters.getProfileByPublicKey(props.pubkey, new URL(props.currentSpace))
|
||||
?.profile;
|
||||
const picture = profile?.picture || "./logo.webp";
|
||||
const name = profile?.name || profile?.display_name || profile?.pubkey;
|
||||
return (
|
||||
<div
|
||||
class={`flex gap-2 rounded-md px-2 py-1 w-full hover:bg-neutral-950 cursor-pointer ${
|
||||
props.pubkey.hex == props.currentConversation?.hex ? "bg-neutral-800" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
props.emit({
|
||||
type: "SelectConversation",
|
||||
pubkey: props.pubkey,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div class="w-4 flex justify-center items-center">
|
||||
<Avatar picture={picture} />
|
||||
</div>
|
||||
<div class="text-white text-sm font-medium font-sans leading-5 flex-1">
|
||||
{name}
|
||||
</div>
|
||||
{props.isPined
|
||||
? (
|
||||
<div class="flex justify-center items-center">
|
||||
<PinIcon
|
||||
class={`w-3 h-3`}
|
||||
style={{
|
||||
fill: "rgb(185, 187, 190)",
|
||||
stroke: "rgb(185, 187, 190)",
|
||||
strokeWidth: 2,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: undefined}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type UserIndicatorProps = {
|
||||
profile: Profile_Nostr_Event | undefined;
|
||||
emit: emitFunc<ShowProfileSetting>;
|
||||
};
|
||||
|
||||
class ProfileMenu extends Component<ProfileMenuProps> {
|
||||
render(props: ProfileMenuProps) {
|
||||
class UserIndicator extends Component<UserIndicatorProps> {
|
||||
render(props: UserIndicatorProps) {
|
||||
return (
|
||||
<div class="w-full">
|
||||
<div
|
||||
|
Loading…
Reference in New Issue
Block a user