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.
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/PKG-INFO +1 -1
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/pyproject.toml +1 -1
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/server_browser.py +28 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/static/index.html +37 -2
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/LICENSE +0 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/README.md +0 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/client.py +0 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/interactive_test.py +0 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/saved_plots/.gitignore +0 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/server.py +0 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/server_browser_gui.py +0 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/static/uPlot.iife.min.js +0 -0
- {better_rtplot-0.4.3 → better_rtplot-0.4.4}/rtplot/static/uPlot.min.css +0 -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-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|