implement contacts with messaging

This commit is contained in:
marlon-erler
2026-09-27 03:01:43 +02:00
parent c5d61a5790
commit a9fba4646d
8 changed files with 118 additions and 98 deletions
+4 -3
View File
@@ -37,9 +37,10 @@ export function ChatMessage(
{InlineReply(chatMessageViewModel)} {InlineReply(chatMessageViewModel)}
<div class="main tile"> <div class="main tile">
<div class="text-container"> <div class="text-container">
<span class="sender-name ellipsis"> <span
{chatMessageViewModel.senderName} class="sender-name ellipsis"
</span> subscribe:innerText={chatMessageViewModel.contact.name}
></span>
<span <span
class="body" class="body"
subscribe:innerText={chatMessageViewModel.body} subscribe:innerText={chatMessageViewModel.body}
+3
View File
@@ -6,6 +6,7 @@ import ConnectionViewModel from "../Global/connectionViewModel";
import * as React from "../../react"; import * as React from "../../react";
import { IndexManager } from "../../Model/Utility/utility"; import { IndexManager } from "../../Model/Utility/utility";
import ChatModel from "../../Model/Chat/chatModel"; import ChatModel from "../../Model/Chat/chatModel";
import ContactListViewModel from "../Global/contactViewModel";
export default class ChatListViewModel { export default class ChatListViewModel {
notificationViewModel: NotificationViewModel; notificationViewModel: NotificationViewModel;
@@ -61,6 +62,7 @@ export default class ChatListViewModel {
this.notificationViewModel, this.notificationViewModel,
this.connectionViewModel, this.connectionViewModel,
this, this,
this.contactListViewModel,
); );
}; };
@@ -97,6 +99,7 @@ export default class ChatListViewModel {
public readonly coreViewModel: CoreViewModel, public readonly coreViewModel: CoreViewModel,
public readonly settingsViewModel: SettingsViewModel, public readonly settingsViewModel: SettingsViewModel,
public readonly connectionViewModel: ConnectionViewModel, public readonly connectionViewModel: ConnectionViewModel,
public readonly contactListViewModel: ContactListViewModel,
) { ) {
this.notificationViewModel = new NotificationViewModel(this); this.notificationViewModel = new NotificationViewModel(this);
+2 -2
View File
@@ -7,13 +7,13 @@ import {
ChatMessageStatuses, ChatMessageStatuses,
ReactionSymbols, ReactionSymbols,
} from "../../Model/Chat/chatModel"; } from "../../Model/Chat/chatModel";
import {ContactViewModel} from "../Global/contactViewModel";
export default class ChatMessageViewModel { export default class ChatMessageViewModel {
// data // data
chatMessage: ChatMessage; chatMessage: ChatMessage;
channel: string = ""; channel: string = "";
senderId: string = ""; senderId: string = "";
senderName: string = "";
dateSent: string = ""; dateSent: string = "";
body: React.State<string> = new React.State(""); body: React.State<string> = new React.State("");
inlineReply: ChatMessageViewModel | undefined = undefined; inlineReply: ChatMessageViewModel | undefined = undefined;
@@ -185,7 +185,6 @@ export default class ChatMessageViewModel {
loadData = (): void => { loadData = (): void => {
this.channel = this.chatMessage.channel; this.channel = this.chatMessage.channel;
this.senderId = this.chatMessage.senderId; this.senderId = this.chatMessage.senderId;
this.senderName = this.chatMessage.senderName;
this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString(); this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString();
this.body.value = this.chatMessage.body; this.body.value = this.chatMessage.body;
this.status.value = this.chatMessage.status; this.status.value = this.chatMessage.status;
@@ -204,6 +203,7 @@ export default class ChatMessageViewModel {
public readonly coreViewModel: CoreViewModel, public readonly coreViewModel: CoreViewModel,
public readonly messagePageViewModel: MessagePageViewModel, public readonly messagePageViewModel: MessagePageViewModel,
chatMessage: ChatMessage, chatMessage: ChatMessage,
public contact: ContactViewModel,
sentByUser: boolean, sentByUser: boolean,
) { ) {
this.chatMessage = chatMessage; this.chatMessage = chatMessage;
+3
View File
@@ -21,6 +21,7 @@ import ChatModel, {
ChatMessage, ChatMessage,
ChatMessageReaction, ChatMessageReaction,
} from "../../Model/Chat/chatModel"; } from "../../Model/Chat/chatModel";
import ContactListViewModel from "../Global/contactViewModel";
export default class ChatViewModel extends ContextHost<ChatPageTypes> { export default class ChatViewModel extends ContextHost<ChatPageTypes> {
calendarViewModel: CalendarPageViewModel; calendarViewModel: CalendarPageViewModel;
@@ -149,6 +150,7 @@ export default class ChatViewModel extends ContextHost<ChatPageTypes> {
public readonly notificationViewModel: NotificationViewModel, public readonly notificationViewModel: NotificationViewModel,
public readonly connectionViewModel: ConnectionViewModel, public readonly connectionViewModel: ConnectionViewModel,
public readonly chatListViewModel: ChatListViewModel, public readonly chatListViewModel: ChatListViewModel,
public readonly contactListViewModel: ContactListViewModel,
) { ) {
super("chat", coreViewModel); super("chat", coreViewModel);
@@ -167,6 +169,7 @@ export default class ChatViewModel extends ContextHost<ChatPageTypes> {
this.messagePageViewModel = new MessagePageViewModel( this.messagePageViewModel = new MessagePageViewModel(
this.coreViewModel, this.coreViewModel,
this, this,
this.contactListViewModel,
); );
this.settingsPageViewModel = new SettingsPageViewModel( this.settingsPageViewModel = new SettingsPageViewModel(
this.coreViewModel, this.coreViewModel,
+13 -5
View File
@@ -3,22 +3,30 @@ import * as React from "../../react";
export default class ContactListViewModel { export default class ContactListViewModel {
// state // state
contacts: React.MapState<ContactViewModel> = new React.MapState(); contacts: Map<string, ContactViewModel> = new Map();
// main // main
updateContact = (id: string, name: string): void => { updateContact = (id: string, name: string): ContactViewModel => {
if (this.contacts.value.has(id)) { const match = this.contacts.get(id);
this.contacts.value.get(id)!.name.value = name; if (match) {
return; match.name.value = name;
return match;
} }
const vm = new ContactViewModel(id, name); const vm = new ContactViewModel(id, name);
this.contacts.set(id, vm); this.contacts.set(id, vm);
return vm;
} }
handleContact = (contact: [string, string]): void => { handleContact = (contact: [string, string]): void => {
this.updateContact(...contact); this.updateContact(...contact);
} }
unwrapContact = (id: string, knownName: string): ContactViewModel => {
if (this.contacts.has(id))
return this.contacts.get(id)!
return this.updateContact(id, knownName);
}
// load // load
loadData = (): void => { loadData = (): void => {
const contacts: [string, string][] = this.contactListModel.loadContacts(); const contacts: [string, string][] = this.contactListModel.loadContacts();
+2 -2
View File
@@ -1,7 +1,7 @@
import CoreViewModel, { Context } from "./coreViewModel"; import CoreViewModel, { Context } from "./coreViewModel";
import * as React from "../../react"; import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes"; import { CommonKeys } from "../../View/keystrokes";
import { Languages, ThemeSettings } from "../../Model/Global/settingsModel"; import SettingsModel, { Languages, ThemeSettings } from "../../Model/Global/settingsModel";
export default class SettingsViewModel extends Context { export default class SettingsViewModel extends Context {
// state // state
@@ -126,7 +126,7 @@ export default class SettingsViewModel extends Context {
}; };
// init // init
constructor(public readonly coreViewModel: CoreViewModel) { constructor(public readonly coreViewModel: CoreViewModel, public readonly settingsModel: SettingsModel) {
super("settings"); super("settings");
this.username.value = coreViewModel.settingsModel.username; this.username.value = coreViewModel.settingsModel.username;
+89 -85
View File
@@ -9,6 +9,7 @@ import {
ChatMessageReaction, ChatMessageReaction,
ReactionSymbols, ReactionSymbols,
} from "../../Model/Chat/chatModel"; } from "../../Model/Chat/chatModel";
import ContactListViewModel from "../Global/contactViewModel";
export default class MessagePageViewModel extends Context { export default class MessagePageViewModel extends Context {
// state // state
@@ -106,135 +107,138 @@ export default class MessagePageViewModel extends Context {
// view // view
showChatMessage = (chatMessage: ChatMessage): void => { showChatMessage = (chatMessage: ChatMessage): void => {
const chatMessageViewModel = new ChatMessageViewModel( const contact = this.contactListViewModel.unwrapContact(chatMessage.senderId, chatMessage.senderName);
this.coreViewModel, const chatMessageViewModel = new ChatMessageViewModel(
this, this.coreViewModel,
chatMessage, this,
chatMessage.senderName == chatMessage,
this.chatViewModel.settingsViewModel.username.value, contact,
); chatMessage.senderId ==
this.chatViewModel.settingsViewModel.settingsModel.userid
);
const existingChatMessageViewModel: ChatMessageViewModel | undefined = const existingChatMessageViewModel: ChatMessageViewModel | undefined =
this.chatMessageViewModels.value.get(chatMessage.id); this.chatMessageViewModels.value.get(chatMessage.id);
if (existingChatMessageViewModel != undefined) { if (existingChatMessageViewModel != undefined) {
existingChatMessageViewModel.body.value = chatMessage.body; existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status; existingChatMessageViewModel.status.value = chatMessage.status;
} else { } else {
this.chatMessageViewModels.set( this.chatMessageViewModels.set(
chatMessage.id, chatMessage.id,
chatMessageViewModel, chatMessageViewModel,
); );
} }
}; };
handleReaction = (reaction: ChatMessageReaction): void => { handleReaction = (reaction: ChatMessageReaction): void => {
const messageViewModel: ChatMessageViewModel | undefined = const messageViewModel: ChatMessageViewModel | undefined =
this.chatMessageViewModels.value.get(reaction.messageId); this.chatMessageViewModels.value.get(reaction.messageId);
if (messageViewModel == undefined) return; if (messageViewModel == undefined) return;
messageViewModel.handleReaction(reaction); messageViewModel.handleReaction(reaction);
}; };
showFilterModal = (): void => { showFilterModal = (): void => {
this.isFilterModalOpen.value = true; this.isFilterModalOpen.value = true;
}; };
hideFilterModal = (): void => { hideFilterModal = (): void => {
this.isFilterModalOpen.value = false; this.isFilterModalOpen.value = false;
}; };
revokeReactionFilter = (persist: boolean = true): void => { revokeReactionFilter = (persist: boolean = true): void => {
this.reactionFilter.value = undefined; this.reactionFilter.value = undefined;
if (persist) this.chatViewModel.chatModel.storeFilter(""); if (persist) this.chatViewModel.chatModel.storeFilter("");
}; };
setReactionFilter = (content: ReactionSymbols): void => { setReactionFilter = (content: ReactionSymbols): void => {
this.reactionFilter.value = content; this.reactionFilter.value = content;
this.chatViewModel.chatModel.storeFilter(content); this.chatViewModel.chatModel.storeFilter(content);
}; };
resetFilter = (): void => { resetFilter = (): void => {
this.revokeReactionFilter(); this.revokeReactionFilter();
this.searchViewModel.search(""); this.searchViewModel.search("");
}; };
setReplyView = (message: ChatMessageViewModel): void => { setReplyView = (message: ChatMessageViewModel): void => {
this.replyViewSelectedMessage.value = message; this.replyViewSelectedMessage.value = message;
this.revokeReactionFilter(false); this.revokeReactionFilter(false);
this.setReply(message, false); this.setReply(message, false);
}; };
resetReplyView = (): void => { resetReplyView = (): void => {
this.replyViewSelectedMessage.value = undefined; this.replyViewSelectedMessage.value = undefined;
this.resetReply(false); this.resetReply(false);
this.restoreFilter(); this.restoreFilter();
}; };
setFocus = (): void => { setFocus = (): void => {
this.focusSetter.callSubscriptions(); this.focusSetter.callSubscriptions();
}; };
// load // load
loadData = (): void => { loadData = (): void => {
this.chatMessageViewModels.clear(); this.chatMessageViewModels.clear();
for (const chatMessage of this.chatViewModel.chatModel.messages) { for (const chatMessage of this.chatViewModel.chatModel.messages) {
this.showChatMessage(chatMessage); this.showChatMessage(chatMessage);
} }
for (const reaction of this.chatViewModel.chatModel.reactions) { for (const reaction of this.chatViewModel.chatModel.reactions) {
this.handleReaction(reaction); this.handleReaction(reaction);
} }
}; };
restoreFilter = (): void => { restoreFilter = (): void => {
const previousFilter = this.chatViewModel.chatModel.getFilter() as any; const previousFilter = this.chatViewModel.chatModel.getFilter() as any;
if (Object.values(ReactionSymbols).includes(previousFilter)) if (Object.values(ReactionSymbols).includes(previousFilter))
this.setReactionFilter(previousFilter); this.setReactionFilter(previousFilter);
}; };
// init // init
constructor( constructor(
public readonly coreViewModel: CoreViewModel, public readonly coreViewModel: CoreViewModel,
public readonly chatViewModel: ChatViewModel, public readonly chatViewModel: ChatViewModel,
public readonly contactListViewModel: ContactListViewModel,
) { ) {
super("message-page"); super("message-page");
this.restoreFilter(); this.restoreFilter();
// states // states
this.cannotSendMessage = React.createProxyState( this.cannotSendMessage = React.createProxyState(
[ [
this.chatViewModel.settingsViewModel.username, this.chatViewModel.settingsViewModel.username,
this.composingMessage, this.composingMessage,
], ],
() => () =>
this.chatViewModel.settingsViewModel.username.value == "" || this.chatViewModel.settingsViewModel.username.value == "" ||
this.composingMessage.value == "", this.composingMessage.value == "",
); );
this.searchViewModel = new SearchViewModel( this.searchViewModel = new SearchViewModel(
this.chatMessageViewModels, this.chatMessageViewModels,
this.filteredMessageViewModels, this.filteredMessageViewModels,
(chatMessageViewModel) => [chatMessageViewModel.body.value], (chatMessageViewModel) => [chatMessageViewModel.body.value],
new React.ListState(), new React.ListState(),
); );
this.isFilterActive = React.createProxyState( this.isFilterActive = React.createProxyState(
[this.searchViewModel.appliedQuery, this.reactionFilter], [this.searchViewModel.appliedQuery, this.reactionFilter],
() => () =>
this.searchViewModel.appliedQuery.value != "" || this.searchViewModel.appliedQuery.value != "" ||
this.reactionFilter.value != undefined, this.reactionFilter.value != undefined,
); );
// keystrokes // keystrokes
this.registerKeyStroke(CommonKeys.Filter, this.showFilterModal); this.registerKeyStroke(CommonKeys.Filter, this.showFilterModal);
this.registerKeyStroke(CommonKeys.CloseOrCancel, () => { this.registerKeyStroke(CommonKeys.CloseOrCancel, () => {
if (this.isFilterModalOpen.value == true) { if (this.isFilterModalOpen.value == true) {
this.hideFilterModal(); this.hideFilterModal();
} else { } else {
this.replyingMessage.value = undefined; this.replyingMessage.value = undefined;
} }
}); });
this.registerKeyStroke(CommonKeys.Reset, this.resetFilter); this.registerKeyStroke(CommonKeys.Reset, this.resetFilter);
this.registerKeyStroke(CommonKeys.Create, this.setFocus); this.registerKeyStroke(CommonKeys.Create, this.setFocus);
this.chatViewModel.registerContext(ChatPageTypes.Messages, this); this.chatViewModel.registerContext(ChatPageTypes.Messages, this);
} }
} }
+2 -1
View File
@@ -46,13 +46,14 @@ const coreViewModel = new CoreViewModel(
); );
const storageViewModel = new StorageViewModel(coreViewModel); const storageViewModel = new StorageViewModel(coreViewModel);
const settingsViewModel = new SettingsViewModel(coreViewModel); const settingsViewModel = new SettingsViewModel(coreViewModel, settingsModel);
const contactListViewModel = new ContactListViewModel(contactListModel); const contactListViewModel = new ContactListViewModel(contactListModel);
const connectionViewModel = new ConnectionViewModel(coreViewModel); const connectionViewModel = new ConnectionViewModel(coreViewModel);
const chatListViewModel = new ChatListViewModel( const chatListViewModel = new ChatListViewModel(
coreViewModel, coreViewModel,
settingsViewModel, settingsViewModel,
connectionViewModel, connectionViewModel,
contactListViewModel,
); );
const fileTransferViewModel = new FileTransferViewModel(coreViewModel); const fileTransferViewModel = new FileTransferViewModel(coreViewModel);