1
0
mirror of https://github.com/craigerl/aprsd.git synced 2025-08-03 05:52:26 -04:00

Webchat: Added tab notifications and raw packet

This patch adds an auto mouseover hover popover for displaying
the raw APRS packet.

This patch also adds the notification counter for an unselected tab.
This commit is contained in:
Hemna 2023-09-21 16:29:15 -04:00
parent f151ae4348
commit 9635893934
4 changed files with 88 additions and 58 deletions

View File

@ -23,7 +23,7 @@ from aprsd import cli_helper, client, conf, packets, stats, threads, utils
from aprsd.log import rich as aprsd_logging from aprsd.log import rich as aprsd_logging
from aprsd.main import cli from aprsd.main import cli
from aprsd.threads import rx, tx from aprsd.threads import rx, tx
from aprsd.utils import objectstore, trace from aprsd.utils import trace
CONF = cfg.CONF CONF = cfg.CONF
@ -57,7 +57,9 @@ def signal_handler(sig, frame):
signal.signal(signal.SIGTERM, sys.exit(0)) signal.signal(signal.SIGTERM, sys.exit(0))
class SentMessages(objectstore.ObjectStoreMixin): #class SentMessages(objectstore.ObjectStoreMixin):
class SentMessages:
_instance = None _instance = None
lock = threading.Lock() lock = threading.Lock()
@ -74,25 +76,7 @@ class SentMessages(objectstore.ObjectStoreMixin):
@wrapt.synchronized(lock) @wrapt.synchronized(lock)
def add(self, msg): def add(self, msg):
self.data[msg.msgNo] = self.create(msg.msgNo) self.data[msg.msgNo] = msg.__dict__
self.data[msg.msgNo]["from"] = msg.from_call
self.data[msg.msgNo]["to"] = msg.to_call
self.data[msg.msgNo]["message"] = msg.message_text.rstrip("\n")
self.data[msg.msgNo]["raw"] = msg.message_text.rstrip("\n")
def create(self, id):
return {
"id": id,
"ts": time.time(),
"ack": False,
"from": None,
"to": None,
"raw": None,
"message": None,
"status": None,
"last_update": None,
"reply": None,
}
@wrapt.synchronized(lock) @wrapt.synchronized(lock)
def __len__(self): def __len__(self):
@ -174,7 +158,7 @@ class WebChatProcessPacketThread(rx.APRSDProcessPacketThread):
"reply": None, "reply": None,
} }
self.socketio.emit( self.socketio.emit(
"new", msg, "new", packet.__dict__,
namespace="/sendmsg", namespace="/sendmsg",
) )
@ -317,6 +301,7 @@ class SendMessageNamespace(Namespace):
to_call=data["to"].upper(), to_call=data["to"].upper(),
message_text=data["message"], message_text=data["message"],
) )
pkt.prepare()
self.msg = pkt self.msg = pkt
msgs = SentMessages() msgs = SentMessages()
msgs.add(pkt) msgs.add(pkt)

View File

