From c97b38dcd6e03c91a0a5d8c19dde259b56b11be2 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Sun, 21 Jul 2024 01:12:35 +0200 Subject: [PATCH] add basic thread view --- dist/index.css | 7 + dist/index.js | 196 +++++++++++++----- src/Model/chatListModel.ts | 28 ++- src/Model/chatMessageViewModel.ts | 24 +++ src/Model/chatModel.ts | 46 +++- src/Model/connectionModel.ts | 22 +- src/Model/objectModel.ts | 2 +- src/View/ChatPages/messagePage.css | 34 +-- src/View/ChatPages/messagePage.tsx | 28 ++- src/View/Components/chatMessage.tsx | 22 ++ ...ListViewModel.tsx => chatListViewModel.ts} | 0 .../{chatViewModel.tsx => chatViewModel.ts} | 37 +++- ...onViewModel.tsx => connectionViewModel.ts} | 0 13 files changed, 359 insertions(+), 87 deletions(-) create mode 100644 src/Model/chatMessageViewModel.ts create mode 100644 src/View/Components/chatMessage.tsx rename src/ViewModel/{chatListViewModel.tsx => chatListViewModel.ts} (100%) rename src/ViewModel/{chatViewModel.tsx => chatViewModel.ts} (88%) rename src/ViewModel/{connectionViewModel.tsx => connectionViewModel.ts} (100%) diff --git a/dist/index.css b/dist/index.css index caa38f92..9c4c7eb6 100644 --- a/dist/index.css +++ b/dist/index.css @@ -369,6 +369,13 @@ body[showing-chat] #chat-page-wrapper { max-width: unset; flex: 1; } +#message-page #message-container { + display: flex; + flex-direction: column; + padding: var(--content-padding); + gap: var(--gap-between-tiles); + scroll-behavior: smooth; +} /* src/View/Components/chatEntry.css */ .chat-entry { diff --git a/dist/index.js b/dist/index.js index 719eafcb..c6d4acb6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -201,13 +201,13 @@ break; } case "append": - case "appendandscroll": case "prepend": { - element.style.scrollBehavior = "smooth"; try { const [listState, toElement] = value; listState.handleAddition((newItem) => { + console.log(newItem, toElement); const child = toElement(newItem); + console.log(child); listState.handleRemoval( newItem, () => child.remove() @@ -218,8 +218,10 @@ element.prepend(child); } }); - } catch { - throw `error: cannot process subscribe:children directive because StateItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`; + } catch (error) { + throw `error: cannot process subscribe:children directive. + Error: ${error} + Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation.`; } } } @@ -392,8 +394,8 @@ id; info; color; - messages = /* @__PURE__ */ new Set(); - objects = /* @__PURE__ */ new Map(); + chatMessageHandler = () => { + }; // sorting get index() { return this.chatListModel.getIndexOfPrimaryChannel( @@ -424,6 +426,7 @@ this.info.primaryChannel = primaryChannel; this.storeInfo(); this.chatListModel.updateIndices(); + this.subscribe(); }; setSecondaryChannels = (secondaryChannels) => { this.info.secondaryChannels = secondaryChannels; @@ -444,21 +447,17 @@ storeColor = () => { this.storageModel.store(this.colorPath, this.color); }; - addMessage = (message) => { - if (!this.messages.has(message)) { - this.messages.add(message); - } - const messagePath = this.getMessagePath(message.id); - this.storageModel.storeStringifiable(messagePath, message); + addMessage = (chatMessage) => { + const messagePath = this.getMessagePath(chatMessage.id); + this.storageModel.storeStringifiable(messagePath, chatMessage); + this.chatMessageHandler(chatMessage); }; - addObject = (object) => { - this.objects.set(object.id, object); - const objectPath = this.getObjectPath(object.id); - this.storageModel.storeStringifiable(objectPath, object); + addObject = (chatObject) => { + const objectPath = this.getObjectPath(chatObject.id); + this.storageModel.storeStringifiable(objectPath, chatObject); }; // delete delete = () => { - this.unloadData(); this.chatListModel.untrackChat(this); const dirPath = [...storageKeys.chats, this.id]; this.storageModel.removeRecursive(dirPath); @@ -481,27 +480,31 @@ this.color = color; } }; - restoreMessages = () => { + get messages() { const messageIds = this.storageModel.list(this.messageDirPath); - if (!Array.isArray(messageIds)) return; + if (!Array.isArray(messageIds)) return []; + let messages = []; for (const messageId of messageIds) { const messagePath = this.getMessagePath(messageId); const message = this.storageModel.restoreStringifiable(messagePath); - if (checkIsValidObject(message) == false) return; - this.messages.add(message); + if (checkIsValidObject(message) == false) continue; + messages.push(message); } - }; - restoreObjects = () => { + return messages; + } + get objects() { const objectIds = this.storageModel.list(this.objectDirPath); - if (!Array.isArray(objectIds)) return; + if (!Array.isArray(objectIds)) return []; + let objects = []; for (const objectId of objectIds) { const objectPathComponents = this.getObjectPath(objectId); const object = this.storageModel.restoreStringifiable(objectPathComponents); - if (checkIsValidObject(object) == false) return; - if (object.id != objectId) return; - this.objects.set(objectId, object); + if (checkIsValidObject(object) == false) continue; + if (object.id != objectId) continue; + objects.push(object); } - }; + return objects; + } // messaging sendMessage = (body) => { const senderName = this.settingsModel.username; @@ -519,14 +522,16 @@ this.connectionModel.sendMessageOrStore(chatMessage); return true; }; - // memory - loadData = () => { - this.restoreMessages(); - this.restoreObjects(); + handleMessage = (channel, body) => { + const chatMessage = _ChatModel.parseMessage(body); + if (chatMessage == null) return; + this.addMessage(chatMessage); }; - unloadData = () => { - this.messages.clear(); - this.objects.clear(); + setMessageHandler = (handler) => { + this.chatMessageHandler = handler; + }; + subscribe = () => { + this.connectionModel.addChannel(this.info.primaryChannel); }; // init constructor(storageModel2, connectionModel2, settingsModel2, chatListModel2, chatId) { @@ -537,6 +542,7 @@ this.chatListModel = chatListModel2; this.restoreInfo(); this.restoreColor(); + this.subscribe(); } // utility static generateChatInfo = (primaryChannel) => { @@ -557,6 +563,15 @@ dateSent: createTimestamp() }; }; + static parseMessage = (string) => { + try { + const parsed = parse(string); + if (checkIsValidObject(parsed) == false) return null; + return parsed; + } catch { + return null; + } + }; }; // src/Model/chatListModel.ts @@ -567,6 +582,21 @@ // data chatModels = /* @__PURE__ */ new Set(); sortedPrimaryChannels = []; + // handlers + messageHandler = (data) => { + for (const chatModel of this.chatModels) { + const channel = data.messageChannel; + const body = data.messageBody; + if (channel == void 0) return; + if (body == void 0) return; + const allChannels = channel.split("/"); + for (const channel2 of allChannels) { + if (channel2 != chatModel.info.primaryChannel) continue; + chatModel.handleMessage(channel2, body); + break; + } + } + }; // store & add addChatModel = (chatModel) => { this.chatModels.add(chatModel); @@ -622,10 +652,28 @@ this.settingsModel = settingsModel2; this.connectionModel = connectionModel2; this.loadChats(); + connectionModel2.setMessageHandler(this.messageHandler); } }; - // src/ViewModel/chatViewModel.tsx + // src/Model/chatMessageViewModel.ts + var ChatMessageViewModel = class { + chatModel; + // data + channel; + sender; + dateSent; + body = new State(""); + constructor(chatModel, chatMessage) { + this.chatModel = chatModel; + this.channel = chatMessage.channel; + this.sender = chatMessage.sender; + this.dateSent = new Date(chatMessage.dateSent).toLocaleString(); + this.body.value = chatMessage.body; + } + }; + + // src/ViewModel/chatViewModel.ts var ChatViewModel = class { chatModel; storageModel; @@ -647,6 +695,7 @@ selectedPage = new State( "messages" /* Messages */ ); + chatMessageViewModels = new ListState(); composingMessage = new State(""); // guards cannotSetPrimaryChannel = createProxyState( @@ -670,6 +719,11 @@ close = () => { this.chatListViewModel.closeChat(); }; + // add + addChatMessage = (chatMessage) => { + const chatMessageModel = new ChatMessageViewModel(this.chatModel, chatMessage); + this.chatMessageViewModels.add(chatMessageModel); + }; // settings setPrimaryChannel = () => { this.chatModel.setPrimaryChannel(this.primaryChannelInput.value); @@ -734,6 +788,9 @@ this.storageModel = storageModel2; this.settingsViewModel = settingsViewModel2; this.chatListViewModel = chatListViewModel2; + chatModel.setMessageHandler((chatMessage) => { + this.addChatMessage(chatMessage); + }); this.primaryChannel.value = chatModel.info.primaryChannel; this.primaryChannelInput.value = chatModel.info.primaryChannel; this.encryptionKeyInput.value = chatModel.info.encryptionKey; @@ -741,18 +798,21 @@ [this.encryptionKeyInput], () => this.encryptionKeyInput.value == this.chatModel.info.encryptionKey ); - this.cannotSendMessage = createProxyState( - [this.settingsViewModel.username, this.composingMessage], - () => this.settingsViewModel.username.value == "" || this.composingMessage.value == "" - ); this.color.value = chatModel.color; this.restoreSecondaryChannels(); this.restorePageSelection(); this.updateIndex(); + for (const chatMessage of chatModel.messages) { + this.addChatMessage(chatMessage); + } + this.cannotSendMessage = createProxyState( + [this.settingsViewModel.username, this.composingMessage], + () => this.settingsViewModel.username.value == "" || this.composingMessage.value == "" + ); } }; - // src/ViewModel/chatListViewModel.tsx + // src/ViewModel/chatListViewModel.ts var ChatListViewModel = class { chatListModel; storageModel; @@ -839,6 +899,15 @@ ); } + // src/View/Components/chatMessage.tsx + function ChatMessage(chatMessageViewModel) { + return /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, chatMessageViewModel.sender), /* @__PURE__ */ createElement("b", { "subscribe:innerText": chatMessageViewModel.body }), /* @__PURE__ */ createElement("span", null, chatMessageViewModel.dateSent))); + } + var ChatMessageViewModelToView = (chatMessageViewModel) => { + console.log(chatMessageViewModel); + return ChatMessage(chatMessageViewModel); + }; + // src/View/translations.ts var englishTranslations = { general: { @@ -928,7 +997,28 @@ // src/View/ChatPages/messagePage.tsx function MessagePage(chatViewModel) { - return /* @__PURE__ */ createElement("div", { id: "message-page" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", { "subscribe:innerText": chatViewModel.primaryChannel })), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("div", { id: "message-container" }), /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement( + const messageContainer = /* @__PURE__ */ createElement( + "div", + { + id: "message-container", + "children:append": [ + chatViewModel.chatMessageViewModels, + ChatMessageViewModelToView + ] + } + ); + function scrollDown() { + console.log("down"); + messageContainer.scrollTop = messageContainer.scrollHeight; + } + function scrollDownIfApplicable() { + const scrollFromBottom = messageContainer.scrollHeight - (messageContainer.scrollTop + messageContainer.offsetHeight); + if (scrollFromBottom > 400) return; + scrollDown(); + } + chatViewModel.chatMessageViewModels.subscribeSilent(scrollDownIfApplicable); + setTimeout(() => scrollDown(), 100); + return /* @__PURE__ */ createElement("div", { id: "message-page" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", { "subscribe:innerText": chatViewModel.primaryChannel })), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement( "input", { "bind:value": chatViewModel.composingMessage, @@ -1307,6 +1397,7 @@ }; messageHandler = () => { }; + channelsToSubscribe = /* @__PURE__ */ new Set(); // connection connect = (address) => { this.udn.connect(address); @@ -1321,11 +1412,21 @@ console.log("connection status:", this.isConnected, this.address); this.connectionChangeHandler(); if (this.isConnected == false) return; - if (this.address) { - this.storeAddress(this.address); - } + if (!this.address) return; + this.storeAddress(this.address); + this.sendSubscriptionRequest(); this.sendMessagesInOutbox(); }; + // subscription + addChannel = (channel) => { + this.channelsToSubscribe.add(channel); + this.sendSubscriptionRequest(); + }; + sendSubscriptionRequest = () => { + for (const channel of this.channelsToSubscribe) { + this.udn.subscribe(channel); + } + }; // outbox getOutboxMessags = () => { const outboxPath = storageKeys.outbox; @@ -1408,7 +1509,7 @@ } }; - // src/ViewModel/connectionViewModel.tsx + // src/ViewModel/connectionViewModel.ts var ConnectionViewModel = class { connectionModel; // state @@ -1439,8 +1540,6 @@ this.previousAddresses.add(this.connectionModel.address); } }; - messageHandler = (data) => { - }; // methods connect = () => { this.connectToAddress(this.serverAddressInput.value); @@ -1471,7 +1570,6 @@ this.connectionModel = connectionModel2; this.updatePreviousAddresses(); connectionModel2.setConnectionChangeHandler(this.connectionChangeHandler); - connectionModel2.setMessageHandler(this.messageHandler); } }; diff --git a/src/Model/chatListModel.ts b/src/Model/chatListModel.ts index b26054af..8484a689 100644 --- a/src/Model/chatListModel.ts +++ b/src/Model/chatListModel.ts @@ -2,6 +2,7 @@ import StorageModel, { storageKeys } from "./storageModel"; import { ChatModel } from "./chatModel"; import ConnectionModel from "./connectionModel"; +import { Message } from "udn-frontend"; import SettingsModel from "./settingsModel"; import { localeCompare } from "./Utility/utility"; import { v4 } from "uuid"; @@ -15,6 +16,23 @@ export default class ChatListModel { chatModels = new Set(); sortedPrimaryChannels: string[] = []; + // handlers + messageHandler = (data: Message): void => { + for (const chatModel of this.chatModels) { + const channel: string | undefined = data.messageChannel; + const body: string | undefined = data.messageBody; + if (channel == undefined) return; + if (body == undefined) return; + + const allChannels = channel.split("/"); + for (const channel of allChannels) { + if (channel != chatModel.info.primaryChannel) continue; + chatModel.handleMessage(channel, body); + break; + } + } + }; + // store & add addChatModel = (chatModel: ChatModel) => { this.chatModels.add(chatModel); @@ -57,7 +75,7 @@ export default class ChatListModel { getIndexOfPrimaryChannel(primaryChannel: string): number { return this.sortedPrimaryChannels.indexOf(primaryChannel); } - + // restore loadChats = (): void => { const chatDir = storageKeys.chats; @@ -75,10 +93,16 @@ export default class ChatListModel { }; // init - constructor(storageModel: StorageModel, settingsModel: SettingsModel, connectionModel: ConnectionModel) { + constructor( + storageModel: StorageModel, + settingsModel: SettingsModel, + connectionModel: ConnectionModel + ) { this.storageModel = storageModel; this.settingsModel = settingsModel; this.connectionModel = connectionModel; this.loadChats(); + + connectionModel.setMessageHandler(this.messageHandler); } } diff --git a/src/Model/chatMessageViewModel.ts b/src/Model/chatMessageViewModel.ts new file mode 100644 index 00000000..02bd52f4 --- /dev/null +++ b/src/Model/chatMessageViewModel.ts @@ -0,0 +1,24 @@ +// this file is responsible for managing individual chat messages. + +import * as React from "bloatless-react"; + +import { ChatMessage, ChatModel } from "./chatModel"; + +export default class ChatMessageViewModel { + chatModel: ChatModel; + + // data + channel: string; + sender: string; + dateSent: string; + body: React.State = new React.State(""); + + constructor(chatModel: ChatModel, chatMessage: ChatMessage) { + this.chatModel = chatModel; + + this.channel = chatMessage.channel; + this.sender = chatMessage.sender; + this.dateSent = new Date(chatMessage.dateSent).toLocaleString(); + this.body.value = chatMessage.body; + } +} diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index bf3bd2db..7ce83b95 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -1,6 +1,7 @@ -// this file is responsible for managing chats and chat messages. objects are delegated to objectModel.ts. +// this file is responsible for managing chats. objects are delegated to objectModel.ts. import StorageModel, { storageKeys } from "./storageModel"; +import { createTimestamp, parse } from "./Utility/utility"; import ChatListModel from "./chatListModel"; import { Color } from "../ViewModel/colors"; @@ -8,7 +9,6 @@ import ConnectionModel from "./connectionModel"; import { Message } from "udn-frontend"; import SettingsModel from "./settingsModel"; import { checkIsValidObject } from "./Utility/typeSafety"; -import { createTimestamp } from "./Utility/utility"; import { v4 } from "uuid"; export class ChatModel { @@ -22,6 +22,8 @@ export class ChatModel { info: ChatInfo; color: Color; + chatMessageHandler: (chatMessage: ChatMessage) => void = () => {}; + // sorting get index(): number { return this.chatListModel.getIndexOfPrimaryChannel( @@ -59,6 +61,7 @@ export class ChatModel { this.info.primaryChannel = primaryChannel; this.storeInfo(); this.chatListModel.updateIndices(); + this.subscribe(); }; setSecondaryChannels = (secondaryChannels: string[]): void => { @@ -85,14 +88,15 @@ export class ChatModel { this.storageModel.store(this.colorPath, this.color); }; - addMessage = (message: ChatMessage): void => { - const messagePath: string[] = this.getMessagePath(message.id); - this.storageModel.storeStringifiable(messagePath, message); + addMessage = (chatMessage: ChatMessage): void => { + const messagePath: string[] = this.getMessagePath(chatMessage.id); + this.storageModel.storeStringifiable(messagePath, chatMessage); + this.chatMessageHandler(chatMessage); }; - addObject = (object: ChatObject): void => { - const objectPath: string[] = this.getObjectPath(object.id); - this.storageModel.storeStringifiable(objectPath, object); + addObject = (chatObject: ChatObject): void => { + const objectPath: string[] = this.getObjectPath(chatObject.id); + this.storageModel.storeStringifiable(objectPath, chatObject); }; // delete @@ -179,6 +183,21 @@ export class ChatModel { return true; }; + handleMessage = (channel: string, body: string): void => { + const chatMessage: ChatMessage | null = ChatModel.parseMessage(body); + if (chatMessage == null) return; + + this.addMessage(chatMessage); + }; + + setMessageHandler = (handler: (chatMessage: ChatMessage) => void): void => { + this.chatMessageHandler = handler; + }; + + subscribe = (): void => { + this.connectionModel.addChannel(this.info.primaryChannel); + }; + // init constructor( storageModel: StorageModel, @@ -195,6 +214,7 @@ export class ChatModel { this.restoreInfo(); this.restoreColor(); + this.subscribe(); } // utility @@ -223,6 +243,16 @@ export class ChatModel { dateSent: createTimestamp(), }; }; + + static parseMessage = (string: string): ChatMessage | null => { + try { + const parsed: any = parse(string); + if (checkIsValidObject(parsed) == false) return null; + return parsed; + } catch { + return null; + } + }; } // types diff --git a/src/Model/connectionModel.ts b/src/Model/connectionModel.ts index a9264933..5ae255b6 100644 --- a/src/Model/connectionModel.ts +++ b/src/Model/connectionModel.ts @@ -23,6 +23,8 @@ export default class ConnectionModel { connectionChangeHandler: () => void = () => {}; messageHandler: (data: Message) => void = () => {}; + channelsToSubscribe: Set = new Set(); + // connection connect = (address: string): void => { this.udn.connect(address); @@ -39,16 +41,26 @@ export default class ConnectionModel { handleConnectionChange = (): void => { console.log("connection status:", this.isConnected, this.address); this.connectionChangeHandler(); - if (this.isConnected == false) return; + if (!this.address) return; - if (this.address) { - this.storeAddress(this.address); - } - + this.storeAddress(this.address); + this.sendSubscriptionRequest(); this.sendMessagesInOutbox(); }; + // subscription + addChannel = (channel: string): void => { + this.channelsToSubscribe.add(channel); + this.sendSubscriptionRequest(); + }; + + sendSubscriptionRequest = (): void => { + for (const channel of this.channelsToSubscribe) { + this.udn.subscribe(channel); + } + }; + // outbox getOutboxMessags = (): ChatMessage[] => { const outboxPath: string[] = storageKeys.outbox; diff --git a/src/Model/objectModel.ts b/src/Model/objectModel.ts index 21cf0bfa..83c998a9 100644 --- a/src/Model/objectModel.ts +++ b/src/Model/objectModel.ts @@ -1 +1 @@ -// this file is responsible for managing message objects. \ No newline at end of file +// this file is responsible for managing message objects.c \ No newline at end of file diff --git a/src/View/ChatPages/messagePage.css b/src/View/ChatPages/messagePage.css index a80c9a36..52707c65 100644 --- a/src/View/ChatPages/messagePage.css +++ b/src/View/ChatPages/messagePage.css @@ -1,29 +1,39 @@ /* layout */ #chat-page #main #message-page .content { - padding: 0px; + padding: 0px; - overflow: hidden; + overflow: hidden; - display: grid; - grid-template-rows: 1fr var(--control-height) var(--safe-area-bottom); + display: grid; + grid-template-rows: 1fr var(--control-height) var(--safe-area-bottom); } #message-page #message-container { - overflow: auto; + overflow: auto; } /* composer */ #message-page #composer { - border-top: 1px solid var(--border); + border-top: 1px solid var(--border); - display: flex; + display: flex; } #message-page #composer input { - margin: 0px; + margin: 0px; - width: 100%; - max-width: unset; + width: 100%; + max-width: unset; - flex: 1; -} \ No newline at end of file + flex: 1; +} + +#message-page #message-container { + display: flex; + flex-direction: column; + + padding: var(--content-padding); + gap: var(--gap-between-tiles); + + scroll-behavior: smooth; +} diff --git a/src/View/ChatPages/messagePage.tsx b/src/View/ChatPages/messagePage.tsx index a7bed643..1038cb51 100644 --- a/src/View/ChatPages/messagePage.tsx +++ b/src/View/ChatPages/messagePage.tsx @@ -2,17 +2,43 @@ import "./messagePage.css"; import * as React from "bloatless-react"; +import { ChatMessageViewModelToView } from "../Components/chatMessage"; import ChatViewModel from "../../ViewModel/chatViewModel"; import { translations } from "../translations"; export function MessagePage(chatViewModel: ChatViewModel) { + const messageContainer = ( +
+ ); + + function scrollDown() { + console.log("down"); + messageContainer.scrollTop = messageContainer.scrollHeight; + } + function scrollDownIfApplicable() { + const scrollFromBottom = + messageContainer.scrollHeight - + (messageContainer.scrollTop + messageContainer.offsetHeight); + if (scrollFromBottom > 400) return; + + scrollDown(); + } + chatViewModel.chatMessageViewModels.subscribeSilent(scrollDownIfApplicable); + setTimeout(() => scrollDown(), 100) + return (
-
+ {messageContainer}
+
+ {chatMessageViewModel.sender} + + {chatMessageViewModel.dateSent} +
+
+ ); +} + +export const ChatMessageViewModelToView: React.StateItemConverter< + ChatMessageViewModel +> = (chatMessageViewModel: ChatMessageViewModel) => { + console.log(chatMessageViewModel); + return ChatMessage(chatMessageViewModel); +}; diff --git a/src/ViewModel/chatListViewModel.tsx b/src/ViewModel/chatListViewModel.ts similarity index 100% rename from src/ViewModel/chatListViewModel.tsx rename to src/ViewModel/chatListViewModel.ts diff --git a/src/ViewModel/chatViewModel.tsx b/src/ViewModel/chatViewModel.ts similarity index 88% rename from src/ViewModel/chatViewModel.tsx rename to src/ViewModel/chatViewModel.ts index edb3378f..4b449920 100644 --- a/src/ViewModel/chatViewModel.tsx +++ b/src/ViewModel/chatViewModel.ts @@ -1,9 +1,10 @@ import * as React from "bloatless-react"; +import { ChatMessage, ChatModel } from "../Model/chatModel"; import StorageModel, { storageKeys } from "../Model/storageModel"; import ChatListViewModel from "./chatListViewModel"; -import { ChatModel } from "../Model/chatModel"; +import ChatMessageViewModel from "../Model/chatMessageViewModel"; import { Color } from "./colors"; import SettingsViewModel from "./settingsViewModel"; import { localeCompare } from "../Model/Utility/utility"; @@ -36,6 +37,7 @@ export default class ChatViewModel { ChatPageType.Messages ); + chatMessageViewModels: React.ListState = new React.ListState(); composingMessage: React.State = new React.State(""); // guards @@ -47,8 +49,7 @@ export default class ChatViewModel { ); cannotAddSecondaryChannel: React.State = React.createProxyState( [this.newSecondaryChannelInput], - () => - this.newSecondaryChannelInput.value == "" + () => this.newSecondaryChannelInput.value == "" ); cannotSetEncryptionKey: React.State; cannotSendMessage: React.State; @@ -67,6 +68,12 @@ export default class ChatViewModel { this.chatListViewModel.closeChat(); }; + // add + addChatMessage = (chatMessage: ChatMessage): void => { + const chatMessageModel = new ChatMessageViewModel(this.chatModel, chatMessage); + this.chatMessageViewModels.add(chatMessageModel); + }; + // settings setPrimaryChannel = (): void => { this.chatModel.setPrimaryChannel(this.primaryChannelInput.value); @@ -145,11 +152,18 @@ export default class ChatViewModel { settingsViewModel: SettingsViewModel, chatListViewModel: ChatListViewModel ) { + // models this.chatModel = chatModel; this.storageModel = storageModel; this.settingsViewModel = settingsViewModel; this.chatListViewModel = chatListViewModel; + // handlers + chatModel.setMessageHandler((chatMessage) => { + this.addChatMessage(chatMessage); + }); + + // restore this.primaryChannel.value = chatModel.info.primaryChannel; this.primaryChannelInput.value = chatModel.info.primaryChannel; @@ -159,18 +173,23 @@ export default class ChatViewModel { () => this.encryptionKeyInput.value == this.chatModel.info.encryptionKey ); + this.color.value = chatModel.color; + + this.restoreSecondaryChannels(); + this.restorePageSelection(); + this.updateIndex(); + + for (const chatMessage of chatModel.messages) { + this.addChatMessage(chatMessage); + } + + // guards this.cannotSendMessage = React.createProxyState( [this.settingsViewModel.username, this.composingMessage], () => this.settingsViewModel.username.value == "" || this.composingMessage.value == "" ); - - this.color.value = chatModel.color; - - this.restoreSecondaryChannels(); - this.restorePageSelection(); - this.updateIndex(); } } diff --git a/src/ViewModel/connectionViewModel.tsx b/src/ViewModel/connectionViewModel.ts similarity index 100% rename from src/ViewModel/connectionViewModel.tsx rename to src/ViewModel/connectionViewModel.ts