Skip to content

Commit 8d7ad43

Browse files
committed
wip
1 parent 9478c1d commit 8d7ad43

File tree

10 files changed

+116
-12
lines changed

10 files changed

+116
-12
lines changed

local/configs/package.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ time_command: true
88

99
requirements:
1010
- aiofiles
11-
- vcorelib>=3.6.3
11+
- vcorelib>=3.6.4
1212
- svgen>=0.8.0
1313
- websockets
1414
- psutil

runtimepy/channel/environment/command/processor.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ def __init__(
4949

5050
self.parser.initialize()
5151

52-
# need a way to build this
53-
# buttons that do msg bus side effects
54-
self.action_markdown = ""
55-
5652
def register_custom_commands(
5753
self, *custom_commands: CustomCommand
5854
) -> None:

runtimepy/data/js/classes/WorkerInterface.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class WorkerInterface {
1010

1111
command(data) { this.send({kind : "command", value : data}); }
1212

13-
bus(data) { this.worker.postMessage({bus : data}); }
13+
bus(key, data) { this.worker.postMessage({key : key, bus : data}); }
1414

1515
toWorker(data, param) { return this.send({"worker" : data}, param); }
1616
}

runtimepy/data/js/sample.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
console.log(`sample.js included (${tab.name})`);
2-
// tab.worker.bus({a: 1, b: 2, c: 3});
2+
// tab.worker.bus("test", {a: 1, b: 2, c: 3});

runtimepy/net/server/app/env/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def populate_tabs(app: AppInfo, tabs: TabbedContent) -> None:
2929
tabs,
3030
icon="ethernet",
3131
markdown=conn.markdown,
32+
# buttons=,
3233
).entry()
3334

3435
# Task tabs.
@@ -40,6 +41,7 @@ def populate_tabs(app: AppInfo, tabs: TabbedContent) -> None:
4041
tabs,
4142
icon="arrow-repeat",
4243
markdown=task.markdown,
44+
# buttons=,
4345
js_uris=task.config.get("js_uris", []),
4446
).entry()
4547

@@ -52,6 +54,7 @@ def populate_tabs(app: AppInfo, tabs: TabbedContent) -> None:
5254
tabs,
5355
icon="bucket",
5456
markdown=struct.markdown,
57+
# buttons=,
5558
js_uris=struct.config.get("js_uris", []),
5659
).entry()
5760

runtimepy/net/server/app/env/tab/base.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
A module implementing a channel-environment tab HTML interface.
33
"""
44

5+
# built-in
6+
from io import StringIO
7+
from json import dumps
8+
from typing import NamedTuple
9+
510
# third-party
11+
from svgen.element import Element
12+
from svgen.element.html import div
13+
from vcorelib.dict import GenericStrDict
614
from vcorelib.io.markdown import MarkdownMixin
715
from vcorelib.logging import LoggerMixin
816
from vcorelib.math import RateLimiter
@@ -13,10 +21,60 @@
1321
ChannelCommandProcessor,
1422
)
1523
from runtimepy.net.arbiter.info import AppInfo
24+
from runtimepy.net.html.bootstrap import icon_str
1625
from runtimepy.net.html.bootstrap.tabs import TabbedContent
1726
from runtimepy.net.server.app.tab import Tab
1827

1928

29+
class ActionButton(NamedTuple):
30+
"""A class implementing an interface for action buttons."""
31+
32+
key: str
33+
payload: GenericStrDict
34+
text: str
35+
icon: str
36+
variant: str
37+
outline: bool
38+
39+
@staticmethod
40+
def from_dict(data: GenericStrDict) -> "ActionButton":
41+
"""Create an action button from dictionary data."""
42+
43+
return ActionButton(
44+
data["key"],
45+
data["payload"],
46+
data.get("text", ""),
47+
data.get("icon", ""),
48+
data.get("variant", "primary"),
49+
data.get("outline", True),
50+
)
51+
52+
def element(self) -> Element:
53+
"""Create an action button element."""
54+
55+
payload = dumps(self.payload).replace('"', """)
56+
57+
text_parts = []
58+
if self.icon:
59+
text_parts.append(
60+
icon_str(
61+
self.icon,
62+
[f"text-{self.variant}-emphasis"] if self.text else [],
63+
)
64+
)
65+
if self.text:
66+
text_parts.append(self.text)
67+
68+
return div(
69+
tag="button",
70+
type="button",
71+
onclick=f"tabs[shown_tab].worker.bus('{self.key}', {payload})",
72+
class_str=f"btn btn{'-outline' if self.outline else ''}"
73+
f"-{self.variant} m-2 ms-1 me-0",
74+
text=" ".join(text_parts),
75+
)
76+
77+
2078
class ChannelEnvironmentTabBase(Tab, LoggerMixin, MarkdownMixin):
2179
"""A channel-environment tab interface."""
2280