@ -93,3 +93,19 @@ input[type=search]::-webkit-search-cancel-button {
.bubble-arrow.alt:after { .bubble-arrow.alt:after {
transform: rotate(45deg) scaleY(-1); transform: rotate(45deg) scaleY(-1);
} }
.popover {
max-width: 400px;
}
.popover-header {
font-size: 8pt;
max-width: 400px;
padding: 5px;
background-color: #ee;
}
.popover-body {
white-space: pre-line;
max-width: 400px;
padding: 5px;
}

View File

@ -2,6 +2,7 @@ var cleared = false;
var callsign_list = {}; var callsign_list = {};
var message_list = {}; var message_list = {};
var from_msg_list = {}; var from_msg_list = {};
var selected_tab_callsign = null;
const socket = io("/sendmsg"); const socket = io("/sendmsg");
MSG_TYPE_TX = "tx"; MSG_TYPE_TX = "tx";
@ -30,6 +31,8 @@ function init_chat() {
}); });
socket.on("sent", function(msg) { socket.on("sent", function(msg) {
console.log("SENT: ");
console.log(msg);
if (cleared === false) { if (cleared === false) {
console.log("CLEARING #msgsTabsDiv"); console.log("CLEARING #msgsTabsDiv");
var msgsdiv = $("#msgsTabsDiv"); var msgsdiv = $("#msgsTabsDiv");
@ -42,6 +45,8 @@ function init_chat() {
socket.on("ack", function(msg) { socket.on("ack", function(msg) {
msg["type"] = MSG_TYPE_ACK; msg["type"] = MSG_TYPE_ACK;
console.log("ACK MESSAGE")
console.log(msg)
ack_msg(msg); ack_msg(msg);
}); });
@ -51,6 +56,8 @@ function init_chat() {
msgsdiv.html('') msgsdiv.html('')
cleared = true; cleared = true;
} }
console.log("NEW MESSAGE")
console.log(msg)
msg["type"] = MSG_TYPE_RX; msg["type"] = MSG_TYPE_RX;
from_msg(msg); from_msg(msg);
}); });
@ -92,6 +99,11 @@ function tab_li_string(callsign, id=false) {
return tab_string(callsign,id)+"Li"; return tab_string(callsign,id)+"Li";
} }
function tab_notification_id(callsign, id=false) {
// The ID of the span that contains the notification count
return tab_string(callsign, id)+"notify";
}
function tab_content_name(callsign, id=false) { function tab_content_name(callsign, id=false) {
return tab_string(callsign, id)+"Content"; return tab_string(callsign, id)+"Content";
} }
@ -114,7 +126,7 @@ function callsign_tab(callsign) {
function message_ts_id(msg) { function message_ts_id(msg) {
//Create a 'id' from the message timestamp //Create a 'id' from the message timestamp
ts_str = msg["ts"].toString(); ts_str = msg["timestamp"].toString();
ts = ts_str.split(".")[0]*1000; ts = ts_str.split(".")[0]*1000;
id = ts_str.split('.')[0]; id = ts_str.split('.')[0];
return {'timestamp': ts, 'id': id}; return {'timestamp': ts, 'id': id};
@ -148,8 +160,8 @@ function init_messages() {
if (message_list == null) { if (message_list == null) {
message_list = {}; message_list = {};
} }
console.log(callsign_list); //console.log(callsign_list);
console.log(message_list); //console.log(message_list);
// Now loop through each callsign and add the tabs // Now loop through each callsign and add the tabs
first_callsign = null; first_callsign = null;
@ -177,7 +189,8 @@ function init_messages() {
ack_id = info['ack_id']; ack_id = info['ack_id'];
acked = msg['ack']; acked = msg['ack'];
} }
msg_html = create_message_html(d, t, msg['from'], msg['to'], msg['message'], ack_id, msg, acked); msg_html = create_message_html(d, t, msg['from_call'], msg['to_call'],
msg['message_text'], ack_id, msg, acked);
append_message_html(callsign, msg_html, new_callsign); append_message_html(callsign, msg_html, new_callsign);
new_callsign = false; new_callsign = false;
} }
@ -204,20 +217,17 @@ function scroll_main_content(callsign=false) {
c_scroll_height = c_div.prop('scrollHeight'); c_scroll_height = c_div.prop('scrollHeight');
//console.log("callsign height " + c_height + " scrollHeight " + c_scroll_height); //console.log("callsign height " + c_height + " scrollHeight " + c_scroll_height);
if (c_height === undefined) { if (c_height === undefined) {
console.log("c_height is undefined");
return false; return false;
} }
if (c_height > clientHeight) { if (c_height > clientHeight) {
wc.animate({ scrollTop: c_scroll_height }, 500); wc.animate({ scrollTop: c_scroll_height }, 500);
} else { } else {
console.log("scroll to 0 " + callsign)
wc.animate({ scrollTop: 0 }, 500); wc.animate({ scrollTop: 0 }, 500);
} }
} else { } else {
if (scrollHeight > clientHeight) { if (scrollHeight > clientHeight) {
wc.animate({ scrollTop: wc.prop('scrollHeight') }, 500); wc.animate({ scrollTop: wc.prop('scrollHeight') }, 500);
} else { } else {
console.log("scroll to 0 " + callsign)
wc.animate({ scrollTop: 0 }, 500); wc.animate({ scrollTop: 0 }, 500);
} }
} }
@ -228,6 +238,7 @@ function create_callsign_tab(callsign, active=false) {
var callsignTabs = $("#msgsTabList"); var callsignTabs = $("#msgsTabList");
tab_id = tab_string(callsign); tab_id = tab_string(callsign);
tab_id_li = tab_li_string(callsign); tab_id_li = tab_li_string(callsign);
tab_notify_id = tab_notification_id(callsign);
tab_content = tab_content_name(callsign); tab_content = tab_content_name(callsign);
if (active) { if (active) {
active_str = "active"; active_str = "active";
@ -236,8 +247,10 @@ function create_callsign_tab(callsign, active=false) {
} }
item_html = '<li class="nav-item" role="presentation" callsign="'+callsign+'" id="'+tab_id_li+'">'; item_html = '<li class="nav-item" role="presentation" callsign="'+callsign+'" id="'+tab_id_li+'">';
item_html += '<button onClick="callsign_select(\''+callsign+'\');" callsign="'+callsign+'" class="nav-link '+active_str+'" id="'+tab_id+'" data-bs-toggle="tab" data-bs-target="#'+tab_content+'" type="button" role="tab" aria-controls="'+callsign+'" aria-selected="true">'; //item_html += '<button onClick="callsign_select(\''+callsign+'\');" callsign="'+callsign+'" class="nav-link '+active_str+'" id="'+tab_id+'" data-bs-toggle="tab" data-bs-target="#'+tab_content+'" type="button" role="tab" aria-controls="'+callsign+'" aria-selected="true">';
item_html += '<button onClick="callsign_select(\''+callsign+'\');" callsign="'+callsign+'" class="nav-link position-relative '+active_str+'" id="'+tab_id+'" data-bs-toggle="tab" data-bs-target="#'+tab_content+'" type="button" role="tab" aria-controls="'+callsign+'" aria-selected="true">';
item_html += callsign+'&nbsp;&nbsp;'; item_html += callsign+'&nbsp;&nbsp;';
item_html += '<span id="'+tab_notify_id+'" class="position-absolute top-0 start-80 translate-middle badge bg-danger border border-light rounded-pill visually-hidden">0</span>';
item_html += '<span onclick="delete_tab(\''+callsign+'\');">×</span>'; item_html += '<span onclick="delete_tab(\''+callsign+'\');">×</span>';
item_html += '</button></li>' item_html += '</button></li>'
callsignTabs.append(item_html); callsignTabs.append(item_html);
@ -303,6 +316,15 @@ function append_message(callsign, msg, msg_html) {
ts_id = message_ts_id(msg); ts_id = message_ts_id(msg);
id = ts_id['id'] id = ts_id['id']
message_list[callsign][id] = msg; message_list[callsign][id] = msg;
if (selected_tab_callsign != callsign) {
// We need to update the notification for the tab
tab_notify_id = tab_notification_id(callsign, true);
// get the current count of notifications
count = parseInt($(tab_notify_id).text());
count += 1;
$(tab_notify_id).text(count);
$(tab_notify_id).removeClass('visually-hidden');
}
// Find the right div to place the html // Find the right div to place the html
new_callsign = add_callsign(callsign); new_callsign = add_callsign(callsign);
@ -310,8 +332,8 @@ function append_message(callsign, msg, msg_html) {
if (new_callsign) { if (new_callsign) {
//Now click the tab //Now click the tab
callsign_tab_id = callsign_tab(callsign); callsign_tab_id = callsign_tab(callsign);
$(callsign_tab_id).click(); //$(callsign_tab_id).click();
callsign_select(callsign); //callsign_select(callsign);
} }
} }
@ -338,35 +360,42 @@ function create_message_html(date, time, from, to, message, ack_id, msg, acked=f
alt = "" alt = ""
} }
bubble_class = "bubble" + alt bubble_class = "bubble" + alt + " text-nowrap"
bubble_name_class = "bubble-name" + alt bubble_name_class = "bubble-name" + alt
date_str = date + " " + time; date_str = date + " " + time;
sane_date_str = date_str.replace(/ /g,"").replaceAll("/","").replaceAll(":","");
msg_html = '<div class="bubble-row'+alt+'">'; msg_html = '<div class="bubble-row'+alt+'">';
msg_html += '<div class="'+ bubble_class + '">'; msg_html += '<div class="'+ bubble_class + '" data-bs-toggle="popover" data-bs-content="'+msg['raw']+'">';
msg_html += '<div class="bubble-text">'; msg_html += '<div class="bubble-text">';
msg_html += '<p class="'+ bubble_name_class +'">'+from+'&nbsp;&nbsp;'; msg_html += '<p class="'+ bubble_name_class +'">'+from+'&nbsp;&nbsp;';
msg_html += '<span class="bubble-timestamp">'+date_str+'</span>'; msg_html += '<span class="bubble-timestamp">'+date_str+'</span>';
if (ack_id) { if (ack_id) {
if (acked) { if (acked) {
msg_html += '<span class="material-symbols-rounded" id="' + ack_id + '">thumb_up</span>'; msg_html += '<span class="material-symbols-rounded md-10" id="' + ack_id + '">thumb_up</span>';
} else { } else {
msg_html += '<span class="material-symbols-rounded" id="' + ack_id + '">thumb_down</span>'; msg_html += '<span class="material-symbols-rounded md-10" id="' + ack_id + '">thumb_down</span>';
} }
} }
msg_html += "</p>"; msg_html += "</p>";
bubble_msg_class = "bubble-message" bubble_msg_class = "bubble-message"
if (ack_id) { if (ack_id) {
bubble_arrow_class = "bubble-arrow alt" bubble_arrow_class = "bubble-arrow alt"
popover_placement = "left"
} else { } else {
bubble_arrow_class = "bubble-arrow" bubble_arrow_class = "bubble-arrow"
popover_placement = "right"
} }
msg_html += '<p class="' +bubble_msg_class+ '">'+message+'</p>'; msg_html += '<p class="' +bubble_msg_class+ '">'+message+'</p>';
msg_html += '<div class="'+ bubble_arrow_class + '"></div>'; msg_html += '<div class="'+ bubble_arrow_class + '"></div>';
msg_html += "</div></div></div>"; msg_html += "</div></div></div>";
return msg_html popover_html = '\n<script>$(function () {$(\'[data-bs-toggle="popover"]\').popover('
popover_html += '{title: "APRS Raw Packet", html: false, trigger: \'hover\', placement: \''+popover_placement+'\'});})';
popover_html += '</script>'
return msg_html+popover_html
} }
function flash_message(msg) { function flash_message(msg) {
@ -383,35 +412,34 @@ function sent_msg(msg) {
d = info['date']; d = info['date'];
ack_id = info['ack_id']; ack_id = info['ack_id'];
msg_html = create_message_html(d, t, msg['from'], msg['to'], msg['message'], ack_id, msg, false); msg_html = create_message_html(d, t, msg['from_call'], msg['to_call'], msg['message_text'], ack_id, msg, false);
append_message(msg['to'], msg, msg_html); append_message(msg['to_call'], msg, msg_html);
save_data(); save_data();
scroll_main_content(msg['from']); scroll_main_content(msg['from_call']);
} }
function from_msg(msg) { function from_msg(msg) {
if (!from_msg_list.hasOwnProperty(msg.from)) { if (!from_msg_list.hasOwnProperty(msg.from)) {
from_msg_list[msg.from] = new Array(); from_msg_list[msg.from_call] = new Array();
} }
if (msg.id in from_msg_list[msg.from]) { if (msg.id in from_msg_list[msg.from_call]) {
// We already have this message // We already have this message
console.log("We already have this message " + msg); console.log("We already have this message " + msg);
// Do some flashy thing? // Do some flashy thing?
flash_message(msg); flash_message(msg);
return false return false
} else { } else {
console.log("Adding message " + msg.id + " to " + msg.from); console.log("Adding message " + msg.msgNo + " to " + msg.from_call);
from_msg_list[msg.from][msg.id] = msg from_msg_list[msg.from_call][msg.msgNo] = msg
} }
info = time_ack_from_msg(msg); info = time_ack_from_msg(msg);
t = info['time']; t = info['time'];
d = info['date']; d = info['date'];
ack_id = info['ack_id']; ack_id = info['ack_id'];
from = msg['from'] from = msg['from_call']
msg_html = create_message_html(d, t, from, false, msg['message'], false, msg, false); msg_html = create_message_html(d, t, from, false, msg['message_text'], false, msg, false);
append_message(from, msg, msg_html); append_message(from, msg, msg_html);
save_data(); save_data();
scroll_main_content(from); scroll_main_content(from);
@ -419,14 +447,11 @@ function from_msg(msg) {
function ack_msg(msg) { function ack_msg(msg) {
// Acknowledge a message // Acknowledge a message
console.log("ack_msg ");
// We have an existing entry // We have an existing entry
ts_id = message_ts_id(msg); ts_id = message_ts_id(msg);
console.log(ts_id)
id = ts_id['id']; id = ts_id['id'];
//Mark the message as acked //Mark the message as acked
callsign = msg['to']; callsign = msg['to_call'];
// Ensure the message_list has this callsign // Ensure the message_list has this callsign
if (!message_list.hasOwnProperty(callsign)) { if (!message_list.hasOwnProperty(callsign)) {
console.log("No message_list for " + callsign); console.log("No message_list for " + callsign);
@ -456,8 +481,11 @@ function ack_msg(msg) {
} }
function callsign_select(callsign) { function callsign_select(callsign) {
console.log("callsign_select " + callsign); var tocall = $("#to_call");
var tocall = $("#to_call"); tocall.val(callsign);
tocall.val(callsign); scroll_main_content(callsign);
scroll_main_content(callsign); selected_tab_callsign = callsign;
tab_notify_id = tab_notification_id(callsign, true);
$(tab_notify_id).addClass('visually-hidden');
$(tab_notify_id).text(0);
} }

View File

@ -2,7 +2,7 @@
<head> <head>
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"> content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="/static/js/upstream/jquery.toast.js"></script> <script src="/static/js/upstream/jquery.toast.js"></script>
<!--<script src="/static/js/upstream/jquery.min.js"></script> --> <!--<script src="/static/js/upstream/jquery.min.js"></script> -->
<!-- <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css"> --> <!-- <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css"> -->
@ -36,8 +36,8 @@
var latitude = parseFloat('{{ latitude|safe }}'); var latitude = parseFloat('{{ latitude|safe }}');
var longitude = parseFloat('{{ longitude|safe }}'); var longitude = parseFloat('{{ longitude|safe }}');
var memory_chart = null var memory_chart = null;
var message_chart = null var message_chart = null;
$(document).ready(function() { $(document).ready(function() {
console.log(initial_stats); console.log(initial_stats);
@ -64,6 +64,7 @@
var callsign = tab.attr("callsign"); var callsign = tab.attr("callsign");
var to_call = $('#to_call'); var to_call = $('#to_call');
to_call.val(callsign); to_call.val(callsign);
selected_tab_callsign = callsign;
}); });
}); });