From c4aa95d427e30804d94edfaad69869f047a1d605 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Sat, 20 Jul 2024 14:16:26 +0200 Subject: [PATCH] add navigation for chat page --- dist/index.css | 14 ++++ dist/index.js | 118 ++++++++++++++++++++++------ src/Model/chatListModel.ts | 9 +-- src/Model/chatModel.ts | 13 ++- src/View/Components/chatEntry.tsx | 21 ++--- src/View/chatPage.css | 15 ++++ src/View/chatPage.tsx | 17 ++++ src/View/chatPageWrapper.tsx | 31 ++++++++ src/View/homePage.tsx | 4 +- src/View/translations.ts | 4 + src/ViewModel/chatListViewModel.tsx | 33 ++++++-- src/ViewModel/chatViewModel.tsx | 35 +++++++-- src/index.tsx | 3 +- 13 files changed, 260 insertions(+), 57 deletions(-) create mode 100644 src/View/chatPage.css create mode 100644 src/View/chatPage.tsx create mode 100644 src/View/chatPageWrapper.tsx diff --git a/dist/index.css b/dist/index.css index 28cd4b53..6ecfd490 100644 --- a/dist/index.css +++ b/dist/index.css @@ -1,3 +1,17 @@ +/* src/View/chatPage.css */ +#chat-page-wrapper { + display: none; +} +#chat-page-wrapper[open] { + display: flex; + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + background-color: var(--background); +} + /* src/View/homePage.css */ #home-page > div { display: flex; diff --git a/dist/index.js b/dist/index.js index 4ec5a71a..16b89128 100644 --- a/dist/index.js +++ b/dist/index.js @@ -362,6 +362,7 @@ // data id; storageModel; + chatListModel; info; messages = /* @__PURE__ */ new Set(); objects = /* @__PURE__ */ new Map(); @@ -410,9 +411,10 @@ const objectPath = this.getObjectPath(object.id); this.storageModel.storeStringifiable(objectPath, object); }; - // remove - remove = () => { + // delete + delete = () => { this.unloadData(); + this.chatListModel.untrackChat(this); const dirPath = [...storageKeys.chats, this.id]; this.storageModel.removeRecursive(dirPath); }; @@ -456,9 +458,10 @@ this.objects.clear(); }; // init - constructor(storageModel2, chatId) { + constructor(storageModel2, chatListModel, chatId) { this.id = chatId; this.storageModel = storageModel2; + this.chatListModel = chatListModel; this.restoreInfo(); } }; @@ -482,15 +485,11 @@ }; createChat = (primaryChannel) => { const id = v4_default(); - const chatModel = new ChatModel(this.storageModel, id); + const chatModel = new ChatModel(this.storageModel, this, id); chatModel.setPrimaryChannel(primaryChannel); this.addChatModel(chatModel); return chatModel; }; - deleteChat = (chat) => { - chat.remove(); - this.untrackChat(chat); - }; untrackChat = (chat) => { this.chatModels.delete(chat.id); }; @@ -499,7 +498,7 @@ const chatDir = storageKeys.chats; const chatIds = this.storageModel.list(chatDir); for (const chatId of chatIds) { - const chatModel = new ChatModel(this.storageModel, chatId); + const chatModel = new ChatModel(this.storageModel, this, chatId); this.addChatModel(chatModel); } }; @@ -510,12 +509,39 @@ } }; + // src/ViewModel/chatViewModel.tsx + var ChatViewModel = class { + chatModel; + chatListViewModel; + // state + primaryChannel = new State(""); + primaryChannelInput = new State(""); + // guards + cannotSetPrimaryChannel = createProxyState( + [this.primaryChannel, this.primaryChannelInput], + () => this.primaryChannelInput.value == "" || this.primaryChannelInput.value == this.primaryChannel.value + ); + // methods + open = () => { + this.chatListViewModel.openChat(this); + }; + // init + constructor(chatListViewModel2, chatModel) { + this.chatModel = chatModel; + this.chatListViewModel = chatListViewModel2; + this.primaryChannel.value = chatModel.info.primaryChannel; + this.primaryChannelInput.value = chatModel.info.primaryChannel; + } + }; + // src/ViewModel/chatListViewModel.tsx var ChatListViewModel = class { + storageModel; chatListModel; // state newChatPrimaryChannel = new State(""); - chatModels = new ListState(); + chatViewModels = new ListState(); + selectedChat = new State(void 0); // guards cannotCreateChat = createProxyState( [this.newChatPrimaryChannel], @@ -527,26 +553,66 @@ this.newChatPrimaryChannel.value ); this.newChatPrimaryChannel.value = ""; - this.chatModels.add(chatModel); + const chatViewModel = new ChatViewModel(this, chatModel); + this.chatViewModels.add(chatViewModel); }; - deleteChat = (chat) => { - this.chatListModel.deleteChat(chat); - this.chatModels.remove(chat); + untrackChat = (chatViewModel) => { + this.chatListModel.untrackChat(chatViewModel.chatModel); + this.chatViewModels.remove(chatViewModel); + }; + openChat = (chatViewModel) => { + this.selectedChat.value = chatViewModel; + }; + closeChat = () => { + this.selectedChat.value = void 0; }; // restore restoreChats = () => { - for (const chat of this.chatListModel.chatModels.values()) { - this.chatModels.add(chat); + for (const chatModel of this.chatListModel.chatModels.values()) { + const chatViewModel = new ChatViewModel(this, chatModel); + this.chatViewModels.add(chatViewModel); } }; // init constructor(storageModel2) { + this.storageModel = storageModel2; const chatListModel = new ChatListModel(storageModel2); this.chatListModel = chatListModel; this.restoreChats(); } }; + // src/View/chatPage.tsx + function ChatPage(chatViewModel) { + return /* @__PURE__ */ createElement("article", { id: "chat-page" }, /* @__PURE__ */ createElement("header", null, /* @__PURE__ */ createElement("span", { "subscribe:innerText": chatViewModel.primaryChannel })), /* @__PURE__ */ createElement("div", null)); + } + + // src/View/chatPageWrapper.tsx + function ChatPageWrapper(chatListViewModel2) { + const chatPageContent = createProxyState( + [chatListViewModel2.selectedChat], + () => { + if (chatListViewModel2.selectedChat.value == void 0) { + return /* @__PURE__ */ createElement("div", null); + } else { + return ChatPage(chatListViewModel2.selectedChat.value); + } + } + ); + const isShowingChat = createProxyState( + [chatListViewModel2.selectedChat], + () => chatListViewModel2.selectedChat.value != void 0 + ); + return /* @__PURE__ */ createElement( + "div", + { + id: "chat-page-wrapper", + "toggle:open": isShowingChat, + "children:set": chatPageContent + } + ); + } + // src/View/translations.ts var englishTranslations = { general: { @@ -605,7 +671,8 @@ connectionModalHeadline: "Manage Connections", /// connectButtonAudioLabel: "connect" - } + }, + chatPage: {} }; var allTranslations = { en: englishTranslations @@ -927,11 +994,17 @@ }; // src/View/Components/chatEntry.tsx - function ChatEntry(chatModel) { - return /* @__PURE__ */ createElement("button", { class: "chat-entry tile" }, /* @__PURE__ */ createElement("span", { class: "shadow" }, chatModel.info.primaryChannel), /* @__PURE__ */ createElement("h2", null, chatModel.info.primaryChannel)); + function ChatEntry(chatViewModel) { + return /* @__PURE__ */ createElement("button", { class: "chat-entry tile", "on:click": chatViewModel.open }, /* @__PURE__ */ createElement( + "span", + { + class: "shadow", + "subscribe:innerText": chatViewModel.primaryChannel + } + ), /* @__PURE__ */ createElement("h2", { "subscribe:innerText": chatViewModel.primaryChannel })); } - var ChatModelToChatEntry = (chatModel) => { - return ChatEntry(chatModel); + var ChatViewModelToChatEntry = (chatViewModel) => { + return ChatEntry(chatViewModel); }; // src/View/homePage.tsx @@ -1025,7 +1098,7 @@ "div", { id: "chat-grid", - "children:append": [chatListViewModel2.chatModels, ChatModelToChatEntry] + "children:append": [chatListViewModel2.chatViewModels, ChatViewModelToChatEntry] } )); function scrollToChat() { @@ -1106,6 +1179,7 @@ var chatListViewModel = new ChatListViewModel(storageModel); document.querySelector("main").append( HomePage(settingsViewModel, connectionViewModel, chatListViewModel), + ChatPageWrapper(chatListViewModel), ConnectionModal(connectionViewModel) ); })(); diff --git a/src/Model/chatListModel.ts b/src/Model/chatListModel.ts index d01174dd..90da700b 100644 --- a/src/Model/chatListModel.ts +++ b/src/Model/chatListModel.ts @@ -17,18 +17,13 @@ export default class ChatListModel { createChat = (primaryChannel: string): ChatModel => { const id: string = v4(); - const chatModel = new ChatModel(this.storageModel, id); + const chatModel = new ChatModel(this.storageModel, this, id); chatModel.setPrimaryChannel(primaryChannel); this.addChatModel(chatModel); return chatModel; }; - deleteChat = (chat: ChatModel): void => { - chat.remove(); - this.untrackChat(chat); - }; - untrackChat = (chat: ChatModel): void => { this.chatModels.delete(chat.id); }; @@ -38,7 +33,7 @@ export default class ChatListModel { const chatDir = storageKeys.chats; const chatIds = this.storageModel.list(chatDir); for (const chatId of chatIds) { - const chatModel = new ChatModel(this.storageModel, chatId); + const chatModel = new ChatModel(this.storageModel, this, chatId); this.addChatModel(chatModel); } }; diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index 0bb05743..731609cf 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -2,6 +2,7 @@ import StorageModel, { storageKeys } from "./storageModel"; +import ChatListModel from "./chatListModel"; import { checkIsValidObject } from "./Utility/typeSafety"; import { createTimestamp } from "./Utility/utility"; import { v4 } from "uuid"; @@ -10,6 +11,7 @@ export class ChatModel { // data id: string; storageModel: StorageModel; + chatListModel: ChatListModel; info: ChatInfo; messages = new Set(); @@ -71,9 +73,13 @@ export class ChatModel { this.storageModel.storeStringifiable(objectPath, object); }; - // remove - remove = () => { + // delete + delete = () => { + // untrack this.unloadData(); + this.chatListModel.untrackChat(this); + + // delete const dirPath = [...storageKeys.chats, this.id]; this.storageModel.removeRecursive(dirPath); }; @@ -128,9 +134,10 @@ export class ChatModel { }; // init - constructor(storageModel: StorageModel, chatId: string) { + constructor(storageModel: StorageModel, chatListModel: ChatListModel, chatId: string) { this.id = chatId; this.storageModel = storageModel; + this.chatListModel = chatListModel; this.restoreInfo(); } diff --git a/src/View/Components/chatEntry.tsx b/src/View/Components/chatEntry.tsx index 18fd0e50..3405cb2c 100644 --- a/src/View/Components/chatEntry.tsx +++ b/src/View/Components/chatEntry.tsx @@ -2,19 +2,22 @@ import "./chatEntry.css"; import * as React from "bloatless-react"; -import { ChatModel } from "../../Model/chatModel"; +import ChatViewModel from "../../ViewModel/chatViewModel"; -export function ChatEntry(chatModel: ChatModel) { +export function ChatEntry(chatViewModel: ChatViewModel) { return ( - ); } -export const ChatModelToChatEntry: React.StateItemConverter = ( - chatModel: ChatModel -) => { - return ChatEntry(chatModel); +export const ChatViewModelToChatEntry: React.StateItemConverter< + ChatViewModel +> = (chatViewModel: ChatViewModel) => { + return ChatEntry(chatViewModel); }; diff --git a/src/View/chatPage.css b/src/View/chatPage.css new file mode 100644 index 00000000..fe5c2cd0 --- /dev/null +++ b/src/View/chatPage.css @@ -0,0 +1,15 @@ +#chat-page-wrapper { + display: none; +} + +#chat-page-wrapper[open] { + display: flex; + + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + + background-color: var(--background); +} \ No newline at end of file diff --git a/src/View/chatPage.tsx b/src/View/chatPage.tsx new file mode 100644 index 00000000..98ef0b4e --- /dev/null +++ b/src/View/chatPage.tsx @@ -0,0 +1,17 @@ +import "./homePage.css"; + +import * as React from "bloatless-react"; + +import ChatViewModel from "../ViewModel/chatViewModel"; +import { translations } from "./translations"; + +export function ChatPage(chatViewModel: ChatViewModel) { + return ( +
+
+ +
+
+
+ ); +} diff --git a/src/View/chatPageWrapper.tsx b/src/View/chatPageWrapper.tsx new file mode 100644 index 00000000..1868d729 --- /dev/null +++ b/src/View/chatPageWrapper.tsx @@ -0,0 +1,31 @@ +import "./chatPage.css"; + +import * as React from "bloatless-react"; + +import ChatListViewModel from "../ViewModel/chatListViewModel"; +import { ChatPage } from "./chatPage"; + +export function ChatPageWrapper(chatListViewModel: ChatListViewModel) { + const chatPageContent = React.createProxyState( + [chatListViewModel.selectedChat], + () => { + if (chatListViewModel.selectedChat.value == undefined) { + return
; + } else { + return ChatPage(chatListViewModel.selectedChat.value); + } + } + ); + const isShowingChat = React.createProxyState( + [chatListViewModel.selectedChat], + () => chatListViewModel.selectedChat.value != undefined + ); + + return ( +
+ ); +} diff --git a/src/View/homePage.tsx b/src/View/homePage.tsx index 711f9bb4..8c99421b 100644 --- a/src/View/homePage.tsx +++ b/src/View/homePage.tsx @@ -5,7 +5,7 @@ import * as React from "bloatless-react"; import { Option, StringToOption } from "./Components/option"; import ChatListViewModel from "../ViewModel/chatListViewModel"; -import { ChatModelToChatEntry } from "./Components/chatEntry"; +import { ChatViewModelToChatEntry } from "./Components/chatEntry"; import ConnectionViewModel from "../ViewModel/connectionViewModel"; import SettingsViewModel from "../ViewModel/settingsViewModel"; import { translations } from "./translations"; @@ -167,7 +167,7 @@ export function HomePage(
); diff --git a/src/View/translations.ts b/src/View/translations.ts index 4467cbee..2127af2c 100644 --- a/src/View/translations.ts +++ b/src/View/translations.ts @@ -72,6 +72,10 @@ const englishTranslations = { connectButtonAudioLabel: "connect", }, + + chatPage: { + + } }; const allTranslations: { [language: string]: typeof englishTranslations } = { diff --git a/src/ViewModel/chatListViewModel.tsx b/src/ViewModel/chatListViewModel.tsx index 2168f54a..4aff7487 100644 --- a/src/ViewModel/chatListViewModel.tsx +++ b/src/ViewModel/chatListViewModel.tsx @@ -2,14 +2,20 @@ import * as React from "bloatless-react"; import ChatListModel from "../Model/chatListModel"; import { ChatModel } from "../Model/chatModel"; +import ChatViewModel from "./chatViewModel"; import StorageModel from "../Model/storageModel"; export default class ChatListViewModel { + storageModel: StorageModel; chatListModel: ChatListModel; // state newChatPrimaryChannel: React.State = new React.State(""); - chatModels: React.ListState = new React.ListState(); + chatViewModels: React.ListState = new React.ListState(); + + selectedChat: React.State = new React.State< + ChatViewModel | undefined + >(undefined); // guards cannotCreateChat = React.createProxyState( @@ -23,23 +29,36 @@ export default class ChatListViewModel { this.newChatPrimaryChannel.value ); this.newChatPrimaryChannel.value = ""; - this.chatModels.add(chatModel); + + const chatViewModel: ChatViewModel = new ChatViewModel(this, chatModel); + this.chatViewModels.add(chatViewModel); }; - deleteChat = (chat: ChatModel): void => { - this.chatListModel.deleteChat(chat); - this.chatModels.remove(chat); + untrackChat = (chatViewModel: ChatViewModel): void => { + this.chatListModel.untrackChat(chatViewModel.chatModel); + this.chatViewModels.remove(chatViewModel); + }; + + openChat = (chatViewModel: ChatViewModel): void => { + this.selectedChat.value = chatViewModel; + }; + + closeChat = (): void => { + this.selectedChat.value = undefined; }; // restore restoreChats = (): void => { - for (const chat of this.chatListModel.chatModels.values()) { - this.chatModels.add(chat); + for (const chatModel of this.chatListModel.chatModels.values()) { + const chatViewModel = new ChatViewModel(this, chatModel); + this.chatViewModels.add(chatViewModel); } }; // init constructor(storageModel: StorageModel) { + this.storageModel = storageModel; + const chatListModel = new ChatListModel(storageModel); this.chatListModel = chatListModel; diff --git a/src/ViewModel/chatViewModel.tsx b/src/ViewModel/chatViewModel.tsx index ed51b5a0..83522e9b 100644 --- a/src/ViewModel/chatViewModel.tsx +++ b/src/ViewModel/chatViewModel.tsx @@ -1,13 +1,36 @@ import * as React from "bloatless-react"; +import ChatListViewModel from "./chatListViewModel"; import { ChatModel } from "../Model/chatModel"; import StorageModel from "../Model/storageModel"; export default class ChatViewModel { - chatModel: ChatModel; + chatModel: ChatModel; + chatListViewModel: ChatListViewModel; - // init - constructor(storageModel: StorageModel) { - - } -} \ No newline at end of file + // state + primaryChannel: React.State = new React.State(""); + primaryChannelInput: React.State = new React.State(""); + + // guards + cannotSetPrimaryChannel: React.State = React.createProxyState( + [this.primaryChannel, this.primaryChannelInput], + () => + this.primaryChannelInput.value == "" || + this.primaryChannelInput.value == this.primaryChannel.value + ); + + // methods + open = () => { + this.chatListViewModel.openChat(this); + } + + // init + constructor(chatListViewModel: ChatListViewModel, chatModel: ChatModel) { + this.chatModel = chatModel; + this.chatListViewModel = chatListViewModel; + + this.primaryChannel.value = chatModel.info.primaryChannel; + this.primaryChannelInput.value = chatModel.info.primaryChannel; + } +} diff --git a/src/index.tsx b/src/index.tsx index 3498cacd..04111340 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,5 @@ -import ChatListModel from "./Model/chatListModel"; import ChatListViewModel from "./ViewModel/chatListViewModel"; +import { ChatPageWrapper } from "./View/chatPageWrapper"; import { ConnectionModal } from "./View/Modals/connectionModal"; import ConnectionViewModel from "./ViewModel/connectionViewModel"; import { HomePage } from "./View/homePage"; @@ -20,5 +20,6 @@ document .querySelector("main")! .append( HomePage(settingsViewModel, connectionViewModel, chatListViewModel), + ChatPageWrapper(chatListViewModel), ConnectionModal(connectionViewModel) );