diff --git a/src/components/messages/MessageViewer.vue b/src/components/messages/MessageViewer.vue index f9e0336..d15865c 100644 --- a/src/components/messages/MessageViewer.vue +++ b/src/components/messages/MessageViewer.vue @@ -124,6 +124,7 @@ export default { data() { return { + isActive: false, messages: [], messagesSubscription: null, @@ -148,10 +149,15 @@ export default { }, activated() { + this.isActive = true; + // update read state when coming back to message viewer - Database.NodeMessagesReadState.touch(this.nodeId); + this.updateMessagesLastReadAt(); }, + deactivated() { + this.isActive = false; + }, methods: { async sendMessage() { @@ -229,7 +235,7 @@ export default { this.scrollMessagesToBottom(); // update read state since we auto scrolled to bottom of new messages - Database.NodeMessagesReadState.touch(this.nodeId); + this.updateMessagesLastReadAt(); } @@ -255,7 +261,7 @@ export default { // update read state since we scrolled to bottom if(isAtBottom){ - Database.NodeMessagesReadState.touch(this.nodeId); + this.updateMessagesLastReadAt(); } }, @@ -265,6 +271,17 @@ export default { container.scrollTop = container.scrollHeight; }); }, + updateMessagesLastReadAt() { + + // do nothing if route is not active + if(!this.isActive){ + return; + } + + // update last read at + Database.NodeMessagesReadState.touch(this.nodeId); + + }, }, computed: { Protobuf() { diff --git a/src/components/nodes/NodeListItem.vue b/src/components/nodes/NodeListItem.vue index 15d280b..97921d2 100644 --- a/src/components/nodes/NodeListItem.vue +++ b/src/components/nodes/NodeListItem.vue @@ -44,6 +44,14 @@ + +
+
+ 99 + {{ unreadMessagesCount }} +
+
+
@@ -110,6 +118,8 @@ import NodeDropDownMenu from "./NodeDropDownMenu.vue"; import GlobalState from "../../js/GlobalState.js"; import TimeUtils from "../../js/TimeUtils.js"; import moment from "moment"; +import Database from "../../js/Database.js"; +import Connection from "../../js/Connection.js"; export default { name: 'NodeListItem', @@ -120,6 +130,22 @@ export default { props: { node: Object, }, + data() { + return { + unreadMessagesCount: 0, + nodeMessagesReadStateSubscription: null, + }; + }, + mounted() { + Connection.addMessageListener(this.onMessage); + this.nodeMessagesReadStateSubscription = Database.NodeMessagesReadState.get(this.node.num).$.subscribe(async (nodeMessagesReadState) => { + await this.onNodeMessagesReadStateChange(nodeMessagesReadState); + }); + }, + unmounted() { + Connection.removeMessageListener(this.onMessage); + this.nodeMessagesReadStateSubscription?.unsubscribe(); + }, methods: { getNodeLongName: (nodeId) => NodeUtils.getNodeLongName(nodeId), formatUnixSecondsAgo(unixSeconds) { @@ -132,6 +158,17 @@ export default { return "Unknown"; }, + async onMessage() { + const nodeMessagesReadState = await Database.NodeMessagesReadState.get(this.node.num).exec(); + await this.onNodeMessagesReadStateChange(nodeMessagesReadState); + }, + async updateUnreadMessagesCount(lastReadTimestamp) { + this.unreadMessagesCount = await Database.Message.getNodeMessagesUnreadCount(this.node.num, lastReadTimestamp).exec(); + }, + async onNodeMessagesReadStateChange(nodeMessagesReadState) { + const messagesLastReadTimestamp = nodeMessagesReadState?.timestamp ?? 0; + await this.updateUnreadMessagesCount(messagesLastReadTimestamp); + }, }, computed: { GlobalState() { diff --git a/src/js/Connection.js b/src/js/Connection.js index 3743128..3bf5463 100644 --- a/src/js/Connection.js +++ b/src/js/Connection.js @@ -5,6 +5,7 @@ import Database from "./Database.js"; class Connection { static clientNotificationListeners = []; + static messageListeners = []; static traceRouteListeners = []; static addClientNotificationListener(listener) { @@ -17,6 +18,16 @@ class Connection { }); } + static addMessageListener(listener) { + this.messageListeners.push(listener); + } + + static removeMessageListener(listenerToRemove) { + this.messageListeners = this.messageListeners.filter((listener) => { + return listener !== listenerToRemove; + }); + } + static addTraceRouteListener(listener) { this.traceRouteListeners.push(listener); } @@ -231,6 +242,11 @@ class Connection { connection.events.onMessagePacket.subscribe(async (data) => { console.log("onMessagePacket", data); await Database.Message.insert(data); + for(const messageListener of this.messageListeners){ + try { + messageListener(data); + } catch(e){} + } }); // listen for device status changes diff --git a/src/js/Database.js b/src/js/Database.js index 6aff864..7b83ba8 100644 --- a/src/js/Database.js +++ b/src/js/Database.js @@ -23,6 +23,7 @@ async function initDatabase(nodeId) { database = await createRxDatabase({ name: `meshtxt_db_node_${nodeId}`, storage: getRxStorageDexie(), + allowSlowCount: true, // fixme: figure out why rxdb complains about indexes when they existed during testing... }); // add database schemas @@ -265,6 +266,23 @@ class Message { }); } + // get unread direct messages count for the provided node id + static getNodeMessagesUnreadCount(nodeId, messagesLastReadTimestamp) { + return database.messages.count({ + selector: { + timestamp: { + $gt: messagesLastReadTimestamp, + }, + from: { + $eq: nodeId, + }, + to: { + $eq: GlobalState.myNodeId, + }, + }, + }); + } + } class TraceRoute { @@ -340,6 +358,17 @@ class NodeMessagesReadState { }); } + // get the read state of messages for the provided node id + static get(nodeId) { + return database.node_messages_read_state.findOne({ + selector: { + id: { + $eq: nodeId.toString(), + }, + }, + }); + } + } export default {