Skip to content

Commit 3422cb8

Browse files
authored
docs: add Diataxis-structured documentation (#10)
Add documentation following the Diataxis framework: **Tutorials:** - `01-your-first-dashboard.md` - Getting started with BB.LiveView **How-to Guides:** - `customise-dashboard-layout.md` - Creating custom dashboard layouts **Other changes:** - Update mix.exs with `groups_for_extras` for ExDoc - Update README.md with links to new documentation
1 parent d3a4793 commit 3422cb8

4 files changed

Lines changed: 453 additions & 2 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,7 @@ Visit `http://localhost:4000` to see the dashboard with a simulated WidowX-200 s
222222

223223
## Documentation
224224

225-
Full documentation is available at [HexDocs](https://hexdocs.pm/bb_liveview).
225+
- [Your First Dashboard](https://hexdocs.pm/bb_liveview/01-your-first-dashboard.html) - getting started tutorial
226+
- [Customise Dashboard Layout](https://hexdocs.pm/bb_liveview/customise-dashboard-layout.html) - creating custom layouts
227+
228+
Full API documentation is available at [HexDocs](https://hexdocs.pm/bb_liveview).
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 James Harton
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
-->
6+
7+
# How to Customise Dashboard Layout
8+
9+
Create a custom LiveView that uses BB.LiveView components with your own layout.
10+
11+
## Prerequisites
12+
13+
- Completed [Your First Dashboard](../tutorials/01-your-first-dashboard.md)
14+
- Understanding of Phoenix LiveView
15+
16+
## Step 1: Create a Custom LiveView
17+
18+
Instead of using the built-in dashboard, create your own LiveView that uses individual components:
19+
20+
```elixir
21+
# lib/my_app_web/live/robot_live.ex
22+
defmodule MyAppWeb.RobotLive do
23+
use MyAppWeb, :live_view
24+
25+
alias BB.LiveView.Components.{Safety, JointControl, Visualisation, EventStream}
26+
27+
@impl true
28+
def mount(_params, _session, socket) do
29+
robot_module = MyRobot
30+
robot = robot_module.robot()
31+
32+
# Subscribe to robot events
33+
if connected?(socket) do
34+
BB.subscribe(robot_module, [:state_machine])
35+
BB.subscribe(robot_module, [:safety])
36+
BB.subscribe(robot_module, [:sensor])
37+
end
38+
39+
{:ok, assign(socket,
40+
robot_module: robot_module,
41+
robot: robot,
42+
positions: initial_positions(robot)
43+
)}
44+
end
45+
46+
@impl true
47+
def render(assigns) do
48+
~H"""
49+
<div class="grid grid-cols-3 gap-4 p-4">
50+
<div class="col-span-1">
51+
<.live_component
52+
module={Safety}
53+
id="safety"
54+
robot_module={@robot_module}
55+
robot={@robot}
56+
/>
57+
</div>
58+
59+
<div class="col-span-2">
60+
<.live_component
61+
module={Visualisation}
62+
id="visualisation"
63+
robot_module={@robot_module}
64+
robot={@robot}
65+
/>
66+
</div>
67+
68+
<div class="col-span-3">
69+
<.live_component
70+
module={JointControl}
71+
id="joints"
72+
robot_module={@robot_module}
73+
robot={@robot}
74+
/>
75+
</div>
76+
</div>
77+
"""
78+
end
79+
80+
@impl true
81+
def handle_info({:bb, [:sensor | path], %{payload: joint_state}}, socket) do
82+
joint_name = List.last(path)
83+
positions = Map.put(socket.assigns.positions, joint_name, hd(joint_state.positions))
84+
85+
send_update(Visualisation, id: "visualisation", event: {:positions_updated, positions})
86+
send_update(JointControl, id: "joints", event: {:positions_updated, positions})
87+
88+
{:noreply, assign(socket, positions: positions)}
89+
end
90+
91+
def handle_info({:bb, [:state_machine], %{payload: transition}}, socket) do
92+
send_update(Safety, id: "safety", event: {:state_changed, transition.to})
93+
{:noreply, socket}
94+
end
95+
96+
def handle_info(_msg, socket), do: {:noreply, socket}
97+
98+
defp initial_positions(robot) do
99+
robot.joints
100+
|> Map.keys()
101+
|> Map.new(fn name -> {name, 0.0} end)
102+
end
103+
end
104+
```
105+
106+
## Step 2: Route to Your LiveView
107+
108+
Add a route to your custom LiveView:
109+
110+
```elixir
111+
# lib/my_app_web/router.ex
112+
scope "/", MyAppWeb do
113+
pipe_through :browser
114+
live "/robot", RobotLive
115+
end
116+
117+
# Still need asset routes for JS/CSS
118+
import BB.LiveView.Router
119+
scope "/" do
120+
bb_dashboard "/__bb_dashboard__", MyRobot # Hidden route just for assets
121+
end
122+
```
123+
124+
Alternatively, serve assets manually by adding the static plug:
125+
126+
```elixir
127+
# lib/my_app_web/endpoint.ex
128+
plug BB.LiveView.Plugs.Static
129+
```
130+
131+
## Step 3: Handle Component Events
132+
133+
Components communicate through events. Handle them in your LiveView:
134+
135+
```elixir
136+
@impl true
137+
def handle_info({:joint_position_changed, positions}, socket) do
138+
# Slider was moved - update visualisation immediately
139+
send_update(Visualisation, id: "visualisation", event: {:positions_updated, positions})
140+
{:noreply, socket}
141+
end
142+
143+
def handle_info({:command_result, result}, socket) do
144+
# Command completed - show notification
145+
{:noreply, put_flash(socket, :info, "Command result: #{inspect(result)}")}
146+
end
147+
```
148+
149+
## Step 4: Select Components
150+
151+
Use only the components you need:
152+
153+
```elixir
154+
# Minimal control interface
155+
def render(assigns) do
156+
~H"""
157+
<div class="flex gap-4">
158+
<.live_component module={Safety} id="safety" robot_module={@robot_module} robot={@robot} />
159+
<.live_component module={JointControl} id="joints" robot_module={@robot_module} robot={@robot} />
160+
</div>
161+
"""
162+
end
163+
164+
# Monitoring only (no control)
165+
def render(assigns) do
166+
~H"""
167+
<div class="grid grid-cols-2 gap-4">
168+
<.live_component module={Visualisation} id="vis" robot_module={@robot_module} robot={@robot} />
169+
<.live_component module={EventStream} id="events" robot_module={@robot_module} />
170+
</div>
171+
"""
172+
end
173+
```
174+
175+
## Available Components
176+
177+
| Component | Module | Purpose |
178+
|-----------|--------|---------|
179+
| Safety | `BB.LiveView.Components.Safety` | Arm/disarm controls |
180+
| JointControl | `BB.LiveView.Components.JointControl` | Position sliders |
181+
| Visualisation | `BB.LiveView.Components.Visualisation` | 3D view |
182+
| EventStream | `BB.LiveView.Components.EventStream` | Message monitor |
183+
| Command | `BB.LiveView.Components.Command` | Command forms |
184+
| Parameters | `BB.LiveView.Components.Parameters` | Parameter editor |
185+
186+
## Component Props
187+
188+
All components require:
189+
- `id` - Unique identifier for LiveComponent
190+
- `robot_module` - Your robot module (e.g., `MyRobot`)
191+
- `robot` - The compiled robot struct from `robot_module.robot()`
192+
193+
## Styling
194+
195+
Components use Tailwind CSS classes. Override styles by:
196+
197+
1. Adding custom CSS after the BB assets
198+
2. Using CSS specificity to override defaults
199+
3. Wrapping components in custom containers
200+
201+
```heex
202+
<div class="my-custom-wrapper">
203+
<.live_component module={Safety} id="safety" ... />
204+
</div>
205+
```
206+
207+
## Common Patterns
208+
209+
### Responsive Layout
210+
211+
```heex
212+
<div class="flex flex-col lg:flex-row gap-4">
213+
<div class="lg:w-1/3">
214+
<.live_component module={Safety} ... />
215+
<.live_component module={JointControl} ... />
216+
</div>
217+
<div class="lg:w-2/3">
218+
<.live_component module={Visualisation} ... />
219+
</div>
220+
</div>
221+
```
222+
223+
### Tabbed Interface
224+
225+
```heex
226+
<div>
227+
<div class="tabs">
228+
<button phx-click="tab" phx-value-tab="control">Control</button>
229+
<button phx-click="tab" phx-value-tab="monitor">Monitor</button>
230+
</div>
231+
232+
<%= case @tab do %>
233+
<% "control" -> %>
234+
<.live_component module={JointControl} ... />
235+
<% "monitor" -> %>
236+
<.live_component module={EventStream} ... />
237+
<% end %>
238+
</div>
239+
```
240+
241+
## Troubleshooting
242+
243+
### Components not updating
244+
245+
Ensure you're:
246+
1. Subscribed to the correct PubSub channels
247+
2. Forwarding events with `send_update/3`
248+
3. Using unique component IDs
249+
250+
### 3D visualisation not rendering
251+
252+
Check that:
253+
1. The Three.js assets are being served
254+
2. The component has a container with height
255+
3. WebGL is available in the browser

0 commit comments

Comments
 (0)