mirror of
https://github.com/BlowaterNostr/blowater.git
synced 2024-10-18 07:33:22 +00:00
Distinguish between DM and GM (#209)
This commit is contained in:
parent
8d15e2b174
commit
2b91bb3b8c
@ -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,
|
||||
|
@ -37,6 +37,7 @@ export function initialModel(): Model {
|
||||
focusedContent: new Map(),
|
||||
hasNewMessages: new Set(),
|
||||
currentSelectedContact: undefined,
|
||||
isGroupMessage: false,
|
||||
},
|
||||
// allUsersInfo: new Map(),
|
||||
editors: editors,
|
||||
|
@ -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
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
@ -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}
|
||||
/>
|
||||
|
1
UI/dm.ts
1
UI/dm.ts
@ -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(
|
||||
|
19
UI/dm.tsx
19
UI/dm.tsx
@ -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,
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -16,6 +16,7 @@ export type SearchPublicKey = {
|
||||
export type SelectConversation = {
|
||||
type: "SelectConversation";
|
||||
pubkey: PublicKey;
|
||||
isGroupChat: boolean;
|
||||
};
|
||||
|
||||
export type SearchModel = {
|
||||
|
@ -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
|
||||
|
@ -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>;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user