runtimepy 5.14.2__py3-none-any.whl → 5.15.1__py3-none-any.whl

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.
Files changed (99) hide show
  1. runtimepy/__init__.py +2 -2
  2. runtimepy/channel/__init__.py +1 -4
  3. runtimepy/channel/environment/__init__.py +93 -2
  4. runtimepy/channel/environment/create.py +16 -1
  5. runtimepy/channel/environment/sample.py +10 -2
  6. runtimepy/channel/registry.py +2 -3
  7. runtimepy/codec/protocol/base.py +34 -14
  8. runtimepy/codec/protocol/json.py +5 -3
  9. runtimepy/codec/system/__init__.py +6 -2
  10. runtimepy/control/source.py +1 -1
  11. runtimepy/data/404.md +16 -0
  12. runtimepy/data/base.yaml +3 -0
  13. runtimepy/data/css/bootstrap_extra.css +59 -44
  14. runtimepy/data/css/main.css +23 -4
  15. runtimepy/data/dummy_load.yaml +5 -2
  16. runtimepy/data/factories.yaml +1 -0
  17. runtimepy/data/js/classes/App.js +54 -2
  18. runtimepy/data/js/classes/ChannelTable.js +6 -8
  19. runtimepy/data/js/classes/Plot.js +9 -4
  20. runtimepy/data/js/classes/TabFilter.js +47 -9
  21. runtimepy/data/js/classes/TabInterface.js +106 -11
  22. runtimepy/data/js/classes/WindowHashManager.js +30 -15
  23. runtimepy/data/js/init.js +18 -1
  24. runtimepy/data/js/markdown_page.js +10 -0
  25. runtimepy/data/js/sample.js +1 -0
  26. runtimepy/data/schemas/BitFields.yaml +9 -0
  27. runtimepy/data/schemas/RuntimeEnum.yaml +6 -0
  28. runtimepy/data/schemas/StructConfig.yaml +9 -1
  29. runtimepy/data/static/css/bootstrap-icons.min.css +4 -3
  30. runtimepy/data/static/css/bootstrap.min.css +3 -4
  31. runtimepy/data/static/css/fonts/bootstrap-icons.woff +0 -0
  32. runtimepy/data/static/css/fonts/bootstrap-icons.woff2 +0 -0
  33. runtimepy/data/static/js/bootstrap.bundle.min.js +5 -4
  34. runtimepy/data/static/js/webglplot.umd.min.js +2 -1
  35. runtimepy/data/static/svg/outline-dark.svg +22 -0
  36. runtimepy/data/static/svg/outline-light.svg +22 -0
  37. runtimepy/enum/__init__.py +13 -1
  38. runtimepy/enum/registry.py +13 -1
  39. runtimepy/message/__init__.py +3 -3
  40. runtimepy/mixins/logging.py +6 -1
  41. runtimepy/net/__init__.py +0 -2
  42. runtimepy/net/arbiter/info.py +36 -4
  43. runtimepy/net/arbiter/struct/__init__.py +3 -2
  44. runtimepy/net/connection.py +6 -7
  45. runtimepy/net/html/__init__.py +29 -11
  46. runtimepy/net/html/bootstrap/__init__.py +2 -2
  47. runtimepy/net/html/bootstrap/elements.py +44 -24
  48. runtimepy/net/html/bootstrap/tabs.py +18 -11
  49. runtimepy/net/http/__init__.py +3 -3
  50. runtimepy/net/http/request_target.py +3 -3
  51. runtimepy/net/mixin.py +4 -2
  52. runtimepy/net/server/__init__.py +16 -9
  53. runtimepy/net/server/app/__init__.py +1 -0
  54. runtimepy/net/server/app/create.py +3 -3
  55. runtimepy/net/server/app/env/__init__.py +30 -4
  56. runtimepy/net/server/app/env/settings.py +4 -7
  57. runtimepy/net/server/app/env/tab/base.py +2 -1
  58. runtimepy/net/server/app/env/tab/controls.py +141 -27
  59. runtimepy/net/server/app/env/tab/html.py +68 -26
  60. runtimepy/net/server/app/env/widgets.py +115 -61
  61. runtimepy/net/server/app/landing_page.py +1 -1
  62. runtimepy/net/server/app/tab.py +12 -3
  63. runtimepy/net/server/html.py +2 -2
  64. runtimepy/net/server/json.py +1 -1
  65. runtimepy/net/server/markdown.py +29 -12
  66. runtimepy/net/server/mux.py +29 -0
  67. runtimepy/net/stream/__init__.py +6 -5
  68. runtimepy/net/stream/base.py +4 -2
  69. runtimepy/net/tcp/connection.py +5 -3
  70. runtimepy/net/tcp/http/__init__.py +10 -9
  71. runtimepy/net/tcp/protocol.py +2 -2
  72. runtimepy/net/tcp/scpi/__init__.py +5 -2
  73. runtimepy/net/tcp/telnet/__init__.py +2 -1
  74. runtimepy/net/udp/connection.py +10 -6
  75. runtimepy/net/udp/protocol.py +5 -6
  76. runtimepy/net/udp/queue.py +5 -2
  77. runtimepy/net/udp/tftp/base.py +2 -1
  78. runtimepy/net/websocket/connection.py +58 -9
  79. runtimepy/primitives/array/__init__.py +7 -5
  80. runtimepy/primitives/base.py +3 -2
  81. runtimepy/primitives/field/__init__.py +35 -2
  82. runtimepy/primitives/field/fields.py +11 -2
  83. runtimepy/primitives/field/manager/base.py +19 -2
  84. runtimepy/primitives/serializable/base.py +5 -2
  85. runtimepy/primitives/serializable/fixed.py +5 -2
  86. runtimepy/primitives/serializable/prefixed.py +4 -1
  87. runtimepy/primitives/types/base.py +4 -1
  88. runtimepy/primitives/types/bounds.py +10 -4
  89. runtimepy/registry/__init__.py +20 -0
  90. runtimepy/registry/name.py +6 -0
  91. runtimepy/requirements.txt +2 -2
  92. runtimepy/ui/controls.py +20 -1
  93. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/METADATA +6 -6
  94. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/RECORD +98 -94
  95. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/WHEEL +1 -1
  96. runtimepy/data/404.html +0 -7
  97. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/entry_points.txt +0 -0
  98. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/licenses/LICENSE +0 -0
  99. {runtimepy-5.14.2.dist-info → runtimepy-5.15.1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,8 @@
1
1
  html {
2
2
  height: 100%;
3
+ max-height: 100%;
3
4
  width: 100%;
5
+ max-width: 100%;
4
6
  position: fixed;
5
7
  }
6
8
 
@@ -15,10 +17,6 @@ body > :first-child {
15
17
  height: 100%;
16
18
  }
17
19
 
18
- #runtimepy {
19
- height: 100%;
20
- }
21
-
22
20
  #runtimepy-tabs {
