better-rtplot 0.4.3__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-rtplot
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary:
5
5
  License: GPL V3.0
6
6
  Author: jmontp
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "better-rtplot"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  description = ""
5
5
  authors = ["jmontp <jmontp@umich.edu>"]
6
6
  license = "GPL V3.0"
@@ -956,6 +956,29 @@ async def rename_tab(tab_id: str, new_name: str):
956
956
  await broadcast_tab(tab_id)
957
957
 
958
958
 
959
+ async def reconnect_tab(tab_id: str):
960
+ """Close and reopen ``tab_id``'s sockets, restart its receiver task.
961
+
962
+ The tab's buffer and plot config are preserved — if the sender on
963
+ the other side is still running the same stream we avoid a visible
964
+ flash to empty. A new config from the sender will replace the state
965
+ via the normal RECEIVED_PLOT_UPDATE path.
966
+ """
967
+ t = tabs.get(tab_id)
968
+ if t is None:
969
+ return
970
+ await _cancel_task(t.receiver_task)
971
+ # _open_tab_sockets closes first, then reopens. Status gets set to
972
+ # "idle" on success or "error" + a message on bind/connect failure.
973
+ _open_tab_sockets(t)
974
+ # Reset the data-rate estimate so a stale Hz doesn't mislead the
975
+ # resources panel until new samples arrive.
976
+ t.data_rate_hz = 0.0
977
+ t._last_rx_ts = 0.0
978
+ t.receiver_task = asyncio.create_task(zmq_receiver(t))
979
+ await broadcast_tab(tab_id)
980
+
981
+
959
982
  ###############################
960
983
  # HTTP / WebSocket handlers #
961
984
  ###############################
@@ -1049,6 +1072,11 @@ async def handle_ws(request):
1049
1072
  if tid and tid != BIND_ME_ID:
1050
1073
  await delete_tab(tid)
1051
1074
 
1075
+ elif ptype == "tab_reconnect":
1076
+ tid = payload.get("id")
1077
+ if tid:
1078
+ await reconnect_tab(tid)
1079
+
1052
1080
  elif ptype == "control_button":
1053
1081
  btn_id = payload.get("id")
1054
1082
  tid = ws_tab.get(ws, BIND_ME_ID)
@@ -18,8 +18,16 @@
18
18
  .tab .tab-dot.error { background: #e05252; }
19
19
  .tab .tab-dot.connecting { background: #e8b13a; animation: pulse 1s ease-in-out infinite alternate; }
20
20
  @keyframes pulse { from { opacity: 0.5; } to { opacity: 1.0; } }
21
- .tab .tab-name { max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
21
+ .tab .tab-labels { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
22
+ .tab .tab-name { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
23
+ .tab .tab-endpoint { font-family: monospace; font-size: calc(10px * var(--ui-scale)); color: #888; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
24
+ .tab.active .tab-endpoint { color: #5a6880; }
22
25
  .tab .tab-name-input { border: 1px solid #2a5db0; border-radius: 3px; padding: 1px 4px; font: inherit; color: #111; width: 160px; }
26
+ .tab .tab-reconnect { font-size: calc(13px * var(--ui-scale)); color: #888; cursor: pointer; padding: 1px 5px; border-radius: 2px; opacity: 0.0; line-height: 1; }
27
+ .tab:hover .tab-reconnect, .tab .tab-dot.error ~ .tab-reconnect { opacity: 1.0; }
28
+ .tab .tab-reconnect:hover { background: #e0e6f0; color: #2a5db0; }
29
+ .tab .tab-reconnect.spinning { animation: tab-spin 0.6s linear; }
30
+ @keyframes tab-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
23
31
  .tab .tab-rename { font-size: calc(11px * var(--ui-scale)); color: #888; cursor: pointer; padding: 1px 4px; border-radius: 2px; opacity: 0.0; }
24
32
  .tab:hover .tab-rename { opacity: 1.0; }
25
33
  .tab .tab-rename:hover { background: #e0e0e0; color: #333; }
@@ -916,10 +924,37 @@
916
924
  if (t.error) dot.title = t.error;
917
925
  el.appendChild(dot);
918
926
 
927
+ // Stacked labels: name on top, endpoint on a muted subline so
928
+ // users can see at a glance which tab is wired to which device.
929
+ const labels = document.createElement('div');
930
+ labels.className = 'tab-labels';
919
931
  const name = document.createElement('span');
920
932
  name.className = 'tab-name';
921
933
  name.textContent = t.name || id;
922
- el.appendChild(name);
934
+ labels.appendChild(name);
935
+ const ep = document.createElement('span');
936
+ ep.className = 'tab-endpoint';
937
+ ep.textContent = (t.mode === 'bind')
938
+ ? `listening on ${t.endpoint || '*:5555'}`
939
+ : `connected to ${t.endpoint || '?'}`;
940
+ labels.appendChild(ep);
941
+ el.appendChild(labels);
942
+
943
+ const reconnect = document.createElement('span');
944
+ reconnect.className = 'tab-reconnect';
945
+ reconnect.textContent = '\u21BB';
946
+ reconnect.title = 'Reconnect';
947
+ reconnect.addEventListener('click', (e) => {
948
+ e.stopPropagation();
949
+ sendCtrl({ type: 'tab_reconnect', id: id });
950
+ // Visual feedback: the icon spins once so users see the click
951
+ // registered even when the server returns the same status
952
+ // (e.g. reconnect succeeded, no dot color change).
953
+ reconnect.classList.remove('spinning');
954
+ void reconnect.offsetWidth; // force reflow to restart animation
955
+ reconnect.classList.add('spinning');
956
+ });
957
+ el.appendChild(reconnect);
923
958
 
924
959
  if (id !== 'bind_me') {
925
960
  const ren = document.createElement('span');
File without changes
File without changes