better-rtplot 0.4.9__tar.gz → 0.4.10__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.9 → better_rtplot-0.4.10}/PKG-INFO +1 -1
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/pyproject.toml +1 -1
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/server_browser.py +188 -5
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/index.html +15 -1
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/LICENSE +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/README.md +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/client.py +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/interactive_test.py +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/saved_plots/.gitignore +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/server.py +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/server_browser_gui.py +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/auto-render.min.js +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/katex.min.css +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/katex.min.js +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/uPlot.iife.min.js +0 -0
- {better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/uPlot.min.css +0 -0
|
@@ -23,6 +23,8 @@ import asyncio
|
|
|
23
23
|
import dataclasses
|
|
24
24
|
import json
|
|
25
25
|
import os
|
|
26
|
+
import socket
|
|
27
|
+
import subprocess
|
|
26
28
|
import struct
|
|
27
29
|
import sys
|
|
28
30
|
import time
|
|
@@ -36,6 +38,7 @@ from typing import Optional
|
|
|
36
38
|
import numpy as np
|
|
37
39
|
import zmq
|
|
38
40
|
import zmq.asyncio
|
|
41
|
+
from zmq.utils.monitor import recv_monitor_message
|
|
39
42
|
|
|
40
43
|
# pyzmq's asyncio integration needs event_loop.add_reader(), which the
|
|
41
44
|
# Windows-default ProactorEventLoop (Python 3.8+) does not implement.
|
|
@@ -206,6 +209,7 @@ MSG_DELTA = 1
|
|
|
206
209
|
|
|
207
210
|
ZMQ_DEFAULT_PORT = 5555
|
|
208
211
|
ZMQ_CONTROL_PORT = ZMQ_DEFAULT_PORT + 1
|
|
212
|
+
TCP_PROBE_TIMEOUT = 0.5
|
|
209
213
|
|
|
210
214
|
RECEIVED_PLOT_UPDATE = 0
|
|
211
215
|
RECEIVED_DATA = 1
|
|
@@ -262,6 +266,84 @@ def _control_target_from_data(ip):
|
|
|
262
266
|
return f"{raw}:{ZMQ_CONTROL_PORT}"
|
|
263
267
|
|
|
264
268
|
|
|
269
|
+
def _host_port_from_endpoint(endpoint, default_port=ZMQ_DEFAULT_PORT):
|
|
270
|
+
"""Parse ``tcp://host:port`` or ``host[:port]`` into ``(host, port)``."""
|
|
271
|
+
raw = endpoint.strip()
|
|
272
|
+
if raw.startswith("tcp://"):
|
|
273
|
+
raw = raw[len("tcp://"):]
|
|
274
|
+
if raw.count(":") >= 1:
|
|
275
|
+
host, port_str = raw.rsplit(":", 1)
|
|
276
|
+
else:
|
|
277
|
+
host, port_str = raw, str(default_port)
|
|
278
|
+
try:
|
|
279
|
+
port = int(port_str)
|
|
280
|
+
except ValueError:
|
|
281
|
+
port = default_port
|
|
282
|
+
return host, port
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _probe_tcp_endpoint(endpoint, timeout=TCP_PROBE_TIMEOUT):
|
|
286
|
+
"""Return ``(open, reachable, error)`` for a TCP endpoint.
|
|
287
|
+
|
|
288
|
+
``Connection refused`` still proves the host answered at the network
|
|
289
|
+
layer, so it is reachable even though that particular port is closed.
|
|
290
|
+
"""
|
|
291
|
+
host, port = _host_port_from_endpoint(endpoint)
|
|
292
|
+
try:
|
|
293
|
+
with socket.create_connection((host, port), timeout=timeout):
|
|
294
|
+
return True, True, None
|
|
295
|
+
except ConnectionRefusedError as exc:
|
|
296
|
+
return False, True, str(exc)
|
|
297
|
+
except OSError as exc:
|
|
298
|
+
return False, False, str(exc)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _ping_host(host):
|
|
302
|
+
"""Best-effort host reachability fallback for firewalled TCP ports."""
|
|
303
|
+
if not host or host == "*":
|
|
304
|
+
return False
|
|
305
|
+
if sys.platform == "win32":
|
|
306
|
+
cmd = ["ping", "-n", "1", "-w", "700", host]
|
|
307
|
+
else:
|
|
308
|
+
cmd = ["ping", "-c", "1", "-W", "1", host]
|
|
309
|
+
try:
|
|
310
|
+
return subprocess.run(
|
|
311
|
+
cmd,
|
|
312
|
+
stdout=subprocess.DEVNULL,
|
|
313
|
+
stderr=subprocess.DEVNULL,
|
|
314
|
+
timeout=1.5,
|
|
315
|
+
check=False,
|
|
316
|
+
).returncode == 0
|
|
317
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
318
|
+
return False
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _probe_connect_target(endpoint):
|
|
322
|
+
"""Classify a connect-mode target for the browser status dot.
|
|
323
|
+
|
|
324
|
+
Returns ``(host_reachable, rtplot_ports_open, error)``. A reachable host
|
|
325
|
+
with closed rtplot ports is still a usable tab: ZeroMQ can connect now and
|
|
326
|
+
auto-complete the connection later when the sender starts listening.
|
|
327
|
+
"""
|
|
328
|
+
host, data_port = _host_port_from_endpoint(endpoint)
|
|
329
|
+
data_open, data_reachable, data_err = _probe_tcp_endpoint(endpoint)
|
|
330
|
+
ctrl_endpoint = _control_target_from_data(endpoint)
|
|
331
|
+
ctrl_open, ctrl_reachable, ctrl_err = _probe_tcp_endpoint(
|
|
332
|
+
ctrl_endpoint, timeout=TCP_PROBE_TIMEOUT
|
|
333
|
+
)
|
|
334
|
+
host_reachable = data_reachable or ctrl_reachable
|
|
335
|
+
if not host_reachable and _ping_host(host):
|
|
336
|
+
host_reachable = True
|
|
337
|
+
if not host_reachable:
|
|
338
|
+
return (
|
|
339
|
+
False,
|
|
340
|
+
False,
|
|
341
|
+
f"{host} unreachable (data {data_port}: {data_err}; "
|
|
342
|
+
f"control {data_port + 1}: {ctrl_err})",
|
|
343
|
+
)
|
|
344
|
+
return True, bool(data_open and ctrl_open), None
|
|
345
|
+
|
|
346
|
+
|
|
265
347
|
###############################
|
|
266
348
|
# Tab model #
|
|
267
349
|
###############################
|
|
@@ -274,7 +356,7 @@ class Tab:
|
|
|
274
356
|
name: str
|
|
275
357
|
mode: str # "bind" or "connect"
|
|
276
358
|
endpoint: str # user-visible label, e.g. "*:5555" or "pi1:5555"
|
|
277
|
-
status: str = "idle" # idle | streaming | error
|
|
359
|
+
status: str = "idle" # idle | connecting | connected | streaming | error
|
|
278
360
|
error: Optional[str] = None
|
|
279
361
|
|
|
280
362
|
# Plot state
|
|
@@ -302,6 +384,7 @@ class Tab:
|
|
|
302
384
|
data_sock: Optional[zmq.Socket] = None
|
|
303
385
|
ctrl_sock: Optional[zmq.Socket] = None
|
|
304
386
|
receiver_task: Optional[asyncio.Task] = None
|
|
387
|
+
monitor_task: Optional[asyncio.Task] = None
|
|
305
388
|
|
|
306
389
|
# Data buffer, lazy-allocated on first use (roughly 4 GB virtual,
|
|
307
390
|
# but pages in only when written on Linux/Windows — a single tab
|
|
@@ -618,9 +701,38 @@ def _open_tab_sockets(tab: Tab):
|
|
|
618
701
|
# Close any prior sockets first (e.g. on retry).
|
|
619
702
|
_close_tab_sockets(tab)
|
|
620
703
|
|
|
704
|
+
connect_label = tab.endpoint
|
|
705
|
+
connect_data_ep = None
|
|
706
|
+
if tab.mode == "connect":
|
|
707
|
+
try:
|
|
708
|
+
connect_data_ep, connect_label = _normalize_connect_target(tab.endpoint)
|
|
709
|
+
except Exception as exc: # noqa: BLE001
|
|
710
|
+
tab.status = "error"
|
|
711
|
+
tab.error = f"invalid endpoint: {exc}"
|
|
712
|
+
print(f"[{tab.id}] ZMQ open failed: {tab.error}")
|
|
713
|
+
return
|
|
714
|
+
|
|
715
|
+
tab.endpoint = connect_label
|
|
716
|
+
tab.status = "connecting"
|
|
717
|
+
tab.error = None
|
|
718
|
+
host_reachable, rtplot_ports_open, peer_err = _probe_connect_target(connect_label)
|
|
719
|
+
if not host_reachable:
|
|
720
|
+
tab.status = "error"
|
|
721
|
+
tab.error = peer_err
|
|
722
|
+
print(f"[{tab.id}] peer probe failed: {peer_err}")
|
|
723
|
+
return
|
|
724
|
+
if not rtplot_ports_open:
|
|
725
|
+
tab.status = "idle"
|
|
726
|
+
tab.error = None
|
|
727
|
+
print(
|
|
728
|
+
f"[{tab.id}] host reachable, rtplot ports closed;"
|
|
729
|
+
f" waiting for ZMQ peer: {connect_label}"
|
|
730
|
+
)
|
|
731
|
+
|
|
621
732
|
last_exc: Exception | None = None
|
|
622
733
|
attempts = 4 if tab.mode == "bind" else 1
|
|
623
734
|
for attempt in range(attempts):
|
|
735
|
+
monitor = None
|
|
624
736
|
try:
|
|
625
737
|
data = zmq_ctx.socket(zmq.SUB)
|
|
626
738
|
data.setsockopt_string(zmq.SUBSCRIBE, "")
|
|
@@ -635,14 +747,20 @@ def _open_tab_sockets(tab: Tab):
|
|
|
635
747
|
tab.endpoint = f"*:{ZMQ_DEFAULT_PORT}"
|
|
636
748
|
print(f"[{tab.id}] ZMQ: bound on tcp://*:{ZMQ_DEFAULT_PORT}")
|
|
637
749
|
else:
|
|
638
|
-
|
|
639
|
-
|
|
750
|
+
monitor = data.get_monitor_socket(
|
|
751
|
+
zmq.EVENT_CONNECTED
|
|
752
|
+
| zmq.EVENT_DISCONNECTED
|
|
753
|
+
| zmq.EVENT_CONNECT_DELAYED
|
|
754
|
+
| zmq.EVENT_CONNECT_RETRIED
|
|
755
|
+
)
|
|
756
|
+
data.connect(connect_data_ep)
|
|
640
757
|
ctrl_ep, _ = _normalize_connect_target(
|
|
641
758
|
_control_target_from_data(tab.endpoint), port=ZMQ_CONTROL_PORT
|
|
642
759
|
)
|
|
643
760
|
ctrl.connect(ctrl_ep)
|
|
644
|
-
tab.endpoint =
|
|
645
|
-
|
|
761
|
+
tab.endpoint = connect_label
|
|
762
|
+
tab.monitor_task = asyncio.create_task(zmq_monitor(tab, monitor))
|
|
763
|
+
print(f"[{tab.id}] ZMQ: connecting to {connect_data_ep} (ctrl {ctrl_ep})")
|
|
646
764
|
|
|
647
765
|
tab.data_sock = data
|
|
648
766
|
tab.ctrl_sock = ctrl
|
|
@@ -657,6 +775,11 @@ def _open_tab_sockets(tab: Tab):
|
|
|
657
775
|
except Exception: pass
|
|
658
776
|
try: ctrl.close(0)
|
|
659
777
|
except Exception: pass
|
|
778
|
+
try:
|
|
779
|
+
if monitor is not None:
|
|
780
|
+
monitor.close(0)
|
|
781
|
+
except Exception:
|
|
782
|
+
pass
|
|
660
783
|
if attempt < attempts - 1:
|
|
661
784
|
wait = 0.15 * (attempt + 1)
|
|
662
785
|
print(
|
|
@@ -692,6 +815,62 @@ async def _cancel_task(task):
|
|
|
692
815
|
pass
|
|
693
816
|
|
|
694
817
|
|
|
818
|
+
async def zmq_monitor(tab: Tab, monitor_sock):
|
|
819
|
+
"""Track transport-level ZMQ state for connect-mode tabs."""
|
|
820
|
+
try:
|
|
821
|
+
while True:
|
|
822
|
+
evt = await recv_monitor_message(monitor_sock)
|
|
823
|
+
event = evt.get("event")
|
|
824
|
+
if event == zmq.EVENT_CONNECTED:
|
|
825
|
+
if tab.status in ("connecting", "idle") and not tab.initialized:
|
|
826
|
+
tab.status = "connected"
|
|
827
|
+
tab.error = None
|
|
828
|
+
await broadcast_tab(tab.id)
|
|
829
|
+
elif event == zmq.EVENT_DISCONNECTED:
|
|
830
|
+
if tab.mode == "connect":
|
|
831
|
+
host_reachable, rtplot_ports_open, peer_err = _probe_connect_target(tab.endpoint)
|
|
832
|
+
if host_reachable and not rtplot_ports_open:
|
|
833
|
+
tab.status = "idle"
|
|
834
|
+
tab.error = None
|
|
835
|
+
elif host_reachable:
|
|
836
|
+
tab.status = "idle"
|
|
837
|
+
tab.error = None
|
|
838
|
+
else:
|
|
839
|
+
tab.status = "error"
|
|
840
|
+
tab.error = peer_err
|
|
841
|
+
await broadcast_tab(tab.id)
|
|
842
|
+
elif event == zmq.EVENT_CONNECT_RETRIED:
|
|
843
|
+
if tab.mode == "connect" and tab.status == "error":
|
|
844
|
+
host_reachable, _rtplot_ports_open, peer_err = _probe_connect_target(tab.endpoint)
|
|
845
|
+
if host_reachable:
|
|
846
|
+
tab.status = "idle"
|
|
847
|
+
tab.error = None
|
|
848
|
+
else:
|
|
849
|
+
tab.error = peer_err
|
|
850
|
+
await broadcast_tab(tab.id)
|
|
851
|
+
elif event == zmq.EVENT_CONNECT_DELAYED:
|
|
852
|
+
if tab.mode == "connect" and tab.status == "error":
|
|
853
|
+
host_reachable, _rtplot_ports_open, peer_err = _probe_connect_target(tab.endpoint)
|
|
854
|
+
if host_reachable:
|
|
855
|
+
tab.status = "idle"
|
|
856
|
+
tab.error = None
|
|
857
|
+
else:
|
|
858
|
+
tab.error = peer_err
|
|
859
|
+
await broadcast_tab(tab.id)
|
|
860
|
+
except asyncio.CancelledError:
|
|
861
|
+
raise
|
|
862
|
+
except Exception as exc: # noqa: BLE001
|
|
863
|
+
if tab.mode == "connect" and tab.status != "streaming":
|
|
864
|
+
tab.status = "error"
|
|
865
|
+
tab.error = f"ZMQ monitor error: {exc}"
|
|
866
|
+
await broadcast_tab(tab.id)
|
|
867
|
+
finally:
|
|
868
|
+
try:
|
|
869
|
+
monitor_sock.close(0)
|
|
870
|
+
except Exception: # noqa: BLE001
|
|
871
|
+
pass
|
|
872
|
+
|
|
873
|
+
|
|
695
874
|
async def send_control_event(tab: Tab, event):
|
|
696
875
|
"""Forward a control event to the user's Python process, best-effort."""
|
|
697
876
|
if tab.ctrl_sock is None:
|
|
@@ -1003,6 +1182,7 @@ async def delete_tab(tab_id: str):
|
|
|
1003
1182
|
if t is None:
|
|
1004
1183
|
return
|
|
1005
1184
|
await _cancel_task(t.receiver_task)
|
|
1185
|
+
await _cancel_task(t.monitor_task)
|
|
1006
1186
|
_close_tab_sockets(t)
|
|
1007
1187
|
# Move any viewers off this tab back to bind_me on the browser side;
|
|
1008
1188
|
# server tells them via tab_removed + they re-subscribe.
|
|
@@ -1091,6 +1271,8 @@ async def reconnect_tab(tab_id: str):
|
|
|
1091
1271
|
if t is None:
|
|
1092
1272
|
return
|
|
1093
1273
|
await _cancel_task(t.receiver_task)
|
|
1274
|
+
await _cancel_task(t.monitor_task)
|
|
1275
|
+
t.monitor_task = None
|
|
1094
1276
|
# _open_tab_sockets closes first, then reopens. Status gets set to
|
|
1095
1277
|
# "idle" on success or "error" + a message on bind/connect failure.
|
|
1096
1278
|
_open_tab_sockets(t)
|
|
@@ -1467,6 +1649,7 @@ async def on_cleanup(app):
|
|
|
1467
1649
|
await _cancel_task(app.get(key))
|
|
1468
1650
|
for t in list(tabs.values()):
|
|
1469
1651
|
await _cancel_task(t.receiver_task)
|
|
1652
|
+
await _cancel_task(t.monitor_task)
|
|
1470
1653
|
_close_tab_sockets(t)
|
|
1471
1654
|
for ws in list(ws_clients):
|
|
1472
1655
|
try:
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
.tab.active { background: #fff; color: #111; border-color: #bbb; font-weight: 600; }
|
|
16
16
|
.tab .tab-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #bbb; flex: 0 0 auto; }
|
|
17
17
|
.tab .tab-dot.idle { background: #bbb; }
|
|
18
|
+
.tab .tab-dot.connected { background: #e8b13a; box-shadow: 0 0 3px rgba(232,177,58,0.5); }
|
|
18
19
|
.tab .tab-dot.streaming { background: #2ebf4e; box-shadow: 0 0 3px rgba(46,191,78,0.5); }
|
|
19
20
|
.tab .tab-dot.error { background: #e05252; }
|
|
20
21
|
.tab .tab-dot.connecting { background: #e8b13a; animation: pulse 1s ease-in-out infinite alternate; }
|
|
@@ -987,6 +988,19 @@
|
|
|
987
988
|
}
|
|
988
989
|
|
|
989
990
|
// ---- Tab bar rendering ----
|
|
991
|
+
function tabStatusTitle(t) {
|
|
992
|
+
if (t.error) return t.error;
|
|
993
|
+
if (t.status === 'streaming') return 'Receiving rtplot data';
|
|
994
|
+
if (t.status === 'connected') return 'ZMQ connected; waiting for plot config/data';
|
|
995
|
+
if (t.status === 'connecting') return 'Checking device connection';
|
|
996
|
+
if (t.status === 'idle') {
|
|
997
|
+
return t.mode === 'bind'
|
|
998
|
+
? 'Listening for a sender'
|
|
999
|
+
: 'Host reachable; rtplot ports are not connected';
|
|
1000
|
+
}
|
|
1001
|
+
return 'Status unknown';
|
|
1002
|
+
}
|
|
1003
|
+
|
|
990
1004
|
function renderTabs() {
|
|
991
1005
|
tabbar.innerHTML = '';
|
|
992
1006
|
// Stable order: bind_me first, then others in insertion order.
|
|
@@ -1002,7 +1016,7 @@
|
|
|
1002
1016
|
|
|
1003
1017
|
const dot = document.createElement('span');
|
|
1004
1018
|
dot.className = 'tab-dot ' + (t.status || 'idle');
|
|
1005
|
-
|
|
1019
|
+
dot.title = tabStatusTitle(t);
|
|
1006
1020
|
el.appendChild(dot);
|
|
1007
1021
|
|
|
1008
1022
|
// Stacked labels: name on top, endpoint on a muted subline so
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_AMS-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Caligraphic-Bold.woff2
RENAMED
|
File without changes
|
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Fraktur-Bold.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Fraktur-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Main-Bold.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Main-BoldItalic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Main-Italic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Main-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Math-BoldItalic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Math-Italic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_SansSerif-Bold.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_SansSerif-Italic.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_SansSerif-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Script-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Size1-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Size2-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Size3-Regular.woff2
RENAMED
|
File without changes
|
{better_rtplot-0.4.9 → better_rtplot-0.4.10}/rtplot/static/katex/fonts/KaTeX_Size4-Regular.woff2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|