Skip to content

Commit

Permalink
implement unread indicators for channel messages and fix issue where …
Browse files Browse the repository at this point in the history
…database might not be ready yet but packets are trying to save to db
  • Loading branch information
liamcottle committed Nov 20, 2024
1 parent 3b6793c commit d903c88
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 61 deletions.
75 changes: 75 additions & 0 deletions src/components/channels/ChannelListItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<div class="flex cursor-pointer p-2 bg-white hover:bg-gray-50">

<!-- channel info -->
<div class="my-auto mr-auto">
<div>{{ getChannelName(channel.index) }}</div>
<div class="text-sm text-gray-500">
<span v-if="channel.role === Protobuf.Channel.Channel_Role.PRIMARY">Primary Channel</span>
<span v-else-if="channel.role === Protobuf.Channel.Channel_Role.SECONDARY">Secondary Channel</span>
<span v-else-if="channel.role === Protobuf.Channel.Channel_Role.DISABLED">Disabled Channel</span>
<span v-else>Unknown Channel Role</span>
</div>
</div>

<!-- unread messages count -->
<div v-if="unreadMessagesCount > 0" class="my-auto mr-2">
<div class="inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 rounded-full shadow">
<span v-if="unreadMessagesCount >= 100">99</span>
<span>{{ unreadMessagesCount }}</span>
</div>
</div>

</div>
</template>

<script>
import Database from "../../js/Database.js";
import Connection from "../../js/Connection.js";
import ChannelUtils from "../../js/ChannelUtils.js";
import {
Protobuf,
} from "@meshtastic/js";
export default {
name: 'ChannelListItem',
props: {
channel: Object,
},
data() {
return {
unreadMessagesCount: 0,
channelMessagesReadStateSubscription: null,
};
},
mounted() {
Connection.addMessageListener(this.onMessage);
this.channelMessagesReadStateSubscription = Database.ChannelMessagesReadState.get(this.channel.index).$.subscribe(async (channelMessagesReadState) => {
await this.onChannelMessagesReadStateChange(channelMessagesReadState);
});
},
unmounted() {
Connection.removeMessageListener(this.onMessage);
this.channelMessagesReadStateSubscription?.unsubscribe();
},
methods: {
getChannelName: (channelId) => ChannelUtils.getChannelName(channelId),
async onMessage() {
const channelMessagesReadState = await Database.ChannelMessagesReadState.get(this.channel.index).exec();
await this.onChannelMessagesReadStateChange(channelMessagesReadState);
},
async updateUnreadMessagesCount(lastReadTimestamp) {
this.unreadMessagesCount = await Database.Message.getChannelMessagesUnreadCount(this.channel.index, lastReadTimestamp).exec();
},
async onChannelMessagesReadStateChange(channelMessagesReadState) {
const messagesLastReadTimestamp = channelMessagesReadState?.timestamp ?? 0;
await this.updateUnreadMessagesCount(messagesLastReadTimestamp);
},
},
computed: {
Protobuf() {
return Protobuf;
},
},
}
</script>
44 changes: 12 additions & 32 deletions src/components/channels/ChannelsList.vue
Original file line number Diff line number Diff line change
@@ -1,58 +1,38 @@
<template>
<div class="w-full">
<div v-for="channel of enabledChannels" @click="onChannelClick(channel)" class="flex cursor-pointer p-2 shadow border-l-2" :class="[ selectedChannelId === channel.index ? 'bg-gray-100 border-blue-500' : 'bg-white border-transparent hover:bg-gray-50 hover:border-gray-200']">

<!-- channel info -->
<div class="my-auto mr-auto">
<div>{{ getChannelName(channel.index) }}</div>
<div class="text-sm text-gray-500">
<span v-if="channel.role === Protobuf.Channel.Channel_Role.PRIMARY">Primary Channel</span>
<span v-else-if="channel.role === Protobuf.Channel.Channel_Role.SECONDARY">Secondary Channel</span>
<span v-else-if="channel.role === Protobuf.Channel.Channel_Role.DISABLED">Disabled Channel</span>
<span v-else>Unknown Channel Role</span>
</div>
</div>

<!-- security badge -->
<div class="my-auto">
<ChannelPskBadge :channel="channel"/>
</div>
<div class="flex flex-col h-full w-full overflow-hidden">

<!-- channels -->
<div class="h-full overflow-y-auto">
<ChannelListItem :key="channel.index" v-for="channel of enabledChannels" :channel="channel" @click="onChannelClick(channel)"/>
</div>

</div>

</template>

