remodel ChatViewModel

This commit is contained in:
marlon-erler
2024-07-22 21:07:44 +02:00
parent 7c4558a5b2
commit ba27c61a29
17 changed files with 541 additions and 361 deletions
+241 -146
View File
@@ -514,7 +514,7 @@
} }
}; };
// src/ViewModel/colors.ts // src/colors.ts
var Color = /* @__PURE__ */ ((Color2) => { var Color = /* @__PURE__ */ ((Color2) => {
Color2["Standard"] = "standard"; Color2["Standard"] = "standard";
Color2["Coral"] = "coral"; Color2["Coral"] = "coral";
@@ -897,6 +897,9 @@
color; color;
chatMessageHandler = () => { chatMessageHandler = () => {
}; };
get secondaryChannels() {
return this.info.secondaryChannels.sort(localeCompare);
}
// paths // paths
getBasePath = () => { getBasePath = () => {
return StorageModel.getPath("chat", filePaths.chat.chatBase(this.id)); return StorageModel.getPath("chat", filePaths.chat.chatBase(this.id));
@@ -1195,9 +1198,9 @@
} }
}; };
// src/ViewModel/chatMessageViewModel.ts // src/ViewModel/Chat/chatMessageViewModel.ts
var ChatMessageViewModel = class { var ChatMessageViewModel = class {
chatViewModel; messagePageViewModel;
// data // data
chatMessage; chatMessage;
channel; channel;
@@ -1211,10 +1214,10 @@
navigator.clipboard.writeText(this.body.value); navigator.clipboard.writeText(this.body.value);
}; };
resendMessage = () => { resendMessage = () => {
this.chatViewModel.sendMessageFromBody(this.body.value); this.messagePageViewModel.sendMessageFromBody(this.body.value);
}; };
decryptMessage = () => { decryptMessage = () => {
this.chatViewModel.decryptMessage(this); this.messagePageViewModel.decryptMessage(this);
}; };
// load // load
loadData = () => { loadData = () => {
@@ -1225,22 +1228,75 @@
this.status.value = this.chatMessage.status; this.status.value = this.chatMessage.status;
}; };
// init // init
constructor(chatViewModel, chatMessage, sentByUser) { constructor(messagePageViewModel, chatMessage, sentByUser) {
this.chatViewModel = chatViewModel; this.messagePageViewModel = messagePageViewModel;
this.chatMessage = chatMessage; this.chatMessage = chatMessage;
this.sentByUser = sentByUser; this.sentByUser = sentByUser;
this.loadData(); this.loadData();
} }
}; };
// src/ViewModel/chatViewModel.ts // src/ViewModel/Pages/messagePageViewModel.ts
var ChatViewModel = class { var MessagePageViewModel = class {
chatModel; chatViewModel;
storageModel; // state
settingsViewModel; chatMessageViewModels = new MapState();
chatListViewModel; composingMessage = new State("");
// guards
cannotSendMessage;
// methods
addChatMessage = (chatMessage) => {
const chatMessageModel = new ChatMessageViewModel(
this,
chatMessage,
chatMessage.sender == this.chatViewModel.settingsViewModel.username.value
);
const existingChatMessageViewModel = this.chatMessageViewModels.value.get(chatMessage.id);
if (existingChatMessageViewModel != void 0) {
existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status;
} else {
this.chatMessageViewModels.set(chatMessage.id, chatMessageModel);
}
};
// messaging
sendMessage = () => {
if (this.cannotSendMessage.value == true) return;
this.sendMessageFromBody(this.composingMessage.value);
this.composingMessage.value = "";
};
sendMessageFromBody = (body) => {
this.chatViewModel.chatModel.sendMessage(body);
};
decryptMessage = async (messageViewModel) => {
const chatMessage = messageViewModel.chatMessage;
await this.chatViewModel.chatModel.decryptMessage(chatMessage);
this.chatViewModel.chatModel.addMessage(chatMessage);
messageViewModel.loadData();
};
// load
loadData = () => {
this.cannotSendMessage = createProxyState(
[this.chatViewModel.settingsViewModel.username, this.composingMessage],
() => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == ""
);
};
loadMessages = () => {
for (const chatMessage of this.chatViewModel.chatModel.messages) {
this.addChatMessage(chatMessage);
}
};
// init
constructor(chatViewModel) {
this.chatViewModel = chatViewModel;
this.loadData();
}
};
// src/ViewModel/Pages/settingsPageViewModel.ts
var SettingsPageViewModel = class {
chatViewModel;
// state // state
index = new State(0);
primaryChannel = new State(""); primaryChannel = new State("");
primaryChannelInput = new State(""); primaryChannelInput = new State("");
secondaryChannels = new ListState(); secondaryChannels = new ListState();
@@ -1252,11 +1308,6 @@
() => this.shouldShowEncryptionKey.value == true ? "text" : "password" () => this.shouldShowEncryptionKey.value == true ? "text" : "password"
); );
color = new State("standard" /* Standard */); color = new State("standard" /* Standard */);
selectedPage = new State(
"messages" /* Messages */
);
chatMessageViewModels = new MapState();
composingMessage = new State("");
// guards // guards
cannotSetPrimaryChannel = createProxyState( cannotSetPrimaryChannel = createProxyState(
[this.primaryChannel, this.primaryChannelInput], [this.primaryChannel, this.primaryChannelInput],
@@ -1267,39 +1318,13 @@
() => this.newSecondaryChannelInput.value == "" () => this.newSecondaryChannelInput.value == ""
); );
cannotSetEncryptionKey; cannotSetEncryptionKey;
cannotSendMessage; // methods
// sorting
updateIndex = () => {
this.index.value = this.chatModel.index;
};
// view
open = () => {
this.chatListViewModel.openChat(this);
this.loadMessages();
};
close = () => {
this.chatListViewModel.closeChat();
};
// add
addChatMessage = (chatMessage) => {
const chatMessageModel = new ChatMessageViewModel(
this,
chatMessage,
chatMessage.sender == this.settingsViewModel.username.value
);
const existingChatMessageViewModel = this.chatMessageViewModels.value.get(chatMessage.id);
if (existingChatMessageViewModel != void 0) {
existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status;
} else {
this.chatMessageViewModels.set(chatMessage.id, chatMessageModel);
}
};
// settings
setPrimaryChannel = () => { setPrimaryChannel = () => {
this.chatModel.setPrimaryChannel(this.primaryChannelInput.value); this.chatViewModel.chatModel.setPrimaryChannel(
this.primaryChannel.value = this.chatModel.info.primaryChannel; this.primaryChannelInput.value
this.chatListViewModel.updateIndices(); );
this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel;
this.chatViewModel.chatListViewModel.updateIndices();
}; };
addSecondaryChannel = () => { addSecondaryChannel = () => {
this.secondaryChannels.add(this.newSecondaryChannelInput.value); this.secondaryChannels.add(this.newSecondaryChannelInput.value);
@@ -1312,37 +1337,99 @@
this.storeSecondaryChannels(); this.storeSecondaryChannels();
}; };
storeSecondaryChannels = () => { storeSecondaryChannels = () => {
this.chatModel.setSecondaryChannels([ this.chatViewModel.chatModel.setSecondaryChannels([
...this.secondaryChannels.value.values() ...this.secondaryChannels.value.values()
]); ]);
}; };
setEncryptionKey = () => { setEncryptionKey = () => {
this.chatModel.setEncryptionKey(this.encryptionKeyInput.value); this.chatViewModel.chatModel.setEncryptionKey(
this.encryptionKeyInput.value
);
this.encryptionKeyInput.callSubscriptions(); this.encryptionKeyInput.callSubscriptions();
}; };
setColor = (newColor) => { setColor = (newColor) => {
this.color.value = newColor; this.color.value = newColor;
this.chatModel.setColor(newColor); this.chatViewModel.chatModel.setColor(newColor);
}; };
remove = () => { remove = () => {
this.close(); this.chatViewModel.close();
this.chatModel.delete(); this.chatViewModel.chatModel.delete();
this.chatListViewModel.loadChats(); this.chatViewModel.chatListViewModel.loadChats();
}; };
// messaging // load
sendMessage = () => { loadViewRelevantData = () => {
if (this.cannotSendMessage.value == true) return; this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel;
this.sendMessageFromBody(this.composingMessage.value); this.color.value = this.chatViewModel.chatModel.color;
this.composingMessage.value = ""; this.cannotSetEncryptionKey = createProxyState(
[this.encryptionKeyInput],
() => this.encryptionKeyInput.value == this.chatViewModel.chatModel.info.encryptionKey
);
}; };
sendMessageFromBody = (body) => { loadData = () => {
this.chatModel.sendMessage(body); this.primaryChannelInput.value = this.chatViewModel.chatModel.info.primaryChannel;
this.loadSecondaryChannels();
this.encryptionKeyInput.value = this.chatViewModel.chatModel.info.encryptionKey;
}; };
decryptMessage = async (messageViewModel) => { loadSecondaryChannels = () => {
const chatMessage = messageViewModel.chatMessage; this.secondaryChannels.clear();
await this.chatModel.decryptMessage(chatMessage); for (const secondaryChannel of this.chatViewModel.chatModel.secondaryChannels) {
this.chatModel.addMessage(chatMessage); this.secondaryChannels.add(secondaryChannel);
messageViewModel.loadData(); }
};
// init
constructor(chatViewModel) {
this.chatViewModel = chatViewModel;
this.loadViewRelevantData();
}
};
// src/ViewModel/Pages/taskPageViewModel.ts
var TaskPageViewModel = class {
taskModel;
// state
newBoardNameInput = new State("");
boards = new ListState();
// guards
cannotCreateBoard = createProxyState(
[this.newBoardNameInput],
() => this.newBoardNameInput.value == ""
);
// methods
createBoard = () => {
if (this.cannotCreateBoard.value == true) return;
this.taskModel.createBoard(this.newBoardNameInput.value);
this.newBoardNameInput.value = "";
};
// init
constructor(taskModel, storageModel2) {
this.taskModel = taskModel;
}
};
// src/ViewModel/Chat/chatViewModel.ts
var ChatViewModel = class {
chatModel;
storageModel;
settingsViewModel;
chatListViewModel;
taskPageViewModel;
messagePageViewModel;
settingsPageViewModel;
// state
index = new State(0);
selectedPage = new State(
"messages" /* Messages */
);
// sorting
updateIndex = () => {
this.index.value = this.chatModel.index;
};
// view
open = () => {
this.chatListViewModel.openChat(this);
};
close = () => {
this.chatListViewModel.closeChat();
}; };
// load // load
loadPageSelection = () => { loadPageSelection = () => {
@@ -1358,47 +1445,27 @@
this.storageModel.write(path, newPage); this.storageModel.write(path, newPage);
}); });
}; };
loadSecondaryChannels = () => {
this.secondaryChannels.clear();
for (const secondaryChannel of this.chatModel.info.secondaryChannels.sort(
localeCompare
)) {
this.secondaryChannels.add(secondaryChannel);
}
};
loadMessages = () => {
for (const chatMessage of this.chatModel.messages) {
this.addChatMessage(chatMessage);
}
};
// init // init
constructor(chatModel, storageModel2, settingsViewModel2, chatListViewModel2) { constructor(chatModel, storageModel2, settingsViewModel2, chatListViewModel2) {
this.chatModel = chatModel; this.chatModel = chatModel;
this.storageModel = storageModel2; this.storageModel = storageModel2;
this.settingsViewModel = settingsViewModel2; this.settingsViewModel = settingsViewModel2;
this.chatListViewModel = chatListViewModel2; this.chatListViewModel = chatListViewModel2;
chatModel.setMessageHandler((chatMessage) => { this.taskPageViewModel = new TaskPageViewModel(
this.addChatMessage(chatMessage); this.chatModel.fileModel.taskModel,
}); this.storageModel
this.primaryChannel.value = chatModel.info.primaryChannel;
this.primaryChannelInput.value = chatModel.info.primaryChannel;
this.encryptionKeyInput.value = chatModel.info.encryptionKey;
this.cannotSetEncryptionKey = createProxyState(
[this.encryptionKeyInput],
() => this.encryptionKeyInput.value == this.chatModel.info.encryptionKey
); );
this.color.value = chatModel.color; this.messagePageViewModel = new MessagePageViewModel(this);
this.loadSecondaryChannels(); this.settingsPageViewModel = new SettingsPageViewModel(this);
chatModel.setMessageHandler((chatMessage) => {
this.messagePageViewModel.addChatMessage(chatMessage);
});
this.loadPageSelection(); this.loadPageSelection();
this.updateIndex(); this.updateIndex();
this.cannotSendMessage = createProxyState(
[this.settingsViewModel.username, this.composingMessage],
() => this.settingsViewModel.username.value == "" || this.composingMessage.value == ""
);
} }
}; };
// src/ViewModel/chatListViewModel.ts // src/ViewModel/Chat/chatListViewModel.ts
var ChatListViewModel = class { var ChatListViewModel = class {
chatListModel; chatListModel;
storageModel; storageModel;
@@ -1565,6 +1632,7 @@
deleteChatButton: "Delete entire chat" deleteChatButton: "Delete entire chat"
}, },
message: { message: {
messagesHeadline: "Messages",
composerInputPlaceholder: "Type a message...", composerInputPlaceholder: "Type a message...",
sendMessageButtonAudioLabel: "send message", sendMessageButtonAudioLabel: "send message",
showMessageInfoButtonAudioLabel: "show message info", showMessageInfoButtonAudioLabel: "show message info",
@@ -1661,13 +1729,14 @@
}; };
// src/View/ChatPages/messagePage.tsx // src/View/ChatPages/messagePage.tsx
function MessagePage(chatViewModel) { function MessagePage(messagePageViewModel) {
messagePageViewModel.loadMessages();
const messageContainer = /* @__PURE__ */ createElement( const messageContainer = /* @__PURE__ */ createElement(
"div", "div",
{ {
id: "message-container", id: "message-container",
"children:append": [ "children:append": [
chatViewModel.chatMessageViewModels, messagePageViewModel.chatMessageViewModels,
ChatMessageViewModelToView ChatMessageViewModelToView
] ]
} }
@@ -1680,13 +1749,15 @@
if (scrollFromBottom > 400) return; if (scrollFromBottom > 400) return;
scrollDown(); scrollDown();
} }
chatViewModel.chatMessageViewModels.subscribeSilent(scrollDownIfApplicable); messagePageViewModel.chatMessageViewModels.subscribeSilent(
scrollDownIfApplicable
);
setTimeout(() => scrollDown(), 100); setTimeout(() => scrollDown(), 100);
return /* @__PURE__ */ createElement("div", { id: "message-page" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__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("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("div", { id: "message-page" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.messagesHeadline)), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement(
"input", "input",
{ {
"bind:value": chatViewModel.composingMessage, "bind:value": messagePageViewModel.composingMessage,
"on:enter": chatViewModel.sendMessage, "on:enter": messagePageViewModel.sendMessage,
placeholder: translations.chatPage.message.composerInputPlaceholder placeholder: translations.chatPage.message.composerInputPlaceholder
} }
), /* @__PURE__ */ createElement( ), /* @__PURE__ */ createElement(
@@ -1694,8 +1765,8 @@
{ {
class: "primary", class: "primary",
"aria-label": translations.chatPage.message.sendMessageButtonAudioLabel, "aria-label": translations.chatPage.message.sendMessageButtonAudioLabel,
"on:click": chatViewModel.sendMessage, "on:click": messagePageViewModel.sendMessage,
"toggle:disabled": chatViewModel.cannotSendMessage "toggle:disabled": messagePageViewModel.cannotSendMessage
}, },
/* @__PURE__ */ createElement("span", { class: "icon" }, "send") /* @__PURE__ */ createElement("span", { class: "icon" }, "send")
))))))); )))))));
@@ -1749,25 +1820,26 @@
} }
// src/View/ChatPages/settingsPage.tsx // src/View/ChatPages/settingsPage.tsx
function SettingsPage(chatViewModel) { function SettingsPage(settingsPageViewModel) {
settingsPageViewModel.loadData();
const secondaryChannelConverter = (secondaryChannel) => { const secondaryChannelConverter = (secondaryChannel) => {
return DeletableListItem(secondaryChannel, /* @__PURE__ */ createElement("span", null), () => { return DeletableListItem(secondaryChannel, /* @__PURE__ */ createElement("span", null), () => {
chatViewModel.removeSecondaryChannel(secondaryChannel); settingsPageViewModel.removeSecondaryChannel(secondaryChannel);
}); });
}; };
return /* @__PURE__ */ createElement("div", { id: "settings-page" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, translations.chatPage.settings.settingsHeadline)), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.settings.primaryChannelLabel), /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("div", { id: "settings-page" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, translations.chatPage.settings.settingsHeadline)), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.settings.primaryChannelLabel), /* @__PURE__ */ createElement(
"input", "input",
{ {
"bind:value": chatViewModel.primaryChannelInput, "bind:value": settingsPageViewModel.primaryChannelInput,
"on:enter": chatViewModel.setPrimaryChannel "on:enter": settingsPageViewModel.setPrimaryChannel
} }
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement( ))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
"button", "button",
{ {
class: "width-50", class: "width-50",
"aria-label": translations.chatPage.settings.setPrimaryChannelButtonAudioLabel, "aria-label": translations.chatPage.settings.setPrimaryChannelButtonAudioLabel,
"on:click": chatViewModel.setPrimaryChannel, "on:click": settingsPageViewModel.setPrimaryChannel,
"toggle:disabled": chatViewModel.cannotSetPrimaryChannel "toggle:disabled": settingsPageViewModel.cannotSetPrimaryChannel
}, },
translations.general.setButton, translations.general.setButton,
/* @__PURE__ */ createElement("span", { class: "icon" }, "check") /* @__PURE__ */ createElement("span", { class: "icon" }, "check")
@@ -1776,16 +1848,16 @@
{ {
"aria-label": translations.chatPage.settings.newSecondaryChannelAudioLabel, "aria-label": translations.chatPage.settings.newSecondaryChannelAudioLabel,
placeholder: translations.chatPage.settings.newSecondaryChannelPlaceholder, placeholder: translations.chatPage.settings.newSecondaryChannelPlaceholder,
"bind:value": chatViewModel.newSecondaryChannelInput, "bind:value": settingsPageViewModel.newSecondaryChannelInput,
"on:enter": chatViewModel.addSecondaryChannel "on:enter": settingsPageViewModel.addSecondaryChannel
} }
), /* @__PURE__ */ createElement( ), /* @__PURE__ */ createElement(
"button", "button",
{ {
class: "primary", class: "primary",
"aria-label": translations.chatPage.settings.addSecondaryChannelButtonAudioLabel, "aria-label": translations.chatPage.settings.addSecondaryChannelButtonAudioLabel,
"on:click": chatViewModel.addSecondaryChannel, "on:click": settingsPageViewModel.addSecondaryChannel,
"toggle:disabled": chatViewModel.cannotAddSecondaryChannel "toggle:disabled": settingsPageViewModel.cannotAddSecondaryChannel
}, },
/* @__PURE__ */ createElement("span", { class: "icon" }, "add") /* @__PURE__ */ createElement("span", { class: "icon" }, "add")
)), /* @__PURE__ */ createElement( )), /* @__PURE__ */ createElement(
@@ -1793,24 +1865,24 @@
{ {
class: "flex-column gap width-input", class: "flex-column gap width-input",
"children:append": [ "children:append": [
chatViewModel.secondaryChannels, settingsPageViewModel.secondaryChannels,
secondaryChannelConverter secondaryChannelConverter
] ]
} }
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.settings.encryptionKeyLabel), /* @__PURE__ */ createElement( ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.settings.encryptionKeyLabel), /* @__PURE__ */ createElement(
"input", "input",
{ {
"bind:value": chatViewModel.encryptionKeyInput, "bind:value": settingsPageViewModel.encryptionKeyInput,
"on:enter": chatViewModel.setEncryptionKey, "on:enter": settingsPageViewModel.setEncryptionKey,
"set:type": chatViewModel.encryptionKeyInputType "set:type": settingsPageViewModel.encryptionKeyInputType
} }
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement( ))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
"button", "button",
{ {
class: "width-50", class: "width-50",
"aria-label": translations.chatPage.settings.setEncryptionKeyButtonAudioLabel, "aria-label": translations.chatPage.settings.setEncryptionKeyButtonAudioLabel,
"on:click": chatViewModel.setEncryptionKey, "on:click": settingsPageViewModel.setEncryptionKey,
"toggle:disabled": chatViewModel.cannotSetEncryptionKey "toggle:disabled": settingsPageViewModel.cannotSetEncryptionKey
}, },
translations.general.setButton, translations.general.setButton,
/* @__PURE__ */ createElement("span", { class: "icon" }, "check") /* @__PURE__ */ createElement("span", { class: "icon" }, "check")
@@ -1818,15 +1890,15 @@
"input", "input",
{ {
type: "checkbox", type: "checkbox",
"bind:checked": chatViewModel.shouldShowEncryptionKey "bind:checked": settingsPageViewModel.shouldShowEncryptionKey
} }
), translations.chatPage.settings.showEncryptionKey), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row gap width-input" }, ...Object.values(Color).map((color) => { ), translations.chatPage.settings.showEncryptionKey), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row gap width-input" }, ...Object.values(Color).map((color) => {
const isSelected = createProxyState( const isSelected = createProxyState(
[chatViewModel.color], [settingsPageViewModel.color],
() => chatViewModel.color.value == color () => settingsPageViewModel.color.value == color
); );
function setColor() { function setColor() {
chatViewModel.setColor(color); settingsPageViewModel.setColor(color);
} }
return /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"button", "button",
@@ -1841,25 +1913,29 @@
})), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( })), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton(
translations.chatPage.settings.deleteChatButton, translations.chatPage.settings.deleteChatButton,
"chat_error", "chat_error",
chatViewModel.remove settingsPageViewModel.remove
))))); )))));
} }
// src/View/ChatPages/taskPage.tsx // src/View/ChatPages/taskPage.tsx
function TaskPage(chatViewModel) { function TaskPage(taskPageViewModel) {
return /* @__PURE__ */ createElement("div", { id: "task-page" }, /* @__PURE__ */ createElement("div", { class: "pane side" }, /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("div", { id: "task-page" }, /* @__PURE__ */ createElement("div", { class: "pane side" }, /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
"input", "input",
{ {
"bind:value": taskPageViewModel.newBoardNameInput,
"on:enter": taskPageViewModel.createBoard,
placeholder: translations.chatPage.task.newBoardNamePlaceholder placeholder: translations.chatPage.task.newBoardNamePlaceholder
} }
), /* @__PURE__ */ createElement( ), /* @__PURE__ */ createElement(
"button", "button",
{ {
class: "primary", class: "primary",
"aria-label": translations.chatPage.task.createBoardButtonAudioLabel "aria-label": translations.chatPage.task.createBoardButtonAudioLabel,
"on:click": taskPageViewModel.createBoard,
"toggle:disabled": taskPageViewModel.cannotCreateBoard
}, },
/* @__PURE__ */ createElement("span", { class: "icon" }, "add") /* @__PURE__ */ createElement("span", { class: "icon" }, "add")
)), /* @__PURE__ */ createElement("div", { class: "flex-row gap" }))), /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", { "subscribe:innerText": chatViewModel.primaryChannel })), /* @__PURE__ */ createElement("div", { class: "content" }))); )), /* @__PURE__ */ createElement("div", { class: "flex-row gap" }))), /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }), /* @__PURE__ */ createElement("div", { class: "content" })));
} }
// src/View/chatPage.tsx // src/View/chatPage.tsx
@@ -1867,19 +1943,24 @@
const mainContent = new State(/* @__PURE__ */ createElement("div", null)); const mainContent = new State(/* @__PURE__ */ createElement("div", null));
chatViewModel.selectedPage.subscribe((selectedPage) => { chatViewModel.selectedPage.subscribe((selectedPage) => {
switch (selectedPage) { switch (selectedPage) {
case "settings" /* Settings */: case "settings" /* Settings */: {
return mainContent.value = SettingsPage(chatViewModel); mainContent.value = SettingsPage(chatViewModel.settingsPageViewModel);
case "tasks" /* Tasks */: break;
return mainContent.value = TaskPage(chatViewModel); }
default: case "tasks" /* Tasks */: {
return mainContent.value = MessagePage(chatViewModel); mainContent.value = TaskPage(chatViewModel.taskPageViewModel);
break;
}
default: {
mainContent.value = MessagePage(chatViewModel.messagePageViewModel);
}
} }
}); });
return /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"article", "article",
{ {
id: "chat-page", id: "chat-page",
"set:color": chatViewModel.color, "set:color": chatViewModel.settingsPageViewModel.color,
class: "subtle-background" class: "subtle-background"
}, },
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { id: "ribbon" }, /* @__PURE__ */ createElement( /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { id: "ribbon" }, /* @__PURE__ */ createElement(
@@ -2300,7 +2381,7 @@
} }
}; };
// src/ViewModel/connectionViewModel.ts // src/ViewModel/Global/connectionViewModel.ts
var ConnectionViewModel = class { var ConnectionViewModel = class {
connectionModel; connectionModel;
// state // state
@@ -2374,13 +2455,27 @@
// src/View/Components/chatEntry.tsx // src/View/Components/chatEntry.tsx
function ChatEntry(chatViewModel) { function ChatEntry(chatViewModel) {
const view = /* @__PURE__ */ createElement("button", { "set:color": chatViewModel.color, class: "chat-entry tile", "on:click": chatViewModel.open }, /* @__PURE__ */ createElement( const view = /* @__PURE__ */ createElement(
"span", "button",
{ {
class: "shadow", "set:color": chatViewModel.settingsPageViewModel.color,
"subscribe:innerText": chatViewModel.primaryChannel class: "chat-entry tile",
} "on:click": chatViewModel.open
), /* @__PURE__ */ createElement("h2", { "subscribe:innerText": chatViewModel.primaryChannel })); },
/* @__PURE__ */ createElement(
"span",
{
class: "shadow",
"subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel
}
),
/* @__PURE__ */ createElement(
"h2",
{
"subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel
}
)
);
chatViewModel.index.subscribe((newIndex) => { chatViewModel.index.subscribe((newIndex) => {
view.style.order = newIndex; view.style.order = newIndex;
}); });
@@ -2542,7 +2637,7 @@
} }
}; };
// src/ViewModel/settingsViewModel.ts // src/ViewModel/Global/settingsViewModel.ts
var SettingsViewModel = class { var SettingsViewModel = class {
settingsModel; settingsModel;
// state // state
@@ -2665,7 +2760,7 @@
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", { class: "padding-0" }, FileBrowser(storageViewModel2)), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.hideStorageModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", { class: "padding-0" }, FileBrowser(storageViewModel2)), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.hideStorageModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
} }
// src/ViewModel/storageViewModel.ts // src/ViewModel/Global/storageViewModel.ts
var StorageViewModel = class { var StorageViewModel = class {
storageModel; storageModel;
// state // state
+5
View File
@@ -5,6 +5,7 @@ import FileModel, { FileContent } from "../Files/fileModel";
import StorageModel, { filePaths } from "../Global/storageModel"; import StorageModel, { filePaths } from "../Global/storageModel";
import { import {
createTimestamp, createTimestamp,
localeCompare,
parseValidObject, parseValidObject,
stringify, stringify,
} from "../Utility/utility"; } from "../Utility/utility";
@@ -31,6 +32,10 @@ export default class ChatModel {
chatMessageHandler: (chatMessage: ChatMessage) => void = () => {}; chatMessageHandler: (chatMessage: ChatMessage) => void = () => {};
get secondaryChannels(): string[] {
return this.info.secondaryChannels.sort(localeCompare);
}
// paths // paths
getBasePath = (): string[] => { getBasePath = (): string[] => {
return StorageModel.getPath("chat", filePaths.chat.chatBase(this.id)); return StorageModel.getPath("chat", filePaths.chat.chatBase(this.id));
+13 -9
View File
@@ -3,15 +3,17 @@ import "./messagePage.css";
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import { ChatMessageViewModelToView } from "../Components/chatMessage"; import { ChatMessageViewModelToView } from "../Components/chatMessage";
import ChatViewModel from "../../ViewModel/Chat/chatViewModel"; import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel";
import { translations } from "../translations"; import { translations } from "../translations";
export function MessagePage(chatViewModel: ChatViewModel) { export function MessagePage(messagePageViewModel: MessagePageViewModel) {
messagePageViewModel.loadMessages();
const messageContainer = ( const messageContainer = (
<div <div
id="message-container" id="message-container"
children:append={[ children:append={[
chatViewModel.chatMessageViewModels, messagePageViewModel.chatMessageViewModels,
ChatMessageViewModelToView, ChatMessageViewModelToView,
]} ]}
></div> ></div>
@@ -28,14 +30,16 @@ export function MessagePage(chatViewModel: ChatViewModel) {
scrollDown(); scrollDown();
} }
chatViewModel.chatMessageViewModels.subscribeSilent(scrollDownIfApplicable); messagePageViewModel.chatMessageViewModels.subscribeSilent(
scrollDownIfApplicable
);
setTimeout(() => scrollDown(), 100); setTimeout(() => scrollDown(), 100);
return ( return (
<div id="message-page"> <div id="message-page">
<div class="pane"> <div class="pane">
<div class="toolbar"> <div class="toolbar">
<span subscribe:innerText={chatViewModel.primaryChannel}></span> <span>{translations.chatPage.message.messagesHeadline}</span>
</div> </div>
<div class="content"> <div class="content">
{messageContainer} {messageContainer}
@@ -43,8 +47,8 @@ export function MessagePage(chatViewModel: ChatViewModel) {
<div class="content-width-constraint"> <div class="content-width-constraint">
<div class="input-width-constraint"> <div class="input-width-constraint">
<input <input
bind:value={chatViewModel.composingMessage} bind:value={messagePageViewModel.composingMessage}
on:enter={chatViewModel.sendMessage} on:enter={messagePageViewModel.sendMessage}
placeholder={ placeholder={
translations.chatPage.message.composerInputPlaceholder translations.chatPage.message.composerInputPlaceholder
} }
@@ -54,8 +58,8 @@ export function MessagePage(chatViewModel: ChatViewModel) {
aria-label={ aria-label={
translations.chatPage.message.sendMessageButtonAudioLabel translations.chatPage.message.sendMessageButtonAudioLabel
} }
on:click={chatViewModel.sendMessage} on:click={messagePageViewModel.sendMessage}
toggle:disabled={chatViewModel.cannotSendMessage} toggle:disabled={messagePageViewModel.cannotSendMessage}
> >
<span class="icon">send</span> <span class="icon">send</span>
</button> </button>
+24 -22
View File
@@ -1,17 +1,19 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import ChatViewModel from "../../ViewModel/chatViewModel";
import { Color } from "../../colors"; import { Color } from "../../colors";
import { DangerousActionButton } from "../Components/dangerousActionButton"; import { DangerousActionButton } from "../Components/dangerousActionButton";
import { DeletableListItem } from "../Components/deletableListItem"; import { DeletableListItem } from "../Components/deletableListItem";
import SettingsPageViewModel from "../../ViewModel/Pages/settingsPageViewModel";
import { translations } from "../translations"; import { translations } from "../translations";
export function SettingsPage(chatViewModel: ChatViewModel) { export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
settingsPageViewModel.loadData();
const secondaryChannelConverter: React.StateItemConverter<string> = ( const secondaryChannelConverter: React.StateItemConverter<string> = (
secondaryChannel: string secondaryChannel: string
) => { ) => {
return DeletableListItem(secondaryChannel, <span></span>, () => { return DeletableListItem(secondaryChannel, <span></span>, () => {
chatViewModel.removeSecondaryChannel(secondaryChannel); settingsPageViewModel.removeSecondaryChannel(secondaryChannel);
}); });
}; };
@@ -27,8 +29,8 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
<div> <div>
<span>{translations.chatPage.settings.primaryChannelLabel}</span> <span>{translations.chatPage.settings.primaryChannelLabel}</span>
<input <input
bind:value={chatViewModel.primaryChannelInput} bind:value={settingsPageViewModel.primaryChannelInput}
on:enter={chatViewModel.setPrimaryChannel} on:enter={settingsPageViewModel.setPrimaryChannel}
></input> ></input>
</div> </div>
</label> </label>
@@ -38,8 +40,8 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
aria-label={ aria-label={
translations.chatPage.settings.setPrimaryChannelButtonAudioLabel translations.chatPage.settings.setPrimaryChannelButtonAudioLabel
} }
on:click={chatViewModel.setPrimaryChannel} on:click={settingsPageViewModel.setPrimaryChannel}
toggle:disabled={chatViewModel.cannotSetPrimaryChannel} toggle:disabled={settingsPageViewModel.cannotSetPrimaryChannel}
> >
{translations.general.setButton} {translations.general.setButton}
<span class="icon">check</span> <span class="icon">check</span>
@@ -56,8 +58,8 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
placeholder={ placeholder={
translations.chatPage.settings.newSecondaryChannelPlaceholder translations.chatPage.settings.newSecondaryChannelPlaceholder
} }
bind:value={chatViewModel.newSecondaryChannelInput} bind:value={settingsPageViewModel.newSecondaryChannelInput}
on:enter={chatViewModel.addSecondaryChannel} on:enter={settingsPageViewModel.addSecondaryChannel}
></input> ></input>
<button <button
class="primary" class="primary"
@@ -65,8 +67,8 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
translations.chatPage.settings translations.chatPage.settings
.addSecondaryChannelButtonAudioLabel .addSecondaryChannelButtonAudioLabel
} }
on:click={chatViewModel.addSecondaryChannel} on:click={settingsPageViewModel.addSecondaryChannel}
toggle:disabled={chatViewModel.cannotAddSecondaryChannel} toggle:disabled={settingsPageViewModel.cannotAddSecondaryChannel}
> >
<span class="icon">add</span> <span class="icon">add</span>
</button> </button>
@@ -75,7 +77,7 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
<div <div
class="flex-column gap width-input" class="flex-column gap width-input"
children:append={[ children:append={[
chatViewModel.secondaryChannels, settingsPageViewModel.secondaryChannels,
secondaryChannelConverter, secondaryChannelConverter,
]} ]}
></div> ></div>
@@ -87,9 +89,9 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
<div> <div>
<span>{translations.chatPage.settings.encryptionKeyLabel}</span> <span>{translations.chatPage.settings.encryptionKeyLabel}</span>
<input <input
bind:value={chatViewModel.encryptionKeyInput} bind:value={settingsPageViewModel.encryptionKeyInput}
on:enter={chatViewModel.setEncryptionKey} on:enter={settingsPageViewModel.setEncryptionKey}
set:type={chatViewModel.encryptionKeyInputType} set:type={settingsPageViewModel.encryptionKeyInputType}
></input> ></input>
</div> </div>
</label> </label>
@@ -99,8 +101,8 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
aria-label={ aria-label={
translations.chatPage.settings.setEncryptionKeyButtonAudioLabel translations.chatPage.settings.setEncryptionKeyButtonAudioLabel
} }
on:click={chatViewModel.setEncryptionKey} on:click={settingsPageViewModel.setEncryptionKey}
toggle:disabled={chatViewModel.cannotSetEncryptionKey} toggle:disabled={settingsPageViewModel.cannotSetEncryptionKey}
> >
{translations.general.setButton} {translations.general.setButton}
<span class="icon">check</span> <span class="icon">check</span>
@@ -110,7 +112,7 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
<label class="inline"> <label class="inline">
<input <input
type="checkbox" type="checkbox"
bind:checked={chatViewModel.shouldShowEncryptionKey} bind:checked={settingsPageViewModel.shouldShowEncryptionKey}
></input> ></input>
{translations.chatPage.settings.showEncryptionKey} {translations.chatPage.settings.showEncryptionKey}
</label> </label>
@@ -120,12 +122,12 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
<div class="flex-row gap width-input"> <div class="flex-row gap width-input">
{...Object.values(Color).map((color) => { {...Object.values(Color).map((color) => {
const isSelected = React.createProxyState( const isSelected = React.createProxyState(
[chatViewModel.color], [settingsPageViewModel.color],
() => chatViewModel.color.value == color () => settingsPageViewModel.color.value == color
); );
function setColor() { function setColor() {
chatViewModel.setColor(color); settingsPageViewModel.setColor(color);
} }
return ( return (
@@ -146,7 +148,7 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
{DangerousActionButton( {DangerousActionButton(
translations.chatPage.settings.deleteChatButton, translations.chatPage.settings.deleteChatButton,
"chat_error", "chat_error",
chatViewModel.remove settingsPageViewModel.remove
)} )}
</div> </div>
</div> </div>
+6 -6
View File
@@ -1,17 +1,17 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import TaskViewModel from "../../ViewModel/Files/taskViewModel"; import TaskPageViewModel from "../../ViewModel/Pages/taskPageViewModel";
import { translations } from "../translations"; import { translations } from "../translations";
export function TaskPage(taskViewModel: TaskViewModel) { export function TaskPage(taskPageViewModel: TaskPageViewModel) {
return ( return (
<div id="task-page"> <div id="task-page">
<div class="pane side"> <div class="pane side">
<div class="content"> <div class="content">
<div class="flex-row width-input"> <div class="flex-row width-input">
<input <input
bind:value={taskViewModel.newBoardNameInput} bind:value={taskPageViewModel.newBoardNameInput}
on:enter={taskViewModel.createBoard} on:enter={taskPageViewModel.createBoard}
placeholder={translations.chatPage.task.newBoardNamePlaceholder} placeholder={translations.chatPage.task.newBoardNamePlaceholder}
></input> ></input>
<button <button
@@ -19,8 +19,8 @@ export function TaskPage(taskViewModel: TaskViewModel) {
aria-label={ aria-label={
translations.chatPage.task.createBoardButtonAudioLabel translations.chatPage.task.createBoardButtonAudioLabel
} }
on:click={taskViewModel.createBoard} on:click={taskPageViewModel.createBoard}
toggle:disabled={taskViewModel.cannotCreateBoard} toggle:disabled={taskPageViewModel.cannotCreateBoard}
> >
<span class="icon">add</span> <span class="icon">add</span>
</button> </button>
+9 -3
View File
@@ -6,12 +6,18 @@ import ChatViewModel from "../../ViewModel/Chat/chatViewModel";
export function ChatEntry(chatViewModel: ChatViewModel) { export function ChatEntry(chatViewModel: ChatViewModel) {
const view = ( const view = (
<button set:color={chatViewModel.color} class="chat-entry tile" on:click={chatViewModel.open}> <button
set:color={chatViewModel.settingsPageViewModel.color}
class="chat-entry tile"
on:click={chatViewModel.open}
>
<span <span
class="shadow" class="shadow"
subscribe:innerText={chatViewModel.primaryChannel} subscribe:innerText={chatViewModel.settingsPageViewModel.primaryChannel}
></span> ></span>
<h2 subscribe:innerText={chatViewModel.primaryChannel}></h2> <h2
subscribe:innerText={chatViewModel.settingsPageViewModel.primaryChannel}
></h2>
</button> </button>
); );
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import ChatMessageViewModel from "../../Model/chatMessageViewModel"; import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
import { translations } from "../translations"; import { translations } from "../translations";
export function ChatMessageInfoModal( export function ChatMessageInfoModal(
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import ChatViewModel, { ChatPageType } from "../../ViewModel/chatViewModel"; import ChatViewModel, { ChatPageType } from "../../ViewModel/Chat/chatViewModel";
export function ChatViewToggleButton( export function ChatViewToggleButton(
label: string, label: string,
+12 -7
View File
@@ -15,19 +15,24 @@ export function ChatPage(chatViewModel: ChatViewModel) {
chatViewModel.selectedPage.subscribe((selectedPage) => { chatViewModel.selectedPage.subscribe((selectedPage) => {
switch (selectedPage) { switch (selectedPage) {
case ChatPageType.Settings: case ChatPageType.Settings: {
return (mainContent.value = SettingsPage(chatViewModel)); mainContent.value = SettingsPage(chatViewModel.settingsPageViewModel);
case ChatPageType.Tasks: break;
return (mainContent.value = TaskPage(chatViewModel)); }
default: case ChatPageType.Tasks: {
return (mainContent.value = MessagePage(chatViewModel)); mainContent.value = TaskPage(chatViewModel.taskPageViewModel);
break;
}
default: {
mainContent.value = MessagePage(chatViewModel.messagePageViewModel);
}
} }
}); });
return ( return (
<article <article
id="chat-page" id="chat-page"
set:color={chatViewModel.color} set:color={chatViewModel.settingsPageViewModel.color}
class="subtle-background" class="subtle-background"
> >
<div> <div>
+1 -1
View File
@@ -2,7 +2,7 @@ import "./chatPage.css";
import * as React from "bloatless-react"; import * as React from "bloatless-react";
import ChatListViewModel from "../ViewModel/chatListViewModel"; import ChatListViewModel from "../ViewModel/Chat/chatListViewModel";
import { ChatPage } from "./chatPage"; import { ChatPage } from "./chatPage";
export function ChatPageWrapper(chatListViewModel: ChatListViewModel) { export function ChatPageWrapper(chatListViewModel: ChatListViewModel) {
+1 -1
View File
@@ -4,7 +4,7 @@ import * as React from "bloatless-react";
import { Option, StringToOption } from "./Components/option"; import { Option, StringToOption } from "./Components/option";
import ChatListViewModel from "../ViewModel/chatListViewModel"; import ChatListViewModel from "../ViewModel/Chat/chatListViewModel";
import { ChatViewModelToChatEntry } from "./Components/chatEntry"; import { ChatViewModelToChatEntry } from "./Components/chatEntry";
import ConnectionViewModel from "../ViewModel/Global/connectionViewModel"; import ConnectionViewModel from "../ViewModel/Global/connectionViewModel";
import SettingsViewModel from "../ViewModel/Global/settingsViewModel"; import SettingsViewModel from "../ViewModel/Global/settingsViewModel";
+2
View File
@@ -104,6 +104,8 @@ const englishTranslations = {
}, },
message: { message: {
messagesHeadline: "Messages",
composerInputPlaceholder: "Type a message...", composerInputPlaceholder: "Type a message...",
sendMessageButtonAudioLabel: "send message", sendMessageButtonAudioLabel: "send message",
+6 -5
View File
@@ -5,9 +5,10 @@ import * as React from "bloatless-react";
import { ChatMessage, ChatMessageStatus } from "../../Model/Chat/chatModel"; import { ChatMessage, ChatMessageStatus } from "../../Model/Chat/chatModel";
import ChatViewModel from "./chatViewModel"; import ChatViewModel from "./chatViewModel";
import MessagePageViewModel from "../Pages/messagePageViewModel";
export default class ChatMessageViewModel { export default class ChatMessageViewModel {
chatViewModel: ChatViewModel; messagePageViewModel: MessagePageViewModel;
// data // data
chatMessage: ChatMessage; chatMessage: ChatMessage;
@@ -25,10 +26,10 @@ export default class ChatMessageViewModel {
navigator.clipboard.writeText(this.body.value); navigator.clipboard.writeText(this.body.value);
}; };
resendMessage = (): void => { resendMessage = (): void => {
this.chatViewModel.sendMessageFromBody(this.body.value); this.messagePageViewModel.sendMessageFromBody(this.body.value);
}; };
decryptMessage = (): void => { decryptMessage = (): void => {
this.chatViewModel.decryptMessage(this); this.messagePageViewModel.decryptMessage(this);
}; };
// load // load
@@ -42,11 +43,11 @@ export default class ChatMessageViewModel {
// init // init
constructor( constructor(
chatViewModel: ChatViewModel, messagePageViewModel: MessagePageViewModel,
chatMessage: ChatMessage, chatMessage: ChatMessage,
sentByUser: boolean sentByUser: boolean
) { ) {
this.chatViewModel = chatViewModel; this.messagePageViewModel = messagePageViewModel;
this.chatMessage = chatMessage; this.chatMessage = chatMessage;
this.sentByUser = sentByUser; this.sentByUser = sentByUser;
+16 -155
View File
@@ -4,10 +4,10 @@ import ChatModel, { ChatMessage } from "../../Model/Chat/chatModel";
import StorageModel, { filePaths } from "../../Model/Global/storageModel"; import StorageModel, { filePaths } from "../../Model/Global/storageModel";
import ChatListViewModel from "./chatListViewModel"; import ChatListViewModel from "./chatListViewModel";
import ChatMessageViewModel from "./chatMessageViewModel"; import MessagePageViewModel from "../Pages/messagePageViewModel";
import { Color } from "../../colors"; import SettingsPageViewModel from "../Pages/settingsPageViewModel";
import SettingsViewModel from "../Global/settingsViewModel"; import SettingsViewModel from "../Global/settingsViewModel";
import { localeCompare } from "../../Model/Utility/utility"; import TaskPageViewModel from "../Pages/taskPageViewModel";
export default class ChatViewModel { export default class ChatViewModel {
chatModel: ChatModel; chatModel: ChatModel;
@@ -15,46 +15,17 @@ export default class ChatViewModel {
settingsViewModel: SettingsViewModel; settingsViewModel: SettingsViewModel;
chatListViewModel: ChatListViewModel; chatListViewModel: ChatListViewModel;
taskPageViewModel: TaskPageViewModel;
messagePageViewModel: MessagePageViewModel;
settingsPageViewModel: SettingsPageViewModel;
// state // state
index: React.State<number> = new React.State(0); index: React.State<number> = new React.State(0);
primaryChannel: React.State<string> = new React.State("");
primaryChannelInput: React.State<string> = new React.State("");
secondaryChannels: React.ListState<string> = new React.ListState();
newSecondaryChannelInput: React.State<string> = new React.State("");
encryptionKeyInput: React.State<string> = new React.State("");
shouldShowEncryptionKey: React.State<boolean> = new React.State(false);
encryptionKeyInputType: React.State<"text" | "password"> =
React.createProxyState([this.shouldShowEncryptionKey], () =>
this.shouldShowEncryptionKey.value == true ? "text" : "password"
);
color: React.State<Color> = new React.State<Color>(Color.Standard);
selectedPage: React.State<ChatPageType> = new React.State<ChatPageType>( selectedPage: React.State<ChatPageType> = new React.State<ChatPageType>(
ChatPageType.Messages ChatPageType.Messages
); );
chatMessageViewModels: React.MapState<ChatMessageViewModel> =
new React.MapState();
composingMessage: React.State<string> = new React.State("");
// guards
cannotSetPrimaryChannel: React.State<boolean> = React.createProxyState(
[this.primaryChannel, this.primaryChannelInput],
() =>
this.primaryChannelInput.value == "" ||
this.primaryChannelInput.value == this.primaryChannel.value
);
cannotAddSecondaryChannel: React.State<boolean> = React.createProxyState(
[this.newSecondaryChannelInput],
() => this.newSecondaryChannelInput.value == ""
);
cannotSetEncryptionKey: React.State<boolean>;
cannotSendMessage: React.State<boolean>;
// sorting // sorting
updateIndex = (): void => { updateIndex = (): void => {
this.index.value = this.chatModel.index; this.index.value = this.chatModel.index;
@@ -63,95 +34,12 @@ export default class ChatViewModel {
// view // view
open = (): void => { open = (): void => {
this.chatListViewModel.openChat(this); this.chatListViewModel.openChat(this);
this.loadMessages();
}; };
close = (): void => { close = (): void => {
this.chatListViewModel.closeChat(); this.chatListViewModel.closeChat();
}; };
// add
addChatMessage = (chatMessage: ChatMessage): void => {
const chatMessageModel = new ChatMessageViewModel(
this,
chatMessage,
chatMessage.sender == this.settingsViewModel.username.value
);
const existingChatMessageViewModel: ChatMessageViewModel | undefined =
this.chatMessageViewModels.value.get(chatMessage.id);
if (existingChatMessageViewModel != undefined) {
existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status;
} else {
this.chatMessageViewModels.set(chatMessage.id, chatMessageModel);
}
};
// settings
setPrimaryChannel = (): void => {
this.chatModel.setPrimaryChannel(this.primaryChannelInput.value);
this.primaryChannel.value = this.chatModel.info.primaryChannel;
this.chatListViewModel.updateIndices();
};
addSecondaryChannel = (): void => {
this.secondaryChannels.add(this.newSecondaryChannelInput.value);
this.newSecondaryChannelInput.value = "";
this.storeSecondaryChannels();
this.loadSecondaryChannels();
};
removeSecondaryChannel = (secondaryChannel: string): void => {
this.secondaryChannels.remove(secondaryChannel);
this.storeSecondaryChannels();
};
storeSecondaryChannels = (): void => {
this.chatModel.setSecondaryChannels([
...this.secondaryChannels.value.values(),
]);
};
setEncryptionKey = (): void => {
this.chatModel.setEncryptionKey(this.encryptionKeyInput.value);
// disable button
this.encryptionKeyInput.callSubscriptions();
};
setColor = (newColor: Color): void => {
this.color.value = newColor;
this.chatModel.setColor(newColor);
};
remove = (): void => {
this.close();
this.chatModel.delete();
this.chatListViewModel.loadChats();
};
// messaging
sendMessage = (): void => {
if (this.cannotSendMessage.value == true) return;
this.sendMessageFromBody(this.composingMessage.value);
this.composingMessage.value = "";
};
sendMessageFromBody = (body: string): void => {
this.chatModel.sendMessage(body);
};
decryptMessage = async (
messageViewModel: ChatMessageViewModel
): Promise<void> => {
const chatMessage: ChatMessage = messageViewModel.chatMessage;
await this.chatModel.decryptMessage(chatMessage);
this.chatModel.addMessage(chatMessage);
messageViewModel.loadData();
};
// load // load
loadPageSelection = (): void => { loadPageSelection = (): void => {
const path: string[] = StorageModel.getPath( const path: string[] = StorageModel.getPath(
@@ -168,21 +56,6 @@ export default class ChatViewModel {
}); });
}; };
loadSecondaryChannels = (): void => {
this.secondaryChannels.clear();
for (const secondaryChannel of this.chatModel.info.secondaryChannels.sort(
localeCompare
)) {
this.secondaryChannels.add(secondaryChannel);
}
};
loadMessages = (): void => {
for (const chatMessage of this.chatModel.messages) {
this.addChatMessage(chatMessage);
}
};
// init // init
constructor( constructor(
chatModel: ChatModel, chatModel: ChatModel,
@@ -196,34 +69,22 @@ export default class ChatViewModel {
this.settingsViewModel = settingsViewModel; this.settingsViewModel = settingsViewModel;
this.chatListViewModel = chatListViewModel; this.chatListViewModel = chatListViewModel;
// page viewModels
this.taskPageViewModel = new TaskPageViewModel(
this.chatModel.fileModel.taskModel,
this.storageModel
);
this.messagePageViewModel = new MessagePageViewModel(this);
this.settingsPageViewModel = new SettingsPageViewModel(this);
// handlers // handlers
chatModel.setMessageHandler((chatMessage) => { chatModel.setMessageHandler((chatMessage) => {
this.addChatMessage(chatMessage); this.messagePageViewModel.addChatMessage(chatMessage);
}); });
// load // load
this.primaryChannel.value = chatModel.info.primaryChannel;
this.primaryChannelInput.value = chatModel.info.primaryChannel;
this.encryptionKeyInput.value = chatModel.info.encryptionKey;
this.cannotSetEncryptionKey = React.createProxyState(
[this.encryptionKeyInput],
() => this.encryptionKeyInput.value == this.chatModel.info.encryptionKey
);
this.color.value = chatModel.color;
this.loadSecondaryChannels();
this.loadPageSelection(); this.loadPageSelection();
this.updateIndex(); this.updateIndex();
// guards
this.cannotSendMessage = React.createProxyState(
[this.settingsViewModel.username, this.composingMessage],
() =>
this.settingsViewModel.username.value == "" ||
this.composingMessage.value == ""
);
} }
} }
@@ -0,0 +1,78 @@
import * as React from "bloatless-react";
import { ChatMessage } from "../../Model/Chat/chatModel";
import ChatMessageViewModel from "../Chat/chatMessageViewModel";
import ChatViewModel from "../Chat/chatViewModel";
export default class MessagePageViewModel {
chatViewModel: ChatViewModel;
// state
chatMessageViewModels: React.MapState<ChatMessageViewModel> =
new React.MapState();
composingMessage: React.State<string> = new React.State("");
// guards
cannotSendMessage: React.State<boolean>;
// methods
addChatMessage = (chatMessage: ChatMessage): void => {
const chatMessageModel = new ChatMessageViewModel(
this,
chatMessage,
chatMessage.sender == this.chatViewModel.settingsViewModel.username.value
);
const existingChatMessageViewModel: ChatMessageViewModel | undefined =
this.chatMessageViewModels.value.get(chatMessage.id);
if (existingChatMessageViewModel != undefined) {
existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status;
} else {
this.chatMessageViewModels.set(chatMessage.id, chatMessageModel);
}
};
// messaging
sendMessage = (): void => {
if (this.cannotSendMessage.value == true) return;
this.sendMessageFromBody(this.composingMessage.value);
this.composingMessage.value = "";
};
sendMessageFromBody = (body: string): void => {
this.chatViewModel.chatModel.sendMessage(body);
};
decryptMessage = async (
messageViewModel: ChatMessageViewModel
): Promise<void> => {
const chatMessage: ChatMessage = messageViewModel.chatMessage;
await this.chatViewModel.chatModel.decryptMessage(chatMessage);
this.chatViewModel.chatModel.addMessage(chatMessage);
messageViewModel.loadData();
};
// load
loadData = (): void => {
this.cannotSendMessage = React.createProxyState(
[this.chatViewModel.settingsViewModel.username, this.composingMessage],
() =>
this.chatViewModel.settingsViewModel.username.value == "" ||
this.composingMessage.value == ""
);
};
loadMessages = (): void => {
for (const chatMessage of this.chatViewModel.chatModel.messages) {
this.addChatMessage(chatMessage);
}
};
// init
constructor(chatViewModel: ChatViewModel) {
this.chatViewModel = chatViewModel;
this.loadData();
}
}
@@ -0,0 +1,124 @@
import * as React from "bloatless-react";
import ChatViewModel from "../Chat/chatViewModel";
import { Color } from "../../colors";
export default class SettingsPageViewModel {
chatViewModel: ChatViewModel;
// state
primaryChannel: React.State<string> = new React.State("");
primaryChannelInput: React.State<string> = new React.State("");
secondaryChannels: React.ListState<string> = new React.ListState();
newSecondaryChannelInput: React.State<string> = new React.State("");
encryptionKeyInput: React.State<string> = new React.State("");
shouldShowEncryptionKey: React.State<boolean> = new React.State(false);
encryptionKeyInputType: React.State<"text" | "password"> =
React.createProxyState([this.shouldShowEncryptionKey], () =>
this.shouldShowEncryptionKey.value == true ? "text" : "password"
);
color: React.State<Color> = new React.State<Color>(Color.Standard);
// guards
cannotSetPrimaryChannel: React.State<boolean> = React.createProxyState(
[this.primaryChannel, this.primaryChannelInput],
() =>
this.primaryChannelInput.value == "" ||
this.primaryChannelInput.value == this.primaryChannel.value
);
cannotAddSecondaryChannel: React.State<boolean> = React.createProxyState(
[this.newSecondaryChannelInput],
() => this.newSecondaryChannelInput.value == ""
);
cannotSetEncryptionKey: React.State<boolean>;
// methods
setPrimaryChannel = (): void => {
this.chatViewModel.chatModel.setPrimaryChannel(
this.primaryChannelInput.value
);
this.primaryChannel.value =
this.chatViewModel.chatModel.info.primaryChannel;
this.chatViewModel.chatListViewModel.updateIndices();
};
addSecondaryChannel = (): void => {
this.secondaryChannels.add(this.newSecondaryChannelInput.value);
this.newSecondaryChannelInput.value = "";
this.storeSecondaryChannels();
this.loadSecondaryChannels();
};
removeSecondaryChannel = (secondaryChannel: string): void => {
this.secondaryChannels.remove(secondaryChannel);
this.storeSecondaryChannels();
};
storeSecondaryChannels = (): void => {
this.chatViewModel.chatModel.setSecondaryChannels([
...this.secondaryChannels.value.values(),
]);
};
setEncryptionKey = (): void => {
this.chatViewModel.chatModel.setEncryptionKey(
this.encryptionKeyInput.value
);
// disable button
this.encryptionKeyInput.callSubscriptions();
};
setColor = (newColor: Color): void => {
this.color.value = newColor;
this.chatViewModel.chatModel.setColor(newColor);
};
remove = (): void => {
this.chatViewModel.close();
this.chatViewModel.chatModel.delete();
this.chatViewModel.chatListViewModel.loadChats();
};
// load
loadViewRelevantData = (): void => {
this.primaryChannel.value =
this.chatViewModel.chatModel.info.primaryChannel;
this.color.value = this.chatViewModel.chatModel.color;
this.cannotSetEncryptionKey = React.createProxyState(
[this.encryptionKeyInput],
() =>
this.encryptionKeyInput.value ==
this.chatViewModel.chatModel.info.encryptionKey
);
};
loadData = (): void => {
this.primaryChannelInput.value =
this.chatViewModel.chatModel.info.primaryChannel;
this.loadSecondaryChannels();
this.encryptionKeyInput.value =
this.chatViewModel.chatModel.info.encryptionKey;
};
loadSecondaryChannels = (): void => {
this.secondaryChannels.clear();
for (const secondaryChannel of this.chatViewModel.chatModel
.secondaryChannels) {
this.secondaryChannels.add(secondaryChannel);
}
};
// init
constructor(chatViewModel: ChatViewModel) {
this.chatViewModel = chatViewModel;
this.loadViewRelevantData();
}
}
@@ -2,14 +2,12 @@ import * as React from "bloatless-react";
import TaskModel, { import TaskModel, {
BoardInfoFileContent, BoardInfoFileContent,
TaskFileContent,
} from "../../Model/Files/taskModel"; } from "../../Model/Files/taskModel";
import StorageModel from "../../Model/Global/storageModel"; import StorageModel from "../../Model/Global/storageModel";
export default class TaskViewModel { export default class TaskPageViewModel {
taskModel: TaskModel; taskModel: TaskModel;
storageModel: StorageModel;
// state // state
newBoardNameInput: React.State<string> = new React.State(""); newBoardNameInput: React.State<string> = new React.State("");
@@ -33,6 +31,5 @@ export default class TaskViewModel {
// init // init
constructor(taskModel: TaskModel, storageModel: StorageModel) { constructor(taskModel: TaskModel, storageModel: StorageModel) {
this.taskModel = taskModel; this.taskModel = taskModel;
this.storageModel = storageModel;
} }
} }