@@ -28,14 +86,61 @@ def __init__(
2886
tabs: TabbedContent,
2987
icon: str = "alarm",
3088
markdown: str = None,
89+
buttons: list[ActionButton] = None,
3190
**kwargs,
3291
) -> None:
3392
"""Initialize this instance."""
3493

94+
# Action buttons.
95+
if buttons is None:
96+
buttons = [
97+
###############################################################
98+
# REMOVE THESE
99+
###############################################################
100+
ActionButton.from_dict(
101+
{"icon": "tux", "key": "test", "payload": {}}
102+
),
103+
ActionButton.from_dict(
104+
{
105+
"icon": "tux",
106+
"key": "test",
107+
"payload": {},
108+
"text": "asdf",
109+
}
110+
),
111+
ActionButton.from_dict(
112+
{
113+
"icon": "tux",
114+
"key": "test",
115+
"payload": {},
116+
"outline": False,
117+
}
118+
),
119+
ActionButton.from_dict(
120+
{
121+
"icon": "tux",
122+
"key": "test",
123+
"payload": {},
124+
"text": "asdf",
125+
"outline": False,
126+
}
127+
),
128+
###############################################################
129+
]
130+
self.buttons: list[ActionButton] = buttons
131+
35132
self.command = command
36133
self.set_markdown(markdown=markdown, package=PKG_NAME)
37134
super().__init__(name, app, tabs, source="env", icon=icon, **kwargs)
38135

39136
# Logging.
40137
LoggerMixin.__init__(self, logger=self.command.logger)
41138
self.log_limiter = RateLimiter.from_s(1.0)
139+
140+
def _action_markdown(self) -> str:
141+
"""Get action-button markdown."""
142+
143+
with StringIO() as stream:
144+
for button in self.buttons:
145+
button.element().encode(stream)
146+
return stream.getvalue()

runtimepy/net/server/app/env/tab/html.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,10 @@ def compose(self, parent: Element) -> None:
289289
)
290290
logs.booleans.add("readonly")
291291

292-
if self.command.action_markdown:
292+
if self.buttons:
293293
centered_markdown(
294294
vert_container,
295-
self.command.action_markdown,
295+
self._action_markdown(),
296296
"border-start",
297297
"border-bottom",
298298
"border-end",

runtimepy/net/server/websocket/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ async def ui_handler(outbox: JsonMessage, inbox: JsonMessage) -> None:
9696

9797
# Handle bus messages.
9898
if "bus" in inbox:
99-
await BUS.send_ro("ui", inbox["bus"])
99+
await BUS.send_ro(inbox["key"], inbox["bus"])
100100
return
101101

102102
# Handle frame messages.

runtimepy/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
aiofiles
2-
vcorelib>=3.6.3
2+
vcorelib>=3.6.4
33
svgen>=0.8.0
44
websockets
55
psutil

tests/net/server/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ async def runtimepy_websocket_client(
2929
) -> None:
3030
"""Test client interactions via WebSocket."""
3131

32-
client.send_json({"ui": {"bus": {"a": 1}}})
32+
client.send_json({"ui": {"key": "test", "bus": {"a": 1}}})
3333

3434
send_ui(client, "test", {"a": 1, "b": 2, "c": 3})
3535

0 commit comments

Comments
 (0)