<script>
import {
Protobuf,
} from "@meshtastic/js";
import ChannelPskBadge from "./ChannelPskBadge.vue";
import ChannelUtils from "../../js/ChannelUtils.js";
import {Protobuf} from "@meshtastic/js";
import ChannelListItem from "./ChannelListItem.vue";
export default {
name: 'ChannelsList',
components: {ChannelPskBadge},
components: {
ChannelListItem,
},
emits: [
"channel-click",
],
props: {
selectedChannelId: Number,
channels: Array,
},
data() {
return {
};
},
methods: {
getChannelName: (channelId) => ChannelUtils.getChannelName(channelId),
onChannelClick(channel) {
this.$emit("channel-click", channel);
},
},
computed: {
Protobuf() {
return Protobuf;
},
enabledChannels() {
return this.channels.filter((channel) => {
return channel.role !== Protobuf.Channel.Channel_Role.DISABLED;
Expand Down
18 changes: 11 additions & 7 deletions src/components/messages/MessageViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -274,17 +274,21 @@ export default {
});
},
updateMessagesLastReadAt() {
if(this.type === "node"){
// do nothing if route is not active
if(!this.isActive){
return;
}
// do nothing if route is not active
if(!this.isActive){
return;
}
// update last read at
// check what type of messages we are viewing
if(this.type === "node"){
// update last read at for node messages
Database.NodeMessagesReadState.touch(this.nodeId);
} else if(this.type === "channel") {
// update last read at for channel messages
Database.ChannelMessagesReadState.touch(this.channelId);
}
},
},
computed: {
Expand Down
3 changes: 3 additions & 0 deletions src/components/pages/MainPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export default {
},
},
computed: {
GlobalState() {
return GlobalState;
},
isConnected() {
return GlobalState.isConnected;
},
Expand Down
66 changes: 44 additions & 22 deletions src/js/Connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,42 @@ class Connection {

static async setupConnectionListeners(connection) {

// weird way to allow us to lock all other callbacks from doing anything, until the database is ready...
// maybe we should use some sort of lock or mutex etc. basically, onMyNodeInfo is called with our node info
// we use this to create a new database instance that is unique based on the node id.
// initDatabase is async, which means all the other callbacks such as onChannelPacket are able to fire before the database is ready
// this means when we try to access the database when it isn't ready yet, we get fun errors...
// so we need to force the callbacks to wait until the database is ready
// we will just resolve this promise when the database is ready, and all the packet callbacks should be set to await it
var onDatabaseReady = null;
const databaseToBeReady = new Promise((resolve) => {
onDatabaseReady = resolve;
});

// listen for device status changes
connection.events.onDeviceStatus.subscribe((deviceStatus) => {

console.log("onDeviceStatus", deviceStatus);
GlobalState.deviceStatus = deviceStatus;

// check if device is now disconnected
if(deviceStatus === Types.DeviceStatusEnum.DeviceDisconnected){
this.disconnect();
}

});

// listen for our node number
connection.events.onMyNodeInfo.subscribe(async (data) => {
console.log("onMyNodeInfo", data);
GlobalState.myNodeId = data.myNodeNum;
await Database.initDatabase(GlobalState.myNodeId);
onDatabaseReady();
});

// listen for lora config
connection.events.onConfigPacket.subscribe((configPacket) => {
connection.events.onConfigPacket.subscribe(async (configPacket) => {
await databaseToBeReady;
if(configPacket.payloadVariant.case.toString() === "lora"){
GlobalState.loraConfig = configPacket.payloadVariant.value;
}
Expand All @@ -161,6 +189,8 @@ class Connection {
// we use this for some packets that don't have their own event listener
connection.events.onFromRadio.subscribe(async (data) => {

await databaseToBeReady;

// handle packets
// we are doing this to get error info for a request id as it's not provided in the onRoutingPacket event
if(data.payloadVariant.case.toString() === "packet") {
Expand Down Expand Up @@ -188,16 +218,11 @@ class Connection {

});

// listen for our node number
connection.events.onMyNodeInfo.subscribe(async (data) => {
console.log("onMyNodeInfo", data);
GlobalState.myNodeId = data.myNodeNum;
await Database.initDatabase(GlobalState.myNodeId);
});

// listen for node info
GlobalState.nodesById = {};
connection.events.onNodeInfoPacket.subscribe((data) => {
connection.events.onNodeInfoPacket.subscribe(async (data) => {

await databaseToBeReady;

console.log("onNodeInfoPacket", data);

Expand All @@ -212,7 +237,9 @@ class Connection {
});

// listen for mesh packets
connection.events.onMeshPacket.subscribe((data) => {
connection.events.onMeshPacket.subscribe(async (data) => {

await databaseToBeReady;

console.log("onMeshPacket", data);

Expand All @@ -238,7 +265,9 @@ class Connection {
});

// listen for user info
connection.events.onUserPacket.subscribe((data) => {
connection.events.onUserPacket.subscribe(async (data) => {

await databaseToBeReady;

console.log("onUserPacket", data);

Expand All @@ -261,13 +290,15 @@ class Connection {

// listen for channels
GlobalState.channelsByIndex = {};
connection.events.onChannelPacket.subscribe((data) => {
connection.events.onChannelPacket.subscribe(async (data) => {
await databaseToBeReady;
console.log("onChannelPacket", data);
GlobalState.channelsByIndex[data.index] = data;
});

// listen for new messages
connection.events.onMessagePacket.subscribe(async (data) => {
await databaseToBeReady;
console.log("onMessagePacket", data);
await Database.Message.insert(data);
for(const messageListener of this.messageListeners){
Expand All @@ -277,18 +308,9 @@ class Connection {
}
});

// listen for device status changes
connection.events.onDeviceStatus.subscribe((data) => {

// check if device is now disconnected
if(data === Types.DeviceStatusEnum.DeviceDisconnected){
this.disconnect();
}

});

// listen for trace routes
connection.events.onTraceRoutePacket.subscribe(async (data) => {
await databaseToBeReady;
console.log("onTraceRoutePacket", data);
await Database.TraceRoute.insert(data);
for(const traceRouteListener of this.traceRouteListeners){
Expand Down
Loading

0 comments on commit d903c88

Please sign in to comment.