From 659bb16cb809fc4c7dbe7fbb7bdb7f1464cbf40f Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Wed, 10 Jul 2024 19:07:58 +0200 Subject: [PATCH 01/28] add basic layout --- dist/index.css | 63 ++++++++++++++++++++++++++++ dist/index.js | 31 +++++++++++++- dist/styles.css | 9 +++- src/Model/model.ts | 6 +++ src/Tabs/messageTab.tsx | 19 ++++++++- src/Views/chatToolView.tsx | 12 ++++++ src/Views/threadView.tsx | 2 +- src/styles.css | 86 ++++++++++++++++++++++++++++++++++++++ src/translations.ts | 1 + 9 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 src/Views/chatToolView.tsx diff --git a/dist/index.css b/dist/index.css index bd34d2a3..02de80dc 100644 --- a/dist/index.css +++ b/dist/index.css @@ -2,3 +2,66 @@ [unread] { border-left: 10px solid var(--text-warn); } +:root { + --thread-view-height-with-tools: 8rem; + --thread-view-width-with-tools: 20rem; + --footer-top-line: calc(var(--control-height) + var(--safe-area-bottom)); +} +#message-tab { + flex-direction: column; +} +.message-tab-content-wrapper { + display: flex; + flex-direction: column; +} +.chat-tool-view { + display: none; +} +[showingchattools] :is(.thread-view, .chat-tool-view) { + overflow: scroll; + position: absolute; + padding: 0px; +} +[showingchattools] header { + border-bottom: 1px solid var(--border); +} +[showingchattools] .chat-tool-view { + display: flex; + top: var(--header-height); + bottom: calc(var(--thread-view-height-with-tools) + var(--footer-top-line)); + border-bottom: 1px solid var(--border); +} +[showingchattools] .thread-view { + display: flex; + height: var(--thread-view-height-with-tools); + top: unset; + bottom: var(--footer-top-line); + flex: 1; +} +@media (max-height: 700px) and (min-width: 640px) { + main.split:has([showingchattools]) { + display: flex; + } + main.split:has([showingchattools]) #settings-tab { + display: none; + } +} +@media (min-width: 1400px) or ((max-height: 700px) and (min-width: 640px)) { + [showingchattools] :is(.thread-view, .chat-tool-view) { + top: var(--header-height); + bottom: var(--footer-top-line); + } + [showingchattools] .chat-tool-view { + left: 0px; + right: var(--thread-view-width-with-tools); + border-bottom: none; + border-right: 1px solid var(--border); + } + [showingchattools] .thread-view { + display: flex; + left: unset; + right: 0px; + height: unset; + width: var(--thread-view-width-with-tools); + } +} diff --git a/dist/index.js b/dist/index.js index 0e7671f7..02856f05 100644 --- a/dist/index.js +++ b/dist/index.js @@ -755,6 +755,7 @@ var chats = new ListState(); var chatIds = restoreListState("chat-ids"); var selectedChat = new State(void 0); + var isShowingChatTools = new State(false); var newChatName = new State(""); var cannotCreateChat = createProxyState( [newChatName], @@ -766,6 +767,7 @@ newChatName.value = ""; } function closeChatView() { + isShowingChatTools.value = false; selectedChat.value = void 0; document.getElementById("settings-tab")?.scrollIntoView(); } @@ -774,6 +776,9 @@ chat.hasUnreadMessages.value = false; document.getElementById("message-tab")?.scrollIntoView(); } + function toggleChatTools() { + isShowingChatTools.value = !isShowingChatTools.value; + } chatIds.value.forEach((id) => chats.add(new Chat(id))); if (serverAddressInput.value != "" && didRequestConnection.value == true) { connect(); @@ -807,6 +812,7 @@ addChat: "Add", // messages showChatOptions: "show chat options", + toggleChatTools: "toggle chat tools", configureChatTitle: "Configure Chat", secondaryChannel: "Secondary channel", secondaryChannelPlaceholder: "Add secondary channel", @@ -1023,6 +1029,11 @@ ), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteChat }, translation.removeChat, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete")))), /* @__PURE__ */ createElement("button", { "on:click": closeModal }, translation.close, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } + // src/Views/chatToolView.tsx + function ChatToolView(chat) { + return /* @__PURE__ */ createElement("div", { class: "chat-tool-view" }, /* @__PURE__ */ createElement("span", null, "Tools")); + } + // src/Views/messageComposer.tsx function MessageComposer(chat) { return /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, " ", /* @__PURE__ */ createElement( @@ -1108,7 +1119,7 @@ "children:append": [chat.outbox, outboxMessageConverter] } ); - const listWrapper = /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, messageList, outboxList); + const listWrapper = /* @__PURE__ */ createElement("div", { class: "thread-view flex-column gap" }, messageList, outboxList); function scrollToBottom() { const scrollFromBottom = listWrapper.scrollHeight - (listWrapper.scrollTop + listWrapper.offsetHeight); if (scrollFromBottom > 400) return; @@ -1132,6 +1143,14 @@ } return [ /* @__PURE__ */ createElement("header", { class: "padding-0" }, /* @__PURE__ */ createElement("span", { class: "flex-row align-center" }, /* @__PURE__ */ createElement("button", { "aria-label": translation.back, "on:click": closeChatView }, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back")), /* @__PURE__ */ createElement("span", { "subscribe:innerText": chat.primaryChannel })), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( + "button", + { + "aria-label": translation.toggleChatTools, + "on:click": toggleChatTools, + "toggle:selected": isShowingChatTools + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "architecture") + ), /* @__PURE__ */ createElement( "button", { "aria-label": translation.showChatOptions, @@ -1139,12 +1158,20 @@ }, /* @__PURE__ */ createElement("span", { class: "icon" }, "tune") ))), + ChatToolView(chat), ThreadView(chat), /* @__PURE__ */ createElement("footer", null, MessageComposer(chat)), ChatOptionModal(chat, isShowingOptions) ]; }); - return /* @__PURE__ */ createElement("article", { id: "message-tab", "children:set": messageTabContent }); + return /* @__PURE__ */ createElement( + "article", + { + "toggle:showingchattools": isShowingChatTools, + id: "message-tab", + "children:set": messageTabContent + } + ); } // src/Views/chatListSection.tsx diff --git a/dist/styles.css b/dist/styles.css index 30028c98..ac93a3ef 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -140,10 +140,12 @@ select, @media (hover: hover) { button:hover, select:hover, - button.tile.ghost:hover, - [selected]:active { + button.tile.ghost:hover { background-color: var(--surface-hover); } + [selected]:hover { + background-color: var(--surface); + } } button:active, select:active, @@ -151,6 +153,9 @@ button.tile.ghost:active, [selected] { background-color: var(--surface-active); } +[selected]:active { + background-color: var(--surface-hover); +} header button { background-color: transparent; } diff --git a/src/Model/model.ts b/src/Model/model.ts index c4fdf655..d70335b5 100644 --- a/src/Model/model.ts +++ b/src/Model/model.ts @@ -139,6 +139,7 @@ export const chats = new React.ListState(); export const chatIds = React.restoreListState("chat-ids"); export const selectedChat = new React.State(undefined); +export const isShowingChatTools = new React.State(false); export const newChatName = new React.State(""); export const cannotCreateChat = React.createProxyState( @@ -154,6 +155,7 @@ export function createChat() { } export function closeChatView() { + isShowingChatTools.value = false; selectedChat.value = undefined; document.getElementById("settings-tab")?.scrollIntoView(); } @@ -164,6 +166,10 @@ export function selectChat(chat: Chat) { document.getElementById("message-tab")?.scrollIntoView(); } +export function toggleChatTools() { + isShowingChatTools.value = !isShowingChatTools.value; +} + chatIds.value.forEach((id) => chats.add(new Chat(id))); // INIT diff --git a/src/Tabs/messageTab.tsx b/src/Tabs/messageTab.tsx index 3c531d08..217088b3 100644 --- a/src/Tabs/messageTab.tsx +++ b/src/Tabs/messageTab.tsx @@ -1,8 +1,9 @@ import * as React from "bloatless-react"; -import { closeChatView, selectedChat } from "../Model/model"; +import { closeChatView, isShowingChatTools, selectedChat, toggleChatTools } from "../Model/model"; import { ChatOptionModal } from "../Views/chatOptionModal"; +import { ChatToolView } from "../Views/chatToolView"; import { MessageComposer } from "../Views/messageComposer"; import { ThreadView } from "../Views/threadView"; import { translation } from "../translations"; @@ -33,6 +34,13 @@ export function MessageTab() { + +
+ ); } diff --git a/src/Views/messageComposer.tsx b/src/Views/messageComposer.tsx index 66a87fed..8d2e281c 100644 --- a/src/Views/messageComposer.tsx +++ b/src/Views/messageComposer.tsx @@ -12,11 +12,11 @@ export function MessageComposer(chat: Chat) { style="max-width: unset" placeholder={translation.composerPlaceholder} bind:value={chat.composingMessage} - on:enter={chat.sendNewMessage} + on:enter={chat.sendMessageFromComposer} >
- +
); } diff --git a/src/Views/connectionSection.tsx b/src/Views/connectionSection.tsx index 92460023..6bb22e64 100644 --- a/src/Views/connectionSection.tsx +++ b/src/Views/connectionSection.tsx @@ -17,7 +17,7 @@ import { import { translation } from "../translations"; -const addressConverter: React.ListItemConverter = (address) => { +const addressConverter: React.StateItemConverter = (address) => { return ; }; diff --git a/src/Views/threadView.tsx b/src/Views/threadView.tsx index 77709167..7fe989a3 100644 --- a/src/Views/threadView.tsx +++ b/src/Views/threadView.tsx @@ -5,7 +5,7 @@ import { Chat, ChatMessage } from "../Model/chatModel"; import { translation } from "../translations"; export function ThreadView(chat: Chat) { - const messageConverter: React.ListItemConverter = (message) => { + const messageConverter: React.StateItemConverter = (message) => { function resendMessage() { chat.resendMessage(message); } @@ -72,7 +72,7 @@ export function ThreadView(chat: Chat) { ); }; - const outboxMessageConverter: React.ListItemConverter = ( + const outboxMessageConverter: React.StateItemConverter = ( message ) => { function remove() { From 885093a7b9d9d3a443d66b2d23326ced3a00c976 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 01:03:07 +0200 Subject: [PATCH 05/28] scroll chats to bottom when opening tools --- dist/index.js | 15 +++++++++------ src/Views/threadView.tsx | 14 ++++++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/dist/index.js b/dist/index.js index b9b1b499..5535c79f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1111,7 +1111,6 @@ }); } const itemConverter = (item) => { - console.log(item); return /* @__PURE__ */ createElement("span", null, item.title); }; return /* @__PURE__ */ createElement("div", { class: "chat-tool-view" }, /* @__PURE__ */ createElement("button", { "on:click": createItem }, "+"), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { "children:append": [chat.items, itemConverter] })); @@ -1204,13 +1203,17 @@ ); const listWrapper = /* @__PURE__ */ createElement("div", { class: "thread-view flex-column gap" }, messageList, outboxList); function scrollToBottom() { - const scrollFromBottom = listWrapper.scrollHeight - (listWrapper.scrollTop + listWrapper.offsetHeight); - if (scrollFromBottom > 400) return; listWrapper.scrollTop = listWrapper.scrollHeight; } - chat.messages.handleAddition(scrollToBottom); - chat.outbox.handleAddition(scrollToBottom); - setTimeout(() => listWrapper.scrollTop = listWrapper.scrollHeight, 50); + function scrollToBottomIfAppropriate() { + const scrollFromBottom = listWrapper.scrollHeight - (listWrapper.scrollTop + listWrapper.offsetHeight); + if (scrollFromBottom > 400) return; + scrollToBottom(); + } + chat.messages.handleAddition(scrollToBottomIfAppropriate); + chat.outbox.handleAddition(scrollToBottomIfAppropriate); + isShowingChatTools.subscribe(() => scrollToBottom()); + setTimeout(() => scrollToBottom(), 50); return listWrapper; } diff --git a/src/Views/threadView.tsx b/src/Views/threadView.tsx index 7fe989a3..72bb5ca1 100644 --- a/src/Views/threadView.tsx +++ b/src/Views/threadView.tsx @@ -2,6 +2,7 @@ import * as React from "bloatless-react"; import { Chat, ChatMessage } from "../Model/chatModel"; +import { isShowingChatTools } from "../Model/model"; import { translation } from "../translations"; export function ThreadView(chat: Chat) { @@ -123,17 +124,22 @@ export function ThreadView(chat: Chat) { ); function scrollToBottom() { + listWrapper.scrollTop = listWrapper.scrollHeight; + } + + function scrollToBottomIfAppropriate() { const scrollFromBottom = listWrapper.scrollHeight - (listWrapper.scrollTop + listWrapper.offsetHeight); if (scrollFromBottom > 400) return; - listWrapper.scrollTop = listWrapper.scrollHeight; + scrollToBottom(); } - chat.messages.handleAddition(scrollToBottom); - chat.outbox.handleAddition(scrollToBottom); - setTimeout(() => (listWrapper.scrollTop = listWrapper.scrollHeight), 50); + chat.messages.handleAddition(scrollToBottomIfAppropriate); + chat.outbox.handleAddition(scrollToBottomIfAppropriate); + isShowingChatTools.subscribe(() => scrollToBottom()); + setTimeout(() => scrollToBottom(), 50); return listWrapper; } From 7047a5e9c1c5d2aa6330ebab5f2f5e0f689ad3c5 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 01:07:02 +0200 Subject: [PATCH 06/28] update layout for tools --- dist/index.css | 4 ++-- src/styles.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/index.css b/dist/index.css index d943c9af..28e13c14 100644 --- a/dist/index.css +++ b/dist/index.css @@ -38,7 +38,7 @@ bottom: var(--footer-top-line); flex: 1; } -@media (max-height: 700px) and (min-width: 640px) and (max-width: 1400px) { +@media (min-width: 640px) and (max-width: 1400px) { main.split:has([showingchattools]) { display: flex; } @@ -46,7 +46,7 @@ display: none; } } -@media (min-width: 1400px) or ((max-height: 700px) and (min-width: 640px)) { +@media (min-width: 640px) { [showingchattools] :is(.thread-view, .chat-tool-view) { top: var(--header-height); bottom: var(--footer-top-line); diff --git a/src/styles.css b/src/styles.css index 6cd1768c..797d1fff 100644 --- a/src/styles.css +++ b/src/styles.css @@ -53,7 +53,7 @@ flex: 1; } -@media (max-height: 700px) and (min-width: 640px) and (max-width: 1400px) { +@media (min-width: 640px) and (max-width: 1400px) { main.split:has([showingchattools]) { display: flex; } @@ -63,7 +63,7 @@ } } -@media (min-width: 1400px) or ((max-height: 700px) and (min-width: 640px)) { +@media (min-width: 640px) { [showingchattools] :is(.thread-view, .chat-tool-view) { top: var(--header-height); bottom: var(--footer-top-line); From 8d53041aafa32e2ebca4c6b27bd018d75d59af12 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 01:18:34 +0200 Subject: [PATCH 07/28] implement outbox for items --- bun.lockb | Bin 11435 -> 11435 bytes dist/index.js | 11 +++++++++++ package.json | 2 +- src/Model/chatModel.ts | 7 +++++++ src/utility.ts | 4 ++++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index 6c715ad7a80084ba987d14ae2cc9a5562057f993..ba1e0d010ae927894ccc6ef5f54676f264f1abe5 100755 GIT binary patch delta 137 zcmV;40CxYYS*ux)E+C_ExWNhU%NzQgm_@n6#Zu-@uBHUwU_g}_<+h^|i(j!$+6Vyz zlTiXEvrq|b3qazTZ<0ZR2u{KnlOHwSt*0zj^eTZ#A2B$>|8mLXp1_oJrp~>TgP;@N rALQYCb3ag+qrfkNtlMKSkzI#t@*A`L6bcFfGqVUE$P58AlhG^Mqr5;1 delta 137 zcmV;40CxYYS*ux)E+9^-^g;^Jkh`;b9XMv&>zDmu=X2AJ@T1nA1ha<79!IfG+6Vyy zlTiXEvrq|b3qWl%nr?Gz3GnBNiLCm~z;8WCjFy!nR!R!_kQZfi{Ar}CQfLHe3SBdw rO`ZuLCLb+{2Ci6Qyqu2%`5bTps93Z86bcFfGP4LD$P589lhG^MM0Gp1 diff --git a/dist/index.js b/dist/index.js index 5535c79f..3d509265 100644 --- a/dist/index.js +++ b/dist/index.js @@ -132,6 +132,7 @@ } // list set(key, item) { + this.remove(key); this.value.set(key, item); this.additionHandlers.forEach((handler) => handler(item)); this.callSubscriptions(); @@ -404,6 +405,9 @@ outbox(id) { return id + "outbox"; }, + itemOutbox(id) { + return id + "item-outbox"; + }, composingMessage(id) { return id + "composing-message"; } @@ -420,6 +424,7 @@ messages; items; outbox; + itemOutbox; isOutBoxEmpty; composingMessage; primaryChannelInput; @@ -448,6 +453,7 @@ this.messages = restoreListState(storageKeys.messages(id)); this.items = restoreMapState(storageKeys.items(id)); this.outbox = restoreListState(storageKeys.outbox(id)); + this.itemOutbox = restoreListState(storageKeys.itemOutbox(id)); this.isOutBoxEmpty = createProxyState( [this.outbox], () => this.outbox.value.size == 0 @@ -563,6 +569,7 @@ const messageString = JSON.stringify(chatMessage); UDN.sendMessage(chatMessage.channel, messageString); } else { + if (chatMessage.itemData) return this.itemOutbox.add(chatMessage); this.outbox.add(chatMessage); } }; @@ -571,6 +578,10 @@ this.sendMessage(message); this.outbox.remove(message); }); + this.itemOutbox.value.forEach((message) => { + this.sendMessage(message); + this.itemOutbox.remove(message); + }); }; clearMessages = () => { this.messages.clear(); diff --git a/package.json b/package.json index 27152464..aba530a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "bloatless-react": "^1.3.3", + "bloatless-react": "^1.3.4", "esbuild": "latest", "udn-frontend": "^1.0.7-beta1" }, diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index 551947f5..b553c92c 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -48,6 +48,7 @@ export class Chat { messages: React.ListState; items: React.MapState; outbox: React.ListState; + itemOutbox: React.ListState; isOutBoxEmpty: React.State; composingMessage: React.State; @@ -83,6 +84,7 @@ export class Chat { this.messages = React.restoreListState(storageKeys.messages(id)); this.items = React.restoreMapState(storageKeys.items(id)); this.outbox = React.restoreListState(storageKeys.outbox(id)); + this.itemOutbox = React.restoreListState(storageKeys.itemOutbox(id)); this.isOutBoxEmpty = React.createProxyState( [this.outbox], () => this.outbox.value.size == 0 @@ -236,6 +238,7 @@ export class Chat { const messageString = JSON.stringify(chatMessage); UDN.sendMessage(chatMessage.channel, messageString); } else { + if (chatMessage.itemData) return this.itemOutbox.add(chatMessage); this.outbox.add(chatMessage); } }; @@ -245,6 +248,10 @@ export class Chat { this.sendMessage(message); this.outbox.remove(message); }); + this.itemOutbox.value.forEach((message) => { + this.sendMessage(message); + this.itemOutbox.remove(message); + }); }; clearMessages = (): void => { diff --git a/src/utility.ts b/src/utility.ts index 8e71f43e..249ed8cb 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -27,6 +27,10 @@ export const storageKeys = { return id + "outbox"; }, + itemOutbox(id: string): string { + return id + "item-outbox"; + }, + composingMessage(id: string): string { return id + "composing-message"; }, From 24e5b5a2e19ee32e75203f2b493c1d34ed9d1733 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 01:24:06 +0200 Subject: [PATCH 08/28] reorganize --- src/Tabs/messageTab.tsx | 8 ++++---- src/Tabs/overviewTab.tsx | 6 +++--- src/Views/{ => Chat}/chatOptionModal.tsx | 6 +++--- src/Views/{ => Chat}/chatToolView.tsx | 4 ++-- src/Views/{ => Chat}/messageComposer.tsx | 4 ++-- src/Views/{ => Chat}/threadView.tsx | 6 +++--- src/Views/{ => Overview}/chatListSection.tsx | 6 +++--- src/Views/{ => Overview}/connectionSection.tsx | 4 ++-- src/Views/{ => Overview}/personalSection.tsx | 4 ++-- 9 files changed, 24 insertions(+), 24 deletions(-) rename src/Views/{ => Chat}/chatOptionModal.tsx (96%) rename src/Views/{ => Chat}/chatToolView.tsx (83%) rename src/Views/{ => Chat}/messageComposer.tsx (86%) rename src/Views/{ => Chat}/threadView.tsx (96%) rename src/Views/{ => Overview}/chatListSection.tsx (92%) rename src/Views/{ => Overview}/connectionSection.tsx (97%) rename src/Views/{ => Overview}/personalSection.tsx (81%) diff --git a/src/Tabs/messageTab.tsx b/src/Tabs/messageTab.tsx index 217088b3..3f971d40 100644 --- a/src/Tabs/messageTab.tsx +++ b/src/Tabs/messageTab.tsx @@ -2,10 +2,10 @@ import * as React from "bloatless-react"; import { closeChatView, isShowingChatTools, selectedChat, toggleChatTools } from "../Model/model"; -import { ChatOptionModal } from "../Views/chatOptionModal"; -import { ChatToolView } from "../Views/chatToolView"; -import { MessageComposer } from "../Views/messageComposer"; -import { ThreadView } from "../Views/threadView"; +import { ChatOptionModal } from "../Views/Chat/chatOptionModal"; +import { ChatToolView } from "../Views/Chat/chatToolView"; +import { MessageComposer } from "../Views/Chat/messageComposer"; +import { ThreadView } from "../Views/Chat/threadView"; import { translation } from "../translations"; export function MessageTab() { diff --git a/src/Tabs/overviewTab.tsx b/src/Tabs/overviewTab.tsx index e94b48ee..dfe8a2dd 100644 --- a/src/Tabs/overviewTab.tsx +++ b/src/Tabs/overviewTab.tsx @@ -2,9 +2,9 @@ import * as React from "bloatless-react"; import { isConnected, isEncryptionAvailable } from "../Model/model"; -import { ChatListSection } from "../Views/chatListSection"; -import { ConnectionSection } from "../Views/connectionSection"; -import { PersonalSection } from "../Views/personalSection"; +import { ChatListSection } from "../Views/Overview/chatListSection"; +import { ConnectionSection } from "../Views/Overview/connectionSection"; +import { PersonalSection } from "../Views/Overview/personalSection"; import { translation } from "../translations"; export function OverviewTab() { diff --git a/src/Views/chatOptionModal.tsx b/src/Views/Chat/chatOptionModal.tsx similarity index 96% rename from src/Views/chatOptionModal.tsx rename to src/Views/Chat/chatOptionModal.tsx index 93630f9b..cecaa584 100644 --- a/src/Views/chatOptionModal.tsx +++ b/src/Views/Chat/chatOptionModal.tsx @@ -1,8 +1,8 @@ import * as React from "bloatless-react"; -import { Chat } from "../Model/chatModel"; -import { closeChatView } from "../Model/model"; -import { translation } from "../translations"; +import { Chat } from "../../Model/chatModel"; +import { closeChatView } from "../../Model/model"; +import { translation } from "../../translations"; export function ChatOptionModal(chat: Chat, isPresented: React.State) { function closeModal() { diff --git a/src/Views/chatToolView.tsx b/src/Views/Chat/chatToolView.tsx similarity index 83% rename from src/Views/chatToolView.tsx rename to src/Views/Chat/chatToolView.tsx index 5ebf2585..c480a08d 100644 --- a/src/Views/chatToolView.tsx +++ b/src/Views/Chat/chatToolView.tsx @@ -1,8 +1,8 @@ import * as React from "bloatless-react"; -import { Chat, Item } from "../Model/chatModel"; +import { Chat, Item } from "../../Model/chatModel"; -import { translation } from "../translations"; +import { translation } from "../../translations"; export function ChatToolView(chat: Chat) { function createItem() { diff --git a/src/Views/messageComposer.tsx b/src/Views/Chat/messageComposer.tsx similarity index 86% rename from src/Views/messageComposer.tsx rename to src/Views/Chat/messageComposer.tsx index 8d2e281c..61b839ca 100644 --- a/src/Views/messageComposer.tsx +++ b/src/Views/Chat/messageComposer.tsx @@ -1,7 +1,7 @@ import * as React from "bloatless-react"; -import { Chat } from "../Model/chatModel"; -import { translation } from "../translations"; +import { Chat } from "../../Model/chatModel"; +import { translation } from "../../translations"; export function MessageComposer(chat: Chat) { return ( diff --git a/src/Views/threadView.tsx b/src/Views/Chat/threadView.tsx similarity index 96% rename from src/Views/threadView.tsx rename to src/Views/Chat/threadView.tsx index 72bb5ca1..0c7ce983 100644 --- a/src/Views/threadView.tsx +++ b/src/Views/Chat/threadView.tsx @@ -1,9 +1,9 @@ import * as React from "bloatless-react"; -import { Chat, ChatMessage } from "../Model/chatModel"; +import { Chat, ChatMessage } from "../../Model/chatModel"; -import { isShowingChatTools } from "../Model/model"; -import { translation } from "../translations"; +import { isShowingChatTools } from "../../Model/model"; +import { translation } from "../../translations"; export function ThreadView(chat: Chat) { const messageConverter: React.StateItemConverter = (message) => { diff --git a/src/Views/chatListSection.tsx b/src/Views/Overview/chatListSection.tsx similarity index 92% rename from src/Views/chatListSection.tsx rename to src/Views/Overview/chatListSection.tsx index 123dab3a..3e57b4bc 100644 --- a/src/Views/chatListSection.tsx +++ b/src/Views/Overview/chatListSection.tsx @@ -7,10 +7,10 @@ import { newChatName, selectChat, selectedChat, -} from "../Model/model"; +} from "../../Model/model"; -import { Chat } from "../Model/chatModel"; -import { translation } from "../translations"; +import { Chat } from "../../Model/chatModel"; +import { translation } from "../../translations"; const chatConverter: React.StateItemConverter = (chat) => { function select() { diff --git a/src/Views/connectionSection.tsx b/src/Views/Overview/connectionSection.tsx similarity index 97% rename from src/Views/connectionSection.tsx rename to src/Views/Overview/connectionSection.tsx index 6bb22e64..5df139ac 100644 --- a/src/Views/connectionSection.tsx +++ b/src/Views/Overview/connectionSection.tsx @@ -13,9 +13,9 @@ import { requestMailbox, resetAddressInput, serverAddressInput, -} from "../Model/model"; +} from "../../Model/model"; -import { translation } from "../translations"; +import { translation } from "../../translations"; const addressConverter: React.StateItemConverter = (address) => { return ; diff --git a/src/Views/personalSection.tsx b/src/Views/Overview/personalSection.tsx similarity index 81% rename from src/Views/personalSection.tsx rename to src/Views/Overview/personalSection.tsx index 8fe8db75..cab91625 100644 --- a/src/Views/personalSection.tsx +++ b/src/Views/Overview/personalSection.tsx @@ -1,7 +1,7 @@ import * as React from "bloatless-react"; -import { senderName } from "../Model/model"; -import { translation } from "../translations"; +import { senderName } from "../../Model/model"; +import { translation } from "../../translations"; export function PersonalSection() { return ( From 6cb4c1ef5f5f9d280247a5829077a45610380004 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 01:47:43 +0200 Subject: [PATCH 09/28] rename item->object, implement basic object editing --- bun.lockb | Bin 11435 -> 11435 bytes dist/index.js | 130 +++++++++++++++++++---------- src/Model/chatModel.ts | 74 ++++++++-------- src/Views/Chat/chatToolView.tsx | 27 ++++-- src/Views/Chat/itemDetailModal.tsx | 68 +++++++++++++++ src/translations.ts | 11 ++- src/utility.ts | 2 +- 7 files changed, 221 insertions(+), 91 deletions(-) create mode 100644 src/Views/Chat/itemDetailModal.tsx diff --git a/bun.lockb b/bun.lockb index ba1e0d010ae927894ccc6ef5f54676f264f1abe5..5c971e022ed9e0d26ebd8bfe95a602c9f4635f7a 100755 GIT binary patch delta 14 VcmZ1-xjJ$KyF8=GW)AtqyZ|U^1fBo@ delta 14 VcmZ1-xjJ$KyF8=uW)AtqyZ|U;1f2i? diff --git a/dist/index.js b/dist/index.js index 3d509265..9ecd69b0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -399,7 +399,7 @@ messages(id) { return id + "messages"; }, - items(id) { + objects(id) { return id + "items"; }, outbox(id) { @@ -422,9 +422,9 @@ secondaryChannels; encryptionKey; messages; - items; + objects; outbox; - itemOutbox; + objectOutbox; isOutBoxEmpty; composingMessage; primaryChannelInput; @@ -451,9 +451,9 @@ ); this.encryptionKey = restoreState(storageKeys.encyptionKey(id), ""); this.messages = restoreListState(storageKeys.messages(id)); - this.items = restoreMapState(storageKeys.items(id)); + this.objects = restoreMapState(storageKeys.objects(id)); this.outbox = restoreListState(storageKeys.outbox(id)); - this.itemOutbox = restoreListState(storageKeys.itemOutbox(id)); + this.objectOutbox = restoreListState(storageKeys.itemOutbox(id)); this.isOutBoxEmpty = createProxyState( [this.outbox], () => this.outbox.value.size == 0 @@ -504,11 +504,11 @@ if (channels.indexOf(this.primaryChannel.value) == -1) return; if (data.subscribed != void 0) this.handleSubscription(data.subscribed); if (!data.messageBody) return; - const { sender, body, channel, isoDate, itemData } = JSON.parse( + const { sender, body, channel, isoDate, messageObject } = JSON.parse( data.messageBody ); - if (itemData && itemData.id && itemData.title) { - return this.handleItem(itemData); + if (messageObject && messageObject.id && messageObject.title) { + return this.handleMessageObject(messageObject); } this.handleMessage({ sender, @@ -527,11 +527,11 @@ this.messages.add(chatMessage); if (selectedChat.value != this) this.hasUnreadMessages.value = true; }; - handleItem = (itemData) => { - this.items.set(itemData.id, itemData); + handleMessageObject = (messageObject) => { + this.objects.set(messageObject.id, messageObject); }; // messages - createMessage = async (messageText, itemData) => { + createChatMessage = async (messageText, messageObject) => { const secondaryChannelNames = [ ...this.secondaryChannels.value.values() ]; @@ -540,14 +540,14 @@ ...secondaryChannelNames ]; const joinedChannelName = allChannelNames.join("/"); - const messageObject = { + const chatMessage = { channel: joinedChannelName, sender: senderName.value, body: messageText, isoDate: (/* @__PURE__ */ new Date()).toISOString() }; - if (itemData) messageObject.itemData = itemData; - return messageObject; + if (messageObject) chatMessage.messageObject = messageObject; + return chatMessage; }; sendMessageFromComposer = async () => { if (this.cannotSendMessage.value == true) return; @@ -555,12 +555,12 @@ this.composingMessage.value = ""; }; sendMessageFromText = async (text) => { - const message = await this.createMessage(text); - this.sendMessage(message); + const chatMessage = await this.createChatMessage(text); + this.sendMessage(chatMessage); }; - resendMessage = (message) => { + resendMessage = (chatMessage) => { if (this.cannotResendMessage.value == true) return; - this.sendMessageFromText(message.body); + this.sendMessageFromText(chatMessage.body); }; sendMessage = async (chatMessage) => { if (isConnected.value == true && this.isSubscribed.value == true) { @@ -569,7 +569,7 @@ const messageString = JSON.stringify(chatMessage); UDN.sendMessage(chatMessage.channel, messageString); } else { - if (chatMessage.itemData) return this.itemOutbox.add(chatMessage); + if (chatMessage.messageObject) return this.objectOutbox.add(chatMessage); this.outbox.add(chatMessage); } }; @@ -578,29 +578,29 @@ this.sendMessage(message); this.outbox.remove(message); }); - this.itemOutbox.value.forEach((message) => { + this.objectOutbox.value.forEach((message) => { this.sendMessage(message); - this.itemOutbox.remove(message); + this.objectOutbox.remove(message); }); }; clearMessages = () => { this.messages.clear(); }; - deleteMessage = (message) => { - this.messages.remove(message); + deleteMessage = (chatMessage) => { + this.messages.remove(chatMessage); }; - deleteOutboxMessage = (message) => { - this.outbox.remove(message); + deleteOutboxMessage = (chatMessage) => { + this.outbox.remove(chatMessage); }; - decryptReceivedMessage = async (message) => { - message.body = await decryptString(message.body, this.encryptionKey.value); + decryptReceivedMessage = async (chatMessage) => { + chatMessage.body = await decryptString(chatMessage.body, this.encryptionKey.value); this.messages.callSubscriptions(); }; - // items - async createItem(item) { - this.items.set(item.id, item); - const message = await this.createMessage("", item); - this.sendMessage(message); + // objects + async createObject(object) { + this.objects.set(object.id, object); + const chatMessage = await this.createChatMessage("", object); + this.sendMessage(chatMessage); } // channel setChannel = () => { @@ -872,9 +872,11 @@ var englishTranslations = { // general set: "Set", + save: "Save", back: "Back", undoChanges: "Undo changes", close: "Close", + discard: "Discard", // overview overview: "Overview", connection: "Connection", @@ -896,7 +898,6 @@ addChat: "Add", // messages showChatOptions: "show chat options", - toggleChatTools: "toggle chat tools", configureChatTitle: "Configure Chat", secondaryChannel: "Secondary channel", secondaryChannelPlaceholder: "Add secondary channel", @@ -914,7 +915,12 @@ resendMessage: "Resend message", decryptMessage: "Decrypt message", copyMessage: "Copy message", - deleteMessage: "Delete message" + deleteMessage: "Delete message", + // objects + toggleChatTools: "toggle chat tools", + objectTitle: "Object title", + objectTitlePlaceholder: "My object", + deleteObject: "Delete object" }; var allTranslations = { en: englishTranslations, @@ -1014,7 +1020,7 @@ var language = navigator.language.substring(0, 2); var translation = allTranslations[language] ?? allTranslations.en; - // src/Views/chatOptionModal.tsx + // src/Views/Chat/chatOptionModal.tsx function ChatOptionModal(chat, isPresented) { function closeModal() { isPresented.value = false; @@ -1113,21 +1119,55 @@ ), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteChat }, translation.removeChat, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete")))), /* @__PURE__ */ createElement("button", { "on:click": closeModal }, translation.close, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } - // src/Views/chatToolView.tsx + // src/Views/Chat/itemDetailModal.tsx + function ItemDetailModal(chat, object, isPresented) { + function closeModal() { + isPresented.value = false; + } + function saveAndClose() { + object.title = editingTitle.value; + chat.objects.set(object.id, object); + closeModal(); + } + function deleteAndClose() { + chat.objects.remove(object.id); + closeModal(); + } + const editingTitle = new State(object.title); + return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, object.title), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectTitle), /* @__PURE__ */ createElement( + "input", + { + "bind:value": editingTitle, + placeholder: translation.objectTitlePlaceholder + } + )))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteAndClose }, translation.deleteObject, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete"))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 danger", "on:click": closeModal }, translation.discard), /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 primary", "on:click": saveAndClose }, translation.save, /* @__PURE__ */ createElement("span", { class: "icon" }, "save"))))); + } + + // src/Views/Chat/chatToolView.tsx function ChatToolView(chat) { + const isShowingObjectModal = new State(false); + const selectedObject = new State(void 0); + const objectModal = createProxyState([selectedObject], () => { + if (selectedObject.value == void 0) return /* @__PURE__ */ createElement("div", null); + return ItemDetailModal(chat, selectedObject.value, isShowingObjectModal); + }); function createItem() { - chat.createItem({ + chat.createObject({ id: UUID(), title: "new item" }); } - const itemConverter = (item) => { - return /* @__PURE__ */ createElement("span", null, item.title); + const itemConverter = (object) => { + function select() { + selectedObject.value = object; + isShowingObjectModal.value = true; + } + return /* @__PURE__ */ createElement("button", { "on:click": select }, object.title); }; - return /* @__PURE__ */ createElement("div", { class: "chat-tool-view" }, /* @__PURE__ */ createElement("button", { "on:click": createItem }, "+"), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { "children:append": [chat.items, itemConverter] })); + return /* @__PURE__ */ createElement("div", { class: "chat-tool-view" }, /* @__PURE__ */ createElement("button", { "on:click": createItem }, "+"), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { "children:append": [chat.objects, itemConverter] }), /* @__PURE__ */ createElement("div", { "children:set": objectModal })); } - // src/Views/messageComposer.tsx + // src/Views/Chat/messageComposer.tsx function MessageComposer(chat) { return /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, " ", /* @__PURE__ */ createElement( "input", @@ -1149,7 +1189,7 @@ )); } - // src/Views/threadView.tsx + // src/Views/Chat/threadView.tsx function ThreadView(chat) { const messageConverter = (message) => { function resendMessage() { @@ -1271,7 +1311,7 @@ ); } - // src/Views/chatListSection.tsx + // src/Views/Overview/chatListSection.tsx var chatConverter = (chat) => { function select() { selectChat(chat); @@ -1318,7 +1358,7 @@ )); } - // src/Views/connectionSection.tsx + // src/Views/Overview/connectionSection.tsx var addressConverter = (address) => { return /* @__PURE__ */ createElement("option", null, address); }; @@ -1385,7 +1425,7 @@ ))); } - // src/Views/personalSection.tsx + // src/Views/Overview/personalSection.tsx function PersonalSection() { return /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.yourName), /* @__PURE__ */ createElement( "input", diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index b553c92c..4165a5ba 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -22,19 +22,15 @@ export interface ChatMessage { body: string; isoDate: string; - itemData?: Item; + messageObject?: MessageObject; } -// item -export interface Item { +// object +export interface MessageObject { id: string; title: string; } -export interface NoteItem extends Item { - body: string; -} - // chat export class Chat { id: string; @@ -46,9 +42,9 @@ export class Chat { encryptionKey: React.State; messages: React.ListState; - items: React.MapState; + objects: React.MapState; outbox: React.ListState; - itemOutbox: React.ListState; + objectOutbox: React.ListState; isOutBoxEmpty: React.State; composingMessage: React.State; @@ -82,9 +78,9 @@ export class Chat { ); this.encryptionKey = React.restoreState(storageKeys.encyptionKey(id), ""); this.messages = React.restoreListState(storageKeys.messages(id)); - this.items = React.restoreMapState(storageKeys.items(id)); + this.objects = React.restoreMapState(storageKeys.objects(id)); this.outbox = React.restoreListState(storageKeys.outbox(id)); - this.itemOutbox = React.restoreListState(storageKeys.itemOutbox(id)); + this.objectOutbox = React.restoreListState(storageKeys.itemOutbox(id)); this.isOutBoxEmpty = React.createProxyState( [this.outbox], () => this.outbox.value.size == 0 @@ -149,12 +145,12 @@ export class Chat { if (data.subscribed != undefined) this.handleSubscription(data.subscribed); if (!data.messageBody) return; - const { sender, body, channel, isoDate, itemData } = JSON.parse( + const { sender, body, channel, isoDate, messageObject } = JSON.parse( data.messageBody ); - if (itemData && itemData.id && itemData.title) { - return this.handleItem(itemData); + if (messageObject && messageObject.id && messageObject.title) { + return this.handleMessageObject(messageObject); } this.handleMessage({ @@ -178,14 +174,14 @@ export class Chat { if (selectedChat.value != this) this.hasUnreadMessages.value = true; }; - handleItem = (itemData: Item): void => { - this.items.set(itemData.id, itemData); + handleMessageObject = (messageObject: MessageObject): void => { + this.objects.set(messageObject.id, messageObject); }; // messages - createMessage = async ( + createChatMessage = async ( messageText: string, - itemData?: Item + messageObject?: MessageObject ): Promise => { // get channels const secondaryChannelNames: string[] = [ @@ -198,15 +194,15 @@ export class Chat { // create object const joinedChannelName = allChannelNames.join("/"); - const messageObject: ChatMessage = { + const chatMessage: ChatMessage = { channel: joinedChannelName, sender: senderName.value, body: messageText, isoDate: new Date().toISOString(), }; - if (itemData) messageObject.itemData = itemData; - return messageObject; + if (messageObject) chatMessage.messageObject = messageObject; + return chatMessage; }; sendMessageFromComposer = async (): Promise => { @@ -217,13 +213,13 @@ export class Chat { }; sendMessageFromText = async (text: string): Promise => { - const message = await this.createMessage(text); - this.sendMessage(message); + const chatMessage = await this.createChatMessage(text); + this.sendMessage(chatMessage); }; - resendMessage = (message: ChatMessage) => { + resendMessage = (chatMessage: ChatMessage) => { if (this.cannotResendMessage.value == true) return; - this.sendMessageFromText(message.body); + this.sendMessageFromText(chatMessage.body); }; sendMessage = async (chatMessage: ChatMessage): Promise => { @@ -238,7 +234,7 @@ export class Chat { const messageString = JSON.stringify(chatMessage); UDN.sendMessage(chatMessage.channel, messageString); } else { - if (chatMessage.itemData) return this.itemOutbox.add(chatMessage); + if (chatMessage.messageObject) return this.objectOutbox.add(chatMessage); this.outbox.add(chatMessage); } }; @@ -248,9 +244,9 @@ export class Chat { this.sendMessage(message); this.outbox.remove(message); }); - this.itemOutbox.value.forEach((message) => { + this.objectOutbox.value.forEach((message) => { this.sendMessage(message); - this.itemOutbox.remove(message); + this.objectOutbox.remove(message); }); }; @@ -258,25 +254,25 @@ export class Chat { this.messages.clear(); }; - deleteMessage = (message: ChatMessage): void => { - this.messages.remove(message); + deleteMessage = (chatMessage: ChatMessage): void => { + this.messages.remove(chatMessage); }; - deleteOutboxMessage = (message: ChatMessage): void => { - this.outbox.remove(message); + deleteOutboxMessage = (chatMessage: ChatMessage): void => { + this.outbox.remove(chatMessage); }; - decryptReceivedMessage = async (message: ChatMessage): Promise => { - message.body = await decryptString(message.body, this.encryptionKey.value); + decryptReceivedMessage = async (chatMessage: ChatMessage): Promise => { + chatMessage.body = await decryptString(chatMessage.body, this.encryptionKey.value); this.messages.callSubscriptions(); }; - // items - async createItem(item: Item): Promise { - this.items.set(item.id, item); + // objects + async createObject(object: MessageObject): Promise { + this.objects.set(object.id, object); - const message = await this.createMessage("", item); - this.sendMessage(message); + const chatMessage = await this.createChatMessage("", object); + this.sendMessage(chatMessage); } // channel diff --git a/src/Views/Chat/chatToolView.tsx b/src/Views/Chat/chatToolView.tsx index c480a08d..3a13d9bd 100644 --- a/src/Views/Chat/chatToolView.tsx +++ b/src/Views/Chat/chatToolView.tsx @@ -1,26 +1,43 @@ import * as React from "bloatless-react"; -import { Chat, Item } from "../../Model/chatModel"; +import { Chat, MessageObject } from "../../Model/chatModel"; +import { ChatOptionModal } from "./chatOptionModal"; +import { ItemDetailModal } from "./itemDetailModal"; import { translation } from "../../translations"; export function ChatToolView(chat: Chat) { + const isShowingObjectModal = new React.State(false); + const selectedObject = new React.State(undefined); + const objectModal = React.createProxyState([selectedObject], () => { + if (selectedObject.value == undefined) return
; + + return ItemDetailModal(chat, selectedObject.value, isShowingObjectModal); + }); + function createItem() { - chat.createItem({ + chat.createObject({ id: React.UUID(), title: "new item", }); } - const itemConverter: React.StateItemConverter = (item) => { - return {item.title}; + const itemConverter: React.StateItemConverter = (object) => { + function select() { + selectedObject.value = object; + isShowingObjectModal.value = true; + } + + return ; }; return (

-
+
+ +
); } diff --git a/src/Views/Chat/itemDetailModal.tsx b/src/Views/Chat/itemDetailModal.tsx new file mode 100644 index 00000000..5def83f9 --- /dev/null +++ b/src/Views/Chat/itemDetailModal.tsx @@ -0,0 +1,68 @@ +import * as React from "bloatless-react"; + +import { Chat, MessageObject } from "../../Model/chatModel"; + +import { translation } from "../../translations"; + +export function ItemDetailModal( + chat: Chat, + object: MessageObject, + isPresented: React.State +) { + function closeModal() { + isPresented.value = false; + } + + function saveAndClose() { + object.title = editingTitle.value; + chat.objects.set(object.id, object); + closeModal(); + } + + function deleteAndClose() { + chat.objects.remove(object.id); + closeModal(); + } + + const editingTitle = new React.State(object.title); + + return ( + + ); +} diff --git a/src/translations.ts b/src/translations.ts index 228b9c41..7eceabc9 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -1,9 +1,11 @@ const englishTranslations = { // general set: "Set", + save: "Save", back: "Back", undoChanges: "Undo changes", close: "Close", + discard: "Discard", // overview overview: "Overview", @@ -36,7 +38,6 @@ const englishTranslations = { // messages showChatOptions: "show chat options", - toggleChatTools: "toggle chat tools", configureChatTitle: "Configure Chat", secondaryChannel: "Secondary channel", @@ -62,6 +63,14 @@ const englishTranslations = { decryptMessage: "Decrypt message", copyMessage: "Copy message", deleteMessage: "Delete message", + + // objects + toggleChatTools: "toggle chat tools", + + objectTitle: "Object title", + objectTitlePlaceholder: "My object", + + deleteObject: "Delete object", }; const allTranslations: { [key: string]: typeof englishTranslations } = { diff --git a/src/utility.ts b/src/utility.ts index 249ed8cb..a769fd66 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -19,7 +19,7 @@ export const storageKeys = { return id + "messages"; }, - items(id: string): string { + objects(id: string): string { return id + "items"; }, From fbed8f8780c26374ee765460b6a4aaefc1864da8 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 02:09:44 +0200 Subject: [PATCH 10/28] fix outbox --- dist/index.js | 51 ++++++++++++---------- src/Model/chatModel.ts | 70 +++++++++++++++++------------- src/Views/Chat/chatToolView.tsx | 4 +- src/Views/Chat/itemDetailModal.tsx | 3 +- 4 files changed, 71 insertions(+), 57 deletions(-) diff --git a/dist/index.js b/dist/index.js index 9ecd69b0..e035d128 100644 --- a/dist/index.js +++ b/dist/index.js @@ -453,7 +453,7 @@ this.messages = restoreListState(storageKeys.messages(id)); this.objects = restoreMapState(storageKeys.objects(id)); this.outbox = restoreListState(storageKeys.outbox(id)); - this.objectOutbox = restoreListState(storageKeys.itemOutbox(id)); + this.objectOutbox = restoreMapState(storageKeys.itemOutbox(id)); this.isOutBoxEmpty = createProxyState( [this.outbox], () => this.outbox.value.size == 0 @@ -556,31 +556,20 @@ }; sendMessageFromText = async (text) => { const chatMessage = await this.createChatMessage(text); - this.sendMessage(chatMessage); + this.outbox.add(chatMessage); + this.sendMessagesInOutbox(); }; resendMessage = (chatMessage) => { if (this.cannotResendMessage.value == true) return; this.sendMessageFromText(chatMessage.body); }; - sendMessage = async (chatMessage) => { - if (isConnected.value == true && this.isSubscribed.value == true) { - const encrypted = this.encryptionKey.value == "" ? chatMessage.body : await encryptString(chatMessage.body, this.encryptionKey.value); - chatMessage.body = encrypted; - const messageString = JSON.stringify(chatMessage); - UDN.sendMessage(chatMessage.channel, messageString); - } else { - if (chatMessage.messageObject) return this.objectOutbox.add(chatMessage); - this.outbox.add(chatMessage); - } - }; sendMessagesInOutbox = () => { this.outbox.value.forEach((message) => { this.sendMessage(message); - this.outbox.remove(message); }); - this.objectOutbox.value.forEach((message) => { - this.sendMessage(message); - this.objectOutbox.remove(message); + this.objectOutbox.value.forEach(async (object) => { + const chatMessage = await this.createChatMessage("", object); + this.sendMessage(chatMessage); }); }; clearMessages = () => { @@ -593,14 +582,28 @@ this.outbox.remove(chatMessage); }; decryptReceivedMessage = async (chatMessage) => { - chatMessage.body = await decryptString(chatMessage.body, this.encryptionKey.value); + chatMessage.body = await decryptString( + chatMessage.body, + this.encryptionKey.value + ); this.messages.callSubscriptions(); }; + sendMessage = async (chatMessage) => { + if (isConnected.value == false || this.isSubscribed.value == false) return; + const encryptedBody = this.encryptionKey.value == "" ? chatMessage.body : await encryptString(chatMessage.body, this.encryptionKey.value); + chatMessage.body = encryptedBody; + const messageString = JSON.stringify(chatMessage); + UDN.sendMessage(chatMessage.channel, messageString); + this.outbox.remove(chatMessage); + }; // objects - async createObject(object) { + async addObjectAndSend(object) { this.objects.set(object.id, object); - const chatMessage = await this.createChatMessage("", object); - this.sendMessage(chatMessage); + await this.sendObject(object); + } + async sendObject(object) { + this.objectOutbox.set(object.id, object); + this.sendMessagesInOutbox(); } // channel setChannel = () => { @@ -1126,7 +1129,7 @@ } function saveAndClose() { object.title = editingTitle.value; - chat.objects.set(object.id, object); + chat.addObjectAndSend(object); closeModal(); } function deleteAndClose() { @@ -1152,7 +1155,7 @@ return ItemDetailModal(chat, selectedObject.value, isShowingObjectModal); }); function createItem() { - chat.createObject({ + chat.addObjectAndSend({ id: UUID(), title: "new item" }); @@ -1164,7 +1167,7 @@ } return /* @__PURE__ */ createElement("button", { "on:click": select }, object.title); }; - return /* @__PURE__ */ createElement("div", { class: "chat-tool-view" }, /* @__PURE__ */ createElement("button", { "on:click": createItem }, "+"), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { "children:append": [chat.objects, itemConverter] }), /* @__PURE__ */ createElement("div", { "children:set": objectModal })); + return /* @__PURE__ */ createElement("div", { class: "chat-tool-view" }, /* @__PURE__ */ createElement("button", { "on:click": createItem }, "+"), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { "children:prepend": [chat.objects, itemConverter] }), /* @__PURE__ */ createElement("div", { "children:set": objectModal })); } // src/Views/Chat/messageComposer.tsx diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index 4165a5ba..2a8e3d28 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -44,7 +44,7 @@ export class Chat { messages: React.ListState; objects: React.MapState; outbox: React.ListState; - objectOutbox: React.ListState; + objectOutbox: React.MapState; isOutBoxEmpty: React.State; composingMessage: React.State; @@ -80,7 +80,7 @@ export class Chat { this.messages = React.restoreListState(storageKeys.messages(id)); this.objects = React.restoreMapState(storageKeys.objects(id)); this.outbox = React.restoreListState(storageKeys.outbox(id)); - this.objectOutbox = React.restoreListState(storageKeys.itemOutbox(id)); + this.objectOutbox = React.restoreMapState(storageKeys.itemOutbox(id)); this.isOutBoxEmpty = React.createProxyState( [this.outbox], () => this.outbox.value.size == 0 @@ -214,7 +214,8 @@ export class Chat { sendMessageFromText = async (text: string): Promise => { const chatMessage = await this.createChatMessage(text); - this.sendMessage(chatMessage); + this.outbox.add(chatMessage); + this.sendMessagesInOutbox(); }; resendMessage = (chatMessage: ChatMessage) => { @@ -222,31 +223,13 @@ export class Chat { this.sendMessageFromText(chatMessage.body); }; - sendMessage = async (chatMessage: ChatMessage): Promise => { - if (isConnected.value == true && this.isSubscribed.value == true) { - const encrypted = - this.encryptionKey.value == "" - ? chatMessage.body - : await encryptString(chatMessage.body, this.encryptionKey.value); - - chatMessage.body = encrypted; - - const messageString = JSON.stringify(chatMessage); - UDN.sendMessage(chatMessage.channel, messageString); - } else { - if (chatMessage.messageObject) return this.objectOutbox.add(chatMessage); - this.outbox.add(chatMessage); - } - }; - sendMessagesInOutbox = () => { this.outbox.value.forEach((message) => { this.sendMessage(message); - this.outbox.remove(message); }); - this.objectOutbox.value.forEach((message) => { - this.sendMessage(message); - this.objectOutbox.remove(message); + this.objectOutbox.value.forEach(async (object) => { + const chatMessage = await this.createChatMessage("", object); + this.sendMessage(chatMessage); }); }; @@ -263,16 +246,43 @@ export class Chat { }; decryptReceivedMessage = async (chatMessage: ChatMessage): Promise => { - chatMessage.body = await decryptString(chatMessage.body, this.encryptionKey.value); + chatMessage.body = await decryptString( + chatMessage.body, + this.encryptionKey.value + ); this.messages.callSubscriptions(); }; - // objects - async createObject(object: MessageObject): Promise { - this.objects.set(object.id, object); + sendMessage = async (chatMessage: ChatMessage): Promise => { + if (isConnected.value == false || this.isSubscribed.value == false) return; - const chatMessage = await this.createChatMessage("", object); - this.sendMessage(chatMessage); + const encryptedBody = + this.encryptionKey.value == "" + ? chatMessage.body + : await encryptString(chatMessage.body, this.encryptionKey.value); + + chatMessage.body = encryptedBody; + + const messageString = JSON.stringify(chatMessage); + UDN.sendMessage(chatMessage.channel, messageString); + + this.outbox.remove(chatMessage); + }; + + // objects + addObjectAndSend(object: MessageObject): void { + this.objects.set(object.id, object); + this.sendObject(object); + } + + sendObject(object: MessageObject): void { + this.objectOutbox.set(object.id, object); + this.sendMessagesInOutbox(); + } + + deleteObject(object: MessageObject): void { + this.objects.remove(object.id); + this.objectOutbox.remove(object.id); } // channel diff --git a/src/Views/Chat/chatToolView.tsx b/src/Views/Chat/chatToolView.tsx index 3a13d9bd..0313e7f6 100644 --- a/src/Views/Chat/chatToolView.tsx +++ b/src/Views/Chat/chatToolView.tsx @@ -16,7 +16,7 @@ export function ChatToolView(chat: Chat) { }); function createItem() { - chat.createObject({ + chat.addObjectAndSend({ id: React.UUID(), title: "new item", }); @@ -35,7 +35,7 @@ export function ChatToolView(chat: Chat) {

-
+
diff --git a/src/Views/Chat/itemDetailModal.tsx b/src/Views/Chat/itemDetailModal.tsx index 5def83f9..66b9bd4e 100644 --- a/src/Views/Chat/itemDetailModal.tsx +++ b/src/Views/Chat/itemDetailModal.tsx @@ -15,7 +15,8 @@ export function ItemDetailModal( function saveAndClose() { object.title = editingTitle.value; - chat.objects.set(object.id, object); + + chat.addObjectAndSend(object); closeModal(); } From cf935b400acf8aceb69d34f93858b3ed69b79d77 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 02:15:02 +0200 Subject: [PATCH 11/28] remove deleted objects from outbox --- dist/index.js | 14 ++++++++++---- src/Model/chatModel.ts | 2 ++ src/Views/Chat/itemDetailModal.tsx | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/dist/index.js b/dist/index.js index e035d128..3b44a238 100644 --- a/dist/index.js +++ b/dist/index.js @@ -595,16 +595,22 @@ const messageString = JSON.stringify(chatMessage); UDN.sendMessage(chatMessage.channel, messageString); this.outbox.remove(chatMessage); + if (chatMessage.messageObject) + this.objectOutbox.remove(chatMessage.messageObject.id); }; // objects - async addObjectAndSend(object) { + addObjectAndSend(object) { this.objects.set(object.id, object); - await this.sendObject(object); + this.sendObject(object); } - async sendObject(object) { + sendObject(object) { this.objectOutbox.set(object.id, object); this.sendMessagesInOutbox(); } + deleteObject(object) { + this.objects.remove(object.id); + this.objectOutbox.remove(object.id); + } // channel setChannel = () => { if (this.cannotSetChannel.value == true) return; @@ -1133,7 +1139,7 @@ closeModal(); } function deleteAndClose() { - chat.objects.remove(object.id); + chat.deleteObject(object); closeModal(); } const editingTitle = new State(object.title); diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index 2a8e3d28..5b71ee8f 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -267,6 +267,8 @@ export class Chat { UDN.sendMessage(chatMessage.channel, messageString); this.outbox.remove(chatMessage); + if (chatMessage.messageObject) + this.objectOutbox.remove(chatMessage.messageObject.id); }; // objects diff --git a/src/Views/Chat/itemDetailModal.tsx b/src/Views/Chat/itemDetailModal.tsx index 66b9bd4e..23448303 100644 --- a/src/Views/Chat/itemDetailModal.tsx +++ b/src/Views/Chat/itemDetailModal.tsx @@ -21,7 +21,7 @@ export function ItemDetailModal( } function deleteAndClose() { - chat.objects.remove(object.id); + chat.deleteObject(object); closeModal(); } From 687d378eb41092090ba79722e0c1830602a053b2 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 02:29:00 +0200 Subject: [PATCH 12/28] add buttons to resend and clear all objects --- bun.lockb | Bin 11435 -> 11435 bytes dist/index.js | 50 +++++++++++++++++++++-------- package.json | 2 +- src/Model/chatModel.ts | 27 ++++++++++++---- src/Views/Chat/chatOptionModal.tsx | 24 ++++++++++++-- src/translations.ts | 7 ++-- 6 files changed, 85 insertions(+), 25 deletions(-) diff --git a/bun.lockb b/bun.lockb index 5c971e022ed9e0d26ebd8bfe95a602c9f4635f7a..e2a4fb722c6d55f1a2eb515caf2e65271c8a49a2 100755 GIT binary patch delta 137 zcmV;40CxYYS*ux)E+9tY)C4st7g9N|_|jp8xqdF_ip4d?C_44R3vY|u-RQAS+6Vy! zlTiXEvrq|b3qbQ$^3uvZwt_p;!G~s%0XFThCx9Se0TH&#rG9^LSX%$zsS_0uPvt#B r5SFAjVuYavwgGB1%s^b3BhW<%RS2{F6bcFfHM0mG$P58BlhG^M(4#vL delta 137 zcmV;40CxYYS*ux)E+C_ExWNhU%NzQgm_@n6#Zu-@uBHUwU_g}_<+h^|i(j!$+6Vyz zlTiXEvrq|b3qazTZ<0ZR2u{KnlOHwSt*0zj^eTZ#A2B$>|8mLXp1_oJrp~>TgP;@N rALQYCb3ag+qrfkNtlMKSkzI#t@*A`L6bcFfG_wdF$P58AlhG^MqsTxE diff --git a/dist/index.js b/dist/index.js index 3b44a238..0afc1a03 100644 --- a/dist/index.js +++ b/dist/index.js @@ -147,8 +147,7 @@ this.removalHandlers.delete(item); } clear() { - this.value.clear(); - this.callSubscriptions(); + [...this.value.keys()].forEach((key) => this.remove(key)); } // handlers handleAddition(handler) { @@ -435,6 +434,7 @@ cannotSetChannel; cannotUndoChannel; cannotClearMessages; + cannotClearObjects; // init constructor(id = UUID()) { this.id = id; @@ -488,6 +488,10 @@ [this.messages], () => this.messages.value.size == 0 ); + this.cannotClearObjects = createProxyState( + [this.objects], + () => this.objects.value.size == 0 + ); } // general deleteSelf = () => { @@ -599,18 +603,26 @@ this.objectOutbox.remove(chatMessage.messageObject.id); }; // objects - addObjectAndSend(object) { + addObjectAndSend = (object) => { this.objects.set(object.id, object); this.sendObject(object); - } - sendObject(object) { + }; + sendObject = (object) => { this.objectOutbox.set(object.id, object); this.sendMessagesInOutbox(); - } - deleteObject(object) { + }; + deleteObject = (object) => { this.objects.remove(object.id); this.objectOutbox.remove(object.id); - } + }; + resendObjects = () => { + this.objects.value.forEach((object) => { + this.sendObject(object); + }); + }; + clearObjects = () => { + this.objects.clear(); + }; // channel setChannel = () => { if (this.cannotSetChannel.value == true) return; @@ -905,7 +917,7 @@ primaryChannel: "Primary channel", primaryChannelPlaceholder: "my-channel", addChat: "Add", - // messages + // chat showChatOptions: "show chat options", configureChatTitle: "Configure Chat", secondaryChannel: "Secondary channel", @@ -915,12 +927,14 @@ encryptionKey: "Encryption key", encryptionKeyPlaceholder: "n10d2482dg283hg", showKey: "Show key", - removeChat: "Remove chat", + clearObjects: "Delete all objects", clearChatMessages: "Clear chat messages", + removeChat: "Remove chat", messageInOutbox: "Pending", noChatSelected: "No chat selected", composerPlaceholder: "Type a message...", sendMessage: "Send", + // messages resendMessage: "Resend message", decryptMessage: "Decrypt message", copyMessage: "Copy message", @@ -929,6 +943,7 @@ toggleChatTools: "toggle chat tools", objectTitle: "Object title", objectTitlePlaceholder: "My object", + resendObjects: "Resend all objects", deleteObject: "Delete object" }; var allTranslations = { @@ -1116,7 +1131,16 @@ "bind:value": chat.encryptionKey, "set:type": inputType } - ))), /* @__PURE__ */ createElement("label", { class: "inline margin-0" }, /* @__PURE__ */ createElement("input", { type: "checkbox", "bind:checked": shouldShowKey }), translation.showKey)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement( + ))), /* @__PURE__ */ createElement("label", { class: "inline margin-0" }, /* @__PURE__ */ createElement("input", { type: "checkbox", "bind:checked": shouldShowKey }), translation.showKey)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap width-input" }, /* @__PURE__ */ createElement("button", { "on:click": chat.resendObjects }, translation.resendObjects, /* @__PURE__ */ createElement("span", { class: "icon" }, "replay"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap width-input" }, /* @__PURE__ */ createElement( + "button", + { + class: "danger", + "on:click": chat.clearObjects, + "toggle:disabled": chat.cannotClearObjects + }, + translation.clearObjects, + /* @__PURE__ */ createElement("span", { class: "icon" }, "delete_sweep") + ), /* @__PURE__ */ createElement( "button", { class: "danger", @@ -1124,8 +1148,8 @@ "toggle:disabled": chat.cannotClearMessages }, translation.clearChatMessages, - /* @__PURE__ */ createElement("span", { class: "icon" }, "delete_sweep") - ), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteChat }, translation.removeChat, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete")))), /* @__PURE__ */ createElement("button", { "on:click": closeModal }, translation.close, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + /* @__PURE__ */ createElement("span", { class: "icon" }, "chat_error") + ), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteChat }, translation.removeChat, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete_forever")))), /* @__PURE__ */ createElement("button", { "on:click": closeModal }, translation.close, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } // src/Views/Chat/itemDetailModal.tsx diff --git a/package.json b/package.json index aba530a7..0566e118 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "bloatless-react": "^1.3.4", + "bloatless-react": "^1.3.5", "esbuild": "latest", "udn-frontend": "^1.0.7-beta1" }, diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index 5b71ee8f..fa012f64 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -57,6 +57,7 @@ export class Chat { cannotSetChannel: React.State; cannotUndoChannel: React.State; cannotClearMessages: React.State; + cannotClearObjects: React.State; // init constructor(id: string = React.UUID()) { @@ -124,6 +125,10 @@ export class Chat { [this.messages], () => this.messages.value.size == 0 ); + this.cannotClearObjects = React.createProxyState( + [this.objects], + () => this.objects.value.size == 0 + ); } // general @@ -272,20 +277,30 @@ export class Chat { }; // objects - addObjectAndSend(object: MessageObject): void { + addObjectAndSend = (object: MessageObject): void => { this.objects.set(object.id, object); this.sendObject(object); - } + }; - sendObject(object: MessageObject): void { + sendObject = (object: MessageObject): void => { this.objectOutbox.set(object.id, object); this.sendMessagesInOutbox(); - } + }; - deleteObject(object: MessageObject): void { + deleteObject = (object: MessageObject): void => { this.objects.remove(object.id); this.objectOutbox.remove(object.id); - } + }; + + resendObjects = (): void => { + this.objects.value.forEach((object) => { + this.sendObject(object); + }); + }; + + clearObjects = (): void => { + this.objects.clear(); + }; // channel setChannel = (): void => { diff --git a/src/Views/Chat/chatOptionModal.tsx b/src/Views/Chat/chatOptionModal.tsx index cecaa584..6ab2fb44 100644 --- a/src/Views/Chat/chatOptionModal.tsx +++ b/src/Views/Chat/chatOptionModal.tsx @@ -131,19 +131,37 @@ export function ChatOptionModal(chat: Chat, isPresented: React.State) {
-
+
+ +
+ +
+ +
+ +
diff --git a/src/translations.ts b/src/translations.ts index 7eceabc9..408b7805 100644 --- a/src/translations.ts +++ b/src/translations.ts @@ -36,7 +36,7 @@ const englishTranslations = { primaryChannelPlaceholder: "my-channel", addChat: "Add", - // messages + // chat showChatOptions: "show chat options", configureChatTitle: "Configure Chat", @@ -49,8 +49,9 @@ const englishTranslations = { encryptionKeyPlaceholder: "n10d2482dg283hg", showKey: "Show key", - removeChat: "Remove chat", + clearObjects: "Delete all objects", clearChatMessages: "Clear chat messages", + removeChat: "Remove chat", messageInOutbox: "Pending", @@ -59,6 +60,7 @@ const englishTranslations = { composerPlaceholder: "Type a message...", sendMessage: "Send", + // messages resendMessage: "Resend message", decryptMessage: "Decrypt message", copyMessage: "Copy message", @@ -70,6 +72,7 @@ const englishTranslations = { objectTitle: "Object title", objectTitlePlaceholder: "My object", + resendObjects: "Resend all objects", deleteObject: "Delete object", }; From e45f9730d65882f59b3df7b19a3198f46b7600ad Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 02:30:38 +0200 Subject: [PATCH 13/28] change object icon --- dist/index.js | 2 +- src/Tabs/messageTab.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 0afc1a03..64bcf960 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1319,7 +1319,7 @@ "on:click": toggleChatTools, "toggle:selected": isShowingChatTools }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "architecture") + /* @__PURE__ */ createElement("span", { class: "icon" }, "deployed_code") ), /* @__PURE__ */ createElement( "button", { diff --git a/src/Tabs/messageTab.tsx b/src/Tabs/messageTab.tsx index 3f971d40..2da35eaa 100644 --- a/src/Tabs/messageTab.tsx +++ b/src/Tabs/messageTab.tsx @@ -39,7 +39,7 @@ export function MessageTab() { on:click={toggleChatTools} toggle:selected={isShowingChatTools} > - architecture + deployed_code + ); +} diff --git a/src/Views/Chat/chatToolView.tsx b/src/Views/Objects/objectListView.tsx similarity index 62% rename from src/Views/Chat/chatToolView.tsx rename to src/Views/Objects/objectListView.tsx index 0313e7f6..2a7e46d3 100644 --- a/src/Views/Chat/chatToolView.tsx +++ b/src/Views/Objects/objectListView.tsx @@ -2,8 +2,8 @@ import * as React from "bloatless-react"; import { Chat, MessageObject } from "../../Model/chatModel"; -import { ChatOptionModal } from "./chatOptionModal"; -import { ItemDetailModal } from "./itemDetailModal"; +import { ObjectDetailModal } from "./objectDetailModal"; +import { ObjectListEntry } from "./objectListEntry"; import { translation } from "../../translations"; export function ChatToolView(chat: Chat) { @@ -12,23 +12,27 @@ export function ChatToolView(chat: Chat) { const objectModal = React.createProxyState([selectedObject], () => { if (selectedObject.value == undefined) return
; - return ItemDetailModal(chat, selectedObject.value, isShowingObjectModal); + return ObjectDetailModal(chat, selectedObject.value, isShowingObjectModal); }); function createItem() { - chat.addObjectAndSend({ - id: React.UUID(), - title: "new item", - }); + const newObject = chat.createObjectFromTitle("New item"); + chat.addObjectAndSend(newObject); } - const itemConverter: React.StateItemConverter = (object) => { + const itemConverter: React.StateItemConverter = ( + messageObject + ) => { + const latestObject = chat.getLatestObjectContent(messageObject); + + if (!latestObject) return
; //TODO + function select() { - selectedObject.value = object; + selectedObject.value = messageObject; isShowingObjectModal.value = true; } - return ; + return ObjectListEntry(messageObject, select); }; return ( From 709e781123f25ae1b1cd9092a5e0a03de224042b Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 13:11:22 +0200 Subject: [PATCH 16/28] unify variable names --- src/Model/chatModel.ts | 49 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index 3e8c2494..012f89f1 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -22,7 +22,7 @@ export interface ChatMessage { body: string; isoDate: string; - messageObject?: string; + messageObjectString?: string; } // object @@ -154,17 +154,16 @@ export class Chat { if (data.subscribed != undefined) this.handleSubscription(data.subscribed); if (!data.messageBody) return; - const { sender, body, channel, isoDate, messageObject } = JSON.parse( - data.messageBody - ); + const message: ChatMessage = JSON.parse(data.messageBody); + const { sender, body, channel, isoDate, messageObjectString } = message; - if (messageObject) { - const decrypted = await decryptString( - messageObject, + if (messageObjectString) { + const decryptedMessageObjectString = await decryptString( + messageObjectString, this.encryptionKey.value ); - const object = JSON.parse(decrypted); - if (object.id && object.title) return this.handleMessageObject(object); + const messageObject = JSON.parse(decryptedMessageObjectString); + if (messageObject.id && messageObject.title) return this.handleMessageObject(messageObject); } this.handleMessage({ @@ -199,10 +198,10 @@ export class Chat { const isSent = await this.sendMessage(message); if (isSent == true) this.outbox.remove(message); }); - this.objectOutbox.value.forEach(async (object) => { - const chatMessage = await this.createChatMessage("", object); + this.objectOutbox.value.forEach(async (messageObject) => { + const chatMessage = await this.createChatMessage("", messageObject); const isSent = await this.sendMessage(chatMessage); - if (isSent == true) this.objectOutbox.remove(object.id); + if (isSent == true) this.objectOutbox.remove(messageObject.id); }); }; @@ -230,7 +229,7 @@ export class Chat { }; if (messageObject) - chatMessage.messageObject = await encryptString( + chatMessage.messageObjectString = await encryptString( JSON.stringify(messageObject), this.encryptionKey.value ); @@ -307,25 +306,25 @@ export class Chat { }; updateObject = (id: string, contents: MessageObjectContent): void => { - const object = this.objects.value.get(id); - if (!object) return; - object.contentVersions.push(contents); - this.sendObject(object); + const messageObject = this.objects.value.get(id); + if (!messageObject) return; + messageObject.contentVersions.push(contents); + this.sendObject(messageObject); }; - addObjectAndSend = (object: MessageObject): void => { - this.addObject(object); - this.sendObject(object); + addObjectAndSend = (messageObject: MessageObject): void => { + this.addObject(messageObject); + this.sendObject(messageObject); }; - sendObject = (object: MessageObject): void => { - this.objectOutbox.set(object.id, object); + sendObject = (messageObject: MessageObject): void => { + this.objectOutbox.set(messageObject.id, messageObject); this.sendMessagesInOutbox(); }; - deleteObject = (object: MessageObject): void => { - this.objects.remove(object.id); - this.objectOutbox.remove(object.id); + deleteObject = (messageObject: MessageObject): void => { + this.objects.remove(messageObject.id); + this.objectOutbox.remove(messageObject.id); }; resendObjects = (): void => { From 19fe4274fc13630cf1a32749529b7dd1177d1631 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Thu, 11 Jul 2024 14:03:15 +0200 Subject: [PATCH 17/28] prepare message object content editing --- dist/index.js | 66 ++++++++++++++----------- dist/styles.css | 2 +- src/Model/chatModel.ts | 16 ++++-- src/Views/Objects/objectDetailModal.tsx | 65 +++++++++++++++++------- src/translations.ts | 2 + 5 files changed, 97 insertions(+), 54 deletions(-) diff --git a/dist/index.js b/dist/index.js index 3107a31d..1c5aa0da 100644 --- a/dist/index.js +++ b/dist/index.js @@ -508,16 +508,16 @@ if (channels.indexOf(this.primaryChannel.value) == -1) return; if (data.subscribed != void 0) this.handleSubscription(data.subscribed); if (!data.messageBody) return; - const { sender, body, channel, isoDate, messageObject } = JSON.parse( - data.messageBody - ); - if (messageObject) { - const decrypted = await decryptString( - messageObject, + const message = JSON.parse(data.messageBody); + const { sender, body, channel, isoDate, messageObjectString } = message; + if (messageObjectString) { + const decryptedMessageObjectString = await decryptString( + messageObjectString, this.encryptionKey.value ); - const object = JSON.parse(decrypted); - if (object.id && object.title) return this.handleMessageObject(object); + const messageObject = JSON.parse(decryptedMessageObjectString); + if (messageObject.id && messageObject.title) + return this.handleMessageObject(messageObject); } this.handleMessage({ sender, @@ -545,10 +545,10 @@ const isSent = await this.sendMessage(message); if (isSent == true) this.outbox.remove(message); }); - this.objectOutbox.value.forEach(async (object) => { - const chatMessage = await this.createChatMessage("", object); + this.objectOutbox.value.forEach(async (messageObject) => { + const chatMessage = await this.createChatMessage("", messageObject); const isSent = await this.sendMessage(chatMessage); - if (isSent == true) this.objectOutbox.remove(object.id); + if (isSent == true) this.objectOutbox.remove(messageObject.id); }); }; // messages @@ -568,7 +568,7 @@ isoDate: (/* @__PURE__ */ new Date()).toISOString() }; if (messageObject) - chatMessage.messageObject = await encryptString( + chatMessage.messageObjectString = await encryptString( JSON.stringify(messageObject), this.encryptionKey.value ); @@ -618,7 +618,7 @@ return { id: UUID(), title, - dateLastEdited: /* @__PURE__ */ new Date(), + isoDateLastEdited: (/* @__PURE__ */ new Date()).toISOString(), contentVersions: [] }; }; @@ -626,22 +626,22 @@ this.objects.set(messageObject.id, messageObject); }; updateObject = (id, contents) => { - const object = this.objects.value.get(id); - if (!object) return; - object.contentVersions.push(contents); - this.sendObject(object); + const messageObject = this.objects.value.get(id); + if (!messageObject) return; + messageObject.contentVersions.push(contents); + this.sendObject(messageObject); }; - addObjectAndSend = (object) => { - this.addObject(object); - this.sendObject(object); + addObjectAndSend = (messageObject) => { + this.addObject(messageObject); + this.sendObject(messageObject); }; - sendObject = (object) => { - this.objectOutbox.set(object.id, object); + sendObject = (messageObject) => { + this.objectOutbox.set(messageObject.id, messageObject); this.sendMessagesInOutbox(); }; - deleteObject = (object) => { - this.objects.remove(object.id); - this.objectOutbox.remove(object.id); + deleteObject = (messageObject) => { + this.objects.remove(messageObject.id); + this.objectOutbox.remove(messageObject.id); }; resendObjects = () => { this.objects.value.forEach((messageObject) => { @@ -653,7 +653,10 @@ }; getLatestObjectContent = (messageObject) => { const versions = messageObject.contentVersions; - if (versions.length == 0) return {}; + if (versions.length == 0) + return { + isoDateVersionCreated: (/* @__PURE__ */ new Date()).toISOString() + }; return versions[versions.length - 1]; }; // channel @@ -976,6 +979,7 @@ toggleChatTools: "toggle chat tools", objectTitle: "Object title", objectTitlePlaceholder: "My object", + objectVersion: "Object version", resendObjects: "Resend all objects", deleteObject: "Delete object" }; @@ -1187,26 +1191,28 @@ // src/Views/Objects/objectDetailModal.tsx function ObjectDetailModal(chat, messageObject, isPresented) { + const editingTitle = new State(messageObject.title); function closeModal() { isPresented.value = false; } function saveAndClose() { messageObject.title = editingTitle.value; - chat.updateObject(messageObject.id, {}); + chat.updateObject(messageObject.id, { + isoDateVersionCreated: (/* @__PURE__ */ new Date()).toISOString() + }); closeModal(); } function deleteAndClose() { chat.deleteObject(messageObject); closeModal(); } - const editingTitle = new State(messageObject.title); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, messageObject.title), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectTitle), /* @__PURE__ */ createElement( + return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, messageObject.title), /* @__PURE__ */ createElement("span", { class: "secondary" }, messageObject.id), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectTitle), /* @__PURE__ */ createElement( "input", { "bind:value": editingTitle, placeholder: translation.objectTitlePlaceholder } - )))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteAndClose }, translation.deleteObject, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete"))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 danger", "on:click": closeModal }, translation.discard), /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 primary", "on:click": saveAndClose }, translation.save, /* @__PURE__ */ createElement("span", { class: "icon" }, "save"))))); + ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "history"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translation.objectVersion), /* @__PURE__ */ createElement("select", null, ...messageObject.contentVersions.map((content) => /* @__PURE__ */ createElement("option", null, new Date(content.isoDateVersionCreated).toLocaleString()))), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("button", { class: "danger", "on:click": deleteAndClose }, translation.deleteObject, /* @__PURE__ */ createElement("span", { class: "icon" }, "delete"))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 danger", "on:click": closeModal }, translation.discard), /* @__PURE__ */ createElement("button", { class: "flex-1 width-100 primary", "on:click": saveAndClose }, translation.save, /* @__PURE__ */ createElement("span", { class: "icon" }, "save"))))); } // src/Views/Objects/objectListEntry.tsx diff --git a/dist/styles.css b/dist/styles.css index ac93a3ef..634da660 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -602,7 +602,7 @@ label.tile:has(> span.icon) { padding-left: var(--control-padding); } -label.tile:has(> span.icon) input { +label.tile:has(> span.icon) :is(input, select) { padding-left: 0px; } diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index 012f89f1..312c3ee6 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -29,11 +29,13 @@ export interface ChatMessage { export interface MessageObject { id: string; title: string; - dateLastEdited: Date; + isoDateLastEdited: string; contentVersions: MessageObjectContent[]; } -export interface MessageObjectContent {} +export interface MessageObjectContent { + isoDateVersionCreated: string; +} // chat export class Chat { @@ -163,7 +165,8 @@ export class Chat { this.encryptionKey.value ); const messageObject = JSON.parse(decryptedMessageObjectString); - if (messageObject.id && messageObject.title) return this.handleMessageObject(messageObject); + if (messageObject.id && messageObject.title) + return this.handleMessageObject(messageObject); } this.handleMessage({ @@ -296,7 +299,7 @@ export class Chat { return { id: React.UUID(), title, - dateLastEdited: new Date(), + isoDateLastEdited: new Date().toISOString(), contentVersions: [], }; }; @@ -341,7 +344,10 @@ export class Chat { messageObject: MessageObject ): MessageObjectContent => { const versions = messageObject.contentVersions; - if (versions.length == 0) return {}; + if (versions.length == 0) + return { + isoDateVersionCreated: new Date().toISOString(), + }; return versions[versions.length - 1]; }; diff --git a/src/Views/Objects/objectDetailModal.tsx b/src/Views/Objects/objectDetailModal.tsx index 58c9ca26..36d634c6 100644 --- a/src/Views/Objects/objectDetailModal.tsx +++ b/src/Views/Objects/objectDetailModal.tsx @@ -1,6 +1,10 @@ import * as React from "bloatless-react"; -import { Chat, MessageObject } from "../../Model/chatModel"; +import { + Chat, + MessageObject, + MessageObjectContent, +} from "../../Model/chatModel"; import { translation } from "../../translations"; @@ -9,13 +13,22 @@ export function ObjectDetailModal( messageObject: MessageObject, isPresented: React.State ) { + // state + const editingTitle = new React.State(messageObject.title); + const selectedMessageObject = new React.State( + chat.getLatestObjectContent(messageObject) + ); + + // methods function closeModal() { isPresented.value = false; } function saveAndClose() { messageObject.title = editingTitle.value; - chat.updateObject(messageObject.id, {}); + chat.updateObject(messageObject.id, { + isoDateVersionCreated: new Date().toISOString(), + }); closeModal(); } @@ -24,30 +37,46 @@ export function ObjectDetailModal( closeModal(); } - const editingTitle = new React.State(messageObject.title); - return (