23
21
  width: min-content;
24
22
  }
@@ -46,12 +44,33 @@ body > :first-child {
46
44
 
47
45
  .toggle-value {
48
46
  /* placeholder */
47
+ background-color: var(--bs-body-bg);
49
48
  }
50
49
 
51
50
  .window-button {
52
51
  border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-link-hover-color) !important;
53
52
  }
54
53
 
54
+ .logo-outline-background {
55
+ background-position: center;
56
+ background-repeat: no-repeat;
57
+ background-size: auto 66.67%;
58
+ }
59
+ [data-bs-theme=dark] .logo-outline-background {
60
+ background-image: url(/static/svg/outline-dark.svg);
61
+ }
62
+ [data-bs-theme=light] .logo-outline-background {
63
+ background-image: url(/static/svg/outline-light.svg);
64
+ }
65
+
55
66
  :focus {
56
67
  outline: none;
57
68
  }
69
+
70
+ .overscroll-behavior-x-none {
71
+ overscroll-behavior-x: none;
72
+ }
73
+
74
+ .overscroll-behavior-none {
75
+ overscroll-behavior: none;
76
+ }
@@ -75,7 +75,7 @@ structs:
75
75
 
76
76
  Should be shown for peer process as well?
77
77
 
78
- - name: struct2
78
+ - name: example.struct2
79
79
  factory: sample_struct
