add outbox

This commit is contained in:
marlon-erler
2024-07-10 16:37:42 +02:00
parent f68b253bb9
commit 3dfbeb3252
5 changed files with 170 additions and 54 deletions
+65 -23
View File
@@ -346,6 +346,9 @@
messages(id) { messages(id) {
return id + "messages"; return id + "messages";
}, },
outbox(id) {
return id + "outbox";
},
composingMessage(id) { composingMessage(id) {
return id + "composing-message"; return id + "composing-message";
} }
@@ -360,6 +363,8 @@
secondaryChannels; secondaryChannels;
encryptionKey; encryptionKey;
messages; messages;
outbox;
isOutBoxEmpty;
composingMessage; composingMessage;
primaryChannelInput; primaryChannelInput;
newSecondaryChannelName; newSecondaryChannelName;
@@ -385,6 +390,11 @@
); );
this.encryptionKey = restoreState(storageKeys.encyptionKey(id), ""); this.encryptionKey = restoreState(storageKeys.encyptionKey(id), "");
this.messages = restoreListState(storageKeys.messages(id)); this.messages = restoreListState(storageKeys.messages(id));
this.outbox = restoreListState(storageKeys.outbox(id));
this.isOutBoxEmpty = createProxyState(
[this.outbox],
() => this.outbox.value.size == 0
);
this.composingMessage = restoreState( this.composingMessage = restoreState(
storageKeys.composingMessage(id), storageKeys.composingMessage(id),
"" ""
@@ -392,18 +402,12 @@
this.newSecondaryChannelName = new State(""); this.newSecondaryChannelName = new State("");
this.primaryChannelInput = new State(this.primaryChannel.value); this.primaryChannelInput = new State(this.primaryChannel.value);
this.cannotSendMessage = createProxyState( this.cannotSendMessage = createProxyState(
[ [this.primaryChannel, this.composingMessage, senderName],
this.primaryChannel, () => this.primaryChannel.value == "" || this.composingMessage.value == "" || senderName.value == ""
this.composingMessage,
this.isSubscribed,
isConnected,
senderName
],
() => this.primaryChannel.value == "" || this.composingMessage.value == "" || senderName.value == "" || this.isSubscribed.value == false || isConnected.value == false
); );
this.cannotResendMessage = createProxyState( this.cannotResendMessage = createProxyState(
[this.primaryChannel, this.isSubscribed, isConnected, senderName], [this.primaryChannel, senderName],
() => this.primaryChannel.value == "" || senderName.value == "" || this.isSubscribed.value == false || isConnected.value == false () => this.primaryChannel.value == "" || senderName.value == ""
); );
this.cannotAddSecondaryChannel = createProxyState( this.cannotAddSecondaryChannel = createProxyState(
[this.newSecondaryChannelName], [this.newSecondaryChannelName],
@@ -447,6 +451,9 @@
}; };
handleSubscription = (isSubscribed) => { handleSubscription = (isSubscribed) => {
this.isSubscribed.value = isSubscribed; this.isSubscribed.value = isSubscribed;
if (isSubscribed == true) {
this.sendMessagesInOutbox();
}
}; };
handleMessage = (chatMessage) => { handleMessage = (chatMessage) => {
this.messages.add(chatMessage); this.messages.add(chatMessage);
@@ -455,14 +462,15 @@
// messages // messages
sendNewMessage = async () => { sendNewMessage = async () => {
if (this.cannotSendMessage.value == true) return; if (this.cannotSendMessage.value == true) return;
this.sendMessageText(this.composingMessage.value); const message = await this.createMessage(this.composingMessage.value);
this.sendExistingMessage(message);
this.composingMessage.value = ""; this.composingMessage.value = "";
}; };
resendMessage = (message) => { resendMessage = (message) => {
if (this.cannotResendMessage.value == true) return; if (this.cannotResendMessage.value == true) return;
this.sendMessageText(message.body); this.sendExistingMessage(message);
}; };
sendMessageText = async (messageText) => { createMessage = async (messageText) => {
const secondaryChannelNames = [ const secondaryChannelNames = [
...this.secondaryChannels.value.values() ...this.secondaryChannels.value.values()
]; ];
@@ -472,14 +480,26 @@
]; ];
const encrypted = this.encryptionKey.value == "" ? messageText : await encryptString(messageText, this.encryptionKey.value); const encrypted = this.encryptionKey.value == "" ? messageText : await encryptString(messageText, this.encryptionKey.value);
const joinedChannelName = allChannelNames.join("/"); const joinedChannelName = allChannelNames.join("/");
const messageObject = { return {
channel: joinedChannelName, channel: joinedChannelName,
sender: senderName.value, sender: senderName.value,
body: encrypted, body: encrypted,
isoDate: (/* @__PURE__ */ new Date()).toISOString() isoDate: (/* @__PURE__ */ new Date()).toISOString()
}; };
const messageString = JSON.stringify(messageObject); };
UDN.sendMessage(joinedChannelName, messageString); sendExistingMessage = async (chatMessage) => {
if (isConnected.value == true && this.isSubscribed.value == true) {
const messageString = JSON.stringify(chatMessage);
UDN.sendMessage(chatMessage.channel, messageString);
} else {
this.outbox.add(chatMessage);
}
};
sendMessagesInOutbox = () => {
this.outbox.value.forEach((message) => {
this.sendExistingMessage(message);
this.outbox.remove(message);
});
}; };
clearMessages = () => { clearMessages = () => {
this.messages.clear(); this.messages.clear();
@@ -487,6 +507,9 @@
deleteMessage = (message) => { deleteMessage = (message) => {
this.messages.remove(message); this.messages.remove(message);
}; };
deleteOutboxMessage = (message) => {
this.outbox.remove(message);
};
decryptReceivedMessage = async (message) => { decryptReceivedMessage = async (message) => {
message.body = await decryptString(message.body, this.encryptionKey.value); message.body = await decryptString(message.body, this.encryptionKey.value);
this.messages.callSubscriptions(); this.messages.callSubscriptions();
@@ -790,6 +813,7 @@
showKey: "Show key", showKey: "Show key",
removeChat: "Remove chat", removeChat: "Remove chat",
clearChatMessages: "Clear chat messages", clearChatMessages: "Clear chat messages",
messageInOutbox: "Pending",
noChatSelected: "No chat selected", noChatSelected: "No chat selected",
composerPlaceholder: "Type a message...", composerPlaceholder: "Type a message...",
sendMessage: "Send", sendMessage: "Send",
@@ -838,6 +862,7 @@
removeChat: "Eliminar chat", removeChat: "Eliminar chat",
clearChatMessages: "Eliminar todos mensajes", clearChatMessages: "Eliminar todos mensajes",
noChatSelected: "Selecciona un chat", noChatSelected: "Selecciona un chat",
messageInOutbox: "Pendiente",
composerPlaceholder: "Escribe un mensaje...", composerPlaceholder: "Escribe un mensaje...",
sendMessage: "Enviar", sendMessage: "Enviar",
resendMessage: "Enviar de nuevo", resendMessage: "Enviar de nuevo",
@@ -883,6 +908,7 @@
removeChat: "Chat l\xF6schen", removeChat: "Chat l\xF6schen",
clearChatMessages: "Nachrichtenverlauf leeren", clearChatMessages: "Nachrichtenverlauf leeren",
noChatSelected: "Kein Chat ausgew\xE4hlt", noChatSelected: "Kein Chat ausgew\xE4hlt",
messageInOutbox: "Ausstehend",
composerPlaceholder: "Neue Nachricht...", composerPlaceholder: "Neue Nachricht...",
sendMessage: "Senden", sendMessage: "Senden",
resendMessage: "Erneut senden", resendMessage: "Erneut senden",
@@ -1058,20 +1084,36 @@
/* @__PURE__ */ createElement("span", { class: "icon" }, "key") /* @__PURE__ */ createElement("span", { class: "icon" }, "key")
), /* @__PURE__ */ createElement("button", { "aria-label": translation.deleteMessage, "on:click": remove }, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete")))), /* @__PURE__ */ createElement("div", { class: "flex-column padding-h padding-bottom" }, /* @__PURE__ */ createElement("b", { class: "break-word", "subscribe:innerText": messageBody }), /* @__PURE__ */ createElement("span", { class: "secondary" }, /* @__PURE__ */ createElement("b", null, new Date(message.isoDate).toLocaleString()), /* @__PURE__ */ createElement("br", null), message.channel)))); ), /* @__PURE__ */ createElement("button", { "aria-label": translation.deleteMessage, "on:click": remove }, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete")))), /* @__PURE__ */ createElement("div", { class: "flex-column padding-h padding-bottom" }, /* @__PURE__ */ createElement("b", { class: "break-word", "subscribe:innerText": messageBody }), /* @__PURE__ */ createElement("span", { class: "secondary" }, /* @__PURE__ */ createElement("b", null, new Date(message.isoDate).toLocaleString()), /* @__PURE__ */ createElement("br", null), message.channel))));
}; };
const listElement = /* @__PURE__ */ createElement( const outboxMessageConverter = (message) => {
function remove() {
chat.deleteOutboxMessage(message);
}
return /* @__PURE__ */ createElement("div", { class: "tile width-100 flex-no padding-0" }, /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement("div", { class: "flex-row justify-apart align-center" }, /* @__PURE__ */ createElement("b", { class: "error padding-h" }, translation.messageInOutbox), /* @__PURE__ */ createElement("span", { class: "flex-row secondary" }, /* @__PURE__ */ createElement("button", { "aria-label": translation.deleteMessage, "on:click": remove }, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete")))), /* @__PURE__ */ createElement("div", { class: "flex-column padding-h padding-bottom" }, /* @__PURE__ */ createElement("b", { class: "break-word" }, message.body), /* @__PURE__ */ createElement("span", { class: "secondary" }, /* @__PURE__ */ createElement("b", null, new Date(message.isoDate).toLocaleString()), /* @__PURE__ */ createElement("br", null), message.channel))));
};
const messageList = /* @__PURE__ */ createElement(
"div", "div",
{ {
class: "flex-column gap", class: "flex-column gap",
"children:append": [chat.messages, messageConverter] "children:append": [chat.messages, messageConverter]
} }
); );
chat.messages.handleAddition(() => { const outboxList = /* @__PURE__ */ createElement(
const scrollFromBottom = listElement.scrollHeight - (listElement.scrollTop + listElement.offsetHeight); "div",
{
class: "flex-column gap",
"children:append": [chat.outbox, outboxMessageConverter]
}
);
const listWrapper = /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, messageList, outboxList);
function scrollToBottom() {
const scrollFromBottom = listWrapper.scrollHeight - (listWrapper.scrollTop + listWrapper.offsetHeight);
if (scrollFromBottom > 400) return; if (scrollFromBottom > 400) return;
listElement.scrollTop = listElement.scrollHeight; listWrapper.scrollTop = listWrapper.scrollHeight;
}); }
setTimeout(() => listElement.scrollTop = listElement.scrollHeight, 50); chat.messages.handleAddition(scrollToBottom);
return listElement; chat.outbox.handleAddition(scrollToBottom);
setTimeout(() => listWrapper.scrollTop = listWrapper.scrollHeight, 50);
return listWrapper;
} }
// src/Tabs/messageTab.tsx // src/Tabs/messageTab.tsx
+39 -23
View File
@@ -34,6 +34,8 @@ export class Chat {
encryptionKey: React.State<string>; encryptionKey: React.State<string>;
messages: React.ListState<ChatMessage>; messages: React.ListState<ChatMessage>;
outbox: React.ListState<ChatMessage>;
isOutBoxEmpty: React.State<boolean>;
composingMessage: React.State<string>; composingMessage: React.State<string>;
primaryChannelInput: React.State<string>; primaryChannelInput: React.State<string>;
@@ -66,6 +68,11 @@ export class Chat {
); );
this.encryptionKey = React.restoreState(storageKeys.encyptionKey(id), ""); this.encryptionKey = React.restoreState(storageKeys.encyptionKey(id), "");
this.messages = React.restoreListState(storageKeys.messages(id)); this.messages = React.restoreListState(storageKeys.messages(id));
this.outbox = React.restoreListState(storageKeys.outbox(id));
this.isOutBoxEmpty = React.createProxyState(
[this.outbox],
() => this.outbox.value.size == 0
);
// inputs // inputs
this.composingMessage = React.restoreState( this.composingMessage = React.restoreState(
@@ -77,27 +84,15 @@ export class Chat {
// guards // guards
this.cannotSendMessage = React.createProxyState( this.cannotSendMessage = React.createProxyState(
[ [this.primaryChannel, this.composingMessage, senderName],
this.primaryChannel,
this.composingMessage,
this.isSubscribed,
isConnected,
senderName,
],
() => () =>
this.primaryChannel.value == "" || this.primaryChannel.value == "" ||
this.composingMessage.value == "" || this.composingMessage.value == "" ||
senderName.value == "" || senderName.value == ""
this.isSubscribed.value == false ||
isConnected.value == false
); );
this.cannotResendMessage = React.createProxyState( this.cannotResendMessage = React.createProxyState(
[this.primaryChannel, this.isSubscribed, isConnected, senderName], [this.primaryChannel, senderName],
() => () => this.primaryChannel.value == "" || senderName.value == ""
this.primaryChannel.value == "" ||
senderName.value == "" ||
this.isSubscribed.value == false ||
isConnected.value == false
); );
this.cannotAddSecondaryChannel = React.createProxyState( this.cannotAddSecondaryChannel = React.createProxyState(
[this.newSecondaryChannelName], [this.newSecondaryChannelName],
@@ -149,6 +144,10 @@ export class Chat {
handleSubscription = (isSubscribed: boolean): void => { handleSubscription = (isSubscribed: boolean): void => {
this.isSubscribed.value = isSubscribed; this.isSubscribed.value = isSubscribed;
if (isSubscribed == true) {
this.sendMessagesInOutbox();
}
}; };
handleMessage = (chatMessage: ChatMessage): void => { handleMessage = (chatMessage: ChatMessage): void => {
@@ -160,16 +159,17 @@ export class Chat {
sendNewMessage = async (): Promise<void> => { sendNewMessage = async (): Promise<void> => {
if (this.cannotSendMessage.value == true) return; if (this.cannotSendMessage.value == true) return;
this.sendMessageText(this.composingMessage.value); const message = await this.createMessage(this.composingMessage.value);
this.sendExistingMessage(message);
this.composingMessage.value = ""; this.composingMessage.value = "";
}; };
resendMessage = (message: ChatMessage) => { resendMessage = (message: ChatMessage) => {
if (this.cannotResendMessage.value == true) return; if (this.cannotResendMessage.value == true) return;
this.sendMessageText(message.body); this.sendExistingMessage(message);
}; };
sendMessageText = async (messageText: string): Promise<void> => { createMessage = async (messageText: string): Promise<ChatMessage> => {
// get channels // get channels
const secondaryChannelNames: string[] = [ const secondaryChannelNames: string[] = [
...this.secondaryChannels.value.values(), ...this.secondaryChannels.value.values(),
@@ -187,16 +187,28 @@ export class Chat {
// create object // create object
const joinedChannelName = allChannelNames.join("/"); const joinedChannelName = allChannelNames.join("/");
const messageObject: ChatMessage = { return {
channel: joinedChannelName, channel: joinedChannelName,
sender: senderName.value, sender: senderName.value,
body: encrypted, body: encrypted,
isoDate: new Date().toISOString(), isoDate: new Date().toISOString(),
}; };
const messageString = JSON.stringify(messageObject); };
// send sendExistingMessage = async (chatMessage: ChatMessage): Promise<void> => {
UDN.sendMessage(joinedChannelName, messageString); if (isConnected.value == true && this.isSubscribed.value == true) {
const messageString = JSON.stringify(chatMessage);
UDN.sendMessage(chatMessage.channel, messageString);
} else {
this.outbox.add(chatMessage);
}
};
sendMessagesInOutbox = () => {
this.outbox.value.forEach((message) => {
this.sendExistingMessage(message)
this.outbox.remove(message);
});
}; };
clearMessages = (): void => { clearMessages = (): void => {
@@ -207,6 +219,10 @@ export class Chat {
this.messages.remove(message); this.messages.remove(message);
}; };
deleteOutboxMessage = (message: ChatMessage): void => {
this.outbox.remove(message);
};
decryptReceivedMessage = async (message: ChatMessage): Promise<void> => { decryptReceivedMessage = async (message: ChatMessage): Promise<void> => {
message.body = await decryptString(message.body, this.encryptionKey.value); message.body = await decryptString(message.body, this.encryptionKey.value);
this.messages.callSubscriptions(); this.messages.callSubscriptions();
+56 -8
View File
@@ -72,20 +72,68 @@ export function ThreadView(chat: Chat) {
); );
}; };
const listElement = ( const outboxMessageConverter: React.ListItemConverter<ChatMessage> = (
message
) => {
function remove() {
chat.deleteOutboxMessage(message);
}
return (
<div class="tile width-100 flex-no padding-0">
<div class="flex-column">
<div class="flex-row justify-apart align-center">
<b class="error padding-h">{translation.messageInOutbox}</b>
<span class="flex-row secondary">
<button aria-label={translation.deleteMessage} on:click={remove}>
<span class="icon">delete</span>
</button>
</span>
</div>
<div class="flex-column padding-h padding-bottom">
<b class="break-word">{message.body}</b>
<span class="secondary">
<b>{new Date(message.isoDate).toLocaleString()}</b>
<br></br>
{message.channel}
</span>
</div>
</div>
</div>
);
};
const messageList = (
<div <div
class="flex-column gap" class="flex-column gap"
children:append={[chat.messages, messageConverter]} children:append={[chat.messages, messageConverter]}
></div> ></div>
); );
chat.messages.handleAddition(() => { const outboxList = (
<div
class="flex-column gap"
children:append={[chat.outbox, outboxMessageConverter]}
></div>
);
const listWrapper = (
<div class="flex-column gap">
{messageList}
{outboxList}
</div>
);
function scrollToBottom() {
const scrollFromBottom = const scrollFromBottom =
listElement.scrollHeight - listWrapper.scrollHeight -
(listElement.scrollTop + listElement.offsetHeight); (listWrapper.scrollTop + listWrapper.offsetHeight);
if (scrollFromBottom > 400) return; if (scrollFromBottom > 400) return;
listElement.scrollTop = listElement.scrollHeight; listWrapper.scrollTop = listWrapper.scrollHeight;
}); }
setTimeout(() => (listElement.scrollTop = listElement.scrollHeight), 50);
return listElement; chat.messages.handleAddition(scrollToBottom);
chat.outbox.handleAddition(scrollToBottom);
setTimeout(() => (listWrapper.scrollTop = listWrapper.scrollHeight), 50);
return listWrapper;
} }
+6
View File
@@ -50,6 +50,8 @@ const englishTranslations = {
removeChat: "Remove chat", removeChat: "Remove chat",
clearChatMessages: "Clear chat messages", clearChatMessages: "Clear chat messages",
messageInOutbox: "Pending",
noChatSelected: "No chat selected", noChatSelected: "No chat selected",
composerPlaceholder: "Type a message...", composerPlaceholder: "Type a message...",
@@ -117,6 +119,8 @@ const allTranslations: { [key: string]: typeof englishTranslations } = {
noChatSelected: "Selecciona un chat", noChatSelected: "Selecciona un chat",
messageInOutbox: "Pendiente",
composerPlaceholder: "Escribe un mensaje...", composerPlaceholder: "Escribe un mensaje...",
sendMessage: "Enviar", sendMessage: "Enviar",
@@ -179,6 +183,8 @@ const allTranslations: { [key: string]: typeof englishTranslations } = {
noChatSelected: "Kein Chat ausgewählt", noChatSelected: "Kein Chat ausgewählt",
messageInOutbox: "Ausstehend",
composerPlaceholder: "Neue Nachricht...", composerPlaceholder: "Neue Nachricht...",
sendMessage: "Senden", sendMessage: "Senden",
+4
View File
@@ -19,6 +19,10 @@ export const storageKeys = {
return id + "messages"; return id + "messages";
}, },
outbox(id: string): string {
return id + "outbox";
},
composingMessage(id: string): string { composingMessage(id: string): string {
return id + "composing-message"; return id + "composing-message";
}, },