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, editors: model.editors,
...model.dm, ...model.dm,
rightPanelModel: model.rightPanelModel, rightPanelModel: model.rightPanelModel,
emit: app.eventBus.emit, bus: app.eventBus,
myAccountContext: myAccountCtx, ctx: myAccountCtx,
db: app.database, db: app.database,
pool: props.pool, pool: props.pool,
conversationLists: app.conversationLists, conversationLists: app.conversationLists,

View File

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

View File

@ -196,6 +196,7 @@ export async function* UI_Interaction_Update(args: {
model.dm.focusedContent.set(event.pubkey.hex, event.pubkey); model.dm.focusedContent.set(event.pubkey.hex, event.pubkey);
} }
app.popOverInputChan.put({ children: undefined }); app.popOverInputChan.put({ children: undefined });
app.model.dm.isGroupMessage = event.isGroupChat;
} else if (event.type == "BackToContactList") { } else if (event.type == "BackToContactList") {
model.dm.currentSelectedContact = undefined; model.dm.currentSelectedContact = undefined;
} else if (event.type == "PinConversation") { } else if (event.type == "PinConversation") {
@ -650,6 +651,7 @@ export async function* Database_Update(
emit({ emit({
type: "SelectConversation", type: "SelectConversation",
pubkey: k, 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); const contact = this.convoSummaries.get(pubkey.hex);
if (contact == undefined) { if (contact == undefined) {
return "Strangers"; 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 { Avatar } from "./components/avatar.tsx";
import { CenterClass, IconButtonClass, LinearGradientsClass } from "./components/tw.ts"; import { CenterClass, IconButtonClass, LinearGradientsClass } from "./components/tw.ts";
import { ConversationSummary, sortUserInfo } from "./conversation-list.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 { 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 { PublicKey } from "../lib/nostr-ts/key.ts";
import { PinConversation, UnpinConversation } from "../nostr.ts"; import { PinConversation, UnpinConversation } from "../nostr.ts";
import { PrimaryTextColor } from "./style/colors.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 { StartCreateGroupChat } from "./create-group.tsx";
import { GroupIcon } from "./icons2/group-icon.tsx"; import { GroupIcon } from "./icons2/group-icon.tsx";
import { Component } from "https://esm.sh/preact@10.17.1"; import { Component } from "https://esm.sh/preact@10.17.1";
import { UI_Interaction_Event } from "./app_update.tsx";
export interface ConversationListRetriever extends GroupChatListGetter { export interface ConversationListRetriever extends GroupChatListGetter {
getContacts: () => Iterable<ConversationSummary>; getContacts: () => Iterable<ConversationSummary>;
getStrangers: () => Iterable<ConversationSummary>; getStrangers: () => Iterable<ConversationSummary>;
getConversationType(pubkey: PublicKey, isGourpChat: boolean): "Contacts" | "Strangers" | "Group";
} }
export interface GroupChatListGetter { export interface GroupChatListGetter {
@ -35,14 +37,15 @@ export type ContactUpdate =
type Props = { type Props = {
emit: emitFunc<ContactUpdate | SearchUpdate>; emit: emitFunc<ContactUpdate | SearchUpdate>;
eventBus: EventSubscriber<UI_Interaction_Event>;
convoListRetriever: ConversationListRetriever; convoListRetriever: ConversationListRetriever;
currentSelected: PublicKey | undefined;
pinListGetter: PinListGetter; pinListGetter: PinListGetter;
hasNewMessages: Set<string>; hasNewMessages: Set<string>;
}; };
type State = { type State = {
selectedContactGroup: ConversationType; selectedContactGroup: ConversationType;
currentSelected: SelectConversation | undefined;
}; };
export class ConversationList extends Component<Props, State> { export class ConversationList extends Component<Props, State> {
@ -50,8 +53,23 @@ export class ConversationList extends Component<Props, State> {
super(); 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> = { state: Readonly<State> = {
selectedContactGroup: "Contacts", selectedContactGroup: "Contacts",
currentSelected: undefined,
}; };
render(props: Props) { render(props: Props) {
@ -166,8 +184,9 @@ export class ConversationList extends Component<Props, State> {
<ContactGroup <ContactGroup
contacts={Array.from(convoListToRender.values())} contacts={Array.from(convoListToRender.values())}
currentSelected={props.currentSelected} currentSelected={this.state.currentSelected}
pinListGetter={props.pinListGetter} pinListGetter={props.pinListGetter}
isGroupChat={listToRender === groups}
emit={props.emit} emit={props.emit}
/> />
</div> </div>
@ -181,13 +200,13 @@ export interface PinListGetter {
type ConversationListProps = { type ConversationListProps = {
contacts: { conversation: ConversationSummary; isMarked: boolean }[]; contacts: { conversation: ConversationSummary; isMarked: boolean }[];
currentSelected: PublicKey | undefined; currentSelected: SelectConversation | undefined;
pinListGetter: PinListGetter; pinListGetter: PinListGetter;
isGroupChat: boolean;
emit: emitFunc<ContactUpdate>; emit: emitFunc<ContactUpdate>;
}; };
function ContactGroup(props: ConversationListProps) { function ContactGroup(props: ConversationListProps) {
const t = Date.now();
props.contacts.sort((a, b) => { props.contacts.sort((a, b) => {
return sortUserInfo(a.conversation, b.conversation); return sortUserInfo(a.conversation, b.conversation);
}); });
@ -209,16 +228,16 @@ function ContactGroup(props: ConversationListProps) {
<li <li
class={tw`${ class={tw`${
props.currentSelected && contact.conversation.pubkey.hex === 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-[#FFFFFF]"
: "bg-[#42464D] text-[#96989D]" : "bg-[#42464D] text-[#96989D]"
} cursor-pointer p-2 hover:bg-[#3C3F45] my-2 rounded-lg flex items-center w-full relative group`} } cursor-pointer p-2 hover:bg-[#3C3F45] my-2 rounded-lg flex items-center w-full relative group`}
onClick={() => { onClick={selectConversation(
props.emit({ props.emit,
type: "SelectConversation", contact.conversation.pubkey,
pubkey: contact.conversation.pubkey, props.isGroupChat,
}); )}
}}
> >
<ConversationListItem <ConversationListItem
conversation={contact.conversation} conversation={contact.conversation}
@ -255,16 +274,16 @@ function ContactGroup(props: ConversationListProps) {
<li <li
class={tw`${ class={tw`${
props.currentSelected && contact.conversation?.pubkey.hex === 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-[#FFFFFF]"
: "bg-transparent text-[#96989D]" : "bg-transparent text-[#96989D]"
} cursor-pointer p-2 hover:bg-[#3C3F45] my-2 rounded-lg flex items-center w-full relative group`} } cursor-pointer p-2 hover:bg-[#3C3F45] my-2 rounded-lg flex items-center w-full relative group`}
onClick={() => { onClick={selectConversation(
props.emit({ props.emit,
type: "SelectConversation", contact.conversation.pubkey,
pubkey: contact.conversation.pubkey, props.isGroupChat,
}); )}
}}
> >
<ConversationListItem <ConversationListItem
conversation={contact.conversation} conversation={contact.conversation}
@ -357,3 +376,12 @@ function ConversationListItem(props: ListItemProps) {
</Fragment> </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} db={database}
eventSyncer={new EventSyncer(pool, database)} eventSyncer={new EventSyncer(pool, database)}
profilesSyncer={new ProfileSyncer(database, pool)} profilesSyncer={new ProfileSyncer(database, pool)}
emit={testEventBus.emit} bus={testEventBus.emit}
rightPanelModel={{ rightPanelModel={{
show: true, show: true,
}} }}
@ -77,7 +77,7 @@ const view = () => {
currentSelectedContact={model.dm.currentSelectedContact} currentSelectedContact={model.dm.currentSelectedContact}
focusedContent={model.dm.focusedContent} focusedContent={model.dm.focusedContent}
hasNewMessages={model.dm.hasNewMessages} hasNewMessages={model.dm.hasNewMessages}
myAccountContext={ctx} ctx={ctx}
pool={pool} pool={pool}
selectedContactGroup={model.dm.selectedContactGroup} selectedContactGroup={model.dm.selectedContactGroup}
/> />

View File

@ -13,6 +13,7 @@ export type DM_Model = {
currentSelectedContact: PublicKey | undefined; currentSelectedContact: PublicKey | undefined;
focusedContent: Map<string, NostrEvent /* thread root event */ | PublicKey /* selected user profile */>; focusedContent: Map<string, NostrEvent /* thread root event */ | PublicKey /* selected user profile */>;
hasNewMessages: Set<string>; hasNewMessages: Set<string>;
isGroupMessage: boolean;
}; };
export function convertEventsToChatMessages( 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 * as cl from "./conversation-list.tsx";
import { Database_Contextual_View } from "../database.ts"; import { Database_Contextual_View } from "../database.ts";
import { MessagePanel, RightPanelModel } from "./message-panel.tsx"; 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 { LeftArrowIcon } from "./icons/left-arrow-icon.tsx";
import { CenterClass, IconButtonClass } from "./components/tw.ts"; import { CenterClass, IconButtonClass } from "./components/tw.ts";
import { DM_EditorModel } from "./editor.tsx"; import { DM_EditorModel } from "./editor.tsx";
@ -25,9 +25,9 @@ import { GroupChatController } from "../group-chat.ts";
type DirectMessageContainerProps = { type DirectMessageContainerProps = {
editors: Map<string, DM_EditorModel>; editors: Map<string, DM_EditorModel>;
rightPanelModel: RightPanelModel; rightPanelModel: RightPanelModel;
myAccountContext: NostrAccountContext; ctx: NostrAccountContext;
pool: ConnectionPool; pool: ConnectionPool;
emit: emitFunc<UI_Interaction_Event>; bus: EventBus<UI_Interaction_Event>;
db: Database_Contextual_View; db: Database_Contextual_View;
conversationLists: ConversationLists; conversationLists: ConversationLists;
profilesSyncer: ProfileSyncer; profilesSyncer: ProfileSyncer;
@ -102,10 +102,10 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
return _; return _;
})(); })();
messagePanel = new MessagePanel({ messagePanel = new MessagePanel({
myPublicKey: props.myAccountContext.publicKey, myPublicKey: props.ctx.publicKey,
messages: convoMsgs, messages: convoMsgs,
rightPanelModel: props.rightPanelModel, rightPanelModel: props.rightPanelModel,
emit: props.emit, emit: props.bus.emit,
editorModel: currentEditorModel, editorModel: currentEditorModel,
focusedContent: focusedContent, focusedContent: focusedContent,
db: props.db, db: props.db,
@ -114,7 +114,7 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
allUserInfo: props.conversationLists.convoSummaries, allUserInfo: props.conversationLists.convoSummaries,
}).render(); }).render();
} }
const canEditGroupProfile = currentConversation && const canEditGroupProfile = currentConversation && props.isGroupMessage &&
props.groupChatController.getGroupAdminCtx(currentConversation); props.groupChatController.getGroupAdminCtx(currentConversation);
const vDom = ( const vDom = (
@ -123,7 +123,8 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
> >
<div class={tw`${currentConversation ? "mobile:hidden" : "mobile:w-full"}`}> <div class={tw`${currentConversation ? "mobile:hidden" : "mobile:w-full"}`}>
<cl.ConversationList <cl.ConversationList
currentSelected={currentConversation} eventBus={props.bus}
emit={props.bus.emit}
convoListRetriever={props.conversationLists} convoListRetriever={props.conversationLists}
{...props} {...props}
/> />
@ -137,7 +138,7 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
<div class={tw`flex items-center`}> <div class={tw`flex items-center`}>
<button <button
onClick={() => { onClick={() => {
props.emit({ props.bus.emit({
type: "BackToContactList", type: "BackToContactList",
}); });
}} }}
@ -161,7 +162,7 @@ export function DirectMessageContainer(props: DirectMessageContainerProps) {
<button <button
class={tw`w-8 h-8 ${CenterClass}`} class={tw`w-8 h-8 ${CenterClass}`}
onClick={() => { onClick={() => {
props.emit({ props.bus.emit({
type: "StartEditGroupChatProfile", type: "StartEditGroupChatProfile",
publicKey: currentConversation, 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 { Database_Contextual_View } from "../database.ts";
import { testEventBus, testEventsAdapter } from "./_setup.test.ts"; import { testEventBus, testEventsAdapter } from "./_setup.test.ts";
import { prepareNormalNostrEvent } from "../lib/nostr-ts/event.ts"; import { prepareNormalNostrEvent } from "../lib/nostr-ts/event.ts";
import { getSocialPosts } from "../features/social.ts";
import { ConversationLists } from "./conversation-list.ts"; import { ConversationLists } from "./conversation-list.ts";
import { EventSyncer } from "./event_syncer.ts"; import { EventSyncer } from "./event_syncer.ts";
import { ConnectionPool } from "../lib/nostr-ts/relay.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`));
await database.addEvent(await prepareNormalNostrEvent(ctx, NostrKind.TEXT_NOTE, [], `hi 2`)); await database.addEvent(await prepareNormalNostrEvent(ctx, NostrKind.TEXT_NOTE, [], `hi 2`));
await database.addEvent(await prepareNormalNostrEvent(ctx, NostrKind.TEXT_NOTE, [], `hi 3`)); 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 pool = new ConnectionPool();
const model = initialModel(); const model = initialModel();
pool.addRelayURL(relays[0]); pool.addRelayURL(relays[0]);
const editor = model.editors.get(ctx.publicKey.hex);
const view = () => { const view = () => {
const threads = getSocialPosts(database, allUserInfo.convoSummaries); if (editor == undefined) {
console.log(database.events, threads); return undefined;
}
return ( return (
<MessagePanel <MessagePanel
allUserInfo={allUserInfo.convoSummaries} allUserInfo={allUserInfo.convoSummaries}
db={database} 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)} eventSyncer={new EventSyncer(pool, database)}
focusedContent={undefined} focusedContent={undefined}
myPublicKey={ctx.publicKey} myPublicKey={ctx.publicKey}
profilesSyncer={new ProfileSyncer(database, pool)} profilesSyncer={new ProfileSyncer(database, pool)}
emit={testEventBus.emit} emit={testEventBus.emit}
messages={threads} messages={[]}
rightPanelModel={{ rightPanelModel={{
show: true, show: true,
}} }}
@ -62,8 +68,6 @@ for await (const e of testEventBus.onChange()) {
lamport, lamport,
pool, pool,
model.editors, model.editors,
model.social.editor,
model.social.replyEditors,
database, database,
); );
if (err instanceof Error) { if (err instanceof Error) {
@ -71,7 +75,6 @@ for await (const e of testEventBus.onChange()) {
continue; // todo: global error toast continue; // todo: global error toast
} }
} else if (e.type == "UpdateMessageText") { } else if (e.type == "UpdateMessageText") {
model.social.editor.text = e.text;
} }
render(view(), document.body); render(view(), document.body);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -65,9 +65,6 @@ export class Database_Contextual_View implements DirectMessageGetter {
const initialEvents: const initialEvents:
(Text_Note_Event | NostrEvent<NostrKind.DIRECT_MESSAGE> | Profile_Nostr_Event)[] = (Text_Note_Event | NostrEvent<NostrKind.DIRECT_MESSAGE> | Profile_Nostr_Event)[] =
await loadInitialData(allEvents, eventsAdapter); await loadInitialData(allEvents, eventsAdapter);
if (initialEvents instanceof Error) {
return initialEvents;
}
console.log("Database_Contextual_View:parsed", Date.now() - t); console.log("Database_Contextual_View:parsed", Date.now() - t);
// Load DMs // 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 c = chan<T>();
private readonly caster = multi<T>(this.c); private readonly caster = multi<T>(this.c);
@ -17,3 +17,7 @@ export type EventEmitter<T> = {
emit: (event: T) => void; emit: (event: T) => void;
}; };
export type emitFunc<T extends { type: string }> = (event: T) => void; export type emitFunc<T extends { type: string }> = (event: T) => void;
export type EventSubscriber<T> = {
onChange(): Channel<T>;
};