/** @jsx h */ import { Fragment, h } from "https://esm.sh/preact@10.17.1"; import { tw } from "https://esm.sh/twind@0.16.16"; import { Avatar } from "./components/avatar.tsx"; import { CenterClass, IconButtonClass, LinearGradientsClass } from "./components/tw.ts"; import { ConversationSummary, sortUserInfo } from "./conversation-list.ts"; import { emitFunc, EventSubscriber } from "../event-bus.ts"; import { SearchUpdate, SelectConversation } from "./search_model.ts"; import { PublicKey } from "../lib/nostr-ts/key.ts"; import { PinConversation, UnpinConversation } from "../nostr.ts"; import { ErrorColor, PrimaryTextColor, SecondaryBackgroundColor } from "./style/colors.ts"; import { ChatIcon } from "./icons/chat-icon.tsx"; import { StartCreateGroupChat } from "./create-group.tsx"; import { GroupIcon } from "./icons/group-icon.tsx"; import { Component } from "https://esm.sh/preact@10.17.1"; import { UI_Interaction_Event } from "./app_update.tsx"; import { ProfileData } from "../features/profile.ts"; import { ProfileGetter } from "./search.tsx"; import { IS_BETA_VERSION } from "./config.js"; import { UnpinIcon } from "./icons/unpin-icon.tsx"; import { PinIcon } from "./icons/pin-icon.tsx"; export interface ConversationListRetriever { getContacts: () => Iterable; getStrangers: () => Iterable; getConversationType(pubkey: PublicKey, isGourpChat: boolean): "Contacts" | "Strangers" | "Group"; } export interface GroupMessageListGetter { getConversationList: () => Iterable; } export type ConversationType = "Contacts" | "Strangers" | "Group"; export type ContactUpdate = | SearchUpdate | PinConversation | UnpinConversation | StartCreateGroupChat; export interface NewMessageChecker { has(hex: string, isGourpChat: boolean): number; } type Props = { emit: emitFunc; eventBus: EventSubscriber; convoListRetriever: ConversationListRetriever; groupChatListGetter: GroupMessageListGetter; hasNewMessages: NewMessageChecker; pinListGetter: PinListGetter; profileGetter: ProfileGetter; }; type State = { selectedContactGroup: ConversationType; currentSelected: SelectConversation | undefined; }; export class ConversationList extends Component { constructor() { super(); } async componentDidMount() { for await (const e of this.props.eventBus.onChange()) { if (e.type == "SelectConversation") { this.setState({ currentSelected: e, selectedContactGroup: this.props.convoListRetriever.getConversationType( e.pubkey, e.isGroupChat, ), }); } } } state: Readonly = { selectedContactGroup: "Contacts", currentSelected: undefined, }; render(props: Props) { let listToRender: ConversationSummary[]; const contacts = Array.from(props.convoListRetriever.getContacts()); const strangers = Array.from(props.convoListRetriever.getStrangers()); const groups = Array.from(props.groupChatListGetter.getConversationList()); let isGroupChat = false; switch (this.state.selectedContactGroup) { case "Contacts": listToRender = contacts; break; case "Strangers": listToRender = strangers; break; case "Group": listToRender = groups; isGroupChat = true; break; } const convoListToRender = []; for (const conversationSummary of listToRender) { convoListToRender.push({ conversation: conversationSummary, newMessageCount: props.hasNewMessages.has(conversationSummary.pubkey.hex, isGroupChat), }); } return (
{IS_BETA_VERSION ? (
) : undefined}
  • this.setState({ selectedContactGroup: "Contacts" })} > Contacts: {contacts.length}
  • { this.setState({ selectedContactGroup: "Strangers", }); }} > Strangers: {strangers.length}
  • {IS_BETA_VERSION ? (
  • { this.setState({ selectedContactGroup: "Group", }); }} > Group: {groups.length}
  • ) : undefined}
); } } export interface PinListGetter { getPinList(): Set; // get a set of npubs that are pinned } type ConversationListProps = { contacts: { conversation: ConversationSummary; newMessageCount: number }[]; currentSelected: SelectConversation | undefined; pinListGetter: PinListGetter; isGroupChat: boolean; emit: emitFunc; profileGetter: ProfileGetter; }; function ContactGroup(props: ConversationListProps) { props.contacts.sort((a, b) => { return sortUserInfo(a.conversation, b.conversation); }); const pinList = props.pinListGetter.getPinList(); const pinned = []; const unpinned = []; for (const contact of props.contacts) { if (pinList.has(contact.conversation.pubkey.hex)) { pinned.push(contact); } else { unpinned.push(contact); } } // console.log("ContactGroup", Date.now() - t); return (
    {pinned.map((contact) => { let profile; const profileEvent = props.profileGetter.getProfilesByPublicKey(contact.conversation.pubkey); if (profileEvent) { profile = profileEvent.profile; } return (
  • ); })} {unpinned.map((contact) => { let profile; const profileEvent = props.profileGetter.getProfilesByPublicKey(contact.conversation.pubkey); if (profileEvent) { profile = profileEvent.profile; } return (
  • ); })}
); } type ListItemProps = { conversation: ConversationSummary; newMessageCount: number; isPinned: boolean; profile: ProfileData | undefined; }; function ConversationListItem(props: ListItemProps) { return (

{props.profile?.name || props.conversation.pubkey.bech32()}

{props.conversation.newestEventReceivedByMe !== undefined ? (

{new Date( props.conversation.newestEventReceivedByMe .created_at * 1000, ).toLocaleString()}

) : undefined} {props.newMessageCount > 0 ? ( {props.newMessageCount} ) : undefined} {props.isPinned ? ( ) : undefined}
); } const selectConversation = (emit: emitFunc, pubkey: PublicKey, isGroupChat: boolean) => () => { emit({ type: "SelectConversation", pubkey, isGroupChat, }); };