-
Notifications
You must be signed in to change notification settings - Fork 3
UI Editor Page
The biggest single page in the app. Where you actually design a show. Lives in ShowBuilder.jsx and a small village of supporting components under src/components/builder/.
📷 Screenshot placeholder: the Editor with a show open, the timeline mid-screen, the Target Grid sub-tab visible below.
Top to bottom:
- Show State Header — show identity, save controls, top-level metadata.
- Audio tracks + waveform — multi-track audio for pyromusicals.
- Timeline — drag/drop pyro items in time and to a zone:target.
- Sub-tabs — Target Grid, Racks, Test Show Builder, Show Layout.
Where you create / pick / rename / save the show.
Controls:
- + New show — opens a modal. Fill in name and authorization code. Returns a new show row in SQLite.
- Show selector — dropdown of every saved show. Pick one to load it into the editor.
- Show name — editable inline. Saved with the next Save.
- Authorization code — editable inline. The Console requires this to load the show; the Editor requires it (the original one, before edits) to save.
-
Stats panel — duration, item count, max simultaneous concurrency, total cost (sum of
inventory.unit_cost× references). -
Save — pushes a PATCH to
/api/shows/[id]. Will prompt for the auth code on first save in this session. Subsequent saves are silent. Auto-save kicks in after the first manual save: every 2 s of inactivity triggers a debounced PATCH if there are unsaved changes. - Clear — wipes the timeline (with confirmation).
📷 Screenshot placeholder: the show state header with stats.
For pyromusical support. Each show can have one or more audio tracks; the Editor uses WaveSurfer.js to render the waveform.
Controls:
- Track tabs — switch between tracks (e.g. main song + intro / outro).
-
Upload — POSTs a file to
/api/shows/upload-audio. The file is stored underpublic/uploads/audio/, and a reference ({ url, durationSec, ... }) is stored on the show row'saudio_fileJSON. - Play / pause / scrub — preview the audio.
-
BPM tools — auto-detect via
analyzeAudioFile(a built-in beat detector) or tap-tempo. Once you have a BPM, snap timeline items to beats. -
Track offset — move the audio relative to the show start (separate from the per-show
audioOffsetMsyou'd dial in on the Console). - Sequential playback toggle — if you have multiple tracks, play them back-to-back during preview rather than overlapping.
The waveform is the primary alignment surface — drag items below it to align cues with musical hits.
A horizontal time-axis (in seconds) with rows for each receiver/zone in the show. Items are placed onto rows; their horizontal position = startTime, their length = duration.
📷 Screenshot placeholder: timeline with several items spread across two zone rows.
- Click + Add item at any time on any row →
AddItemModal. - Pick an inventory item. Set zone, target, optional fuse delay overrides.
- For shells (with lift delay), the visible item start =
startTime, but the actual cue fire time =startTime - lift_delay. The daemon stores the cue time, not the visible time.
- Click an item to open its edit modal. Change inventory reference, zone/target, timing, color overrides, etc.
- Drag an item horizontally to reposition (snaps to grid; hold a modifier to free-move).
- Drag vertically to a different zone row.
- Multi-select with Cmd/Ctrl-click → Chain timing modal lets you align delays between selected items.
- Copy item — pick a source item, then click anywhere on the timeline to drop a clone (zone/target asked in a modal first).
The major timeline item types:
| Type | What it represents | Special editing |
|---|---|---|
SHELL |
Single aerial shell. | Just cue + visual time. |
CAKE_FOUNTAIN / CAKE_200G / CAKE_500G
|
Multi-shot cake. | Pulls shot timestamps from the cake's firing profile (if any). |
FUSED_LINE |
A chain of items on the same cue, fired in sequence by physical fuse. | Built in FusedItemLineBuilderModal — pick items, their delays, their relative times. |
FUSED_SHELL_LINE |
A chain of shells on the same cue, fused. |
FusedLineBuilderModal — pick shells, lead-in inches, inter-shell delays. |
RACK_SHELLS |
One cue lights a fuse that propagates through cells of a rack. | Built from a rack defined in the Racks sub-tab; uses RackShellsSelector to pick fired cells. |
For cakes and fused lines, the firing profile (from YouTube analysis) lets the timeline visualize each individual shot inside the multi-shot item — useful for music alignment. See Inventory page.
Each item ends up in Show.display_payload as an object with this shape (compressed):
{
"id": "uuid",
"type": "SHELL",
"startTime": 12.5,
"duration": 0.6,
"delay": 1.5,
"zone": "RX146",
"target": 1,
"inventoryId": 42,
"color": "red"
}(Plus type-specific fields like rackId, rackCells, fireableItem for racks; chainItems for fused lines.)
The daemon's process_display_payload later transforms each item into a firing-array entry: { startTime: startTime - delay, zone, target, id }. So the visible item's left edge in the timeline = the visual effect time; the daemon fires earlier by delay to make the visual happen on time.
Below the timeline are four sub-tabs.
A 2D editor showing each receiver in the show as a row, with a column per cue. Click a cell to add/remove an item from that cue. Drag items between cells to reassign cues.
-
Add receiver opens
ShowReceiverModal: pick a receiver from the global list, give it a label, mark which cues it controls in this show. - Edit receiver changes label or cue count.
- Remove receiver is gated — refuses if there are items still pointing at this receiver.
-
Migrate zones — for renaming or restructuring; updates every item's
zone:targetin one go.
The Target Grid is the canonical target view — Show.show_receivers JSON column. The Receivers page in the global nav is a different concept (the global Receivers SQL table — what receivers physically exist).
Per-show rack design. See RacksTab.jsx.
- + Rack — give the rack a name and dimensions (X rows × Y rows, plus spacings in inches).
- Rack tabs — multiple racks can exist per show.
-
Resize —
RackResizeModalwith a destructive-changes preview (it'll tell you which cells / fuses /RACK_SHELLSitems will be invalidated). - Clone / Delete — copy or remove the rack.
Inside a rack, the RackGrid.jsx editor lets you:
- Shells mode — assign an inventory shell to each cell. Click a cell, pick from the shell list, optionally pick which "shell number" of a shell pack it is.
- Fusing mode — draw fuse chains. Pick a fuse type from inventory, set the lead-in (inches from ignition source to first cell), then click cells in the order the fuse propagates.
Once a rack has cells with shells and (optionally) fuses, you can reference it from the timeline as a RACK_SHELLS item — one cue ignites the rack's lead-in, and the fuse propagates to fire each shell at calculated delays from the rack geometry and fuse burn rate.
Synthetic test patterns for confirming hardware works. Generates timed sequences across all assigned cues for things like:
- Cycle-test (fire each cue once in sequence)
- All-at-once (worst-case concurrency stress test)
- Custom interval
Useful for shop testing without writing a "real" show.
Satellite-map view (SpatialLayoutMap.jsx using Leaflet + Esri imagery). Place each receiver pin geographically, draw safety rings around them (computed from product metadata.safety_distance_ft if present), and view the inter-receiver distances.
-
Address Search (
AddressSearch.jsx) — geocode an address via Nominatim, or paste alat,lng, or use the browser's geolocation API. - Drag pins to position receivers.
-
Save Locations persists
receiver_locationsJSON on the show row.
This data is also used by the Loadout page to print a packing map.
The first save of a session prompts for the show's authorization code. The auth code prevents accidental edits to the wrong show — you have to know it. After a successful save, the editor caches the code in memory and saves silently (auto-save) until you reload the page.
If you forget the code, you have to either dig it out of the SQL row directly or delete the show.
src/components/builder/
├── ShowBuilder.jsx # everything binds here
├── ShowStateHeader.jsx # header + stats
├── Timeline.jsx # the timeline (also used in Console)
├── AudioWaveform.jsx # WaveSurfer integration
├── AudioTrackTabs.jsx # multi-track UI
├── TrackPlayer.jsx # play/pause for a track
├── AddItemModal.jsx
├── ChainTimingModal.jsx # multi-select alignment
├── CopyItemModal.jsx
├── RacksTab.jsx
├── RackGrid.jsx
├── RackResizeModal.jsx
├── RackShellsSelector.jsx # pick a fireable rack region
├── FuseModal.jsx # pick fuse type + lead-in
├── FusedLineBuilderModal.jsx # FUSED_SHELL_LINE
├── FusedItemLineBuilderModal.jsx # FUSED_LINE
├── CellShellSelector.jsx
├── ShowTargetGrid.jsx
├── ShowReceiverModal.jsx
├── SpatialLayoutMap.jsx
├── SatelliteMap.jsx # Leaflet wrapper
├── AddressSearch.jsx # Nominatim
└── ItemPreview.jsx
Getting started
- Overview
- Desktop installers (macOS / Windows)
- macOS
- Linux
- Windows
- Production vs Development
- Connecting the dongle
- Flash a receiver
- Flash a dongle
- OTA flashing
Raspberry Pi
System overview
Subsystems
Hardware
- Receiver firmware
- Dongle firmware
- RF protocol
- Contributor Portal — BOMs, schematics, and board resources
UI walkthrough
Reference
Downloads
- Firmware
- Installers
Module Build & User Guides
- Cue
- Receiver
- Dongle