Distinguish between DM and GM (#209)

This commit is contained in:
BlowaterNostr 2023-10-04 21:34:43 +00:00 committed by GitHub
parent 8d15e2b174
commit 2b91bb3b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 91 additions and 50 deletions

View File

@ -386,8 +386,8 @@ export function AppComponent(props: {
editors: model.editors,
...model.dm,
rightPanelModel: model.rightPanelModel,
emit: app.eventBus.emit,
myAccountContext: myAccountCtx,
bus: app.eventBus,
ctx: myAccountCtx,
db: app.database,
pool: props.pool,
conversationLists: app.conversationLists,

View File

@ -37,6 +37,7 @@ export function initialModel(): Model {
focusedContent: new Map(),
hasNewMessages: new Set(),
currentSelectedContact: undefined,
isGroupMessage: false,
},
// allUsersInfo: new Map(),
editors: editors,

View File

@ -196,6 +196,7 @@ export async function* UI_Interaction_Update(args: {
model.dm.focusedContent.set(event.pubkey.hex, event.pubkey);
}
app.popOverInputChan.put({ children: undefined });
app.model.dm.isGroupMessage = event.isGroupChat;
} else if (event.type == "BackToContactList") {
model.dm.currentSelectedContact = undefined;
} else if (event.type == "PinConversation") {
@ -650,6 +651,7 @@ export async function* Database_Update(
emit({
type: "SelectConversation",
pubkey: k,
isGroupChat: false, // todo
});
},
);

View File

@ -60,7 +60,10 @@ export class ConversationLists implements ConversationListRetriever, GroupChatLi
}
}
getConversationType(pubkey: PublicKey) {
getConversationType(pubkey: PublicKey, isGroupChat: boolean) {
if (isGroupChat) {
return "Group";
}
const contact = this.convoSummaries.get(pubkey.hex);
if (contact == undefined) {
return "Strangers";

View File

@ -4,9 +4,9 @@ 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 } from "../event-bus.ts";
import { emitFunc, EventSubscriber } from "../event-bus.ts";
import { PinIcon, UnpinIcon } from "./icons/mod.tsx";
import { SearchUpdate } from "./search_model.ts";
import { SearchUpdate, SelectConversation } from "./search_model.ts";
import { PublicKey } from "../lib/nostr-ts/key.ts";
import { PinConversation, UnpinConversation } from "../nostr.ts";
import { PrimaryTextColor } from "./style/colors.ts";
@ -15,10 +15,12 @@ import { ChatIcon } from "./icons2/chat-icon.tsx";
import { StartCreateGroupChat } from "./create-group.tsx";
import { GroupIcon } from "./icons2/group-icon.tsx";
import { Component } from "https://esm.sh/preact@10.17.1";
import { UI_Interaction_Event } from "./app_update.tsx";
export interface ConversationListRetriever extends GroupChatListGetter {
getContacts: () => Iterable<ConversationSummary>;
getStrangers: () => Iterable<ConversationSummary>;
getConversationType(pubkey: PublicKey, isGourpChat: boolean): "Contacts" | "Strangers" | "Group";
}
export interface GroupChatListGetter {
@ -35,14 +37,15 @@ export type ContactUpdate =
type Props = {
emit: emitFunc<ContactUpdate | SearchUpdate>;
eventBus: EventSubscriber<UI_Interaction_Event>;
convoListRetriever: ConversationListRetriever;
currentSelected: PublicKey | undefined;
pinListGetter: PinListGetter;
hasNewMessages: Set<string>;
};
type State = {
selectedContactGroup: ConversationType;
currentSelected: SelectConversation | undefined;
};
export class ConversationList extends Component<Props, State> {
@ -50,8 +53,23 @@ export class ConversationList extends Component<Props, State> {
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<State> = {
selectedContactGroup: "Contacts",
currentSelected: undefined,
};
render(props: Props) {
@ -166,8 +184,9 @@ export class ConversationList extends Component<Props, State> {
<ContactGroup
contacts={Array.from(convoListToRender.values())}
currentSelected={props.currentSelected}
currentSelected={this.state.currentSelected}
pinListGetter={props.pinListGetter}
isGroupChat={listToRender === groups}
emit={props.emit}
/>
</div>
@ -181,13 +200,13 @@ export interface PinListGetter {
type ConversationListProps = {
contacts: { conversation: ConversationSummary; isMarked: boolean }[];
currentSelected: PublicKey | undefined;
currentSelected: SelectConversation | undefined;
pinListGetter: PinListGetter;
isGroupChat: boolean;
emit: emitFunc<ContactUpdate>;
};
function ContactGroup(props: ConversationListProps) {
const t = Date.now();
props.contacts.sort((a, b) => {
return sortUserInfo(a.conversation, b.conversation);
});
@ -209,16 +228,16 @@ function ContactGroup(props: ConversationListProps) {
<li
class={tw`${
props.currentSelected && contact.conversation.pubkey.hex ===
props.currentSelected.hex
props.currentSelected.pubkey.hex &&
props.isGroupChat == props.currentSelected.isGroupChat
? "bg-[#42464D] text-[#FFFFFF]"
: "bg-[#42464D] text-[#96989D]"
} cursor-pointer p-2 hover:bg-[#3C3F45] my-2 rounded-lg flex items-center w-full relative group`}
onClick={() => {
props.emit({
type: "SelectConversation",
pubkey: contact.conversation.pubkey,
});
}}
onClick={selectConversation(
props.emit,
contact.conversation.pubkey,
props.isGroupChat,
)}
>
<ConversationListItem
conversation={contact.conversation}
@ -255,16 +274,16 @@ function ContactGroup(props: ConversationListProps) {
<li
class={tw`${
props.currentSelected && contact.conversation?.pubkey.hex ===
props.currentSelected.hex
props.currentSelected.pubkey.hex &&
props.isGroupChat == props.currentSelected.isGroupChat
? "bg-[#42464D] text-[#FFFFFF]"
: "bg-transparent text-[#96989D]"
} cursor-pointer p-2 hover:bg-[#3C3F45] my-2 rounded-lg flex items-center w-full relative group`}
onClick={() => {
props.emit({
type: "SelectConversation",
pubkey: contact.conversation.pubkey,
});
}}
onClick={selectConversation(
props.emit,
contact.conversation.pubkey,
props.isGroupChat,
)}
>
<ConversationListItem
conversation={contact.conversation}
@ -357,3 +376,12 @@ function ConversationListItem(props: ListItemProps) {
</Fragment>
);
}
const selectConversation =
(emit: emitFunc<SelectConversation>, pubkey: PublicKey, isGroupChat: boolean) => () => {
emit({
type: "SelectConversation",
pubkey,
isGroupChat,
});
};

View File

@ -69,7 +69,7 @@ const view = () => {
db={database}
eventSyncer={new EventSyncer(pool, database)}
profilesSyncer={new ProfileSyncer(database, pool)}
emit={testEventBus.emit}
bus={testEventBus.emit}
rightPanelModel={{
show: true,
}}
@ -77,7 +77,7 @@ const view = () => {
currentSelectedContact={model.dm.currentSelectedContact}
focusedContent={model.dm.focusedContent}
hasNewMessages={model.dm.hasNewMessages}
myAccountContext={ctx}
ctx={ctx}
pool={pool}
selectedContactGroup={model.dm.selectedContactGroup}
/>

View File

@ -13,6 +13,7 @@ export type DM_Model = {
currentSelectedContact: PublicKey | undefined;
focusedContent: Map<string, NostrEvent /* thread root event */ | PublicKey /* selected user profile */>;
hasNewMessages: Set<string>;
isGroupMessage: boolean;
};
export function convertEventsToChatMessages(

View File

@ -4,7 +4,7 @@ import { tw } from "https://esm.sh/twind@0.16.16";
import * as cl from "./conversation-list.tsx";
import { Database_Contextual_View } from "../database.ts";
import { MessagePanel, RightPanelModel } from "./message-panel.tsx";
import { emitFunc, EventBus } from "../event-bus.ts";
import { EventBus } from "../event-bus.ts";
import { LeftArrowIcon } from "./icons/left-arrow-icon.tsx";
import { CenterClass, IconButtonClass } from "./components/tw.ts";
import { DM_EditorModel } from "./editor.tsx";
@ -25,9 +25,9 @@ import { GroupChatController } from "../group-chat.ts";
type DirectMessageContainerProps = {
editors: Map<string, DM_EditorModel>;
rightPanelModel: RightPanelModel;
myAccountContext: NostrAccountContext;
ctx: NostrAccountContext;
pool: ConnectionPool;
emit: emitFunc<UI_Interaction_Event>;
bus: EventBus<UI_Interaction_Event>;
db: Database_Contextual_View;
conversationLists: ConversationLists;
profilesSyncer: ProfileSyncer;
@ -102,10 +102,10 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
return _;
})();
messagePanel = new MessagePanel({
myPublicKey: props.myAccountContext.publicKey,
myPublicKey: props.ctx.publicKey,
messages: convoMsgs,
rightPanelModel: props.rightPanelModel,
emit: props.emit,
emit: props.bus.emit,
editorModel: currentEditorModel,
focusedContent: focusedContent,
db: props.db,
@ -114,7 +114,7 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
allUserInfo: props.conversationLists.convoSummaries,
}).render();
}
const canEditGroupProfile = currentConversation &&
const canEditGroupProfile = currentConversation && props.isGroupMessage &&
props.groupChatController.getGroupAdminCtx(currentConversation);
const vDom = (
@ -123,7 +123,8 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
>
<div class={tw`${currentConversation ? "mobile:hidden" : "mobile:w-full"}`}>
<cl.ConversationList
currentSelected={currentConversation}
eventBus={props.bus}
emit={props.bus.emit}
convoListRetriever={props.conversationLists}
{...props}
/>
@ -137,7 +138,7 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
<div class={tw`flex items-center`}>
<button
onClick={() => {
props.emit({
props.bus.emit({
type: "BackToContactList",
});
}}
@ -161,7 +162,7 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
<button
class={tw`w-8 h-8 ${CenterClass}`}
onClick={() => {
props.emit({
props.bus.emit({
type: "StartEditGroupChatProfile",
publicKey: currentConversation,
});

View File

@ -6,7 +6,6 @@ import { InMemoryAccountContext, NostrKind } from "../lib/nostr-ts/nostr.ts";
import { Database_Contextual_View } from "../database.ts";
import { testEventBus, testEventsAdapter } from "./_setup.test.ts";
import { prepareNormalNostrEvent } from "../lib/nostr-ts/event.ts";
import { getSocialPosts } from "../features/social.ts";
import { ConversationLists } from "./conversation-list.ts";
import { EventSyncer } from "./event_syncer.ts";
import { ConnectionPool } from "../lib/nostr-ts/relay.ts";
@ -25,25 +24,32 @@ const lamport = new LamportTime(0);
await database.addEvent(await prepareNormalNostrEvent(ctx, NostrKind.TEXT_NOTE, [], `hi`));
await database.addEvent(await prepareNormalNostrEvent(ctx, NostrKind.TEXT_NOTE, [], `hi 2`));
await database.addEvent(await prepareNormalNostrEvent(ctx, NostrKind.TEXT_NOTE, [], `hi 3`));
const allUserInfo = new ConversationLists(ctx);
const allUserInfo = new ConversationLists(ctx, new ProfileSyncer(database, new ConnectionPool()));
const pool = new ConnectionPool();
const model = initialModel();
pool.addRelayURL(relays[0]);
const editor = model.editors.get(ctx.publicKey.hex);
const view = () => {
const threads = getSocialPosts(database, allUserInfo.convoSummaries);
console.log(database.events, threads);
if (editor == undefined) {
return undefined;
}
return (
<MessagePanel
allUserInfo={allUserInfo.convoSummaries}
db={database}
editorModel={model.social.editor}
/**
* If we use a map to store all editor models,
* need to distinguish editor models for DMs and GMs
*/
editorModel={editor}
eventSyncer={new EventSyncer(pool, database)}
focusedContent={undefined}
myPublicKey={ctx.publicKey}
profilesSyncer={new ProfileSyncer(database, pool)}
emit={testEventBus.emit}
messages={threads}
messages={[]}
rightPanelModel={{
show: true,
}}
@ -62,8 +68,6 @@ for await (const e of testEventBus.onChange()) {
lamport,
pool,
model.editors,
model.social.editor,
model.social.replyEditors,
database,
);
if (err instanceof Error) {
@ -71,7 +75,6 @@ for await (const e of testEventBus.onChange()) {
continue; // todo: global error toast
}
} else if (e.type == "UpdateMessageText") {
model.social.editor.text = e.text;
}
render(view(), document.body);
}

View File

@ -90,7 +90,6 @@ interface DirectMessagePanelProps {
allUserInfo: Map<string, ConversationSummary>;
}
// export function MessagePanel(props: DirectMessagePanelProps) {
export class MessagePanel extends Component<DirectMessagePanelProps> {
render() {
const props = this.props;

View File

@ -1,7 +1,7 @@
/** @jsx h */
import { Fragment, h } from "https://esm.sh/preact@10.17.1";
import { tw } from "https://esm.sh/twind@0.16.16";
import { emitFunc, EventEmitter } from "../event-bus.ts";
import { emitFunc } from "../event-bus.ts";
import {
DirectMessagePanelUpdate,
NameAndTime,

View File

@ -82,6 +82,7 @@ export class Search extends Component<Props, State> {
this.props.emit({
type: "SelectConversation",
pubkey: profile instanceof PublicKey ? profile : profile.publicKey,
isGroupChat: false, // todo
});
};

View File

@ -16,6 +16,7 @@ export type SearchPublicKey = {
export type SelectConversation = {
type: "SelectConversation";
pubkey: PublicKey;
isGroupChat: boolean;
};
export type SearchModel = {

View File

@ -65,9 +65,6 @@ export class Database_Contextual_View implements DirectMessageGetter {
const initialEvents:
(Text_Note_Event | NostrEvent<NostrKind.DIRECT_MESSAGE> | Profile_Nostr_Event)[] =
await loadInitialData(allEvents, eventsAdapter);
if (initialEvents instanceof Error) {
return initialEvents;
}
console.log("Database_Contextual_View:parsed", Date.now() - t);
// Load DMs

View File

@ -1,6 +1,6 @@
import { chan, multi } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
import { chan, Channel, multi } from "https://raw.githubusercontent.com/BlowaterNostr/csp/master/csp.ts";
export class EventBus<T> implements EventEmitter<T> {
export class EventBus<T> implements EventEmitter<T>, EventSubscriber<T> {
private readonly c = chan<T>();
private readonly caster = multi<T>(this.c);
@ -17,3 +17,7 @@ export type EventEmitter<T> = {
emit: (event: T) => void;
};
export type emitFunc<T extends { type: string }> = (event: T) => void;
export type EventSubscriber<T> = {
onChange(): Channel<T>;
};