80
80
  config:
81
81
  markdown: |
@@ -83,8 +83,11 @@ structs:
83
83
 
84
84
  One of the structs of all time.
85
85
 
86
- - name: struct3
86
+ - name: example.struct3
87
87
  factory: sample_struct
88
+ config:
89
+ js_uris:
90
+ - package://runtimepy/js/sample.js
88
91
 
89
92
  # Sample peer processes.
90
93
  processes:
@@ -37,6 +37,7 @@ factories:
37
37
  - {name: runtimepy.control.step.StepperToggler}
38
38
 
39
39
  # Useful structs.
40
+ - {name: runtimepy.net.arbiter.info.RuntimeStruct}
40
41
  - {name: runtimepy.net.arbiter.info.TrigStruct}
41
42
  - {name: runtimepy.net.arbiter.info.SampleStruct}
42
43
  - {name: runtimepy.net.server.struct.UiState}
@@ -20,7 +20,7 @@ class App {
20
20
  worker.addEventListener("message", async (event) => {
21
21
  if (event.data == 0) {
22
22
  /* Manage settings modal. */
23
- let _modal = document.getElementById("runtimepy-plot");
23
+ let _modal = document.getElementById("runtimepy-settings");
24
24
  if (_modal) {
25
25
  modalManager = new PlotModalManager(_modal);
26
26
  }
@@ -53,12 +53,64 @@ class App {
53
53
  this.switchTab(hash.tab);
54
54
  }
55
55
 
56
+ hash.updateTabFilter(hash.tabFilter);
57
+
56
58
  /* Handle settings controls. */
57
59
  loadSettings();
58
60
 
59
61
  /* Handle individual settings. */
60
62
  this.handleInitialMinTxPeriod();
61
63
 
64
+ /* Handle channel-table expand button. */
65
+ let _button = document.getElementById("open-channels-button");
66
+ if (_button) {
67
+ _button.onclick = () => {
68
+ /* Ensure channel table is visible. */
69
+ if (!hash.channelsShown && hash.channelsButton) {
70
+ hash.channelsButton.click();
71
+ }
72
+
73
+ /* Ensure channel table is at maximum width. */
74
+ if (shown_tab in tabs) {
75
+ let elem = tabs[shown_tab].query(".channel-column");
76
+ if (elem) {
77
+ elem.style.width = window.innerWidth + "px";
78
+ tabs[shown_tab].correctVerticalBarPosition();
79
+ tabs[shown_tab].focus();
80
+ }
81
+ }
82
+ };
83
+ }
84
+ _button = document.getElementById("dedent-channels-button");
85
+ if (_button) {
86
+ _button.onclick = () => {
87
+ if (!hash.channelsShown) {
88
+ return;
89
+ }
90
+
91
+ /* Reduce channel table width. */
92
+ if (shown_tab in tabs) {
93
+ let elem = tabs[shown_tab].query(".channel-column");
94
+ if (elem) {
95
+ let newWidth = elem.getBoundingClientRect().width - 50;
96
+ if (newWidth > 0) {
97
+ elem.style.width = newWidth + "px";
98
+ tabs[shown_tab].focus();
99
+ } else {
100
+ hash.channelsButton.click();
101
+ }
102
+ }
103
+ }
104
+ };
105
+ }
106
+
107
+ /* Set initial focus. */
108
+ if (hash.tabsShown && tabFilter) {
109
+ tabFilter.input.focus();
110
+ } else if (hash.channelsShown && shown_tab in tabs) {
111
+ tabs[shown_tab].focus();
112
+ }
113
+
62
114
  startMainLoop();
63
115
  }
64
116
  }, {once : true});
@@ -110,7 +162,7 @@ function startMainLoop() {
110
162
  if (splash) {
111
163
  let curr = window.getComputedStyle(splash).getPropertyValue("opacity");
112
164
  if (curr > 0) {
113
- splash.style.opacity = curr - Math.min(0.05, deltaT / 2000);
165
+ splash.style.opacity = curr - Math.min(0.05, deltaT / 1000);
114
166
  } else {
115
167
  splash.style.display = "none";
116
168
  splash = undefined;
@@ -20,14 +20,12 @@ class ChannelTable {
20
20
  for (let input of table.querySelectorAll("input.channel-value-input")) {
21
21
  this.channelInputs[input.id] = input;
22
22
 
23
- let cmd =
24
-
25
- /* Register handler for pressing enter. */
26
- input.onkeypress = (event) => {
27
- if (event.key == "Enter") {
28
- this.worker.command(`set ${input.id} ${input.value}`);
29
- }
30
- };
23
+ /* Register handler for pressing enter. */
24
+ input.onkeypress = (event) => {
25
+ if (event.key == "Enter") {
26
+ this.worker.command(`set ${input.id} ${input.value}`);
27
+ }
28
+ };
31
29
 
32
30
  /* Register handler for pressing send button. */
33
31
  input.nextElementSibling.onclick =
@@ -20,11 +20,16 @@ class Plot {
20
20
  /* Handle overlay events. */
21
21
  if (this.overlay) {
22
22
  /* Scroll, click and keyboard. */
23
- this.overlay.onwheel = this.createEventSender(scrollEventKeys);
24
- this.overlay.onclick = this.createEventSender(pointerEventKeys);
23
+ const options = {passive : true};
24
+
25
+ this.overlay.addEventListener(
26
+ "wheel", this.createEventSender(scrollEventKeys), options);
27
+ this.overlay.addEventListener(
28
+ "click", this.createEventSender(pointerEventKeys), options);
29
+
25
30
  let eventHandler = this.createEventSender(keyboardEventKeys);
26
- this.overlay.addEventListener("keydown", eventHandler);
27
- this.overlay.addEventListener("keyup", eventHandler);
31
+ this.overlay.addEventListener("keydown", eventHandler, options);
32
+ this.overlay.addEventListener("keyup", eventHandler, options);
28
33
 
29
34
  /* Should there be a keybind that opens this? */
30
35
  // let plotButton = document.getElementById("runtimepy-plot-button");
@@ -15,34 +15,72 @@ class TabFilter {
15
15
  }
16
16
 
17
17
  updateStyles(pattern) {
18
+ pattern = pattern.trim();
18
19
  hash.setTabFilter(pattern);
19
20
 
20
21
  if (!pattern) {
21
22
  pattern = ".*";
22
23
  }
23
- const re = new RegExp(pattern);
24
+
25
+ let parts = pattern.split(/(\s+)/)
26
+ .filter((x) => x.trim().length > 0)
27
+ .map((x) => new RegExp(x));
24
28
 
25
29
  for (let [name, elem] of Object.entries(this.buttons)) {
26
- if (re.test(name)) {
27
- elem.style.display = "block";
28
- } else if (!elem.classList.contains("active")) {
29
- elem.style.display = "none";
30
+ let found = elem.classList.contains("active");
31
+
32
+ if (!found) {
33
+ for (const re of parts) {
34
+ if (re.test(name)) {
35
+ found = true;
36
+ break;
37
+ }
38
+ }
39
+ }
40
+
41
+ if (found) {
42
+ for (const child of elem.parentElement.children) {
43
+ child.style.display = "block";
44
+ }
45
+ } else {
46
+ for (const child of elem.parentElement.children) {
47
+ child.style.display = "none";
48
+ }
30
49
  }
31
50
  }
32
51
  }
33
52
 
34
53
  keydown(event) {
54
+ // ctrl-l - go to channel table (open if needed) then close tabs
55
+
56
+ if (globalKeyEvent(event) || ignoreFilterKeyEvent(event)) {
57
+ return;
58
+ }
59
+
60
+ let curr = this.input.value;
61
+
62
+ // new features: make '$' either auto-complete (one character per press)
63
+ // to the first non-selected tab currently appearing in filter and presses
64
+ // the nav button when it's the only button left
65
+ //
66
+ // tbd need another button for opening new tab, '@'?
35
67
  if (event.key == "Enter") {
36
- this.input.value = "";
37
- this.updateStyles(this.input.value);
68
+ curr = "";
69
+ event.preventDefault();
38
70
  } else {
39
- let curr = this.input.value;
40
71
  if (event.key == "Backspace") {
41
72
  curr = curr.slice(0, -1);
42
73
  } else {
43
74
  curr += event.key;
44
75
  }
45
- this.updateStyles(curr);
46
76
  }
77
+
78
+ if (!curr) {
79
+ this.input.value = curr;
80
+ }
81
+
82
+ this.updateStyles(curr);
83
+
84
+ this.input.focus();
47
85
  }
48
86
  }
@@ -28,6 +28,8 @@ class TabInterface {
28
28
  this.time = 0;
29
29
  this.channelTimestamps = {};
30
30
  this.channelColorButtons = {};
31
+ this.commandShown = true;
32
+ this.regularShown = true;
31
33
 
32
34
  let table = this.query("tbody");
33
35
  if (table) {
@@ -46,6 +48,9 @@ class TabInterface {
46
48
 
47
49
  /* Audit vertical bar position. */
48
50
  this.correctVerticalBarPosition();
51
+
52
+ /* Focus channel filter if it exists. */
53
+ this.focus();
49
54
  }
50
55
  this.worker.send({kind : msg});
51
56
  } ];
@@ -61,6 +66,12 @@ class TabInterface {
61
66
  ((event) => { this.correctVerticalBarPosition(); }).bind(this));
62
67
  }
63
68
 
69
+ focus() {
70
+ if (this.channelFilter) {
71
+ this.channelFilter.focus();
72
+ }
73
+ }
74
+
64
75
  initCommand() {
65
76
  let command = this.query(`#${this.queryName}-command`);
66
77
  if (command) {
@@ -82,15 +93,41 @@ class TabInterface {
82
93
  }
83
94
 
84
95
  updateChannelStyles(pattern) {
96
+ pattern = pattern.trim();
85
97
  hash.handleChannelFilter(this.name, pattern);
86
98
 
87
- if (!pattern) {
88
- pattern = ".*";
99
+ let parts = pattern.split(/(\s+)/).filter((x) => x.trim().length > 0);
100
+
101
+ let showRegular = this.regularShown;
102
+ let special = "!";
103
+ if (parts.includes(special)) {
104
+ showRegular = false;
105
+ parts = parts.filter(e => e != special);
106
+ }
107
+ let showCommand = this.commandShown;
108
+ special = "@";
109
+ if (parts.includes(special)) {
110
+ showCommand = false;
111
+ parts = parts.filter(e => e != special);
112
+ }
113
+
114
+ if (!parts.length) {
115
+ parts.push(".*");
89
116
  }
90
- const re = new RegExp(pattern);
91
117
 
92
118
  for (let [name, elem] of Object.entries(this.channelRows)) {
93
- if (re.test(name)) {
119
+ let found = false;
120
+ if ((showRegular && elem.classList.contains("channel-regular")) ||
121
+ (showCommand && elem.classList.contains("channel-commandable"))) {
122
+ for (const re of parts.map((x) => new RegExp(x))) {
123
+ if (re.test(name)) {
124
+ found = true;
125
+ break;
126
+ }
127
+ }
128
+ }
129
+
130
+ if (found) {
94
131
  elem.style.display = "table-row";
95
132
  } else {
96
133
  elem.style.display = "none";
@@ -99,19 +136,34 @@ class TabInterface {
99
136
  }
100
137
 
101
138
  channelKeydown(event) {
139
+ // ctrl-h - go to tabs (open if necessary)
140
+
141
+ if (globalKeyEvent(event) || ignoreFilterKeyEvent(event)) {
142
+ return;
143
+ }
144
+
145
+ let curr = this.channelFilter.value;
146
+
147
+ // new feature: make '$' toggle the state of all telemetry channels visible
148
+ // if any are already selected, enable all, if they're all already enabled
149
+ // disable them
102
150
  if (event.key == "Enter") {
103
- this.channelFilter.value = "";
104
- this.updateChannelStyles(this.channelFilter.value);
151
+ curr = "";
105
152
  } else {
106
- let curr = this.channelFilter.value;
107
153
  if (event.key == "Backspace") {
108
154
  curr = curr.slice(0, -1);
109
155
  } else {
110
156
  curr += event.key;
111
157
  }
158
+ }
112
159
 
113
- this.updateChannelStyles(curr);
160
+ if (!curr) {
161
+ this.channelFilter.value = curr;
114
162
  }
163
+
164
+ this.updateChannelStyles(curr);
165
+
166
+ this.focus();
115
167
  }
116
168
 
117
169
  initControls() {
@@ -175,6 +227,42 @@ class TabInterface {
175
227
  if (selector && send) {
176
228
  send.onclick = () => { this.worker.command(`custom ${selector.value}`); };
177
229
  }
230
+
231
+ /* Initialize commandable/regular channel visibility toggle. */
232
+ let commandToggle = this.query("#toggle-command-channels");
233
+ if (commandToggle) {
234
+ commandToggle.onclick = () => {
235
+ this.commandShown = !this.commandShown;
236
+ let classes = commandToggle.children[0].classList;
237
+ if (this.commandShown) {
238
+ classes.toggle("bi-eye-slash");
239
+ classes.toggle("bi-eye");
240
+ classes.toggle("stale");
241
+ } else {
242
+ classes.toggle("bi-eye");
243
+ classes.toggle("bi-eye-slash");
244
+ classes.toggle("stale");
245
+ }
246
+ this.updateChannelStyles(this.channelFilter.value);
247
+ };
248
+ }
249
+ let regularToggle = this.query("#toggle-regular-channels");
250
+ if (regularToggle) {
251
+ regularToggle.onclick = () => {
252
+ this.regularShown = !this.regularShown;
253
+ let classes = regularToggle.children[0].classList;
254
+ if (this.regularShown) {
255
+ classes.toggle("bi-eye-slash-fill");
256
+ classes.toggle("bi-eye-fill");
257
+ classes.toggle("stale");
258
+ } else {
259
+ classes.toggle("bi-eye-slash-fill");
260
+ classes.toggle("bi-eye-fill");
261
+ classes.toggle("stale");
262
+ }
263
+ this.updateChannelStyles(this.channelFilter.value);
264
+ };
265
+ }
178
266
  }
179
267
 
180
268
  setHandler(elem) {
@@ -217,14 +305,14 @@ class TabInterface {
217
305
  document.addEventListener(
218
306
  up, (event) => { document.removeEventListener(move, handleMouse); },
219
307
  {once : true});
220
- });
308
+ }, {passive : true});
221
309
  }
222
310
 
223
311
  setupVerticalDivider(elem) {
224
312
  setupCursorContext(elem, this.setupVerticalDividerEvents.bind(this));
225
313
  }
226
314
 
227
- correctVerticalBarPosition(elem) {
315
+ correctVerticalBarPosition() {
228
316
  if (shown_tab == this.name && this.divider) {
229
317
  let margin =
230
318
  window.innerWidth - this.divider.getBoundingClientRect().right;
@@ -331,6 +419,13 @@ class TabInterface {
331
419
  log(message) {
332
420
  if (this.logs) {
333
421
  this.logs.value += message + "\n";
422
+
423
+ const max_length = 4096;
424
+ if (this.logs.value.length > max_length) {
425
+ this.logs.value =
426
+ this.logs.value.substr(this.logs.value.length - max_length);
427
+ }
428
+
334
429
  if (this.isShown()) {
335
430
  this.logs.scrollTo(0, this.logs.scrollHeight);
336
431
  }
@@ -384,7 +479,7 @@ class TabInterface {
384
479
  this.clearPlotPoints();
385
480
  break;
386
481
  default:
387
- console.log(`Action '${action}' not hangled!`);
482
+ console.log(`Action '${action}' not handled!`);
388
483
  break;
389
484
  }
390
485
  }
@@ -16,6 +16,13 @@ class WindowHashManager {
16
16
  tabClick(event) {
17
17
  this.tabsShown = !this.tabsShown;
18
18
  this.update();
19
+
20
+ /* Handle focus. */
21
+ if (!this.tabsShown && this.channelsShown && shown_tab in tabs) {
22
+ tabs[shown_tab].focus();
23
+ } else if (this.tabsShown && tabFilter) {
24
+ tabFilter.input.focus();
25
+ }
19
26
  }
20
27
 
21
28
  lightDarkClick(event) {
@@ -40,6 +47,13 @@ class WindowHashManager {
40
47
  }
41
48
 
42
49
  this.update();
50
+
51
+ /* Handle focus. */
52
+ if (this.channelsShown && shown_tab in tabs) {
53
+ tabs[shown_tab].focus();
54
+ } else if (this.tabsShown && tabFilter) {
55
+ tabFilter.input.focus();
56
+ }
43
57
  }
44
58
 
45
59
  handleChannelFilter(tabName, value) {
@@ -111,9 +125,10 @@ class WindowHashManager {
111
125
  }
112
126
 
113
127
  /* Click handler for channels hide/show. */
114
- let channelsButton = document.getElementById("channels-button");
115
- if (channelsButton) {
116
- channelsButton.addEventListener("click", this.channelClick.bind(this));
128
+ this.channelsButton = document.getElementById("channels-button");
129
+ if (this.channelsButton) {
130
+ this.channelsButton.addEventListener("click",
131
+ this.channelClick.bind(this));
117
132
  }
118
133
 
119
134
  /* Click handler for light/dark toggle. */
@@ -147,7 +162,7 @@ class WindowHashManager {
147
162
  if (this.original) {
148
163
  let boolsChannels = this.original.split("/");
149
164
  let split = boolsChannels[0].split(",");
150
- this.tab = split[0];
165
+ this.tab = decodeURI(split[0]);
151
166
 
152
167
  /* Toggle plot-channel check boxes. */
153
168
  for (let i = 1; i < boolsChannels.length; i++) {
@@ -155,12 +170,12 @@ class WindowHashManager {
155
170
  for (let chan of nameChannels[1].split(",")) {
156
171
  if (!chan.includes("=")) {
157
172
  /* Handle regular channel names. */
158
- this.togglePlotChannel(nameChannels[0], chan);
173
+ this.togglePlotChannel(nameChannels[0], decodeURI(chan));
159
174
  } else {
160
175
  /* Handle key-value pairs. */
161
176
  let keyVal = chan.split("=");
162
177
  if (keyVal.length == 2 && keyVal[0] == "filter" && keyVal[1]) {
163
- this.setTabChannelFilter(nameChannels[0], keyVal[1]);
178
+ this.setTabChannelFilter(nameChannels[0], decodeURI(keyVal[1]));
164
179
  }
165
180
  }
166
181
  }
@@ -172,7 +187,7 @@ class WindowHashManager {
172
187
  let keyVal = item.split("=");
173
188
  if (keyVal.length == 2) {
174
189
  if (keyVal[0] == "filter" && keyVal[1]) {
175
- this.updateTabFilter(keyVal[1]);
190
+ this.updateTabFilter(decodeURI(keyVal[1]));
176
191
  }
177
192
  if (keyVal[0] == "min-tx-period-ms") {
178
193
  this.minTxPeriod = Number(keyVal[1]);
@@ -181,13 +196,13 @@ class WindowHashManager {
181
196
  }
182
197
  }
183
198
 
184
- if (split.includes("hide-tabs")) {
199
+ if (tabButton && split.includes("hide-tabs")) {
185
200
  tabButton.click();
186
201
  }
187
- if (split.includes("hide-channels")) {
188
- channelsButton.click();
202
+ if (this.channelsButton && split.includes("hide-channels")) {
203
+ this.channelsButton.click();
189
204
  }
190
- if (split.includes("light-mode")) {
205
+ if (lightDarkButton && split.includes("light-mode")) {
191
206
  lightDarkButton.click();
192
207
  }
193
208
  }
@@ -210,7 +225,7 @@ class WindowHashManager {
210
225
  let hash = this.tab;
211
226
 
212
227
  if (this.tabFilter) {
213
- hash += ",filter=" + this.tabFilter;
228
+ hash += ",filter=" + encodeURI(this.tabFilter);
214
229
  }
215
230
  if (!this.tabsShown) {
216
231
  hash += ",hide-tabs"
@@ -236,14 +251,14 @@ class WindowHashManager {
236
251
  for (let name in channels) {
237
252
  if (channels[name]) {
238
253
  if (firstChan) {
239
- hash += "/" + tab + ":"
254
+ hash += "/" + encodeURI(tab) + ":"
240
255
  firstChan = false;
241
256
  }
242
257
 
243
258
  if (hash.slice(-1) != ":") {
244
259
  hash += ",";
245
260
  }
246
- hash += name;
261
+ hash += encodeURI(name);
247
262
  }
248
263
  }
249
264
  }
@@ -258,7 +273,7 @@ class WindowHashManager {
258
273
  if (hash.slice(-1) != ":") {
259
274
  hash += ",";
260
275
  }
261
- hash += "filter=" + this.filters[tab];
276
+ hash += "filter=" + encodeURI(this.filters[tab]);
262
277
  }
263
278
  }
264
279
 
runtimepy/data/js/init.js CHANGED
@@ -40,13 +40,30 @@ function setupCursorMove(elem, down, move, up, handleMove) {
40
40
  document.removeEventListener(move, handleMove);
41
41
  elem.classList.remove("moving");
42
42
  }, {once : true});
43
- });
43
+ }, {passive : true});
44
44
  }
45
45
 
46
46
  function randomHexColor() {
47
47
  return "#" + (Math.random() * 0xFFFFFF << 0).toString(16).padStart(6, "0");
48
48
  }
49
49
 
50
+ function isModifierKeyEvent(event) {
51
+ return event.key == "Shift" || event.key == "Control" ||
52
+ event.key == "Meta" || event.key == "Alt";
53
+ }
54
+
55
+ function ignoreFilterKeyEvent(event) {
56
+ // home end pg up pg down delete f keys
57
+ return isModifierKeyEvent(event) || event.key == "Tab";
58
+ }
59
+
60
+ function globalKeyEvent(event) {
61
+ // (common)
62
+ // keybind for dedent (ctrl-d?) + expand (ctrl-e?)
63
+ // toggle tabs + toggle channel table
64
+ return false;
65
+ }
66
+
50
67
  /* Load settings control mappings. */
51
68
  let settings = {};
52
69
 
@@ -25,4 +25,14 @@ window.onload = () => {
25
25
  }
26
26
 
27
27
  bootstrap_init();
28
+
29
+ /* Hide button column for print mode. */
30
+ const params = new URLSearchParams(window.location.search);
31
+ if (params.get("print") == "true") {
32
+ const elem = document.getElementById("button-column");
33
+ if (elem) {
34
+ elem.classList.remove("d-flex");
35
+ elem.style.display = "none";
36
+ }
37
+ }
28
38
  };
@@ -0,0 +1 @@
1
+ console.log(`sample.js included (${tab.name})`);
@@ -33,6 +33,15 @@ properties:
33
33
  description:
34
34
  type: string
35
35
 
36
+ default:
37
+ oneOf:
38
+ - type: string
39
+ - type: integer
40
+ - type: boolean
41
+
42
+ controls:
43
+ type: object
44
+
36
45
  index:
37
46
  type: integer
38
47
  minimum: 0